Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

All notable changes to `mcp-parser` are documented here. Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [SemVer](https://semver.org).

## [0.4.3] - 2026-11-08

Requires mcp-schema 0.3.3 or newer.

### Added
- Agent Plugins 1.0.0 MCP configuration parsing, offline schema and semantic validation, per-server diagnostics, and standard configuration generation from explicit transport hints.
- `parse-config`, `validate-config`, `snapshot --config --server`, and `generate --format agent-plugin` CLI workflows.
- Configured stdio snapshots resolve plugin paths, expand plugin variables once, provide persistent plugin data, and support `cwd` through the existing transport.

### Fixed
- Client-generated HTTP/MCP headers take precedence over configured headers regardless of casing. Remote transports refuse redirects and cross-origin legacy SSE message endpoints.
- stdio spawn and stdin errors reject the snapshot instead of escaping as uncaught process errors.

## [0.4.2] - 2026-09-24

### Added
Expand Down
68 changes: 67 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,73 @@ await writeFile("mcp.json", JSON.stringify(spec, null, 2));
await writeFile("mcp.md", generateMarkdown(spec));
```

## What is mcp.json?
## Snapshots and Agent Plugins configuration

Two different documents commonly use the name `mcp.json`:

| Document | Identifying fields | Purpose |
| --- | --- | --- |
| Sourcey server snapshot | `mcpSpec`, `server`, `tools`/`resources`/`prompts` | Capture and document one server's advertised surface |
| [Agent Plugins MCP configuration](https://agent-plugins.org/specification#721-discovery-and-configuration) | `$schema`, `mcpServers` | Locate and connect to the servers in a plugin |

The package supports the published Agent Plugins **1.0.0** configuration contract.
Use `mcp.json` for that configuration at the plugin root, and a separate filename
such as `api.snapshot.json` for a captured surface. A connection configuration
does not contain tools or their schemas; snapshot its server to obtain them.

```bash
mcp-parser validate-config ./plugin/mcp.json
mcp-parser parse-config ./plugin/mcp.json
mcp-parser snapshot --config ./plugin/mcp.json --server api -o api.snapshot.json
# For stdio, also supply a dedicated persistent directory:
mcp-parser snapshot --config ./plugin/mcp.json --server local \
--plugin-data ./plugin-state/local -o local.snapshot.json
# Emit the standard plugin configuration from explicit snapshot transport hints:
mcp-parser generate api.snapshot.json --format agent-plugin --server api -o mcp.json
```

Config snapshots select exactly one server and default to `mcp.snapshot.json`;
they refuse to overwrite the input configuration. `--config` cannot be combined
with another transport or `--header`. `--plugin-root` defaults to the config's
directory. stdio uses that root as its default working directory, resolves
bundled executables and explicit working directories within their real roots,
and supplies `PLUGIN_ROOT` and `PLUGIN_DATA`. Only `args`, `env` values and `cwd`
expand those two variables, once. Other placeholders remain literal.

Remote URLs require HTTPS except on loopback, and cannot contain user information
or fragments. Configured headers remain literal; generated HTTP/MCP headers take
precedence. Redirects and cross-origin SSE message endpoints are refused.
Snapshots omit environment values and HTTP headers. Add any required public
configuration values when authoring a plugin; never embed credentials in it.
Generation requires valid explicit transport hints and does not infer missing
commands, URLs, or authentication settings.

These are MCP configuration utilities, not a plugin installer or a complete
Agent Plugins client. The caller owns manifest validation, matching its schema
version, skill discovery, authorization, and the per-plugin data directory's
lifecycle. `validateConfig(value, { pluginSchema })` can check the manifest's
schema identifier against the MCP configuration. Validation is offline, selects
only the recognized canonical schema, and rejects unsupported releases.

### Configuration API

`parseConfig(path)` and `parseConfigString(content)` require a fully valid config.
`validateConfig(value, options?)` returns `valid`, `topLevelValid`, `diagnostics`,
and a `servers` map containing independently valid entries. An invalid envelope
empties that map; an invalid server leaves valid siblings available.
`resolveConfigServer(config, name, { pluginRoot, pluginData? })` resolves one
entry for the existing `snapshot` API. `snapshotConfig` combines those steps.

```ts
import { createConfig, snapshotConfig } from "mcp-parser";

const config = createConfig({
api: { type: "streamable-http", url: "https://example.com/mcp" },
});
const surface = await snapshotConfig(config, "api", { pluginRoot: "/path/to/plugin" });
```

### Server snapshot format

A static snapshot of an MCP server's capabilities: its tools, resources, and prompts. Think of it as `openapi.json` for MCP servers.

Expand Down
66 changes: 60 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mcp-parser",
"version": "0.4.2",
"version": "0.4.3",
"description": "Snapshot, parse, validate, and document Model Context Protocol servers.",
"type": "module",
"engines": {
Expand Down Expand Up @@ -58,6 +58,7 @@
},
"homepage": "https://github.com/sourcey/mcp-parser",
"dependencies": {
"mcp-schema": "^0.3.2"
"ajv": "^8.20.0",
"mcp-schema": "^0.3.3"
}
}
64 changes: 59 additions & 5 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
* mcp-parser generate <file> Generate docs/context output from an mcp.json
*/

import { readFile, writeFile } from "node:fs/promises";
import { readFile, writeFile, realpath } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { parse } from "./parse.js";
import { validate } from "./validate.js";
import { snapshot } from "./snapshot.js";
import { parseConfig, validateConfig, snapshotConfig, generateConfig } from "./config.js";
import { generateLlmsTxt, generateLlmsFullTxt, generateMarkdown } from "./generate.js";

const args = process.argv.slice(2);
Expand All @@ -27,6 +29,22 @@ async function main(): Promise<void> {
case "validate":
await cmdValidate();
break;
case "parse-config": {
if (!args[1]) throw new Error("Usage: mcp-parser parse-config <file>");
const json = JSON.stringify(await parseConfig(args[1]), null, 2) + "\n";
const output = getOutputFlag();
if (output) await writeFile(output, json);
else console.log(json.trimEnd());
break;
}
case "validate-config": {
if (!args[1]) throw new Error("Usage: mcp-parser validate-config <file>");
const result = validateConfig(JSON.parse(await readFile(args[1], "utf8")));
for (const diagnostic of result.diagnostics) console.error(`${diagnostic.path}: ${diagnostic.message}`);
if (!result.valid) process.exitCode = 1;
else console.log("Valid Agent Plugins MCP configuration.");
break;
}
case "snapshot":
await cmdSnapshot();
break;
Expand All @@ -52,17 +70,23 @@ mcp-parser - Snapshot, parse, validate, and document MCP servers
Usage:
mcp-parser parse <file> Parse and print an mcp.json
mcp-parser validate <file> Validate an mcp.json file
mcp-parser parse-config <file> Parse Agent Plugins MCP configuration
mcp-parser validate-config <file> Validate Agent Plugins MCP configuration
mcp-parser snapshot <transport> [options] Snapshot a running MCP server
mcp-parser generate <file> [--format <fmt>] Generate output from mcp.json

Transports:
--stdio "<command>" Spawn a process and communicate via stdin/stdout
--sse <url> Connect to an SSE endpoint
--http <url> Connect via streamable HTTP
--config <file> Read Agent Plugins MCP configuration (select with --server)

Options:
--output, -o <file> Write output to file instead of stdout
--format <fmt> Output format: markdown (default), llms-txt, llms-full-txt
--format <fmt> Output: markdown (default), llms-txt, llms-full-txt, agent-plugin
--server <name> Configuration server to snapshot or generate
--plugin-root <dir> Plugin root (default: configuration file's directory)
--plugin-data <dir> Dedicated persistent plugin data directory (stdio config)
--header <key:value> Add HTTP header (SSE/HTTP transports, repeatable)
--help, -h Show this help message

Expand All @@ -74,6 +98,9 @@ Examples:
mcp-parser snapshot --http http://localhost:3000/mcp --protocol-version 2026-07-28
mcp-parser generate ./mcp.json -o mcp.md
mcp-parser generate ./mcp.json --format llms-txt -o llms.txt
mcp-parser validate-config ./plugin/mcp.json
mcp-parser snapshot --config ./plugin/mcp.json --server api -o api.snapshot.json
mcp-parser generate api.snapshot.json --format agent-plugin --server api -o mcp.json
`);
}

