Docs MCP service
read as.mdHosted surface — part of hosted GGUI.
The Docs MCP service is a public, anonymous-auth Model Context Protocol surface that lets any LLM agent query the GGUI documentation corpus from inside its tool loop. Three read-only tools, one in-memory index, no token required.
One clarification on what’s open vs. hosted: the McpService mount primitive this service is built on is OSS (it ships in @ggui-ai/mcp-server — you can mount your own anonymous docs-style service today), and the hosted docs corpus service itself is live at mcp.ggui.ai.
If you’re building a coding assistant, an IDE plugin, or any agent that needs to ground its answers in GGUI’s docs, point it at https://mcp.ggui.ai/docs and the model gets docs_search / docs_read / docs_list for free.
What it is
Section titled “What it is”A first-party MCP service mounted at https://mcp.ggui.ai/docs exposing three tools that wrap an in-memory index of the docs corpus:
docs_search— keyword search, ranked by occurrence count with a 3× title weighting.docs_read— fetch the raw markdown body of one doc by path.docs_list— enumerate every doc in the corpus, optionally filtered by path prefix.
The service is built on the same McpService primitive that hosts the main agent API at mcp.ggui.ai. See MCP services architecture for the anonymous-mode mechanics and how multiple services are composed into one createGguiServer boot.
Why anonymous
Section titled “Why anonymous”The GGUI docs corpus is public — every page on this site is reachable without a login, and every byte the service returns is something a browser could fetch by following a URL. There is no per-user state, no rate-shaped quota tied to identity, no privileged content behind the corpus.
Forcing OAuth on a read-only public surface adds setup friction (Dynamic Client Registration ceremony, token refresh, secret storage) for zero security gain. Anonymous mode is the intentional default for surfaces that meet all of:
- The data is already public.
- The service has no side effects (no writes, no mutating tool calls, no external API spend).
- Per-user state would be a lie (the same query produces the same answer for everyone).
The agent surface at mcp.ggui.ai does not meet these criteria — sessions, BYOK credentials, and per-app rate limits all require an identified caller. The docs surface does, so it skips auth.
Endpoint
Section titled “Endpoint”POST https://mcp.ggui.ai/docsServed as a separate MCP server, not folded into /mcp. The two surfaces have different auth modes (anonymous vs. OAuth) and different tool catalogs; keeping them on distinct paths means a docs-only client never has to discover or skip past agent-loop tools, and the agent loop never has to surface read-only docs tools alongside its session lifecycle.
No Authorization header is required. Content-Type: application/json and the JSON-RPC 2.0 envelope are still required — this is MCP over HTTP, identical wire shape to /mcp, only the auth handshake is skipped.
/docs is a standalone service with its own path — the design-time ggui_protocol_* tools live on the /control plane instead, which is likewise anonymous for that half.
The three tools
Section titled “The three tools”docs_search
Section titled “docs_search”Keyword search over the corpus. The query is tokenized on whitespace and lowercased; matches are case-insensitive. Each hit’s score is the sum of token-occurrence counts in the doc — title occurrences are weighted 3×, body occurrences 1×.
The server clamps results to a maximum of 50 hits regardless of the requested limit. Default limit is 10.
| Field | Type | Required | Description |
|---|---|---|---|
q |
string |
Yes | Search terms — whitespace-separated; case-insensitive. |
limit |
number |
No | Cap on returned hits. Default 10. Server-clamped to 50. |
Returns: { hits: SearchHit[] }
interface SearchHit { path: string; // corpus-relative path, e.g. "principles/strict-typing.md" title: string; // first H1, or filename if none summary: string; // first ~200 chars of prose after the title score: number; // keyword score, higher is better}Hits are sorted by score descending, with a stable secondary sort on path ascending so identical-score hits land in deterministic order. Follow up with docs_read to fetch the full body of any hit.
docs_read
Section titled “docs_read”Fetch the full markdown body of one doc by its corpus-relative path. Pair with docs_search (search → read top hit) or docs_list (browse → read).
| Field | Type | Required | Description |
|---|---|---|---|
path |
string |
Yes | Path relative to the corpus root, forward slashes. Leading ./ or / is trimmed before lookup. |
Returns: { found, path, title, summary, body, bytes }
interface DocsReadOutput { found: boolean; // false when the path isn't in the corpus path: string; title: string; summary: string; body: string; // full raw markdown, including any frontmatter bytes: number; // utf-8 byte length of body}When the path doesn’t match any doc, the tool returns found: false with empty body / title / summary rather than throwing — agents see a clean “no such doc” result and can fall back to docs_search or ask the user to refine.
docs_list
Section titled “docs_list”Enumerate every doc in the corpus, optionally filtered by a path prefix. Returns metadata only (path / title / summary); bodies are not included.
| Field | Type | Required | Description |
|---|---|---|---|
prefix |
string |
No | Case-sensitive path-prefix filter. When set, only entries whose path starts with this prefix are returned. |
Returns: { entries: DocMeta[], total }
interface DocMeta { path: string; title: string; summary: string;}Entries are sorted by path ascending. total is the count after the prefix filter — use it to detect a typo’d prefix (e.g. total: 0 when you expected dozens) without scanning entries.
The full unfiltered list is small — one entry per page, 74 today — comfortably under any sane MCP response budget. Use prefix to scope to one section (principles/, protocol/, architecture/, etc.) when you want a focused slice.
Corpus loading
Section titled “Corpus loading”The corpus is the docs site’s own build artifact, /docs-corpus.json — every page of docs.ggui.ai as one JSON document, each body exactly the bytes that page’s .md companion serves (see LLM agents → What’s available). The hosted service fetches its environment’s artifact at boot — dev.docs.sandbox.ggui.ai for dev, staging.docs.sandbox.ggui.ai for staging, docs.ggui.ai for production (GGUI_DOCS_CORPUS_URL) — and refreshes it every five minutes with a conditional GET on the artifact’s ETag, so a docs deploy reaches the service within that window with no pod deploy involved. title and summary come from the page’s frontmatter (summary is the description when the page declares one). A self-hosted run can point DOCS_CORPUS_PATH at a directory of .md files instead; the directory loader exists for that path only.
interface DocsCorpus { list(): readonly DocMeta[]; read(docPath: string): DocEntry | null; search(query: string, limit?: number): readonly SearchHit[];}The whole corpus is held in process memory — 74 pages today, about 1 MB — and every request is served from RAM: no filesystem hit per call, no vector DB, no Algolia. On any refresh failure the last good corpus keeps serving and the service logs one line naming the staleness (docs_corpus_refresh_failed { lastGoodBuildSha, ageSeconds }); a boot that can reach nothing serves an empty corpus and says so (docs_corpus_unavailable) rather than failing to start. docs_read returns exactly the bytes the page’s .md companion serves, frontmatter envelope included — nothing is re-rendered service-side — and docs_search’s index is built from the fetched records.
The DocsCorpus interface is the upgrade seam. A future v2 can drop in an embedding-backed implementation — precomputed vectors, semantic ranking — without changing the tool surface or the wire shape. Agents written against today’s keyword search keep working; quality improves under their feet.
Connecting from agents
Section titled “Connecting from agents”Claude Agent SDK
Section titled “Claude Agent SDK”Add the service to the mcpServers block of your agent config:
import { query } from "@anthropic-ai/claude-agent-sdk";
const result = await query({ prompt: "How do I write a blueprint for a contact form?", options: { mcpServers: { docs: { type: "http", url: "https://mcp.ggui.ai/docs", }, }, // No `authToken` needed — service is anonymous. },});Tools surface to the model as mcp__docs__docs_search, mcp__docs__docs_read, and mcp__docs__docs_list. The SDK auto-discovers them on the first turn and injects them into the model’s tool list.
Raw @modelcontextprotocol/sdk
Section titled “Raw @modelcontextprotocol/sdk”For non-Claude agents (Gemini, GPT, local models) talking MCP directly:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const client = new Client({ name: "my-agent", version: "1.0.0" });const transport = new StreamableHTTPClientTransport(new URL("https://mcp.ggui.ai/docs"));await client.connect(transport);
const tools = await client.listTools();// ["docs_search", "docs_read", "docs_list"]
const hits = await client.callTool({ name: "docs_search", arguments: { q: "audience routes principle", limit: 5 },});For one-off probes or wiring into a shell pipeline:
# Searchcurl -X POST https://mcp.ggui.ai/docs \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"docs_search","arguments":{"q":"blueprint first","limit":5}}}'
# Read one doccurl -X POST https://mcp.ggui.ai/docs \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"docs_read","arguments":{"path":"principles/blueprint-first-architecture.md"}}}'
# List one sectioncurl -X POST https://mcp.ggui.ai/docs \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"docs_list","arguments":{"prefix":"principles/"}}}'What it pairs with
Section titled “What it pairs with”The Docs MCP service is the runtime half of GGUI’s LLM-agent surface. The static half lives at Agents track — narrative guides, recipe cookbooks, and step-by-step walkthroughs of agent patterns. Use the static docs to learn the protocol; use the Docs MCP service inside your agent to look up the specifics on demand.
It also pairs naturally with the main agent API at /mcp. An agent that’s already authenticated for session work can add the docs service as a second mcpServers entry — the two are independent endpoints with independent auth.
Limits
Section titled “Limits”- Search hit cap: 50. Any
limitabove 50 is silently clamped server-side. For corpus-wide enumeration usedocs_list, not a hugedocs_searchlimit. - No streaming. All three tools are request/response. There is no
tools/streamingvariant — bodies are small enough that streaming would add latency without benefit. - Refresh window: five minutes. The corpus is re-fetched by ETag every five minutes; a docs deploy is visible to
docs_readanddocs_searchwithin that window, with no pod deploy. Between refreshes the corpus is immutable. - Pages only. The corpus is the docs site’s page set — the same pages the sitemap lists. Images, separately hosted code samples and any non-page content are not indexed; the agent gets exactly the markdown the site serves as each page’s
.mdcompanion, never screenshots or rendered HTML. - Anonymous. Anyone can call these tools without identifying themselves. Do not assume there is a per-user audit trail on this surface — there isn’t. (Rate limiting at the edge still applies; behavior under high load is best-effort.)
See also
Section titled “See also”- MCP services architecture — how anonymous services compose with auth’d services on one host
- MCP Protocol Reference — the main agent API at
mcp.ggui.ai - LLM agents — narrative track for building agents against GGUI