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/initialized"
}

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/initialized",
    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

Lifecycle

MethodTypeDescription
initializeRequestInitialize connection
notifications/initializedNotificationConfirm initialization
pingRequestCheck connection

Tools

MethodTypeDescription
tools/listRequestList available tools
tools/callRequestExecute a tool

Resources

MethodTypeDescription
resources/listRequestList resources
resources/readRequestRead a resource
resources/templates/listRequestList templates
resources/subscribeRequestSubscribe to changes

Prompts

MethodTypeDescription
prompts/listRequestList prompts
prompts/getRequestGet a prompt

Logging

MethodTypeDescription
logging/setLevelRequestSet log level

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, "ping")) {
                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