# Errors, Validation and Recoverable Failures — MCP & Agent-to-Agent Protocols

Source: https://www.geekswithgeeks.com/en/mcp-a2a/b-errors

> Distinguish protocol errors from tool errors and help the model recover.

## Two kinds of failure

There are two places things go wrong. **Protocol errors** are JSON-RPC errors such as an unknown method (-32601) or malformed request, handled by the SDK. **Tool execution errors** happen inside a valid call (bad arguments, a downstream service failing); they should be reported in the tool **result** with `isError: true` and a **clear message** so the model can read it and correct course ("order_id must be exactly 6 digits") instead of the whole run failing. Validate input with the schema **and** in code; never trust model-supplied arguments, especially for file paths, SQL, shell commands and URLs. Do not leak stack traces, secrets or internal hostnames in error text.

## Both failure kinds on the real server, run

I ran this against the real server shown earlier, using the official `mcp` Python SDK 2.2.0 in a virtual environment; the client side is raw JSON-RPC over stdio with no SDK. Save the server as `server.py` in the same folder. SDK names and defaults change between versions, so check the current documentation. An invalid id returns a normal result whose text is a clear error message the model can act on. A missing argument fails schema validation and comes back with `isError` true. An unknown method produces a protocol error with code -32601.

```python
import json, subprocess, sys

proc = subprocess.Popen([sys.executable, "server.py"], stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)

def send(msg):
    proc.stdin.write(json.dumps(msg) + "\n"); proc.stdin.flush()

def call(id_, method, params=None):
    send({"jsonrpc": "2.0", "id": id_, "method": method, **({"params": params} if params is not None else {})})
    return json.loads(proc.stdout.readline())

init = call(1, "initialize", {"protocolVersion": "2025-06-18", "capabilities": {},
                              "clientInfo": {"name": "demo-client", "version": "0.1"}})
print("server:", init["result"]["serverInfo"]["name"], "| protocol:", init["result"]["protocolVersion"])
print("capabilities:", sorted(init["result"]["capabilities"]))
send({"jsonrpc": "2.0", "method": "notifications/initialized"})     # a notification: no id, no reply

tools = call(2, "tools/list")["result"]["tools"]
print("tools:", [t["name"] for t in tools])
print("input schema:", json.dumps(tools[0]["inputSchema"]))

ok = call(3, "tools/call", {"name": "get_order_status", "arguments": {"order_id": "481516"}})
print("call ok     :", ok["result"]["content"][0]["text"], "| isError:", ok["result"].get("isError"))
bad = call(4, "tools/call", {"name": "get_order_status", "arguments": {"order_id": "12"}})
print("call bad id :", bad["result"]["content"][0]["text"])
missing = call(5, "tools/call", {"name": "get_order_status", "arguments": {}})
print("missing arg :", "isError" , missing["result"].get("isError"), "|", missing["result"]["content"][0]["text"][:60].replace(chr(10), " "))
unknown = call(6, "no/such/method")
print("unknown     :", unknown["error"]["code"], unknown["error"]["message"])

res = call(7, "resources/list")["result"]["resources"]
print("resources:", [r["uri"] for r in res])
print("read:", call(8, "resources/read", {"uri": "policy://refunds"})["result"]["contents"][0]["text"])
pr = call(9, "prompts/get", {"name": "triage", "arguments": {"ticket": "I was charged twice"}})
print("prompt:", pr["result"]["messages"][0]["content"]["text"])
proc.stdin.close(); proc.wait()

```

Output:

```
server: demo-orders | protocol: 2025-06-18
capabilities: ['experimental', 'prompts', 'resources', 'tools']
tools: ['get_order_status']
input schema: {"properties": {"order_id": {"title": "Order Id", "type": "string"}}, "required": ["order_id"], "type": "object", "title": "get_order_statusArguments"}
call ok     : {
  "order_id": "481516",
  "status": "shipped"
} | isError: False
call bad id : {
  "error": "order_id must be exactly 6 digits"
}
missing arg : isError True | Error executing tool get_order_status: 1 validation error fo
unknown     : -32601 Method not found
resources: ['policy://refunds']
read: Refunds above 5000 rupees need approval.
prompt: Classify this ticket as billing, technical or other: I was charged twice
```

**Quiz:** How should a tool report bad arguments to help the model?

- [ ] By crashing the server
- [x] With a clear, specific message in the tool result
- [ ] With a full stack trace
- [ ] By returning nothing

*Answer:* With a clear, specific message in the tool result. Actionable messages let the model self-correct on the next step.
