Skip to main content
Glama

Citadel MCP

npm citadel-mcp package

A Code Mode MCP server that gives AI coding agents curated, per-stack documentation as typed tools.

Citadel is built to be forked and tailored. The hosted package ships with a small starter set of stacks, but the real value comes from packaging the docs your agents actually need. Each stack is a tiny module — adding one is mostly mechanical.

Getting Started

Requirements

Install with add-mcp

Install the MCP server for all your coding agents:

npx add-mcp citadel-mcp@latest

Add -y to skip the confirmation prompt and install to all detected agents already in use in the project directory. Add -g to install globally across all projects.

Manual installation

Add the following config to your MCP client:

{
  "mcpServers": {
    "citadel": {
      "command": "npx",
      "args": ["-y", "citadel-mcp@latest"]
    }
  }
}
NOTE

Usingcitadel-mcp@latest ensures that your MCP client will always use the latest version of the Citadel MCP server.

MCP Client Configuration

Using Amp CLI:

amp mcp add citadel -- npx citadel-mcp@latest

Or configure manually:

Follow Amp's MCP documentation and apply the standard configuration shown above.

Use the Claude Code CLI to add the Citadel MCP server:

claude mcp add -s user citadel -- npx -y citadel-mcp@latest

Use -s project instead of -s user to scope the install to the current project. Restart Claude Code, then verify with claude mcp list. A working server advertises a single tool named docs.

Using Codex CLI:

codex mcp add citadel -- npx citadel-mcp@latest

Or configure manually:

Follow the MCP setup guide with the standard configuration format:

  • Command: npx

  • Arguments: -y, citadel-mcp@latest

Go to Cursor Settings -> MCP -> New MCP Server. Use the JSON config provided above.

Using Gemini CLI:

Project-wide installation:

gemini mcp add citadel npx citadel-mcp@latest

Global installation:

gemini mcp add -s user citadel npx citadel-mcp@latest

Using VS Code CLI:

code --add-mcp '{"name":"citadel","command":"npx","args":["-y","citadel-mcp@latest"]}'

Or configure manually:

Follow the official VS Code MCP server setup guide and add the Citadel server through VS Code settings.

Navigate to Settings | AI | Manage MCP Servers and select + Add to register a new MCP server with the following configuration:

  • Name: citadel

  • Command: npx

  • Arguments: -y, citadel-mcp@latest

Related MCP server: Enterprise MCP Documentation Server

What you get out of the box

The hosted package includes a starter set of stacks so you can try it immediately:

  • Next.js (App Router, Next.js 16)

  • React

  • Turborepo

  • Supabase guides

This list is intentionally small. For the best results, fork this repo and add the stacks your agents care about — your internal libraries, the framework version you actually use, or any docs site that publishes machine-readable markdown. See docs/adding-a-docs-tool.md.

How it works

Citadel is a Code Mode server: instead of advertising one tool per docs source, it advertises a single docs tool. The agent writes one async () => { ... } per turn that calls codemode.<stack>_docs(...) and codemode.<stack>_index() directly, and the server runs that code in a local Node sandbox. N doc fetches collapse into one round-trip.

Each stack contributes two callable tools to the sandbox SDK:

  • <stack>_index() — returns the doc index so the agent can pick a valid path

  • <stack>_docs({ path }) — fetches that specific doc as markdown

Citadel also exposes a cross-stack ranked search: codemode.docs_search({ query, stacks?, fetch: true }) returns BM25-ranked matches with markdown content attached in a single call.

Documentation

  • Using Citadel as an AI agent — playbook for the docs tool, served at runtime as MCP resource citadel://docs/agent-usage. SDK reference, parallel fan-out, error handling, worked examples.

  • Adding a docs tool — package a new stack as <stack>_index + <stack>_docs.

Local Development

To run the MCP server locally for development:

  1. Clone the repository.

  2. Install and build:

    pnpm install
    pnpm build
  3. Configure your MCP client to use the local version:

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

    Or with the Claude Code CLI:

    claude mcp add -s user citadel-dev -- node /absolute/path/to/citadel-mcp/dist/index.js

For development with auto-reload, point the MCP client at tsx and the src entry instead:

claude mcp add -s user citadel-dev -- npx tsx /absolute/path/to/citadel-mcp/src/index.ts

License

MIT

Available Tools

1 tool
docsA

Run JavaScript in a local Node sandbox. Write ONE async arrow function that returns a value.

Inside the sandbox you have:

  • codemode.(args) — calls an MCP tool over the wire. Each codemode.* method below is a real callable.

  • fetch, Promise, JSON, standard async — Node 18+ globals.

  • 30s async timeout (sync infinite loops are not bounded). One call, one result; no streaming.

Fan out independent calls with Promise.all — that is why this tool exists. N tool calls collapse into one MCP round-trip.

Available tools: type NextjsDocsInput = { /** Documentation path (e.g., '/docs/app/api-reference/functions/refresh'). Do NOT include a '.md' suffix — the tool appends it. Get valid paths by calling codemode.nextjs_index() first. App Router only — Pages Router paths ('/docs/pages/...') are not supported. / path: string; /* Optional anchor/section from the index (e.g., 'usage'). Included in response metadata to indicate relevant section. / anchor?: string; }; type NextjsDocsOutput = { path: string; url?: string; content?: string; anchor?: string | null; error?: string; message?: string; }; type ReactDocsInput = { /* Documentation path (e.g., '/learn/react-compiler.md' or '/reference/react/useState.md'). The path includes the .md suffix as listed in the index. Get valid paths by calling codemode.react_index() first. / path: string; }; type ReactDocsOutput = { path: string; url?: string; content?: string; anchor?: string | null; error?: string; message?: string; }; type TurborepoDocsInput = { /* Documentation path (e.g., '/guides/tools/docker.md', '/reference/run.md', or 'index.md'). Paths are relative to '/docs/'; the tool resolves them under turborepo.dev/docs/. Get valid paths by calling codemode.turborepo_index() first. / path: string; }; type TurborepoDocsOutput = { path: string; url?: string; content?: string; anchor?: string | null; error?: string; message?: string; }; type SupabaseDocsInput = { /* Guide path (e.g., '/docs/guides/functions/auth'). Must start with '/docs/guides/'. Do NOT include a '.md' suffix — the tool appends it. Get valid paths by calling codemode.supabase_index() first. / path: string; }; type SupabaseDocsOutput = { path: string; url?: string; content?: string; anchor?: string | null; error?: string; message?: string; }; type EffectDocsInput = { /* Documentation path (e.g., '/docs/batching/' or '/docs/schema/introduction/'). Paths are pathnames from effect.website with the trailing slash, exactly as listed in the index. Get valid paths by calling codemode.effect_index() first. / path: string; }; type EffectDocsOutput = { path: string; url?: string; content?: string; anchor?: string | null; error?: string; message?: string; }; type OxcDocsInput = { /* Documentation path from the index (e.g., '/docs/guide/usage/linter/automatic-fixes.md'). Paths already include the '.md' suffix as listed in the index. Get valid paths by calling codemode.oxc_index() first. / path: string; }; type OxcDocsOutput = { path: string; url?: string; content?: string; anchor?: string | null; error?: string; message?: string; }; type TanstackStartDocsInput = { /* Documentation path from the index (e.g., '/start/latest/docs/framework/react/overview'). Do NOT include a '.md' suffix — the tool appends it. Get valid paths by calling the matching codemode.tanstack_index() first. / path: string; }; type TanstackStartDocsOutput = { path: string; url?: string; content?: string; anchor?: string | null; error?: string; message?: string; }; type TanstackRouterDocsInput = { /* Documentation path from the index (e.g., '/start/latest/docs/framework/react/overview'). Do NOT include a '.md' suffix — the tool appends it. Get valid paths by calling the matching codemode.tanstackindex() first. / path: string; }; type TanstackRouterDocsOutput = { path: string; url?: string; content?: string; anchor?: string | null; error?: string; message?: string; }; type TanstackQueryDocsInput = { /* Documentation path from the index (e.g., '/start/latest/docs/framework/react/overview'). Do NOT include a '.md' suffix — the tool appends it. Get valid paths by calling the matching codemode.tanstack_index() first. / path: string; }; type TanstackQueryDocsOutput = { path: string; url?: string; content?: string; anchor?: string | null; error?: string; message?: string; }; type DocsSearchInput = { /* Free-text search query. Tokenized on whitespace and punctuation. / query: string; /* Stacks to search (e.g. ['nextjs', 'react']). Defaults to all registered stacks. / stacks?: string[]; /* Maximum matches to return. Default 10. / limit?: number; /* When true, fan out doc fetches in parallel and attach .content to each match. One MCP call yields ranked results AND their full markdown. */ fetch?: boolean; }; type DocsSearchOutput = { matches: { stack: string; path: string; url: string; title: string; description?: string; score: number; content?: string; error?: string; }[]; }; type NextjsIndexOutput = string; type ReactIndexOutput = string; type TurborepoIndexOutput = string; type SupabaseIndexOutput = string; type EffectIndexOutput = string; type OxcIndexOutput = string; type TanstackStartIndexOutput = string; type TanstackRouterIndexOutput = string; type TanstackQueryIndexOutput = string;

