Module 3 of 10, lesson 2 of 5
Going deeper
Tools and MCP
A schema is a prompt
The model reads your tool definition to decide when and how to call it. The name, the description and each parameter description work like instructions, and improving them often helps more than rewriting the system prompt.
{
"name": "refund_order",
"description": "Refund a delivered order. Use only after checking the policy.",
"input_schema": {
"type": "object",
"properties": {
"order_id": { "type": "string", "description": "Format: ORD-1234" },
"amount": { "type": "number", "exclusiveMinimum": 0 }
},
"required": ["order_id", "amount"]
}
}
A schema can’t express everything. It won’t tell you that order 4512 belongs to this customer, or that the amount fits the order. Those checks belong in your code, on the server side. Even when a provider enforces the schema, treat the arguments as untrusted input.
Several calls in one turn
A model may ask for several tools at once. All results then go back in the next message, each one linked to its request by id. Running them in parallel saves time when they only read. When they write, order matters, and the safe choice is to run them one after another or to disable parallel calls.
Errors are for the model
“500 Internal Server Error” teaches a model nothing. “Order ORD-99 not found. Ids look like ORD-1234” lets it recover by itself. Return failures as tool results marked as errors, and keep your stack traces in your logs.
Retries pay twice
A network timeout doesn’t tell you whether the action happened. So the retry may repeat it. An idempotency key solves this: the caller generates one key per intended action, and the server returns the first result if it sees that key again. Have your code create the key, not the model, because a model can produce a fresh key on each attempt and defeat the purpose.
MCP under the hood
MCP is JSON-RPC 2.0 over a transport: stdio for a local process, Streamable HTTP for a remote server. A session starts with an initialize exchange, in which each side declares what it supports. A server can then expose three primitives: tools the model calls, resources the application reads, and prompts the user picks.
→ {"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"get_order","arguments":{"id":4512}}}
← {"jsonrpc":"2.0","id":2,
"result":{"content":[{"type":"text","text":"{\"total\":89}"}]}}
Note the two levels of failure. A protocol problem, such as an unknown method, is a JSON-RPC error. A tool that fails is a normal result with isError: true, so that the model can read it.