MCP through a relay: what works, what never will, and how to test it in ten minutes
Ask the right question first
"Does your service support MCP?" is the most common question in selection groups and the one most often answered wrongly.
The reason is simple: MCP (Model Context Protocol) is a protocol between a client and a tool server. It never passes through the model API.
A single tool-enabled conversation actually involves three things:
- The client writes the list of available tools into the request's
toolsfield and sends it to the model API. - The model returns
tool_calls, saying which tool it wants and with what arguments. - The client itself executes that tool (a local subprocess, or a remote MCP server) and sends the result back as a message with
role: tool.
Both transports in the MCP spec, stdio and Streamable HTTP, describe step 3: how the client talks to the tool server. The model API plays no part in either.
So: a relay cannot support or not support MCP. It can only support or not support tool use. When a service says it "supports MCP", it usually means "our models return correctly formatted tool_calls" — which carries no information, because correct formatting is the passing grade, not a capability.
The three things that really block you
Translated into verifiable questions, "supporting MCP" comes down to three points.
One: multi-turn round trips
Tool calling is inherently multi-turn: the model calls, the client executes, the result goes back, the model continues. Many gateways only get the first turn right.
The typical failure: the second request carries a role: tool message, the gateway's protocol conversion layer does not recognise that role, and it either returns 400 or silently drops the message. The model then "forgets" it ever called a tool and starts inventing results.
This is most common on gateways that translate between Anthropic and OpenAI, because the two encode tool messages differently: OpenAI uses assistant.tool_calls plus separate role: tool messages, while Anthropic uses tool_use blocks inside assistant content and tool_result blocks inside user content. One missing tool_use_id mapping and the chain breaks right there.
Two: argument fragments under streaming
tool_calls[].function.arguments is a JSON string, and in a streaming response it arrives in fragments — you receive a series of arguments pieces that you have to join by index before parsing.
The problem: some gateways "helpfully" aggregate those fragments into one complete arguments string. That looks friendlier, but if the aggregation has a bug (for example, merging several parallel calls into one index), the client receives syntactically invalid JSON. The error is often just Expecting ',' delimiter, which has nothing to do with the network and is very hard to trace.
Test: make one tool call in streaming mode and print every chunk verbatim. You should see either "several delta fragments" or "one complete string" — never "half a string". The last case means the aggregator truncates.
Three: parallel tool calls
Ask "what is the temperature in Beijing and Shanghai today" and the model should return two tool_calls at once. That requires parallel_tool_calls support, plus the ability to pair both tool results back on the next turn.
Many gateways only process tool_calls[0]. The symptom: the model clearly wants two cities but you receive one call, or both results go back and only one is recognised.
A ten-minute test
No MCP server needed. Two fake tools are enough:
tools = [
{"type": "function", "function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]}}},
{"type": "function", "function": {
"name": "get_time",
"description": "Get current time for a city",
"parameters": {"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]}}}
]
Three rounds, one conclusion each:
| Round | What you send | What you look at | What failure means |
|---|---|---|---|
| 1 | "What is the weather in Beijing", with both tools | Is get_weather in tool_calls, and does arguments pass json.loads | No tool call means tool use is not wired up |
| 2 | Send the role: tool result back | Does the model answer from the result instead of calling again | 400 or a repeated call means multi-turn is broken |
| 3 | "Temperature in Beijing and Shanghai separately" | Two tool_calls returned at once | Only one means parallel calls are unsupported |
Pass all three and MCP will run fine. Fail any one and changing providers is faster than changing code.
Matching the error messages
These messages almost always mean the gateway is at fault, not your code:
tool_use ids were found without tool_result blocks— ids do not match on the way back, Anthropic style. Usually the gateway rewrote ids without keeping them in sync.messages.1.content: unexpected tool_use_id— the same in OpenAI style.Invalid schema for function— the gateway re-validatesparametersagainst a subset of JSON Schema. Remove$refandoneOfand retry; if it passes, the gateway is the problem.- Tool calls work perfectly but streaming reports
Expecting ',' delimiter— the argument fragment aggregation bug described above.
One boundary people miss
If your tools live on a remote MCP server (Streamable HTTP), the client has to connect to two places at once: the model API (the relay) and the MCP server (usually your own cloud host or a third-party service).
That means:
- A relay outage leaves the MCP server untouched, but the whole chain is still down;
- Conversely, an MCP server on your private network that is not publicly reachable can only use stdio mode (a local process), in which case there is no network protocol issue at all;
- The client machine's egress policy affects both connections, so troubleshooting must test them separately.
Verify the two connections separately: first confirm the model API returns valid tool_calls, then confirm the client can reach the MCP server. Tested together, you can never tell which side is at fault.
Summary
- MCP never passes through the model API, so "does the relay support MCP" is a false question. The real question is the three tool-use behaviours.
- In order of importance: multi-turn round trips, then argument fragments, then parallel calls.
- Two fake tools, three rounds, ten minutes — no real MCP setup required.
- If an error mentions
tool_use_id,tool_resultorExpecting ',' delimiter, suspect the gateway's protocol conversion layer first. - For remote MCP, the two connections must be verified separately or your troubleshooting direction will be wrong.
Was this useful? If you have run into something the article does not cover, tell us and we will verify it and update the text.
Related articles
Video generation behind a relay: three API shapes, four billing traps, one acceptance checklist
A relay selection decision tree: working back from your scenario to a site
Logging and reconciliation: attributing token spend to the project level
Migrating suppliers gradually: replacing an API site without interrupting service
Build your own probe: twenty lines of script to monitor your API suppliers
IP allowlists, sub-accounts and permission tiers: a survey of relay governance
Read on: a few of the services we list
This article does not point at a specific model family, so here are a few of the highest-scoring services we list, in case you want to read on. They are ordered by score and uptime and are not a recommendation.
See the full list in the directory, or browse by model family.