Two Protocols, Three Translations: How a Host Bridges MCP and an LLM Provider API A developer has published a walkthrough of how an MCP Host bridges the Model Context Protocol and an LLM provider API, tracing a single tool call through 20 steps across four phases. The account frames the architecture as "two protocols, three translations, one complete tool-call lifecycle," showing how the Host discovers tools from an MCP server, converts them into the provider's tool-definition format, translates the model's tool decision back into an MCP request, and converts the result for the model's final answer. Following one question through the MCP stack — from the user's prompt to the final answer Modern AI applications increasingly sit between two different worlds: MCP Model Context Protocol and an LLM provider API . MCP provides a standardized way for an AI application to discover and invoke external tools. The LLM provider API provides the interface through which the application sends prompts, tool definitions, and tool results to the model. The important part is that these two systems do not necessarily speak the same format. The Host sits between them. The Host discovers tools from an MCP Server, translates those tools into the format expected by the LLM provider, receives the LLM's tool decision, translates that decision back into an MCP request, executes the tool, and finally translates the result back into the provider's format so the LLM can produce a natural-language response. This creates a useful mental model: Two protocols. Three translations. One complete tool-call lifecycle. In this article, we will follow one simple question through the entire process: "What is 5 plus 7?" Our MCP Server exposes one tool: php add numbers a: number, b: number - number We will follow the request through 20 steps , divided into four phases: Before looking at the 20 steps, we need to understand the components involved. The architecture looks approximately like this: ┌──────────────────┐ │ USER │ │ "What is 5 + 7?" │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ HOST │ │ │ │ Agent / │ │ Orchestrator │ └────────┬─────────┘ │ MCP / JSON-RPC │ ▼ ┌──────────────────┐ │ CLIENT │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ SERVER │ │ │ │ Tool Registry │ │ │ │ add numbers │ └──────────────────┘ ┌──────────────────┐ │ HOST │ └────────┬─────────┘ │ Provider API │ ▼ ┌──────────────────┐ │ LLM │ │ │ │ Tool selection │ │ Argument creation│ └──────────────────┘ The most important thing to understand is that there are two protocol boundaries. The first is the MCP side . The MCP Client and MCP Server communicate using MCP messages based on JSON-RPC. For example: { "method": "tools/list", "id": 1 } Or: { "method": "tools/call", "params": { "name": "add numbers", "arguments": { "a": 5, "b": 7 } }, "id": 2 } The second side is the LLM provider API . The Host does not simply send those MCP JSON-RPC messages directly to the LLM. Instead, the Host translates the MCP tool definition into the format expected by the provider. For example, conceptually: { "name": "add numbers", "description": "Adds two numbers", "parameters": { "type": "object", "properties": { "a": { "type": "number" }, "b": { "type": "number" } }, "required": "a", "b" } } The LLM can then respond with a provider-specific tool or function call such as: { "function call": { "name": "add numbers", "arguments": "{\"a\":5,\"b\":7}" } } The Host then translates this back into an MCP tools/call request. That is the bridge. The first phase answers one fundamental question: What tools are available? At this point, the LLM has not yet been asked to solve the user's question. The Host first needs to discover what tools are available from the MCP Server. The user enters: What is 5 plus 7? The Host receives this prompt. At this moment, the Host knows what the user wants, but it may not yet know what tools are available on the connected MCP Server. The Host's orchestration layer starts the agent process. Conceptually, we could imagine something like: await agent.run "What is 5 plus 7?" ; The Host now needs to discover the tools that are available. It asks the MCP Client to perform tool discovery. The MCP Client sends an MCP request to the Server. Conceptually: { "method": "tools/list", "id": 1 } This is an MCP request using JSON-RPC. The important point is that this message is part of the MCP communication between the Client and Server. The LLM is not involved yet. The MCP Server receives the tools/list request. The server maintains a tool registry. Conceptually, the registry might contain: { name: "add numbers", description: "Adds two numbers", inputSchema: { ... }, handler: ... } The registry contains both the public definition of the tool and the executable handler. However, the Server does not send the implementation of the handler to the Client. Instead, it exposes the public contract: name description input schema { "name": "add numbers", "description": "Adds two numbers", "inputSchema": { "type": "object", "properties": { "a": { "type": "number" }, "b": { "type": "number" } }, "required": "a", "b" } } The Server is effectively saying: "I have a tool called add numbers . It accepts two numbers: a and b ." The Server responds to the Client. { "id": 1, "result": { "tools": { "name": "add numbers", "description": "Adds two numbers", "inputSchema": { "type": "object", "properties": { "a": { "type": "number" }, "b": { "type": "number" } }, "required": "a", "b" } } } } Notice what was returned. The Server returned: name description schema It did not return: the implementation of the function The handler remains on the Server. The MCP Client receives the response. It passes the discovered tools back to the Host. The Host now knows: Tool: add numbers Arguments: a: number b: number The discovery phase is complete. The Host can now tell the LLM what tools are available. This is where one of the most important architectural concepts appears. The Host has an MCP tool definition. But the LLM provider does not necessarily understand MCP's tool representation. Therefore, the Host must perform a translation. The Host converts the MCP tool schema into the tool/function schema expected by the LLM provider. MCP Tool Schema | | Translation 1 v LLM Provider Tool Schema The MCP tool might look conceptually like: { "name": "add numbers", "description": "Adds two numbers", "inputSchema": { "type": "object", "properties": { "a": { "type": "number" }, "b": { "type": "number" } }, "required": "a", "b" } } The Host transforms it into something the provider understands: { "name": "add numbers", "description": "Adds two numbers", "parameters": { "type": "object", "properties": { "a": { "type": "number" }, "b": { "type": "number" } }, "required": "a", "b" } } The exact field names depend on the provider. The important concept is the translation. The Host is acting as an adapter between MCP and the provider API. The Host now has two pieces of information: User prompt: "What is 5 plus 7?" Available tool: add numbers The Host sends both to the LLM provider. { "messages": { "role": "user", "content": "What is 5 plus 7?" } , "tools": { "name": "add numbers", "description": "Adds two numbers", "parameters": { "type": "object", "properties": { "a": { "type": "number" }, "b": { "type": "number" } }, "required": "a", "b" } } } This is now a provider API request . It is no longer the MCP tools/list request. This distinction is critical. The Host has crossed from: MCP into: LLM Provider API The LLM receives the user question and the available tool. It reasons that the add numbers tool is appropriate. It generates the arguments: { "a": 5, "b": 7 } This is an important distinction. The Server provided the schema. The LLM generated the actual values. The Server effectively said: a must be a number b must be a number The LLM generates: a = 5 b = 7 The model has therefore created new structured data that matches the schema it was given. The provider returns something conceptually like: { "function call": { "name": "add numbers", "arguments": "{\"a\":5,\"b\":7}" } } An extremely important point is that the LLM has not executed the function . It has only requested that the Host execute it. The LLM is effectively saying: "I believe the add numbers tool should be called with a = 5 and b = 7 ." The Host is still responsible for the actual invocation. Now the Host has the LLM's tool decision. But the decision is expressed in the provider's format. The Host needs to turn it back into an MCP request. The Host performs the second translation. It takes the provider's tool call: { "name": "add numbers", "arguments": "{\"a\":5,\"b\":7}" } and converts it into an MCP tools/call request: { "method": "tools/call", "params": { "name": "add numbers", "arguments": { "a": 5, "b": 7 } }, "id": 2 } The Host has now crossed the protocol boundary again: LLM Provider API | | Translation 2 v MCP / JSON-RPC The MCP Client sends the new request to the Server: { "method": "tools/call", "params": { "name": "add numbers", "arguments": { "a": 5, "b": 7 } }, "id": 2 } Notice that this is a new JSON-RPC request. The discovery request used: id: 1 The tool invocation uses: id: 2 The two requests are separate operations. The Server receives the request. It reads: params.name which contains: add numbers The Server looks up that name in its registry. js const tool = registry.get "add numbers" ; The Server now has access to the tool definition and its handler. Before executing anything, the Server validates the arguments. The arguments are: { "a": 5, "b": 7 } The Server validates them against the tool's schema. z.object { a: z.number , b: z.number } ; The validation succeeds: a = 5 ✓ number b = 7 ✓ number This step is extremely important. The LLM generated the arguments. But the MCP Server should still validate them. The model should not be treated as a trusted source of executable input. The schema acts as a contract between the tool definition and the actual execution. Only now does the actual computation happen. The Server executes: 5 + 7 = 12 The handler might look like: async function addNumbers args: { a: number; b: number; } { return args.a + args.b; } The Server then packages the result into an MCP-compatible result: { "content": { "type": "text", "text": "Result: 12" } } This is the first point in the lifecycle where the actual tool computation happens. Everything before this point was discovery, translation, decision-making, validation, or orchestration. The Server sends the result back to the Client: { "id": 2, "result": { "content": { "type": "text", "text": "Result: 12" } } } This is again MCP/JSON-RPC. The result has not yet been given back to the LLM. The Client receives it first. The tool has now executed successfully. The result is: Result: 12 But the user did not ask to see raw tool output. The user asked a natural-language question. The Host therefore needs the LLM to turn the structured tool result into a final response. The MCP Client receives: Result: 12 and passes the result back to the Host. The Host now has the output of the MCP tool. The Host performs the third and final translation. It converts: MCP Tool Result LLM Provider Tool Result MCP Result | | Translation 3 v Provider Tool Output { "role": "tool", "name": "add numbers", "content": "Result: 12" } The exact structure depends on the LLM provider. The important concept is that the Host again acts as the bridge. The LLM now receives the conversation context together with the tool result. User: What is 5 plus 7? Tool: Result: 12 The LLM can now generate a natural-language response: 5 plus 7 is 12. The tool provided the computation. The LLM provides the conversational response. Finally, the Host returns: 5 plus 7 is 12. The user sees: 5 plus 7 is 12. The complete lifecycle is finished. The easiest way to understand the entire architecture is to focus on the three translations. The Host converts: MCP Tool Schema ↓ LLM Provider Tool Schema This allows the LLM to understand which tools are available and what arguments they accept. LLM Provider Tool Call ↓ MCP tools/call This takes the LLM's decision and turns it into an actual MCP tool invocation. MCP Tool Result ↓ LLM Provider Tool Result This allows the LLM to see the result of the tool execution and formulate the final response. We can now compress the entire architecture into one diagram: USER | | "What is 5 + 7?" v HOST | | Start discovery v CLIENT | | tools/list v SERVER | | Read tool registry | Return tool definitions v CLIENT | v HOST | | Translation 1 | MCP schema - Provider schema v LLM | | Decide: | add numbers a=5,b=7 v HOST | | Translation 2 | Provider call - MCP tools/call v CLIENT | | tools/call v SERVER | | Find tool | Validate arguments | Execute handler | | 5 + 7 = 12 v CLIENT | v HOST | | Translation 3 | MCP result - Provider result v LLM | | "5 plus 7 is 12." v HOST | v USER Here is the entire process in one place. 1. User The user asks: "What is 5 plus 7?" 2. Host The Host starts the agent and initiates tool discovery. 3. Client The MCP Client sends: { "method": "tools/list", "id": 1 } 4. Server The MCP Server reads its tool registry and prepares the available tool definitions. 5. Server The Server responds: { "id": 1, "result": { "tools": { "name": "add numbers", "description": "Adds two numbers", "inputSchema": {} } } } 6. Client The Client passes the tool definitions back to the Host. 7. Host — Translation 1 MCP tool schema ↓ Provider tool schema 8. Host The Host sends the user's prompt and the translated tools to the LLM provider. 9. LLM The LLM determines that add numbers is appropriate and generates: { "a": 5, "b": 7 } 10. LLM The LLM returns its provider-specific tool/function call. 11. Host — Translation 2 The Host converts the provider tool call into an MCP tools/call . 12. Client The Client sends: { "method": "tools/call", "params": { "name": "add numbers", "arguments": { "a": 5, "b": 7 } }, "id": 2 } 13. Server The Server looks up add numbers in its registry. 14. Server The Server validates: a = 5 ✓ b = 7 ✓ 15. Server The Server executes the handler: 5 + 7 = 12 16. Server The Server returns the MCP result. 17. Client The Client receives the MCP result and passes it to the Host. 18. Host — Translation 3 MCP result ↓ Provider tool result and sends it to the LLM. 19. LLM "5 plus 7 is 12." 20. User 5 plus 7 is 12. The following simplified TypeScript code demonstrates the main architectural boundaries. js import { z } from "zod"; // ========================================== // 1. TOOL DEFINITION // ========================================== const addNumbersSchema = z.object { a: z.number , b: z.number , } ; type AddNumbersArgs = z.infer