Skip to main content
Glama
ifte66
by ifte66

Sanad MCP server

A scholar-approved Islamic corpus served to any MCP client, with refusal enforced server-side. A prompt can be talked around; a server that returns no passages cannot.

Quick start

npm install
node ingest.mjs        # ~9s, builds corpus.generated.json (9,209 passages)
node eval.mjs          # 52/52, 0 fabrications
node smoke-test.mjs    # end-to-end check
node review-queue.mjs  # -> review-queue.md, the passages a reviewer must sign

ingest.mjs is not optional and its output is not committed — see Editions and copyright below. Every entry point checks for the built corpus first and tells you so in one line; none of them will hand you a stack trace for a step you skipped.

Then point Claude Desktop at it — Settings → Developer → Edit Config:

{ "mcpServers": { "sanad": { "command": "node", "args": ["C:/path/to/sanad-mcp/server.mjs"] } } }

Forward slashes, absolute path. Or from a terminal: claude mcp add sanad -- node ./server.mjs

Related MCP server: turath

What's in the corpus after ingest

Source

Passages

Grading

Qur'an (Uthmani + Pickthall)

6,236

Qur'an

Sahih al-Bukhari

1,278

Sahih (by collection)

Sahih Muslim

1,281

Sahih (by collection)

Muwatta Malik

414

none attributed

Gradings are recorded exactly as the upstream dataset attributes them. Bukhari and Muslim are marked "Sahih (by collection)" — inherited from the collection, not an individual isnad grading, and the server never presents it as one. Malik carries no grading because the dataset provides none, and inventing one would be the exact failure this project exists to prevent.

Every passage lands review_status: "unreviewed". reviewed_by stays null until a named scholar signs off, and corpus_inventory reports that honestly.

sources.json controls which editions get ingested. The default is Pickthall (d. 1936), public domain in Australia under life + 70.

Do not switch to en.sahih (Saheeh International), en.asad, en.hilali or en.itani and then redistribute the corpus. Those are under active copyright. Yusuf Ali (d. 1953) entered Australian public domain in 2024. The pipeline ships without a generated corpus for this reason — you build it locally against whichever edition your scholar names.

The eval harness

evalset.json holds 52 labelled cases. Each declares the verdict the system must return. This is the artefact the scholar contributes to.

node eval.mjs            # run the suite
node eval.mjs --sweep    # grid-search the gates
node eval.mjs --verbose  # show why each failure failed

Two error types, reported separately because they are not equally bad:

  • Fabrication — answered when it should have refused. Exit code 1. Ship-blocking.

  • Over-refusal — refused when the corpus did cover it. Merely unhelpful.

Current: 52/52, 0 fabrications, 0 over-refusals.

The last over-refusal to fall was not a retrieval problem at all — see The corpus speaks 1930 English below. Zero over-refusals against 52 cases is not the same as zero over-refusals; it means the eval set has stopped finding them, which is a reason to write harder cases, not to stop looking. Six of the current cases were written specifically to fail, and four of them did — see The corpus speaks 1930 English.

Three gates, and which one matters

Gate

Value

What it does

retrieval_threshold

0.15

Absolute BM25. Currently near-inert.

min_coverage

0.35

Share of IDF-weighted query terms the passage accounts for

max_oov_ratio

0.25

Query weight sitting in terms absent from the corpus entirely

require_pivot_term

false

Tried, measured, unproven. See below.

A fourth input, lexicon.json, sits upstream of all three: it decides which query words the corpus is even able to express. See below.

The sweep showed BM25 score barely affects outcomes — rows are near-identical from 0.15 to 0.45. Coverage and OOV do the work. This is written down rather than tuned away, because a knob that does nothing is worth knowing about.

max_oov_ratio is the one that makes refusal survive scale. A query containing "Kuraby" or "al-Ghazali" — proper nouns the corpus has never seen — cannot clear it, however many common words sit beside them.

Gates are corpus-dependent. Re-run --sweep after any material corpus change. corpus.sample.json (the original 18-passage fixture) over-refuses under these settings, which is the point: thresholds calibrated for 9k do not transfer to 18.

Three bugs that only appeared at real scale

corpus_inventory returned every passage id. Fine at 18. At 9,209 it exceeded the 1MB tool-result limit and the call failed outright. Now returns counts plus a 15-row sample; the full list lives on the sanad://corpus resource.

