Ganjoor Mcp Server

io.github.ehsanghaffarPublished by GitHub account ehsanghaffarv0.1.1Updated Oct 30, 2026

MCP server for the Ganjoor (گنجور) Persian classical poetry API at https://ganjoor.net

VerifiedSTDIODesktop onlyKnowledge & MemoryMedia & Design

Overview

AI-generated overview

Read-only MCP server for the Ganjoor archive of Persian classical poetry, letting an assistant browse poets, search and read poems, and analyse metre, rhyme…

What you can do
  • Browse poets, books and categories, including century groupings and category geotags
  • Search poems by keyword or by meaning, then read full poems, verses, sections and page URLs
  • Analyse prosody and rhyme, list metres, and find poems or sections sharing metre and rhyme
  • Look up audio recitations, per-verse timing and top recitations per category
  • Explore the people graph: biographies, tagged poems, kinship relations and family trees
  • Read recent reader comments, FAQ entries, poem images, quotations and corrections
When to use it
Useful when working with Persian classical poetry: identifying a poet or book, finding a poem from a remembered phrase or theme, reading verses, or studying metre, rhyme, reply-poems and recitations. Also handy for a Hafez divination reading or for tracing historical figures linked to a work.
Requirements
Runs locally over stdio as an npm package, so Node.js 18.17 or newer is needed; no API key or account. It calls the public Ganjoor API over the network. Optional environment variables cover HTTP mode, port, host, allowed origins and alternative API or site base URLs.
Before you install
All tools are read-only and anonymous, so nothing on Ganjoor is changed. Poem text is Persian and right-to-left and is returned verbatim. Coverage is curated: person, geo and language tags are incomplete, some dates are flagged approximate, and semantic search may be unavailable upstream. Requests go to the public Ganjoor API.

Installation

In SourceWeft

  1. Open Ganjoor Mcp Server in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

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

English | فارسی

Ganjoor MCP Server — the Model Context Protocol server for the Ganjoor Persian poetry API.

It lets an agent browse poets and collections, read poems with their verses, run prosody and rhyme analysis, trace reply-poems across poets, look up recitations and commentary, and explore the related-people and family-tree graphs.

44 tools, read-only access. No API key required.


Contents


Install

Requires Node.js 18.17 or newer.

bash
npm install ganjoor-mcp-server # for locally installed stdio transport. no need if using VS Code or Claude Desktop / Cursor.

VS Code

No need install npm package — the MCP server runs in-process and is launched by VS Code when needed.

Just click the badge below to install the Ganjoor MCP server in your VS Code workspace. It will automatically add the server to your .vscode/mcp.json config.

[Install in VS Code]

Or add to .vscode/mcp.json:

json
{  "servers": {    "io.github.ehsanghaffar/ganjoor-mcp-server": {      "command": "npx",      "args": [        "-y",        "ganjoor-mcp-server"      ],      "env": {}    }  }}

VS Code CLI:

bash
code --add-mcp '{\"name\":\"io.github.ehsanghaffar/ganjoor-mcp-server\",\"command\":\"npx\",\"args\":[\"-y\",\"ganjoor-mcp-server\"],\"env\":{}}'

Claude Desktop / Cursor

Just click the badge below to install the Ganjoor MCP server in your Claude Desktop or Cursor app. It will automatically add the server to your MCP config.

[Install in Cursor]

Or add to your MCP config file:

json
{  "mcpServers": {    "io.github.ehsanghaffar/ganjoor-mcp-server": {      "command": "npx",      "args": [        "-y",        "ganjoor-mcp-server"      ],      "env": {},      "type": "local",      "tools": [        "*"      ]    }  }}

Other clients

Claude Code

Use the Claude Code CLI to add the io.github.ehsanghaffar/ganjoor-mcp-server MCP server:

bash
claude mcp add io.github.ehsanghaffar/ganjoor-mcp-server npx -y ganjoor-mcp-server
Claude Desktop

Follow the MCP install guide, use the standard config above.

Codex

Create or edit the configuration file ~/.codex/config.toml and add:

toml
[mcp_servers.ganjoor-mcp-server]command = "npx"args = ["-y", "ganjoor-mcp-server"]

For more information, see the Codex MCP documentation.

Gemini CLI

Follow the MCP install guide, use the standard config above.

OpenCode

Follow the MCP Servers documentation. For example in ~/.config/opencode/opencode.json:

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

GitHub Copilot CLI supports adding MCP servers interactively and through a config file.

Option 1: Interactive setup with /mcp add
  1. Open GitHub Copilot CLI in interactive mode.
  2. Run /mcp add.
  3. Enter Server Name: io.github.ehsanghaffar/ganjoor-mcp-server.
  4. Choose Server Type:
    • STDIO for command-based servers
    • HTTP for remote servers
  5. Fill in command/url settings from the standard config above and set Tools to *.
  6. Press Ctrl+S to save.
Option 2: Edit ~/.copilot/mcp-config.json
json
{  "mcpServers": {    "io.github.ehsanghaffar/ganjoor-mcp-server": {      "command": "npx",      "args": [        "-y",        "ganjoor-mcp-server"      ],      "env": {},      "type": "local",      "tools": [        "*"      ]    }  }}

For more information, see the GitHub Copilot CLI MCP documentation.

GitHub Copilot Coding Agent

GitHub Copilot Coding Agent can use MCP servers to extend its capabilities. Use the configuration below specifically formatted for the Coding Agent:

json
{  "mcpServers": {    "ganjoor-mcp-server": {      "command": "npx",      "args": [        "-y",        "ganjoor-mcp-server"      ],      "env": {},      "type": "local",      "tools": [        "*"      ]    }  }}

Add this configuration to your repository settings under Copilot > Coding agent. The "tools": ["*"] setting enables all available tools from the MCP server.

For more information, see the GitHub Copilot Coding Agent MCP documentation.

Install from this source

bash
npm installnpm run buildnpm start            # stdio transport (default)
VS Code

Add to .vscode/mcp.json:

json
{  "servers": {    "ganjoor": {      "type": "stdio",      "command": "node",      "args": ["${workspaceFolder}/dist/index.js"]    }  }}
Claude Desktop / Cursor

Add to your MCP config file:

json
{  "mcpServers": {    "ganjoor": {      "command": "node",      "args": ["/absolute/path/to/ganjoor-mcp/dist/index.js"]    }  }}

Streamable HTTP (remote / shared deployment)

bash
HTTP=true PORT=3000 npm start

