Lesson 3 - Step 2 of 5

Creating Requests

Build a function to create valid JSON-RPC 2.0 requests.

How to Read Code Blocks

In this lesson:

  • Filename in header (e.g., jsonrpc.py) = Write this code to that file
  • "Add after X" = Append this code to your existing file
  • bash blocks = Commands to run in your terminal
  • "Expected Output" = What you should see (don't write this)

Create: jsonrpc.py

Create a new file called jsonrpc.py. This will be your JSON-RPC utility module that you'll build up throughout this lesson.

python jsonrpc.py
"""
jsonrpc.py - JSON-RPC 2.0 utilities for MCP.

This module provides functions for creating and handling JSON-RPC messages.
"""

import json
from typing import Any


def create_request(
    method: str,
    params: dict | None = None,
    request_id: int | None = None
) -> dict:
    """
    Create a JSON-RPC 2.0 request.

    Args:
        method: The method name to call (e.g., "tools/list")
        params: Optional parameters for the method
        request_id: Unique identifier for matching responses

    Returns:
        A properly formatted JSON-RPC request dict
    """
    request: dict[str, Any] = {
        "jsonrpc": "2.0",
        "method": method,
    }

    if request_id is not None:
        request["id"] = request_id

    if params is not None:
        request["params"] = params

    return request


# =============================================================================
# Try it out
# =============================================================================

if __name__ == "__main__":
    # Create a simple request
    req1 = create_request("tools/list", request_id=1)
    print("Request 1 (tools/list):")
    print(json.dumps(req1, indent=2))
    print()

    # Create a request with parameters
    req2 = create_request(
        method="tools/call",
        params={"name": "calculator", "arguments": {"expression": "2 + 2"}},
        request_id=2
    )
    print("Request 2 (tools/call with params):")
    print(json.dumps(req2, indent=2))

Try It

Run your new module:

bash
python jsonrpc.py

Expected Output

text
Request 1 (tools/list):
{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "id": 1
}

Request 2 (tools/call with params):
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "id": 2,
  "params": {
    "name": "calculator",
    "arguments": {
      "expression": "2 + 2"
    }
  }
}

Understanding the Code

The Four Fields

A JSON-RPC 2.0 request has four possible fields:

Field Required? Description
jsonrpc Yes Must be exactly "2.0"
method Yes The method name to call
id For requests Unique identifier (string or int)
params No Parameters for the method

Type Annotations

Notice the type hints use dict | None syntax. This tells Python (and your IDE) that the parameter can be either a dict or None:

python
# This is the modern Python 3.10+ syntax
params: dict | None = None

# Equivalent to the older typing.Optional syntax:
# from typing import Optional
# params: Optional[dict] = None
Why IDs Matter

The id field lets you match responses to requests. When you send multiple requests concurrently, responses might arrive out of order. The ID tells you which response belongs to which request.

What's Next

Your module can create requests, but what about responses? In the next step, you'll add functions to create success responses, error responses, and handle JSON-RPC error codes.