/**

  • Sandbox SDK. Each method calls an MCP tool over the wire (one network round-trip per call).

  • Fan out parallel calls with Promise.all to batch fetches into one server hop. / declare const codemode: { /*

    • Fetch Next.js official documentation by path. Scoped to App Router on Next.js 16; Pages Router paths are rejected. IMPORTANT: Call codemode.nextjs_index() first to get valid paths. Do NOT guess paths. Workflow: 1. Call codemode.nextjs_index() to get the documentation index 2. Find the relevant path(s) in the index 3. Call codemode.nextjs_docs({ path }) — fan out parallel fetches with Promise.all when looking up multiple docs at once

    • @param input.path - Documentation path (e.g., '/docs/app/api-reference/functions/refresh'). Do NOT include a '.md' suffix — the tool appends it. Get valid paths by calling codemode.nextjs_index() first. App Router only — Pages Router paths ('/docs/pages/...') are not supported.

    • @param input.anchor - Optional anchor/section from the index (e.g., 'usage'). Included in response metadata to indicate relevant section. / nextjs_docs: (input: NextjsDocsInput) => Promise; /*

    • Fetch React official documentation by path. IMPORTANT: Call codemode.react_index() first to get valid paths. Do NOT guess paths. Workflow: 1. Call codemode.react_index() to get the documentation index 2. Find the relevant path(s) in the index (paths include the .md suffix) 3. Call codemode.react_docs({ path }) — fan out parallel fetches with Promise.all when looking up multiple docs at once

    • @param input.path - Documentation path (e.g., '/learn/react-compiler.md' or '/reference/react/useState.md'). The path includes the .md suffix as listed in the index. Get valid paths by calling codemode.react_index() first. / react_docs: (input: ReactDocsInput) => Promise; /*

    • Fetch Turborepo official documentation by path. IMPORTANT: Call codemode.turborepo_index() first to get valid paths. Do NOT guess paths. Workflow: 1. Call codemode.turborepo_index() to get the documentation index 2. Find the relevant path(s) in the index (paths include the .md suffix) 3. Call codemode.turborepo_docs({ path }) — fan out parallel fetches with Promise.all when looking up multiple docs at once

    • @param input.path - Documentation path (e.g., '/guides/tools/docker.md', '/reference/run.md', or 'index.md'). Paths are relative to '/docs/'; the tool resolves them under turborepo.dev/docs/. Get valid paths by calling codemode.turborepo_index() first. / turborepo_docs: (input: TurborepoDocsInput) => Promise; /*

    • Fetch a Supabase guide by path. Scoped to /docs/guides/** content. IMPORTANT: Call codemode.supabase_index() first to get valid paths. Do NOT guess paths. Workflow: 1. Call codemode.supabase_index() to get the guides index 2. Find the relevant path(s) in the index 3. Call codemode.supabase_docs({ path }) — no .md suffix, the tool appends it. Fan out parallel fetches with Promise.all when looking up multiple guides at once

    • @param input.path - Guide path (e.g., '/docs/guides/functions/auth'). Must start with '/docs/guides/'. Do NOT include a '.md' suffix — the tool appends it. Get valid paths by calling codemode.supabase_index() first. / supabase_docs: (input: SupabaseDocsInput) => Promise; /*

    • Fetch Effect (TypeScript) official documentation by path. IMPORTANT: Call codemode.effect_index() first to get valid paths. Do NOT guess paths. Workflow: 1. Call codemode.effect_index() to get the documentation index 2. Find the relevant path(s) in the index 3. Call codemode.effect_docs({ path }) — fan out parallel fetches with Promise.all when looking up multiple docs at once Effect publishes a single concatenated llms-full.txt; this tool slices the requested page out of it, so all fetches share one cached download.

    • @param input.path - Documentation path (e.g., '/docs/batching/' or '/docs/schema/introduction/'). Paths are pathnames from effect.website with the trailing slash, exactly as listed in the index. Get valid paths by calling codemode.effect_index() first. / effect_docs: (input: EffectDocsInput) => Promise; /*

    • Fetch Oxc (Oxlint + Oxfmt) official documentation by path. IMPORTANT: Call codemode.oxc_index() first to get valid paths. Do NOT guess paths. Workflow: 1. Call codemode.oxc_index() to get the documentation index 2. Find the relevant path(s) in the index (paths include the .md suffix) 3. Call codemode.oxc_docs({ path }) — fan out parallel fetches with Promise.all when looking up multiple docs at once

    • @param input.path - Documentation path from the index (e.g., '/docs/guide/usage/linter/automatic-fixes.md'). Paths already include the '.md' suffix as listed in the index. Get valid paths by calling codemode.oxc_index() first. / oxc_docs: (input: OxcDocsInput) => Promise; /*

    • Fetch TanStack Start official documentation by path. IMPORTANT: Call codemode.tanstack_start_index() first to get valid paths. Do NOT guess paths. Workflow: 1. Call codemode.tanstack_start_index() to get the documentation index 2. Find the relevant path(s) in the index 3. Call codemode.tanstack_start_docs({ path }) — fan out parallel fetches with Promise.all when looking up multiple docs at once

    • @param input.path - Documentation path from the index (e.g., '/start/latest/docs/framework/react/overview'). Do NOT include a '.md' suffix — the tool appends it. Get valid paths by calling the matching codemode.tanstack__index() first. / tanstack_start_docs: (input: TanstackStartDocsInput) => Promise; /*

    • Fetch TanStack Router official documentation by path. IMPORTANT: Call codemode.tanstack_router_index() first to get valid paths. Do NOT guess paths. Workflow: 1. Call codemode.tanstack_router_index() to get the documentation index 2. Find the relevant path(s) in the index 3. Call codemode.tanstack_router_docs({ path }) — fan out parallel fetches with Promise.all when looking up multiple docs at once

    • @param input.path - Documentation path from the index (e.g., '/start/latest/docs/framework/react/overview'). Do NOT include a '.md' suffix — the tool appends it. Get valid paths by calling the matching codemode.tanstack__index() first. / tanstack_router_docs: (input: TanstackRouterDocsInput) => Promise; /*

    • Fetch TanStack Query official documentation by path. IMPORTANT: Call codemode.tanstack_query_index() first to get valid paths. Do NOT guess paths. Workflow: 1. Call codemode.tanstack_query_index() to get the documentation index 2. Find the relevant path(s) in the index 3. Call codemode.tanstack_query_docs({ path }) — fan out parallel fetches with Promise.all when looking up multiple docs at once

    • @param input.path - Documentation path from the index (e.g., '/start/latest/docs/framework/react/overview'). Do NOT include a '.md' suffix — the tool appends it. Get valid paths by calling the matching codemode.tanstack__index() first. / tanstack_query_docs: (input: TanstackQueryDocsInput) => Promise; /*

    • Cross-stack ranked search over the registered documentation indexes. Use this when the agent's question spans more than one stack (e.g. "how does caching work in Next.js and React?") or when the agent doesn't know which stack the answer lives in. One call returns ranked matches across all requested stacks; pass fetch: true to also pull each match's markdown content in the same round-trip. Workflow: 1. Call codemode.docs_search({ query: "caching", stacks: ["nextjs", "react"], limit: 5, fetch: true }) 2. The result is { matches: [{ stack, path, url, title, description?, score, content? }] } 3. Use the content directly, or fan out further codemode._docs() calls for additional pages.

    • @param input.query - Free-text search query. Tokenized on whitespace and punctuation.

    • @param input.stacks - Stacks to search (e.g. ['nextjs', 'react']). Defaults to all registered stacks.

    • @param input.limit - Maximum matches to return. Default 10.

    • @param input.fetch - When true, fan out doc fetches in parallel and attach .content to each match. One MCP call yields ranked results AND their full markdown. / docs_search: (input: DocsSearchInput) => Promise; /*

    • Returns the raw Next.js (App Router, Next.js 16) documentation index. Search this output to find valid paths before calling nextjs_docs. No arguments. / nextjs_index: () => Promise; /*

    • Returns the raw React documentation index. Search this output to find valid paths before calling react_docs. No arguments. / react_index: () => Promise; /*

    • Returns the raw Turborepo documentation index. Search this output to find valid paths before calling turborepo_docs. No arguments. / turborepo_index: () => Promise; /*

    • Returns the raw Supabase guides documentation index. Search this output to find valid paths before calling supabase_docs. No arguments. / supabase_index: () => Promise; /*

    • Returns the raw Effect (TypeScript) documentation index. Search this output to find valid paths before calling effect_docs. No arguments. / effect_index: () => Promise; /*

    • Returns the raw Oxc (Oxlint + Oxfmt) documentation index. Search this output to find valid paths before calling oxc_docs. No arguments. / oxc_index: () => Promise; /*

    • Returns the raw TanStack Start documentation index. Search this output to find valid paths before calling tanstack_start_docs. No arguments. / tanstack_start_index: () => Promise; /*

    • Returns the raw TanStack Router documentation index. Search this output to find valid paths before calling tanstack_router_docs. No arguments. / tanstack_router_index: () => Promise; /*

    • Returns the raw TanStack Query documentation index. Search this output to find valid paths before calling tanstack_query_docs. No arguments. */ tanstack_query_index: () => Promise; };

