- Go 98.2%
- Just 0.5%
- Go Template 0.4%
- Shell 0.4%
- Python 0.3%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Forgejo auto-requested Kai's review on every agent pull request because this repo carried `* @coilysiren`. Branch protection runs `required_approvals: 0`, so that request never blocked a merge, it only ever looked like one. A standing review request from the repo owner contradicts the landing lane in the one place an agent checks before deciding whether it may act. Fleet sweep tracked at coilyco-bridge/agentic-os-kai#889. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
| .agents/skills/repo-mcp-beaver | ||
| .forgejo/workflows | ||
| .github | ||
| .ward | ||
| chart | ||
| cmd/mcp-beaver | ||
| docs | ||
| examples | ||
| internal | ||
| scripts | ||
| .gitattributes | ||
| .pre-commit-config.yaml | ||
| _typos.toml | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| justfile | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
mcp-beaver
A MCP server generator with a natural flow
A dam is not a wall. It decides what gets through.
About
mcp-beaver renders a umbra Guardfile into a guarded MCP server and HTTP tool API, baked into an OCI image. One generic runtime, many guardfiles. No per-server Go, no per-server Dockerfile, no per-server MCP or HTTP handler - and no per-tool input schema, because umbra's engine derives it from the inline operation definition in the .mcp.kdl.
The spec configures only the image interior: which upstream, which outbound auth, which grants become which tools. The image serves MCP over the official Go SDK's streamable HTTP transport at /mcp and automatically exposes each tool at POST /api/{tool-name} (never stdio - these run as remote k3s pods reached by URL). mcp-beaver shares no code with ward, whose name this project used to carry as ward-mcp. One ward spelling survives on purpose: wrap ward mcp <name> opens every guardfile below, because that line is umbra's inline grammar rather than anything this runtime owns, and it moves when umbra moves. cli-mcp is a code reference only, not a dependency.
You declare the operations an agent may reach, down to the leaf. Everything you declared works. Nothing else is reachable. An unwritten delete issue grant means no delete_issue tool or HTTP endpoint is ever served (deny-by-absence), and restrict owner matches coilyco-* bounds every path. The one entry that is not a grant is mcp_beaver_info, a read-only tool that reports the server's own shape, reaches no upstream, and can be turned off with server-info disabled. Audit one small file, hand a write-capable MCP to an agent, know the blast radius.
The claim is about the running server, not the image. The image is deliberately generic: it carries no guardfile, and a consumer mounts the spec at deploy time. In upstream-proxy mode the undeclared tools exist behind an endpoint the container holds credentials for, and the runtime re-checks allowlist membership on every call - unreachable rather than absent. In spec mode an undeclared operation has no handler at all.
Quickstart
The generic mcp-beaver serve runtime renders any .mcp.kdl into a guarded MCP
server. Each grant also projects ChatGPT-friendly metadata: a human title,
user-goal description, input and output schemas, and safety annotations derived
from the operation's HTTP behavior. Run it directly:
# initialize, then reuse the session id for tools/list and tools/call
FORGEJO_TOKEN=... go run ./cmd/mcp-beaver serve examples/forgejo-issues.mcp.kdl --http :8080
# list the derived tools (SDK-backed streamable HTTP transport at /mcp)
curl -s -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' localhost:8080/mcp
# call the same guarded handler without an MCP session
curl -s -H 'Content-Type: application/json' \
-d '{"owner":"coilyco-flight-deck","repo":"mcp-beaver","index":"41"}' \
localhost:8080/api/get_issue
Every grant-backed result is {"coverage": {...}, "result": ...}, in that
order, in both the text and the structured content. Coverage leads because a
consuming harness bounds a tool result by keeping the front and discarding the
tail, so a caveat serialized last is the first thing destroyed - and the model
then reads rows with no caveat and answers as though the view were complete. It
states truncated (always false; nothing here truncates), the payload's
bytes, whether that is over_budget for the smallest consumer cap measured
in the fleet, and items naming every array in the payload and its length. A
count in meaning is what changes an answer; a byte total is not. See
docs/DESIGN.md for what the runtime enforces and what stays
the upstream's word.
The HTTP projection is always present. It accepts one JSON argument object,
uses the same tool handler as tools/call, and returns the MCP
CallToolResult JSON shape. mcp-beaver performs no inbound authentication.
Consuming deployments own caller identity, authentication, TLS, ingress, and
network reachability. Guardfile auth configures mcp-beaver's credential for the
upstream service, not the caller's credential to mcp-beaver.
Or as the image (one runtime, many specs - the spec is mounted, not baked):
docker build -t mcp-beaver .
docker run -p 8080:8080 -e SKILLSMP_API_KEY \
-v $PWD/examples/skillsmp.mcp.kdl:/spec/skillsmp.mcp.kdl \
mcp-beaver serve /spec/skillsmp.mcp.kdl --http :8080
For a passthrough MCP wrapper over a private upstream, use serve-upstream
with an allowlist. --connect-timeout lets a co-located upstream warm up
without putting the wrapper into a crash cycle:
mcp-beaver serve-upstream --name grubhub-mcp \
--upstream http://playwright-mcp.namespace.svc.cluster.local/mcp \
--tool browser_navigate --tool browser_click \
--connect-timeout 2m --http :8080
A hosted upstream demanding a credential takes --upstream-header, whose
{env:VAR} span resolves in the container per request so the token never
reaches argv. See docs/upstream.md.
For an exact-parameter AWS SSM reader, use the KDL-backed SDK runtime:
mcp-beaver serve-ssm /spec/aws-ssm.mcp.kdl --http :8080
The SSM policy declares one parameter and exactly two read tools. The general getter accepts a name but rejects every value except the declared path. The convenience getter fixes that same path internally.
Distributes as image + chart
The product ships two artifacts (mcp-beaver#6): the generic runtime image above, and a generic Helm chart (chart/) that templates the k3s exposure. Deploying an MCP is then a values file plus helm upgrade - no per-guardfile image build, no per-service manifest fork:
helm upgrade --install skillsmp mcp-beaver \
-f skillsmp.values.yaml \
--set-file spec=skillsmp.mcp.kdl \
--set image.tag=<built-runtime-sha>
Every push to canonical main publishes the private single-architecture
runtime as
forgejo.coilysiren.me/coilyco-flight-deck/mcp-beaver:<full-source-sha>.
The trusted publisher verifies the remote manifest, and every fleet release
consumes that exact reference through a separate read-only credential.
The chart has two runtime modes. spec mounts a .mcp.kdl from chart values.
upstream runs serve-upstream with an exact tool allowlist and can co-locate
the private upstream through extraContainers. The chart templates only the
auth-neutral runtime layer: Deployment, Service, optional NodePort, and
application Secret wiring. In spec mode it stays spec-opaque and never
parses the guardfile. deploy
owns public ingress, authentication, TLS, DNS, and rollout.
Layout
cmd/mcp-beaver- theserveentrypoint: parse a spec, project tools, bind the SDK-backed HTTP listener.lintis the same path minus the listener and telemetry, andlint-upstreamis the allowlist counterpart forserve-upstream.internal/mcpserver- the thin shell: grant→MCP-tool and HTTP endpoint projection, the SDK-backed streamable HTTP/session layer, and the non-MCP/healthzplus/admin/*operator endpoints.examples/forgejo-issues.mcp.kdl- the worked "hello world": Forgejo issues as an MCP. Its body is the frozen mcp-beaver inline grammar (opcore.ParseInline), and it is the whole contract.examples/skillsmp.mcp.kdl- the first end-to-end target: two read tools over the SDK-backed transport against skillsmp.com.examples/*.values.yaml- reference auth-neutral chart values:skillsmpuses the default ClusterIP, andforgejo-issuesdemonstrates the optional NodePort.examples/upstream.values.yaml- reference allowlisted upstream mode with a co-located MCP container.examples/upstream-authed.values.yaml- the hosted counterpart: an off-cluster upstream reached with a Secret-backed credential.chart/- the generic mcp-beaver Helm chart. Seedocs/chart.md..ward/ward.yamlandscripts/ward-command.sh- the tracked development command surface.docs/DESIGN.md- the spec→image pipeline, the interior-only scope, and the SDK-backed transport + safety model.docs/chart.md- the chart's templates, values reference, and the runtime contract it targets.docs/FEATURES.md- the living inventory of what ships today.
Status
The mcp-beaver serve runtime is implemented (mcp-beaver#7): it parses a .mcp.kdl, serves the derived tools over MCP at /mcp and HTTP at /api/{tool-name}, guarded-executes both projections through the same handler, and exposes operator-only /healthz plus /admin/describe and /admin/reload endpoints. The generic Helm chart that runs this image is also in (mcp-beaver#8). Tracking coilysiren/inbox#164 (concept) and coilyco-bridge/deploy#40 (first consumer).
License
MIT. See LICENSE.
Authoring guardfiles
The authoring guidance lives in docs/:
- lint.md - validating a guardfile, and stating a method.
- guardfile-siblings.md and
guardfile-controls.md - the optional nodes
stated beside
wrap. - upstream.md and upstream-pins.md - allowlists, scoping, and upstream credentials.
- telemetry.md - opting into OpenTelemetry.
See also
- AGENTS.md - agent operating rules for this repository.
- docs/FEATURES.md - what ships today.
- docs/DESIGN.md - why it is shaped this way.
- justfile - dev verbs.
- .ward/ward.yaml - catalog metadata only.