Example JSON-RPC Messages for Anthropic MCP (Stateless HTTP Mode)

July 17, 2025 · View on GitHub

This document provides example JSON-RPC messages for the supported MCP commands.

Example JSON-RPC Messages for Anthropic MCP (Stateless HTTP Mode)

Below are example JSON-RPC 2.0 request and response objects for each relevant Model Context Protocol (MCP) command in stateless HTTP mode. Each example shows a complete JSON structure with realistic field values, based on the MCP specification. (All streaming or SSE-based fields are omitted, as these examples assume a non-streaming HTTP interaction.)

initialize

Description: The client begins a session by sending an initialize request with its supported protocol version, capabilities, and client info. The server replies with its own protocol version (which may be negotiated), supported server capabilities (e.g. logging, prompts, resources, tools), server info, and any optional instructions.

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "roots": {
        "listChanged": true
      },
      "sampling": {}
    },
    "clientInfo": {
      "name": "ExampleClient",
      "version": "1.0.0"
    }
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "logging": {},
      "prompts": {
        "listChanged": true
      },
      "resources": {
        "subscribe": true,
        "listChanged": true
      },
      "tools": {
        "listChanged": true
      }
    },
    "serverInfo": {
      "name": "ExampleServer",
      "version": "1.0.0"
    },
    "instructions": "Optional instructions for the client"
  }
}

initialized (notification)

Description: After the server responds to initialize, the client sends an initialized notification to signal that it is ready for normal operations. This is a JSON-RPC notification (no id field and no response expected).

Notification:

{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

ping

Description: Either party can send a ping request at any time to check connectivity. The ping request has no parameters, and the receiver must promptly return an empty result object if still alive.

Request:

{
  "jsonrpc": "2.0",
  "id": "123",
  "method": "ping"
}

Response:

{
  "jsonrpc": "2.0",
  "id": "123",
  "result": {}
}

resources/list

Description: The client requests a list of available resources (files, data, etc.) from the server. The resources/list request may include an optional cursor for pagination. The response contains an array of resource descriptors (each with fields like uri, name, description, mimeType, etc.) and may include a nextCursor token if more results are available.

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "resources/list",
  "params": {
    "cursor": "optional-cursor-value"
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resources": [
      {
        "uri": "file:///project/src/main.rs",
        "name": "main.rs",
        "description": "Primary application entry point",
        "mimeType": "text/x-rust"
      }
    ],
    "nextCursor": "next-page-cursor"
  }
}

resources/read

Description: The client retrieves the contents of a specific resource by sending resources/read with the resource's URI. The server's response includes a contents array with the resource data. If the resource is text-based, it appears under a text field (with an associated MIME type); for binary data, a blob (base64 string) would be used instead.

Request:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "resources/read",
  "params": {
    "uri": "file:///project/src/main.rs"
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "contents": [
      {
        "uri": "file:///project/src/main.rs",
        "mimeType": "text/x-rust",
        "text": "fn main() {\n    println!(\"Hello world!\");\n}"
      }
    ]
  }
}

resources/templates/list

Description: The client can query available resource templates (parameterized resource URIs) by sending resources/templates/list. The response provides a list of resource template definitions, each with a uriTemplate (often containing placeholders), a human-readable name and description, and an optional mimeType indicating the type of resource produced.

Request:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "resources/templates/list"
}

Response:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "resourceTemplates": [
      {
        "uriTemplate": "file:///{path}",
        "name": "Project Files",
        "description": "Access files in the project directory",
        "mimeType": "application/octet-stream"
      }
    ]
  }
}

prompts/list

Description: The client requests a list of available prompt templates by sending prompts/list. This may also support pagination via a cursor. The server responds with an array of prompt definitions, where each prompt has a unique name, a description of what it does, and an optional list of expected arguments (each argument with a name, description, and whether it's required). A nextCursor may be provided if the list is paginated.

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "prompts/list",
  "params": {
    "cursor": "optional-cursor-value"
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "prompts": [
      {
        "name": "code_review",
        "description": "Asks the LLM to analyze code quality and suggest improvements",
        "arguments": [
          {
            "name": "code",
            "description": "The code to review",
            "required": true
          }
        ]
      }
    ],
    "nextCursor": "next-page-cursor"
  }
}

prompts/get

Description: To fetch the content of a specific prompt template (possibly filling in arguments), the client sends prompts/get with the prompt's name and an arguments object providing any required values. The server returns the resolved prompt: typically a description and a sequence of messages that make up the prompt. Each message has a role (e.g. "user" or "assistant") and content which could be text or other supported content types.

Request:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "prompts/get",
  "params": {
    "name": "code_review",
    "arguments": {
      "code": "def hello():\n    print('world')"
    }
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "description": "Code review prompt",
    "messages": [
      {
        "role": "user",
        "content": {
          "type": "text",
          "text": "Please review this Python code:\n def hello():\n    print('world')"
        }
      }
    ]
  }
}