Expand Down Expand Up @@ -135,13 +162,37 @@ async function cmdSnapshot(): Promise<void> {
process.exit(1);
}
const headers = parseHeaders();
const output = getOutputFlag() ?? "mcp.json";
const configFile = getFlagValue("--config");
if (args.includes("--config") && !configFile) throw new Error("--config requires a file");
const output = getOutputFlag() ?? (configFile ? "mcp.snapshot.json" : "mcp.json");

let transport: Parameters<typeof snapshot>[0]["transport"];

const stdioIdx = args.indexOf("--stdio");
const sseIdx = args.indexOf("--sse");
const httpIdx = args.indexOf("--http");
const modes = [stdioIdx !== -1, sseIdx !== -1, httpIdx !== -1, !!configFile].filter(Boolean).length;
if (modes !== 1) throw new Error("Choose exactly one transport: --stdio, --sse, --http, or --config");

if (configFile) {
if (args.includes("--header")) throw new Error("Configured headers come from mcp.json; --header cannot override them");
const serverName = getFlagValue("--server");
if (!serverName) throw new Error("--config requires --server <name>");
const inputPath = await realpath(configFile);
const outputPath = await realpath(output).catch(() => resolve(output));
if (inputPath === outputPath) throw new Error("Snapshot output must differ from the connection configuration");
const config: unknown = JSON.parse(await readFile(configFile, "utf8"));
const validation = validateConfig(config);
for (const diagnostic of validation.diagnostics) console.error(`${diagnostic.path}: ${diagnostic.message}`);
const spec = await snapshotConfig(config, serverName, {
pluginRoot: getFlagValue("--plugin-root") ?? dirname(inputPath),
pluginData: getFlagValue("--plugin-data"),
...(protocolVersion && { protocolVersion }),
});
await writeFile(output, JSON.stringify(spec, null, 2) + "\n");
console.log(`Snapshot written to ${output}`);
return;
}

if (stdioIdx !== -1 && args[stdioIdx + 1]) {
const commandStr = args[stdioIdx + 1];
Expand Down Expand Up @@ -191,8 +242,11 @@ async function cmdGenerate(): Promise<void> {
case "llms-full-txt":
content = generateLlmsFullTxt(spec);
break;
case "agent-plugin":
content = generateConfig(spec, getFlagValue("--server") ?? spec.server.name);
break;
default:
console.error(`Unknown format: ${format}. Use: markdown, llms-txt, llms-full-txt`);
console.error(`Unknown format: ${format}. Use: markdown, llms-txt, llms-full-txt, agent-plugin`);
process.exit(1);
}

Expand Down Expand Up @@ -225,7 +279,7 @@ function getOutputFlag(): string | undefined {

function getFlagValue(flag: string): string | undefined {
const idx = args.indexOf(flag);
if (idx !== -1 && args[idx + 1]) return args[idx + 1];
if (idx !== -1 && args[idx + 1] && !args[idx + 1].startsWith("-")) return args[idx + 1];
return undefined;
}

Expand Down
Loading
Loading