@usewombat/gateway
v0.1.0
Published
Resource-level permissions for MCP agents — rwxd on any resource, deny by default
Maintainers
Readme
Wombat
Resource-level permissions for MCP agents.
Wombats protect their burrow with their reinforced rear end.
Wombat protects your agent with a permissions manifest.
Agent (Claude Code, Cursor, OpenClaw, etc.)
│
▼
┌──────────────────────────────┐
│ wombat │
│ checks permissions.json │ deny by default
│ on every single tool call │
└──────┬───────────────────────┘
│ allowed calls only
┌────┴────┬──────────┐
│ │ │
GitHub Slack FilesystemWhy Wombat?
Every MCP permission system today controls which tools an agent can use:
# Everyone else
permissions:
allow: ["mcp:github:get_*", "mcp:slack:send_message"]
deny: ["mcp:github:delete_*"]This is tool-name pattern matching. The tool is either on or off.
Wombat controls what the agent can do with those tools:
{ "resource": "github/org/repo/main", "mode": "r---" }The same push_files tool is allowed on feature/x and denied on main. No other MCP permission system can express this.
This is the Unix model applied to AI agents: rwxd on any resource, deny by default, most specific rule wins.
Install
npm install -g @wombat/gateway
# or just: npx @wombat/gatewayRequires Node.js 22+
Quick start
1. Create a manifest
cp $(wombat --example) ./permissions.json
# edit permissions.json2. Add Wombat to Claude Code
claude mcp add wombat -- wombat \
--manifest ./permissions.json \
--upstream github \
--upstream filesystem \
--dashboard 7842Add your token to .claude.json:
{
"mcpServers": {
"wombat": {
"command": "wombat",
"args": ["--manifest", "./permissions.json", "--upstream", "github", "--dashboard", "7842"],
"env": { "GITHUB_TOKEN": "your_token_here" }
}
}
}3. Open the dashboard
open http://localhost:7842Every tool call appears live — allowed in green, denied in red.
The manifest
{
"version": "0.1",
"umask": "----",
"grants": [
{ "resource": "github/my-org/my-repo", "mode": "rw--", "comment": "Read and write" },
{ "resource": "github/my-org/my-repo/main", "mode": "r---", "comment": "Main is read-only" },
{ "resource": "filesystem/project", "mode": "rw--", "comment": "Project files" },
{ "resource": "filesystem/project/.env", "mode": "----", "comment": "Never touch .env" }
],
"sudo": { "enabled": false }
}Mode strings
4 characters: rwxd. Use - to deny that position.
| Pos | Char | Meaning |
|---|---|---|
| 0 | r | read — get, list, view |
| 1 | w | write — create, update, push |
| 2 | x | execute — run, merge, shell |
| 3 | d | delete |
r--- = read only · rw-- = read+write · rwxd = full access · ---- = explicit deny
Specificity rules
More specific paths win. Exact match beats wildcard.
github/org/repo/main ← wins (most specific)
github/org/repo ← second
github/org/* ← third
github/** ← least specificThe umask
Default for anything not in a grant. Always set to ----.
Built-in servers
Use short names — Wombat auto-expands to the right package:
wombat --upstream github # @github/mcp-server
wombat --upstream filesystem # @modelcontextprotocol/server-filesystem
wombat --upstream slack # @modelcontextprotocol/server-slack
wombat --upstream postgres # @modelcontextprotocol/server-postgres
wombat --upstream brave # @modelcontextprotocol/server-brave-search
wombat --upstream puppeteer # @modelcontextprotocol/server-puppeteer
wombat --upstream memory # @modelcontextprotocol/server-memory
wombat --upstream fetch # @modelcontextprotocol/server-fetch
# Or specify the full command manually:
wombat --upstream "myserver:npx @my/mcp-package"See all registered servers:
wombat --listWombat warns at startup if required env vars are missing.
Plugin system
Wombat has three tiers of server support:
Tier 1 — Built-in
The servers above, maintained by the Wombat team. Full resolver tables, well-tested.
Tier 2 — Community plugins
Install a community plugin and Wombat auto-discovers it:
npm install @wombat-plugin/notion
npm install @wombat-plugin/linear
npm install @wombat-plugin/jira
# restart wombat — plugins are loaded automaticallyThen use them like any built-in server:
wombat --upstream notion --upstream linearAnd grant access in permissions.json:
{ "resource": "notion/page/*", "mode": "r---" },
{ "resource": "linear/issue/*", "mode": "rw--" }Tier 3 — Local plugins
For internal MCP servers that will never be published. Create wombat.plugins.js in your project root:
// wombat.plugins.js
export default [
{
name: "mycompany",
tools: {
mycompany__get_customer: ["r", (p) => `mycompany/customer/${p.id}`],
mycompany__update_customer: ["w", (p) => `mycompany/customer/${p.id}`],
mycompany__delete_customer: ["d", (p) => `mycompany/customer/${p.id}`],
},
exampleGrants: [
{ resource: "mycompany/customer/*", mode: "r---", comment: "Read any customer" },
],
}
];No npm account, no PR, no waiting. Loaded automatically on startup.
Publishing a community plugin
Copy the plugin template, fill in the tool table, publish:
npm publish --access publicThat's it. Anyone who installs @wombat-plugin/yourserver gets your resolver table automatically. No PR to the Wombat repo required.
Manifest integrity
Wombat computes a hash of permissions.json at startup and verifies it on every call. If the file is modified after startup — including by the agent — all calls are denied until you restart.
# Default — integrity enforced (production)
wombat --manifest ./permissions.json --upstream github
# Development — live reload, no integrity check
wombat --manifest ./permissions.json --upstream github --live-reloadStore permissions.json outside any directory the agent can write to. The default working directory is writable by the agent — move your manifest to a parent directory:
wombat --manifest ~/wombat/permissions.json --upstream githubHow it works
Claude Code spawns Wombat as a child process and talks via stdio MCP. Wombat simultaneously:
- Acts as an MCP server to Claude Code — advertises all upstream tools
- Acts as an MCP client to each upstream — forwards allowed calls
Claude Code calls github__push_files(owner, repo, branch: "main")
│
▼
resolver: "write on github/org/repo/main"
│
▼
manifest: github/org/repo/main has r--- → write denied
│
▼
error returned to Claude Code ✗ GitHub never sees the callClaude Code calls github__get_file_contents(owner, repo, path: "README.md")
│
▼
resolver: "read on github/org/repo"
│
▼
manifest: github/org/repo has rw-- → read allowed
│
▼
forwarded to GitHub MCP server ✓ result returnedDashboard
Local dashboard at http://localhost:7842 — every tool call in real time.
- allow (green) — forwarded to upstream
- deny (red) — blocked, error returned to agent
- Filter by decision, server, or resource
- "Clear session" resets the view without deleting audit history
Only accessible on localhost. No data leaves your machine.
Audit log
Every call logged to wombat-audit.jsonl:
{"ts":"2026-03-17T10:23:01Z","session":"sess_abc","agent":"claude-code","tool":"github__push_files","server":"github","resource":"github/org/repo/main","mode":"w","decision":"deny","ms":2,"manifest_hash":"sha256:abc..."}Logged: tool name, resource path, mode, decision, timing.
Never logged: parameter values, file contents, API responses, personal data.
CLI reference
wombat [options]
--manifest <path> Path to permissions.json (required)
--upstream <n> Server name or "name:command" — repeat for multiple
--dashboard <n> Dashboard port (default: 7842)
--agent <n> Agent ID for audit log (default: unknown)
--audit <path> Audit log path (default: ./wombat-audit.jsonl)
--live-reload Reload manifest on every call, disable integrity checks
WARNING: development only
--lock <path> Integrity lock file path (default: /tmp/wombat-<session>.lock)
--list List all servers in the built-in registry
--example Print path to example permissions.json
--help Show this helpHow Wombat differs
| | Tool-name permissions | Wombat |
|---|---|---|
| Claude Code built-in | Bash(git push:*) — allow/deny by tool name | ✗ |
| Cogvel / agent YAML | allow: ["mcp:github:push_*"] — same tool for all repos | ✗ |
| Runlayer / MintMCP | Role-based, enterprise sales call required, not open source | ✗ |
| Wombat | github/org/repo/main: r--- — same tool, different decision per resource | ✓ |
They control which tools the agent can use. Wombat controls what the agent can do with those tools.
No other tool lets you say: allow push_files to feature branches, deny it to main. In Wombat:
{ "resource": "github/org/repo", "mode": "rw--" },
{ "resource": "github/org/repo/main", "mode": "r---" }Known limitation
The local Wombat is a process-level proxy. An agent that knows the real MCP server addresses could connect directly, bypassing Wombat. This is a documented limitation of the local open-source version.
The hosted version (coming) enforces this structurally — real API tokens never reach the developer's machine, so there is nothing to bypass.
Security
See SECURITY.md to report vulnerabilities. Email [email protected]. Do not open a public issue.
License
MIT — LICENSE
Built by Wombat
