Skip to content
Public preview · the console is open

Docs MCP service

read as .md

Hosted 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.

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.

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.

POST https://mcp.ggui.ai/docs

Served 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.

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.

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.

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.

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.

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.

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:

Terminal window
# Search
curl -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 doc
curl -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 section
curl -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/"}}}'

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.

  • Search hit cap: 50. Any limit above 50 is silently clamped server-side. For corpus-wide enumeration use docs_list, not a huge docs_search limit.
  • No streaming. All three tools are request/response. There is no tools/streaming variant — 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_read and docs_search within 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 .md companion, 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.)