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

GANJOOR MCP SERVER: MCP server for the Ganjoor (گنجور) Persian classical poetry API

VERSION: 0.1.0 PLATFORM: Node >=18.17 LICENSE: MIT

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

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.

Install in VS Code

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.

Install in Cursor

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

  1. Abra o GitHub Copilot CLI no modo interativo.
  2. Execute /mcp add.
  3. Digite Nome do Servidor: io.github.ehsanghaffar/ganjoor-mcp-server.
  4. Escolha Tipo de Servidor:
    • STDIO para servidores baseados em comando
    • HTTP para servidores remotos
  5. Preencha as configurações de comando/url da configuração padrão acima e defina Ferramentas como *.
  6. Pressione Ctrl+S para 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ávelPadrãoFinalidade
HTTP(não definido)Defina como true para servir HTTP streamable em vez de stdio.
PORT3000Porta HTTP.
HOST127.0.0.1Endereço de vinculação HTTP.
ALLOWED_ORIGINS(vazio)Valores adicionais permitidos de Origin, separados por vírgulas.
GANJOOR_API_BASE_URLhttps://api.ganjoor.netURL base da API upstream — aponte para uma instância RMuseum local para desenvolvimento.
GANJOOR_SITE_BASE_URLhttps://ganjoor.netBase 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, Saadi 7, Rumi 5, Khayyam 3, Ferdowsi 4. ganjoor_list_poets resolve um nome para um id.
  • Categoria — um livro ou seção dentro da obra de um poeta (CatType 0 geral, 1 poemas, 2 livro). A categoria raiz de Hafez é 9; seus ghazals são 24.
  • 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 (versePosition 0 e 1).

Slugs de URL precisam de uma barra inicial. A API upstream retorna 404 para ?url=hafez/ghazal/sh494 mas 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

FerramentaDescrição
ganjoor_list_poetsTodos os poetas publicados com biografia, datas, locais. Paginado.
ganjoor_get_poetUm poeta por id ou slug, com biografia e livros/seções de nível superior.
ganjoor_get_poets_by_centuryTodos os poetas agrupados em meio-séculos históricos.
ganjoor_list_booksColeções nomeadas, filtráveis por poeta ou substring do nome.

Categorias

FerramentaDescrição
ganjoor_get_categoryUma categoria com trilha de navegação, sub-seções e poemas contidos.
ganjoor_list_category_poemsPoemas paginados em uma categoria, com filtro opcional de título.
ganjoor_get_category_geotagsLocais geográficos/históricos marcados sob a subárvore de uma categoria.
ganjoor_get_category_people_graphRede de pessoas por trás da aba "personagens" de uma categoria.

Poemas

FerramentaDescrição
ganjoor_search_poemsBusca de texto completo em poemas, com escopo por poeta ou categoria.
ganjoor_semantic_search_poemsBusca baseada em significado ("encontre um poema sobre…").
ganjoor_get_poemUm poema completo por URL, com versos agrupados em dísticos.
ganjoor_get_poem_by_idO mesmo, por id numérico.
ganjoor_get_poem_versesApenas os versos — mais leve do que buscar o poema inteiro.
ganjoor_get_random_poemUm poema aleatório, opcionalmente de um poeta.
ganjoor_get_hafez_faalUm ghazal aleatório de Hafez para adivinhação persa.
ganjoor_get_page_urlResolve qualquer id de página (poema, categoria, poeta) para sua URL web.
ganjoor_get_redirect_urlResolve uma URL legada do Ganjoor para seu endereço atual.

Prosódia, rima e estrutura

FerramentaDescrição
ganjoor_list_rhythmsTodos os metros prosódicos persas com contagens de uso.
ganjoor_analyze_poem_rhythmDetecta o metro de um poema.
ganjoor_analyze_poem_rhymeDetecta a letra de rima e o padrão de um poema.
ganjoor_find_similar_poemsPoemas que compartilham metro e rima — o grafo de poemas-resposta.
ganjoor_get_poem_sectionsAs seções estruturais de um poema (laços, faixas masnavi, tercetos).
ganjoor_get_sectionUma seção com versos e seções relacionadas.
ganjoor_get_related_sectionsSeções de outros poemas que correspondem ao metro e rima deste.
ganjoor_get_couplet_sectionsA quais seções um determinado dístico pertence.
ganjoor_get_language_tagged_sectionsEstrofes marcadas com um código de idioma não persa.

Recitações de áudio

FerramentaDescrição
ganjoor_search_recitationsBusca recitações por recitador, poeta ou categoria.
ganjoor_get_recitationUma recitação com sua URL de áudio.
ganjoor_get_recitation_sync_infoTempo por verso para acompanhar o áudio.
ganjoor_get_poem_recitationsTodas as recitações publicadas para um poema.
ganjoor_get_category_top_recitationsRecitações mais votadas dentro de uma categoria.

Pessoas

FerramentaDescrição
ganjoor_list_peopleFiguras históricas e literárias no grafo de conhecimento do Ganjoor.
ganjoor_get_personBiografia, datas e locais de uma pessoa.
ganjoor_get_person_poemsPoemas marcados com uma pessoa.
ganjoor_get_person_relationsArestas de parentesco e afiliação de uma pessoa.
ganjoor_get_person_family_treeTodo o componente de parentesco conectado a partir de uma pessoa.

Comentários, FAQ e material suplementar

FerramentaDescrição
ganjoor_get_recent_commentsComentários recentes publicados de leitores.
ganjoor_list_faq_categoriesCategorias de FAQ editorial do Ganjoor.
ganjoor_get_faq_itemsTodas as entradas de Q&A em uma categoria de FAQ.
ganjoor_get_faq_itemUma única entrada de FAQ por id.
ganjoor_get_poem_imagesImagens de manuscritos e fac-símiles.
ganjoor_get_poem_quoted_poemsPoemas que este cita, e poemas que o citam.
ganjoor_get_poem_effective_correctionsO registro editorial antes/depois de correções de texto.
ganjoor_get_poem_geotagsLocais 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 como structuredContent do 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 resultados isError com mensagens acionáveis, renderize markdown ou JSON de forma consistente e retorne structuredContent junto 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 descarta null/false para 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_poems retorna um 503. A ferramenta exibe uma mensagem clara e aponta você para ganjoor_search_poems com 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: false em 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_books cobre cerca de 168 coleções nomeadas e omite alguns poetas importantes — Hafez não tem entradas. Use ganjoor_get_poet com include_cats: true em 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.