Skip to content

mcp-beaver lint <spec.mcp.kdl>

The offline validation surface: serve minus the listener and telemetry. Read the spec, build the same server, print the minted tool names, exit. No network, so it runs in a sealed clone and in CI.

Lint builds through the same constructor serve uses rather than calling opcore.ParseInline directly, so it validates the grant-to-tool projection as well as the parse. A well-formed file whose grants collide on one tool name fails here.

A clean spec exits 0 and writes the projected tool names to stdout, sorted, one per line. That is how a consumer repo reads its served surface off the owning loader instead of writing a second parser. A rejected spec exits non-zero with the failure on stderr and writes nothing to stdout.

Warnings

Two facts invisible from every other surface, so a spec carrying one lints identically to a working spec. An unknown verb resolving to POST by fallthrough, and a resource stating no audience. Both are legitimate, so both warn rather than fail, and both stay off stdout so a warning never edits the diffable surface. The fallthrough warning reads opcore's MethodInferred, so a grant stating method is owed no warning.

Stated HTTP method

method "POST" inside a can body picks the method outright and leaves the verb free to name the tool well. The verb otherwise does both jobs and they collide on a read served over POST, which forced tool names like create_web_search for a call that creates nothing. A stated DELETE marks the grant destructive whatever the verb is called, since the confirmation gate keys off effect rather than spelling, and anything outside GET, POST, PUT, PATCH, DELETE, HEAD fails closed.

Scope is the wrap inline grammar. serve-ssm policies use a separate grammar and are not lintable through this path. lint-upstream is in upstream.md.