Curriculum/Tools & Function Calling
Model Context Protocol (MCP)
MCP is a standard way to list and call tools on other servers — not a new brain and not a permission system.
MCP (Model Context Protocol) is a way for a host (your agent app, an IDE) to talk to servers that expose:
- Tools — functions the model may call
- Resources — readable blobs (files, tickets)
- Prompts — named templates the host can insert
The calls look like JSON-RPC: tools/list, tools/call, resources/read. The point is decoupling. The people who know GitHub ship a server. Your agent does not lock to one Python framework. Your dispatcher still exists. MCP is USB for tools. USB will still shock you if you plug the wrong thing into production.
MCP is not:
- A smarter model
- A replacement for permissions
- A reason to attach 40 servers and 300 tools to every session
- A guarantee that arguments are safe
- A sandbox
The host is the product. The server is a catalog plus handlers. The loop is a client of the host. If the host forwards every listed tool blindly, you imported someone else’s allowlist.
MCP is USB for tools. It is not a permission system.
Host talks to serversResources are not tools
A resource is extra context (a schema file, a ticket). The model should not “call” it to mutate the world. Reading a resource is closer to a GET. Auth belongs on the server (the GitHub token lives there) and on the host (which user may enable that server). Resource bodies are untrusted text — injection — same as any observation. Pack them. Do not promote them into the system prompt as instructions.
Prompts in MCP are templates the host may insert. They are not automatically policy. You still pin your own spec.
Isolation on the wire
Production servers are separate processes. Stdio servers must not print debug noise on stdout — stdout is the protocol. Logging goes to stderr. That is an ops fact that becomes a security fact when a debug line breaks JSON-RPC and the host retries a write.
In this classroom we do not open a socket. We keep a registry dict. The discipline is the same: discover, call, fail closed. Unknown methods and unknown tools return an error object, not a best-effort handler.
Discovery is not permission
tools/list tells you what the server offers. It does not tell you what this session may run. The next lesson is host policy: filter before advertise, deny on call. Here, notice that list and call are different RPCs. A tool that appears in list can still be denied by the host. A tool that does not appear should never be called.
Credentials stay on the server. The model never gets the GitHub token. The host never logs it. The server should use a tight bot identity, not a personal admin token. Confused deputy still applies across the socket.
Classroom RPC
Two servers: files and tickets. List returns names. Call get_ticket returns the row. Call delete_prod is unknown. Unknown method is unknown. That is the part that matters. The wire format can change; fail-closed lookup does not.
Run to execute this in your browser. Nothing is sent to a server.
What printed: list shows four names across two servers. get_ticket returns the OOM row. delete_prod is unknown. explode is unknown method. Discover, call, fail closed. You still need the host filter in the next lesson — this server would happily expose everything it has.
What goes wrong
Attaching every MCP server a blog mentioned. Treating list as allowlist. Letting servers print on stdout. Putting admin tokens on the server “just for local.” Believing MCP validates JSON Schema for you. Schema validation is still your host or your server code. The spec does not refund money safely by existing.
How to test MCP-shaped dispatch
Fake rpc as we did. Assert unknown tool and unknown method. Assert arguments reach the handler. Assert list is generated from the same dict as call. Do not require a real socket in unit tests.
Transport is not a catalog you blindly import
MCP is useful because someone else ships GitHub tools. It is dangerous for the same reason. Each server is a credential, an injection surface, and a list of names you did not design. Install fewer than you want. Pin versions. Stdio servers must keep stdout clean: one JSON-RPC stream, logs on stderr. A debug print on stdout breaks the parser; a confused host that retries a write is an incident.
Resources are GETs of untrusted text. Pack them like observations. Do not splice a ticket body into the system prompt as instructions. Prompts the server offers are templates you may insert after review. They are not your policy. You still pin your spec.
Schema validation still happens in code you own. The spec does not refund safely by existing. Unknown methods and unknown tools fail closed. List and call are different RPCs: appearing in list is not permission. The next lesson is the host filter. This lesson is the discipline on the wire: discover, call, fail closed, keep tokens on the server, log server plus tool name.
Do not attach forty servers to every session. The loop’s assembler will drown, and one of those servers will grow delete_repo in a minor tag. Curiosity is not an allowlist. The host’s job in the next lesson is to treat list as an untrusted catalog: interesting, not permitted. Until that filter exists, even this classroom RPC is too generous — it will run every name it has. That is why MCP without host policy is incomplete.
How agents use this
Install fewer servers than your curiosity wants. Each server is another injection surface and another credential. The loop should see filtered tools, not raw list. Pin server versions. Log server name plus tool name on every call. MCP standardizes how tools are listed and called. Your host still allowlists and audits.
Note:Isolation only helps if the host does not forward every tool blindly.
Check your understanding