AI Foundations · MCP — Tools for Agents

MCP — Tools for Agents

An agent that can only produce text is just a chatbot. The Model Context Protocol is the open standard that gives agents hands — letting an assistant like GitHub Copilot, Anthropic’s Claude, or a Cursor session query your database, hit your APIs, read your files, and act on the outside world.

Learning objectives

Explain the host / client / server architecture; name the three server primitives and three client primitives and when to use each; choose between the stdio and Streamable HTTP transports; build a simple MCP server; connect one to a host such as Copilot or Claude; and reason about MCP’s security model.

☺ Explain it like I’m 10

MCP is like a universal plug (think USB) that lets your AI helper connect to any tool — a calculator, your files, a website — without needing a different cable for each one.

🐦🦫Your host for this topic: Pip the Hummingbird, with Benny the Beaver — Pip carries messages between the agent and your apps, and Benny builds the little server that plugs them in.

The problem MCP solves

☺ Like you’re 10: Imagine every gadget in your house needed its own weird plug shape. MCP is like agreeing everyone will use the same plug, so any helper can connect to any tool without a special cable each time.

Before MCP, every AI application needed custom code for every tool it wanted to use. With M apps and N tools, the industry faced M × N bespoke integrations to build and maintain — each inventing its own authentication, sandboxing, and data-handling, all inconsistent and error-prone. Worse, models couldn’t reliably discover what tools were available or how to call them without hand-rolled prompt engineering.

MCP — released by Anthropic in late 2024 and now natively supported across the major AI platforms — turns that M × N problem into M + N. Build a tool server once, and any MCP-compatible host can use it; build a host once, and it can use any server. The common analogy: MCP is the USB-C port for AI — one standard connector instead of a drawer full of adapters.

🎬 At the AI Academy
🦊

Foxy: Owl, why does every helper need its own special cable to reach my files and apps? That’s so many cables!

🦉

Professor Owl: That’s the whole problem, Foxy. MCP is one standard plug — a host with its model, a client inside it, and a little server — so any agent can reach any tool.

🐦

Pip the Hummingbird: And I do the flying! I carry each message from the agent over to the tool and zip the answer straight back.

🦫

Benny the Beaver: I build the server that Pip plugs into — one for your files, one for your database — each snapping in with the same connector.

🐢

Timmy the Turtle: And I only hand a server the keys it truly needs — least privilege — so a note-reading tool can’t go changing your database.

MCP is the wiring that turns a static language model into a living, tool-wielding agent. It is the foundation under almost everything in the rest of this course.

The architecture: three roles

☺ Like you’re 10: Think of ordering food at a restaurant: you (the host) tell a waiter (the client), and the waiter walks to exactly one kitchen (the server) to get what you asked for. You never shout into the kitchen yourself — the waiter always carries the message.

MCP is a client–server protocol with three distinct roles. Getting these straight is the whole game:

HOST — the LLM app (Copilot · Claude Desktop · Cursor · Claude Code) Model Client A1 per server Client B1 per server Client C1 per server JSON-RPC 2.0 · 1 client ⟷ 1 server Serverfiles Serverdatabase ServerGitHub ↓ filesystem↓ your database↓ GitHub API
⌁ Key fact

The server never talks to the model directly. All interaction is mediated by the client. The model decides it wants something; the client calls the server; the server executes and returns a result; the client hands the result back to the model. This indirection is what makes the security model tractable.

The protocol & the lifecycle

☺ Like you’re 10: When two friends start a walkie-talkie chat, they first say hello and check which channels they both have. MCP does the same handshake so the helper and the tool agree on what each can do before they start working.

Under the hood, MCP is JSON-RPC 2.0 over a duplex transport, with stateful sessions and capability negotiation. Every message is a UTF-8 JSON-RPC request, notification, or response. That’s the whole thing — the rest is detail.

A connection follows a simple handshake:

Client Server initialize — “I support X, Y…” capabilities — “I offer tools, resources…” notifications/initialized tools/list → tools/call → { result } resources/list → resources/read ready to use for the whole session

The initialize step negotiates which features each side supports, so a host always knows exactly what a server offers. Discovery is standardized: a host finds a server’s tools via tools/list and reads data via resources/read — no bespoke prompt engineering required. The spec is date-string versioned (e.g. 2025-11-25) and negotiated during initialize.

