Lesson 3 - Step 4 of 5

Notifications

Add notification support and message type detection.

What is a Notification?

A notification is a JSON-RPC message that doesn't expect a response. The key difference from a request is the absence of an ID field:

json
// Request (has ID, expects response)
{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}

// Notification (no ID, no response expected)
{"jsonrpc": "2.0", "method": "notifications/initialized"}
MCP Notifications

MCP uses notifications for events like notifications/initialized (client is ready), notifications/cancelled (cancel a request), and notifications/progress (progress updates).

Edit: jsonrpc.py

Step 1: Add Notification Creator

Add this function after create_error_response:

python jsonrpc.py - Add after create_error_response
def create_notification(method: str, params: dict | None = None) -> dict:
    """
    Create a JSON-RPC notification (no ID, no response expected).

    Args:
        method: The notification method name
        params: Optional parameters

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

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

    return notification

Step 2: Add Message Type Detection

Add these helper functions after create_notification:

python jsonrpc.py - Add after create_notification
def is_request(message: dict) -> bool:
    """Check if a message is a JSON-RPC request (has method AND id)."""
    return (
        message.get("jsonrpc") == "2.0"
        and "method" in message
        and "id" in message
    )


def is_notification(message: dict) -> bool:
    """Check if a message is a notification (has method, NO id)."""
    return (
        message.get("jsonrpc") == "2.0"
        and "method" in message
        and "id" not in message
    )


def is_response(message: dict) -> bool:
    """Check if a message is a response (has id and result/error, NO method)."""
    return (
        message.get("jsonrpc") == "2.0"
        and "id" in message
        and ("result" in message or "error" in message)
        and "method" not in message
    )


def get_message_type(message: dict) -> str:
    """
    Determine the type of a JSON-RPC message.

    Returns:
        One of: "request", "notification", "response", or "invalid"
    """
    if is_request(message):
        return "request"
    elif is_notification(message):
        return "notification"
    elif is_response(message):
        return "response"
    else:
        return "invalid"

Step 3: Update the Test Block

Replace your test block with this expanded version:

python jsonrpc.py - Replace the test block
# =============================================================================
# Try it out
# =============================================================================

if __name__ == "__main__":
    print("=" * 60)
    print("JSON-RPC Message Types Demo")
    print("=" * 60)

    # Create different message types
    request = create_request("tools/list", request_id=1)
    notification = create_notification("notifications/initialized")
    success = create_success_response(1, {"tools": []})
    error = create_error_response(1, JsonRpcErrorCode.METHOD_NOT_FOUND)

    # Demonstrate each type
    messages = [
        ("Request", request),
        ("Notification", notification),
        ("Success Response", success),
        ("Error Response", error),
    ]

    for name, msg in messages:
        print(f"\n{name}:")
        print(json.dumps(msg, indent=2))
        print(f"  -> Type: {get_message_type(msg)}")

    # Show the detection logic
    print("\n" + "=" * 60)
    print("Message Type Detection")
    print("=" * 60)

    test_cases = [
        {"jsonrpc": "2.0", "id": 1, "method": "test"},           # request
        {"jsonrpc": "2.0", "method": "test"},                     # notification
        {"jsonrpc": "2.0", "id": 1, "result": {}},               # response
        {"jsonrpc": "2.0", "id": 1, "error": {"code": -1}},      # error response
        {"method": "test"},                                       # invalid (no jsonrpc)
    ]

    for msg in test_cases:
        print(f"\n{json.dumps(msg)}")
        print(f"  -> is_request: {is_request(msg)}")
        print(f"  -> is_notification: {is_notification(msg)}")
        print(f"  -> is_response: {is_response(msg)}")
        print(f"  -> type: {get_message_type(msg)}")

Try It

bash
python jsonrpc.py

Expected Output

text
============================================================
JSON-RPC Message Types Demo
============================================================

Request:
{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "id": 1
}
  -> Type: request

Notification:
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}
  -> Type: notification

Success Response:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": []
  }
}
  -> Type: response

Error Response:
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32601,
    "message": "Method not found"
  }
}
  -> Type: response

============================================================
Message Type Detection
============================================================

{"jsonrpc": "2.0", "id": 1, "method": "test"}
  -> is_request: True
  -> is_notification: False
  -> is_response: False
  -> type: request

{"jsonrpc": "2.0", "method": "test"}
  -> is_request: False
  -> is_notification: True
  -> is_response: False
  -> type: notification

...

Quick Reference: Message Types

Type Has ID? Has Method? Response?
Request Yes Yes Yes (wait for it)
Notification No Yes No (fire and forget)
Response Yes No Is the response
Never Respond to Notifications!

When your server receives a notification, process it silently. Never send a response — even if processing fails. The sender isn't waiting for one, and sending one could confuse clients.

Your File So Far

Your jsonrpc.py now includes:

  • create_request() — Make requests
  • create_success_response() — Make success responses
  • create_error_response() — Make error responses
  • create_notification() — Make notifications
  • is_request(), is_notification(), is_response() — Detect message types
  • get_message_type() — Get message type as a string

What's Next

You have all the building blocks for JSON-RPC messages. In the final step, you'll create a router — a class that dispatches incoming messages to the right handler functions. This is the foundation of any MCP server.