Repository navigation
v0.4.0
Bring your own server.
All 10 languages.
October 2026
Every language now does what Node.js does: federate remote MCP servers, pass your app's runtime into tools, and hand you a registry to mount in the HTTP server you already run. And none of them ships its own HTTP server anymore.
What's new
No built-in HTTP server
ZeroMCP doesn't own the HTTP layer. Every language ships stdio plus the framework-neutral pieces: a request handler and a registry that gives you routes, an OpenAPI document and an MCP handler as plain data. Mount them in Express, FastAPI, Axum, Javalin, ASP.NET, Sinatra, Slim — whatever you run. Auth, CORS and health checks are your server's job.
A config with an http transport now fails at startup with a message naming the replacement. Nothing falls back silently.
Remote MCP servers, everywhere
The remote config federates other MCP servers over HTTP. Their tools appear as <name>.<tool>; tools optionally limits which ones are exposed; a local tool with the same name wins. auth accepts a token or env:VAR. Works in the stdio server, the handler and the registry.
{
"tools": "./tools",
"remote": [
{ "name": "github", "url": "https://mcp.example.com/mcp", "auth": "env:GH_TOKEN", "tools": ["list_issues"] }
]
}Runtime context
Pass something from your app — a DB pool, the current user — and local tools receive it as ctx.runtime on every call.
Hot reload
autoload_tools watches tool directories and sends notifications/tools/list_changed over stdio, in the languages that load tools from directories (Node.js, Python, Go, Ruby, PHP).
Breaking changes
- The built-in HTTP server is removed in every language (
serveHttp,serve_http,ServeHttp,startHttp, thehttptransport, HTTP auth,/docs,/health). Mount the handler or registry in your own server instead. - Registry constructors that now connect to remote servers are async where the language needs it (noted per language below).
Node.js
- Removed the built-in HTTP listener (
zeromcp serve http,{ "type": "http" }). MountcreateHandler(@antidrift/zeromcp/handler) orcreateRegistry(@antidrift/zeromcp/registry) in your own server.
Python
- New
create_registry(from zeromcp import create_registry): routes, OpenAPI and an MCP handler from a tools dict. Async; takesget_env,title,version,execute_timeoutandremote. remoteconfig, with thetoolsallowlist, inserve(),create_handler()andcreate_registry().create_handler(config, get_runtime=...)passes the value to tools asctx.runtime.autoload_toolshot reload.- Removed the built-in HTTP server. An
httptransport exits with an error namingcreate_registry/create_handler.
Go
remoteconfig, with thetoolsallowlist; remote tools show up inHandleRequestandRegistry().MCP.(*Server).SetRuntime(func() any)→ctx.Runtime.autoload_toolshot reload;tools,namespacing,resourcesandpromptsconfig.- The module path is now
github.com/antidrift-dev/zeromcp-go, matching the repo it ships from. The old path couldn't be installed; usego get github.com/antidrift-dev/zeromcp-go/pkg/zeromcp. - Fixed a deadlock between
Registry()and a concurrentTool(). - Removed
serveHTTP,TransportConfig.Port/.AuthandResolveAuth. Anhttptransport exits with an error namingRegistry().
Rust
remoteconfig, with thetoolsallowlist. CallServer::prepare().awaitbeforehandle_requestto connect remotes (servedoes it for you).create_registryis now async and takesRegistryOptions { remote }.Server::set_runtime→ctx.runtime_as::<T>().page_sizeandiconconfig.- Removed
serve_http. Anhttptransport panics with a message namingcreate_registry. - Minimum Rust version is now 1.85 (remote support uses
reqwest).
Java
remoteconfig orZeroMcp.remote(...), with thetoolsallowlist; remote tools appear inregistry().mcp().ZeroMcp.runtime(Supplier)→ctx.runtime().page_sizeandiconconfig.- Removed
serveHttpandConfig.port().serve()throws on any transport other than stdio, namingregistry().
Kotlin
remoteconfig, with thetoolsallowlist. CallconnectRemotes()beforehandleRequest(serve()andregistry()do it for you).registry()is now asuspendfunction.runtimeProvider→ctx.runtime.iconandpage_sizeconfig.- Removed
serveHttpand theportconfig. Non-stdio transports fail at startup, namingregistry()/handleRequest(). - kotlinx-serialization and kotlinx-coroutines are now
apidependencies, so you can compile against the registry's types.
Swift
remoteconfig, with thetoolsallowlist. Callprepare()once beforehandleRequest(serve()does it for you).createRegistryis nowasyncand connects remotes.getRuntimeon the server and in the registry options →ctx.runtime.page_sizeandiconconfig.- Removed
serveHttp. Anhttptransport exits with an error namingcreateRegistry. - The library now builds on Linux; every Swift conformance suite runs and passes.
C#
remoteconfig, with thetoolsallowlist.Prepare()connects remotes (Serve()calls it).- New
Registry.CreateAsyncwithRegistryOptions.Remote. ZeroMcpServer.GetRuntime→ToolContext.Runtime.page_sizeandiconconfig.- Removed
ServeHttp.Serve()throws on anhttptransport.
Ruby
remoteconfig, with thetoolsallowlist;ZeroMcp.create_registry(tools, remote: [...]).get_runtime:onServerandcreate_registry→ctx.runtime.autoload_toolshot reload; tool sources withprefix, andnamespacing;iconas an http(s) URL.- Removed
serve_http. The gem no longer needs WEBrick. Anhttptransport exits with an error namingZeroMcp.create_registry.
PHP
remoteconfig, with thetoolsallowlist;Registry::create($tools, ['remote' => [...]]).new Server($config, ['get_runtime' => ...])→$ctx->runtime.autoload_toolshot reload; tool sources withprefix, andnamespacing.- Logging no longer crashes under
php -S, FPM or Apache (it usedSTDERR, which only exists in the CLI). - Removed
serveHttp()and thehttpcommand. Anhttptransport fails at startup.