latam-market
Enables querying item prices, offers, and market analysis for the LATAM Ragnarok Online server (FREYA) via an MCP interface.
README
latam-market
API e servidor MCP para o mercado de jogadores do Ragnarok Online LATAM (FREYA e NIDHOGG). Os mesmos dados por dois canais: HTTP para aplicações, MCP para agentes de IA.
🔗 https://mercado.latam-tools.com.br
Interface → https://mercado.latam-tools.com.br
API REST → https://mercado.latam-tools.com.br/api/v1
MCP → https://mercado.latam-tools.com.br/mcp
Sem cadastro, sem chave de API, sem login. Licença MIT.
O que dá para perguntar
Conectado a um agente (Claude, por exemplo), o MCP responde coisas assim:
"Quanto custa um Elixir Dourado?" "Quero comprar uma Boina — me lista quem está vendendo e por quanto." "Vender minha Escama Invertida por 500 mil é bom negócio?" "O preço da Ração Luxuosa subiu esse mês?" "Tem alguma pechincha no mercado agora?" "Toma meu replay: quanto vale tudo que eu tenho? O que dá para vender com lucro?"
Recursos
- Preço de qualquer item — a faixa histórica publicada pelo site e o resumo das lojas abertas na última coleta, que são coisas diferentes e vêm separadas.
- Ofertas ativas — quem está vendendo, por quanto, em que loja e em que mapa.
- Histórico próprio — construído acumulando nossas coletas, com retenção em camadas (anúncio cru por um dia, resumo por 30 dias, resumo diário para sempre).
- Avaliação de preço — quantas lojas estão mais baratas, quanto pedir para ser o mais barato, e como o preço se compara à média dos últimos dias.
- Pechinchas e maiores variações — varreduras sobre o mercado inteiro.
- Leitura de replay
.rrf— inventário, carrinho de mercador e equipamento, com preço de mercado em cada item. - Consulta ao vivo — quando o dado coletado não basta, consulta o site na hora. É o caminho caro, e está marcado como tal.
- Links prontos — todo item vem com a página no Divine Pride e a busca ao vivo no site oficial.
Interface web
Em https://mercado.latam-tools.com.br dá para usar tudo isso sem escrever uma linha
de comando: sobe o .rrf, e a tabela mostra item por item o que ele vale, com ícone,
descrição no hover, filtro por origem (mochila, carrinho, equipado), botão para esconder
os intransferíveis, exportação em CSV e links para o Divine Pride e para o mercado
oficial. Tem também busca, pechinchas, maiores variações e o estado das coletas.
A interface é servida pelo mesmo domínio da API — arquivos estáticos pelo Caddy, com
/api/*, /mcp e /healthz indo para o serviço Node. Código e instruções de
desenvolvimento em web/.
Usando a API
Toda rota aceita ?server=FREYA|NIDHOGG. Sem o parâmetro, responde FREYA; um valor
desconhecido é 400, e não o padrão em silêncio.
# buscar um item
curl 'https://mercado.latam-tools.com.br/api/v1/items?q=elixir'
# o mesmo item no outro servidor
curl 'https://mercado.latam-tools.com.br/api/v1/items/501?server=NIDHOGG'
# preço (aceita id ou nome)
curl 'https://mercado.latam-tools.com.br/api/v1/items/Elixir%20Dourado'
# quem está vendendo
curl 'https://mercado.latam-tools.com.br/api/v1/items/1100005/offers?limit=10'
# vale a pena vender a 2600?
curl 'https://mercado.latam-tools.com.br/api/v1/items/1100005/appraise?price=2600'
# avaliar um replay
curl -X POST --data-binary @replay.rrf \
-H 'content-type: application/octet-stream' \
https://mercado.latam-tools.com.br/api/v1/replay
| Rota | O que faz |
|---|---|
GET /api/v1/items?q= |
Busca por nome (sem acento, sem caixa) |
GET /api/v1/items/:item |
Preço, ofertas e resumo. :item é id ou nome |
GET /api/v1/items/:item/offers |
Lojas vendendo agora, da mais barata para a mais cara |
GET /api/v1/items/:item/history?days=30 |
Série histórica |
GET /api/v1/items/:item/appraise?price=N |
Avaliação de um preço |
GET /api/v1/movers?days=7&dir=up |
Maiores variações |
GET /api/v1/deals?min_discount=25 |
Itens bem abaixo do usual |
GET /api/v1/snapshots |
Coletas recentes |
GET /api/v1/status |
Idade dos dados |
POST /api/v1/replay |
Envie o .rrf no corpo; devolve os itens precificados |
POST /api/v1/live/items/:item/refresh |
Consulta ao vivo (5/min por IP) |
GET /healthz |
Saúde do serviço |
Quando um nome casa com vários itens, a resposta é 300 com a lista de candidatos — o serviço não escolhe por você.
Conectando o MCP
Em clientes com suporte a MCP remoto (Claude Desktop, Claude Code):
{
"mcpServers": {
"mercado-ro": {
"type": "http",
"url": "https://mercado.latam-tools.com.br/mcp"
}
}
}
| Ferramenta | Para quê |
|---|---|
search_items |
Achar o id de um item pelo nome |
get_price |
Quanto custa |
list_offers |
Quem está vendendo |
price_history |
Como o preço se comportou |
appraise_price |
Se um preço é bom |
top_movers |
O que subiu ou caiu |
find_deals |
Pechinchas |
value_inventory |
Precificar um replay .rrf (envie em base64) |
data_status |
De quando são os dados |
refresh_live |
Consultar o site ao vivo — caro, só sob pedido explícito |
⚠️
refresh_livegasta uma requisição ao site e pode ser bloqueada por limite de taxa. As descrições das ferramentas dizem isso ao agente, para ele preferir os dados já coletados — que costumam ter menos de uma hora.
Como funciona
┌──────────────────────────────────┐
Caddy ──── :8788 ──│ API REST ─┐ │
│ ├─→ core/ ─→ cache │
│ MCP ─┘ ↑ │
└───────────────────────│──────────┘
│
SQLite ←──── worker do crawl
│
EgressPool (lanes)
│
ro.gnjoylatam.com
Um processo só. api/ e mcp/ são casca fina sobre core/ e não podem ler o
banco direto — é isso que garante que os dois canais respondam a mesma coisa, e há um
teste de paridade que quebra se alguém contornar.
O mercado inteiro cabe na memória (~5 mil itens com preço, ~20 mil anúncios), então nenhuma leitura toca o disco. O SQLite existe para o histórico e para sobreviver a reinício.
Coleta e rotação de IP
O site limita a ~50 requisições por minuto por IP e por endpoint, e o bloqueio dura mais de cinco minutos. A saída é ter várias identidades de rede.
Cada "lane" é uma conta Cloudflare WARP anônima dentro da sua própria network
namespace do Linux. Isso importa porque o servidor é compartilhado: um
warp-cli connect trocaria a rota da máquina inteira e derrubaria as conexões dos
outros serviços a cada rotação. Dentro da namespace, a rota do host nunca é tocada.
Duas outras decisões que caem daí:
- A rotação é fora-de-lugar. A saída nova é criada e publicada antes de a antiga ser encerrada, e o encerramento espera as requisições em andamento terminarem. Trocar no lugar mataria justamente as requisições que provocaram o bloqueio.
- A saída direta é reservada para consultas ao vivo. O crawl roda só nas lanes de túnel, então um bloqueio na coleta nunca deixa um usuário esperando — e vice-versa.
Detalhes de medição e de operação do coletor estão em docs/scraper.md.
Rodando localmente
Precisa de Node 22.5+ (por causa do node:sqlite) e pnpm.
pnpm install
pnpm import --run-id full # carrega os NDJSON já coletados no SQLite
pnpm dev # sobe em http://127.0.0.1:8788
Sem dados em data/raw/, o serviço sobe com o mercado vazio — colete primeiro com
pnpm crawl (veja docs/scraper.md).
| Comando | O que faz |
|---|---|
pnpm dev |
Servidor com recarga automática |
pnpm crawl |
Coleta manual do mercado |
pnpm import |
Importa NDJSON para o SQLite |
pnpm report |
Relatório de cobertura do coletor |
pnpm test |
Testes |
pnpm typecheck |
Tipos |
pnpm build |
Bundle de produção em dist/ |
pnpm sync:items |
Atualiza o catálogo a partir do latam-ro-calc |
Variáveis úteis: PORT, DB_PATH, DATA_DIR, EGRESS_LANES, CRAWL_ENABLED,
ALLOWED_HOSTS, ALLOWED_ORIGINS (veja src/server/config.ts).
Estrutura
src/
scraper/ coletor: cobertura de termos, cliente HTTP, extração do payload RSC
egress/ pool de saídas de rede, lanes e rotação de IP
store/ SQLite, cache quente e retenção
replay/ leitura de arquivos .rrf (inventário, carrinho, equipamento)
core/ a lógica de mercado — a única camada que API e MCP enxergam
api/ rotas REST
mcp/ ferramentas MCP
server/ processo HTTP
worker/ coleta periódica em worker thread
cli/ comandos de linha
web/ interface web (React + Vite), servida estática pelo Caddy
infra/ systemd, Caddy e provisionamento das lanes
docs/ documentação do coletor
Deploy
São dois fluxos, um por push na main.
Serviço (deploy.yml, sem filtro de caminho): typecheck, testes, bundle com
esbuild, envio por scp para o EC2, rsync em /opt/latam-market e reinício do
systemd. A verificação bate no /healthz, faz uma chamada MCP real e uma consulta
REST — se qualquer uma falhar, o deploy falha.
Interface (web-deploy.yml, disparado por web/** e pelo catálogo): typecheck,
testes, build do Vite e rsync em /opt/latam-market-web. Não reinicia nem recarrega
nada — arquivo estático não é configuração. A verificação confere que a interface subiu
e que a API, o MCP e o /healthz continuam respondendo, porque os dois dividem o
mesmo bloco do Caddy.
O banco fica em /var/lib/latam-market/market.db, fora de /opt, porque o deploy
usa rsync --delete e levaria o histórico junto.
Arquivos de infraestrutura em infra/, com o provisionamento manual
necessário documentado no topo de cada um.
Limites conhecidos
- Só o lado de quem está vendendo (
storeType=BUY;SELLnão retorna nada nesse site). FREYA e NIDHOGG são coletados na mesma cadência. - O histórico de NIDHOGG começa agora. FREYA acumula desde o início do projeto; o outro servidor entrou depois, então pechinchas e variações só ficam úteis lá quando houver alguns dias de coleta.
- Refino, cartas e bônus aleatórios não são precificados. O mercado agrega por id de item, então uma arma +9 encantada aparece com o preço da arma base — as respostas avisam quando é o caso, em vez de fingir precisão.
- O histórico começa quando começamos a coletar. O site não publica série temporal; o que ele dá é um agregado acumulado, e o resto é medição nossa.
- O retrato de um replay é do início da gravação. Itens pegos ou gastos durante a gravação não aparecem.
- Alguns containers de item do
.rrfainda não foram identificados (provavelmente armazém da Kafra). Eles vêm à parte, emunidentified, e não entram no total.
Créditos
Catálogo de itens em pt-BR do projeto irmão
latam-ro-calc (simulador de dano). A leitura de .rrf
é portada de lá, que por sua vez segue o
Rrf-Parser do Tokeiburu.
Projeto não-oficial, feito para a comunidade. Sem vínculo com a Gravity ou a Gnjoy.
Licença
MIT.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.