Sakina Islamic Knowledge MCP Server

Scholar-reviewed Islamic knowledge for AI agents. Quranic verses, authenticated du'as, and the 99 Names of Allah — verified across the four mainstream Sunni schools (Hanafi, Maliki, Shafi'i, Hanbali). Every response is wrapped in a presentation contract so agents cannot silently misrepresent the content.
Architectural posture: "Sakina ships records, agents ship answers." The server is a reference library, not an advisor. Each response includes a _sakina_meta envelope with disclaimer, LLM directives, presentation contract, and educational context. What the calling agent does with the record is its responsibility — but every response arms the agent with enough structural context to make mishandling difficult and trackable.
What it provides
Three lookup tools and one canonical resource over the Model Context Protocol. Every record is sourced from Sakina's main app (asksakina.com), so the MCP server can never drift from what the public app surfaces.
get_quran_verse | Verbatim Quranic verse lookup by surah:ayah |
get_dua | Authenticated du'a lookup by life context (anxiety, grief, morning, travel, ...) |
get_name_of_allah | One of the 99 Names by number (1–99) or string |
sakina://about (resource) | Read-once briefing — seven core directives plus three Gem-cleared dawah texts (Quran preservation, hadith grading, madhab attribution) |
Tools
get_quran_verse
Verbatim Quranic verse lookup by surah:ayah. Returns the canonical Sakina envelope with Arabic text, translation, surah name (Arabic + English), and the quran presentation contract — no paraphrasing, surah:ayah citation required, "Translation of the Meaning" labelling.
Parameters
surah | 1..114 | yes | Surah number |
ayah | 1..286 | yes | Ayah within the surah |
locale | 'en' | 'id' | 'ur' | no, defaults en | Translation language |
Example response (truncated)
{
"_sakina_meta": {
"version": "1.0",
"content_type": "quran_verse",
"presentation_contract": {
"quran": {
"no_paraphrase": true,
"require_citation": "surah:ayah",
"translation_label": "Translation of the Meaning"
}
}
/* … */
},
"content": {
"surah": { "number": 2, "name_arabic": "البقرة", "name_english": "Al-Baqarah" },
"ayah": 186,
"arabic": "وَإِذَا سَأَلَكَ عِبَادِي عَنِّي فَإِنِّي قَرِيبٌ ۖ …",
"translation": "And when My servants ask you concerning Me — indeed I am near. …"
}
}
get_dua
Returns du'as matching a life context (anxiety, grief, morning, travel, etc.). Context is resolved against canonical category slugs, an alias map, and tag dimensions. If the context contains a crisis keyword (matched against the main app's detectCrisis keyword list), the response includes a mandatory crisis_resource block with the appropriate hotline and prosocial directive.
Parameters
context | string | yes | Free-form life context |
locale | 'en' | 'id' | 'ur' | 'ar' | no, defaults en | Translation language |
Example response (truncated)
{
"_sakina_meta": { "content_type": "dua_collection" /* … */ },
"content": {
"category": "anxiety",
"duas": [
{
"arabic": "اللَّهُمَّ إِنِّي أَعُوذُ بِكَ مِنَ الْهَمِّ وَالْحَزَنِ …",
"transliteration": "Allahumma inni a'udhu bika minal-hammi wal-hazan …",
"translation": "O Allah, I seek refuge in You from grief and sadness …",
"source": "Sahih al-Bukhari 2893"
}
/* … */
]
}
// crisis_resource block added if context triggers the crisis keyword list
}
get_name_of_allah
Look up by number (1–99) or string (transliteration, Arabic, or English meaning). Returns Arabic, transliteration, locale-aware meaning, reflection, and Quranic references.
Parameters — one of:
number | 1..99 | Primary lookup |
name | string | Transliteration, Arabic, or English meaning |
Plus optional locale.
Example response (truncated)
{
"_sakina_meta": { "content_type": "name_of_allah" /* … */ },
"content": {
"number": 29,
"arabic": "الحَكَم",
"transliteration": "Al-Hakam",
"meaning": "The Judge",
"reflection": "Allah is the absolute Judge…",
"quran_references": ["6:114", "13:41"]
}
}
Resource: sakina://about
Read this resource before using the tools. It contains the seven core directives plus three Gem-cleared educational dawah texts. Most MCP clients fetch it automatically the first time they connect.
Response envelope
Every tool returns the same shape:
{
"_sakina_meta": {
"version": "1.0",
"source": "Sakina Islamic Knowledge Server (asksakina.com)",
"content_type": "quran_verse" | "dua_collection" | "name_of_allah" | "not_found",
"disclaimer": "Sakina provides verified Islamic reference content for educational purposes. …",
"llm_directives": { "CRITICAL_RULES": ["…"] },
"presentation_contract": {
"quran": { "no_paraphrase": true, "require_citation": "surah:ayah" /* … */ },
"hadith": { "require_grading": true, "require_source": true /* … */ },
"name_of_allah": { "no_paraphrase_arabic": true /* … */ }
},
"educational_context": "…"
},
"content": { /* tool-specific record */ },
// get_dua only, when crisis keywords detected:
"crisis_resource": { "directive": "…", "text": "…" }
}
The envelope is the contract: _sakina_meta.presentation_contract tells the agent how to render the record, disclaimer tells the agent what claims it cannot make on its own authority, and crisis_resource overrides everything when a user's distress crosses a safety threshold.
Quick start
Run via npx (no install)
npx -y @asksakina/islamic-knowledge-mcp
The package speaks MCP over stdio out of the box — wire it into any MCP client.
Connect via remote streamable HTTP
https://sakina-mcp.fly.dev/mcp
For agents that prefer a hosted endpoint over a local subprocess. Same tools, same envelope, same rate limits.
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%/Claude/claude_desktop_config.json (Windows):
{
"mcpServers": {
"sakina": {
"command": "npx",
"args": ["-y", "@asksakina/islamic-knowledge-mcp"]
}
}
}
Restart Claude Desktop. The three tools and the sakina://about resource appear in the tool picker.
Cursor
Add to ~/.cursor/mcp.json (or workspace .cursor/mcp.json):
{
"mcpServers": {
"sakina": {
"command": "npx",
"args": ["-y", "@asksakina/islamic-knowledge-mcp"]
}
}
}
VS Code (with an MCP-aware extension)
Add to .vscode/mcp.json in your workspace:
{
"servers": {
"sakina-islamic-knowledge": {
"command": "npx",
"args": ["-y", "@asksakina/islamic-knowledge-mcp"]
}
}
}
Generic MCP client over remote HTTP
{
"mcpServers": {
"sakina": {
"type": "streamable-http",
"url": "https://sakina-mcp.fly.dev/mcp"
}
}
}
Use cases
The MCP server is designed for any agent or application that needs verified Islamic source material with a hard guarantee against silent misrepresentation.
- Build an Islamic chatbot with verified sources. Pipe
get_quran_verse and get_dua into your conversation flow; the presentation contract keeps the agent honest about what is direct revelation, what is hadith with a grading, and what is editorial reflection.
- Add prayer-time-aware spiritual content to your agent. Combine the Quranic and du'a tools with a prayer-time API to surface Allah's words at the right moment of the day.
- Recommend du'as based on user context. The crisis-keyword filter in
get_dua handles the dangerous edge cases (self-harm language, abuse) so your assistant never replies with a generic du'a to a safety-critical message.
- Answer questions about the 99 Names.
get_name_of_allah returns the canonical Arabic, transliteration, meaning, and reflection — plus Quranic references — for both number-based and name-based lookups.
- Stay theologically inclusive. Every record is reviewed across Hanafi, Maliki, Shafi'i, and Hanbali positions. The server never positions a contested ruling as the universal answer.
Content integrity
Every piece of content surfaced by this server has been reviewed by Sakina's "Gem" specialist-AI review chain before shipping. The chain has nine reviewers; four of them gate every Islamic-knowledge release:
- Quran & Translation Verification. Ayah accuracy, surah/ayah citation correctness, hadith grading, source authentication.
- Fiqh Diversity (Multi-Madhab). Cross-school accuracy, no single-madhab framing on contested topics, inclusive language.
- Islamic Psychology & Pastoral Care. Comfort window safety, crisis filter coverage, no guilt-based motivation.
- Explorer / New-to-Islam Accessibility. Glossing of Arabic terms for non-Muslim audiences, plain-English fiqh, zero-assumed-belief reflections.
Architectural guarantees enforced in code:
- The Unicode prophet salutation (U+FDFA, ﷺ) is replaced with
(peace be upon him) at every data-loader boundary. The response builder rejects any output containing the symbol.
- Hadith grading is paired with each du'a record; the
hadith presentation contract asserts require_grading: true.
- Crisis keyword detection runs on every
get_dua request — the same keyword list used by the main app's safety gate.
Sacred Use License. This server is distributed under a Sacred Use License (see LICENSE). Permitted uses centre on dawah, education, personal worship, and respectful integration into Muslim-serving applications. The license forbids monetisation that frames Islamic knowledge as scarce or paywalled. A public summary of the license terms lives at https://www.asksakina.com/en/mcp.
Privacy. No tracking. No analytics. No request logging beyond rate-limit counters. The server records the IP for the rate-limit window only; nothing else is persisted.
Running locally for development
cd mcp-server
npm install
npm run bundle-data
npm run typecheck
npm run start
npm run test:tools
npm run build
npm run start:dist
POST /mcp accepts standard MCP JSON-RPC 2.0. GET /health returns { "status": "ok", "name", "version" }.
Rate limiting
Default: 60 requests per minute per IP.
UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN set → distributed Upstash Redis fixed window.
- Otherwise → in-memory token bucket, per process.
Responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. A 429 with code -32029 is returned on exceed.
Authentication
v1 ships unauthenticated. An optional X-Sakina-App-Id header is accepted but not enforced or logged in v1. This becomes required in v2.
Analytics (1.2.0)
Every tool call is logged as a single JSONL line:
{
"ts": "2026-05-17T11:00:00.000Z",
"tool": "get_dua",
"params": { "context": "anxiety", "locale": "en" },
"status": "ok",
"duration_ms": 42,
"response_time_ms": 42,
"error": false,
"country_code": "GB", // ISO 3166-1 alpha-2, from geoip-lite
"region": "England", // when MaxMind has it
"client": "claude-desktop", // classified bucket
"user_agent": "Claude-Desktop/1.2.3" // truncated, 200 char cap
}
No raw IP addresses are ever persisted. The client IP is resolved once per request to (a) key the rate-limiter and (b) look up country / region via the embedded MaxMind GeoLite2 database, then discarded. The full User-Agent is stored (truncated) so we can spot a new MCP client and extend the classifier; the client bucket is what /stats aggregates.
The context parameter on get_dua is a category keyword per tool contract ("anxiety", "morning", "grief") and is logged (200-char cap) so we can answer "which life situations do agents query most" without ever associating it with a person.
Logs land in LOG_DIR (default /data/logs, persisted on a Fly volume). One file per UTC day: requests-YYYY-MM-DD.jsonl. Writes are fire-and-forget — disk slowness never bleeds into tool response latency.
Updating the GeoIP database
geoip-lite ships an embedded MaxMind GeoLite2 snapshot. Geography drifts, so refresh monthly:
cd mcp-server
MAXMIND_LICENSE_KEY=<key> npm run update-geoip
A free MaxMind license key is required (sign up at maxmind.com/en/geolite2/signup). After running, rebuild + redeploy so the new DB ships with the container. The bundled DB at install time is sufficient for an initial launch.
/stats endpoint
Authenticated summary endpoint:
curl -H "Authorization: Bearer $MCP_STATS_TOKEN" https://sakina-mcp.fly.dev/stats
Requires the MCP_STATS_TOKEN env var to be set (otherwise the endpoint returns 503 — refuses to serve unauthenticated). Token comparison is constant-time.
Payload shape:
{
"generated_at": "2026-05-17T...",
"log_dir": "/data/logs",
"totals": { "all_time": N, "last_7d": N, "last_24h": N },
"by_tool": { "get_quran_verse": {...}, "get_dua": {...}, "get_name_of_allah": {...} },
"by_status": { "ok": N, "not_found": N, "error": N },
"error_rate": { "all_time": 0.0, "last_7d": 0.0, "last_24h": 0.0 },
"avg_response_time_ms": {
"all_time": 42.1, "last_7d": 41.8, "last_24h": 39.5,
"by_tool": { "get_quran_verse": {...}, "get_dua": {...}, "get_name_of_allah": {...} }
},
"top_contexts": [{ "context": "anxiety", "count": N }, ...],
"top_verses": [{ "ref": "2:255", "count": N }, ...],
"by_country": {
"last_7d": [{ "country_code": "GB", "count": N }, ...],
"all_time": [{ "country_code": "GB", "count": N }, ...]
},
"by_client": {
"last_7d": { "claude-desktop": N, "cursor": N, ... },
"all_time": { "claude-desktop": N, "cursor": N, ... }
},
"popular_topics": [
{ "kind": "dua_context", "key": "anxiety", "count": N },
{ "kind": "quran_verse", "key": "2:255", "count": N },
...
],
"oldest_entry": "...",
"newest_entry": "..."
}
One-time setup on Fly
fly volumes create mcp_logs --region lhr --size 1
fly secrets set MCP_STATS_TOKEN=$(openssl rand -hex 32)
fly deploy
The volume auto-mounts at /data on every subsequent deploy (see fly.toml [mounts]).
Data sources
get_quran_verse | alquran.cloud (Tanzil-derived Uthmani Arabic + Saheeh International / Jalandhri / Indonesian MoRA translations). Same upstream the main Sakina app uses. Cached in process for 24 h per verse + edition. |
get_dua | Direct import of Sakina's 444-entry du'a corpus from ../src/data/duas. |
get_name_of_allah | Direct import of Sakina's 99 Names from ../src/lib/data/99-names. |
The two direct-imported sources mean the MCP server cannot drift from what the Sakina app surfaces. Any update to the main repo's data files automatically lands here on the next npm run bundle-data.
Out of scope for v1
Per the Phase 3 planning doc:
- Semantic search /
search_islamic_guidance — needs a Gem-reviewed eval set.
explain_islamic_concept — directly conflicts with CLAUDE.md's "AI never gives spiritual/fiqh advice" rule until an Architect-level ruling is made.
get_pastoral_guidance — Gem 3's framing depends on Sakina-controlled surface; cannot ship via MCP without a separate review.
check_halal_ingredient — blocked on WO#61 restoration.
- HMAC signing enforcement — v1 accepts but ignores the
X-Sakina-App-Id header.
Deployment
Self-contained at publish time — npm run bundle-data snapshots the du'a corpus, the 99 Names, and the canonical safety modules from the main Sakina monorepo into data/ and src/safety/_synced/. The compiled dist/ plus the bundled data/ directory have no runtime dependency on the main app.
bundle-data runs automatically via a prebuild script (WO#138), so npm run build is self-contained on a fresh checkout — no manual bundling step required. The src/safety/_synced/ directory IS committed to git so any drift from the main-app safety modules shows up in git diff between commits.
docker build does NOT run prebuild (it only copies the mcp-server/ directory, not the monorepo). Run npm run bundle-data on the host before docker build so data/ is populated. The same applies if you build from a fresh clone — npm install && npm run bundle-data before docker build.
CI deploy (canonical — WO#249)
All MCP releases go through the MCP Server Deploy GitHub Actions workflow
(.github/workflows/mcp-deploy.yml) — no developer machine holds flyctl or npm
auth. The Architect triggers it from the repo Actions tab
(workflow_dispatch) with two boolean inputs:
deploy_fly (default true) — build + flyctl deploy to sakina-mcp.fly.dev.
publish_npm (default true) — npm publish @asksakina/islamic-knowledge-mcp.
The job runs the full Gem 10 runbook on main: npm ci → npm run bundle-data
(load-bearing) → flyctl deploy → poll /health → live Pickthall byte-check on
2:255 → npm publish → confirm the registry shows the published version. The
byte-check runs the MCP initialize handshake + tools/call get_quran_verse
against the live endpoint: "save Him" (Pickthall) passes; "except Him" (Saheeh
International) or an unreachable /mcp fails the job before npm publish,
dumps flyctl status + logs, and prints the manual rollback commands. Secrets
FLY_API_TOKEN and NPM_TOKEN live in repo settings and are never echoed.
Automated rollback-to-previous-image is the Gem 10 end-state but is a deferred
follow-up: the current live image is itself broken, so there is no known-good
image to auto-restore yet. The first runs are fail-loud + manual rollback only.
Fly.io (manual / break-glass)
Prefer the CI workflow above. These commands are the underlying mechanics, for a
machine that already has flyctl auth:
cd mcp-server
fly launch
fly secrets set \
UPSTASH_REDIS_REST_URL=... \
UPSTASH_REDIS_REST_TOKEN=...
npm run bundle-data
fly deploy
fly status
curl https://<app>.fly.dev/health
Production app: sakina-mcp.fly.dev. App sakina-mcp, region lhr, internal port 3030, force HTTPS, healthcheck on /health every 30 s, auto-stop on idle, 1 shared CPU, 256 MB RAM.
Post-deploy verification and rollback
After every fly deploy, byte-check one Quran verse from the live endpoint against the locked Pickthall table (docs/wo-cert/si-pickthall-LOCKED-final.json) to confirm the translation edition is Pickthall, not Saheeh International:
curl -s https://sakina-mcp.fly.dev/mcp ...
If the byte-check fails (SI text served, or the translation does not match Pickthall), roll back first, diagnose second. Immediately redeploy the previous image before any further investigation or fix:
fly releases --app sakina-mcp
fly deploy --app sakina-mcp --image <previous-image-ref>
Do not attempt a forward fix on a live SI regression — restore the known-good image, confirm the byte-check passes on it, then diagnose the bad build offline.
Local container test
docker compose up --build
curl http://localhost:3030/health
Other targets
The package is just a Node.js HTTP server, so it runs on any platform that takes a Dockerfile or a Node process: Railway, Render, a small VPS, Cloudflare Workers (with the streamable-http transport), Vercel Functions (with a small adapter). The in-memory rate limiter falls back gracefully when Upstash isn't configured.
npm publishing
Publishing goes through the MCP Server Deploy workflow (see CI deploy) with publish_npm: true — the Architect triggers it from the Actions tab; NPM_TOKEN lives in repo settings. The manual equivalent (break-glass, needs local npm auth):
cd mcp-server
npm login
npm publish --access public
prepublishOnly runs bundle-data → build → test:tools so the published tarball always contains fresh data and a clean build. The files field whitelists dist/, data/, README.md, LICENSE, and .mcp/server.json.
Verify the published tarball with npm pack --dry-run first to see what would ship.
MCP Registry submission
mcp-server/.mcp/server.json declares Sakina's identity for the official MCP Registry:
- Server name:
com.asksakina/islamic-knowledge
- Package:
@asksakina/islamic-knowledge-mcp (npm, stdio transport)
- Remote:
https://sakina-mcp.fly.dev/mcp (Streamable HTTP)
Publishing flow:
mcp-publisher login http --domain asksakina.com --private-key <KEY>
mcp-publisher publish .mcp/server.json
curl "https://registry.modelcontextprotocol.io/v0/servers?search=com.asksakina"
Smithery re-publish (after a version bump)
Three places carry the version — all bumped together:
package.json ("version")
mcp-server/.mcp/server.json (top-level version and packages[0].version)
mcp-server/src/server.ts (SERVER_VERSION constant — surfaces in /health and /.well-known/mcp/server-card.json)
Re-publish checklist when bumping:
cd mcp-server
npm run typecheck && npm run test:tools && npm run test:analytics
npm run bundle-data
npm publish
mcp-publisher publish .mcp/server.json
fly deploy
Smithery auto-scans /.well-known/mcp/server-card.json and re-indexes within a few minutes — no manual Smithery API call needed.
Links