Ganjoor MCP
o servidor MCP para a API de poesia persa Ganjoor. Ele permite que um agente navegue por poetas e coleções, leia poemas com seus versos, execute análises de prosódia e rima, rastreie poemas-resposta entre poetas, consulte recitações e comentários, e explore os grafos de pessoas relacionadas e árvore genealógica.
Documentação
English | فارسی
Ganjoor MCP Server — o servidor do Model Context Protocol para a API de poesia persa do Ganjoor.
Ele permite que um agente navegue por poetas e coleções, leia poemas com seus versos, execute análises de prosódia e rima, rastreie poemas-resposta entre poetas, consulte recitações e comentários, e explore os grafos de pessoas relacionadas e árvore genealógica.
44 ferramentas, acesso somente leitura. Nenhuma chave de API necessária.
Conteúdo
- Instalação
- Docker
- Configuração
- Conceitos
- Referência de ferramentas
- Formatos de resposta
- Uso
- Arquitetura
- Desenvolvimento
- Limitações conhecidas
- Avaliação
- Contribuição
Instalação
Requer Node.js 18.17 ou mais recente.
npm install ganjoor-mcp-server # for locally installed stdio transport. no need if using VS Code or Claude Desktop / Cursor.
VS Code
Não é necessário instalar o pacote npm — o servidor MCP roda em processo e é iniciado pelo VS Code quando necessário.
Basta clicar no selo abaixo para instalar o servidor Ganjoor MCP no seu workspace do VS Code. Ele
adicionará automaticamente o servidor à sua configuração .vscode/mcp.json.
Ou adicione ao .vscode/mcp.json:
{
"servers": {
"io.github.ehsanghaffar/ganjoor-mcp-server": {
"command": "npx",
"args": [
"-y",
"ganjoor-mcp-server"
],
"env": {}
}
}
}
CLI do VS Code:
code --add-mcp '{\"name\":\"io.github.ehsanghaffar/ganjoor-mcp-server\",\"command\":\"npx\",\"args\":[\"-y\",\"ganjoor-mcp-server\"],\"env\":{}}'
Claude Desktop / Cursor
Basta clicar no selo abaixo para instalar o servidor Ganjoor MCP no seu aplicativo Claude Desktop ou Cursor. Ele adicionará automaticamente o servidor à sua configuração MCP.
Ou adicione ao seu arquivo de configuração MCP:
{
"mcpServers": {
"io.github.ehsanghaffar/ganjoor-mcp-server": {
"command": "npx",
"args": [
"-y",
"ganjoor-mcp-server"
],
"env": {},
"type": "local",
"tools": [
"*"
]
}
}
}
Outros clientes
Claude Code
Use a CLI do Claude Code para adicionar o servidor MCP io.github.ehsanghaffar/ganjoor-mcp-server:
claude mcp add io.github.ehsanghaffar/ganjoor-mcp-server npx -y ganjoor-mcp-server
Claude Desktop
Siga o guia de instalação do MCP, use a configuração padrão acima.
Codex
Crie ou edite o arquivo de configuração ~/.codex/config.toml e adicione:
[mcp_servers.ganjoor-mcp-server]
command = "npx"
args = ["-y", "ganjoor-mcp-server"]
Para mais informações, consulte a documentação do MCP do Codex.
Gemini CLI
Siga o guia de instalação do MCP, use a configuração padrão acima.
OpenCode
Siga a documentação de Servidores MCP. Por exemplo, em ~/.config/opencode/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"io.github.ehsanghaffar/ganjoor-mcp-server": {
"type": "local",
"command": [
"npx",
"-y",
"ganjoor-mcp-server"
],
"enabled": true
}
}
}
GitHub Copilot CLI
O GitHub Copilot CLI suporta adicionar servidores MCP interativamente e por meio de um arquivo de configuração.
Opção 1: Configuração interativa com /mcp add
- Abra o GitHub Copilot CLI no modo interativo.
- Execute
/mcp add. - Digite Nome do Servidor:
io.github.ehsanghaffar/ganjoor-mcp-server. - Escolha Tipo de Servidor:
STDIOpara servidores baseados em comandoHTTPpara servidores remotos
- Preencha as configurações de comando/url da configuração padrão acima e defina Ferramentas como
*. - Pressione
Ctrl+Spara salvar.
Opção 2: Editar ~/.copilot/mcp-config.json
{
"mcpServers": {
"io.github.ehsanghaffar/ganjoor-mcp-server": {
"command": "npx",
"args": [
"-y",
"ganjoor-mcp-server"
],
"env": {},
"type": "local",
"tools": [
"*"
]
}
}
}
Para mais informações, consulte a documentação do MCP do GitHub Copilot CLI.
GitHub Copilot Coding Agent
O GitHub Copilot Coding Agent pode usar servidores MCP para estender suas capacidades. Use a configuração abaixo formatada especificamente para o Coding Agent:
{
"mcpServers": {
"ganjoor-mcp-server": {
"command": "npx",
"args": [
"-y",
"ganjoor-mcp-server"
],
"env": {},
"type": "local",
"tools": [
"*"
]
}
}
}
Adicione esta configuração às configurações do seu repositório em Copilot > Coding agent. A configuração "tools": ["*"] habilita todas as ferramentas disponíveis do servidor MCP.
Para mais informações, consulte a documentação do MCP do GitHub Copilot Coding Agent.
Instalar a partir desta fonte
npm install
npm run build
npm start # stdio transport (default)
VS Code
Adicione ao .vscode/mcp.json:
{
"servers": {
"ganjoor": {
"type": "stdio",
"command": "node",
"args": ["${workspaceFolder}/dist/index.js"]
}
}
}
Claude Desktop / Cursor
Adicione ao seu arquivo de configuração MCP:
{
"mcpServers": {
"ganjoor": {
"command": "node",
"args": ["/absolute/path/to/ganjoor-mcp/dist/index.js"]
}
}
}
HTTP Streamable (implantação remota / compartilhada)
HTTP=true PORT=3000 npm start
O endpoint MCP é POST /mcp, e GET /health relata o status do servidor e do upstream. O transporte
HTTP roda sem estado (uma nova instância do servidor por requisição), então ele escala horizontalmente. Ele vincula
127.0.0.1 por padrão e valida o cabeçalho Origin para bloquear ataques de rebinding de DNS; defina
ALLOWED_ORIGINS (separado por vírgulas) se você precisar de origens não-loopback.
MCP Inspector
npm run inspector
Docker
Um Dockerfile multi-estágio e um docker-compose.yml estão incluídos. A imagem é construída sobre
node:22-alpine e contém apenas a saída compilada e as dependências de produção.
docker compose up --build
O arquivo compose inicia o servidor no modo HTTP streamable na porta http://localhost:3000/mcp, com
GET /health relatando o status do servidor e do upstream. HOST está definido como 0.0.0.0 para que a porta publicada
seja alcançável a partir do host; toda variável de configuração da tabela abaixo funciona como uma variável de ambiente
comum. Para executar um único contêiner sem compose:
docker build -t ganjoor-mcp .
docker run --rm -p 3000:3000 -e HTTP=true -e HOST=0.0.0.0 ganjoor-mcp
Transporte stdio via Docker
Clientes que iniciam o servidor por conta própria (em vez de conectar via HTTP) podem usar o perfil stdio,
que abre stdin para o fluxo do protocolo e não inicia nenhum listener HTTP:
docker compose --profile stdio run --rm ganjoor-mcp-stdio
Com Claude Desktop
Para executar o servidor via Docker em vez de uma instalação Node local:
{
"mcpServers": {
"ganjoor": {
"command": "docker",
"args": ["run", "--rm", "-i", "ganjoor-mcp"]
}
}
}
Configuração
Todas as configurações são variáveis de ambiente; nenhuma é obrigatória.
| Variável | Padrão | Finalidade |
|---|---|---|
HTTP | (não definido) | Defina como true para servir HTTP streamable em vez de stdio. |
PORT | 3000 | Porta HTTP. |
HOST | 127.0.0.1 | Endereço de vinculação HTTP. |
ALLOWED_ORIGINS | (vazio) | Valores adicionais permitidos de Origin, separados por vírgulas. |
GANJOOR_API_BASE_URL | https://api.ganjoor.net | URL base da API upstream — aponte para uma instância RMuseum local para desenvolvimento. |
GANJOOR_SITE_BASE_URL | https://ganjoor.net | Base do front-end usada para construir links clicáveis. |
Conceitos
O modelo de dados do Ganjoor tem três níveis aninhados. Entender isso é a diferença entre uma sessão de agente produtiva e uma infrutífera.
graph TD
P["Poet — e.g. Hafez, id 2, slug /hafez"] --> C1["Category — e.g. Ghazals, id 24"]
P --> C2["Category — e.g. Qeta, id 25"]
C1 --> M1["Poem — e.g. Ghazal 494, id 2623, /hafez/ghazal/sh494"]
C2 --> M2["Poem"]
M1 --> V["Verses (hemistichs, paired into couplets)"]
M1 --> S["Sections (nested stanzas with metre + rhyme)"]
M1 --> R["Recitations (audio)"]
M1 --> CM["Comments (reader commentary)"]
M1 --> Q["Quoted / related poems"]
- Poeta — um autor. Hafez é o id de poeta
2, Saadi7, Rumi5, Khayyam3, Ferdowsi4.ganjoor_list_poetsresolve um nome para um id. - Categoria — um livro ou seção dentro da obra de um poeta (CatType
0geral,1poemas,2livro). A categoria raiz de Hafez é9; seus ghazals são24. - Poema — um texto, identificado por um id numérico e um slug de URL como
hafez/ghazal/sh494. - Verso — um hemistíquio. Dois versos consecutivos formam um
couplet(versePosition0 e 1).
Slugs de URL precisam de uma barra inicial. A API upstream retorna 404 para
?url=hafez/ghazal/sh494mas resolve?url=/hafez/ghazal/sh494. Este servidor normaliza para você, então qualquer uma das formas funciona.
O texto persa é retornado verbatim. Ele é da direita para a esquerda com diacríticos de escrita árabe usados para prosódia. Não translitere ou traduza, a menos que o usuário peça explicitamente.
Referência de ferramentas
Poetas e coleções
| Ferramenta | Descrição |
|---|---|
ganjoor_list_poets | Todos os poetas publicados com biografia, datas, locais. Paginado. |
ganjoor_get_poet | Um poeta por id ou slug, com biografia e livros/seções de nível superior. |
ganjoor_get_poets_by_century | Todos os poetas agrupados em meio-séculos históricos. |
ganjoor_list_books | Coleções nomeadas, filtráveis por poeta ou substring do nome. |
Categorias
| Ferramenta | Descrição |
|---|---|
ganjoor_get_category | Uma categoria com trilha de navegação, sub-seções e poemas contidos. |
ganjoor_list_category_poems | Poemas paginados em uma categoria, com filtro opcional de título. |
ganjoor_get_category_geotags | Locais geográficos/históricos marcados sob a subárvore de uma categoria. |
ganjoor_get_category_people_graph | Rede de pessoas por trás da aba "personagens" de uma categoria. |
Poemas
| Ferramenta | Descrição |
|---|---|
ganjoor_search_poems | Busca de texto completo em poemas, com escopo por poeta ou categoria. |
ganjoor_semantic_search_poems | Busca baseada em significado ("encontre um poema sobre…"). |
ganjoor_get_poem | Um poema completo por URL, com versos agrupados em dísticos. |
ganjoor_get_poem_by_id | O mesmo, por id numérico. |
ganjoor_get_poem_verses | Apenas os versos — mais leve do que buscar o poema inteiro. |
ganjoor_get_random_poem | Um poema aleatório, opcionalmente de um poeta. |
ganjoor_get_hafez_faal | Um ghazal aleatório de Hafez para adivinhação persa. |
ganjoor_get_page_url | Resolve qualquer id de página (poema, categoria, poeta) para sua URL web. |
ganjoor_get_redirect_url | Resolve uma URL legada do Ganjoor para seu endereço atual. |
Prosódia, rima e estrutura
| Ferramenta | Descrição |
|---|---|
ganjoor_list_rhythms | Todos os metros prosódicos persas com contagens de uso. |
ganjoor_analyze_poem_rhythm | Detecta o metro de um poema. |
ganjoor_analyze_poem_rhyme | Detecta a letra de rima e o padrão de um poema. |
ganjoor_find_similar_poems | Poemas que compartilham metro e rima — o grafo de poemas-resposta. |
ganjoor_get_poem_sections | As seções estruturais de um poema (laços, faixas masnavi, tercetos). |
ganjoor_get_section | Uma seção com versos e seções relacionadas. |
ganjoor_get_related_sections | Seções de outros poemas que correspondem ao metro e rima deste. |
ganjoor_get_couplet_sections | A quais seções um determinado dístico pertence. |
ganjoor_get_language_tagged_sections | Estrofes marcadas com um código de idioma não persa. |
Recitações de áudio
| Ferramenta | Descrição |
|---|---|
ganjoor_search_recitations | Busca recitações por recitador, poeta ou categoria. |
ganjoor_get_recitation | Uma recitação com sua URL de áudio. |
ganjoor_get_recitation_sync_info | Tempo por verso para acompanhar o áudio. |
ganjoor_get_poem_recitations | Todas as recitações publicadas para um poema. |
ganjoor_get_category_top_recitations | Recitações mais votadas dentro de uma categoria. |
Pessoas
| Ferramenta | Descrição |
|---|---|
ganjoor_list_people | Figuras históricas e literárias no grafo de conhecimento do Ganjoor. |
ganjoor_get_person | Biografia, datas e locais de uma pessoa. |
ganjoor_get_person_poems | Poemas marcados com uma pessoa. |
ganjoor_get_person_relations | Arestas de parentesco e afiliação de uma pessoa. |
ganjoor_get_person_family_tree | Todo o componente de parentesco conectado a partir de uma pessoa. |
Comentários, FAQ e material suplementar
| Ferramenta | Descrição |
|---|---|
ganjoor_get_recent_comments | Comentários recentes publicados de leitores. |
ganjoor_list_faq_categories | Categorias de FAQ editorial do Ganjoor. |
ganjoor_get_faq_items | Todas as entradas de Q&A em uma categoria de FAQ. |
ganjoor_get_faq_item | Uma única entrada de FAQ por id. |
ganjoor_get_poem_images | Imagens de manuscritos e fac-símiles. |
ganjoor_get_poem_quoted_poems | Poemas que este cita, e poemas que o citam. |
ganjoor_get_poem_effective_corrections | O registro editorial antes/depois de correções de texto. |
ganjoor_get_poem_geotags | Locais e datas comemorativas marcadas em um poema. |
Toda ferramenta é anotada com readOnlyHint, idempotentHint e openWorldHint, e nenhuma delas
modifica o estado no Ganjoor.
Formatos de resposta
Toda ferramenta aceita um argumento response_format:
markdown(padrão) — saída legível por humanos com cabeçalhos, listas e links clicáveis.json— os mesmos dados como um objeto estruturado, também retornados comostructuredContentdo MCP para que clientes que suportam possam consumir campos diretamente sem analisar texto.
Ferramentas de lista compartilham um envelope de paginação consistente:
{
"total": 842,
"count": 20,
"page": 1,
"page_size": 20,
"has_more": true,
"next_page": 2,
"poems": [{ "id": 2623, "title": "غزل شمارهٔ ۴۹۴", "full_url": "/hafez/ghazal/sh494" }]
}
page e page_size são baseados em 1; page_size tem limite de 100. Respostas com mais de 25.000 caracteres são
truncadas com um sinalizador explícito truncated em vez de cortadas silenciosamente.
Dois endpoints upstream (/api/ganjoor/poets e /api/ganjoor/rhythms) ignoram seus parâmetros de paginação
e sempre retornam a coleção inteira. O servidor detecta isso e fatia a janela
solicitada localmente, então page e has_more permanecem corretos e consistentes em todas as ferramentas de listagem.
Erros são retornados como resultados de ferramenta (não erros de protocolo) com uma mensagem que nomeia o endpoint e
sugere um próximo passo — por exemplo, um 404 aponta você para ganjoor_list_poets para encontrar um id válido.
Uso
"Quem é Hafez?"
ganjoor_get_poet { "url": "hafez" }
"Mostre-me o ghazal 494 de Hafez"
ganjoor_get_poem { "url": "hafez/ghazal/sh494", "include_recitations": true }
"Quais poemas de Hafez contêm a palavra صبر?"
ganjoor_search_poems { "term": "صبر", "poet_id": 2, "page_size": 10 }
"Qual é o metro deste poema, e quem mais escreveu nesse metro?"
ganjoor_analyze_poem_rhythm { "poem_id": 2623 }
ganjoor_list_rhythms { "page_size": 100 }
ganjoor_find_similar_poems { "metre": "<rhythm from the previous step>", "page_size": 10 }
"Quem são os ancestrais desta figura?"
ganjoor_get_person { "person_id": 22 }
ganjoor_get_person_family_tree { "person_id": 22 }
"O que Hafez diz sobre minha pergunta?"
ganjoor_get_hafez_faal { }
Arquitetura
src/
├── index.ts # Process entry: stdio or streamable HTTP transport
├── server.ts # createServer() — registers every tool
├── client.ts # fetch wrapper, error mapping, pagination helpers
├── types.ts # TypeScript interfaces for Ganjoor's payloads
├── constants.ts # Base URLs, limits, server identity
├── schemas.ts # Shared Zod schemas (pagination, URL slugs, ids)
├── format.ts # Markdown rendering and formatting helpers
└── tools/
├── registry.ts # defineTool() wrapper: consistent errors + both formats
├── poets.ts # Poet discovery
├── categories.ts # Category browsing
├── poems.ts # Poem retrieval, search, URL resolution
├── analysis.ts # Prosody, rhyme, sections
├── audio.ts # Recitations
├── content.ts # Images, quotations, corrections, geo tags
└── people.ts # People graph, comments, FAQ
Duas convenções percorrem o código:
defineTool()envolve cada registro. Garante que cada ferramenta reporte falhas como resultadosisErrorcom mensagens acionáveis, renderize markdown ou JSON de forma consistente e retornestructuredContentjunto com o texto.md()constrói documentos. Preserva linhas em branco explícitas para que cabeçalhos e listas de markdown sejam separados adequadamente, enquanto descartanull/falsepara que fragmentos condicionais desapareçam limpos.
Peculiaridades upstream são normalizadas em client.ts e nos módulos de ferramenta, em vez de vazarem para os chamadores —
por exemplo, o envelope { poet, cat } em endpoints de categoria, o category.cat aninhado em payloads
de poemas e o requisito de barra inicial em parâmetros de URL.
Desenvolvimento
npm run build # compile to dist/
npm run typecheck # type-check without emitting
npm test # live smoke test against api.ganjoor.net
npm run inspect # print real tool output for review
npm run clean
npm test inicia o servidor sobre um transporte em memória, verifica que cada ferramenta tem uma descrição
substancial e invoca todas as 44 ferramentas contra a API ao vivo, falhando em qualquer resposta de erro.
Limitações conhecidas
- A busca semântica pode estar desativada. O backend de embeddings do Ganjoor é opcional; quando uma implantação o tem
desligado,
ganjoor_semantic_search_poemsretorna um 503. A ferramenta exibe uma mensagem clara e aponta você paraganjoor_search_poemscom uma palavra-chave em persa. - A resolução de redirecionamentos é esparsa. O Ganjoor só armazena redirecionamentos para caminhos que realmente renomeou, então
a maioria das URLs válidas retorna
has_redirect: falseem vez de um destino. - Algumas ferramentas sempre precisam de autenticação. Endpoints atrás de uma conta Ganjoor (marcadores, postagem de comentários, envio de correções) estão fora do escopo por design — este servidor é somente leitura e anônimo.
- O catálogo de livros é curado.
ganjoor_list_bookscobre cerca de 168 coleções nomeadas e omite alguns poetas importantes — Hafez não tem entradas. Useganjoor_get_poetcominclude_cats: trueem vez disso, que lê as seções de cada poeta diretamente de sua categoria raiz. - Sem contagens totais em endpoints paginados. A API upstream pagina sem relatar totais, então
totalé omitido quando desconhecido;has_moreé inferido buscando uma linha além da página. - A cobertura do texto poético é curada. A marcação de pessoas, tags geográficas e tags de idioma são incompletas por natureza — um resultado vazio geralmente é uma lacuna de cobertura, não uma prova de ausência.
- Campos de data às vezes são aproximados. O Ganjoor sinaliza datas sobre as quais não tem certeza
(
validBirthDate/validDeathDate); as ferramentas expõem esses sinalizadores para que os chamadores possam filtrar.
Avaliação
evaluation/evaluation.xml contém 10 perguntas somente leitura com respostas verificadas. Cada uma exige
várias chamadas de ferramenta para responder e foi verificada contra a API ao vivo antes de ser registrada:
npx tsx tests/verify-evaluation.ts # replays every question through the MCP tools
As perguntas evitam deliberadamente trivia de consulta única. Elas cruzam poetas com séculos, percorrem o grafo de parentesco, comparam a prosódia de um poema com a lista de metros do corpus e resolvem uma cadeia de poemas-resposta por meio de registros de citação.
Contribuindo
Veja CONTRIBUTING.md.