MCP Servers

模型上下文协议服务器、框架、SDK 和模板的综合目录。

MCP server by justrach

创建于 2/21/2026
更新于 2 months ago
Repository documentation and setup instructions

mcp-zig

mcp-zig

License: MIT Build Zig MCP

Build MCP servers that fit in a tweet-sized binary.

131 KB. Zero dependencies. Zero runtime. One static binary that gives Claude Code (or any MCP client) new capabilities.

zig build -Doptimize=ReleaseSmall && strip zig-out/bin/mcp-zig
# → 131 KB — 397x smaller than the TypeScript SDK

Your tools are written in Zig. This is a Zig template — you write tool handlers as Zig functions. If you want to use Python or TypeScript, use the official SDKs. If you want a 131 KB binary with zero dependencies that starts in milliseconds, keep reading.


Why this exists

Every MCP server I spun up for Claude Code came with 52 MB of node_modules. For a process that reads stdin and writes stdout. MCP over stdio is one JSON object per line — it doesn't need an async runtime, a schema validator, or a dependency injection framework.

So I built the thinnest possible implementation. The entire server is ~864 lines of Zig across 8 files. It compiles to a static binary you can drop in a dotfiles repo and forget about.

How the SDKs compare

| SDK | Language | Distributable | Dependencies | |-----|----------|---------------|-------------| | typescript-sdk | TypeScript | ~52 MB node_modules + Node.js | 17 npm packages + zod | | python-sdk | Python | ~50+ MB site-packages + Python | pydantic, httpx, anyio, starlette... | | csharp-sdk | C# NativeAOT | ~8-15 MB binary | System.Text.Json + reflection | | go-sdk | Go | ~5-8 MB binary | GC + reflect + encoding/json | | rust-sdk | Rust | ~2-4 MB binary | tokio + serde_json + async runtime | | mcp-zig | Zig | 131 KB binary | 0 — just the Zig stdlib compiled in |

What you ship

| SDK | Deployment | |-----|-----------| | TypeScript | node_modules/ + your code + Node.js installation | | Python | Virtual environment + your code + Python installation | | C# / Go / Rust | Single binary (language toolchain to build) | | Zig | Single 131 KB binary (Zig to build, nothing to run) |


Important: tools are Zig

mcp-zig is a Zig-native template. Your tool handlers are Zig functions that write results to an ArrayList(u8). You can't drop in a Python script or a TypeScript module.

But you have escape hatches:

  • C ABI interop — Zig calls C natively. You can link against C libraries, Rust (extern "C"), or Go (cgo)
  • Shell out — your handler can spawn any external process (std.process.Child) and pipe the output back as the tool response
  • MCP-to-MCP — the included client library (client.zig) can call MCP servers written in any language over stdio, so you can build cross-language pipelines

If you're already in a Node.js or Python environment and just want tools fast, use the official SDKs. mcp-zig is for when you care about binary size, startup latency, zero dependencies, and distributable simplicity.


Quick start

git clone https://github.com/justrach/mcp-zig.git
cd mcp-zig
zig build -Doptimize=ReleaseSmall
strip zig-out/bin/mcp-zig    # optional, shrinks further

Register with Claude Code in ~/.claude.json:

{
  "mcpServers": {
    "my-server": {
      "command": "/absolute/path/to/mcp-zig",
      "args": []
    }
  }
}

Restart Claude Code. Your tools appear as mcp__my-server__read_file, mcp__my-server__list_dir, etc.

Run the same server over HTTP for remote MCP clients:

zig-out/bin/mcp-zig --http 127.0.0.1:8000

The HTTP transport serves MCP JSON-RPC on POST /mcp. initialize returns an Mcp-Session-Id header; send that header on later tools/list and tools/call requests.

Requires Zig 0.16.0.


Use as a Zig package

Instead of cloning, you can import mcp-zig as a dependency in your own Zig project.

1. Add to your build.zig.zon:

.dependencies = .{
    .mcp_zig = .{
        .url = "https://github.com/justrach/mcp-zig/archive/main.tar.gz",
        .hash = "...",  // zig build will tell you the correct hash
    },
},

2. Wire it in your build.zig:

const mcp_dep = b.dependency("mcp_zig", .{});
exe.root_module.addImport("mcp", mcp_dep.module("mcp"));

3. Import in your code:

const mcp = @import("mcp");