The MCP endpoint is POST /mcp, and GET /health reports server and upstream status. The HTTP transport runs statelessly (a fresh server instance per request), so it scales horizontally. It binds 127.0.0.1 by default and validates the Origin header to block DNS-rebinding attacks; set ALLOWED_ORIGINS (comma-separated) if you need non-loopback origins.

MCP Inspector

bash
npm run inspector

Docker

A multi-stage Dockerfile and a docker-compose.yml are included. The image is built on node:22-alpine and contains only the compiled output and production dependencies.

bash
docker compose up --build

The compose file starts the server in streamable HTTP mode on http://localhost:3000/mcp, with GET /health reporting server and upstream status. HOST is set to 0.0.0.0 so the published port is reachable from the host; every configuration variable from the table below works as an ordinary environment variable. To run a single container without compose:

bash
docker build -t ganjoor-mcp .docker run --rm -p 3000:3000 -e HTTP=true -e HOST=0.0.0.0 ganjoor-mcp

stdio transport over Docker

Clients that launch the server themselves (rather than connecting over HTTP) can use the stdio profile, which opens stdin for the protocol stream and starts no HTTP listener:

bash
docker compose --profile stdio run --rm ganjoor-mcp-stdio

With Claude Desktop

To run the server through Docker instead of a local Node install:

json
{  "mcpServers": {    "ganjoor": {      "command": "docker",      "args": ["run", "--rm", "-i", "ganjoor-mcp"]    }  }}

Configuration

All settings are environment variables; none are required.

VariableDefaultPurpose
HTTP(unset)Set to true to serve streamable HTTP instead of stdio.
PORT3000HTTP port.
HOST127.0.0.1HTTP bind address.
ALLOWED_ORIGINS(empty)Extra allowed Origin values, comma-separated.
GANJOOR_API_BASE_URLhttps://api.ganjoor.netUpstream API base URL — point at a local RMuseum instance for development.
GANJOOR_SITE_BASE_URLhttps://ganjoor.netFront-end base used to build clickable links.

Concepts

Ganjoor's data model has three nested levels. Getting these straight is the difference between a productive and a fruitless agent session.

mermaid
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"]
  • Poet — an author. Hafez is poet id 2, Saadi 7, Rumi 5, Khayyam 3, Ferdowsi 4. ganjoor_list_poets resolves a name to an id.
  • Category — a book or section inside a poet's work (CatType 0 general, 1 poems, 2 book). Hafez's root category is 9; his ghazals are 24.
  • Poem — one text, identified by a numeric id and a URL slug such as hafez/ghazal/sh494.
  • Verse — a hemistich. Two consecutive verses form a couplet (versePosition 0 and 1).

URL slugs need a leading slash. The upstream API returns 404 for ?url=hafez/ghazal/sh494 but resolves ?url=/hafez/ghazal/sh494. This server normalizes for you, so either form works.

Persian text is returned verbatim. It is right-to-left with Arabic-script diacritics used for prosody. Do not transliterate or translate it unless the user explicitly asks.


Tool reference

Poets and collections

ToolDescription
ganjoor_list_poetsAll published poets with biography, dates, places. Paginated.
ganjoor_get_poetOne poet by id or slug, with biography and top-level books/sections.
ganjoor_get_poets_by_centuryEvery poet grouped into historical half-centuries.
ganjoor_list_booksNamed collections, filterable by poet or name substring.

Categories

ToolDescription
ganjoor_get_categoryA category with breadcrumb, sub-sections, and contained poems.
ganjoor_list_category_poemsPaginated poems in a category, with optional title filter.
ganjoor_get_category_geotagsGeographic/historical places tagged under a category's subtree.
ganjoor_get_category_people_graphPeople network behind a category's "characters" tab.

Poems

ToolDescription
ganjoor_search_poemsFull-text search across poems, scoped by poet or category.
ganjoor_semantic_search_poemsMeaning-based search ("find a poem about…").
ganjoor_get_poemA complete poem by URL, with verses grouped into couplets.
ganjoor_get_poem_by_idThe same, by numeric id.
ganjoor_get_poem_versesJust the verses — lighter than fetching the whole poem.
ganjoor_get_random_poemA random poem, optionally from one poet.
ganjoor_get_hafez_faalA random Hafez ghazal for Persian divination.
ganjoor_get_page_urlResolve any page id (poem, category, poet) to its web URL.
ganjoor_get_redirect_urlResolve a legacy Ganjoor URL to its current address.

Prosody, rhyme, and structure

ToolDescription
ganjoor_list_rhythmsAll Persian prosodic metres with usage counts.
ganjoor_analyze_poem_rhythmDetect a poem's metre.
ganjoor_analyze_poem_rhymeDetect a poem's rhyme letter and pattern.
ganjoor_find_similar_poemsPoems sharing a metre and rhyme — the reply-poem graph.
ganjoor_get_poem_sectionsA poem's structural sections (bonds, masnavi bands, tercets).
ganjoor_get_sectionOne section with verses and related sections.
ganjoor_get_related_sectionsSections from other poems matching this one's metre and rhyme.
ganjoor_get_couplet_sectionsWhich sections a given couplet belongs to.
ganjoor_get_language_tagged_sectionsStanzas tagged with a non-Persian language code.

Audio recitations

ToolDescription
ganjoor_search_recitationsSearch recitations by reciter, poet, or category.
ganjoor_get_recitationOne recitation with its audio URL.
ganjoor_get_recitation_sync_infoPer-verse timing for following along with audio.
ganjoor_get_poem_recitationsEvery recitation published for one poem.
ganjoor_get_category_top_recitationsMost-upvoted recitations within a category.

People

ToolDescription
ganjoor_list_peopleHistorical and literary figures in Ganjoor's knowledge graph.
ganjoor_get_personOne person's biography, dates, and places.
ganjoor_get_person_poemsPoems tagged with a person.
ganjoor_get_person_relationsA person's kinship and affiliation edges.
ganjoor_get_person_family_treeThe whole connected kinship component from one person.

Commentary, FAQ, and supplementary material

ToolDescription
ganjoor_get_recent_commentsRecent published reader commentary.
ganjoor_list_faq_categoriesGanjoor's editorial FAQ categories.
ganjoor_get_faq_itemsAll Q&A entries in one FAQ category.
ganjoor_get_faq_itemA single FAQ entry by id.
ganjoor_get_poem_imagesManuscript and facsimile images.
ganjoor_get_poem_quoted_poemsPoems this one quotes, and poems quoting it.
ganjoor_get_poem_effective_correctionsThe editorial before/after record of text corrections.
ganjoor_get_poem_geotagsPlaces and commemorative dates tagged to one poem.

Every tool is annotated readOnlyHint, idempotentHint, and openWorldHint, and none of them modify state on Ganjoor.


Response formats

Every tool takes a response_format argument:

  • markdown (default) — human-readable output with headers, lists, and clickable links.
  • json — the same data as a structured object, also returned as MCP structuredContent so clients that support it can consume fields directly without parsing text.

List tools share a consistent pagination envelope:

json
{  "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 and page_size are 1-based; page_size caps at 100. Responses over 25,000 characters are truncated with an explicit truncated flag rather than silently cut.

Two upstream endpoints (/api/ganjoor/poets and /api/ganjoor/rhythms) ignore their paging parameters and always return the whole collection. The server detects that and slices the requested window locally, so page and has_more stay correct and consistent across every list tool.

Errors are returned as tool results (not protocol errors) with a message that names the endpoint and suggests a next step — for example, a 404 points you at ganjoor_list_poets to find a valid id.


Usage

"Who is Hafez?"

ganjoor_get_poet { "url": "hafez" }

"Show me Hafez's ghazal 494"

ganjoor_get_poem { "url": "hafez/ghazal/sh494", "include_recitations": true }

"Which Hafez poems contain the word صبر?"

ganjoor_search_poems { "term": "صبر", "poet_id": 2, "page_size": 10 }

"What metre is this poem in, and who else wrote in that metre?"

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 }

"Who are this figure's ancestors?"

ganjoor_get_person { "person_id": 22 }ganjoor_get_person_family_tree { "person_id": 22 }

"What does Hafez say about my question?"

ganjoor_get_hafez_faal { }

Architecture

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

Two conventions run through the codebase:

  • defineTool() wraps every registration. It guarantees each tool reports failures as isError results with actionable messages, renders markdown or JSON consistently, and returns structuredContent alongside text.
  • md() builds documents. It preserves explicit blank lines so markdown headings and lists are separated properly, while dropping null/false so conditional fragments disappear cleanly.

Upstream quirks are normalized in client.ts and the tool modules rather than leaking to callers — for example the { poet, cat } envelope on category endpoints, the nested category.cat on poem payloads, and the leading-slash requirement on URL parameters.


Development

bash
npm run build       # compile to dist/npm run typecheck   # type-check without emittingnpm test            # live smoke test against api.ganjoor.netnpm run inspect     # print real tool output for reviewnpm run clean

npm test boots the server over an in-memory transport, asserts every tool has a substantial description, and invokes all 44 tools against the live API, failing on any error response.


Known limitations

  • Semantic search may be disabled. Ganjoor's embedding backend is optional; when a deployment has it turned off, ganjoor_semantic_search_poems returns a 503. The tool surfaces a clear message and points you at ganjoor_search_poems with a Persian keyword instead.
  • Redirect resolution is sparse. Ganjoor only stores redirects for paths it actually renamed, so most valid URLs return has_redirect: false rather than a target.
  • Some tools always need authentication. Endpoints behind a Ganjoor account (bookmarks, posting comments, uploading corrections) are out of scope by design — this server is read-only and anonymous.
  • The book catalog is curated. ganjoor_list_books covers ~168 named collections and omits some major poets — Hafez has no entries. Use ganjoor_get_poet with include_cats: true instead, which reads each poet's sections straight from their root category.
  • No total counts on paged endpoints. The upstream API pages without reporting totals, so total is omitted where unknown; has_more is inferred by fetching one row beyond the page.
  • Poetry text coverage is curated. Person tagging, geo tags, and language tags are incomplete by nature — an empty result is usually a coverage gap, not proof of absence.
  • Date fields are sometimes approximate. Ganjoor flags dates it is unsure about (validBirthDate / validDeathDate); the tools expose those flags so callers can filter.

Evaluation

evaluation/evaluation.xml holds 10 read-only questions with verified answers. Each one requires several tool calls to answer and was checked against the live API before being written down:

bash
npx tsx tests/verify-evaluation.ts   # replays every question through the MCP tools

The questions deliberately avoid single-lookup trivia. They cross-reference poets against centuries, walk the kinship graph, compare the prosody of a poem against the corpus-wide metre list, and resolve a reply-poem chain through quotation records.


Contributing

See CONTRIBUTING.md.

Source: README.md at commit ddfd1b6

Tools

44

ganjoor_list_poets

List Ganjoor Poets

List all poets published on Ganjoor.net, including their biographies. Returns one row per poet with: id, name, nickname, URL slug, root category id, birth/death years (Hijri), and birth/death places. Use this first when you need to translate a poet's name to a numeric id for other Ganjoor tools. Args: - page (number): Page number, starting from 1 (default: 1) - page_size (number): Items per page, 1-100 (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: A paginated poet list. For JSON: { total, count, page, page_size, has_more, next_page, poets: [{ id, name, nickname, full_url, root_cat_id, birth_year_hijri, death_year_hijri, birth_place, death_place }] } Examples: - "Who is on Ganjoor?" -> page_size=50 - "List the poets born in the 10th century Hijri" -> fetch pages and filter birth_year_hijri Notes: - Only public, read-only data. No authentication required. - The list is long (~170 poets); paginate rather than requesting page_size=100 repeatedly.

Parameterspagepage_sizeresponse_format

ganjoor_get_poet

Get Ganjoor Poet

Get a single poet's full profile (including biography) by numeric id or URL slug. Use this to turn a slug like "/hafez" into the numeric poet id, or to read a poet's biography and catalogue of works. Args: - id (number): Numeric poet id (optional if `url` is given). Hafez = 2, Saadi = 7, Rumi = 5. - url (string): Poet URL slug such as 'hafez' or '/hafez' (optional if `id` is given) - include_cats (boolean): Include the poet's top-level book/section list (default: true) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: Poet profile with id, name, nickname, biography, birth/death (Hijri + places), plus for JSON: { id, name, nickname, biography, birth_year_hijri, death_year_hijri, birth_place, death_place, full_url, cats: [{ id, title, full_url, cat_type }] } Examples: - "Tell me about Hafez" -> url='hafez' - "What are Saadi's works?" -> id=7, include_cats=true Notes: - Supply exactly one of `id` or `url`. If both are given, `id` wins.

Parametersidurlinclude_catsresponse_format

ganjoor_get_poets_by_century

Get Ganjoor Poets By Century

Get every Ganjoor poet grouped into historical half-centuries. The first group (id 0) holds the "pinned"/featured poets shown on the Ganjoor home page. Each later group carries a name, an approximate Gregorian year range, and the poets active in that period. Args: - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { centuries: [{ id, name, start_year, end_year, poet_count, poets: [{ id, name, full_url }] }] } Examples: - "Which poets are from the 14th century?" -> inspect the century entries and their poets Notes: - The whole payload is returned at once; it is a few hundred KB at most. - Grouping is editorial, so a poet appears exactly once.

