# JSON-RPC 2.0 in Five Minutes — MCP & Agent-to-Agent Protocols

Source: https://www.geekswithgeeks.com/en/mcp-a2a/m-jsonrpc

> Read the three message shapes MCP is built on.

## Requests, responses and notifications

MCP messages are **JSON-RPC 2.0**. A **request** has `jsonrpc: "2.0"`, a `method`, optional `params` and an `id`, and expects a **response** with the same `id` containing either a `result` or an `error` (with a numeric `code` and a `message`). A **notification** has a method but **no id** and gets no reply. Standard error codes include **-32700** (parse error), **-32600** (invalid request), **-32601** (method not found), **-32602** (invalid params) and **-32603** (internal error). Because messages are plain JSON, MCP is easy to log, replay and test with ordinary tools.

## JSON-RPC underneath

MCP is JSON-RPC messages, a capability handshake and three server primitives.

![Five parts: messages, handshake, tools, resources, prompts.](assets/figures/mcp-a2a/section-2-map.svg) — Figure 2.1 — Messages, handshake, tools, resources and prompts.

## A mini JSON-RPC dispatcher, run

I ran this with plain Python 3 (standard library only). The function returns a result for a valid call, `None` for a notification (no id, no reply), and the standard error objects for an unknown method (-32601), bad params (-32602) and unparseable text (-32700).

```python
import json

def handle(raw, methods):
    """A tiny JSON-RPC 2.0 dispatcher showing request, notification and error shapes."""
    try:
        msg = json.loads(raw)
    except json.JSONDecodeError:
        return {"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "Parse error"}}
    if "id" not in msg:                                   # notification: do the work, send no reply
        methods.get(msg["method"], lambda p: None)(msg.get("params"))
        return None
    if msg["method"] not in methods:
        return {"jsonrpc": "2.0", "id": msg["id"], "error": {"code": -32601, "message": "Method not found"}}
    try:
        return {"jsonrpc": "2.0", "id": msg["id"], "result": methods[msg["method"]](msg.get("params"))}
    except (KeyError, TypeError):
        return {"jsonrpc": "2.0", "id": msg["id"], "error": {"code": -32602, "message": "Invalid params"}}

methods = {"add": lambda p: p["a"] + p["b"], "ping": lambda p: {}}
for raw in ('{"jsonrpc":"2.0","id":1,"method":"add","params":{"a":2,"b":3}}',
            '{"jsonrpc":"2.0","method":"ping"}',
            '{"jsonrpc":"2.0","id":2,"method":"mul","params":{}}',
            '{"jsonrpc":"2.0","id":3,"method":"add","params":{"a":2}}',
            '{not json'):
    print(handle(raw, methods))

```

Output:

```
{'jsonrpc': '2.0', 'id': 1, 'result': 5}
None
{'jsonrpc': '2.0', 'id': 2, 'error': {'code': -32601, 'message': 'Method not found'}}
{'jsonrpc': '2.0', 'id': 3, 'error': {'code': -32602, 'message': 'Invalid params'}}
{'jsonrpc': '2.0', 'id': None, 'error': {'code': -32700, 'message': 'Parse error'}}
```

**Quiz:** What marks a JSON-RPC message as a notification?

- [ ] It has a result field
- [x] It has no id and expects no reply
- [ ] It uses XML
- [ ] It has error code -32601

*Answer:* It has no id and expects no reply. Only requests carry an id that a response must echo.