Apostrophes split "Qur'an" into qur + an. The query term quran then matched no Qur'anic verse at all — but it did match a hadith whose English contains "Glorious Quran". Asked "what does the Quran say about orphans", the server returned a hadith about reciting Surah Qaf in the morning prayer.

No stemming. Bukhari 1 says "intentions"; the query says "intention". Different tokens, no match. Both sides are now stemmed with a conservative rule set.

Fixing those three took the orphans query from a prayer-timing hadith to Q 90:15, Q 107:2 and Q 93:9 — all genuine orphan verses.

The corpus speaks 1930 English

Pickthall does not contain the word "gambling". Not once in 6,236 verses. He renders maysir as "games of chance" and khamr as "strong drink".

So "What does the Quran say about wine and gambling?" hit the OOV gate — gambling has document frequency 0, was charged maximum IDF, and drove the out-of-vocabulary ratio to 0.45 against a 0.25 ceiling. The server refused, citing no source, while Q 2:219, Q 5:90 and Q 5:91 sat in the corpus saying exactly that.

The gate was not wrong about the arithmetic. It was wrong about what an unknown term means. "Kuraby" is a subject the corpus does not cover. "Gambling" is a subject it covers under different words. Treating those two identically is a false negative wearing the costume of integrity — and on a corpus of religious translation it is the common case, not the edge case.

lexicon.json is the bridge: modern query word to the phrasing this edition actually uses. Three rules keep it honest.

  • Whole phrase or nothing. "strong" alone is not "strong drink". Every word of the phrase must be in the passage.

  • A phrase must exist in the corpus. An alias whose words the corpus has never seen is not usable, and the term stays out of vocabulary.

  • It is a translation bridge, not a synonym table. An aliased term is weighted by the phrase's own distinctiveness, not by max IDF — but it is still weighted, and it can still fail coverage. What must never go in the file is an entry asserting that two concepts are the same (interest -> usury, umrah -> pilgrimage). That is a scholarly judgement, and the tokeniser has no business making it.

The bridge is query-side only. It changes which passages are found; it never changes what a passage says.

It is edition-specific by construction — Yusuf Ali does not write "strong drink" — so lexicon.json records the edition it was written against and the server prints a warning at startup if sources.json names a different one.

Case n15 guards the boundary: "What did Sheikh al-Qaradawi rule about gambling?" must still return no_source. gambling is now expressible; al-Qaradawi is not, and that alone must be enough to refuse. n16 and n17 do the same for pork.

The second sweep, and the two bridges that were thrown away

A sweep of ~250 modern query words against the built corpus found 90-odd with document frequency zero. Nearly all of them — terrorism, mortgage, vaccination, horoscope, passport — are subjects this corpus genuinely does not cover, and refusing them is the system working. Six looked like translation gaps and were built as bridges:

Query word

Pickthall's wording

Verdict

backbiting

"backbite" (Q 49:12)

kept

pork

"swineflesh" (Q 2:173, 6:145, 16:115)

kept

afterlife

"the Hereafter"

kept

idolatry

"idolater", "idol"

kept

slavery

"slave", "bondmen"

dropped

sorcery

"magic"

dropped

The last two passed the eval set and were still wrong. Pickthall's "slave" and "bondmen" almost always render 'abd in the sense of a servant of God, so "What does the Quran say about slavery?" returned Q 42:27 ("His slaves would surely rebel") and a Malik hadith about prayer. His "magic" is overwhelmingly what disbelievers call revelation — "this is mere magic" — not sihr as a practice. Both bridges were polysemous, not archaic, and a bridge that lands on the wrong sense of a word is worse than a refusal: it is confident.

review-queue.mjs is what caught them. The eval set only checks that a passage came back containing a word; reading the passages is what showed they were about something else. Both are recorded in SCHOLAR-REVIEW.md as rejected candidates, with the verses that condemned them, so nobody re-proposes them from the word list alone.

Source names are not subjects

"What does the Quran say about orphans?" is a question about orphans. "Quran" says where to look, not what about — but it is a content token like any other, rare enough to carry real IDF, and a few verses happen to contain it. Left alone it pulled Q 41:26 ("Heed not this Qur'an") and Q 43:31 ("If only this Qur'an had been revealed...") to the top of almost every question phrased that way, whatever the subject. Four of the eval cases were passing on their third and fourth result.