tools/list

Description: The client sends tools/list to get the list of tools (functions/actions) the server provides. The response includes an array of tool definitions. Each tool has a name, a description of its functionality, and an inputSchema (a JSON Schema object) describing the expected parameters for that tool. The example below shows one tool with a required location parameter. A nextCursor may appear if the list is paginated.

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {
    "cursor": "optional-cursor-value"
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "get_weather",
        "description": "Get current weather information for a location",
        "inputSchema": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "City name or zip code"
            }
          },
          "required": ["location"]
        }
      }
    ],
    "nextCursor": "next-page-cursor"
  }
}

tools/call

Description: To execute a specific tool, the client sends a tools/call request with the tool's name and an arguments object providing the needed inputs. The server will run the tool and return a result. The result includes a content array (which may contain text or other content types, depending on what the tool returns) and an isError boolean indicating whether the tool succeeded. In this example, the tool returns a text result (weather information) and isError: false to show success.

Request:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "location": "New York"
    }
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Current weather in New York:\n Temperature: 72°F\n Conditions: Partly cloudy"
      }
    ],
    "isError": false
  }
}

notifications/cancelled (notification)

Description: If either side needs to cancel an in-progress request (e.g. due to a timeout or user action), it sends a notifications/cancelled notification. This one-way message includes the requestId of the original request to be aborted and an optional reason string. The receiver should stop work on that request but does not send any response to the notification.

Notification:

{
  "jsonrpc": "2.0",
  "method": "notifications/cancelled",
  "params": {
    "requestId": "123",
    "reason": "User requested cancellation"
  }
}

Each JSON example above illustrates the structure and fields defined by the MCP specification for stateless HTTP usage, covering the full request/response cycle (or one-way notification) for that command. These messages can be sent over an HTTP-based JSON-RPC connection to manage the model's context and actions without using server-sent events or streaming protocols. All field names and nesting conform to the MCP spec, ensuring interoperability between MCP clients and servers.

Introduction

This specification aims to describe a simple protocol for LLMs to discover and use remote APIs, while minimizing the context window used. While Anthropic's Model Context Protocol (MCP) is primarily used for the purpose, its design is non-optimal. MCP began as a simple STDIO‑based local solution but evolved into a complex, stateful HTTP/SSE system that burdens developers with session management and infrastructure headaches. Maintaining persistent connections conflicts with stateless microservice patterns, leading to scalability and load‑balancing challenges. This specification adopts a minimal, stateless design to avoid these pitfalls.

Overview

Webtools expose a lightweight, HTTP‑based contract that allows consumers to

  • Discover capabilities through self‑describing metadata
  • Validate inputs and outputs via JSON Schema definitions
    • one schema for the request object (requestSchema)
    • one schema for the response object (responseSchema)
  • Execute actions with optional per‑request configuration
  • Consume predictable, strongly‑typed responses
  • Lock-in specific API versions to improve security

Use Case Scenario

Webtools are defined by URLs. The typical workflow follows these steps:

  1. Discovery: A user finds a webtool URL from a tool provider, marketplace, or other source
  2. Metadata Retrieval: The user's system issues a GET request to the URL to retrieve the webtool's metadata
  3. Configuration: The user fills in configuration data according to the configSchema defined in the metadata
  4. Integration: The system is now able to use the webtool with LLMs, passing the configuration and handling requests/responses

Security Considerations

After reviewing the schemas and metadata, users may choose to lock-in a specific version by storing the validated metadata on their side and no longer fetching it from the remote server. This prevents potential security risks where malicious instructions could be injected into the LLM context through schema changes in newer versions of the webtool metadata.

HTTP Methods

GET {webtoolUrl}/ — Webtool Metadata (latest)

Returns metadata about the latest version of the webtool.

{
  "name": "webtool_name",
  "description": "Human‑readable description of what this webtool does",
  "version": "2.1.0",
  "actions": [
    {
      "name": "action_name",
      "description": "What this action does",
      "requestSchema": { /* JSON Schema for request */ },
      "responseSchema": { /* JSON Schema for response */ }
    }
  ],
  "configSchema": { /* JSON Schema for configuration */ },
  "defaultConfig": { /* Default configuration values */ }
}

GET {webtoolUrl}/{version} — Webtool Metadata (specific version)

Returns metadata for the specified semantic version. Use this to fetch historical versions or pin a client to a stable release. The version parameter is optional; if omitted, the server SHOULD default to the latest version.

GET /1.0.0
{
  "name": "weather",
  "description": "Provides weather information",
  "version": "1.0.0",
  "actions": [ /* …as above… */ ],
  "configSchema": { /* … */ },
  "defaultConfig": { /* … */ }
}

Note: If the version is not found, the endpoint should return 404 Not Found with an error envelope identical to the standard error response.

POST {webtoolUrl}/ — Webtool Execution

