Skip to content

mcp-beaver serve <spec.mcp.kdl> --http :addr

One static binary renders any .mcp.kdl into a guarded MCP server over the MCP Go SDK's streamable HTTP transport at /mcp, and exposes the identical tool arguments at POST /api/{tool-name}. No per-guardfile Go, and it never binds stdio: these run as pods reached by URL.

Spec parse

opcore.ParseInline (umbra http/opcore) parses the inline grammar: wrap header, base-url, auth, restrict, and each can <verb> <resource> { path/query/body/set } grant. Body blocks preserve typed scalars, scalar arrays, nested objects, required fields, and raw object or array subtrees, and can project required nested string inputs onto fresh top-level keys without forwarding undeclared input. Query blocks preserve string, boolean, integer, number, and scalar-array types, numeric bounds, array length bounds, required fields, mutually-exclusive groups, and safe local aliases. Method is inferred from the verb, path params from {template}. sql grants reach Postgres only: pgx is the one driver this binary links.

Grant to tool projection. Each Descriptor becomes one MCP tool and one HTTP endpoint named verb_resource, with a draft-07 inputSchema derived from path, query, and body, and a description from describe. mcp-beaver derives a title plus read-only, destructive, idempotent, and open-world annotations from the HTTP behavior, and a {coverage, result} output schema. internal/mcpserver.

Coverage before payload. Every grant-backed result leads with coverage and carries the payload under result, in both text and structured content. A harness that bounds a result keeps the front, so a caveat serialized last is destroyed first. Coverage states truncated, bytes, over_budget past the smallest measured consumer cap, and items naming every array and its length. The envelope is a struct rather than a map, so field order is a contract.

Execution. MCP arguments route onto opcore.Args without flattening nested body objects, then fire opcore.Operation.Execute: metachar gate, restrict allowlist, base-url, and env-token auth are the engine's. A denied or failed call returns as a tool result with isError. Query arguments keep their JSON types, arrays become repeated upstream keys in caller order, and violations fail before the upstream sees a request. The served surface is exactly the can grants, so an unwritten grant is an absent tool.