Server-side primitives: Tools, Resources, Prompts

☺ Like you’re 10: A server offers three kinds of things, like a toy box: buttons that actually do something (tools), a picture book you can only look at (resources), and a recipe card that tells you the steps (prompts).

An MCP server exposes capabilities through exactly three primitives. This lean taxonomy covers the vast majority of real use cases:

PrimitiveWhat it isControlled byRisk
ToolsExecutable operations with side effects — insert a DB row, send an email, hit an API. Each has a typed JSON schema for its parameters and return type.Model (the LLM decides to call it)Highest — they change the world
ResourcesRead-only access to data — file contents, DB records, a README. Retrieve context without changing anything. The backbone of RAG-style workflows.ApplicationLow — read-only
PromptsReusable, parameterized templates that standardize common interactions — e.g. a code-review prompt taking language and file_path.User (the human invokes it)Low — templates
Rule of thumb: resources query, tools act, prompts standardize. If your server only needs to give the model information, expose a resource. If it needs to do something, expose a tool. If you want to template how the model approaches a task, expose a prompt.

Client-side primitives: Sampling, Roots, Elicitation

☺ Like you’re 10: The tool can talk back too. It can borrow the helper’s brain to think (sampling), it must stay inside the fenced yard it’s allowed in (roots), and it can stop to ask you a question first, like “Are you sure?” (elicitation).

MCP is bidirectional — the client offers three primitives back to the server, which is what enables richer, human-in-the-loop workflows:

Together with dynamic updates (servers can notify the host when their capabilities change), these primitives make MCP a protocol for building modular, interactive, secure workflows — not just a static plugin format.

Transports: stdio vs. Streamable HTTP

☺ Like you’re 10: There are two ways to reach a tool: talking to a friend sitting right next to you (stdio, for tools on your own computer) or phoning a friend far away (Streamable HTTP, for tools online). Near ones are simple; far ones need a password to make sure it’s really you.

The spec defines two standard transport mechanisms (custom transports are allowed). Which one you use depends on whether the server runs locally or remotely:

stdio (the default)Streamable HTTP
Where the server runsLocally, as a subprocess of the clientRemotely, as a hosted service over HTTPS
How it worksClient spawns the server; JSON-RPC messages flow over stdin/stdout, newline-delimitedJSON-RPC over HTTP with streaming responses
AuthLocal process trustOAuth 2.1
Use whenA local tool — filesystem, a local DB, dev toolingA shared/production service used by many clients
⌁ Evolution note

Streamable HTTP replaced the older HTTP+SSE transport as the recommended remote transport in spec revision 2025-03-26. If you see older tutorials wiring up “SSE” servers, that’s the predecessor — prefer Streamable HTTP for new remote servers. The protocol was donated to the Linux Foundation’s Agentic AI Foundation in December 2025, signaling long-term stability.

The practical migration path: start local with stdio while developing, then go remote with Streamable HTTP + OAuth 2.1 when you’re ready for production and multiple users.

Building an MCP server

☺ Like you’re 10: Making your own tool is like snapping together LEGO bricks with labels on them. You write a short piece of code, put a clear label on each button so the helper knows what it does, and now anyone’s AI can press it.

Official SDKs exist for many languages — TypeScript, Python, Go, Kotlin, Java, C#, Swift, Rust, Ruby, PHP. The most popular Python framework is FastMCP, which makes a server a few decorators. Here’s a server exposing all three primitives:

from fastmcp import FastMCP

mcp = FastMCP("orders-service")

# 1 · A TOOL — an action with side effects. The model can call this.
@mcp.tool()
def create_ticket(order_id: str, issue: str) -> str:
    """Open a support ticket for an order. Use when a customer reports a problem."""
    ticket_id = db.insert_ticket(order_id, issue)   # real side effect
    return f"Created ticket {ticket_id}"

# 2 · A RESOURCE — read-only data the model can pull in as context.
@mcp.resource("order://{order_id}")
def order_details(order_id: str) -> dict:
    """Return status, ETA, and items for an order."""
    return db.get_order(order_id)                   # read-only

