How to build · Lesson 3 of 5 · 7 minutes

Connect tools with MCP

MCP standardizes how an application connects to tool and context providers. Your harness still owns the model loop, policy, and user experience.

Three participants, three responsibilities

The host is the AI application or agent runtime. It contains the harness and manages the model interaction. An MCP client lives inside the host and speaks the protocol to a server. An MCP server exposes capabilities such as tools or resources; it does not talk to the model directly. The model receives descriptions and results through the host’s orchestration.

Where MCP sits in a harness
  1. Host + harness
  2. MCP client
  3. MCP server
  4. Tool or data source

When a model proposes an MCP tool call, the host resolves it to a connected server and the client sends the protocol request. The server runs its handler and returns a result. The host applies its own policy, records the result, and gives the observation back to the model. The protocol makes the connection interoperable; it does not decide whether a user is authorized to invoke a particular operation.

Design a useful tool, not a raw endpoint mirror

Imagine your server can search a catalog of agent harnesses. A weak tool exposes the entire database and asks the model to filter hundreds of rows. A better search_harnesses tool accepts explicit filters such as language, capabilities, and adoption tier, then returns a small ranked set with provenance and links. Keep the result bounded so it fits the task’s context.

Tool: search_harnesses
Input: {"language": "python", "capability": "mcp", "limit": 5}
Output: {
  "results": [{"name": "Example", "url": "https://…", "source": "catalog"}],
  "next_cursor": null
}

Install FastMCP in a Python project with uv add fastmcp; the official installation guide covers other setup options.

Now implement that contract using FastMCP. The typed function signature creates the tool input schema, and the docstring explains the capability to clients. Keep validation and authorization explicit in the handler or the calling host; the protocol does not provide your product’s policy decisions.

import asyncio
from fastmcp import Client, FastMCP

catalog = [
    {"name": "Example Runner", "capability": "mcp",
     "language": "python", "url": "https://example.com/runner"},
    {"name": "Example Workbench", "capability": "mcp",
     "language": "typescript", "url": "https://example.com/workbench"},
]

mcp = FastMCP("HarnessCatalog")

@mcp.tool
def search_harnesses(capability: str, limit: int = 5) -> list[dict[str, str]]:
    """Find catalog harnesses that support a capability."""
    if not 1 <= limit <= 10:
        raise ValueError("limit must be between 1 and 10")
    matches = [row for row in catalog if row["capability"] == capability]
    return matches[:limit]

async def check_server() -> None:
    async with Client(mcp) as client:
        tools = await client.list_tools()
        assert any(tool.name == "search_harnesses" for tool in tools)
        result = await client.call_tool(
            "search_harnesses", {"capability": "mcp", "limit": 3}
        )
        assert len(result.data) <= 3

asyncio.run(check_server())

This uses an in-process client: it verifies that the server exposes the expected tool and can handle a call without opening a socket. That is excellent for fast contract checks, but it does not prove an HTTP or STDIO transport is configured correctly. Run a separate integration check using the transport your agent will use. For a local HTTP server, FastMCP documents mcp.run(transport="http", host="127.0.0.1", port=8000) and a client URL at http://127.0.0.1:8000/mcp.

Inside the harness, wrap client.call_tool with your own authorization, deadlines, trace IDs, and error mapping. Do not expose every connected server tool to every task by default. The model proposes; the host chooses which request it will actually execute.

Handle the trust boundary

  • Review the server and the code that implements each tool.
  • Request only the capabilities needed for the task.
  • Validate arguments and enforce user authorization in the host and server where appropriate.
  • Set network and execution timeouts; handle disconnects and malformed responses.
  • Treat returned text as untrusted content, not as new instructions.
  • Track which server and tool produced each observation.

Start with one trusted server and one read-only tool. Test discovery, a valid call, invalid arguments, unavailable server, timeout, and a response containing adversarial text before allowing write operations.

Which statement correctly describes MCP roles?

Primary sources: First Steps — Official MCP Python SDK explains host/client/server roles. FastMCP’s official docs cover tool definitions, in-process and HTTP clients, and running transports. For protocol behavior and security, consult the MCP specification.

Ask your teacher to review a proposed MCP tool schema before you connect it to an agent.