Tools and MCP Across Providers: One Server, Three Clients
Function calling looks different in each SDK but is the same protocol underneath. Learn the request/response shapes side by side, then connect a single MCP server to Gemini, Claude and OpenAI.
After this lesson you can
- ✓Translate a tool definition and a tool-call round-trip between the three SDKs from memory
- ✓Connect one MCP server to all three providers
- ✓Explain what MCP standardises and what it deliberately leaves to you
Function calling is one idea with three spellings
Every provider does the same dance: you send tool definitions (name, description, JSON-schema parameters); the model returns a structured call (name + arguments + an ID); you execute it and send the result back tagged with that ID; the model continues. The differences are purely in field names and message shapes, and knowing them cold is a fast way to signal fluency.
| Gemini (google-genai) | Claude (Messages API) | OpenAI (Responses API) | |
|---|---|---|---|
| Define a tool | types.Tool(function_declarations=[{name, description, parameters}]) or pass a Python function | {name, description, input_schema} | {type: "function", name, description, parameters} |
| Model asks to call | part.function_call → {name, args} | content block type "tool_use" → {id, name, input} | output item type "function_call" → {call_id, name, arguments (JSON string)} |
| Send result back | types.Part.from_function_response(name, response) | {type: "tool_result", tool_use_id, content} | {type: "function_call_output", call_id, output} |
| Force / restrict tools | tool_config.function_calling_config.mode = ANY / NONE + allowed_function_names | tool_choice = {type: "tool", name} / "any" / "none" | tool_choice = "required" / {type: "function", name} / "none" |
| Parallel calls | Multiple function_call parts in one response | Multiple tool_use blocks | Multiple function_call items |
| Auto-execute Python functions | Yes — automatic function calling in the SDK | Via tool_runner / Agent SDK | Via @function_tool in Agents SDK |
What MCP standardises
The Model Context Protocol turns "write a Slack connector for every framework" into "write one Slack MCP server". A server exposes tools, resources (readable context addressed by URI) and prompts over stdio or streamable HTTP; any MCP client — Claude Desktop, Gemini CLI, Codex, your own agent — connects, lists what is offered, and mediates the model’s access. It converts an N×M integration matrix into N+M.
What MCP deliberately does not standardise: which model you use, how the agent loop runs, or how you authorise the model to call a given tool. Those stay with you, and interviewers want to hear that you know where the protocol ends. Authentication (OAuth for remote servers), tool-level permissions, and result-size limits are your responsibility as the client.
One MCP server, three clients
Below, a remote MCP server (say, your team’s orders service) is attached to each provider. Claude and OpenAI can connect their API directly to a remote MCP server — the provider’s infrastructure calls the server for you. With Gemini you hold the MCP session in your code and pass it to the SDK as a tool; the SDK translates the server’s tool list into function declarations and routes calls back through the session.
from anthropic import Anthropic
client = Anthropic()
resp = client.beta.messages.create(
model="claude-sonnet-4-5",
max_tokens=1000,
# The MCP connector: Anthropic's servers connect to your MCP server,
# expose its tools to the model, and execute the calls for you.
mcp_servers=[{
"type": "url",
"url": "https://mcp.example.com/orders",
"name": "orders",
"authorization_token": "…", # OAuth token if the server needs one
"tool_configuration": {"allowed_tools": ["list_orders", "order_status"]},
}],
betas=["mcp-client-2025-04-04"],
messages=[{"role": "user", "content": "How many orders shipped late last week?"}],
)
print(next(b.text for b in resp.content if b.type == "text"))
# Claude Code / Agent SDK: declare servers in .mcp.json (project) or via
# ClaudeAgentOptions(mcp_servers={...}); tools appear as mcp__<server>__<tool>.Designing tools models actually call correctly
Provider differences are trivia; tool design is the skill. The description is a prompt: say what the tool does, when to use it instead of its siblings, and what it returns. Parameters should be few, named the way a human would name them, with enums wherever the set is closed. Return errors as data the model can act on ("order_id not found; valid IDs look like ORD-123456") rather than raising, because a raised exception ends the turn and loses the model’s chance to recover.
Two tools that overlap ("search_orders" and "find_orders") will be confused; merge them. A tool with twelve optional parameters will be called with the wrong three; split it. And every tool that has side effects needs an idempotency key, because agents retry.
- •Description = when to use + what it returns; test it by asking the model to explain when it would call the tool
- •Closed sets are enums; free text is a last resort
- •Errors are structured results with a hint, never exceptions
- •Side-effecting tools take an idempotency key and are the only tools a supervisor agent holds
- •Past ~30 tools, add a tool-search tool and load definitions on demand — the same progressive-disclosure idea as skills
Interview questions this lesson prepares you for
- Walk me through a tool-call round trip. What does the model send, what do you send back, and why do IDs matter?
- What does MCP standardise and what does it leave to the client? Where does authentication live?
- A tool is being called with wrong arguments 20% of the time. How do you diagnose and fix it?
- Why should a tool return an error as data rather than raise an exception?