Start
Wrapping an MCP server
An MCP server is a tool surface your agent can call — Linear, Slack, a database, your own. Grenz wraps one the same way it wraps GitHub: the server's real credential goes in the vault, the agent gets a GRENZ_TOKEN, and each JSON-RPC method is matched against your policy.
1. Add the upstream
upstreams:
linear:
type: mcp
base_url: https://mcp.linear.app/sse
credential: linear_token # vault keyprintf %s "$LINEAR_TOKEN" | grenz vault set linear_token2. Grant the methods it may call
For MCP upstreams, the action is the tool call the agent is making. Grant reads freely, gate writes behind a human, and deny anything destructive:
grants:
- tool: linear
allow: [session:*, tools:list, issue:read, comment:read, project:read]
# Linear writes are upserts — one action covers create and update
require_approval: [issue:write, comment:write]
deny: [comment:delete, attachment:delete]
budget:
per_upstream:
linear: 60Not sure what the server exposes? Run in --shadow for a while and read the log: every call the agent attempts is recorded with the action name Grenz derived, including the ones a policy would have blocked. Then grenz policy shrinkwrap can tighten the allow list to what was actually used.
3. Point the agent's MCP config at Grenz
Change the server URL to Grenz's route for that upstream, and pass the GRENZ_TOKEN:
{
"mcpServers": {
"linear": {
"url": "http://127.0.0.1:8787/u/linear",
"headers": { "Authorization": "Bearer cav_…" }
}
}
}Batched calls
One MCP request can carry a JSON-RPC batch of several calls. Grenz evaluates each one against the policy rather than judging the envelope, so a batch cannot smuggle a denied action alongside allowed ones. Budgets are counted the same way — a batch bills the sum of its actions, not one.