Walk through the full tools/list → tools/call flow.
Quick Answer
1) After initialization, the client sends tools/list (optionally paginated with a cursor); the server returns every available tool's name, description, and inputSchema. 2) The host reshapes these into the LLM's function-calling format and includes them in a request to the model. 3) The model, given the conversation, decides to invoke one and returns a structured call with a tool name and arguments. 4) The host sends tools/call with {name, arguments} to the server. 5) The server validates the arguments, executes the underlying logic, and returns a CallToolResult containing a content array (text/image/audio/embedded resource) and an isError flag. 6) The host feeds that result back into the conversation as the tool's output, and the model continues generating its response.
Detailed Answer
Tracing one full round trip end to end makes the pieces click together.
Step by step
// 1. Client -> Server: discover tools
{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}
// Server -> Client
{"jsonrpc": "2.0", "id": 1, "result": {
"tools": [
{"name": "get_weather", "description": "...", "inputSchema": {...}}
]
}}
// 2-3. Host sends tools + conversation to the LLM's own API.
// The model returns something like: "call get_weather(city='Austin')"
// 4. Host -> Server: invoke it
{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {
"name": "get_weather", "arguments": {"city": "Austin"}
}}
// 5. Server -> Host
{"jsonrpc": "2.0", "id": 2, "result": {
"content": [{"type": "text", "text": "Austin: 91F, sunny"}],
"isError": false
}}
// 6. Host appends "Austin: 91F, sunny" as the tool result in the conversation,
// and the model produces its final natural-language answer using it.
Where the host does real work
Steps 2-3 and 6 happen entirely inside the host/client, not over MCP at all. MCP only governs steps 1, 4, and 5. This is why the same MCP server works unmodified whether the host uses Anthropic's tool-use format, OpenAI's function-calling format, or something else. Reshaping into a model-specific format is the host's job, not the server's.
Where things typically go wrong
- Forgetting that
tools/listmay be paginated (anextCursorin the result), and only fetching the first page. - Not handling
isError: truedistinctly from a JSON-RPC-level error. A tool that fails at the business-logic level (e.g. "city not found") should return a normal result withisError: true, not a protocol error.