# 3 · A PROMPT — a reusable template that standardizes a task.
@mcp.prompt()
def triage(order_id: str) -> str:
    return f"Investigate order {order_id}. Check status, then summarize next steps."

if __name__ == "__main__":
    mcp.run()              # stdio by default; switch to HTTP for remote

The same in TypeScript, registering a single tool with a typed schema:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({ name: "orders-service", version: "1.0.0" });

server.tool(
  "create_ticket",
  { orderId: z.string(), issue: z.string() },     // JSON-schema params
  async ({ orderId, issue }) => {
    const id = await db.insertTicket(orderId, issue);
    return { content: [{ type: "text", text: `Created ticket ${id}` }] };
  }
);

// transport wired separately: stdio locally, Streamable HTTP for remote

Notice the tool’s description and schema do the work: the model receives a structured list of available tools with descriptions, and when it decides it needs one, it emits a tool-call request with the right arguments — the client executes it through the server and returns the result. Write tool descriptions the way you’d write a SKILL.md description: they are the trigger.

Connecting an MCP server to a host

☺ Like you’re 10: Once your tool is built, you just tell your AI helper its address, like adding a new contact to a phone. After that, the helper can call it whenever it needs — the same way no matter which helper you use.

Once a server exists, you declare it in configuration and its tools become available to your host — Copilot’s agent mode, CLI, cloud agent, and SDK, or the equivalent surfaces in Claude Code, Claude Desktop, Cursor, and others — wherever you’ve enabled it. The definition is the same everywhere; only the root key and the config file differ per client. Pick yours:

VS Code (GitHub Copilot) — file .vscode/mcp.json · root key servers
// .vscode/mcp.json  (workspace-level; or the "mcp.servers" block in settings.json)
{
  "servers": {
    "filesystem": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "${workspaceFolder}"]
    },
    "orders-service": {
      "type": "http",
      "url": "https://orders.internal.example.com/mcp"
    }
  }
}

VS Code / Copilot uniquely use the servers key. Every other client below uses mcpServers — pasting a mcpServers config into VS Code is the classic mistake.

Claude Desktop — file claude_desktop_config.json · root key mcpServers
// claude_desktop_config.json  (Settings → Developer → Edit Config)
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/project"]
    },
    "orders-service": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://orders.internal.example.com/mcp"]
    }
  }
}

Local stdio servers run as a subprocess; remote HTTP servers are bridged with the mcp-remote helper (or added as Connectors in the UI). True at time of writing.

Claude Code — file .mcp.json (project) or claude mcp add · root key mcpServers
// .mcp.json in the project root (shared with your team via git)
// or one command:  claude mcp add filesystem npx -y @modelcontextprotocol/server-filesystem .
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
    },
    "orders-service": {
      "type": "http",
      "url": "https://orders.internal.example.com/mcp"
    }
  }
}

Claude Code reads stdio and HTTP/SSE servers; scope them to the project (.mcp.json) or your user config.

Cursor — file .cursor/mcp.json (project) or ~/.cursor/mcp.json (global) · root key mcpServers
// .cursor/mcp.json (project) — or ~/.cursor/mcp.json for every project
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
    },
    "orders-service": {
      "url": "https://orders.internal.example.com/mcp"
    }
  }
}

Cursor infers the transport from the fields (a command means stdio; a url means remote). True at time of writing.

The first is a local stdio server (the client spawns it as a subprocess). The second is a remote HTTP server. Vendors also ship built-in servers you can drop in: GitHub offers a hosted GitHub MCP server — its tools (like list_workflow_runs) are what the example skill in Customizing Copilot relied on — and Anthropic and others publish reference servers (filesystem, fetch, and more) you wire up the same way.

From code, agent SDKs take MCP server configs directly — the Copilot SDK and Anthropic’s Claude SDK both accept a map of servers. Conceptually (shape varies by SDK — verify against the current README):

const session = await client.createSession({
  model: "claude-sonnet-4-6",
  mcpServers: {
    filesystem: { type: "stdio", command: "npx",
      args: ["-y", "@modelcontextprotocol/server-filesystem", "."] },
    docs: { type: "http", url: "https://docs.example.com/mcp" },
  },
});

MCP and the agent loop

☺ Like you’re 10: An agent works like a treasure hunt: think about what you need, do one thing to get it, look at what you got, then think again. MCP is the “do a thing and look at the result” part that keeps the hunt moving.

Here’s why MCP is foundational to everything agentic. Recall the agent loop from Agentic AI — reason, act, observe, repeat. MCP is the “act” and “observe.” When an agent reasons that it needs information or an action, it calls an MCP tool (act); the server executes and returns a result (observe); the agent reasons again. MCP is the standard plumbing for that loop:

Agent Client Server World tools/call JSON-RPC query rows result observe

The security model (read this twice)

☺ Like you’re 10: Giving a helper tools is like handing someone the keys to your house — handy, but you only give the keys you must, you ask before letting them do big things, and you don’t trust notes from strangers that say “now go do this.”

Tools are the most powerful and most security-sensitive primitive — they take real actions in the world. MCP defines a security model, but you are responsible for using it well:

⚠ The pattern to fear

The dangerous combination is an agent with (1) access to sensitive/private data, (2) the ability to take external actions, and (3) exposure to untrusted content all at the same time. Any one is fine; together they’re how a hidden instruction in a web page or file turns into exfiltrated data or an unwanted action. Audit your tool wiring with this in mind, and check agent systems against a framework like the OWASP Agentic Security Initiative (ASI) Top 10 before production.

Ecosystem & where it’s going

☺ Like you’re 10: Because everyone agreed on the same plug, tons of people are now building tools that all snap together, and there are plans to let AI helpers team up and even hand jobs to each other — like friends passing a ball on the same team.

The MCP ecosystem is growing fast: new servers, clients, and SDKs ship constantly, and adoption across all major AI platforms means MCP skills are broadly transferable. The donation to the Linux Foundation signals long-term governance stability. Looking ahead, the maturing pieces include stateless server operation, automatic discovery via server registries, and agent-to-agent (A2A) coordination — the subject of the next lesson, Agent Protocols, which positions MCP as the integration layer beneath the multi-agent systems you will meet later. The practical guidance for 2026: make MCP your default integration layer, and design security and governance in from day one.

🦫 Benny’s workshop · 45 min

Install an existing MCP server and connect it to a host of your choice to feel the loop, then build a tiny one. (a) Add the filesystem server config above to your host — VS Code with Copilot, Claude Desktop, or Claude Code all work — then in agent mode ask it to summarize the files in a folder and watch it call the server’s tools. (b) With Python, pip install fastmcp and write a two-tool server (one resource, one tool) for a toy domain you care about. Run it over stdio and connect it. You’ve now consumed and produced MCP.

🐢 Timmy’s checkpoint

(1) Name the three roles and the relationship between client and server. (2) Match each to a primitive: “read a file for context” / “send an email” / “a reusable code-review template.” (3) When do you choose stdio over Streamable HTTP? (4) In one sentence, how does MCP relate to the agent loop? (5) Describe the three-part combination that makes an agent dangerous.

Check your answers
  1. The three roles: Host (the LLM app the user interacts with — Copilot, Claude Desktop, Cursor, Claude Code — which manages the model and creates client sessions), Client (a connector living inside the host), and Server (an independent process exposing one domain’s capabilities). A client is responsible for exactly one server: the client-to-server relationship is 1:1.
  2. Question to primitive: “Read a file for context” is a Resource (read-only data). “Send an email” is a Tool (an executable action with side effects). “A reusable code-review template” is a Prompt (a parameterized template). Rule of thumb: resources query, tools act, prompts standardize.
  3. When to choose stdio: Choose stdio for a local tool that runs as a subprocess of the client — a filesystem, a local DB, or dev tooling — relying on local process trust. Streamable HTTP is for remote, hosted services used by many clients, where you need OAuth 2.1 over TLS.
  4. MCP and the agent loop: In the reason–act–observe loop, MCP is the standardized “act” and “observe” — the agent calls an MCP tool to act, and the server executes and returns a result for the agent to observe before it reasons again.
  5. The dangerous combination: An agent becomes dangerous when it simultaneously has (1) access to sensitive/private data, (2) the ability to take external actions, and (3) exposure to untrusted content. Any one alone is fine, but together they let a hidden instruction turn into exfiltrated data or an unwanted action.