Middleware Server Example
Demonstrates using middleware to wrap tool handlers with logging, authentication, and custom logic. Shows how middleware can intercept and modify requests before they reach the actual handler.
Overview
This example shows how to:
- Define a shared context struct (
Ctx) for handler state - Create a logging middleware that counts and logs every tool call
- Create an authentication middleware that can short-circuit requests
- Register tools that require authentication vs public tools
- Use
user_datato pass context to handlers
Full Source Code
zig
const std = @import("std");
const mcp = @import("mcp");
const Ctx = struct {
server: *mcp.Server,
io: std.Io,
allocator: std.mem.Allocator,
request_count: u32 = 0,
auth_token: ?[]const u8 = null,
};
fn loggingMiddleware(ctx: ?*anyopaque, io: std.Io, allocator: std.mem.Allocator, name: []const u8, args: ?std.json.Value) ?mcp.tools.ToolResult {
_ = args;
const c: *Ctx = @ptrCast(@alignCast(ctx.?));
c.request_count += 1;
std.debug.print("[MIDDLEWARE] Request #{d}: tool '{s}' called\n", .{ c.request_count, name });
_ = io;
_ = allocator;
return null; // Continue to actual handler
}
fn authMiddleware(ctx: ?*anyopaque, io: std.Io, allocator: std.mem.Allocator, name: []const u8, args: ?std.json.Value) ?mcp.tools.ToolResult {
_ = name;
_ = args;
const c: *Ctx = @ptrCast(@alignCast(ctx.?));
if (c.auth_token == null) {
std.debug.print("[MIDDLEWARE] Authentication failed: no token provided\n", .{});
const result = mcp.tools.textResult(allocator, "Error: Authentication required") catch return null;
_ = io;
return result; // Short-circuit with error
}
std.debug.print("[MIDDLEWARE] Authentication passed\n", .{});
_ = io;
return null; // Continue to actual handler
}
pub fn main(init: std.process.Init) void {
run(init.io, init.gpa) catch |err| mcp.reportError(err);
}
fn run(io: std.Io, allocator: std.mem.Allocator) !void {
var server = mcp.Server.init(allocator, .{
.name = "middleware-server",
.version = "1.0.0",
.title = "Middleware Server",
.description = "Demonstrates middleware for logging and authentication",
});
defer server.deinit();
var ctx: Ctx = .{
.server = &server,
.io = io,
.allocator = allocator,
};
try server.addTool(.{
.name = "protected_action",
.description = "A tool that requires authentication",
.user_data = &ctx,
.handler = protectedActionHandler,
});
try server.addTool(.{
.name = "public_action",
.description = "A tool that doesn't require authentication",
.user_data = &ctx,
.handler = publicActionHandler,
});
server.enableLogging();
try server.run(io, allocator, .stdio);
}
fn protectedActionHandler(user_data: ?*anyopaque, _: std.Io, allocator: std.mem.Allocator, _: ?std.json.Value) mcp.tools.ToolError!mcp.tools.ToolResult {
const ctx: *Ctx = @ptrCast(@alignCast(user_data.?));
_ = ctx;
return mcp.tools.textResult(allocator, "Protected action executed successfully!") catch return mcp.tools.ToolError.OutOfMemory;
}
fn publicActionHandler(user_data: ?*anyopaque, _: std.Io, allocator: std.mem.Allocator, _: ?std.json.Value) mcp.tools.ToolError!mcp.tools.ToolResult {
const ctx: *Ctx = @ptrCast(@alignCast(user_data.?));
_ = ctx;
return mcp.tools.textResult(allocator, "Public action executed successfully!") catch return mcp.tools.ToolError.OutOfMemory;
}Build and Run
bash
zig build
./zig-out/bin/middleware-serverPowerShell (Windows):
powershell
zig build
.\zig-out\bin\middleware-server.exeClient Usage
Discover Server
bash
echo '{"jsonrpc":"2.0","id":1,"method":"server/discover"}' | ./zig-out/bin/middleware-serverPowerShell:
powershell
'{"jsonrpc":"2.0","id":1,"method":"server/discover"}' | .\zig-out\bin\middleware-server.exeCall a Tool
bash
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"public_action"}}' | ./zig-out/bin/middleware-serverPowerShell:
powershell
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"public_action"}}' | .\zig-out\bin\middleware-server.exeExpected Output
Discover response:
json
{"jsonrpc":"2.0","id":1,"result":{"supportedVersions":["2026-07-28","2025-11-25","2025-06-18","2025-03-26","2024-11-05"],"capabilities":{"tools":{"listChanged":true},"logging":{}},"serverInfo":{"name":"middleware-server","version":"1.0.0","title":"Middleware Server","description":"Demonstrates middleware for logging and authentication"}}}Tool call response:
json
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Public action executed successfully!"}],"isError":false,"resultType":"complete","structuredContent":{"text":"Public action executed successfully!"}}}Console output (stderr):
text
[MIDDLEWARE] Request #1: tool 'public_action' called
[MIDDLEWARE] Authentication passedHow Middleware Works
Middleware functions intercept tool calls before the actual handler runs:
- Logging middleware increments a counter and prints the tool name, then returns
nullto continue to the handler - Auth middleware checks
ctx.auth_token— ifnull, it returns an error result (short-circuit); otherwise returnsnullto continue
The middleware chain allows you to add cross-cutting concerns like logging, authentication, rate limiting, and request validation without modifying individual handlers.