@mcp-guard/gateway
v0.1.1
Published
Runtime stdio proxy and policy engine for Model Context Protocol (MCP) servers. Sits between Claude Desktop and any MCP server and enforces YAML policy on every JSON-RPC frame.
Maintainers
Readme
@mcp-guard/gateway
Runtime stdio proxy and policy engine for Model Context Protocol (MCP) servers.
The gateway sits between an MCP client (like Claude Desktop) and any MCP server, and enforces a YAML policy on every JSON-RPC frame in both directions. Every frame is logged to an audit trail. Violations are blocked at the point of call, not after the fact.
Install
npm install -g @mcp-guard/gateway
# or
npx @mcp-guard/gateway validate ./policy.yamlUse
Wrap any upstream MCP server:
mcp-guard-gateway run \
--policy ./examples/policy.yaml \
--server filesystem \
-- node /path/to/fs-server.jsFrom Claude Desktop's claude_desktop_config.json:
{
"mcpServers": {
"filesystem": {
"command": "mcp-guard-gateway",
"args": [
"run",
"--policy",
"/Users/you/.mcp-guard/policy.yaml",
"--server",
"filesystem",
"--",
"node",
"/path/to/fs-server.js"
]
}
}
}Policy file
Policies are YAML. See examples/policy.yaml for a full example.
version: 1
name: example
defaultAction: allow # allow | deny
rules:
- id: deny-shell-tools
when:
direction: client-to-server
method: tools/call
tool.name:
matches: '^(execute|run|exec)[-_]?(shell|command|cmd|bash|sh)$'
action:
type: deny
reason: Shell execution is not allowed.
- id: rate-limit-tool-calls
when:
direction: client-to-server
method: tools/call
action:
type: rate-limit
requests: 60
window: 1m
reason: Tool call rate limit exceeded.
audit:
sink: file # file | stdout | stderr | none
path: ~/.mcp-guard/audit.jsonl
includeBody: false # if true, full frames are logged (may contain secrets)Rule actions
| Action | Behavior |
| ------------ | --------------------------------------------------------------------------------- |
| allow | Pass the frame through. First matching rule wins. |
| deny | Block the frame and return a JSON-RPC error to the caller. |
| transform | Replace the frame with a transformer-produced variant. Used for secret redaction. |
| rate-limit | Count the frame against a sliding window. Deny if over the limit, pass otherwise. |
When clause matchers
| Field | Meaning |
| ----------------- | ------------------------------------------------------------------------ |
| direction | client-to-server or server-to-client. |
| method | JSON-RPC method name. String or array of strings. |
| tool.name | For tools/call, the tool name. String, array, or {matches: /regex/}. |
| params.contains | Substring search inside the stringified params. |
Default policy
If no --policy is provided, a built-in default is used:
- Deny tools whose names match
.*(execute|run|exec)[-_]?(shell|command|cmd|bash|sh)$ - Rate-limit all
tools/callto 120 requests per minute - Allow everything else
- Audit to
~/.mcp-guard/audit.jsonl
Audit log
Each decision emits one JSON Line:
{
"ts": "2026-04-10T12:34:56.789Z",
"server": "filesystem",
"direction": "client-to-server",
"method": "tools/call",
"toolName": "read_file",
"decision": "allow",
"ruleId": null,
"reason": null
}Programmatic use
import { loadPolicyFile, PolicyEngine, createAuditSink, StdioProxy } from '@mcp-guard/gateway';
const policy = loadPolicyFile('./policy.yaml');
const engine = new PolicyEngine(policy);
const audit = createAuditSink(policy.audit);
const proxy = new StdioProxy({
command: 'node',
args: ['./upstream-mcp-server.js'],
serverName: 'upstream',
engine,
audit,
includeBodies: false,
});
await proxy.start();
process.on('SIGINT', () => proxy.stop());License
MIT