// Use the client from a std.process.Init entry point
const McpClient = mcp.client.McpClient;
pub fn main(init: std.process.Init) !void {
    const alloc = init.gpa;
    const io = init.io;

    var client = try McpClient.init(alloc, io, &.{"/path/to/server"}, null);
    defer client.deinit();
}

// Use the registry
const my_tools = mcp.registry.Registry(&.{
    .{ .name = "my_tool", .handler = myHandler, .schema = my_schema },
});

// Use JSON helpers
const value = mcp.json.getStr(args, "key");

Structure

src/
  main.zig           — entry point (5 lines of logic)
  lib.zig            — package root (re-exports public API)
  mcp.zig            — MCP protocol loop + session state (roots, capabilities)
  http.zig           — Streamable HTTP transport entry point (/mcp)
  tools.zig          — YOUR TOOLS GO HERE (read_file + list_dir as examples)
  json.zig           — line reader, field extraction, JSON escaping
  registry.zig       — comptime tool registry (optional, reduces boilerplate)
  client.zig         — MCP client library (spawn server, call tools)
  client_example.zig — client CLI example
build.zig
build.zig.zon        — package manifest (v0.2.0)

To add your own tools, edit tools.zig. Or use registry.zig to cut it down to a single definition.


Adding a tool — 4 steps

1. Add to the enum:

pub const Tool = enum {
    read_file,
    list_dir,
    my_new_tool,  // add here
};

2. Add the JSON schema (this is what Claude reads to understand your tool):

pub const tools_list =
    \\{"tools":[
    \\...,
    \\{"name":"my_new_tool","description":"Does something useful.","inputSchema":{"type":"object","properties":{"input":{"type":"string"}},"required":["input"]}}
    \\]}
;

3. Add a dispatch branch:

pub fn dispatch(...) void {
    switch (tool) {
        .read_file    => handleReadFile(alloc, args, out),
        .list_dir     => handleListDir(alloc, args, out),
        .my_new_tool  => handleMyNewTool(alloc, args, out),
    }
}

4. Write the handler:

fn handleMyNewTool(
    alloc: std.mem.Allocator,
    args: *const std.json.ObjectMap,
    out: *std.ArrayList(u8),
) void {
    const input = json.getStr(args, "input") orelse {
        out.appendSlice(alloc, "error: missing 'input'") catch {};
        return;
    };
    out.appendSlice(alloc, input) catch {};
}

Whatever you write to out becomes the tool response shown to Claude. Errors go to out too — never panic.


Comptime registry — 1 step (optional)

registry.zig reduces the 4-step process to a single definition. It generates parse(), dispatch(), and tools_list at compile time.

const registry = @import("registry.zig");

const my_tools = registry.Registry(&.{
    .{ .name = "read_file",  .handler = handleReadFile,  .schema = read_file_schema },
    .{ .name = "list_dir",   .handler = handleListDir,   .schema = list_dir_schema  },
});

// my_tools.parse("read_file")   → 0
// my_tools.dispatch(alloc, 0, args, out)
// my_tools.tools_list           → combined JSON

wrapFn — zero-boilerplate handlers

Write a normal Zig function and wrapFn generates the MCP handler at comptime:

fn greet(name: []const u8) []const u8 {
    return name;
}
const handler = registry.wrapFn(greet, &.{"name"});

wrapFn inspects the function signature at compile time and generates parameter extraction from JSON args ([]const u8getStr, i64getInt, boolgetBool). Error unions are caught and their error names written as error messages.


Client — calling MCP servers from Zig

mcp-zig includes a client library for calling any MCP server programmatically — regardless of what language that server is written in.

Library API

const McpClient = @import("client.zig").McpClient;

pub fn main(init: std.process.Init) !void {
    const alloc = init.gpa;
    const io = init.io;

    var client = try McpClient.init(alloc, io, &.{"/path/to/server"}, null);
    defer client.deinit();

    const init_result = try client.initialize();
    defer alloc.free(init_result);
    try client.notifyInitialized();

    const tools = try client.listTools();
    defer alloc.free(tools);

    const result = try client.callTool("read_file", "{\"path\":\"hello.txt\"}");
    defer alloc.free(result);
}

One-shot convenience

const callOnce = @import("client.zig").callOnce;

// Spawn → initialize → call → return → clean up, in one call
pub fn main(init: std.process.Init) !void {
    const alloc = init.gpa;
    const io = init.io;

    const result = try callOnce(alloc, io, &.{"/path/to/server"}, "read_file", "{\"path\":\"hello.txt\"}");
    defer alloc.free(result);
}

CLI example

zig build
./zig-out/bin/mcp-client ./zig-out/bin/mcp-zig                          # list tools
./zig-out/bin/mcp-client ./zig-out/bin/mcp-zig read_file '{"path":"."}'  # call a tool
./zig-out/bin/mcp-client ./zig-out/bin/mcp-zig batch '{"operations":[{"tool":"read_file","arguments":{"path":"README.md","max_bytes":80}},{"tool":"list_dir","arguments":{"path":"src"}}]}'

The batch tool returns a structured JSON object with ordered per-item results, so clients can group several filesystem reads into a single tools/call without losing partial-failure information.


Build options

zig build                              # debug (fast compile)
zig build -Doptimize=ReleaseSmall      # release (small binary)
zig build -Dio-backend=evented         # Linux only; macOS/other targets fall back to threaded
strip zig-out/bin/mcp-zig              # shrink further
codesign --sign - --force zig-out/bin/mcp-zig   # macOS Apple Silicon only

mcp-zig uses std.Io.Threaded by default. The -Dio-backend=evented option is gated to Linux builds so you can experiment with std.Io.Evented there without pulling in the current macOS Dispatch backend issues. On macOS and other non-Linux targets, the binaries continue to use Threaded.


Protocol notes

Protocol version: 2025-06-18 (spec)

MCP over stdio is newline-delimited JSON-RPC 2.0 — one JSON object per line, no Content-Length headers (unlike LSP). The critical invariant: every write to stdout is exactly one JSON object followed by \n.

The writeResult function in mcp.zig strips \n and \r from result strings before writing. This matters because Zig \\ multiline string literals embed literal newlines — without stripping, Claude Code's ReadBuffer would parse each line as a separate (invalid) JSON-RPC message and kill the server.

MCP over HTTP is available with mcp-zig --http [host:port]. The first implementation supports POST /mcp request/response JSON-RPC with session headers, plus GET /mcp as an SSE-compatible placeholder event. Resumable long-lived SSE streams are still future work.

What's new in v0.2.0 (2025-06-18 protocol)

| Feature | Description | |---------|------------| | Client capability parsing | Server reads params.capabilities from the initialize request | | Workspace roots | After handshake, server sends roots/list to discover client workspace directories | | Roots change tracking | Handles notifications/roots/list_changed — re-queries roots automatically | | Bidirectional JSON-RPC | New writeRequest() for server-to-client requests (not just responses) | | Title field | serverInfo includes title for human-readable display names | | Session state | Session struct tracks capabilities, pending requests, and parsed roots across the connection |

The server now participates in the full MCP lifecycle:

Client                          Server
  │                               │
  ├─ initialize ─────────────────►│  ← parses client capabilities
  │◄──────────────── result ──────┤  ← returns 2025-06-18 + tools cap
  │                               │
  ├─ notifications/initialized ──►│
  │◄──────────── roots/list ──────┤  ← if client supports roots
  ├─ result (roots array) ───────►│  ← stores workspace roots
  │                               │
  ├─ tools/list ─────────────────►│
  │◄──────────────── result ──────┤
  │                               │
  ├─ notifications/roots/list_changed ►│
  │◄──────────── roots/list ──────┤  ← re-queries on change
  ├─ result (updated roots) ─────►│

Coming soon

  • Structured tool output (outputSchema / structuredContent)
  • Elicitation — server requesting user input via client UI
  • Resource links — returning ResourceLink content blocks from tools
  • Completions — autocomplete for tool arguments
  • Resumable Streamable HTTP SSE event store
  • More example tools (database queries, HTTP requests, file watchers)
  • Cross-compilation targets (Linux, Windows from macOS)
  • Benchmark suite for latency and throughput profiling

Have ideas? Open an issue.


License

MIT


Blog post: mcp-zig: A 131 KB MCP Server Template in Zig — deeper dive into the architecture, benchmarks, and how it was built.

快速设置
此服务器的安装指南

安装命令 (包未发布)

git clone https://github.com/justrach/mcp-zig
手动安装: 请查看 README 获取详细的设置说明和所需的其他依赖项。

Cursor 配置 (mcp.json)

{ "mcpServers": { "justrach-mcp-zig": { "command": "git", "args": [ "clone", "https://github.com/justrach/mcp-zig" ] } } }
作者服务器
其他服务器由 justrach