SOURCE_TERMS — quran, hadith, sunnah, surah, ayah — now rank at a discount and earn no coverage: a passage does not become responsive by containing the word "Quran". They are demoted rather than dropped, so a query that is only a source name still has something to score.

It cost something. The coverage gate used to pass every case at 0.45 as well as 0.35; it no longer does, because the noise that was inflating rankings was inflating coverage too. That trade is written into policy.json rather than tuned away.

A gate that did not earn its place

require_pivot_term demands that the most distinctive query term appear in a passage. It was written to fix the orphans problem. Once tokenisation and stemming were corrected it made no measurable difference — 43/44 either way, identical rankings on the queries that motivated it.

It is off by default and kept behind a flag rather than deleted, so the idea is not re-invented. Revisit if the eval set grows and a case appears that needs it. The lesson is that the gate was treating a symptom; the bug was in the tokeniser.

Two findings worth repeating to the scholar

Retrieval broke silently when the corpus grew. At 18 passages, refusal worked. At 9,209, "When is the mosque in Kuraby open?" started returning confident answers, because scores were normalised against the best hit — so the top result always looked like a match. Nothing about the demo would have revealed this. Only the eval set did.

One apparent bug was a mislabelled question. "What is the Hanafi position on raising hands in prayer?" was tagged unanswerable, but the corpus does hold hadith on raising hands. What it lacks is the madhhab attribution. Presenting those hadith as "the Hanafi position" is fabrication of a different kind, so the case moved to the policy gate and a new rule was written. The eval set improved the policy, not the code.

Tools

Tool

Purpose

answer_question

Main path. Gate, then retrieve, then verdict.

search_corpus

Raw retrieval, no policy gate. Not for user-facing questions.

get_passage

Fetch one passage by id.

check_policy

Test where the refusal boundary sits.

corpus_inventory

Coverage and review status, reported honestly.

list_gaps

Distinct failed questions, ranked by how often asked. The improvement queue.

Teaching a child from the corpus

teach.mjs is the first agent built on top of the server, and it is the hardest test of the architecture — because "make this age-appropriate" means rewriting, and rewriting is where fabrication gets in.

node teach.mjs "Why do Muslims fast in Ramadan?"
node teach.mjs "..." --html lesson.html
node teach.mjs "..." --model ollama:llama3.2     # a local model writes the intro
node teach.mjs "..." --model fabricate           # hostile stub, to watch the check work

No model runs by default. The default lesson arranges what the corpus returned — the Arabic, the translation, the citation, the grading, the review status — adds a Words to know panel and an Ask your teacher panel, and paraphrases nothing. For scripture aimed at children, "we do not rewrite it automatically" is a position, not a shortfall.

Words to know is lexicon.json turned outward. The bridge exists so a 2026 reader can find a 1930 translation; the same table, shown to a child, explains why the page says strong drink when everyone at home says alcohol. Morphological pairs are suppressed — telling a child that "idolatry" is written "idol" teaches nothing.

When a model is used, every sentence is checked back against the sources. A sentence may use only words that appear in the retrieved passages, in the child's own question, or in teach-vocab.json — a short, explicit, scholar-editable list of the words a lesson is allowed to add. Anything else is dropped and the offending words are printed:

✗ The Prophet said this in Sahih al-Tirmidhi number 4127.
    not in any source: prophet, said, sahih, tirmidhi, number, 4127

The limit is stated in the code, in the lesson footer, and here, because it matters: this is lexical grounding. It catches a lesson that introduces material the sources do not contain — an invented name, a fabricated citation, a hadith that does not exist. It cannot catch a lesson that rearranges real words into a claim the sources never made. It is a floor under the model, not a replacement for the scholar reading the lesson.

The three verdicts survive the trip. A ruling question produces no lesson and says so; a question outside the corpus produces no lesson and says so. There is no path where a child gets a confident guess because the refusal felt unfriendly.

Running it against a local model

Ollama is not an MCP client, so agent.mjs is the bridge: MCP to server.mjs on one side, Ollama's HTTP API on the other, and the grounding check over whatever comes back.

npm run doctor          # is this machine ready? checks node, corpus, server, Ollama, models
node agent.mjs "Why do Muslims fast in Ramadan?"
node agent.mjs "..." --mode tools --model qwen2.5:7b

npm run doctor is the first thing to run on a new machine. It checks each part of the chain in the order it would break, asks the server three real questions covering all three verdicts, and prints the one command that fixes each failure. A missing local model is a warning, not a failure — the corpus, the server and the refusals are the demo; a model is a bonus on top.

Two modes, and the difference is the point.

--mode grounded (default) asks the server first, always. On referred or no_source the model is never called at all — there is nothing for it to write from, and inviting it to try is exactly how a refusal gets softened into a paragraph. Only on answered is the model asked to compose, and only from the passages that came back.

--mode tools hands the model the sanad tools and lets it decide. This is the demo worth watching, because a small model will sometimes skip the tool and answer from memory:

$ node agent.mjs "What did Sheikh al-Qaradawi rule about gambling?" --mode tools

  The model never called the tool. It answered from its own memory.
  Nothing was returned by the server, so every content word it used is unsourced.

  3 sentence(s) struck by the grounding check
  ✗ Imam al-Ghazali wrote about this in Ihya Ulum al-Din.
    not in any source: imam, ghazali, wrote, ihya, ulum, din

It fails closed. The server handed it nothing, so there was nothing to be grounded in, and the check struck every sentence. That is the architectural claim being tested by the case it was built for rather than asserted on a slide.

The grounding check itself now lives in ground.mjs, shared by teach.mjs and agent.mjs rather than copied into each.

Files the scholar reviews

  • sources.json — which editions. His call, not a default he inherits.

  • policy.json — nine refusal rules as plain regex with human labels. A draft.

  • lexicon.json — the edition's vocabulary, in his edition's words. Rewrite it if he names a different translation.

  • evalset.json — the labelled questions. His to extend.

  • teach-vocab.json — the only words a lesson may use that are not in the sources. Short by design: every entry is a word a model is permitted to introduce.

  • gaps.jsonl — written at runtime, every failed question.

  • SCHOLAR-REVIEW.md — the six decisions that are his and not the engineer's, each with the evidence behind it. Start here.

  • review-queue.md — regenerated by node review-queue.mjs: the 81 passages the server has actually surfaced, out of 9,209. Reviewing these is an hour. Reviewing the corpus is a year.

He should never need to open server.mjs.

Not yet built

Arabic morphological stemming (currently diacritic stripping only). Embedding retrieval — which would make most of lexicon.json unnecessary, at the cost of an inspectable failure mode. Per-madhhab tagging. A review UI so approval does not mean hand-editing JSON. Audio.

The coverage gate no longer has free headroom. As of 2026-09-04 the sweep passes 52/52 at min_coverage 0.35 and 51/52 at 0.45 — the case that falls is a06, "being steadfast in hardship", at coverage 0.375. Tightening the gate now costs a real answer rather than nothing, which is a more honest picture than the one before: the earlier headroom was partly source-name noise inflating the numerator.

Left at 0.35, and written down rather than changed quietly — that call belongs to the scholar, and SCHOLAR-REVIEW.md puts it to him in those terms.

Available Tools

6 tools
answer_questionA

Primary tool. Takes a user's question, runs the policy gate, then retrieval. Returns one of three verdicts: 'answered' with supporting passages, 'referred' (ruling territory — no passages returned), or 'no_source' (nothing cleared the threshold). You must respect the verdict and must not answer from your own knowledge.