{
  "sessionId": "unique-session-identifier",
  "version": "1.0.0",
  "action": "action_name",
  "config": { /* Optional configuration object matching configSchema */ },
  "request": { /* Required data matching requestSchema */ }
}

The sessionId field is optional and used for maintaining state across multiple requests to the same webtool. The version field is optional; if omitted, the server SHOULD default to the latest version. The config property contains data that is not generated by the LLM but rather supplied by the environment, allowing minimization of context use, passing security credentials, parameter defaults, or user-configured values.

Response Examples

Successful Response
{
  "status": "ok",
  "data": { /* Action‑specific result */ }
}
Error Response
{
  "status": "error",
  "error": {
    "code": "INVALID_INPUT",
    "message": "Validation failed for field 'location'"
  }
}

Content Types

All requests and responses MUST use application/json content type. Servers MUST include Content-Type: application/json headers in their responses.

JSON Schema Requirements

Webtools MAY use any JSON Schema features. Schemas SHOULD include descriptions and example values to help LLMs understand the expected data structure and format.

Error Handling

The specification defines standard error codes for common validation errors:

  • WEBTOOL_NOT_FOUND - Requested webtool or version does not exist
  • SCHEMA_ERROR - Request data does not match the action's requestSchema
  • CONFIG_ERROR - Configuration data does not match the webtool's configSchema
  • RATE_LIMIT - Request rate limit exceeded
  • INTERNAL_ERROR - Unrecoverable server error

Webtools MAY define their own custom error codes for domain-specific errors. Error messages SHOULD be human-readable.

Integration Guides

Using Webtools with the Vercel AI SDK

The Vercel AI SDK supports OpenAI‑style tool calling out‑of‑the‑box.

1 – Fetch Metadata at Build Time

// lib/tools/weather.ts
import type { Tool } from "ai";

export async function getWeatherTool(): Promise<Tool> {
  const res = await fetch("/api/webtools/weather");
  const meta = await res.json();
  return {
    name: meta.name,
    description: meta.description,
    parameters: meta.actions[0].requestSchema, // <-- uses requestSchema
    execute: async (args) => {
      const exec = await fetch("/api/webtools/weather", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          sessionId: crypto.randomUUID(),
          action: args.action,
          request: args
        })
      });
      return (await exec.json()).data; // unwrap the envelope
    }
  };
}

2 – Create a Tool Caller

import { createToolCaller } from "ai/tool-caller";
import OpenAI from "@ai-sdk/openai";
import { getWeatherTool } from "@/lib/tools/weather";

const toolCaller = createToolCaller([await getWeatherTool()]);

export async function chat(messages) {
  const llm = new OpenAI();
  const modelResponse = await llm.chat({ messages, tools: toolCaller.tools });
  const final = await toolCaller.call(modelResponse);
  return final;
}

Tip: The AI SDK automatically translates requestSchema into the function‑calling format the model expects.

Using Webtools with LangChain

LangChain's StructuredTool helper lets you wrap a webtool with schema metadata so agents can invoke it.

from langchain_core.tools import StructuredTool
import requests, uuid

WEATHER_ENDPOINT = "https://api.example.com/webtools/weather"

def run_get_current(location: str, units: str = "metric"):
    body = {
        "sessionId": str(uuid.uuid4()),
        "action": "get_current",
        "request": {"location": location},
        "config": {"units": units}
    }
    return requests.post(WEATHER_ENDPOINT, json=body, timeout=10).json()

weather_tool = StructuredTool.from_function(
    func=run_get_current,
    name="get_current_weather",
    description="Return the current weather for a given location via the Weather webtool",
    schema={
        "type": "object",
        "properties": {
            "location": {"type": "string"},
            "units": {"type": "string", "enum": ["metric", "imperial"], "default": "metric"}
        },
        "required": ["location"]
    }
)

Then add weather_tool to any LCEL runnable or agent:

from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_functions_agent

llm = ChatOpenAI(model_name="gpt-4o")
agent = create_openai_functions_agent(llm, tools=[weather_tool])
executor = AgentExecutor(agent=agent, tools=[weather_tool])

result = executor.invoke("Should I take an umbrella to Paris today?")
print(result)

Error Handling & Best Practices

  • Validate client input against each action's requestSchema before issuing a POST.
  • For recoverable failures (4xx), return status: "error" with appropriate error codes.
  • For unrecoverable server errors (5xx), set code INTERNAL_ERROR and avoid leaking internals.
  • Use standard HTTP status codes alongside the JSON response for broad compatibility.
  • Rate limiting, quotas, and other implementation details are left to implementers.

Authentication & Authorization

This specification is agnostic regarding authorization schemes. Consumers MAY include an Authorization: Bearer header on every request. Additional details—such as token scopes, expiration, or refresh flows—are considered implementation-specific and are not mandated by this spec.


Version: 2025‑06‑30