Course Build · Tools Tools-01

Build · Tools · Module 01

MCP from the API Side

After this, you understand why MCP exists, can read an MCP tool definition and know — for any new "I want Claude to do X" — whether the answer is a Skill, an inline tool, an MCP server or a hook.

The problem MCP solves

In API-02 you defined a tool inline. The tools=[...] list lived in the script. The executor was the function next to it. That works for one tool in one script. It stops working when you have ten tools that should be available to fifteen different applications and you don't want to copy-paste schemas around.

The Model Context Protocol is the standard answer. A tool (or a set of related tools, or a data source) lives in its own server — a small process that speaks MCP over stdio or HTTP. Any client that speaks MCP can connect, ask "what tools do you have?", read the schemas and call them. Claude Code is one such client. Claude.ai is another. Your API code can be another. The tool's author writes it once.

The mental model is the same as inline tools: schema, executor, loop. What changes is where each piece lives and who can reach it.

What an MCP server actually is

A program. Usually small. It exposes a few endpoints in the MCP protocol — list tools, list resources, call a tool — and that's about it. The Python and TypeScript SDKs from modelcontextprotocol handle the protocol; you write the tool logic and a couple of register-this-tool calls.

A minimal stdio server in Python looks like this:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("calculator-server")

@mcp.tool()
def calculate(expression: str) -> str:
    """Evaluate a simple arithmetic expression."""
    return str(eval(expression))  # still toy; see the warning below

if __name__ == "__main__":
    mcp.run()

Save as calc_server.py. That's a complete server. It speaks stdio MCP, exposes one tool called calculate with one string argument and returns a string. Anyone running it can connect a client and call it.

To use it from Claude Code, register it in your .mcp.json:

{
  "mcpServers": {
    "calculator": {
      "command": "python",
      "args": ["calc_server.py"]
    }
  }
}

Start a session. Claude Code launches the server, discovers the tool, and the calculate tool is available alongside the built-ins. To use it from the API, the SDK has a mcp parameter on messages.create that connects to a list of servers; the model sees the tools and calls them with the same tool_use shape from API-02.

The inline tool in API-02 and the MCP tool here do the same thing. The difference is where the schema lives, who else can reach it and whether you're rewriting it every time you start a new script.

The four primitives — when each fits

Once you've written one inline tool and one MCP tool, the next question is which extension primitive belongs in which situation. There are four. They're easy to confuse.

Skill. Markdown instructions the model reads when it decides the situation matches. A folder with a SKILL.md, a description in the frontmatter and a body of instructions the model follows once activated. The model proactively reaches for it. Skills inform; they don't enforce.

Inline tool. A JSON schema in your tools=[...] list with an executor function next to it. The model calls the tool, your code runs it, the loop continues. Lives inside one process; tied to your specific script.

MCP server. The same shape as an inline tool, but lifted into a standalone process that speaks the protocol. Any client can reach it. Reusable across applications. Auth, logging and rate-limiting are yours to add; nothing comes for free.

Hook. A side-effect run by the harness around model actions — PreToolUse, PostToolUse, SessionStart and friends. Hooks are deterministic. They fire whether or not the model "thinks" about them. Use them to enforce constraints the model could ignore (lint before commit, deny tool calls that touch a path, log every action).

Decision · Skill vs inline tool vs MCP vs hook

Need to extend Claude's capabilities?
├─ Reusable across sessions or applications? ──── Skill or MCP
│  ├─ Just instructions (no new actions)? ──────── Skill
│  └─ A real action — fetches data, runs code? ── MCP server
│
├─ Just this conversation, just this script? ── Inline tool
│  └─ Throwaway; rewrite next time
│
├─ Must run every time regardless of model? ── Hook
│  └─ Lint, deny, log, guardrail
│
└─ Combination of the above? ────────────────── Plugin (bundles them)

The tell that you've chosen wrong: a Skill that wishes the model would always pick it (should have been a hook), an inline tool you've copied into three scripts (should have been MCP), an MCP server that doesn't actually need to be discoverable by anything else (should have been an inline tool), a hook that fires on situations where the model could have read context and decided itself (should have been a Skill).

The minimum viable MCP round-trip

If you only do one thing in this module: write the four-line calculator server above, register it in .mcp.json, run claude in the same folder and ask it to do an arithmetic problem. Watch the round-trip in the session.

You should see Claude Code list a new tool called calculate, ask permission to call it, run it, receive the result and produce a final answer. The flow is the loop from API-02, with the schema and executor moved out of your script and into the server. That's it. That's the whole conceptual shift.

The eval warning, again

eval() in the server is the same disaster as in the inline version. The model can send any expression. The expression runs on your machine. In a tutorial you're fine; in anything real, parse the expression, validate the operators, refuse anything you didn't expect. Or use a real arithmetic library and never eval.

The rule generalises. Any MCP tool that takes a string and acts on the host should treat the string as untrusted input and validate before acting. The model is not adversarial; the input distribution still includes things you didn't plan for.

Next

Production-01 (Cache and Cost) covers the prompt-cache mechanics that make the loops you just learned cheap enough to run at volume. It's the highest-leverage standalone module in the Build axis — most early production cost is solved by knowing what to cache.