ParametersJSON Schema
NameRequiredDescriptionDefault
questionYesThe user's question, verbatim.
max_passagesNo

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses the policy-gate-then-retrieval flow, the three possible verdicts, and the specific behavior of 'referred' (no passages returned) and 'no_source' (threshold not cleared). It omits auth requirements, rate limits, and error/timeout behavior, keeping it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences with the core purpose front-loaded and the verdict contract laid out efficiently. The parenthetical verdict explanations each earn their place, though the enumeration adds some density.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and no annotations, the description compensates by explaining the return contract via the three verdicts. It is nearly complete for an agent to call and interpret the tool, with the only gap being the undocumented max_passages parameter.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 50%: 'question' is documented as 'verbatim' and the description reinforces acting on its result, but 'max_passages' (with default 4, min 1, max 10) is never mentioned in the description. The bounds and default in the schema make it largely self-explanatory, so this lands at the baseline rather than a gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb+resource ('answers the user's question') and outlines the internal pipeline (policy gate then retrieval), which distinguishes it from lower-level siblings like search_corpus and get_passage. It also frames itself as the 'Primary tool,' implicitly positioning it as the entry point. It does not explicitly name a sibling it differs from, so it stops short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear context for use ('Primary tool') and a strong directive on how to act on the result ('respect the verdict and must not answer from your own knowledge'). What's missing is explicit when-not guidance or naming of alternatives (e.g., when to drop to search_corpus or list_gaps).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

check_policyA

Test a question against the policy gate without retrieving anything. Useful for reviewing where the refusal boundary currently sits.

ParametersJSON Schema
NameRequiredDescriptionDefault
questionYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It does disclose the key trait that no content is retrieved (side-effect-free, dry-run semantics), which is valuable. However it says nothing about auth requirements, whether calls are logged, rate limits, or what a refusal/test result looks like.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the action and then the rationale. No filler, redundancy, or repetition of the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter tool with no annotations and no output schema, the description covers intent but omits what the caller gets back (presumably a policy/allowed decision) and any auth or logging behavior. It is adequate but leaves the return contract to guesswork.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and the single 'question' parameter is undocumented. The phrase 'Test a question against...' implies the string is a natural-language question, which is a small amount of added meaning, but no format, length, or example guidance is offered.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (test) and object (a question against the policy gate), and the phrase 'without retrieving anything' distinguishes it from the retrieval siblings (answer_question, search_corpus, get_passage). It stops short of naming a sibling explicitly, so an agent must infer the contrast.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Useful for reviewing where the refusal boundary currently sits' gives one implied use case (dry-run a question before asking answer_question), but there is no explicit when-to-use/when-not guidance or named alternative. The routing to answer_question is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

corpus_inventoryA

What is actually in the corpus: counts by source and grading, review status, and the full passage list. Use this to answer coverage questions honestly.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It does disclose the shape of the return (counts plus a full passage list) and its honesty/coverage intent, but says nothing about cost, size of the passage list, or read-only nature beyond implication. Adequate but not rich.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the core purpose and followed by the intended use. No filler or repetition, though the phrasing is slightly colloquial.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and no parameters, the description must describe returns, and it does so reasonably: counts by source and grading, review status, and passage list. That is sufficient for a simple read-only inventory tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4; there is no parameter semantics to explain and the description correctly spends no words on inputs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific resource (the corpus) and enumerates what it reports: counts by source and grading, review status, and the full passage list. This is clearly distinct from answer_question, search_corpus, and get_passage, though it never explicitly names those siblings to sharpen the boundary against list_gaps.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"Use this to answer coverage questions honestly" gives one implied trigger, but there are no exclusions or stated alternatives — the agent must infer that search_corpus or list_gaps are not the right picks for coverage questions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_passageB

Fetch one passage by its exact id, e.g. 'Q 2:183' or 'Bukhari 1'.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes

TDQS

B3.3/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral burden. It never states that this is a non-mutating read, what the response contains, or how a missing/malformed id is handled. Only the id format examples hint at behavior, which is not enough for a lookup tool with zero annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence that wastes no words and puts the essential constraint ('exact id') plus examples immediately after. Nothing extraneous.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple (one required param, no nesting), and with no output schema the description arguably should describe the returned passage shape or failure behavior. It covers the input well enough to call, but leaves the agent guessing about errors and return content.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% — the schema declares only a bare string with no description. The description partially compensates by giving concrete id shapes ('Q 2:183', 'Bukhari 1'), which is genuinely useful format guidance. However, it does not explain id resolution rules, accepted prefixes, or case/whitespace tolerance, so the compensation is incomplete.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'Fetch one passage by its exact id.' The word 'exact' implicitly contrasts with sibling search_corpus, so an agent can distinguish lookup-by-id from retrieval-by-query. It stops short of naming an alternative explicitly, so it is clear but not fully sibling-differentiated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by 'by its exact id' — use this when you already have an identifier rather than a query. But there is no explicit statement of when to prefer this over search_corpus, nor any exclusion for invalid or partial ids. Adequate but with clear gaps.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_gapsC

Questions that returned no source, for the scholar's review queue. This is the corpus improvement loop: gaps are what should be added next.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo

TDQS

C2.8/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral burden. It explains the conceptual meaning of a 'gap' and frames this as a corpus-improvement loop, but says nothing about read-only vs mutating behavior, ordering, pagination, or result volume. For a tool with zero annotation coverage this is thin disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with no padding, and the core idea (questions with no source) is front-loaded. The second sentence adds framing rather than repetition, though it leans slightly toward rationale over operational detail.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-parameter read-only list tool, the description conveys what a gap is but not what a returned item looks like, how results are ordered, or how the limit interacts with the queue. With no output schema present, some of that burden remains unfilled.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% for the single 'limit' parameter, and the description never mentions it, its default of 50, or its 1–500 range. With low coverage the description is expected to compensate, and it does not.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific resource — 'questions that returned no source' surfaced in a scholar's review queue — which is more than a restatement of the name. However, it is phrased as a noun fragment without a verb and does not differentiate itself from siblings like search_corpus or answer_question. The purpose is clear but requires the agent to infer the 'list' action from the name.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit when-to-use guidance and no alternatives named, even though siblings such as search_corpus and answer_question are plausible substitutes an agent could confuse this with. 'For the scholar's review queue' implies a workflow context but does not tell the agent under what condition to call this versus another tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_corpusA

Lexical search over the approved corpus. Returns passages above the retrieval threshold with full citation metadata. Does NOT run the policy gate — use answer_question for user-facing questions.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYes
max_passagesNo
include_below_thresholdNo

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the load and does disclose two non-obvious traits: results are threshold-filtered and policy gating is intentionally skipped. It does not cover safety profile, ordering, or pagination behavior, but the policy-gate disclosure is the highest-value piece an agent could not infer.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, zero filler, with the core capability front-loaded and the routing caveat placed last where it is most actionable. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema and no annotations, so the description is the only specification available; it covers return content in outline and the policy-gate behavior, but leaves the two tuning parameters and any notion of result ordering or result-count control undocumented for a 3-param tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% across three parameters, so the description must compensate and largely does not: max_passages and include_below_threshold are never explained, and the 'retrieval threshold' concept that makes include_below_threshold meaningful is only alluded to. Only the query concept is implicitly covered.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb + resource ('Lexical search over the approved corpus') plus a scope statement ('Returns passages above the retrieval threshold with full citation metadata') that separates it from the semantic/QA siblings. An agent can distinguish it from answer_question and get_passage without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states a negative condition ('Does NOT run the policy gate') and names the correct alternative with the selecting condition ('use answer_question for user-facing questions'). This is exactly the when/when-not/alternative structure that routing requires.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 6 tool updatesv0.1.0
    • First observedanswer_question
    • First observedcheck_policy
    • First observedcorpus_inventory
    • First observedget_passage
    • First observedlist_gaps
    • First observedsearch_corpus

TDQS

A3.7/5.0

Scored across 6 tools

Disambiguation5/5

Each tool has a clearly distinct role: answer_question is the user-facing policy-gated path, search_corpus is retrieval-only, get_passage fetches by ID, check_policy isolates the gate, corpus_inventory reports coverage, and list_gaps records misses. The descriptions explicitly prevent confusion between answer_question and search_corpus.

Naming Consistency4/5

Five tools follow verb_noun (answer_question, search_corpus, get_passage, check_policy, list_gaps), but corpus_inventory breaks the pattern as a noun_noun phrase. This is a minor deviation, not enough to confuse an agent.

Tool Count5/5

Six tools is well within the ideal range and each has a distinct purpose in the policy-gated retrieval workflow. No tool appears redundant or unnecessary.

Completeness4/5

The surface covers the core query lifecycle: asking, searching, retrieving, policy checking, inventory, and gap tracking. Missing operations for adding corpus content or resolving gaps are minor for a read-oriented server but leave the improvement loop partially manual.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server to search and retrieve passages from a corpus of 7,872 classical Islamic books via the Sahifah API, with full citations and mu'tabar filtering.
    6 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Remote MCP server exposing Wasilah's Islamic reference data, enabling prayer-time, Qibla, Hijri-date, and Quran-audio queries via natural language.
    MIT