Skip to content

feat(mcp): declare MCP servers in a skill, gated like the code they are - #108

Merged
singhharsh1708 merged 1 commit into
mainfrom
feat/mcp-servers
Aug 9, 2026
Merged

singhharsh1708 merged 1 commit into
mainfrom
feat/mcp-servers

Conversation

@singhharsh1708

Copy link
Copy Markdown
Owner

What

A skill can now declare the MCP servers it needs in [mcp.servers.<name>]. compile writes the native config for every target that has one, and the install gate reviews it first.

[mcp.servers.deploy-tools]
transport = "stdio"                        # stdio | streamable-http | sse
command = "npx"                            # a single executable, never a shell string
args = ["-y", "@acme/deploy-mcp@2.4.1"]    # pin it — unpinned fails the gate
tools = ["plan_diff"]                      # required: deny-by-default allowlist

[mcp.servers.deploy-tools.env]
DEPLOY_TOKEN = "${ACME_DEPLOY_TOKEN}"      # a reference; a literal credential fails the gate

Only three targets emit anything — deliberately

Of the eleven targets, only three have a dedicated, project-scoped, primary-source-confirmed MCP config file:

Target File
claude-code .mcp.json
copilot .github/mcp.json
agent-plugins <plugin-root>/mcp.json

The 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 tools allowlist, 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 → http for Claude Code and Copilot, left as-is for Agent Plugins
  • An explicit type is always written — a url with no type is a documented hard error in Claude Code
  • tools is written only where the format has a field, with a warning where it doesn't — never a silent drop
  • Agent Plugins' mcp.json gets exactly its two allowed top-level keys and the byte-exact $schema const

Trust

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, ^, >=), plain http off loopback, credentials in a URL, missing tools, malformed/duplicate names, and literal credentials in env/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

  • The manifest uses ${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.
  • I verified the claim that our agent-plugin/ layout was wrong — it isn't. plugin.json and skills/ are already siblings at the plugin root, as the spec requires, so mcp.json joins them there.

28 new tests. Full suite, typecheck, site --check, bench gate and self-lint all green. Ships as 0.19.0.

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.
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
kitbash Ready Ready Preview Aug 9, 2026 7:54pm

@github-actions github-actions Bot added documentation Docs, spec, RFCs, README, site dependencies Dependency or action version bumps labels Aug 9, 2026
@singhharsh1708
singhharsh1708 merged commit c633fb0 into main Aug 9, 2026
10 checks passed
@singhharsh1708
singhharsh1708 deleted the feat/mcp-servers branch August 9, 2026 19:55

This branch was successfully deployed

1 active deployment
Preview — 0c4852a9 Deployed Aug 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Dependency or action version bumps documentation Docs, spec, RFCs, README, site

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant