Repository navigation
feat(mcp): declare MCP servers in a skill, gated like the code they are - #108
Merged
Merged
Conversation
A skill that needs an MCP server had to say so in prose and hope you
configured it. Now it declares the server in `[mcp.servers.<name>]`,
`compile` writes the native config for every target that has one, and the
install gate reviews it first.
The research mattered more than the code. Of the eleven targets, only three
have a dedicated, project-scoped, primary-source-confirmed MCP config file:
claude-code (.mcp.json), copilot (.github/mcp.json) and agent-plugins
(<plugin-root>/mcp.json). The rest have no MCP mechanism (aider, agentsmd),
no project scope (cline, windsurf), or keep servers in a shared settings
file full of unrelated user config (zed, gemini) — and cursor's file cannot
express a tools allowlist, so a declaration would be silently widened.
Emitting a plausible-looking file for any of those produces something that
reads as configured and does nothing, which is the failure this project
exists to prevent. They get a typed warning naming the reason, and no file.
Nothing is passed through verbatim: the client dialects genuinely disagree,
so `streamable-http` is translated to `http` for Claude Code and Copilot,
an explicit `type` is always emitted (a url with no type is a documented
hard error in Claude Code), and `tools` is written only where the format
has a field — with a warning where it does not, never a silent drop.
`tools` is required in the manifest because it is the one field kitbash
cannot synthesize, Copilot's format demands it, and deny-by-default is the
point. Secrets may only appear as ${VAR}; where a format defines no
credential reference and expands no variables, the server is omitted with a
warning rather than emitted with a reference that would never resolve.
The MCP lints join the four safety lints as non-bypassable, because a
server declaration is a request to run third-party code with the agent's
permissions: shell strings, shell metacharacters, unpinned versions, plain
http off loopback, credentials in a URL, a missing tools allowlist, and
malformed or duplicate names all block install with --yes.
Note the manifest uses ${VAR} references rather than the inline-table
`{ secret = "NAME" }` form: this repo's TOML parser has no inline-table
support, and extending it was more risk than the syntax was worth.
Adds 28 tests. 0.19.0.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A skill can now declare the MCP servers it needs in
[mcp.servers.<name>].compilewrites the native config for every target that has one, and the install gate reviews it first.Only three targets emit anything — deliberately
Of the eleven targets, only three have a dedicated, project-scoped, primary-source-confirmed MCP config file:
claude-code.mcp.jsoncopilot.github/mcp.jsonagent-plugins<plugin-root>/mcp.jsonThe rest have no MCP mechanism (aider, agentsmd), no project scope (cline, windsurf are user-global only), or keep servers in a shared settings file alongside unrelated user config (zed, gemini) — and Cursor's file cannot express a
toolsallowlist, so a declaration there would be silently widened. Emitting a plausible-looking file for any of those produces something that reads as configured and does nothing, which is the failure this project exists to prevent. They get a typed warning naming the reason (no-mcp-surface,no-project-scope,needs-shared-file-merge) and no file.Nothing is passed through
The client dialects genuinely disagree, so every emitter translates:
streamable-http→httpfor Claude Code and Copilot, left as-is for Agent Pluginstypeis always written — aurlwith notypeis a documented hard error in Claude Codetoolsis written only where the format has a field, with a warning where it doesn't — never a silent dropmcp.jsongets exactly its two allowed top-level keys and the byte-exact$schemaconstTrust
MCP lints join the four safety lints as non-bypassable — a server declaration is a request to run third-party code with your agent's permissions. Blocked with
--yes: shell strings and shell metacharacters, unpinned versions (@latest,^,>=), plainhttpoff loopback, credentials in a URL, missingtools, malformed/duplicate names, and literal credentials inenv/headers.Secrets may only appear as
${VAR}. Where a format defines no credential reference and expands no variables (Agent Plugins), the server is omitted with a warning rather than emitted with a reference that would never resolve.Notes
${VAR}references rather than an inline-table{ secret = "NAME" }form: this repo's TOML parser has no inline-table support, and extending it was more risk than the syntax was worth.agent-plugin/layout was wrong — it isn't.plugin.jsonandskills/are already siblings at the plugin root, as the spec requires, somcp.jsonjoins them there.28 new tests. Full suite, typecheck, site
--check, bench gate and self-lint all green. Ships as 0.19.0.