Example (parallel cross-stack survey, the common case): Example (parallel cross-stack survey): async () => { const [nextjsIdx, reactIdx, turborepoIdx, supabaseIdx, effectIdx, oxcIdx, tanstack_startIdx, tanstack_routerIdx, tanstack_queryIdx] = await Promise.all([ codemode.nextjs_index(), codemode.react_index(), codemode.turborepo_index(), codemode.supabase_index(), codemode.effect_index(), codemode.oxc_index(), codemode.tanstack_start_index(), codemode.tanstack_router_index(), codemode.tanstack_query_index(), ]); // Pick relevant paths from each index, then fan out doc fetches with another Promise.all. return { nextjsIdx, reactIdx, turborepoIdx, supabaseIdx, effectIdx, oxcIdx, tanstack_startIdx, tanstack_routerIdx, tanstack_queryIdx }; }

Return the value the caller needs. If you console.log, output is captured in a [logs] block alongside the result. For deeper patterns read the MCP resource citadel://docs/agent-usage.

ParametersJSON Schema
NameRequiredDescriptionDefault
codeYesJavaScript async arrow function to execute

TDQS

A4.9/5.0
Behavior5/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure, and it does so thoroughly. It discloses the 30s async timeout, the unbounded nature of sync infinite loops, the one-call/one-result no-streaming model, Node 18+ globals, and that console.log output is captured in a separate [logs] block. This gives the agent a complete safety and execution model.

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?

The description is front-loaded with the core concept and then systematically expands into SDK details, type definitions, and examples. However, it is quite verbose, with repeated boilerplate for each of the nine documentation stacks (identical workflow comments and near-identical type definitions). The structure is clear, but trimming redundant per-stack repetition would make it more concise without losing value.

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

Completeness5/5

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

Despite having no output schema, the description embeds output type definitions for every callable and explains return shapes concretely. It covers the complete sandbox environment, all available SDK methods, error handling via returned error fields, and even points to a deeper MCP resource for advanced patterns. This is exceptionally complete for a tool of this complexity.

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

Parameters5/5

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

While the schema has 100% coverage for the single `code` parameter, the description adds immense semantic value beyond the schema's one-line description. It explains that the code must be an async arrow function, what globals are available, how to use the codemode SDK, and includes working examples. The example code alone supersedes the schema in teaching correct parameter usage.

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?

The description opens with a specific verb+resource statement: "Run JavaScript in a local Node sandbox. Write ONE async arrow function that returns a value." This clearly distinguishes the tool's role as an execution harness for the bundled documentation SDK. It goes beyond the name 'docs' by explaining the mechanism and the expected function shape.

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?

The description explicitly states when to use the fan-out capability: "Fan out independent calls with Promise.all — that is why this tool exists." It also gives detailed usage workflows for internal tools like docs_search, including when to use search versus direct doc fetches. The repeated workflow steps ('Call index first, then fetch docs') provide clear, actionable guidance.

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. 1 tool updatev2.2.0
    • First observeddocs

TDQS

A4.6/5.0

Scored across 1 tool

Disambiguation5/5

Only one MCP tool is exposed, so there is no possibility of selecting the wrong tool. The internal codemode methods are clearly named and described, leaving no ambiguity about what each call does.

Naming Consistency4/5

The single tool name 'docs' is simple and matches the documentation domain. Internal methods follow a consistent snake_case pattern (e.g., nextjs_docs, react_index), and no conflicting naming conventions appear across the tool surface.

Tool Count2/5

With only one MCP tool, the server feels severely undersurfaced for its broad scope covering nine documentation stacks. The design hides ~20 callable methods behind a single JS sandbox, reducing discoverability and forcing agents to write code to access basic functionality.

Completeness5/5

The tool provides comprehensive coverage of the documentation domain: per-stack indices, path-based doc fetchers, and a cross-stack search with optional content fetching. There are no obvious gaps in the workflow.

Maintenance

ActivityInactive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides a local MCP server for searching and retrieving documentation from 22+ open-source projects, enabling AI coding assistants to access up-to-date docs without network dependency.
    11 npm
    2
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI coding agents to search and access open-source code, documentation, and package information via a local MCP server.
    1,852 npm
    105
    Apache 2.0