diff --git a/.devwork/plan.md b/.devwork/plan.md
new file mode 100644
index 0000000..9f62249
--- /dev/null
+++ b/.devwork/plan.md
@@ -0,0 +1,6 @@
+# Execution Plan
+
+## Tasks: 7
+
+{"summary":"Implement MCP (Model Context Protocol) support for gRPC-zig, a JSON-RPC 2.0 based protocol for AI assistants. This involves creating MCP types, protocol handling, transport layer (stdio/HTTP+SSE), and server implementation compatible with Claude.","tasks":[{"id":"T001","tool":"Bash","args":{"command":"mkdir -p /tmp/claudev-pr-95466/src/mcp"},"depends_on":[],"description":"Create MCP directory structure"},{"id":"T002","tool":"Write","args":{"file_path":"/tmp/claudev-pr-95466/src/mcp/types.zig","content":"const std = @import(\"std\");\n\npub const JSONRPCVersion = \"2.0\";\n\npub const ErrorCode = enum(i32) {\n ParseError = -32700,\n InvalidRequest = -32600,\n MethodNotFound = -32601,\n InvalidParams = -32602,\n InternalError = -32603,\n};\n\npub const JSONRPCError = struct {\n code: ErrorCode,\n message: []const u8,\n data: ?[]const u8 = null,\n};\n\npub const ProgressToken = union(enum) {\n string: []const u8,\n number: i64,\n};\n\npub const JSONValue = union(enum) {\n null,\n bool: bool,\n number: f64,\n string: []const u8,\n array: []JSONValue,\n object: std.json.ObjectMap,\n};\n\npub const TextContent = struct {\n type: []const u8 = \"text\",\n text: []const u8,\n};\n\npub const ImageContent = struct {\n type: []const u8 = \"image\",\n data: []const u8,\n mimeType: []const u8,\n};\n\npub const BlobResourceContents = struct {\n type: []const u8 = \"blob\",\n blob: []const u8,\n mimeType: []const u8,\n};\n\npub const EmbeddedResource = struct {\n type: []const u8 = \"resource\",\n resource: Resource,\n};\n\npub const Content = union(enum) {\n text: TextContent,\n image: ImageContent,\n blob: BlobResourceContents,\n embedded: EmbeddedResource,\n};\n\npub const Resource = struct {\n uri: []const u8,\n mimeType: []const u8,\n text: ?[]const u8 = null,\n blob: ?[]const u8 = null,\n};\n\npub const TextResourceContents = struct {\n uri: []const u8,\n mimeType: []const u8,\n text: []const u8,\n};\n\npub const CallToolResult = struct {\n content: []Content,\n isError: bool = false,\n};\n\npub const Tool = struct {\n name: []const u8,\n description: []const u8,\n inputSchema: std.json.ObjectMap,\n};\n\npub const ResourceTemplate = struct {\n uriTemplate: []const u8,\n name: []const u8,\n description: []const u8,\n mimeType: []const u8,\n};\n\npub const Resource_ = struct {\n uri: []const u8,\n name: []const u8,\n description: []const u8,\n mimeType: []const u8,\n};\n\npub const Prompt = struct {\n name: []const u8,\n description: []const u8,\n arguments: ?[]struct {\n name: []const u8,\n description: []const u8,\n required: bool,\n } = null,\n};\n\npub const PromptMessage = struct {\n role: []const u8,\n content: Content,\n};\n\npub const PromptResult = struct {\n messages: []PromptMessage,\n};\n\npub const SamplingMessage = struct {\n role: []const u8,\n content: Content,\n};\n\npub const CreateMessageRequest = struct {\n messages: []SamplingMessage,\n systemPrompt: ?[]const u8 = null,\n includeContext: []const u8,\n temperature: ?f64 = null,\n maxTokens: ?u32 = null,\n};\n\npub const CreateMessageResult = struct {\n role: []const u8,\n content: Content,\n model: []const u8,\n stopReason: []const u8,\n};\n\npub const InitializeResult = struct {\n protocolVersion: []const u8,\n capabilities: ServerCapabilities,\n serverInfo: ServerInfo,\n};\n\npub const ServerCapabilities = struct {\n tools: ?struct {\n listChanged: bool = false,\n } = null,\n resources: ?struct {\n subscribe: bool = false,\n listChanged: bool = false,\n } = null,\n prompts: ?struct {\n listChanged: bool = false,\n } = null,\n sampling: ?struct {},\n};\n\npub const ServerInfo = struct {\n name: []const u8,\n version: []const u8,\n};\n\npub const ClientCapabilities = struct {\n tools: ?struct {\n listChanged: bool = false,\n } = null,\n resources: ?struct {\n subscribe: bool = false,\n listChanged: bool = false,\n } = null,\n prompts: ?struct {\n listChanged: bool = false,\n } = null,\n sampling: ?struct {},\n};\n\npub const InitializeParams = struct {\n protocolVersion: []const u8,\n capabilities: ClientCapabilities,\n clientInfo: ClientInfo,\n};\n\npub const ClientInfo = struct {\n name: []const u8,\n version: []const u8,\n};\n\npub const EmptyParams = struct {};\n\npub const ListToolsResult = struct {\n tools: []Tool,\n};\n\npub const ListResourcesResult = struct {\n resources: []Resource_,\n};\n\npub const ListResourceTemplatesResult = struct {\n resourceTemplates: []ResourceTemplate,\n};\n\npub const ListPromptsResult = struct {\n prompts: []Prompt,\n};\n\npub const ReadResourceResult = struct {\n contents: []const union(enum) {\n TextResourceContents,\n BlobResourceContents,\n },\n};\n\npub const JSONRPCMessage = union(enum) {\n request: JSONRPCRequest,\n notification: JSONRPCNotification,\n response: JSONRPCResponse,\n error: JSONRPCErrorResponse,\n};\n\npub const JSONRPCRequest = struct {\n jsonrpc: []const u8,\n id: union(enum) {\n string: []const u8,\n number: i64,\n },\n method: []const u8,\n params: ?JSONValue = null,\n};\n\npub const JSONRPCNotification = struct {\n jsonrpc: []const u8,\n method: []const u8,\n params: ?JSONValue = null,\n};\n\npub const JSONRPCResponse = struct {\n jsonrpc: []const u8,\n id: union(enum) {\n string: []const u8,\n number: i64,\n },\n result: JSONValue,\n};\n\npub const JSONRPCErrorResponse = struct {\n jsonrpc: []const u8,\n id: ?union(enum) {\n string: []const u8,\n number: i64,\n } = null,\n error: JSONRPCError,\n};\n\npub fn parseMessage(allocator: std.mem.Allocator, data: []const u8) !JSONRPCMessage {\n const parsed = try std.json.parseFromSlice(JSONValue, allocator, data, .{});\n defer parsed.deinit();\n\n const value = parsed.value;\n if (value != .object) {\n return error.InvalidJSON;\n }\n\n const obj = value.object;\n\n // Check if it's a request (has method and id)\n if (obj.get(\"method\")) |method_val| {\n if (obj.get(\"id\")) |id_val| {\n // Request\n const method = method_val.string;\n const id = if (id_val == .number) .{ number = @intCast(id_val.number) } else .{ string = id_val.string };\n const params = if (obj.get(\"params\")) |p| p else null;\n\n return JSONRPCMessage{\n .request = .{\n .jsonrpc = JSONRPCVersion,\n .id = id,\n .method = method,\n .params = params,\n },\n };\n } else {\n // Notification\n const method = method_val.string;\n const params = if (obj.get(\"params\")) |p| p else null;\n\n return JSONRPCMessage{\n .notification = .{\n .jsonrpc = JSONRPCVersion,\n .method = method,\n .params = params,\n },\n };\n }\n }\n\n // Check if it's an error response\n if (obj.get(\"error\")) |_| {\n const id = if (obj.get(\"id\")) |id_val| if (id_val == .number) .{ number = @intCast(id_val.number) } else .{ string = id_val.string } else null;\n const err_obj = obj.get(\"error\").?.object;\n const code = @enumFromInt(@intFromFloat(err_obj.get(\"code\").?.number));\n const message = err_obj.get(\"message\").?.string;\n const data_val = if (err_obj.get(\"data\")) |d| d.string else null;\n\n return JSONRPCMessage{\n .error = .{\n .jsonrpc = JSONRPCVersion,\n .id = id,\n .error = .{ .code = code, .message = message, .data = data_val },\n },\n };\n }\n\n // Must be a response\n const id = if (obj.get(\"id\").?) |id_val| if (id_val == .number) .{ number = @intCast(id_val.number) } else .{ string = id_val.string } else unreachable;\n const result = obj.get(\"result\").?;\n\n return JSONRPCMessage{\n .response = .{\n .jsonrpc = JSONRPCVersion,\n .id = id,\n .result = result,\n },\n };\n}\n\npub fn serializeMessage(allocator: std.mem.Allocator, msg: JSONRPCMessage) ![]u8 {\n var buf = std.ArrayList(u8).init(allocator);\n try serializeMessageWriter(msg, buf.writer());\n return buf.toOwnedSlice();\n}\n\nfn serializeMessageWriter(msg: JSONRPCMessage, writer: anytype) !void {\n switch (msg) {\n .request => |req| {\n try writer.writeByte('{');\n try writer.print(\"\\\"jsonrpc\\\":\\\"{s}\\\",\\\"id\\\":\", .{req.jsonrpc});\n switch (req.id) {\n .string => |s| try writer.print(\"\\\"{s}\\\",\", .{s}),\n .number => |n| try writer.print(\"{},\", .{n}),\n }\n try writer.print(\"\\\"method\\\":\\\"{s}\\\"\", .{req.method});\n if (req.params) |params| {\n try writer.writeByte(',');\n try writer.writeAll(\"\\\"params\\\":\");\n try jsonValueWrite(params, writer);\n }\n try writer.writeByte('}');\n },\n .notification => |notif| {\n try writer.writeByte('{');\n try writer.print(\"\\\"jsonrpc\\\":\\\"{s}\\\",\\\"method\\\":\\\"{s}\\\"\", .{notif.jsonrpc, notif.method});\n if (notif.params) |params| {\n try writer.writeByte(',');\n try writer.writeAll(\"\\\"params\\\":\");\n try jsonValueWrite(params, writer);\n }\n try writer.writeByte('}');\n },\n .response => |resp| {\n try writer.writeByte('{');\n try writer.print(\"\\\"jsonrpc\\\":\\\"{s}\\\",\\\"id\\\":\", .{resp.jsonrpc});\n switch (resp.id) {\n .string => |s| try writer.print(\"\\\"{s}\\\",\", .{s}),\n .number => |n| try writer.print(\"{},\", .{n}),\n }\n try writer.writeAll(\"\\\"result\\\":\");\n try jsonValueWrite(resp.result, writer);\n try writer.writeByte('}');\n },\n .error => |err| {\n try writer.writeByte('{');\n try writer.print(\"\\\"jsonrpc\\\":\\\"{s}\\\",\\\"error\\\":{{\"code\":{},\\\"message\\\":\\\"{s}\\\"\", .{err.jsonrpc, @intFromEnum(err.error.code), err.error.message});\n if (err.error.data) |data| {\n try writer.print(\",\\\"data\\\":\\\"{s}\\\"\", .{data});\n }\n try writer.writeByte('}');\n if (err.id) |id| {\n try writer.writeByte(',');\n try writer.writeAll(\"\\\"id\\\":\");\n switch (id) {\n .string => |s| try writer.print(\"\\\"{s}\\\"\", .{s}),\n .number => |n| try writer.writeAll(\"{}*{n}\"),\n }\n }\n try writer.writeByte('}');\n },\n }\n}\n\nfn jsonValueWrite(val: JSONValue, writer: anytype) !void {\n switch (val) {\n .null => try writer.writeAll(\"null\"),\n .bool => |b| try writer.writeAll(if (b) \"true\" else \"false\"),\n .number => |n| try writer.print(\"{}\", .{n}),\n .string => |s| {\n try writer.writeByte('\"');\n for (s) |c| {\n switch (c) {\n '\\\\' => try writer.writeAll(\"\\\\\\\\\"),\n '\"' => try writer.writeAll(\"\\\\\\\"\"),\n '\\n' => try writer.writeAll(\"\\\\n\"),\n '\\r' => try writer.writeAll(\"\\\\r\"),\n '\\t' => try writer.writeAll(\"\\\\t\"),\n else => try writer.writeByte(c),\n }\n }\n try writer.writeByte('\"');\n },\n .array => |arr| {\n try writer.writeByte('[');\n for (arr, 0..) |item, i| {\n if (i > 0) try writer.writeByte(',');\n try jsonValueWrite(item, writer);\n }\n try writer.writeByte(']');\n },\n .object => |obj| {\n try writer.writeByte('{');\n var first = true;\n var it = obj.iterator();\n while (it.next()) |entry| {\n if (!first) try writer.writeByte(',');\n first = false;\n try writer.print(\"\\\"{s}\\\":\", .{entry.key_ptr.*});\n try jsonValueWrite(entry.value_ptr.*, writer);\n }\n try writer.writeByte('}');\n },\n }\n}\n"}, "depends_on": ["T001"], "description":"Create MCP types module with JSON-RPC types, capabilities, and message parsing"},{"id":"T003","tool":"Write","args":{"file_path":"/tmp/claudev-pr-95466/src/mcp/server.zig","content":"const std = @import(\"std\");\nconst types = @import(\"types.zig\");\nconst json = std.json;\n\npub const MethodHandler = fn (params: types.JSONValue, allocator: std.mem.Allocator) anyerror!types.JSONValue;\n\npub const MCPServer = struct {\n allocator: std.mem.Allocator,\n capabilities: types.ServerCapabilities,\n serverInfo: types.ServerInfo,\n protocolVersion: []const u8,\n handlers: std.StringHashMap(MethodHandler),\n tools: std.ArrayList(types.Tool),\n resources: std.ArrayList(types.Resource_),\n resourceTemplates: std.ArrayList(types.ResourceTemplate),\n prompts: std.ArrayList(types.Prompt),\n\n pub fn init(allocator: std.mem.Allocator, name: []const u8, version: []const u8) MCPServer {\n return MCPServer{\n .allocator = allocator,\n .capabilities = .{\n .tools = .{ .listChanged = false },\n .resources = .{ .subscribe = false, .listChanged = false },\n .prompts = .{ .listChanged = false },\n .sampling = .{},\n },\n .serverInfo = .{ .name = name, .version = version },\n .protocolVersion = \"2025-11-25\",\n .handlers = std.StringHashMap(MethodHandler).init(allocator),\n .tools = std.ArrayList(types.Tool).init(allocator),\n .resources = std.ArrayList(types.Resource_).init(allocator),\n .resourceTemplates = std.ArrayList(types.ResourceTemplate).init(allocator),\n .prompts = std.ArrayList(types.Prompt).init(allocator),\n };\n }\n\n pub fn deinit(self: *MCPServer) void {\n self.handlers.deinit();\n self.tools.deinit();\n self.resources.deinit();\n self.resourceTemplates.deinit();\n self.prompts.deinit();\n }\n\n pub fn registerTool(self: *MCPServer, name: []const u8, description: []const u8, handler: MethodHandler) !void {\n const tool = types.Tool{\n .name = name,\n .description = description,\n .inputSchema = std.json.ObjectMap.init(self.allocator),\n };\n try self.tools.append(tool);\n try self.handlers.put(try self.allocator.dupe(u8, name), handler);\n }\n\n pub fn registerResource(self: *MCPServer, uri: []const u8, name: []const u8, description: []const u8, mimeType: []const u8) !void {\n const resource = types.Resource_{\n .uri = uri,\n .name = name,\n .description = description,\n .mimeType = mimeType,\n };\n try self.resources.append(resource);\n }\n\n pub fn registerPrompt(self: *MCPServer, name: []const u8, description: []const u8) !void {\n const prompt = types.Prompt{\n .name = name,\n .description = description,\n .arguments = null,\n };\n try self.prompts.append(prompt);\n }\n\n pub fn handleMessage(self: *MCPServer, data: []const u8) ![]u8 {\n const msg = try types.parseMessage(self.allocator, data);\n defer {\n // Clean up parsed message if needed\n }\n\n switch (msg) {\n .request => |req| {\n const result = try self.handleRequest(req);\n const response = types.JSONRPCMessage{\n .response = .{\n .jsonrpc = types.JSONRPCVersion,\n .id = req.id,\n .result = result,\n },\n };\n return try types.serializeMessage(self.allocator, response);\n },\n .notification => |notif| {\n try self.handleNotification(notif);\n return &[_]u8{};\n },\n .error => |err| {\n const response = types.JSONRPCMessage{\n .error = .{\n .jsonrpc = types.JSONRPCVersion,\n .id = err.id,\n .error = .{\n .code = .InvalidRequest,\n .message = \"Received error message\",\n },\n },\n };\n return try types.serializeMessage(self.allocator, response);\n },\n .response => |_| {\n // Ignore unexpected responses\n return &[_]u8{};\n },\n }\n }\n\n fn handleRequest(self: *MCPServer, req: types.JSONRPCRequest) !types.JSONValue {\n if (std.mem.eql(u8, req.method, \"initialize\")) {\n return try self.handleInitialize(req.params);\n } else if (std.mem.eql(u8, req.method, \"ping\")) {\n return try self.handlePing(req.params);\n } else if (std.mem.eql(u8, req.method, \"tools/list\")) {\n return try self.handleToolsList(req.params);\n } else if (std.mem.eql(u8, req.method, \"tools/call\")) {\n return try self.handleToolsCall(req.params);\n } else if (std.mem.eql(u8, req.method, \"resources/list\")) {\n return try self.handleResourcesList(req.params);\n } else if (std.mem.eql(u8, req.method, \"resources/read\")) {\n return try self.handleResourcesRead(req.params);\n } else if (std.mem.eql(u8, req.method, \"resources/templates/list\")) {\n return try self.handleResourceTemplatesList(req.params);\n } else if (std.mem.eql(u8, req.method, \"prompts/list\")) {\n return try self.handlePromptsList(req.params);\n } else if (std.mem.eql(u8, req.method, \"prompts/get\")) {\n return try self.handlePromptsGet(req.params);\n } else if (std.mem.eql(u8, req.method, \"sampling/createMessage\")) {\n return try self.handleSampling(req.params);\n } else {\n return types.JSONValue{ .null = {} };\n }\n }\n\n fn handleNotification(self: *MCPServer, notif: types.JSONRPCNotification) !void {\n if (std.mem.eql(u8, notif.method, \"notifications/initialized\")) {\n // Client has finished initialization\n std.log.info(\"MCP client initialized\", .{});\n }\n }\n\n fn handleInitialize(self: *MCPServer, params: ?types.JSONValue) !types.JSONValue {\n if (params) |p| {\n if (p == .object) {\n if (p.object.get(\"protocolVersion\")) |pv| {\n self.protocolVersion = try self.allocator.dupe(u8, pv.string);\n }\n }\n }\n\n const result = types.InitializeResult{\n .protocolVersion = self.protocolVersion,\n .capabilities = self.capabilities,\n .serverInfo = self.serverInfo,\n };\n\n var obj = std.json.ObjectMap.init(self.allocator);\n try obj.put(\"protocolVersion\", .{ .string = result.protocolVersion });\n try obj.put(\"capabilities\", try jsonValueFromCapabilities(self.allocator, result.capabilities));\n try obj.put(\"serverInfo\", try jsonValueFromServerInfo(self.allocator, result.serverInfo));\n\n return .{ .object = obj };\n }\n\n fn handlePing(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n return .{ .object = std.json.ObjectMap.init(self.allocator) };\n }\n\n fn handleToolsList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n var tools_array = std.json.Array.init(self.allocator);\n for (self.tools.items) |tool| {\n var tool_obj = std.json.ObjectMap.init(self.allocator);\n try tool_obj.put(\"name\", .{ .string = tool.name });\n try tool_obj.put(\"description\", .{ .string = tool.description });\n try tools_array.append(.{ .object = tool_obj });\n }\n\n var result = std.json.ObjectMap.init(self.allocator);\n try result.put(\"tools\", .{ .array = tools_array });\n\n return .{ .object = result };\n }\n\n fn handleToolsCall(self: *MCPServer, params: ?types.JSONValue) !types.JSONValue {\n if (params == null or params.? != .object) {\n return types.JSONValue{ .null = {} };\n }\n\n const params_obj = params.?.object;\n const name = params_obj.get(\"name\").?.string;\n\n if (self.handlers.get(name)) |handler| {\n const arguments = params_obj.get(\"arguments\");\n const result = try handler(arguments orelse .{ .object = std.json.ObjectMap.init(self.allocator) }, self.allocator);\n return result;\n }\n\n // Return error\n var error_obj = std.json.ObjectMap.init(self.allocator);\n try error_obj.put(\"content\", .{ .array = std.json.Array.init(self.allocator) });\n try error_obj.put(\"isError\", .{ .bool = true });\n\n return .{ .object = error_obj };\n }\n\n fn handleResourcesList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n var resources_array = std.json.Array.init(self.allocator);\n for (self.resources.items) |resource| {\n var res_obj = std.json.ObjectMap.init(self.allocator);\n try res_obj.put(\"uri\", .{ .string = resource.uri });\n try res_obj.put(\"name\", .{ .string = resource.name });\n try res_obj.put(\"description\", .{ .string = resource.description });\n try res_obj.put(\"mimeType\", .{ .string = resource.mimeType });\n try resources_array.append(.{ .object = res_obj });\n }\n\n var result = std.json.ObjectMap.init(self.allocator);\n try result.put(\"resources\", .{ .array = resources_array });\n\n return .{ .object = result };\n }\n\n fn handleResourcesRead(self: *MCPServer, params: ?types.JSONValue) !types.JSONValue {\n if (params == null or params.? != .object) {\n return types.JSONValue{ .null = {} };\n }\n\n const params_obj = params.?.object;\n const uri = params_obj.get(\"uri\").?.string;\n\n // Return empty content for now - implement actual resource reading\n var contents_array = std.json.Array.init(self.allocator);\n var result = std.json.ObjectMap.init(self.allocator);\n try result.put(\"contents\", .{ .array = contents_array });\n\n return .{ .object = result };\n }\n\n fn handleResourceTemplatesList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n var templates_array = std.json.Array.init(self.allocator);\n\n var result = std.json.ObjectMap.init(self.allocator);\n try result.put(\"resourceTemplates\", .{ .array = templates_array });\n\n return .{ .object = result };\n }\n\n fn handlePromptsList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n var prompts_array = std.json.Array.init(self.allocator);\n for (self.prompts.items) |prompt| {\n var prompt_obj = std.json.ObjectMap.init(self.allocator);\n try prompt_obj.put(\"name\", .{ .string = prompt.name });\n try prompt_obj.put(\"description\", .{ .string = prompt.description });\n try prompts_array.append(.{ .object = prompt_obj });\n }\n\n var result = std.json.ObjectMap.init(self.allocator);\n try result.put(\"prompts\", .{ .array = prompts_array });\n\n return .{ .object = result };\n }\n\n fn handlePromptsGet(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n return types.JSONValue{ .null = {} };\n }\n\n fn handleSampling(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n return types.JSONValue{ .null = {} };\n }\n};\n\nfn jsonValueFromCapabilities(allocator: std.mem.Allocator, caps: types.ServerCapabilities) !types.JSONValue {\n var obj = std.json.ObjectMap.init(allocator);\n\n if (caps.tools) |_| {\n var tools_obj = std.json.ObjectMap.init(allocator);\n try tools_obj.put(\"listChanged\", .{ .bool = false });\n try obj.put(\"tools\", .{ .object = tools_obj });\n }\n\n if (caps.resources) |_| {\n var res_obj = std.json.ObjectMap.init(allocator);\n try res_obj.put(\"subscribe\", .{ .bool = false });\n try res_obj.put(\"listChanged\", .{ .bool = false });\n try obj.put(\"resources\", .{ .object = res_obj });\n }\n\n if (caps.prompts) |_| {\n var prompts_obj = std.json.ObjectMap.init(allocator);\n try prompts_obj.put(\"listChanged\", .{ .bool = false });\n try obj.put(\"prompts\", .{ .object = prompts_obj });\n }\n\n if (caps.sampling) |_| {\n try obj.put(\"sampling\", .{ .object = std.json.ObjectMap.init(allocator) });\n }\n\n return .{ .object = obj };\n}\n\nfn jsonValueFromServerInfo(allocator: std.mem.Allocator, info: types.ServerInfo) !types.JSONValue {\n var obj = std.json.ObjectMap.init(allocator);\n try obj.put(\"name\", .{ .string = info.name });\n try obj.put(\"version\", .{ .string = info.version });\n return .{ .object = obj };\n}\n"}, "depends_on": ["T001"], "description":"Create MCP server module with method handlers and message routing"},{"id":"T004","tool":"Write","args":{"file_path":"/tmp/claudev-pr-95466/src/mcp/transport.zig","content":"const std = @import(\"std\");\nconst types = @import(\"types.zig\");\n\npub const Transport = union(enum) {\n stdio: StdioTransport,\n http: HttpTransport,\n};\n\npub const StdioTransport = struct {\n allocator: std.mem.Allocator,\n\n pub fn init(allocator: std.mem.Allocator) StdioTransport {\n return StdioTransport{\n .allocator = allocator,\n };\n }\n\n pub fn readMessage(self: StdioTransport) ![]u8 {\n // Read length as 4-byte big-endian integer\n var len_buf: [4]u8 = undefined;\n const bytes_read = try std.os.read(std.os.STDIN_FILENO, &len_buf);\n if (bytes_read == 0) {\n return error.ConnectionClosed;\n }\n if (bytes_read < 4) {\n return error.InvalidMessage;\n }\n\n const len = std.mem.readBig(u32, &len_buf);\n if (len > 10 * 1024 * 1024) {\n return error.MessageTooLarge;\n }\n\n const message = try self.allocator.alloc(u8, len);\n errdefer self.allocator.free(message);\n\n var remaining = len;\n var offset: usize = 0;\n while (remaining > 0) {\n const read = try std.os.read(std.os.STDIN_FILENO, message[offset..]);\n if (read == 0) {\n return error.ConnectionClosed;\n }\n offset += read;\n remaining -= read;\n }\n\n return message;\n }\n\n pub fn writeMessage(self: StdioTransport, data: []const u8) !void {\n // Write length as 4-byte big-endian integer followed by data\n var len_buf: [4]u8 = undefined;\n std.mem.writeBig(u32, &len_buf, @intCast(data.len));\n\n var total_written: usize = 0;\n while (total_written < 4) {\n const written = try std.os.write(std.os.STDOUT_FILENO, len_buf[total_written..]);\n if (written == 0) {\n return error.ConnectionClosed;\n }\n total_written += written;\n }\n\n total_written = 0;\n while (total_written < data.len) {\n const written = try std.os.write(std.os.STDOUT_FILENO, data[total_written..]);\n if (written == 0) {\n return error.ConnectionClosed;\n }\n total_written += written;\n }\n }\n};\n\npub const HttpTransport = struct {\n allocator: std.mem.Allocator,\n stream: std.net.Stream,\n buffer: std.ArrayList(u8),\n\n pub fn init(allocator: std.mem.Allocator, stream: std.net.Stream) HttpTransport {\n return HttpTransport{\n .allocator = allocator,\n .stream = stream,\n .buffer = std.ArrayList(u8).init(allocator),\n };\n }\n\n pub fn deinit(self: *HttpTransport) void {\n self.buffer.deinit();\n }\n\n pub fn readMessage(self: *HttpTransport) ![]u8 {\n // Read HTTP headers\n var header_buf = [4096]u8{};\n var header_end: usize = 0;\n \n while (true) {\n const bytes_read = try self.stream.read(header_buf[header_end..header_end + 1]);\n if (bytes_read == 0) {\n return error.ConnectionClosed;\n }\n\n if (header_end >= 3 and \n header_buf[header_end - 3] == '\\r' and \n header_buf[header_end - 2] == '\\n' and \n header_buf[header_end - 1] == '\\r' and\n header_end >= 1 and header_buf[header_end] == '\\n') {\n break;\n }\n header_end += bytes_read;\n }\n\n // Parse Content-Length header\n const headers = header_buf[0..header_end];\n var content_length: usize = 0;\n var lines = std.mem.split(u8, headers, \"\\r\\n\");\n while (lines.next()) |line| {\n if (std.ascii.startsWithIgnoreCase(line, \"content-length:\")) {\n const value = std.mem.trim(u8, line[15..], \" \");\n content_length = try std.fmt.parseInt(usize, value, 10);\n }\n }\n\n if (content_length == 0) {\n return error.InvalidMessage;\n }\n\n // Read body\n const body = try self.allocator.alloc(u8, content_length);\n errdefer self.allocator.free(body);\n\n var remaining = content_length;\n var offset: usize = 0;\n while (remaining > 0) {\n const read = try self.stream.read(body[offset..]);\n if (read == 0) {\n return error.ConnectionClosed;\n }\n offset += read;\n remaining -= read;\n }\n\n return body;\n }\n\n pub fn writeMessage(self: *HttpTransport, data: []const u8) !void {\n // Write HTTP response with SSE\n var buf = std.ArrayList(u8).init(self.allocator);\n defer buf.deinit();\n\n try buf.writer().print(\n \"HTTP/1.1 200 OK\\r\\n\" ++\n \"Content-Type: text/event-stream\\r\\n\" ++\n \"Cache-Control: no-cache\\r\\n\" ++\n \"Connection: keep-alive\\r\\n\" ++\n \"Transfer-Encoding: chunked\\r\\n\" ++\n \"\\r\\n\",\n .{}\n );\n\n // Write chunked body\n try buf.writer().print(\"{x}\\r\\n\", .{data.len});\n try buf.appendSlice(data);\n try buf.appendSlice(\"\\r\\n\");\n try buf.appendSlice(\"0\\r\\n\\r\\n\");\n\n try self.stream.writeAll(buf.items);\n }\n};\n\npub fn runStdioServer(allocator: std.mem.Allocator, server: anytype) !void {\n var transport = StdioTransport.init(allocator);\n\n while (true) {\n const message = transport.readMessage() catch |err| {\n if (err == error.ConnectionClosed) {\n break;\n }\n std.log.err(\"Failed to read message: {}\", .{err});\n continue;\n };\n\n const response = server.handleMessage(message) catch |err| {\n std.log.err(\"Failed to handle message: {}\", .{err});\n allocator.free(message);\n continue;\n };\n allocator.free(message);\n\n if (response.len > 0) {\n transport.writeMessage(response) catch |err| {\n std.log.err(\"Failed to write response: {}\", .{err});\n break;\n };\n allocator.free(response);\n }\n }\n}\n"}, "depends_on": ["T001"], "description":"Create MCP transport module for stdio and HTTP+SSE transports"},{"id":"T005","tool":"Write","args":{"file_path":"/tmp/claudev-pr-95466/src/mcp.zig","content":"pub const mcp = struct {\n pub const types = @import(\"mcp/types.zig\");\n pub const server = @import(\"mcp/server.zig\");\n pub const transport = @import(\"mcp/transport.zig\");\n};\n"}, "depends_on": ["T001"], "description":"Create main MCP module that re-exports all MCP components"},{"id":"T006","tool":"Write","args":{"file_path":"/tmp/claudev-pr-95466/examples/mcp_server.zig","content":"const std = @import(\"std\");\nconst mcp = @import(\"mcp\");\n\npub fn main() !void {\n var gpa = std.heap.GeneralPurposeAllocator(.{}){};\n defer _ = gpa.deinit();\n const allocator = gpa.allocator();\n\n // Create MCP server\n var server = mcp.server.MCPServer.init(allocator, \"gRPC-zig MCP Server\", \"1.0.0\");\n defer server.deinit();\n\n // Register a simple echo tool\n try server.registerTool(\"echo\", \"Echo back the input text\", echoHandler);\n\n // Register a greeting tool\n try server.registerTool(\"greet\", \"Generate a greeting message\", greetHandler);\n\n // Register a sample resource\n try server.registerResource(\"example://hello\", \"Hello Resource\", \"A simple hello world resource\", \"text/plain\");\n\n // Register a sample prompt\n try server.registerTool(\"generate_prompt\", \"Generate a prompt for AI\", promptHandler);\n\n std.debug.print(\"Starting MCP server on stdio...\\n\", .{});\n\n // Run the server\n try mcp.transport.runStdioServer(allocator, &server);\n}\n\nfn echoHandler(params: mcp.types.JSONValue, allocator: std.mem.Allocator) !mcp.types.JSONValue {\n var obj = std.json.ObjectMap.init(allocator);\n var content_array = std.json.Array.init(allocator);\n\n var content_obj = std.json.ObjectMap.init(allocator);\n try content_obj.put(\"type\", .{ .string = \"text\" });\n\n var text = \"echo\";\n if (params == .object) {\n if (params.object.get(\"text\")) |t| {\n text = t.string;\n }\n }\n try content_obj.put(\"text\", .{ .string = text });\n\n try content_array.append(.{ .object = content_obj });\n try obj.put(\"content\", .{ .array = content_array });\n try obj.put(\"isError\", .{ .bool = false });\n\n return .{ .object = obj };\n}\n\nfn greetHandler(params: mcp.types.JSONValue, allocator: std.mem.Allocator) !mcp.types.JSONValue {\n var obj = std.json.ObjectMap.init(allocator);\n var content_array = std.json.Array.init(allocator);\n\n var content_obj = std.json.ObjectMap.init(allocator);\n try content_obj.put(\"type\", .{ .string = \"text\" });\n\n var name = \"World\";\n if (params == .object) {\n if (params.object.get(\"name\")) |n| {\n name = n.string;\n }\n }\n const greeting = try std.fmt.allocPrint(allocator, \"Hello, {s}!\", .{name});\n defer allocator.free(greeting);\n\n try content_obj.put(\"text\", .{ .string = greeting });\n try content_array.append(.{ .object = content_obj });\n try obj.put(\"content\", .{ .array = content_array });\n try obj.put(\"isError\", .{ .bool = false });\n\n return .{ .object = obj };\n}\n\nfn promptHandler(params: mcp.types.JSONValue, allocator: std.mem.Allocator) !mcp.types.JSONValue {\n var obj = std.json.ObjectMap.init(allocator);\n var content_array = std.json.Array.init(allocator);\n\n var content_obj = std.json.ObjectMap.init(allocator);\n try content_obj.put(\"type\", .{ .string = \"text\" });\n\n var task = \"general\";\n if (params == .object) {\n if (params.object.get(\"task\")) |t| {\n task = t.string;\n }\n }\n\n const prompt_text = switch (task[0]) {\n 's' => \"Summarize the following: \",\n 'e' => \"Explain the following: \",\n else => \"Analyze the following: \",\n };\n\n try content_obj.put(\"text\", .{ .string = prompt_text });\n try content_array.append(.{ .object = content_obj });\n try obj.put(\"content\", .{ .array = content_array });\n try obj.put(\"isError\", .{ .bool = false });\n\n return .{ .object = obj };\n}\n"}, "depends_on": ["T001"], "description":"Create example MCP server showing usage of MCP types and server"},{"id":"T007","tool":"Read","args":{"file_path":"/tmp/claudev-pr-95466/build.zig"},"depends_on":[],"description":"Read build.zig to understand existing structure before modification"},null,"T008","tool Edit - will be added after reviewing build.zig","args:{},"]}
+
diff --git a/.devwork/plan_response.txt b/.devwork/plan_response.txt
new file mode 100644
index 0000000..2edaceb
--- /dev/null
+++ b/.devwork/plan_response.txt
@@ -0,0 +1 @@
+{"summary":"Implement MCP (Model Context Protocol) support for gRPC-zig, a JSON-RPC 2.0 based protocol for AI assistants. This involves creating MCP types, protocol handling, transport layer (stdio/HTTP+SSE), and server implementation compatible with Claude.","tasks":[{"id":"T001","tool":"Bash","args":{"command":"mkdir -p /tmp/claudev-pr-95466/src/mcp"},"depends_on":[],"description":"Create MCP directory structure"},{"id":"T002","tool":"Write","args":{"file_path":"/tmp/claudev-pr-95466/src/mcp/types.zig","content":"const std = @import(\"std\");\n\npub const JSONRPCVersion = \"2.0\";\n\npub const ErrorCode = enum(i32) {\n ParseError = -32700,\n InvalidRequest = -32600,\n MethodNotFound = -32601,\n InvalidParams = -32602,\n InternalError = -32603,\n};\n\npub const JSONRPCError = struct {\n code: ErrorCode,\n message: []const u8,\n data: ?[]const u8 = null,\n};\n\npub const ProgressToken = union(enum) {\n string: []const u8,\n number: i64,\n};\n\npub const JSONValue = union(enum) {\n null,\n bool: bool,\n number: f64,\n string: []const u8,\n array: []JSONValue,\n object: std.json.ObjectMap,\n};\n\npub const TextContent = struct {\n type: []const u8 = \"text\",\n text: []const u8,\n};\n\npub const ImageContent = struct {\n type: []const u8 = \"image\",\n data: []const u8,\n mimeType: []const u8,\n};\n\npub const BlobResourceContents = struct {\n type: []const u8 = \"blob\",\n blob: []const u8,\n mimeType: []const u8,\n};\n\npub const EmbeddedResource = struct {\n type: []const u8 = \"resource\",\n resource: Resource,\n};\n\npub const Content = union(enum) {\n text: TextContent,\n image: ImageContent,\n blob: BlobResourceContents,\n embedded: EmbeddedResource,\n};\n\npub const Resource = struct {\n uri: []const u8,\n mimeType: []const u8,\n text: ?[]const u8 = null,\n blob: ?[]const u8 = null,\n};\n\npub const TextResourceContents = struct {\n uri: []const u8,\n mimeType: []const u8,\n text: []const u8,\n};\n\npub const CallToolResult = struct {\n content: []Content,\n isError: bool = false,\n};\n\npub const Tool = struct {\n name: []const u8,\n description: []const u8,\n inputSchema: std.json.ObjectMap,\n};\n\npub const ResourceTemplate = struct {\n uriTemplate: []const u8,\n name: []const u8,\n description: []const u8,\n mimeType: []const u8,\n};\n\npub const Resource_ = struct {\n uri: []const u8,\n name: []const u8,\n description: []const u8,\n mimeType: []const u8,\n};\n\npub const Prompt = struct {\n name: []const u8,\n description: []const u8,\n arguments: ?[]struct {\n name: []const u8,\n description: []const u8,\n required: bool,\n } = null,\n};\n\npub const PromptMessage = struct {\n role: []const u8,\n content: Content,\n};\n\npub const PromptResult = struct {\n messages: []PromptMessage,\n};\n\npub const SamplingMessage = struct {\n role: []const u8,\n content: Content,\n};\n\npub const CreateMessageRequest = struct {\n messages: []SamplingMessage,\n systemPrompt: ?[]const u8 = null,\n includeContext: []const u8,\n temperature: ?f64 = null,\n maxTokens: ?u32 = null,\n};\n\npub const CreateMessageResult = struct {\n role: []const u8,\n content: Content,\n model: []const u8,\n stopReason: []const u8,\n};\n\npub const InitializeResult = struct {\n protocolVersion: []const u8,\n capabilities: ServerCapabilities,\n serverInfo: ServerInfo,\n};\n\npub const ServerCapabilities = struct {\n tools: ?struct {\n listChanged: bool = false,\n } = null,\n resources: ?struct {\n subscribe: bool = false,\n listChanged: bool = false,\n } = null,\n prompts: ?struct {\n listChanged: bool = false,\n } = null,\n sampling: ?struct {},\n};\n\npub const ServerInfo = struct {\n name: []const u8,\n version: []const u8,\n};\n\npub const ClientCapabilities = struct {\n tools: ?struct {\n listChanged: bool = false,\n } = null,\n resources: ?struct {\n subscribe: bool = false,\n listChanged: bool = false,\n } = null,\n prompts: ?struct {\n listChanged: bool = false,\n } = null,\n sampling: ?struct {},\n};\n\npub const InitializeParams = struct {\n protocolVersion: []const u8,\n capabilities: ClientCapabilities,\n clientInfo: ClientInfo,\n};\n\npub const ClientInfo = struct {\n name: []const u8,\n version: []const u8,\n};\n\npub const EmptyParams = struct {};\n\npub const ListToolsResult = struct {\n tools: []Tool,\n};\n\npub const ListResourcesResult = struct {\n resources: []Resource_,\n};\n\npub const ListResourceTemplatesResult = struct {\n resourceTemplates: []ResourceTemplate,\n};\n\npub const ListPromptsResult = struct {\n prompts: []Prompt,\n};\n\npub const ReadResourceResult = struct {\n contents: []const union(enum) {\n TextResourceContents,\n BlobResourceContents,\n },\n};\n\npub const JSONRPCMessage = union(enum) {\n request: JSONRPCRequest,\n notification: JSONRPCNotification,\n response: JSONRPCResponse,\n error: JSONRPCErrorResponse,\n};\n\npub const JSONRPCRequest = struct {\n jsonrpc: []const u8,\n id: union(enum) {\n string: []const u8,\n number: i64,\n },\n method: []const u8,\n params: ?JSONValue = null,\n};\n\npub const JSONRPCNotification = struct {\n jsonrpc: []const u8,\n method: []const u8,\n params: ?JSONValue = null,\n};\n\npub const JSONRPCResponse = struct {\n jsonrpc: []const u8,\n id: union(enum) {\n string: []const u8,\n number: i64,\n },\n result: JSONValue,\n};\n\npub const JSONRPCErrorResponse = struct {\n jsonrpc: []const u8,\n id: ?union(enum) {\n string: []const u8,\n number: i64,\n } = null,\n error: JSONRPCError,\n};\n\npub fn parseMessage(allocator: std.mem.Allocator, data: []const u8) !JSONRPCMessage {\n const parsed = try std.json.parseFromSlice(JSONValue, allocator, data, .{});\n defer parsed.deinit();\n\n const value = parsed.value;\n if (value != .object) {\n return error.InvalidJSON;\n }\n\n const obj = value.object;\n\n // Check if it's a request (has method and id)\n if (obj.get(\"method\")) |method_val| {\n if (obj.get(\"id\")) |id_val| {\n // Request\n const method = method_val.string;\n const id = if (id_val == .number) .{ number = @intCast(id_val.number) } else .{ string = id_val.string };\n const params = if (obj.get(\"params\")) |p| p else null;\n\n return JSONRPCMessage{\n .request = .{\n .jsonrpc = JSONRPCVersion,\n .id = id,\n .method = method,\n .params = params,\n },\n };\n } else {\n // Notification\n const method = method_val.string;\n const params = if (obj.get(\"params\")) |p| p else null;\n\n return JSONRPCMessage{\n .notification = .{\n .jsonrpc = JSONRPCVersion,\n .method = method,\n .params = params,\n },\n };\n }\n }\n\n // Check if it's an error response\n if (obj.get(\"error\")) |_| {\n const id = if (obj.get(\"id\")) |id_val| if (id_val == .number) .{ number = @intCast(id_val.number) } else .{ string = id_val.string } else null;\n const err_obj = obj.get(\"error\").?.object;\n const code = @enumFromInt(@intFromFloat(err_obj.get(\"code\").?.number));\n const message = err_obj.get(\"message\").?.string;\n const data_val = if (err_obj.get(\"data\")) |d| d.string else null;\n\n return JSONRPCMessage{\n .error = .{\n .jsonrpc = JSONRPCVersion,\n .id = id,\n .error = .{ .code = code, .message = message, .data = data_val },\n },\n };\n }\n\n // Must be a response\n const id = if (obj.get(\"id\").?) |id_val| if (id_val == .number) .{ number = @intCast(id_val.number) } else .{ string = id_val.string } else unreachable;\n const result = obj.get(\"result\").?;\n\n return JSONRPCMessage{\n .response = .{\n .jsonrpc = JSONRPCVersion,\n .id = id,\n .result = result,\n },\n };\n}\n\npub fn serializeMessage(allocator: std.mem.Allocator, msg: JSONRPCMessage) ![]u8 {\n var buf = std.ArrayList(u8).init(allocator);\n try serializeMessageWriter(msg, buf.writer());\n return buf.toOwnedSlice();\n}\n\nfn serializeMessageWriter(msg: JSONRPCMessage, writer: anytype) !void {\n switch (msg) {\n .request => |req| {\n try writer.writeByte('{');\n try writer.print(\"\\\"jsonrpc\\\":\\\"{s}\\\",\\\"id\\\":\", .{req.jsonrpc});\n switch (req.id) {\n .string => |s| try writer.print(\"\\\"{s}\\\",\", .{s}),\n .number => |n| try writer.print(\"{},\", .{n}),\n }\n try writer.print(\"\\\"method\\\":\\\"{s}\\\"\", .{req.method});\n if (req.params) |params| {\n try writer.writeByte(',');\n try writer.writeAll(\"\\\"params\\\":\");\n try jsonValueWrite(params, writer);\n }\n try writer.writeByte('}');\n },\n .notification => |notif| {\n try writer.writeByte('{');\n try writer.print(\"\\\"jsonrpc\\\":\\\"{s}\\\",\\\"method\\\":\\\"{s}\\\"\", .{notif.jsonrpc, notif.method});\n if (notif.params) |params| {\n try writer.writeByte(',');\n try writer.writeAll(\"\\\"params\\\":\");\n try jsonValueWrite(params, writer);\n }\n try writer.writeByte('}');\n },\n .response => |resp| {\n try writer.writeByte('{');\n try writer.print(\"\\\"jsonrpc\\\":\\\"{s}\\\",\\\"id\\\":\", .{resp.jsonrpc});\n switch (resp.id) {\n .string => |s| try writer.print(\"\\\"{s}\\\",\", .{s}),\n .number => |n| try writer.print(\"{},\", .{n}),\n }\n try writer.writeAll(\"\\\"result\\\":\");\n try jsonValueWrite(resp.result, writer);\n try writer.writeByte('}');\n },\n .error => |err| {\n try writer.writeByte('{');\n try writer.print(\"\\\"jsonrpc\\\":\\\"{s}\\\",\\\"error\\\":{{\"code\":{},\\\"message\\\":\\\"{s}\\\"\", .{err.jsonrpc, @intFromEnum(err.error.code), err.error.message});\n if (err.error.data) |data| {\n try writer.print(\",\\\"data\\\":\\\"{s}\\\"\", .{data});\n }\n try writer.writeByte('}');\n if (err.id) |id| {\n try writer.writeByte(',');\n try writer.writeAll(\"\\\"id\\\":\");\n switch (id) {\n .string => |s| try writer.print(\"\\\"{s}\\\"\", .{s}),\n .number => |n| try writer.writeAll(\"{}*{n}\"),\n }\n }\n try writer.writeByte('}');\n },\n }\n}\n\nfn jsonValueWrite(val: JSONValue, writer: anytype) !void {\n switch (val) {\n .null => try writer.writeAll(\"null\"),\n .bool => |b| try writer.writeAll(if (b) \"true\" else \"false\"),\n .number => |n| try writer.print(\"{}\", .{n}),\n .string => |s| {\n try writer.writeByte('\"');\n for (s) |c| {\n switch (c) {\n '\\\\' => try writer.writeAll(\"\\\\\\\\\"),\n '\"' => try writer.writeAll(\"\\\\\\\"\"),\n '\\n' => try writer.writeAll(\"\\\\n\"),\n '\\r' => try writer.writeAll(\"\\\\r\"),\n '\\t' => try writer.writeAll(\"\\\\t\"),\n else => try writer.writeByte(c),\n }\n }\n try writer.writeByte('\"');\n },\n .array => |arr| {\n try writer.writeByte('[');\n for (arr, 0..) |item, i| {\n if (i > 0) try writer.writeByte(',');\n try jsonValueWrite(item, writer);\n }\n try writer.writeByte(']');\n },\n .object => |obj| {\n try writer.writeByte('{');\n var first = true;\n var it = obj.iterator();\n while (it.next()) |entry| {\n if (!first) try writer.writeByte(',');\n first = false;\n try writer.print(\"\\\"{s}\\\":\", .{entry.key_ptr.*});\n try jsonValueWrite(entry.value_ptr.*, writer);\n }\n try writer.writeByte('}');\n },\n }\n}\n"}, "depends_on": ["T001"], "description":"Create MCP types module with JSON-RPC types, capabilities, and message parsing"},{"id":"T003","tool":"Write","args":{"file_path":"/tmp/claudev-pr-95466/src/mcp/server.zig","content":"const std = @import(\"std\");\nconst types = @import(\"types.zig\");\nconst json = std.json;\n\npub const MethodHandler = fn (params: types.JSONValue, allocator: std.mem.Allocator) anyerror!types.JSONValue;\n\npub const MCPServer = struct {\n allocator: std.mem.Allocator,\n capabilities: types.ServerCapabilities,\n serverInfo: types.ServerInfo,\n protocolVersion: []const u8,\n handlers: std.StringHashMap(MethodHandler),\n tools: std.ArrayList(types.Tool),\n resources: std.ArrayList(types.Resource_),\n resourceTemplates: std.ArrayList(types.ResourceTemplate),\n prompts: std.ArrayList(types.Prompt),\n\n pub fn init(allocator: std.mem.Allocator, name: []const u8, version: []const u8) MCPServer {\n return MCPServer{\n .allocator = allocator,\n .capabilities = .{\n .tools = .{ .listChanged = false },\n .resources = .{ .subscribe = false, .listChanged = false },\n .prompts = .{ .listChanged = false },\n .sampling = .{},\n },\n .serverInfo = .{ .name = name, .version = version },\n .protocolVersion = \"2025-11-25\",\n .handlers = std.StringHashMap(MethodHandler).init(allocator),\n .tools = std.ArrayList(types.Tool).init(allocator),\n .resources = std.ArrayList(types.Resource_).init(allocator),\n .resourceTemplates = std.ArrayList(types.ResourceTemplate).init(allocator),\n .prompts = std.ArrayList(types.Prompt).init(allocator),\n };\n }\n\n pub fn deinit(self: *MCPServer) void {\n self.handlers.deinit();\n self.tools.deinit();\n self.resources.deinit();\n self.resourceTemplates.deinit();\n self.prompts.deinit();\n }\n\n pub fn registerTool(self: *MCPServer, name: []const u8, description: []const u8, handler: MethodHandler) !void {\n const tool = types.Tool{\n .name = name,\n .description = description,\n .inputSchema = std.json.ObjectMap.init(self.allocator),\n };\n try self.tools.append(tool);\n try self.handlers.put(try self.allocator.dupe(u8, name), handler);\n }\n\n pub fn registerResource(self: *MCPServer, uri: []const u8, name: []const u8, description: []const u8, mimeType: []const u8) !void {\n const resource = types.Resource_{\n .uri = uri,\n .name = name,\n .description = description,\n .mimeType = mimeType,\n };\n try self.resources.append(resource);\n }\n\n pub fn registerPrompt(self: *MCPServer, name: []const u8, description: []const u8) !void {\n const prompt = types.Prompt{\n .name = name,\n .description = description,\n .arguments = null,\n };\n try self.prompts.append(prompt);\n }\n\n pub fn handleMessage(self: *MCPServer, data: []const u8) ![]u8 {\n const msg = try types.parseMessage(self.allocator, data);\n defer {\n // Clean up parsed message if needed\n }\n\n switch (msg) {\n .request => |req| {\n const result = try self.handleRequest(req);\n const response = types.JSONRPCMessage{\n .response = .{\n .jsonrpc = types.JSONRPCVersion,\n .id = req.id,\n .result = result,\n },\n };\n return try types.serializeMessage(self.allocator, response);\n },\n .notification => |notif| {\n try self.handleNotification(notif);\n return &[_]u8{};\n },\n .error => |err| {\n const response = types.JSONRPCMessage{\n .error = .{\n .jsonrpc = types.JSONRPCVersion,\n .id = err.id,\n .error = .{\n .code = .InvalidRequest,\n .message = \"Received error message\",\n },\n },\n };\n return try types.serializeMessage(self.allocator, response);\n },\n .response => |_| {\n // Ignore unexpected responses\n return &[_]u8{};\n },\n }\n }\n\n fn handleRequest(self: *MCPServer, req: types.JSONRPCRequest) !types.JSONValue {\n if (std.mem.eql(u8, req.method, \"initialize\")) {\n return try self.handleInitialize(req.params);\n } else if (std.mem.eql(u8, req.method, \"ping\")) {\n return try self.handlePing(req.params);\n } else if (std.mem.eql(u8, req.method, \"tools/list\")) {\n return try self.handleToolsList(req.params);\n } else if (std.mem.eql(u8, req.method, \"tools/call\")) {\n return try self.handleToolsCall(req.params);\n } else if (std.mem.eql(u8, req.method, \"resources/list\")) {\n return try self.handleResourcesList(req.params);\n } else if (std.mem.eql(u8, req.method, \"resources/read\")) {\n return try self.handleResourcesRead(req.params);\n } else if (std.mem.eql(u8, req.method, \"resources/templates/list\")) {\n return try self.handleResourceTemplatesList(req.params);\n } else if (std.mem.eql(u8, req.method, \"prompts/list\")) {\n return try self.handlePromptsList(req.params);\n } else if (std.mem.eql(u8, req.method, \"prompts/get\")) {\n return try self.handlePromptsGet(req.params);\n } else if (std.mem.eql(u8, req.method, \"sampling/createMessage\")) {\n return try self.handleSampling(req.params);\n } else {\n return types.JSONValue{ .null = {} };\n }\n }\n\n fn handleNotification(self: *MCPServer, notif: types.JSONRPCNotification) !void {\n if (std.mem.eql(u8, notif.method, \"notifications/initialized\")) {\n // Client has finished initialization\n std.log.info(\"MCP client initialized\", .{});\n }\n }\n\n fn handleInitialize(self: *MCPServer, params: ?types.JSONValue) !types.JSONValue {\n if (params) |p| {\n if (p == .object) {\n if (p.object.get(\"protocolVersion\")) |pv| {\n self.protocolVersion = try self.allocator.dupe(u8, pv.string);\n }\n }\n }\n\n const result = types.InitializeResult{\n .protocolVersion = self.protocolVersion,\n .capabilities = self.capabilities,\n .serverInfo = self.serverInfo,\n };\n\n var obj = std.json.ObjectMap.init(self.allocator);\n try obj.put(\"protocolVersion\", .{ .string = result.protocolVersion });\n try obj.put(\"capabilities\", try jsonValueFromCapabilities(self.allocator, result.capabilities));\n try obj.put(\"serverInfo\", try jsonValueFromServerInfo(self.allocator, result.serverInfo));\n\n return .{ .object = obj };\n }\n\n fn handlePing(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n return .{ .object = std.json.ObjectMap.init(self.allocator) };\n }\n\n fn handleToolsList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n var tools_array = std.json.Array.init(self.allocator);\n for (self.tools.items) |tool| {\n var tool_obj = std.json.ObjectMap.init(self.allocator);\n try tool_obj.put(\"name\", .{ .string = tool.name });\n try tool_obj.put(\"description\", .{ .string = tool.description });\n try tools_array.append(.{ .object = tool_obj });\n }\n\n var result = std.json.ObjectMap.init(self.allocator);\n try result.put(\"tools\", .{ .array = tools_array });\n\n return .{ .object = result };\n }\n\n fn handleToolsCall(self: *MCPServer, params: ?types.JSONValue) !types.JSONValue {\n if (params == null or params.? != .object) {\n return types.JSONValue{ .null = {} };\n }\n\n const params_obj = params.?.object;\n const name = params_obj.get(\"name\").?.string;\n\n if (self.handlers.get(name)) |handler| {\n const arguments = params_obj.get(\"arguments\");\n const result = try handler(arguments orelse .{ .object = std.json.ObjectMap.init(self.allocator) }, self.allocator);\n return result;\n }\n\n // Return error\n var error_obj = std.json.ObjectMap.init(self.allocator);\n try error_obj.put(\"content\", .{ .array = std.json.Array.init(self.allocator) });\n try error_obj.put(\"isError\", .{ .bool = true });\n\n return .{ .object = error_obj };\n }\n\n fn handleResourcesList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n var resources_array = std.json.Array.init(self.allocator);\n for (self.resources.items) |resource| {\n var res_obj = std.json.ObjectMap.init(self.allocator);\n try res_obj.put(\"uri\", .{ .string = resource.uri });\n try res_obj.put(\"name\", .{ .string = resource.name });\n try res_obj.put(\"description\", .{ .string = resource.description });\n try res_obj.put(\"mimeType\", .{ .string = resource.mimeType });\n try resources_array.append(.{ .object = res_obj });\n }\n\n var result = std.json.ObjectMap.init(self.allocator);\n try result.put(\"resources\", .{ .array = resources_array });\n\n return .{ .object = result };\n }\n\n fn handleResourcesRead(self: *MCPServer, params: ?types.JSONValue) !types.JSONValue {\n if (params == null or params.? != .object) {\n return types.JSONValue{ .null = {} };\n }\n\n const params_obj = params.?.object;\n const uri = params_obj.get(\"uri\").?.string;\n\n // Return empty content for now - implement actual resource reading\n var contents_array = std.json.Array.init(self.allocator);\n var result = std.json.ObjectMap.init(self.allocator);\n try result.put(\"contents\", .{ .array = contents_array });\n\n return .{ .object = result };\n }\n\n fn handleResourceTemplatesList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n var templates_array = std.json.Array.init(self.allocator);\n\n var result = std.json.ObjectMap.init(self.allocator);\n try result.put(\"resourceTemplates\", .{ .array = templates_array });\n\n return .{ .object = result };\n }\n\n fn handlePromptsList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n var prompts_array = std.json.Array.init(self.allocator);\n for (self.prompts.items) |prompt| {\n var prompt_obj = std.json.ObjectMap.init(self.allocator);\n try prompt_obj.put(\"name\", .{ .string = prompt.name });\n try prompt_obj.put(\"description\", .{ .string = prompt.description });\n try prompts_array.append(.{ .object = prompt_obj });\n }\n\n var result = std.json.ObjectMap.init(self.allocator);\n try result.put(\"prompts\", .{ .array = prompts_array });\n\n return .{ .object = result };\n }\n\n fn handlePromptsGet(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n return types.JSONValue{ .null = {} };\n }\n\n fn handleSampling(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {\n return types.JSONValue{ .null = {} };\n }\n};\n\nfn jsonValueFromCapabilities(allocator: std.mem.Allocator, caps: types.ServerCapabilities) !types.JSONValue {\n var obj = std.json.ObjectMap.init(allocator);\n\n if (caps.tools) |_| {\n var tools_obj = std.json.ObjectMap.init(allocator);\n try tools_obj.put(\"listChanged\", .{ .bool = false });\n try obj.put(\"tools\", .{ .object = tools_obj });\n }\n\n if (caps.resources) |_| {\n var res_obj = std.json.ObjectMap.init(allocator);\n try res_obj.put(\"subscribe\", .{ .bool = false });\n try res_obj.put(\"listChanged\", .{ .bool = false });\n try obj.put(\"resources\", .{ .object = res_obj });\n }\n\n if (caps.prompts) |_| {\n var prompts_obj = std.json.ObjectMap.init(allocator);\n try prompts_obj.put(\"listChanged\", .{ .bool = false });\n try obj.put(\"prompts\", .{ .object = prompts_obj });\n }\n\n if (caps.sampling) |_| {\n try obj.put(\"sampling\", .{ .object = std.json.ObjectMap.init(allocator) });\n }\n\n return .{ .object = obj };\n}\n\nfn jsonValueFromServerInfo(allocator: std.mem.Allocator, info: types.ServerInfo) !types.JSONValue {\n var obj = std.json.ObjectMap.init(allocator);\n try obj.put(\"name\", .{ .string = info.name });\n try obj.put(\"version\", .{ .string = info.version });\n return .{ .object = obj };\n}\n"}, "depends_on": ["T001"], "description":"Create MCP server module with method handlers and message routing"},{"id":"T004","tool":"Write","args":{"file_path":"/tmp/claudev-pr-95466/src/mcp/transport.zig","content":"const std = @import(\"std\");\nconst types = @import(\"types.zig\");\n\npub const Transport = union(enum) {\n stdio: StdioTransport,\n http: HttpTransport,\n};\n\npub const StdioTransport = struct {\n allocator: std.mem.Allocator,\n\n pub fn init(allocator: std.mem.Allocator) StdioTransport {\n return StdioTransport{\n .allocator = allocator,\n };\n }\n\n pub fn readMessage(self: StdioTransport) ![]u8 {\n // Read length as 4-byte big-endian integer\n var len_buf: [4]u8 = undefined;\n const bytes_read = try std.os.read(std.os.STDIN_FILENO, &len_buf);\n if (bytes_read == 0) {\n return error.ConnectionClosed;\n }\n if (bytes_read < 4) {\n return error.InvalidMessage;\n }\n\n const len = std.mem.readBig(u32, &len_buf);\n if (len > 10 * 1024 * 1024) {\n return error.MessageTooLarge;\n }\n\n const message = try self.allocator.alloc(u8, len);\n errdefer self.allocator.free(message);\n\n var remaining = len;\n var offset: usize = 0;\n while (remaining > 0) {\n const read = try std.os.read(std.os.STDIN_FILENO, message[offset..]);\n if (read == 0) {\n return error.ConnectionClosed;\n }\n offset += read;\n remaining -= read;\n }\n\n return message;\n }\n\n pub fn writeMessage(self: StdioTransport, data: []const u8) !void {\n // Write length as 4-byte big-endian integer followed by data\n var len_buf: [4]u8 = undefined;\n std.mem.writeBig(u32, &len_buf, @intCast(data.len));\n\n var total_written: usize = 0;\n while (total_written < 4) {\n const written = try std.os.write(std.os.STDOUT_FILENO, len_buf[total_written..]);\n if (written == 0) {\n return error.ConnectionClosed;\n }\n total_written += written;\n }\n\n total_written = 0;\n while (total_written < data.len) {\n const written = try std.os.write(std.os.STDOUT_FILENO, data[total_written..]);\n if (written == 0) {\n return error.ConnectionClosed;\n }\n total_written += written;\n }\n }\n};\n\npub const HttpTransport = struct {\n allocator: std.mem.Allocator,\n stream: std.net.Stream,\n buffer: std.ArrayList(u8),\n\n pub fn init(allocator: std.mem.Allocator, stream: std.net.Stream) HttpTransport {\n return HttpTransport{\n .allocator = allocator,\n .stream = stream,\n .buffer = std.ArrayList(u8).init(allocator),\n };\n }\n\n pub fn deinit(self: *HttpTransport) void {\n self.buffer.deinit();\n }\n\n pub fn readMessage(self: *HttpTransport) ![]u8 {\n // Read HTTP headers\n var header_buf = [4096]u8{};\n var header_end: usize = 0;\n \n while (true) {\n const bytes_read = try self.stream.read(header_buf[header_end..header_end + 1]);\n if (bytes_read == 0) {\n return error.ConnectionClosed;\n }\n\n if (header_end >= 3 and \n header_buf[header_end - 3] == '\\r' and \n header_buf[header_end - 2] == '\\n' and \n header_buf[header_end - 1] == '\\r' and\n header_end >= 1 and header_buf[header_end] == '\\n') {\n break;\n }\n header_end += bytes_read;\n }\n\n // Parse Content-Length header\n const headers = header_buf[0..header_end];\n var content_length: usize = 0;\n var lines = std.mem.split(u8, headers, \"\\r\\n\");\n while (lines.next()) |line| {\n if (std.ascii.startsWithIgnoreCase(line, \"content-length:\")) {\n const value = std.mem.trim(u8, line[15..], \" \");\n content_length = try std.fmt.parseInt(usize, value, 10);\n }\n }\n\n if (content_length == 0) {\n return error.InvalidMessage;\n }\n\n // Read body\n const body = try self.allocator.alloc(u8, content_length);\n errdefer self.allocator.free(body);\n\n var remaining = content_length;\n var offset: usize = 0;\n while (remaining > 0) {\n const read = try self.stream.read(body[offset..]);\n if (read == 0) {\n return error.ConnectionClosed;\n }\n offset += read;\n remaining -= read;\n }\n\n return body;\n }\n\n pub fn writeMessage(self: *HttpTransport, data: []const u8) !void {\n // Write HTTP response with SSE\n var buf = std.ArrayList(u8).init(self.allocator);\n defer buf.deinit();\n\n try buf.writer().print(\n \"HTTP/1.1 200 OK\\r\\n\" ++\n \"Content-Type: text/event-stream\\r\\n\" ++\n \"Cache-Control: no-cache\\r\\n\" ++\n \"Connection: keep-alive\\r\\n\" ++\n \"Transfer-Encoding: chunked\\r\\n\" ++\n \"\\r\\n\",\n .{}\n );\n\n // Write chunked body\n try buf.writer().print(\"{x}\\r\\n\", .{data.len});\n try buf.appendSlice(data);\n try buf.appendSlice(\"\\r\\n\");\n try buf.appendSlice(\"0\\r\\n\\r\\n\");\n\n try self.stream.writeAll(buf.items);\n }\n};\n\npub fn runStdioServer(allocator: std.mem.Allocator, server: anytype) !void {\n var transport = StdioTransport.init(allocator);\n\n while (true) {\n const message = transport.readMessage() catch |err| {\n if (err == error.ConnectionClosed) {\n break;\n }\n std.log.err(\"Failed to read message: {}\", .{err});\n continue;\n };\n\n const response = server.handleMessage(message) catch |err| {\n std.log.err(\"Failed to handle message: {}\", .{err});\n allocator.free(message);\n continue;\n };\n allocator.free(message);\n\n if (response.len > 0) {\n transport.writeMessage(response) catch |err| {\n std.log.err(\"Failed to write response: {}\", .{err});\n break;\n };\n allocator.free(response);\n }\n }\n}\n"}, "depends_on": ["T001"], "description":"Create MCP transport module for stdio and HTTP+SSE transports"},{"id":"T005","tool":"Write","args":{"file_path":"/tmp/claudev-pr-95466/src/mcp.zig","content":"pub const mcp = struct {\n pub const types = @import(\"mcp/types.zig\");\n pub const server = @import(\"mcp/server.zig\");\n pub const transport = @import(\"mcp/transport.zig\");\n};\n"}, "depends_on": ["T001"], "description":"Create main MCP module that re-exports all MCP components"},{"id":"T006","tool":"Write","args":{"file_path":"/tmp/claudev-pr-95466/examples/mcp_server.zig","content":"const std = @import(\"std\");\nconst mcp = @import(\"mcp\");\n\npub fn main() !void {\n var gpa = std.heap.GeneralPurposeAllocator(.{}){};\n defer _ = gpa.deinit();\n const allocator = gpa.allocator();\n\n // Create MCP server\n var server = mcp.server.MCPServer.init(allocator, \"gRPC-zig MCP Server\", \"1.0.0\");\n defer server.deinit();\n\n // Register a simple echo tool\n try server.registerTool(\"echo\", \"Echo back the input text\", echoHandler);\n\n // Register a greeting tool\n try server.registerTool(\"greet\", \"Generate a greeting message\", greetHandler);\n\n // Register a sample resource\n try server.registerResource(\"example://hello\", \"Hello Resource\", \"A simple hello world resource\", \"text/plain\");\n\n // Register a sample prompt\n try server.registerTool(\"generate_prompt\", \"Generate a prompt for AI\", promptHandler);\n\n std.debug.print(\"Starting MCP server on stdio...\\n\", .{});\n\n // Run the server\n try mcp.transport.runStdioServer(allocator, &server);\n}\n\nfn echoHandler(params: mcp.types.JSONValue, allocator: std.mem.Allocator) !mcp.types.JSONValue {\n var obj = std.json.ObjectMap.init(allocator);\n var content_array = std.json.Array.init(allocator);\n\n var content_obj = std.json.ObjectMap.init(allocator);\n try content_obj.put(\"type\", .{ .string = \"text\" });\n\n var text = \"echo\";\n if (params == .object) {\n if (params.object.get(\"text\")) |t| {\n text = t.string;\n }\n }\n try content_obj.put(\"text\", .{ .string = text });\n\n try content_array.append(.{ .object = content_obj });\n try obj.put(\"content\", .{ .array = content_array });\n try obj.put(\"isError\", .{ .bool = false });\n\n return .{ .object = obj };\n}\n\nfn greetHandler(params: mcp.types.JSONValue, allocator: std.mem.Allocator) !mcp.types.JSONValue {\n var obj = std.json.ObjectMap.init(allocator);\n var content_array = std.json.Array.init(allocator);\n\n var content_obj = std.json.ObjectMap.init(allocator);\n try content_obj.put(\"type\", .{ .string = \"text\" });\n\n var name = \"World\";\n if (params == .object) {\n if (params.object.get(\"name\")) |n| {\n name = n.string;\n }\n }\n const greeting = try std.fmt.allocPrint(allocator, \"Hello, {s}!\", .{name});\n defer allocator.free(greeting);\n\n try content_obj.put(\"text\", .{ .string = greeting });\n try content_array.append(.{ .object = content_obj });\n try obj.put(\"content\", .{ .array = content_array });\n try obj.put(\"isError\", .{ .bool = false });\n\n return .{ .object = obj };\n}\n\nfn promptHandler(params: mcp.types.JSONValue, allocator: std.mem.Allocator) !mcp.types.JSONValue {\n var obj = std.json.ObjectMap.init(allocator);\n var content_array = std.json.Array.init(allocator);\n\n var content_obj = std.json.ObjectMap.init(allocator);\n try content_obj.put(\"type\", .{ .string = \"text\" });\n\n var task = \"general\";\n if (params == .object) {\n if (params.object.get(\"task\")) |t| {\n task = t.string;\n }\n }\n\n const prompt_text = switch (task[0]) {\n 's' => \"Summarize the following: \",\n 'e' => \"Explain the following: \",\n else => \"Analyze the following: \",\n };\n\n try content_obj.put(\"text\", .{ .string = prompt_text });\n try content_array.append(.{ .object = content_obj });\n try obj.put(\"content\", .{ .array = content_array });\n try obj.put(\"isError\", .{ .bool = false });\n\n return .{ .object = obj };\n}\n"}, "depends_on": ["T001"], "description":"Create example MCP server showing usage of MCP types and server"},{"id":"T007","tool":"Read","args":{"file_path":"/tmp/claudev-pr-95466/build.zig"},"depends_on":[],"description":"Read build.zig to understand existing structure before modification"},null,"T008","tool Edit - will be added after reviewing build.zig","args:{},"]}
diff --git a/.devwork/prompt.txt b/.devwork/prompt.txt
new file mode 100644
index 0000000..fcfdfaa
--- /dev/null
+++ b/.devwork/prompt.txt
@@ -0,0 +1,89 @@
+# PR #23: [WIP] Add support for MCP compatibility
+
+## Description
+Thanks for assigning this issue to me. I'm starting to work on it and will keep this PR's description up to date as I form a plan and make progress.
+
+
+
+
+
+
+
+Original prompt
+
+>
+> ----
+>
+> *This section details on the original issue you should resolve*
+>
+> implement MCP
+> add support of MCP, schema: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts
+>
+> example of rust sdk: https://github.com/modelcontextprotocol/rust-sdk
+>
+> mcp must be fully compatable with claude
+>
+> ## Comments on the Issue (you are @copilot in this section)
+>
+>
+>
+>
+
+
+
+
+
+
+
+
+- Fixes ziglana/gRPC-zig#22
+
+
+---
+
+💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
+
+
+## Task
+Thanks for assigning this issue to me. I'm starting to work on it and will keep this PR's description up to date as I form a plan and make progress.
+
+
+
+
+
+
+
+Original prompt
+
+>
+> ----
+>
+> *This section details on the original issue you should resolve*
+>
+> implement MCP
+> add support of MCP, schema: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts
+>
+> example of rust sdk: https://github.com/modelcontextprotocol/rust-sdk
+>
+> mcp must be fully compatable with claude
+>
+> ## Comments on the Issue (you are @copilot in this section)
+>
+>
+>
+>
+
+
+
+
+
+
+
+
+- Fixes ziglana/gRPC-zig#22
+
+
+---
+
+💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
+
diff --git a/.devwork/research/summary.md b/.devwork/research/summary.md
new file mode 100644
index 0000000..104ffe1
--- /dev/null
+++ b/.devwork/research/summary.md
@@ -0,0 +1,176 @@
+# Research Summary
+
+## Prompt
+# PR #23: [WIP] Add support for MCP compatibility
+
+## Description
+Thanks for assigning this issue to me. I'm starting to work on it and will keep this PR's description up to date as I form a plan and make progress.
+
+
+
+
+
+
+
+Original prompt
+
+>
+> ----
+>
+> *This section details on the original issue you should resolve*
+>
+> implement MCP
+> add support of MCP, schema: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts
+>
+> example of rust sdk: https://github.com/modelcontextprotocol/rust-sdk
+>
+> mcp must be fully compatable with claude
+>
+> ## Comments on the Issue (you are @copilot in this section)
+>
+>
+>
+>
+
+
+
+
+
+
+
+
+- Fixes ziglana/gRPC-zig#22
+
+
+---
+
+💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
+
+
+## Task
+Thanks for assigning this issue to me. I'm starting to work on it and will keep this PR's description up to date as I form a plan and make progress.
+
+
+
+
+
+
+
+Original prompt
+
+>
+> ----
+>
+> *This section details on the original issue you should resolve*
+>
+> implement MCP
+> add support of MCP, schema: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts
+>
+> example of rust sdk: https://github.com/modelcontextprotocol/rust-sdk
+>
+> mcp must be fully compatable with claude
+>
+> ## Comments on the Issue (you are @copilot in this section)
+>
+>
+>
+>
+
+
+
+
+
+
+
+
+- Fixes ziglana/gRPC-zig#22
+
+
+---
+
+💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
+
+
+
+## Findings
+## Research Summary: Adding MCP Support to gRPC-zig
+
+### Project Overview
+- **Project**: gRPC-zig - A blazingly fast gRPC client & server implementation in Zig
+- **Zig Version**: 0.15.2
+- **Language**: Pure Zig (zero dependencies)
+- **Location**: `/tmp/claudev-pr-95466`
+
+### Project Structure
+```
+/tmp/claudev-pr-95466/
+├── build.zig, build.zig.zon # Zig build configuration
+├── src/
+│ ├── client.zig # gRPC client implementation
+│ ├── server.zig # gRPC server implementation
+│ ├── transport.zig # Transport layer
+│ ├── http2/ # HTTP/2 protocol implementation
+│ ├── proto/ # Protocol buffer handling
+│ ├── features/ # Features (auth, compression, streaming, health)
+│ └── tests.zig, benchmark.zig
+├── examples/ # Example implementations
+└── integration_test/
+```
+
+### MCP (Model Context Protocol) Overview
+
+**MCP is a JSON-RPC 2.0 based protocol** for AI assistants to interact with tools and resources. Key points:
+
+1. **Protocol Version**: 2025-11-25 (latest) or 2025-06-18
+2. **Transport**: stdio or HTTP with SSE (Server-Sent Events)
+3. **Message Types**:
+ - `JSONRPCRequest` - expects response (has `id`)
+ - `JSONRPCNotification` - no response expected (no `id`)
+ - `JSONRPCResponse` - successful response
+ - `JSONRPCErrorResponse` - error response
+
+4. **Core Methods**:
+ - `initialize` - Client/server handshake with capabilities
+ - `notifications/initialized` - Client confirmation
+ - `ping` - Keepalive/liveness check
+ - `tools/list`, `tools/call` - Tool operations
+ - `resources/list`, `resources/read`, `resources/subscribe` - Resource operations
+ - `prompts/list`, `prompts/get` - Prompt templates
+ - `sampling/createMessage` - LLM sampling
+
+5. **Standard Error Codes**:
+ - `-32700` - Parse error
+ - `-32600` - Invalid request
+ - `-32601` - Method not found
+ - `-32602` - Invalid params
+ - `-32603` - Internal error
+
+### Implementation Requirements
+
+To make MCP compatible with Claude:
+
+1. **JSON-RPC 2.0 Compliance**: Must properly handle both requests and notifications
+2. **Initialization Handshake**:
+ - Server responds to `initialize` with `ServerCapabilities`
+ - Client sends `notifications/initialized` after receiving capabilities
+3. **Capability Negotiation**: Both client and server declare their capabilities
+4. **Transport Options**:
+ - stdio (common for local tools)
+ - HTTP with SSE (for web-based servers)
+
+### Key Gotchas
+
+1. **Notification vs Request**: Notifications have no `id` field, requests have an `id`
+2. **Progress Tokens**: Can be used for long-running operations with `notifications/progress`
+3. **Content Types**: MCP uses its own content block types (TextContent, ImageContent, BlobResourceContents, EmbeddedResource)
+4. **Progress Token**: Either string or number type
+
+### Recommended Implementation Approach
+
+Since gRPC-zig already provides HTTP/2 infrastructure, you could:
+1. Add an MCP layer on top that handles JSON-RPC messaging
+2. Reuse HTTP/2 transport for the transport layer
+3. Implement the core methods (initialize, ping, tools, resources, prompts)
+4. Ensure proper capability negotiation
+
+The MCP implementation would be a new module that wraps or integrates with the existing gRPC server/client infrastructure, translating between JSON-RPC messages and the internal gRPC-zig types.
diff --git a/.devwork/result.json b/.devwork/result.json
new file mode 100644
index 0000000..23de156
--- /dev/null
+++ b/.devwork/result.json
@@ -0,0 +1,10 @@
+{
+ "success": true,
+ "tasks_total": 7,
+ "tasks_completed": 7,
+ "tasks_failed": 0,
+ "planning_ms": 248129,
+ "execution_ms": 40329,
+ "total_ms": 288569,
+ "error": ""
+}
diff --git a/.devwork/tree-after.txt b/.devwork/tree-after.txt
new file mode 100644
index 0000000..04a73a3
--- /dev/null
+++ b/.devwork/tree-after.txt
@@ -0,0 +1,53 @@
+# Directory snapshot after SpecKit execution
+
+/tmp/claudev-pr-95466
+├── build.zig
+├── build.zig.zon
+├── docs
+│  └── benchmarking.md
+├── examples
+│  ├── auth.zig
+│  ├── basic_client.zig
+│  ├── basic_server.zig
+│  ├── compression.zig
+│  ├── health.zig
+│  ├── http2_transport.zig
+│  ├── mcp_server.zig
+│  └── streaming.zig
+├── integration_test
+│  ├── proto.zig
+│  ├── README.md
+│  ├── requirements.txt
+│  ├── run_tests.sh
+│  ├── test_client.py
+│  ├── test_server.zig
+│  └── test_service.proto
+├── LICENSE
+├── README.md
+├── scripts
+│  └── run_benchmark.sh
+└── src
+ ├── benchmark.zig
+ ├── client.zig
+ ├── features
+ │  ├── auth.zig
+ │  ├── compression.zig
+ │  ├── health.zig
+ │  └── streaming.zig
+ ├── http2
+ │  ├── connection.zig
+ │  ├── frame.zig
+ │  ├── hpack.zig
+ │  └── stream.zig
+ ├── mcp
+ │  ├── server.zig
+ │  ├── transport.zig
+ │  └── types.zig
+ ├── mcp.zig
+ ├── proto
+ │  └── service.zig
+ ├── server.zig
+ ├── tests.zig
+ └── transport.zig
+
+10 directories, 39 files
\ No newline at end of file
diff --git a/.devwork/tree.txt b/.devwork/tree.txt
new file mode 100644
index 0000000..e48e176
--- /dev/null
+++ b/.devwork/tree.txt
@@ -0,0 +1,48 @@
+# Directory snapshot before SpecKit execution
+# 1771270859044551889
+
+/tmp/claudev-pr-95466
+├── build.zig
+├── build.zig.zon
+├── docs
+│  └── benchmarking.md
+├── examples
+│  ├── auth.zig
+│  ├── basic_client.zig
+│  ├── basic_server.zig
+│  ├── compression.zig
+│  ├── health.zig
+│  ├── http2_transport.zig
+│  └── streaming.zig
+├── integration_test
+│  ├── proto.zig
+│  ├── README.md
+│  ├── requirements.txt
+│  ├── run_tests.sh
+│  ├── test_client.py
+│  ├── test_server.zig
+│  └── test_service.proto
+├── LICENSE
+├── README.md
+├── scripts
+│  └── run_benchmark.sh
+└── src
+ ├── benchmark.zig
+ ├── client.zig
+ ├── features
+ │  ├── auth.zig
+ │  ├── compression.zig
+ │  ├── health.zig
+ │  └── streaming.zig
+ ├── http2
+ │  ├── connection.zig
+ │  ├── frame.zig
+ │  ├── hpack.zig
+ │  └── stream.zig
+ ├── proto
+ │  └── service.zig
+ ├── server.zig
+ ├── tests.zig
+ └── transport.zig
+
+9 directories, 34 files
\ No newline at end of file
diff --git a/examples/mcp_server.zig b/examples/mcp_server.zig
new file mode 100644
index 0000000..ba70930
--- /dev/null
+++ b/examples/mcp_server.zig
@@ -0,0 +1,103 @@
+const std = @import("std");
+const mcp = @import("mcp");
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const allocator = gpa.allocator();
+
+ // Create MCP server
+ var server = mcp.server.MCPServer.init(allocator, "gRPC-zig MCP Server", "1.0.0");
+ defer server.deinit();
+
+ // Register a simple echo tool
+ try server.registerTool("echo", "Echo back the input text", echoHandler);
+
+ // Register a greeting tool
+ try server.registerTool("greet", "Generate a greeting message", greetHandler);
+
+ // Register a sample resource
+ try server.registerResource("example://hello", "Hello Resource", "A simple hello world resource", "text/plain");
+
+ // Register a sample prompt
+ try server.registerTool("generate_prompt", "Generate a prompt for AI", promptHandler);
+
+ std.debug.print("Starting MCP server on stdio...\n", .{});
+
+ // Run the server
+ try mcp.transport.runStdioServer(allocator, &server);
+}
+
+fn echoHandler(params: mcp.types.JSONValue, allocator: std.mem.Allocator) !mcp.types.JSONValue {
+ var obj = std.json.ObjectMap.init(allocator);
+ var content_array = std.json.Array.init(allocator);
+
+ var content_obj = std.json.ObjectMap.init(allocator);
+ try content_obj.put("type", .{ .string = "text" });
+
+ var text = "echo";
+ if (params == .object) {
+ if (params.object.get("text")) |t| {
+ text = t.string;
+ }
+ }
+ try content_obj.put("text", .{ .string = text });
+
+ try content_array.append(.{ .object = content_obj });
+ try obj.put("content", .{ .array = content_array });
+ try obj.put("isError", .{ .bool = false });
+
+ return .{ .object = obj };
+}
+
+fn greetHandler(params: mcp.types.JSONValue, allocator: std.mem.Allocator) !mcp.types.JSONValue {
+ var obj = std.json.ObjectMap.init(allocator);
+ var content_array = std.json.Array.init(allocator);
+
+ var content_obj = std.json.ObjectMap.init(allocator);
+ try content_obj.put("type", .{ .string = "text" });
+
+ var name = "World";
+ if (params == .object) {
+ if (params.object.get("name")) |n| {
+ name = n.string;
+ }
+ }
+ const greeting = try std.fmt.allocPrint(allocator, "Hello, {s}!", .{name});
+ defer allocator.free(greeting);
+
+ try content_obj.put("text", .{ .string = greeting });
+ try content_array.append(.{ .object = content_obj });
+ try obj.put("content", .{ .array = content_array });
+ try obj.put("isError", .{ .bool = false });
+
+ return .{ .object = obj };
+}
+
+fn promptHandler(params: mcp.types.JSONValue, allocator: std.mem.Allocator) !mcp.types.JSONValue {
+ var obj = std.json.ObjectMap.init(allocator);
+ var content_array = std.json.Array.init(allocator);
+
+ var content_obj = std.json.ObjectMap.init(allocator);
+ try content_obj.put("type", .{ .string = "text" });
+
+ var task = "general";
+ if (params == .object) {
+ if (params.object.get("task")) |t| {
+ task = t.string;
+ }
+ }
+
+ const prompt_text = switch (task[0]) {
+ 's' => "Summarize the following: ",
+ 'e' => "Explain the following: ",
+ else => "Analyze the following: ",
+ };
+
+ try content_obj.put("text", .{ .string = prompt_text });
+ try content_array.append(.{ .object = content_obj });
+ try obj.put("content", .{ .array = content_array });
+ try obj.put("isError", .{ .bool = false });
+
+ return .{ .object = obj };
+}
diff --git a/src/mcp.zig b/src/mcp.zig
new file mode 100644
index 0000000..0175d6a
--- /dev/null
+++ b/src/mcp.zig
@@ -0,0 +1,5 @@
+pub const mcp = struct {
+ pub const types = @import("mcp/types.zig");
+ pub const server = @import("mcp/server.zig");
+ pub const transport = @import("mcp/transport.zig");
+};
diff --git a/src/mcp/server.zig b/src/mcp/server.zig
new file mode 100644
index 0000000..65b84f2
--- /dev/null
+++ b/src/mcp/server.zig
@@ -0,0 +1,313 @@
+const std = @import("std");
+const types = @import("types.zig");
+const json = std.json;
+
+pub const MethodHandler = fn (params: types.JSONValue, allocator: std.mem.Allocator) anyerror!types.JSONValue;
+
+pub const MCPServer = struct {
+ allocator: std.mem.Allocator,
+ capabilities: types.ServerCapabilities,
+ serverInfo: types.ServerInfo,
+ protocolVersion: []const u8,
+ handlers: std.StringHashMap(MethodHandler),
+ tools: std.ArrayList(types.Tool),
+ resources: std.ArrayList(types.Resource_),
+ resourceTemplates: std.ArrayList(types.ResourceTemplate),
+ prompts: std.ArrayList(types.Prompt),
+
+ pub fn init(allocator: std.mem.Allocator, name: []const u8, version: []const u8) MCPServer {
+ return MCPServer{
+ .allocator = allocator,
+ .capabilities = .{
+ .tools = .{ .listChanged = false },
+ .resources = .{ .subscribe = false, .listChanged = false },
+ .prompts = .{ .listChanged = false },
+ .sampling = .{},
+ },
+ .serverInfo = .{ .name = name, .version = version },
+ .protocolVersion = "2025-11-25",
+ .handlers = std.StringHashMap(MethodHandler).init(allocator),
+ .tools = std.ArrayList(types.Tool).init(allocator),
+ .resources = std.ArrayList(types.Resource_).init(allocator),
+ .resourceTemplates = std.ArrayList(types.ResourceTemplate).init(allocator),
+ .prompts = std.ArrayList(types.Prompt).init(allocator),
+ };
+ }
+
+ pub fn deinit(self: *MCPServer) void {
+ self.handlers.deinit();
+ self.tools.deinit();
+ self.resources.deinit();
+ self.resourceTemplates.deinit();
+ self.prompts.deinit();
+ }
+
+ pub fn registerTool(self: *MCPServer, name: []const u8, description: []const u8, handler: MethodHandler) !void {
+ const tool = types.Tool{
+ .name = name,
+ .description = description,
+ .inputSchema = std.json.ObjectMap.init(self.allocator),
+ };
+ try self.tools.append(tool);
+ try self.handlers.put(try self.allocator.dupe(u8, name), handler);
+ }
+
+ pub fn registerResource(self: *MCPServer, uri: []const u8, name: []const u8, description: []const u8, mimeType: []const u8) !void {
+ const resource = types.Resource_{
+ .uri = uri,
+ .name = name,
+ .description = description,
+ .mimeType = mimeType,
+ };
+ try self.resources.append(resource);
+ }
+
+ pub fn registerPrompt(self: *MCPServer, name: []const u8, description: []const u8) !void {
+ const prompt = types.Prompt{
+ .name = name,
+ .description = description,
+ .arguments = null,
+ };
+ try self.prompts.append(prompt);
+ }
+
+ pub fn handleMessage(self: *MCPServer, data: []const u8) ![]u8 {
+ const msg = try types.parseMessage(self.allocator, data);
+ defer {
+ // Clean up parsed message if needed
+ }
+
+ switch (msg) {
+ .request => |req| {
+ const result = try self.handleRequest(req);
+ const response = types.JSONRPCMessage{
+ .response = .{
+ .jsonrpc = types.JSONRPCVersion,
+ .id = req.id,
+ .result = result,
+ },
+ };
+ return try types.serializeMessage(self.allocator, response);
+ },
+ .notification => |notif| {
+ try self.handleNotification(notif);
+ return &[_]u8{};
+ },
+ .error => |err| {
+ const response = types.JSONRPCMessage{
+ .error = .{
+ .jsonrpc = types.JSONRPCVersion,
+ .id = err.id,
+ .error = .{
+ .code = .InvalidRequest,
+ .message = "Received error message",
+ },
+ },
+ };
+ return try types.serializeMessage(self.allocator, response);
+ },
+ .response => |_| {
+ // Ignore unexpected responses
+ return &[_]u8{};
+ },
+ }
+ }
+
+ fn handleRequest(self: *MCPServer, req: types.JSONRPCRequest) !types.JSONValue {
+ if (std.mem.eql(u8, req.method, "initialize")) {
+ return try self.handleInitialize(req.params);
+ } else if (std.mem.eql(u8, req.method, "ping")) {
+ return try self.handlePing(req.params);
+ } else if (std.mem.eql(u8, req.method, "tools/list")) {
+ return try self.handleToolsList(req.params);
+ } else if (std.mem.eql(u8, req.method, "tools/call")) {
+ return try self.handleToolsCall(req.params);
+ } else if (std.mem.eql(u8, req.method, "resources/list")) {
+ return try self.handleResourcesList(req.params);
+ } else if (std.mem.eql(u8, req.method, "resources/read")) {
+ return try self.handleResourcesRead(req.params);
+ } else if (std.mem.eql(u8, req.method, "resources/templates/list")) {
+ return try self.handleResourceTemplatesList(req.params);
+ } else if (std.mem.eql(u8, req.method, "prompts/list")) {
+ return try self.handlePromptsList(req.params);
+ } else if (std.mem.eql(u8, req.method, "prompts/get")) {
+ return try self.handlePromptsGet(req.params);
+ } else if (std.mem.eql(u8, req.method, "sampling/createMessage")) {
+ return try self.handleSampling(req.params);
+ } else {
+ return types.JSONValue{ .null = {} };
+ }
+ }
+
+ fn handleNotification(self: *MCPServer, notif: types.JSONRPCNotification) !void {
+ if (std.mem.eql(u8, notif.method, "notifications/initialized")) {
+ // Client has finished initialization
+ std.log.info("MCP client initialized", .{});
+ }
+ }
+
+ fn handleInitialize(self: *MCPServer, params: ?types.JSONValue) !types.JSONValue {
+ if (params) |p| {
+ if (p == .object) {
+ if (p.object.get("protocolVersion")) |pv| {
+ self.protocolVersion = try self.allocator.dupe(u8, pv.string);
+ }
+ }
+ }
+
+ const result = types.InitializeResult{
+ .protocolVersion = self.protocolVersion,
+ .capabilities = self.capabilities,
+ .serverInfo = self.serverInfo,
+ };
+
+ var obj = std.json.ObjectMap.init(self.allocator);
+ try obj.put("protocolVersion", .{ .string = result.protocolVersion });
+ try obj.put("capabilities", try jsonValueFromCapabilities(self.allocator, result.capabilities));
+ try obj.put("serverInfo", try jsonValueFromServerInfo(self.allocator, result.serverInfo));
+
+ return .{ .object = obj };
+ }
+
+ fn handlePing(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {
+ return .{ .object = std.json.ObjectMap.init(self.allocator) };
+ }
+
+ fn handleToolsList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {
+ var tools_array = std.json.Array.init(self.allocator);
+ for (self.tools.items) |tool| {
+ var tool_obj = std.json.ObjectMap.init(self.allocator);
+ try tool_obj.put("name", .{ .string = tool.name });
+ try tool_obj.put("description", .{ .string = tool.description });
+ try tools_array.append(.{ .object = tool_obj });
+ }
+
+ var result = std.json.ObjectMap.init(self.allocator);
+ try result.put("tools", .{ .array = tools_array });
+
+ return .{ .object = result };
+ }
+
+ fn handleToolsCall(self: *MCPServer, params: ?types.JSONValue) !types.JSONValue {
+ if (params == null or params.? != .object) {
+ return types.JSONValue{ .null = {} };
+ }
+
+ const params_obj = params.?.object;
+ const name = params_obj.get("name").?.string;
+
+ if (self.handlers.get(name)) |handler| {
+ const arguments = params_obj.get("arguments");
+ const result = try handler(arguments orelse .{ .object = std.json.ObjectMap.init(self.allocator) }, self.allocator);
+ return result;
+ }
+
+ // Return error
+ var error_obj = std.json.ObjectMap.init(self.allocator);
+ try error_obj.put("content", .{ .array = std.json.Array.init(self.allocator) });
+ try error_obj.put("isError", .{ .bool = true });
+
+ return .{ .object = error_obj };
+ }
+
+ fn handleResourcesList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {
+ var resources_array = std.json.Array.init(self.allocator);
+ for (self.resources.items) |resource| {
+ var res_obj = std.json.ObjectMap.init(self.allocator);
+ try res_obj.put("uri", .{ .string = resource.uri });
+ try res_obj.put("name", .{ .string = resource.name });
+ try res_obj.put("description", .{ .string = resource.description });
+ try res_obj.put("mimeType", .{ .string = resource.mimeType });
+ try resources_array.append(.{ .object = res_obj });
+ }
+
+ var result = std.json.ObjectMap.init(self.allocator);
+ try result.put("resources", .{ .array = resources_array });
+
+ return .{ .object = result };
+ }
+
+ fn handleResourcesRead(self: *MCPServer, params: ?types.JSONValue) !types.JSONValue {
+ if (params == null or params.? != .object) {
+ return types.JSONValue{ .null = {} };
+ }
+
+ const params_obj = params.?.object;
+ const uri = params_obj.get("uri").?.string;
+
+ // Return empty content for now - implement actual resource reading
+ var contents_array = std.json.Array.init(self.allocator);
+ var result = std.json.ObjectMap.init(self.allocator);
+ try result.put("contents", .{ .array = contents_array });
+
+ return .{ .object = result };
+ }
+
+ fn handleResourceTemplatesList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {
+ var templates_array = std.json.Array.init(self.allocator);
+
+ var result = std.json.ObjectMap.init(self.allocator);
+ try result.put("resourceTemplates", .{ .array = templates_array });
+
+ return .{ .object = result };
+ }
+
+ fn handlePromptsList(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {
+ var prompts_array = std.json.Array.init(self.allocator);
+ for (self.prompts.items) |prompt| {
+ var prompt_obj = std.json.ObjectMap.init(self.allocator);
+ try prompt_obj.put("name", .{ .string = prompt.name });
+ try prompt_obj.put("description", .{ .string = prompt.description });
+ try prompts_array.append(.{ .object = prompt_obj });
+ }
+
+ var result = std.json.ObjectMap.init(self.allocator);
+ try result.put("prompts", .{ .array = prompts_array });
+
+ return .{ .object = result };
+ }
+
+ fn handlePromptsGet(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {
+ return types.JSONValue{ .null = {} };
+ }
+
+ fn handleSampling(self: *MCPServer, _: ?types.JSONValue) !types.JSONValue {
+ return types.JSONValue{ .null = {} };
+ }
+};
+
+fn jsonValueFromCapabilities(allocator: std.mem.Allocator, caps: types.ServerCapabilities) !types.JSONValue {
+ var obj = std.json.ObjectMap.init(allocator);
+
+ if (caps.tools) |_| {
+ var tools_obj = std.json.ObjectMap.init(allocator);
+ try tools_obj.put("listChanged", .{ .bool = false });
+ try obj.put("tools", .{ .object = tools_obj });
+ }
+
+ if (caps.resources) |_| {
+ var res_obj = std.json.ObjectMap.init(allocator);
+ try res_obj.put("subscribe", .{ .bool = false });
+ try res_obj.put("listChanged", .{ .bool = false });
+ try obj.put("resources", .{ .object = res_obj });
+ }
+
+ if (caps.prompts) |_| {
+ var prompts_obj = std.json.ObjectMap.init(allocator);
+ try prompts_obj.put("listChanged", .{ .bool = false });
+ try obj.put("prompts", .{ .object = prompts_obj });
+ }
+
+ if (caps.sampling) |_| {
+ try obj.put("sampling", .{ .object = std.json.ObjectMap.init(allocator) });
+ }
+
+ return .{ .object = obj };
+}
+
+fn jsonValueFromServerInfo(allocator: std.mem.Allocator, info: types.ServerInfo) !types.JSONValue {
+ var obj = std.json.ObjectMap.init(allocator);
+ try obj.put("name", .{ .string = info.name });
+ try obj.put("version", .{ .string = info.version });
+ return .{ .object = obj };
+}
diff --git a/src/mcp/transport.zig b/src/mcp/transport.zig
new file mode 100644
index 0000000..9841ccc
--- /dev/null
+++ b/src/mcp/transport.zig
@@ -0,0 +1,199 @@
+const std = @import("std");
+const types = @import("types.zig");
+
+pub const Transport = union(enum) {
+ stdio: StdioTransport,
+ http: HttpTransport,
+};
+
+pub const StdioTransport = struct {
+ allocator: std.mem.Allocator,
+
+ pub fn init(allocator: std.mem.Allocator) StdioTransport {
+ return StdioTransport{
+ .allocator = allocator,
+ };
+ }
+
+ pub fn readMessage(self: StdioTransport) ![]u8 {
+ // Read length as 4-byte big-endian integer
+ var len_buf: [4]u8 = undefined;
+ const bytes_read = try std.os.read(std.os.STDIN_FILENO, &len_buf);
+ if (bytes_read == 0) {
+ return error.ConnectionClosed;
+ }
+ if (bytes_read < 4) {
+ return error.InvalidMessage;
+ }
+
+ const len = std.mem.readBig(u32, &len_buf);
+ if (len > 10 * 1024 * 1024) {
+ return error.MessageTooLarge;
+ }
+
+ const message = try self.allocator.alloc(u8, len);
+ errdefer self.allocator.free(message);
+
+ var remaining = len;
+ var offset: usize = 0;
+ while (remaining > 0) {
+ const read = try std.os.read(std.os.STDIN_FILENO, message[offset..]);
+ if (read == 0) {
+ return error.ConnectionClosed;
+ }
+ offset += read;
+ remaining -= read;
+ }
+
+ return message;
+ }
+
+ pub fn writeMessage(self: StdioTransport, data: []const u8) !void {
+ // Write length as 4-byte big-endian integer followed by data
+ var len_buf: [4]u8 = undefined;
+ std.mem.writeBig(u32, &len_buf, @intCast(data.len));
+
+ var total_written: usize = 0;
+ while (total_written < 4) {
+ const written = try std.os.write(std.os.STDOUT_FILENO, len_buf[total_written..]);
+ if (written == 0) {
+ return error.ConnectionClosed;
+ }
+ total_written += written;
+ }
+
+ total_written = 0;
+ while (total_written < data.len) {
+ const written = try std.os.write(std.os.STDOUT_FILENO, data[total_written..]);
+ if (written == 0) {
+ return error.ConnectionClosed;
+ }
+ total_written += written;
+ }
+ }
+};
+
+pub const HttpTransport = struct {
+ allocator: std.mem.Allocator,
+ stream: std.net.Stream,
+ buffer: std.ArrayList(u8),
+
+ pub fn init(allocator: std.mem.Allocator, stream: std.net.Stream) HttpTransport {
+ return HttpTransport{
+ .allocator = allocator,
+ .stream = stream,
+ .buffer = std.ArrayList(u8).init(allocator),
+ };
+ }
+
+ pub fn deinit(self: *HttpTransport) void {
+ self.buffer.deinit();
+ }
+
+ pub fn readMessage(self: *HttpTransport) ![]u8 {
+ // Read HTTP headers
+ var header_buf = [4096]u8{};
+ var header_end: usize = 0;
+
+ while (true) {
+ const bytes_read = try self.stream.read(header_buf[header_end..header_end + 1]);
+ if (bytes_read == 0) {
+ return error.ConnectionClosed;
+ }
+
+ if (header_end >= 3 and
+ header_buf[header_end - 3] == '\r' and
+ header_buf[header_end - 2] == '\n' and
+ header_buf[header_end - 1] == '\r' and
+ header_end >= 1 and header_buf[header_end] == '\n') {
+ break;
+ }
+ header_end += bytes_read;
+ }
+
+ // Parse Content-Length header
+ const headers = header_buf[0..header_end];
+ var content_length: usize = 0;
+ var lines = std.mem.split(u8, headers, "\r\n");
+ while (lines.next()) |line| {
+ if (std.ascii.startsWithIgnoreCase(line, "content-length:")) {
+ const value = std.mem.trim(u8, line[15..], " ");
+ content_length = try std.fmt.parseInt(usize, value, 10);
+ }
+ }
+
+ if (content_length == 0) {
+ return error.InvalidMessage;
+ }
+
+ // Read body
+ const body = try self.allocator.alloc(u8, content_length);
+ errdefer self.allocator.free(body);
+
+ var remaining = content_length;
+ var offset: usize = 0;
+ while (remaining > 0) {
+ const read = try self.stream.read(body[offset..]);
+ if (read == 0) {
+ return error.ConnectionClosed;
+ }
+ offset += read;
+ remaining -= read;
+ }
+
+ return body;
+ }
+
+ pub fn writeMessage(self: *HttpTransport, data: []const u8) !void {
+ // Write HTTP response with SSE
+ var buf = std.ArrayList(u8).init(self.allocator);
+ defer buf.deinit();
+
+ try buf.writer().print(
+ "HTTP/1.1 200 OK\r\n" ++
+ "Content-Type: text/event-stream\r\n" ++
+ "Cache-Control: no-cache\r\n" ++
+ "Connection: keep-alive\r\n" ++
+ "Transfer-Encoding: chunked\r\n" ++
+ "\r\n",
+ .{}
+ );
+
+ // Write chunked body
+ try buf.writer().print("{x}\r\n", .{data.len});
+ try buf.appendSlice(data);
+ try buf.appendSlice("\r\n");
+ try buf.appendSlice("0\r\n\r\n");
+
+ try self.stream.writeAll(buf.items);
+ }
+};
+
+pub fn runStdioServer(allocator: std.mem.Allocator, server: anytype) !void {
+ var transport = StdioTransport.init(allocator);
+
+ while (true) {
+ const message = transport.readMessage() catch |err| {
+ if (err == error.ConnectionClosed) {
+ break;
+ }
+ std.log.err("Failed to read message: {}", .{err});
+ continue;
+ };
+
+ const response = server.handleMessage(message) catch |err| {
+ std.log.err("Failed to handle message: {}", .{err});
+ allocator.free(message);
+ continue;
+ };
+ allocator.free(message);
+
+ if (response.len > 0) {
+ transport.writeMessage(response) catch |err| {
+ std.log.err("Failed to write response: {}", .{err});
+ break;
+ };
+ allocator.free(response);
+ }
+ }
+}
diff --git a/src/mcp/types.zig b/src/mcp/types.zig
new file mode 100644
index 0000000..8bfe851
--- /dev/null
+++ b/src/mcp/types.zig
@@ -0,0 +1,431 @@
+const std = @import("std");
+
+pub const JSONRPCVersion = "2.0";
+
+pub const ErrorCode = enum(i32) {
+ ParseError = -32700,
+ InvalidRequest = -32600,
+ MethodNotFound = -32601,
+ InvalidParams = -32602,
+ InternalError = -32603,
+};
+
+pub const JSONRPCError = struct {
+ code: ErrorCode,
+ message: []const u8,
+ data: ?[]const u8 = null,
+};
+
+pub const ProgressToken = union(enum) {
+ string: []const u8,
+ number: i64,
+};
+
+pub const JSONValue = union(enum) {
+ null,
+ bool: bool,
+ number: f64,
+ string: []const u8,
+ array: []JSONValue,
+ object: std.json.ObjectMap,
+};
+
+pub const TextContent = struct {
+ type: []const u8 = "text",
+ text: []const u8,
+};
+
+pub const ImageContent = struct {
+ type: []const u8 = "image",
+ data: []const u8,
+ mimeType: []const u8,
+};
+
+pub const BlobResourceContents = struct {
+ type: []const u8 = "blob",
+ blob: []const u8,
+ mimeType: []const u8,
+};
+
+pub const EmbeddedResource = struct {
+ type: []const u8 = "resource",
+ resource: Resource,
+};
+
+pub const Content = union(enum) {
+ text: TextContent,
+ image: ImageContent,
+ blob: BlobResourceContents,
+ embedded: EmbeddedResource,
+};
+
+pub const Resource = struct {
+ uri: []const u8,
+ mimeType: []const u8,
+ text: ?[]const u8 = null,
+ blob: ?[]const u8 = null,
+};
+
+pub const TextResourceContents = struct {
+ uri: []const u8,
+ mimeType: []const u8,
+ text: []const u8,
+};
+
+pub const CallToolResult = struct {
+ content: []Content,
+ isError: bool = false,
+};
+
+pub const Tool = struct {
+ name: []const u8,
+ description: []const u8,
+ inputSchema: std.json.ObjectMap,
+};
+
+pub const ResourceTemplate = struct {
+ uriTemplate: []const u8,
+ name: []const u8,
+ description: []const u8,
+ mimeType: []const u8,
+};
+
+pub const Resource_ = struct {
+ uri: []const u8,
+ name: []const u8,
+ description: []const u8,
+ mimeType: []const u8,
+};
+
+pub const Prompt = struct {
+ name: []const u8,
+ description: []const u8,
+ arguments: ?[]struct {
+ name: []const u8,
+ description: []const u8,
+ required: bool,
+ } = null,
+};
+
+pub const PromptMessage = struct {
+ role: []const u8,
+ content: Content,
+};
+
+pub const PromptResult = struct {
+ messages: []PromptMessage,
+};
+
+pub const SamplingMessage = struct {
+ role: []const u8,
+ content: Content,
+};
+
+pub const CreateMessageRequest = struct {
+ messages: []SamplingMessage,
+ systemPrompt: ?[]const u8 = null,
+ includeContext: []const u8,
+ temperature: ?f64 = null,
+ maxTokens: ?u32 = null,
+};
+
+pub const CreateMessageResult = struct {
+ role: []const u8,
+ content: Content,
+ model: []const u8,
+ stopReason: []const u8,
+};
+
+pub const InitializeResult = struct {
+ protocolVersion: []const u8,
+ capabilities: ServerCapabilities,
+ serverInfo: ServerInfo,
+};
+
+pub const ServerCapabilities = struct {
+ tools: ?struct {
+ listChanged: bool = false,
+ } = null,
+ resources: ?struct {
+ subscribe: bool = false,
+ listChanged: bool = false,
+ } = null,
+ prompts: ?struct {
+ listChanged: bool = false,
+ } = null,
+ sampling: ?struct {},
+};
+
+pub const ServerInfo = struct {
+ name: []const u8,
+ version: []const u8,
+};
+
+pub const ClientCapabilities = struct {
+ tools: ?struct {
+ listChanged: bool = false,
+ } = null,
+ resources: ?struct {
+ subscribe: bool = false,
+ listChanged: bool = false,
+ } = null,
+ prompts: ?struct {
+ listChanged: bool = false,
+ } = null,
+ sampling: ?struct {},
+};
+
+pub const InitializeParams = struct {
+ protocolVersion: []const u8,
+ capabilities: ClientCapabilities,
+ clientInfo: ClientInfo,
+};
+
+pub const ClientInfo = struct {
+ name: []const u8,
+ version: []const u8,
+};
+
+pub const EmptyParams = struct {};
+
+pub const ListToolsResult = struct {
+ tools: []Tool,
+};
+
+pub const ListResourcesResult = struct {
+ resources: []Resource_,
+};
+
+pub const ListResourceTemplatesResult = struct {
+ resourceTemplates: []ResourceTemplate,
+};
+
+pub const ListPromptsResult = struct {
+ prompts: []Prompt,
+};
+
+pub const ReadResourceResult = struct {
+ contents: []const union(enum) {
+ TextResourceContents,
+ BlobResourceContents,
+ },
+};
+
+pub const JSONRPCMessage = union(enum) {
+ request: JSONRPCRequest,
+ notification: JSONRPCNotification,
+ response: JSONRPCResponse,
+ error: JSONRPCErrorResponse,
+};
+
+pub const JSONRPCRequest = struct {
+ jsonrpc: []const u8,
+ id: union(enum) {
+ string: []const u8,
+ number: i64,
+ },
+ method: []const u8,
+ params: ?JSONValue = null,
+};
+
+pub const JSONRPCNotification = struct {
+ jsonrpc: []const u8,
+ method: []const u8,
+ params: ?JSONValue = null,
+};
+
+pub const JSONRPCResponse = struct {
+ jsonrpc: []const u8,
+ id: union(enum) {
+ string: []const u8,
+ number: i64,
+ },
+ result: JSONValue,
+};
+
+pub const JSONRPCErrorResponse = struct {
+ jsonrpc: []const u8,
+ id: ?union(enum) {
+ string: []const u8,
+ number: i64,
+ } = null,
+ error: JSONRPCError,
+};
+
+pub fn parseMessage(allocator: std.mem.Allocator, data: []const u8) !JSONRPCMessage {
+ const parsed = try std.json.parseFromSlice(JSONValue, allocator, data, .{});
+ defer parsed.deinit();
+
+ const value = parsed.value;
+ if (value != .object) {
+ return error.InvalidJSON;
+ }
+
+ const obj = value.object;
+
+ // Check if it's a request (has method and id)
+ if (obj.get("method")) |method_val| {
+ if (obj.get("id")) |id_val| {
+ // Request
+ const method = method_val.string;
+ const id = if (id_val == .number) .{ number = @intCast(id_val.number) } else .{ string = id_val.string };
+ const params = if (obj.get("params")) |p| p else null;
+
+ return JSONRPCMessage{
+ .request = .{
+ .jsonrpc = JSONRPCVersion,
+ .id = id,
+ .method = method,
+ .params = params,
+ },
+ };
+ } else {
+ // Notification
+ const method = method_val.string;
+ const params = if (obj.get("params")) |p| p else null;
+
+ return JSONRPCMessage{
+ .notification = .{
+ .jsonrpc = JSONRPCVersion,
+ .method = method,
+ .params = params,
+ },
+ };
+ }
+ }
+
+ // Check if it's an error response
+ if (obj.get("error")) |_| {
+ const id = if (obj.get("id")) |id_val| if (id_val == .number) .{ number = @intCast(id_val.number) } else .{ string = id_val.string } else null;
+ const err_obj = obj.get("error").?.object;
+ const code = @enumFromInt(@intFromFloat(err_obj.get("code").?.number));
+ const message = err_obj.get("message").?.string;
+ const data_val = if (err_obj.get("data")) |d| d.string else null;
+
+ return JSONRPCMessage{
+ .error = .{
+ .jsonrpc = JSONRPCVersion,
+ .id = id,
+ .error = .{ .code = code, .message = message, .data = data_val },
+ },
+ };
+ }
+
+ // Must be a response
+ const id = if (obj.get("id").?) |id_val| if (id_val == .number) .{ number = @intCast(id_val.number) } else .{ string = id_val.string } else unreachable;
+ const result = obj.get("result").?;
+
+ return JSONRPCMessage{
+ .response = .{
+ .jsonrpc = JSONRPCVersion,
+ .id = id,
+ .result = result,
+ },
+ };
+}
+
+pub fn serializeMessage(allocator: std.mem.Allocator, msg: JSONRPCMessage) ![]u8 {
+ var buf = std.ArrayList(u8).init(allocator);
+ try serializeMessageWriter(msg, buf.writer());
+ return buf.toOwnedSlice();
+}
+
+fn serializeMessageWriter(msg: JSONRPCMessage, writer: anytype) !void {
+ switch (msg) {
+ .request => |req| {
+ try writer.writeByte('{');
+ try writer.print("\"jsonrpc\":\"{s}\",\"id\":", .{req.jsonrpc});
+ switch (req.id) {
+ .string => |s| try writer.print("\"{s}\",", .{s}),
+ .number => |n| try writer.print("{},", .{n}),
+ }
+ try writer.print("\"method\":\"{s}\"", .{req.method});
+ if (req.params) |params| {
+ try writer.writeByte(',');
+ try writer.writeAll("\"params\":");
+ try jsonValueWrite(params, writer);
+ }
+ try writer.writeByte('}');
+ },
+ .notification => |notif| {
+ try writer.writeByte('{');
+ try writer.print("\"jsonrpc\":\"{s}\",\"method\":\"{s}\"", .{notif.jsonrpc, notif.method});
+ if (notif.params) |params| {
+ try writer.writeByte(',');
+ try writer.writeAll("\"params\":");
+ try jsonValueWrite(params, writer);
+ }
+ try writer.writeByte('}');
+ },
+ .response => |resp| {
+ try writer.writeByte('{');
+ try writer.print("\"jsonrpc\":\"{s}\",\"id\":", .{resp.jsonrpc});
+ switch (resp.id) {
+ .string => |s| try writer.print("\"{s}\",", .{s}),
+ .number => |n| try writer.print("{},", .{n}),
+ }
+ try writer.writeAll("\"result\":");
+ try jsonValueWrite(resp.result, writer);
+ try writer.writeByte('}');
+ },
+ .error => |err| {
+ try writer.writeByte('{');
+ try writer.print("\"jsonrpc\":\"{s}\",\"error\":{{"code":{},\"message\":\"{s}\"", .{err.jsonrpc, @intFromEnum(err.error.code), err.error.message});
+ if (err.error.data) |data| {
+ try writer.print(",\"data\":\"{s}\"", .{data});
+ }
+ try writer.writeByte('}');
+ if (err.id) |id| {
+ try writer.writeByte(',');
+ try writer.writeAll("\"id\":");
+ switch (id) {
+ .string => |s| try writer.print("\"{s}\"", .{s}),
+ .number => |n| try writer.writeAll("{}*{n}"),
+ }
+ }
+ try writer.writeByte('}');
+ },
+ }
+}
+
+fn jsonValueWrite(val: JSONValue, writer: anytype) !void {
+ switch (val) {
+ .null => try writer.writeAll("null"),
+ .bool => |b| try writer.writeAll(if (b) "true" else "false"),
+ .number => |n| try writer.print("{}", .{n}),
+ .string => |s| {
+ try writer.writeByte('"');
+ for (s) |c| {
+ switch (c) {
+ '\\' => try writer.writeAll("\\\\"),
+ '"' => try writer.writeAll("\\\""),
+ '\n' => try writer.writeAll("\\n"),
+ '\r' => try writer.writeAll("\\r"),
+ '\t' => try writer.writeAll("\\t"),
+ else => try writer.writeByte(c),
+ }
+ }
+ try writer.writeByte('"');
+ },
+ .array => |arr| {
+ try writer.writeByte('[');
+ for (arr, 0..) |item, i| {
+ if (i > 0) try writer.writeByte(',');
+ try jsonValueWrite(item, writer);
+ }
+ try writer.writeByte(']');
+ },
+ .object => |obj| {
+ try writer.writeByte('{');
+ var first = true;
+ var it = obj.iterator();
+ while (it.next()) |entry| {
+ if (!first) try writer.writeByte(',');
+ first = false;
+ try writer.print("\"{s}\":", .{entry.key_ptr.*});
+ try jsonValueWrite(entry.value_ptr.*, writer);
+ }
+ try writer.writeByte('}');
+ },
+ }
+}