Skip to content

JSON-RPC Protocol ​

mcp.zig implements JSON-RPC 2.0 for all MCP communication.

Overview ​

JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol that uses JSON for data encoding.

Message Types ​

Request ​

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "greet",
    "arguments": { "name": "World" }
  }
}

Response ​

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{ "type": "text", "text": "Hello, World!" }]
  }
}

Notification ​

json
{
  "jsonrpc": "2.0",
  "method": "notifications/roots/list_changed"
}

Error Response ​

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32600,
    "message": "Invalid Request"
  }
}

Using the API ​

Parse Messages ​

zig
const message = try mcp.jsonrpc.parseMessage(allocator, json_string);

switch (message) {
    .request => |req| {
        std.debug.print("Request: {s}\n", .{req.method});
    },
    .notification => |notif| {
        std.debug.print("Notification: {s}\n", .{notif.method});
    },
    .response => |resp| {
        std.debug.print("Response ID: {any}\n", .{resp.id});
    },
    .error_response => |err| {
        std.debug.print("Error: {s}\n", .{err.error.message});
    },
}

Create Messages ​

zig
// Request
const req = mcp.jsonrpc.createRequest(
    .{ .integer = 1 },
    "tools/list",
    null,
);

// Response
const resp = mcp.jsonrpc.createResponse(
    .{ .integer = 1 },
    result_value,
);

// Notification
const notif = mcp.jsonrpc.createNotification(
    "notifications/roots/list_changed",
    null,
);

Serialize Messages ​

zig
const json = try mcp.jsonrpc.serializeMessage(allocator, message);
defer allocator.free(json);

// Send json over transport

Error Codes ​

CodeConstantDescription
-32700PARSE_ERRORInvalid JSON
-32600INVALID_REQUESTInvalid request object
-32601METHOD_NOT_FOUNDMethod doesn't exist
-32602INVALID_PARAMSInvalid method parameters
-32603INTERNAL_ERRORInternal JSON-RPC error

Creating Error Responses ​

zig
// Parse error
const err = mcp.jsonrpc.createParseError(null);

// Method not found
const err = mcp.jsonrpc.createMethodNotFound(request_id, method);

// Invalid params
const err = mcp.jsonrpc.createInvalidParams(request_id, "Missing required field");

// Custom error
const err = mcp.jsonrpc.createErrorResponse(
    request_id,
    -32000,  // Custom code
    "Custom error message",
    null,
);

Request IDs ​

Request IDs can be integers or strings:

zig
// Integer ID
const id: mcp.types.RequestId = .{ .integer = 42 };

// String ID
const id: mcp.types.RequestId = .{ .string = "request-001" };

MCP Methods (2026-07-28) ​

Lifecycle ​

MethodTypeDescription
server/discoverRequestDiscover server capabilities

Tools ​

MethodTypeDescription
tools/listRequestList available tools
tools/callRequestExecute a tool

Resources ​

MethodTypeDescription
resources/listRequestList resources
resources/readRequestRead a resource
resources/templates/listRequestList templates
subscriptions/listenRequestSubscribe to changes

Prompts ​

MethodTypeDescription
prompts/listRequestList prompts
prompts/getRequestGet a prompt

Completion ​

MethodTypeDescription
completion/completeRequestArgument completion

Tasks ​

MethodTypeDescription
tasks/getRequestGet task status
tasks/resultRequestGet task result
tasks/listRequestList all tasks
tasks/cancelRequestCancel a task

Complete Example ​

zig
const std = @import("std");
const mcp = @import("mcp");

pub fn handleMessage(allocator: std.mem.Allocator, json: []const u8) ![]const u8 {
    // Parse incoming message
    const message = mcp.jsonrpc.parseMessage(allocator, json) catch {
        const err = mcp.jsonrpc.createParseError(null);
        return try mcp.jsonrpc.serializeMessage(allocator, .{ .error_response = err });
    };

    // Handle based on message type
    switch (message) {
        .request => |req| {
            if (std.mem.eql(u8, req.method, "server/discover")) {
                const resp = mcp.jsonrpc.createResponse(req.id, null);
                return try mcp.jsonrpc.serializeMessage(allocator, .{ .response = resp });
            }

            const err = mcp.jsonrpc.createMethodNotFound(req.id, req.method);
            return try mcp.jsonrpc.serializeMessage(allocator, .{ .error_response = err });
        },
        .notification => {
            // Handle notification (no response needed)
            return "";
        },
        else => {
            return error.UnexpectedMessage;
        },
    }
}

Next Steps ​