Parametersresponse_format

ganjoor_list_books

List Ganjoor Books

List Ganjoor books (named collections such as دیوان حافظ or غزلیات سعدی), alphabetically. Every returned entry is a category of type "Book". Use the resulting category id with ganjoor_get_category to browse the poems inside, or with ganjoor_get_category_poems to list them. Args: - name (string): Optional filter — only books whose name contains this substring (case-insensitive) - poet_id (number): Optional filter — only books belonging to this poet id - page (number): Page number, starting from 1 (default: 1) - page_size (number): Items per page, 1-100 (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { count, page, page_size, has_more, next_page, books: [{ id, name, poet_id, poet_name, full_url }] } Examples: - "What books does Hafez have?" -> poet_id=2 - "Find books matching 'ghazal'" -> name='ghazal' - "Show me Molavi's collections" -> poet_id=5 Notes: - Filtering happens server-side, so it is much cheaper than fetching everything and filtering locally. - Ganjoor's book catalog is a curated subset: some major poets (including Hafez) have no entries. For them use ganjoor_get_poet with include_cats=true, which lists a poet's sections directly.

Parametersnamepagepoet_idpage_sizeresponse_format

ganjoor_get_category

Get Ganjoor Category

Get a Ganjoor category (a book, section, or sub-collection) by numeric id. A category is the container for poems inside a poet's work — for example Hafez's ghazals (category id 10) or Rumi's Masnavi. The response includes the breadcrumb path, the direct children (sub-sections), and optionally the poems it contains. Args: - cat_id (number): Category id. Hafez's root category is 9. - include_poems (boolean): Include the poems inside this category (default: true) - include_children (boolean): Include the direct child categories/sections (default: true) - poem_limit (number): Maximum poems to return when include_poems is true, 1-200 (default: 50) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: Category metadata: id, title, breadcrumb, description, table-of-contents style, child categories, and (optionally) contained poems with their ids, titles and URLs. Set include_poems=false to skip the poem list; use ganjoor_list_category_poems when you only want the poems and their total count. Examples: - "What is inside Hafez's ghazals?" -> cat_id=10, include_children=false - "List Hafez's sections" -> cat_id=9, include_poems=false Notes: - A category that directly contains poems returns those poems; a pure section returns children instead. Follow `children` to descend.

Parameterscat_idrequiredpoem_limitinclude_poemsresponse_formatinclude_children

ganjoor_list_category_poems

List Poems In A Ganjoor Category

List the poems contained in a Ganjoor category, optionally filtered by the author's name. Args: - cat_id (number): Category id to list poems from - title_contains (string): Optional filter — only poems whose title contains this text - page (number): Page number, starting from 1 (default: 1) - page_size (number): Poems per page, 1-100 (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { total, count, page, page_size, has_more, next_page, category: { id, title, full_url }, poems: [{ id, title, url_slug, full_url, excerpt }] } Examples: - "Which poems are in category 10?" -> cat_id=10 - "Find the 'شمارهٔ ۱' poems in a category" -> title_contains='شمارهٔ ۱' Notes: - Title filtering is done client-side because the upstream category endpoint is unpaged. - For large collections, combine this with page_size to walk the whole diwan.

Parameterspagecat_idrequiredpage_sizetitle_containsresponse_format

ganjoor_get_category_geotags

Get Category Geo Tags

Get the geographic and historical locations tagged to poems within a Ganjoor category and its descendants. Each tag pairs a place with the Hijri/Gregorian date a poem commemorates, which is how Ganjoor records historical events (a battle, a siege) in poetry. Args: - cat_id (number): Category id - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { total, tags: [{ id, latitude, longitude, lunar_date, gregorian_date, child_cat }] } Examples: - "Where are the events in Hafez's poetry set?" -> cat_id=9 Notes: - Tag lists can be large (tens of entries for a whole diwan). - Coordinates are decimal degrees, WGS84.

Parameterscat_idrequiredresponse_format

ganjoor_get_category_people_graph

Get Category People Graph

Get the network of historical people relevant to a Ganjoor category or work. Returns every person tagged in a poem under this category's subtree, plus their kinship and affiliation ties one hop out — the data behind Ganjoor's "characters" (شخصیت‌ها) tab. Args: - cat_id (number): Category id - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { node_count, edge_count, nodes: [{ id, name, importance }], edges: [{ from_person_id, to_person_id, relation }] } Examples: - "Which historical figures appear in the Shahnameh?" -> cat_id=<Shahnameh category id> Notes: - Empty graphs are normal for categories with no person tagging — that is not an error.

Parameterscat_idrequiredresponse_format

ganjoor_search_poems

Search Ganjoor Poems

Full-text search across all poems on Ganjoor, optionally scoped to one poet or category. The search matches Persian text inside poem bodies and titles. Use this when you know what the poem says but not what it is called; use ganjoor_list_category_poems when you know the book. Args: - term (string): Search text. Persian phrases work best; matches are substring based. - poet_id (number): Restrict to one poet (default: 0 = all poets) - cat_id (number): Restrict to one category (default: 0 = all categories) - page (number): Page number, starting from 1 (default: 1) - page_size (number): Results per page, 1-100 (default: 20) - include_text (boolean): Include each poem's full text in the response (default: false) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { count, page, page_size, has_more, next_page, poems: [{ id, title, full_title, full_url, plain_text? }] } Examples: - "Find poems mentioning 'دل'" -> term='دل', poet_id=2 - "Search Hafez for 'صبر'" -> term='صبر', poet_id=2 - "Give me the full text of the matches" -> include_text=true Notes: - Narrow with `poet_id` or `cat_id` first: unfiltered search over all poems is broad and slow to page through. - Set `include_text=true` only for a small page; full poems are long.

Parameterspagetermrequiredcat_idpoet_idpage_sizeinclude_textresponse_format

ganjoor_semantic_search_poems

Semantic Search Ganjoor Poems

Meaning-based ("find a poem about...") search over Ganjoor's poetry collection. Use this when you want poems by theme or feeling rather than an exact word. Describe the idea in natural language — in English or Persian — and Ganjoor's embedding search returns the closest verses. Prefer ganjoor_search_poems when you have an exact phrase to look for. Args: - query (string): Natural-language description of the poem's theme, 1-500 characters - top_k (number): How many poems to return, 1-50 (default: 10) - poet_id (number): Restrict to a poet id. 0 = all poets (default: 0) - cat_id (number): Restrict to a category id. 0 = all categories (default: 0) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { count, results: [{ id, title, full_title, full_url, plain_text }] } ordered by relevance. Examples: - "Poems about patience and divine trust" -> query='patience and trust in God' - "غزلی دربارهٔ جدایی از معشوق" -> query='a ghazal about separation from the beloved' Notes: - Ganjoor's semantic backend is an optional feature that some deployments leave disabled; when it is off this tool returns a 503 with a message saying so. In that case fall back to ganjoor_search_poems with a Persian keyword describing the theme. - Relevance ordering is approximate — always quote the returned text rather than paraphrasing it.

Parametersqueryrequiredtop_kcat_idpoet_idresponse_format

ganjoor_get_poem

Get Ganjoor Poem By URL

Fetch a complete poem by its Ganjoor URL, including verses, metre, rhyme, and recitations. This is the main way to read a poem. The URL is the same as the browser address on ganjoor.net, e.g. "hafez/ghazal/sh494". Use ganjoor_search_poems or ganjoor_list_category_poems to discover URLs, and ganjoor_get_poem_by_id when you only have a numeric id. Args: - url (string): Poem URL slug, with or without leading slash, e.g. 'hafez/ghazal/sh494' - include_category (boolean): Include the book/category metadata (default: true) - include_siblings (boolean): Include the list of other poems in the same book (default: false) - include_recitations (boolean): Include audio recitations (default: true) - include_images (boolean): Include manuscript images (default: false) - include_comments (boolean): Include published reader comments (default: false) - include_verse_details (boolean): Include per-verse ids, positions, and couplet summaries (default: true) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: Poem id, title, full title, poet, book/category path, plain text, and the ordered verse list grouped into couplets. For JSON: { id, title, full_title, full_url, poet, category, couplets_count, verses: [{ couplet_index, position, text }], recitations: [...], next_poem, previous_poem } Examples: - "Show me Hafez's ghazal 494" -> url='hafez/ghazal/sh494' - "Read this poem with its commentary" -> include_comments=true Notes: - Persian text is returned verbatim; it is right-to-left, so present it as-is. - Keep include_siblings and include_comments false unless needed — both can be very large.

Parametersurlrequiredinclude_imagesresponse_formatinclude_categoryinclude_commentsinclude_siblingsinclude_recitationsinclude_verse_details

ganjoor_get_poem_by_id

Get Ganjoor Poem By Id

Fetch a complete poem by its numeric Ganjoor id. Same content as ganjoor_get_poem, but keyed on the numeric poem id instead of the URL. Use it when you have an id from search results or another tool and do not want to build a URL. Args: - poem_id (number): Numeric poem id - include_category (boolean): Include the book/category metadata (default: true) - include_recitations (boolean): Include audio recitations (default: false) - include_comments (boolean): Include published reader comments (default: false) - include_verse_details (boolean): Include per-verse ids and positions plus the prose summary for each couplet (default: true). Set false for a leaner payload; the verse text itself is always returned. - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: Identical shape to ganjoor_get_poem. Examples: - "Show poem 2623" -> poem_id=2623 - "What is poem 13836?" -> poem_id=13836 Notes: - Combined with ganjoor_get_page_url you can also resolve an id to its web address.

Parameterspoem_idrequiredresponse_formatinclude_categoryinclude_commentsinclude_recitationsinclude_verse_details

ganjoor_get_poem_verses

Get Ganjoor Poem Verses

Get just the verses of a poem by id — a lightweight alternative to fetching the whole poem. Each verse comes back in order with its couplet index and whether it is the first or second hemi- stich. Optionally restrict to a single couplet. Args: - poem_id (number): Numeric poem id - couplet_index (number): Return only this couplet (0-based). Omit for the whole poem. - include_summary (boolean): Include the per-couplet prose summary (default: false) - include_original_text (boolean): Include the uncorrected original text where available (default: false) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { poem_id, verse_count, verses: [{ id, order, couplet_index, position, text }] } Examples: - "Quote the verses of poem 2623" -> poem_id=2623 - "Show only couplet 3" -> poem_id=2623, couplet_index=3 Notes: - Position 0 is the first hemistich, position 1 the second; couplet_index groups them.

Parameterspoem_idrequiredcouplet_indexinclude_summaryresponse_formatinclude_original_text

ganjoor_get_random_poem

Get Random Ganjoor Poem

Get a random poem from Ganjoor, optionally from a specific poet. Args: - poet_id (number): Poet to draw from. 0 = a random poet (default: 0). Hafez = 2. - include_recitations (boolean): Include audio recitations (default: false) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: A single complete poem in the same shape as ganjoor_get_poem_by_id. Examples: - "Surprise me with a poem" -> no arguments - "A random Saadi poem" -> poet_id=7 Notes: - Non-deterministic: repeated calls return different poems.

Parameterspoet_idresponse_formatinclude_recitations

ganjoor_get_hafez_faal

Get Hafez Faal (Divination)

Draw a random Hafez ghazal as a Persian divination (فال حافظ). This is the traditional "ask Hafez" reading: one ghazal selected at random, to be interpreted in the context of the question asked. Each call draws a different ghazal. Args: - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: A complete Hafez ghazal in the same shape as ganjoor_get_poem_by_id. Examples: - "Draw a faal for me" -> no arguments - "What does Hafez say about my question?" -> no arguments Notes: - Non-deterministic. The interpretation is traditionally left to the reader.

Parametersresponse_format

ganjoor_get_page_url

Resolve Ganjoor Page Id To URL

Resolve any Ganjoor page id (poem, category, or poet) to its public web URL path. Useful when you have a bare numeric id from an index or a citation and need to cite or link it. Args: - id (number): Any Ganjoor page id — poem, category, or poet - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { id, url, full_url } Examples: - "What is the link for id 13836?" -> id=13836 Notes: - Ids are globally unique across poems, categories and poets on Ganjoor.

Parametersidrequiredresponse_format

ganjoor_get_redirect_url

Resolve Ganjoor Redirect

Resolve a legacy or short Ganjoor URL to its current canonical address. Ganjoor has renamed many sections over the years; this endpoint maps an old path to today's one. Args: - url (string): The URL path to resolve, with or without a leading slash - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { requested_url, redirect_url, full_url, has_redirect } — redirect_url is null and has_redirect is false when Ganjoor has no redirect for that URL (which includes URLs that are already canonical). Examples: - "Where does an old Hafez section URL go now?" -> url='hafez/divan/ghazal/sh1' Notes: - Ganjoor only keeps explicit redirects for paths it actually renamed; most valid URLs return has_redirect=false rather than a target.

Parametersurlrequiredresponse_format

ganjoor_list_rhythms

List Ganjoor Metres (Rhythms)

List every Persian prosodic metre (وزن) used in the Ganjoor corpus, with its verse-pattern notation. Use this to translate a metre name into the id Ganjoor uses, or to discover the metre vocabulary before calling ganjoor_find_similar_poems or ganjoor_analyze_poem_rhythm. Args: - page (number): Page number, starting from 1 (default: 1) - page_size (number): Items per page, 1-100 (default: 50) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { count, page, page_size, has_more, next_page, rhythms: [{ id, rhythm, verse_count }] } Examples: - "What metres exist?" -> no arguments - "Find the metre 'فاعلاتن'" -> page through results looking for the pattern Notes: - `rhythm` is the classical Persian scansion pattern; `verse_count` is how many Ganjoor poems use it. - There are roughly 50-70 metres; one page of 100 usually covers everything.

Parameterspagepage_sizeresponse_format

ganjoor_analyze_poem_rhythm

Analyze Poem Rhythm (Metre)

Detect the prosodic metre (وزن) of a poem by its numeric id. Runs Ganjoor's prosody engine over the poem's verses and returns the matched metre together with its confidence and the competing alternatives considered. Args: - poem_id (number): Numeric poem id - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { poem_id, metre: { id, rhythm }, confidence, alternatives: [{ id, rhythm, score }] } Examples: - "What metre is poem 2623 in?" -> poem_id=2623 Notes: - The detected metre may differ from the poem's stored metre for edited or composite texts.

Parameterspoem_idrequiredresponse_format

ganjoor_analyze_poem_rhyme

Analyze Poem Rhyme

Detect the rhyme letter (قافیه) and rhyme pattern of a poem by its numeric id. Args: - poem_id (number): Numeric poem id - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { poem_id, rhyme, rhyme_letter, fail_verse } — `rhyme` is the full rhyming ending as the prosody engine sees it, and `rhyme_letter` is its last character. Examples: - "What is the rhyme of poem 2623?" -> poem_id=2623 Notes: - Some poems (short texts, composite editions) have no determinable rhyme; those return rhyme=null plus a fail_verse note explaining where analysis stopped.

Parameterspoem_idrequiredresponse_format

ganjoor_find_similar_poems

Find Similar Poems By Metre And Rhyme

Find poems that share a poem's prosodic metre and rhyme letter — the basis of Ganjoor's "related poems" (شعرهای مرتبط) pages. Two Persian poems matching in metre and rhyme can be read as answers to each other (reply-poetry), so this is also how you trace how one poet answered another. Args: - metre (string): The prosodic metre pattern, exactly as listed by ganjoor_list_rhythms - rhyme (string): Optional rhyme letter filter (e.g. 'ا', 'ب'). Empty means any. - poet_id (number): Restrict to a poet id. 0 = all poets (default: 0) - cat_id (number): Restrict to a category id. 0 = all categories (default: 0) - couplet_count_min (number): Only poems with at least this many couplets (default: 0) - couplet_count_max (number): Only poems with at most this many couplets (default: 0, meaning no cap) - page (number): Page number, starting from 1 (default: 1) - page_size (number): Results per page, 1-100 (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { count, page, page_size, has_more, next_page, poems: [{ id, title, full_title, full_url, plain_text }] } Examples: - "Which Hafez poems share metre 'فاعلاتن مفاعلن فعلن'?" -> metre='فاعلاتن مفاعلن فعلن', poet_id=2 - "Poems in metre X rhyming on ب" -> metre='...', rhyme='ب' Notes: - `metre` must match the metre notation exactly; call ganjoor_list_rhythms to get valid values. - A metre with no rhyme filter returns a very large set — always page.

Parameterspagemetrerequiredrhymecat_idpoet_idpage_sizeresponse_formatcouplet_count_maxcouplet_count_min

ganjoor_get_poem_sections

Get Poem Sections

Get the structural sections of a poem — the stanzas (بند), couplets (مثنوی), and tercets (رباعی) it is built from. Ganjoor models a poem as nested sections so that poetry written in distinct forms within one poem (e.g. a ghazal that closes with a qet'e) can be analysed per-section. Args: - poem_id (number): Numeric poem id - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { poem_id, section_count, sections: [{ id, index, number, section_type, verse_type, metre, rhyme }] } Examples: - "What is the structure of poem 2623?" -> poem_id=2623 Notes: - Section `index` values feed directly into ganjoor_get_related_sections.

Parameterspoem_idrequiredresponse_format

ganjoor_get_section

Get Poem Section

Get one section of a poem by its section id, including its verses and its own related sections. Args: - section_id (number): Section id — get these from ganjoor_get_poem_sections - include_verses (boolean): Include the section's verses (default: true) - include_related (boolean): Include related sections from other poems (default: false) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: Section metadata, its metre/rhyme, optionally its verses and its related sections. Examples: - "Show me section 38101" -> section_id=38101 Notes: - A section id differs from a poem id; use ganjoor_get_poem_sections first to map between them.

Parameterssection_idrequiredinclude_versesinclude_relatedresponse_format

ganjoor_get_couplet_sections

Get Sections For A Couplet

Find which structural sections a specific couplet of a poem belongs to. A verse carries up to four nested section indices; this endpoint resolves one couplet to the stanzas that contain it, which you need when analysing a ghazal's opening or closing section. Args: - poem_id (number): Numeric poem id - couplet_index (number): 0-based couplet number - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { poem_id, couplet_index, sections: [{ id, index, number, section_type, metre, rhyme }] } Examples: - "Which section is couplet 5 of poem 2623 in?" -> poem_id=2623, couplet_index=5 Notes: - An empty list means the couplet index is out of range for that poem.

Parameterspoem_idrequiredcouplet_indexrequiredresponse_format

ganjoor_get_language_tagged_sections

Get Language-Tagged Poem Sections

List poem sections Ganjoor has tagged with a specific language, newest-scoped by poet. Ganjoor tags translated or bilingual stanzas with a language code (for example `ar` for Arabic quoted lines, or `en` for English ones). This finds those passages across the corpus. Args: - language (string): Language code, e.g. 'ar' for Arabic, 'en' for English, 'fa-IR' for Persian - poet_id (number): Restrict to a poet id. 0 = all poets (default: 0) - page (number): Page number, starting from 1 (default: 1) - page_size (number): Items per page, 1-100 (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { language, count, page, page_size, has_more, next_page, sections: [{ id, poem_id, poem_title, poet_name }] } Examples: - "Which passages quote Arabic?" -> language='ar' - "Arabic stanzas in Hafez" -> language='ar', poet_id=2 Notes: - Coverage is patchy: most Persian stanzas are untagged, so an empty result does not mean no such passage exists.

Parameterspagepoet_idlanguagerequiredpage_sizeresponse_format

ganjoor_search_recitations

Search Ganjoor Recitations

Search the published audio recitations (روایت) of Ganjoor poems, optionally by reciter or poem. Each recitation is a sung or spoken performance of one poem by a named artist, published on ganjoor.net. Args: - search_term (string): Optional text to match against the reciter's (artist's) name - poet_id (number): Restrict to a poet id. 0 = all poets (default: 0) - cat_id (number): Restrict to a category id. 0 = all categories (default: 0) - page (number): Page number, starting from 1 (default: 1) - page_size (number): Items per page, 1-100 (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { count, page, page_size, has_more, next_page, recitations: [{ id, poem_id, poem_title, poem_url, artist, title, publish_date, duration_seconds }] } Examples: - "Any recitations by محمدرضا ضیاء?" -> search_term='محمدرضا ضیاء' - "Recitations of Hafez's ghazals" -> poet_id=2 Notes: - When scoped to a poet or category, results are ordered by poem id; otherwise by publish date, newest first.

Parameterspagecat_idpoet_idpage_sizesearch_termresponse_format

ganjoor_get_recitation

Get Ganjoor Recitation

Get one published recitation by its numeric id, including its audio file location and poem reference. Args: - recitation_id (number): Recitation id (from ganjoor_search_recitations or a poem's recitation list) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { id, poem_id, poem_title, poem_url, title, artist, artist_url, audio_url, publish_date, duration_seconds, review_status } Examples: - "Details for recitation 37535" -> recitation_id=37535 Notes: - Audio URLs point at Ganjoor's CDN and are stable once published.

Parametersrecitation_idrequiredresponse_format

ganjoor_get_recitation_sync_info

Get Recitation Verse Sync Timing

Get the per-verse timing map for a recitation, showing when each couplet begins in the audio. Useful for following along with the text while a recitation plays. Args: - recitation_id (number): Recitation id - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { recitation_id, sync_point_count, total_duration_ms, sync_points: [{ verse_order, text, start_time_ms, start_time_seconds }] } Examples: - "Sync data for recitation 37535" -> recitation_id=37535 Notes: - Not every recitation has timing data; an empty list is normal for older uploads.

Parametersrecitation_idrequiredresponse_format

ganjoor_get_poem_recitations

Get Poem Recitations

List every audio recitation published for one poem. Args: - poem_id (number): Numeric poem id - page (number): Page number, starting from 1 (default: 1) - page_size (number): Items per page, 1-100 (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { poem_id, count, page, page_size, has_more, next_page, recitations: [{ id, artist, title, publish_date, duration_seconds }] } Examples: - "How has poem 2623 been recited?" -> poem_id=2623 Notes: - Text fields are intentionally blank upstream; fetch the poem itself for its text.

Parameterspagepoem_idrequiredpage_sizeresponse_format

ganjoor_get_category_top_recitations

Get Top Recitations For A Category

Get the most-liked recitations within a category — Ganjoor's "top 1" (بهترین) listings for a book. Args: - cat_id (number): Category id - include_poem_text (boolean): Include each poem's text (default: false) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { cat_id, count, recitations: [{ recitation_id, poem_id, poem_title, artist, score, plain_text? }] } Examples: - "The best-known recitations of Hafez's ghazals" -> find the ghazals category id, then call this Notes: - Ordering is by community upvote score, which changes over time. - The ghazals category for Hafez is id 10.

Parameterscat_idrequiredresponse_formatinclude_poem_text

ganjoor_list_people

List Ganjoor People

List the historical and literary figures Ganjoor has catalogued as related persons (شخصیت‌ها). These are people who appear in a poem's commentary or annotations — rulers, prophets, companions, poets' contemporaries — as distinct from poets themselves. Args: - search (string): Optional filter — only people whose name contains this text - page (number): Page number, starting from 1 (default: 1) - page_size (number): Items per page, 1-100 (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { total, count, page, page_size, has_more, next_page, people: [{ id, name, birth_year_hijri, death_year_hijri, birth_location, death_location, importance, gender }] } Examples: - "Who does Ganjoor know about?" -> no arguments - "Find the person named 'حافظ'" -> search='حافظ' Notes: - Filtering is done client-side; the full list is a few hundred entries. - `importance` ranks how central the person is; higher means better documented.

Parameterspagesearchpage_sizeresponse_format

ganjoor_get_person

Get Ganjoor Person

Get one person's full record — biography, dates, places, and external references. Args: - person_id (number): Numeric person id (from ganjoor_list_people) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { id, name, description, wiki_url, birth_year_hijri, death_year_hijri, birth_location, death_location, family_tree_caption, machine_generated, importance, gender } Examples: - "Who is person 22?" -> person_id=22 Notes: - `machine_generated` marks entries created automatically; their biographies may be sparse. - Follow with ganjoor_get_person_poems or ganjoor_get_person_relations for context.

Parametersperson_idrequiredresponse_format

ganjoor_get_person_poems

Get Poems Tagged With A Person

Get every poem Ganjoor has tagged with a given person. This is the strongest link between a historical figure and the poetry about them. Args: - person_id (number): Numeric person id - page (number): Page number, starting from 1 (default: 1) - page_size (number): Items per page, 1-100 (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { person_id, total, count, page, page_size, has_more, next_page, poems: [{ id, title, full_url }] } Examples: - "Which poems mention this figure?" -> person_id=<id> Notes: - Tagging is curated and incomplete for older figures.

Parameterspagepage_sizeperson_idrequiredresponse_format

ganjoor_get_person_relations

Get Person Relations

Get a person's kinship and affiliation edges — parents, siblings, spouses, teachers, and contemporaries. Args: - person_id (number): Numeric person id - relation_type (string): Optional filter — only relations of this kind, e.g. 'father', 'son', 'spouse', 'student', 'teacher' - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { person_id, relation_count, relations: [{ id, from_person_id, from_person_name, to_person_id, to_person_name, relation_kind, relation_name }] } Examples: - "Who was this person's teacher?" -> person_id=<id>, relation_type='teacher' Notes: - Edges are stored undirected with a kind; the same edge appears once. - Use ganjoor_get_person_family_tree for the whole connected component.

Parametersperson_idrequiredrelation_typeresponse_format

ganjoor_get_person_family_tree

Get Person Family Tree

Get the full connected kinship component reachable from a person — ancestors, descendants, spouses, and siblings. This returns the whole clan cluster, not just one hop, which makes it the right tool for tracing a figure's lineage across generations. Args: - person_id (number): Numeric person id - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { person_id, root_id, person_count, people: [{ id, name, birth_year_hijri, death_year_hijri, family_tree_caption }], relation_count, relations: [{ person1_id, person1_name, person2_id, person2_name, relation_label }] } Examples: - "Show me this figure's lineage" -> person_id=<id> Notes: - Components can be large for well-documented dynasties (the Kayumars tree has ~56 people). - Data quality varies: some edges are machine-generated and may be speculative — check the machine_generated flag per person.

Parametersperson_idrequiredresponse_format

ganjoor_get_recent_comments

Get Recent Ganjoor Comments

Get the most recent published reader comments across Ganjoor, optionally filtered. Comments are scholarly notes attached to a specific couplet of a poem. Args: - term (string): Optional text filter applied to comment bodies - filter_user_id (string): Optional UUID — only comments by this user - page (number): Page number, starting from 1 (default: 1) - page_size (number): Items per page, 1-100 (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { count, page, page_size, has_more, next_page, comments: [{ id, poem_id, poem_title, couplet_index, user, text, date, has_owner_response }] } Examples: - "What are people discussing?" -> no arguments - "Comments about 'انتظار'" -> term='انتظار' Notes: - Ordering is by recency, so results change over time — not suitable for questions whose answer must stay stable. - Replies are not included, only top-level comments.

Parameterspagetermpage_sizefilter_user_idresponse_format

ganjoor_list_faq_categories

List Ganjoor FAQ Categories

List the Ganjoor site's published FAQ (پرسش‌های متداول) categories. These are editorial explanations of how Ganjoor works — text sourcing, version differences, contribution rules. Args: - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { count, categories: [{ id, title, description, item_count }] } Examples: - "What topics does Ganjoor's FAQ cover?" -> no arguments Notes: - Follow up with ganjoor_get_faq_items using a category id.

Parametersresponse_format

ganjoor_get_faq_items

Get Ganjoor FAQ Items

Get every published question-and-answer in a Ganjoor FAQ category. Args: - cat_id (number): FAQ category id (from ganjoor_list_faq_categories) - page (number): Page number, starting from 1 (default: 1) - page_size (number): Items per page, 1-100 (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { cat_id, total, count, page, page_size, has_more, next_page, items: [{ id, question, answer, answer_excerpt }] } Examples: - "How does Ganjoor decide which text to use?" -> cat_id=<id>, then read the answers Notes: - Answers are Persian prose and may contain HTML markup from the source; return them verbatim. - Despite its name, answerExcerpt carries the complete body — fullAnswer is always empty upstream.

Parameterspagecat_idrequiredpage_sizeresponse_format

ganjoor_get_faq_item

Get Ganjoor FAQ Item

Get one Ganjoor FAQ entry by its numeric id. Args: - faq_id (number): FAQ item id (from ganjoor_get_faq_items) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { id, category_id, question, answer, answer_excerpt, pinned } Examples: - "Show FAQ item 7" -> faq_id=7 Notes: - Despite its name, answerExcerpt carries the complete body; fullAnswer is always empty upstream. - Only published items are served. An upstream 500 usually means the id does not exist — re-check the id from ganjoor_get_faq_items.

Parametersfaq_idrequiredresponse_format

ganjoor_get_poem_images

Get Poem Manuscript Images

Get the manuscript and facsimile images Ganjoor holds for a poem. These are photographs of the physical manuscripts, with a thumbnail and a full-resolution link per image — useful for checking a textual variant against its source. Args: - poem_id (number): Numeric poem id - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { poem_id, image_count, images: [{ id, url, thumbnail_url, alt, description }] } Examples: - "Show the manuscript for poem 2623" -> poem_id=2623 Notes: - Most poems have no images; an empty list is the normal case.

Parameterspoem_idrequiredresponse_format

ganjoor_get_poem_quoted_poems

Get Poems Quoted By A Poem

Get the poems that a given poem quotes or alludes to, and the poems that quote it. Matching in metre and rhyme is common in Persian poetry — poets answer each other by reusing the other's metre and rhyme. This endpoint maps those links in both directions. Args: - poem_id (number): Numeric poem id - only_both_poems (boolean): Only keep pairs where both poems claim the quotation (default: false) - published_only (boolean): Only keep published records (default: true) - main_list_only (boolean): Only keep records curated onto the main "related poems" list (default: false) - skip (number): Results to skip (default: 0) - items_count (number): How many to return. 0 or negative returns all (default: 20) - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { poem_id, total, returned, quotes: [{ id, related_poem_id, related_poet_name, related_poem_title, related_poem_url, is_prior, quoted_couplet, quoting_couplet, claimed_by_both, note }] } Examples: - "Which poems does this one answer?" -> poem_id=<id> Notes: - Each record pairs the quoted couplet with the couplet in the other poem that quotes it, so the actual verbal borrowing is visible. - `is_prior: true` means this poem came first chronologically. - `items_count=0` returns everything, which can be large.

Parametersskippoem_idrequireditems_countmain_list_onlypublished_onlyonly_both_poemsresponse_format

ganjoor_get_poem_effective_corrections

Get Poem Text Corrections

Get the editorial corrections applied to a poem's text — the before/after record of every fix. Useful for understanding why the text on Ganjoor differs from an older printed edition. Args: - poem_id (number): Numeric poem id - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { poem_id, correction_count, corrections: [{ id, verse_order, text, original_text }] } Examples: - "What corrections were made to poem 2623?" -> poem_id=2623 Notes: - Each entry pairs the corrected text with the original, per verse order.

Parameterspoem_idrequiredresponse_format

ganjoor_get_poem_geotags

Get Poem Geo Tags

Get the geographic and historical locations tagged to a single poem. A geo tag pins a poem to a place and to the Hijri/Gregorian date it commemorates — for instance a poem about a battle fought at a named site. Args: - poem_id (number): Numeric poem id - response_format ('markdown' | 'json'): Output format (default: 'markdown') Returns: For JSON: { poem_id, tag_count, tags: [{ id, latitude, longitude, lunar_date, gregorian_date }] } Examples: - "Where is the event in poem 2623?" -> poem_id=2623 Notes: - For a whole diwan, use ganjoor_get_category_geotags instead.

Parameterspoem_idrequiredresponse_format

Version history

2
  1. v0.1.1LatestOct 23, 2026
  2. v0.1.0Oct 15, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts