jedisct1/zig-mcp-server
Write super fast and memory efficient MCP servers in Zig.
The Zig MCP Server is a memory-efficient implementation of the Model Context Protocol (MCP) specification. It provides both a standalone server executable and a library that can be embedded into your Zig applications.
# Standard build
zig build
# Optimized for release
zig build -Doptimize=ReleaseSmall
# Run with stdio transport (default)
./zig-out/bin/mcp_server
# Run with HTTP transport
./zig-out/bin/mcp_server --transport http --port 7777
# Run with custom thread count and connection limits
./zig-out/bin/mcp_server --transport http --threads 4 --max-connections 500 --timeout 60000
# Build the project
make build
# Run with stdio transport
make run
# Run with HTTP transport
make run-http
The standalone server provides ready-to-use MCP functionality with minimal setup.
This MCP server comes with the following example tools out of the box:
echo
: Echoes back any provided parametersreverse
: Takes a string input and returns its reverseUsage: mcp_server [options]
Options:
--port, -p <port> Port to listen on (default: 7777)
--host, -h <host> Host to bind to (default: 127.0.0.1)
--transport, -t <transport> Transport to use (stdio, http) (default: stdio)
--threads, -j <count> Number of worker threads (default: CPU core count)
--max-connections, -c <count> Maximum concurrent connections (default: 1000)
--timeout, -T <milliseconds> Connection timeout in milliseconds (default: 30000, 0 = no timeout)
--help Print this help message
Server Endpoints:
/jsonrpc Main JSON-RPC endpoint (POST)
/health Health check endpoint (GET)
The HTTP transport uses a thread pool for handling concurrent connections. By default, it uses a number of threads equal to the available CPU cores, but you can adjust this with the --threads
option.
To prevent resource exhaustion, you can limit the number of concurrent connections using the --max-connections
option. When this limit is reached, new connections will be rejected.
Use the --non-blocking
option to run the HTTP server in non-blocking mode. This starts a dedicated thread for the server, allowing the main thread to perform other tasks. This is especially useful when using the library in applications that need to do other work while the server is running.
Each connection has a configurable timeout (default: 30 seconds) after which inactive connections are automatically closed. Set to 0 to disable timeouts completely.
The server supports graceful shutdown, which allows in-flight requests to complete before the server exits. This prevents abruptly terminating active connections during shutdown.
The HTTP server provides metrics tracking, including:
A basic health endpoint at /health
returns server status information.
The server can be used with any client that implements the MCP protocol. We provide several example clients for demonstration.
Run the server first:
./zig-out/bin/mcp_server --transport http
Then run the Python client:
# Install required dependencies
pip install requests
# Run the example client
python examples/mcp_client.py --transport http --url "http://127.0.0.1:7777/jsonrpc"
The Python client demonstrates:
For a Node.js client example:
# Make the script executable
chmod +x test_client.js
# Run the client (automatically starts the server in stdio mode)
./test_client.js
This JavaScript client shows:
The MCP protocol is based on JSON-RPC 2.0, making it easy to implement clients in any language. Key points to follow:
initialize
request with your client capabilitiesmcp/tools/list
to discover available toolsmcp/tools/invoke
with the tool name and parametersshutdown
request when doneThe server can be compiled to WebAssembly for deployment in serverless environments:
zig build -Dtarget=wasm32-wasi -Doptimize=ReleaseSmall
When compiled for WebAssembly, the following restrictions apply:
--help
works)This makes the WebAssembly binary smaller and more focused for running in WebAssembly environments like Fastly Compute or other WASI-compatible serverless platforms where HTTP handling is typically provided by the host environment.
Zig MCP Server is designed to be easily embedded in your Zig applications as a library.
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Add the zig-mcp dependency
const zig_mcp_dep = b.dependency("zig-mcp", .{
.target = target,
.optimize = optimize,
});
// Get the module from the dependency
const zig_mcp_mod = zig_mcp_dep.module("zig-mcp");
// Create your application
const exe = b.addExecutable(.{
.name = "my_mcp_app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Add the zig-mcp module to your application
exe.addModule("zig-mcp", zig_mcp_mod);
b.installArtifact(exe);
}
.{
.name = .your_app_name,
.version = "0.1.0",
.fingerprint = 0x..., // Replace with actual fingerprint
.dependencies = .{
.@"zig-mcp" = .{
.url = "https://github.com/jedisct1/zig-mcp-server/archive/refs/tags/v0.1.0.tar.gz",
.hash = "12345...", // Replace with actual hash
},
},
}
Here's a simple example showing how to integrate the MCP server into your application:
const std = @import("std");
const zig_mcp = @import("zig-mcp");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Define your tools
const tools = [_]zig_mcp.mcp.Tool{
.{
.name = "hello",
.description = "Greeting tool",
.handler = &helloHandler,
},
};
// Configure server settings with HTTP transport
const settings = zig_mcp.mcp.Settings{
.transport = .http,
.host = "127.0.0.1",
.port = 7777,
.tools = &tools,
.max_connections = 100, // Max 100 concurrent connections
.thread_count = 4, // 4 worker threads
.connection_timeout_ms = 30000, // 30 second connection timeout
.non_blocking_http = true, // Run in non-blocking mode
};
// Create and start MCP server
var server = try zig_mcp.mcp.Server.init(allocator, settings);
defer server.deinit();
std.debug.print("MCP Server starting\n", .{});
try server.start();
std.debug.print("Server running in background, you can continue with other tasks...\n", .{});
// Since we're using non-blocking mode, the main thread can do other work
var i: usize = 0;
while (i < 5) : (i += 1) {
std.time.sleep(5 * std.time.ns_per_s); // Sleep for 5 seconds
std.debug.print("Main thread still active...\n", .{});
}
std.debug.print("Main application shutting down\n", .{});
// Explicitly request a graceful shutdown before the defer
server.shutdown();
std.debug.print("Server shutdown requested, waiting for completion...\n", .{});
// Allow some time for the shutdown to complete
std.time.sleep(1 * std.time.ns_per_s);
}
// Tool handler implementation
fn helloHandler(ctx: *zig_mcp.jsonrpc.Context, params: std.json.Value) !std.json.Value {
const allocator = ctx.allocator();
// Extract name parameter if present
const name = if (params.object.get("name")) |n|
if (n == .string) n.string else "world"
else
"world";
// Build response
var result = std.json.Value{ .object = std.json.ObjectMap.init(allocator) };
const greeting = try std.fmt.allocPrint(allocator, "Hello, {s}!", .{name});
try result.object.put("greeting", std.json.Value{ .string = greeting });
return result;
}
Creating custom tools is straightforward:
zig_mcp.mcp.ToolHandlerFn
signaturefn myComplexTool(ctx: *zig_mcp.jsonrpc.Context, params: std.json.Value) !std.json.Value {
const allocator = ctx.allocator();
// Extract and validate parameters
const value1 = params.object.get("value1") orelse return zig_mcp.jsonrpc.Error.invalidParams;
const value2 = params.object.get("value2") orelse return zig_mcp.jsonrpc.Error.invalidParams;
if (value1 != .integer or value2 != .integer) {
return zig_mcp.jsonrpc.Error.invalidParams;
}
// Business logic
const result_value = value1.integer * value2.integer;
// Format and return result
var result = std.json.Value{ .object = std.json.ObjectMap.init(allocator) };
try result.object.put("calculated_value", std.json.Value{ .integer = result_value });
return result;
}
Choose the appropriate transport for your application:
The server uses an arena allocator for each request, automatically handling cleanup after each request/response cycle to prevent memory leaks.
This project is under active development. The server has been improved with threading support, connection limiting, and timeouts, but several areas still need work: