MCP: Building a Tool Server

The Model Context Protocol is the standard way to give agents access to tools and data. Build a server in Python, connect it to a coding agent and to your own harness, write tool descriptions a model will use correctly, and evaluate whether it does.

Every agent needs tools, and until recently every framework described tools differently, so every integration was written once per framework. The Model Context Protocol (MCP) is the open standard that ended that. A server exposes tools, resources, and prompts; any compatible client (a chat app, an IDE, a coding agent, your harness) discovers and uses them. Every major provider supports it. For a student, building an MCP server is one of the most visible portfolio projects available: it is concrete, it plugs into tools recruiters use, and it shows you understand how agents connect to the world.

The Pieces

Roles. The host is the application (Claude Code, a chat client, your agent). It runs clients, each connected to one server. Servers are small processes, usually one per system: your database, your ticketing tool, your file system.

Primitives.

  • Tools: functions the model can call, each with a JSON schema for arguments.
  • Resources: readable data addressed by URI (notes://2026-09-01). Loaded into context by the host, not called by the model.
  • Prompts: reusable templates the server offers, shown to users as commands.

Transport. Stdio (the host launches the server as a subprocess) for local tools; HTTP for remote or shared servers. Same protocol either way.

Build One

We will wrap a small personal knowledge base: a folder of Markdown notes. Two tools, one resource.

python
# notes_server.py from pathlib import Path from mcp.server.fastmcp import FastMCP NOTES = Path("~/notes").expanduser() mcp = FastMCP("notes") @mcp.tool() def search_notes(query: str, limit: int = 5) -> list[dict]: """Find notes whose text contains the query (case-insensitive). Use this first to locate relevant notes before reading one in full. Returns up to `limit` items, each with `path`, `title`, and a one-line `snippet`. """ hits = [] for p in sorted(NOTES.glob("*.md")): text = p.read_text() if query.lower() in text.lower(): line = next((l for l in text.splitlines() if query.lower() in l.lower()), "") hits.append({"path": p.name, "title": text.splitlines()[0].lstrip("# "), "snippet": line[:160]}) if len(hits) >= min(limit, 20): break return hits @mcp.tool() def append_note(path: str, text: str) -> dict: """Append a paragraph to an existing note. This modifies a file. Only call after reading the note. `path` must be a file name returned by search_notes. Returns the new byte length of the file. """ target = NOTES / Path(path).name # no directory traversal if not target.exists(): raise ValueError(f"no such note: {path}") with target.open("a") as f: f.write("\n\n" + text.strip() + "\n") return {"path": target.name, "bytes": target.stat().st_size} @mcp.resource("notes://{path}") def read_note(path: str) -> str: """Full text of one note.""" return (NOTES / Path(path).name).read_text() if __name__ == "__main__": mcp.run()

Connect it to a coding agent:

bash
claude mcp add notes -- python /full/path/notes_server.py

Now "find my notes about gradient descent and add a paragraph summarizing what I got wrong last week" works, with the agent's harness deciding whether append_note needs your approval (it should, because it has a side effect).

Test it without an agent using the MCP inspector, a small web UI that connects to your server and lets you call tools by hand. Every tool should be exercised there before a model touches it.

Tool Descriptions Are the Interface

The model chooses tools from their names, descriptions, and schemas. Nothing else. Most "the model used my tool wrong" bugs are description bugs.

  • Name says what; description says when. "Use this first to locate relevant notes" is the sentence the model plans with.
  • Describe the return shape. The model plans the next step from what it expects back.
  • Constrain in the schema and repeat in prose. Types and enums stop bad values; the sentence stops the model from trying them.
  • Flag side effects. "This modifies a file" makes the model cautious and lets the harness gate it.
  • Return less. Summaries with identifiers, detail on demand through a resource or a second tool.
  • Keep it small. Five good tools beat twenty terse ones. Split large surfaces into servers per task.

Use It From Your Own Harness

Your harness from the previous module is a host. List the server's tools, convert schemas to your model's format, filter by the task's allowlist, and dispatch calls.

python
from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client params = StdioServerParameters(command="python", args=["notes_server.py"]) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = (await session.list_tools()).tools visible = [to_model_schema(t) for t in tools if t.name in allowed] result = await session.call_tool("search_notes", {"query": "gradient", "limit": 3})

MCP standardizes discovery and calling. It does not do permissions, budgets, or approvals. Those stay in your harness. And a server you did not write is untrusted code: its descriptions are text the model reads, and a malicious server can inject through them. Treat third-party servers like third-party packages.

The Other Protocols, Briefly

MCP connects an agent to tools. A2A connects agents to each other: each publishes an agent card, and another agent can discover it and delegate a task. You need it when two independently owned agents must cooperate; inside one project, a function call to a subagent is simpler. There are also protocols for streaming agent state to a UI and for agent payments. Learn MCP well; know the others exist.

Common Mistakes and Bad Instincts

  • Terse descriptions. "Search notes." gives the model nothing.
  • Directory traversal and friends. Tools are an attack surface; validate inputs.
  • Returning entire files from a search tool. Summaries with paths.
  • Skipping the inspector. Test tools by hand first.
  • Trusting third-party servers. Review them.
  • Thinking MCP is the harness. It is the plug.

Where to Go Next

  • context-and-harness-engineering: the policy layer MCP leaves to you
  • agent-evals-and-ai-security: measure tool selection, and treat descriptions as an injection surface
  • model-serving-apis-inference-and-performance-tradeoffs: serving the model this server talks to

What to Practice Next

Build an MCP server for a system you actually use: a folder of files, a personal database, a public API. At least three tools (one with a side effect) and one resource. Test every tool in the inspector, connect it to a coding agent, and then write a 15-task eval: for each task, which tools should be called in what order. Run it, fix failures by improving descriptions before anything else, and publish the repository with the eval results in the README.

Module 27 of 35 · College Student to ML/AI Engineer

Related Posts

More posts

Agentic Coding: Working With Claude Code, Codex, and Cursor

Coding agents are now the default way software gets written. Learn the gather-act-verify loop, how to write CLAUDE.md and AGENTS.md files that actually steer an agent, when to use skills and subagents, and how to review agent output like a senior engineer.

#coding-agents#agents#agent-engineering#python

Context Engineering: Designing What the Model Sees

The context window is a budget, and everything competes for it: the system prompt, the tool list, retrieved documents, memory, and the conversation so far. Learn to design the context deliberately, scope tools per task, compact without losing what matters, and treat cache hit rate as the metric it has become.

#context-engineering#agent-engineering#prompt-caching#agent-memory#rag#llm

Harness Engineering: The Runtime Around the Model

Agent = model + harness. The harness is the deterministic runtime that validates, authorizes, executes, and logs every action the model proposes. Learn its five layers, build one from scratch, and adopt the loop that turns every agent failure into a permanent fix.

#harness-engineering#agent-engineering#agents#durable-execution#guardrails#system-design