Lesson 5 of 8Intermediate·13 min read

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 tooltypes.Tool(function_declarations=[{name, description, parameters}]) or pass a Python function{name, description, input_schema}{type: "function", name, description, parameters}
Model asks to callpart.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 backtypes.Part.from_function_response(name, response){type: "tool_result", tool_use_id, content}{type: "function_call_output", call_id, output}
Force / restrict toolstool_config.function_calling_config.mode = ANY / NONE + allowed_function_namestool_choice = {type: "tool", name} / "any" / "none"tool_choice = "required" / {type: "function", name} / "none"
Parallel callsMultiple function_call parts in one responseMultiple tool_use blocksMultiple function_call items
Auto-execute Python functionsYes — automatic function calling in the SDKVia tool_runner / Agent SDKVia @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.

Connecting the same remote MCP server. Claude and OpenAI connect server-side; Gemini connects from your process.
python
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

  1. Walk me through a tool-call round trip. What does the model send, what do you send back, and why do IDs matter?
  2. What does MCP standardise and what does it leave to the client? Where does authentication live?
  3. A tool is being called with wrong arguments 20% of the time. How do you diagnose and fix it?
  4. Why should a tool return an error as data rather than raise an exception?