A tool can keep its name while gaining a required argument. A server release can change its output schema or remove a resource. Those changes belong in code review, but MCP’s advertised interface is normally discovered by connecting to a running server.
mcp-parser saves that interface to a file. A reviewer can inspect it in the pull request; a documentation build can read it without starting the server or borrowing production credentials.
mcp.json is the static format that closes that gap. A versioned, on-disk snapshot of the server: tools, resources, prompts, capabilities, transport hints. It serves a similar documentation and tooling role to openapi.json for HTTP APIs, using a separate format defined by this project. mcp-schema defines the format. mcp-parser snapshots live servers into that format, validates the result, and renders documentation from it.
Commit the interface with the change
Snapshot the server at the revision being released and commit mcp.json beside its source. The implementation diff shows how a tool changed; the snapshot diff shows what the server now advertises to clients.
server revision -> snapshot -> mcp.json
|-- pull-request diff
|-- validation
`-- reference + context exports
Use repeatable capture conditions. Credentials, configuration and server state can change the list returned to a connection. A snapshot made with an administrator account may describe a different surface from one made with a restricted account.
Snapshot
Point it at any of the three MCP transports and it captures the spec:
import { snapshot } from "mcp-parser";
import { writeFile } from "node:fs/promises";
const spec = await snapshot({
transport: { type: "stdio", command: "node", args: ["server.js"] },
});
await writeFile("mcp.json", JSON.stringify(spec, null, 2));
The transport object is the only difference between stdio, SSE, and streamable HTTP. SSE and HTTP take a headers object if your server is gated. The default 30-second timeout is generous for a normal tools/list round trip but worth bumping if your server lazy-loads its tool list from a remote source the first time it’s asked.
After capture, the spec is just a file. You can diff it on a PR, ship it next to the README, hand it to someone who hasn’t installed your server, or load it from a docs build without spawning the server.
Pagination is part of capture. The parser follows the advertised pages for tools, resources, resource templates and prompts. If its page bound stops collection early, x-mcp-parser-incomplete records that fact. A partial list should not silently become the published description of the whole server.
Snapshotting lists the server’s surface. It does not call each tool, read every resource or establish that the advertised descriptions and annotations match the implementation.
Validate
import { validate } from "mcp-parser";
const result = validate(spec);
if (!result.valid) {
for (const d of result.diagnostics) {
console.error(`${d.severity}: ${d.path} - ${d.message}`);
}
}
It catches the obvious things: required fields, duplicate tool names, an inputSchema that isn’t actually a JSON Schema object. It also catches the less obvious things; a tool with no description, for instance, gets flagged as a warning rather than an error because MCP clients can still load the server, but the resulting documentation and tool selection context are weaker.
Errors block. Warnings are advisory. Diagnostics include the JSON path of the offending field, which matters more than it sounds; real specs run hundreds of lines and a tool name on its own often isn’t unique enough to find the issue.
Generate
A parsed spec produces three rendered outputs from the same source. generateMarkdown writes a human-readable reference suited to a docs site or README, and it is the default CLI output. generateLlmsTxt writes a compact llms.txt-style index for tools that already consume that convention. generateLlmsFullTxt writes the long-form reference with every tool schema inline, useful when you explicitly hand a model or retrieval system the server context.
CLI
mcp-parser snapshot --stdio "node server.js" -o mcp.json
mcp-parser snapshot --sse http://localhost:3000/sse -o mcp.json
mcp-parser snapshot --http http://localhost:3000/mcp -o mcp.json
mcp-parser validate ./mcp.json
mcp-parser generate ./mcp.json -o mcp.md
mcp-parser generate ./mcp.json --format llms-txt -o llms.txt
--header "Authorization:Bearer ..." for gated servers. The CLI is a thin wrapper over the library, useful when a one-line command is enough.
mcp-schema
mcp-schema is the format on its own. TypeScript types if you want IDE help, a JSON Schema if you want to hand validation off to ajv or your existing validation stack. No parser, no transport code, no opinion about how you load the file.
import type { McpSpec, McpTool } from "mcp-schema";
import { mcpSpecSchema } from "mcp-schema/schema";
Worth installing on its own if you’re writing your own tooling around mcp.json and don’t need the snapshot logic.
The static format is independent of the MCP wire protocol. mcpVersion records a negotiated revision, while mcpVersions records the supported revisions reported by a server. The schema validates their date shape rather than requiring a hard-coded list of known revisions. The snapshot client owns revision-specific connection behaviour; parsing and rendering consume the saved document.
The types cover tools (with inputSchema, outputSchema, and annotation hints like readOnlyHint and destructiveHint), resources, resource templates, prompts, server capabilities, and the three transports.
Through Sourcey
Sourcey reads mcp.json directly:
// import { mcp } from "sourcey";
{
tab: "MCP Server",
source: mcp("./mcp.json"),
}
Tools, resources, and prompts render as first-class reference documentation in the same site as the OpenAPI tabs and markdown guides. Annotation badges display the server’s read-only, destructive and idempotent hints. They describe what the server advertises; they do not verify a tool’s effects. Code samples auto-generate in JSON-RPC, TypeScript SDK, and Python SDK. Sourcey also includes the same compact and full-text context exports that mcp-parser can write on its own.
Sourcey is optional. The output is plain JSON; the same workflows that work with openapi.json can work with mcp.json.
