Drop a file.
It's an MCP tool.
No server class. No 17 dependencies. No plaintext secrets. Just a function in a folder.
npm install -g zeromcp MCP is a great protocol with a
terrible developer experience
Hand-edited JSON. Zombie processes. Plaintext secrets. Config drift across the team. These are real quotes from developers who've had enough.
"MCP configuration is a sh*tshow, especially when secrets like API keys and database passwords are involved."
— Hagen Hubel, Medium
"Every AI coding session spawns a tree of child processes. When the session ends, these processes are supposed to terminate. They don't."
— thestack_ai, DEV Community
"You drop plaintext secrets into your Claude config. Any server you define can read your environment variables, access your filesystem, and send data anywhere on the internet."
— Cyata.ai, Security Research
File becomes tool in
three seconds
Drop a file
Write a JavaScript file with a description, inputs, and an execute function. Put it in ./tools/.
ZeroMCP scans it
Recursive directory scan. Auto-namespacing from folder structure. Credential injection. Permission enforcement.
Serve over MCP
One command. stdio for Claude Code and Cursor. Library export for Express, Lambda, Workers.
Tools are files.
Distribution is git clone.
MCP tools today are buried in npm packages with their own dependencies and config READMEs. ZeroMCP tools are files. Push one to a repo. Someone else clones it into their tools folder. Done. No install step. No build step. No dependency conflicts.
Write a tool
Push to a repo
Anyone clones it
It works
zeromcp audit for permission violations, undeclared network access, and credential leaks before it enters the registry. Community registry coming soon. Tools are files today. Share them however you share files.
Compose MCP servers like
building blocks
Connect your existing MCP servers into one process. Auto-namespaced. Layer your own local tools on top. One stdio connection for your client. The official SDK can't do this.
// zeromcp.config.json { "tools": "./tools", "remote": [ { "name": "github", "url": "http://localhost:3001/mcp" }, { "name": "jira", "url": "http://localhost:3002/mcp" }, { "name": "stripe", "url": "http://localhost:3003/mcp", "auth": "env:STRIPE_MCP_TOKEN" } ] }
$ zeromcp serve [zeromcp] Connected to github (HTTP): 15 tool(s) [zeromcp] Connected to jira (HTTP): 12 tool(s) [zeromcp] Connected to stripe (HTTP): 17 tool(s) [zeromcp] Loaded: my_custom_tool [zeromcp] 1 local + 44 remote = 45 tool(s) [zeromcp] stdio transport ready
Auto-namespaced
Remote tools become github.create_issue, jira.list_tickets. No collisions.
Local overrides remote
Your tools take priority. Wrap, extend, or replace any remote tool.
Auth from environment
"auth": "env:TOKEN" reads from your environment. Never stored in config.
ZeroMCP vs the alternatives
| ZeroMCP | Official SDK @modelcontextprotocol/sdk | Separate Servers N individual MCP servers | |
|---|---|---|---|
| Configuration | Drop a file | Server class + Zod schemas | JSON per server |
| Dependencies | 0 | 17 | N x each server's deps |
| Code size | ~1K LOC | 4.3 MB | N x server packages |
| Processes | 1 | 1 (per server) | N separate processes |
| Composability | Built-in | Not supported | Not supported |
| Credential injection | ctx.credentials | DIY | Plaintext JSON |
| Sandboxed fetch | Domain allowlisting | No | No |
| Hot reload | Built-in | Manual restart | Manual restart |
| Library export | Express / Lambda / Workers | Custom integration | No |
Two ways to use ZeroMCP
Write once, serve anywhere
Same tool files work locally over stdio for Claude Code and Cursor, or embedded in your own server via library export.
// embed in any server const mcp = await zeromcp('./tools') app.post('/mcp', mcp.handle)
Compose MCP servers
Connect GitHub + Jira + Stripe MCP servers into one process. Auto-namespaced. Add your own tools on top.
// one process, all tools $ zeromcp serve [zeromcp] 45 tools, 1 process
Three commands. That's it.
Install
npm install -g zeromcp Drop a tool file
export default { description: "Say hello", input: { name: "string" }, execute: async ({ name }) => `Hello, ${name}!` }
Serve
zeromcp serve
That's it. Your MCP client can now call hello.
Security is the default,
not an afterthought
Tools can't phone home to undeclared domains. They can't access other tools' credentials. They can't touch the filesystem without declaring it.
Permission declarations
Tools declare what they need: network domains, filesystem paths, exec access. Enforced at runtime, not advisory.
permissions: { network: ["api.stripe.com"] } Sandboxed fetch
ctx.fetch only reaches declared domains. Undeclared network calls are blocked. All calls logged when logging is enabled.
ctx.fetch("https://api.stripe.com/v1/...") Credential injection
Tools use ctx.credentials. Keys are mapped from env vars or files in one config. Never hardcoded, never passed as arguments.
credentials: { stripe: { env: "STRIPE_KEY" } } Audit CLI
zeromcp audit runs static analysis on tool files. Catches undeclared network access, credential leaks, and permission violations. Gates the community registry.
$ zeromcp audit ./tools Replace your MCP config
in 60 seconds
Install ZeroMCP, drop a tool file, and connect it to Claude Code or Cursor.
npm install -g zeromcp