MCP Tool Integration#

The run_shell / read_file / write_file from CH02 are native tools — both the tool definition and the implementation are written by you, and every new capability has to be built from scratch. This chapter introduces MCP: a standard protocol that plugs externally written community tools into the same dispatch flow alongside native tools, solving the tool-extension problem.


3.1 Why MCP Is Needed#

Pain Point: Hand-Written Tools Don’t Scale#

Native tools are one-offs. If you also want to:

  • Connect to the GitHub API
  • Operate Slack
  • Query a database
  • Run a scraping framework

Each one has to be implemented, maintained, and tested from scratch. The same tool has to be rewritten by every agent; tools you write can’t be reused by anyone else.

MCP Is a USB Standard for Tools#

MCP (Model Context Protocol) is a tool standard pushed by Anthropic. Think of it as “USB for LLMs”:

  • Anyone who writes an MCP server produces a plug-and-play toolset
  • Any MCP-aware client (Claude Desktop, Claude Code, your own agent) can plug right in
  • Tool definitions, execution, and return formats are all standardized

Community-written MCP servers already cover fetch, filesystem, github, slack, postgres, puppeteer, various cloud APIs… When you need a new capability, check whether the community has an MCP server first; if yes, plug it in directly; if not, write your own (also as an MCP server, so others can use it too).

Where MCP Servers Run: Local vs Remote#

The MCP spec supports two transports, which determine which machine the server runs on:

TransportWhere the server runsCommunicationTypical examples
stdioLocal subprocessstdin/stdout JSON-RPCmcp_server_fetch, mcp_server_filesystem
HTTP / SSERemote cloud serviceHTTPS streamable connectionNotion, Linear, Cloudflare, various SaaS-hosted MCP servers

From the agent’s point of view there is no difference between the two — only the transport differs; tool discovery, tool calls, and return formats are identical. Our minimal-agent uses stdio to connect to a local mcp_server_fetch, which is what the architecture diagram in 3.2 below depicts; to switch to a remote MCP server, only the transport section of the agent code needs to change.

Native vs MCP: How to Choose#

DimensionNative toolsMCP tools
Locationin-process (same Python process)Separate subprocess
CommunicationDirect function callstdio JSON-RPC
LatencyMicrosecondsMilliseconds
ExtensionEdit code and restartSwap the server, agent stays untouched
SharingTied to this agentShared across multiple clients
EcosystemMaintain it yourselfUse existing community servers
Best forHigh-frequency, private logicThird-party integrations, cross-client sharing

Bottom line: core, private, high-frequency → native; general, third-party, shareable → MCP. Our minimal agent runs both side by side.


3.2 The Role of MCP in the Agent#

Workflow After Integrating MCP Tools#

%%{init: {'sequence': {'noteAlign': 'left'}}}%%
sequenceDiagram
    participant MCP as MCP Server (subprocess)
    box AI Agent
        participant Executor
    end
    participant Model
    Executor->>MCP: (1) Start server (subprocess)
    Executor->>MCP: (2) handshake
    MCP-->>Executor: server info
    Executor->>MCP: (3) list_tools (what tools do you have?)
    MCP-->>Executor: (4) [fetch(url, max_length, ...)]
    Note over Executor: (5) Got mcp tools<br/>(6) Merge with native tools into all_tools
    loop Agent loop (until Model returns end_turn)
        Executor->>Model: (7) messages list + all_tools
        Model-->>Executor: (8) tool_use(name, args)
        alt name in mcp_tool_names -> go MCP
            Executor->>MCP: (9) call_tool(name, args)
            MCP-->>Executor: (10) tool_result
        else go native
            Note over Executor: (9) Call native tools<br/>(10) Get tool_result
        end
        Note over Executor: (11) Append tool_result to messages list, back to (7)
    end

Three key points:

  • MCP tools are discovered dynamically (steps 1-4) — At startup, the Executor asks the MCP server once via list_tools: “what tools do you have?”, and gets back the MCP tool list. You don’t need to hard-code what the server provides; swap in a different MCP server and the agent automatically gains different capabilities, without changing a single line of code.
  • Merged with native tools into a single tool list (steps 5-6) — Once the MCP tools are retrieved, they’re merged with native tools into all_tools and handed to the Model. This is completely transparent to the Model — what it sees is a single unified tool list; it has no idea which ones are native and which are MCP.
  • Dispatch routing (step 9) — The Executor checks whether the name in tool_use is in mcp_tool_names to decide whether to route to MCP or native. The “Dispatch Routing Pattern” section below covers the implementation.

Dispatch Routing Pattern#

When the Model returns a tool_use, the executor needs to decide whether this tool is an MCP tool or a native tool, in order to choose between executing via the MCP server or directly calling a Python function itself.

Here is an example implementation that puts every MCP tool name into a set as the basis for the decision:

1
2
3
4
5
6
7
8
mcp_tool_names = {"fetch"}                    # Built at startup

if tool_name in mcp_tool_names:
    # Go MCP (async call_tool over stdio)
    result = await mcp_session.call_tool(tool_name, args)
else:
    # Go native (call the Python function directly)
    result = native_tools[tool_name](**args)

This if/else is the branching node at step 9 in the workflow diagram — the tool_name the Model returns comes in, the executor glances at the set, and automatically takes the right path.


3.3 Try It#

$ python minimal_agent.py
[init] native tools: ['run_shell', 'read_file', 'write_file']
[init] mcp tools:    ['fetch']

you> Fetch the contents of example.com and summarize
  [mcp] fetch({'url': 'https://example.com'})
claude> This page is the IANA-reserved example domain ...

you> List the current directory
  [native] run_shell({'command': 'ls'})
claude> The directory contains minimal_agent.py, README.md ...

The two kinds of tools coexist, and the Model doesn’t need to know which is which — it only reads the description to decide who to call.


Recap#

By this point you should understand:

  • What MCP solves — reusability of the tool ecosystem, sharing across clients
  • The native vs MCP decision logic — high-frequency private vs general integration
  • Dynamic tool discovery — handshake at startup to fetch the list, no need to hard-code
  • Dispatch routing — use a set to separate the two kinds of tools, transparent to the Model

But whether native or MCP, so far the agent is still one-shot — one conversation runs and it’s done. The next chapter, CH04, upgrades it to a multi-turn conversation using a REPL.


References#