Module 7 of 26 · Open Source Track
MCP
& Rich Output

The Model Context Protocol is USB-C for agents: wrap a capability once as a server, and any agent can plug in. Six concepts — from the N×M problem to bridging MCP tools into a local Mistral agent and returning a rendered chart.

Track 2: Tool Use ⏱ ~18 min read 7 / 26
Concept 1 of 6

N×M adapters collapse to N+M plugs

You have N agents and M capabilities (a database, GitHub, a filesystem, web search). Wiring them by hand is N×M bespoke adapters — and each new capability means re-integrating it into every agent.

MCP fixes this by agreeing on one plug. A capability is wrapped once as an MCP server; any agent that speaks MCP (a client) connects with no custom code. Adding a capability is now +1 server every agent can use; adding an agent is +1 client that reaches every server. N×M becomes N+M.

Concept 1 of 6

The cable drawer before USB-C

BEFORE: Every device had its own proprietary cable — camera, phone, printer, each a different port. A drawer full of incompatible cables was the proof.

PAIN: Every device maker had to support every computer, and vice versa — a multiplication table of adapters. Add one new gadget and you needed a new cable for every machine. None of it was reusable.

MAPPING: USB-C fixed it with one connector: any device with the port works with any computer with the port. MCP is USB-C for agents — one standard plug, and the tangle becomes a hub.

Concept 1 of 6

Wrap once, plug in everywhere

  1. Wrap the capability onceA database or folder becomes an MCP server that speaks the standard protocol — written by whoever owns it.
  2. Any agent plugs inAn MCP-speaking client connects to the server with no integration code on the agent side.
  3. +1 server reaches all agentsPublish a server (Postgres, Slack, web search) once and every MCP host on earth gains it for free.
  4. +1 client reaches all serversA new agent that speaks MCP instantly reaches every server already published.
Concept 1 of 6

Hand-wired tool or MCP server?

# Question: how should I add this capability?

IF only ONE agent will ever use it
   AND it's trivial / throwaway:
  USE a hand-wired local tool (M05/M06)

ELIF multiple agents need it
     OR a community server already exists
     OR you'll reuse it across projects:
  USE an MCP server      # +1, reaches all

ELSE:
  PREFER MCP
  # integration becomes a config line, not code

Hundreds of community MCP servers already exist — reaching one is config, not a coding project.

Concept 1 of 6

Misconceptions

"MCP is an Anthropic-only thing."
MCP is an open standard. The same server plugs into Claude Desktop, your local Mistral agent, or any MCP host. That's exactly what makes it useful for open-source agents.
"MCP replaces tool calling."
It standardizes how tools are packaged and discovered. The model still calls tools the same way — MCP just turns each capability into a reusable plug-in instead of bespoke code.

MCP turns "integrate this capability" from a coding project into a config line. Wrap once as a server; every agent plugs in.

The arithmetic — N+M instead of N×M — is what unlocks a whole ecosystem of reusable servers.

Concept 2 of 6

Host, client, server — and three primitives

The host is your app (the program driving Mistral). It spins up one client per server, so a single agent can reach a filesystem server, a database server, and a search server at once.

A server exposes its capability through three primitives: Resources (read-only data addressed by a URI), Tools (actions the model invokes with typed arguments), and Prompts (reusable templates the user picks). Knowing which to use is most of designing a good server.

Concept 2 of 6

A restaurant kitchen

BEFORE: You want a meal but can't go into the kitchen yourself — you don't know where anything is or how the equipment works.

PAIN: If every diner walked in and operated the stoves directly, it'd be chaos and nobody could specialize. You need a clean interface to the kitchen's capability.

MAPPING: The host is you at the table; the server is the kitchen. The menu you read is a resource (data, no side effects). Placing an order is a tool (an action with arguments). A set lunch combo is a prompt (a reusable template you pick by name).

Concept 2 of 6

The three primitives

  1. Resource — data the app readsRead-only, addressed by a URI like filing://f-101. The app decides when to pull it into context. No side effects.
  2. Tool — an action the model callsTyped arguments, may have side effects, the model chooses to invoke it during reasoning (e.g. search_filings(query)).
  3. Prompt — a reusable templateA parameterized prompt the user selects (like a slash command), e.g. /summarize-filing.
  4. One host, many clientsThe host opens a client per server, so one agent reaches several capabilities at once.
Concept 2 of 6

Resource or Tool?

# The line is DATA vs ACTION

IF the model must DECIDE to invoke it
   WITH arguments (may have side effects):
  → it's a TOOL
  # "search filings mentioning bankruptcy"

ELIF it's read-only data the app
     pulls into context by id/URI:
  → it's a RESOURCE
  # "the full text of filing 123"

ELIF it's a reusable prompt the
     USER selects by name:
  → it's a PROMPT

Open models support tool calling natively; resources are surfaced by the host reading them into the prompt.

Concept 2 of 6

Misconceptions

"Resources and tools are interchangeable."
Data vs action. A resource is read-only and app-controlled; a tool is an action the model chooses to invoke with arguments. If the model needs to decide to call it, it's a tool.
"My Mistral agent gets resources and prompts for free."
Open models only speak tool calling. Your host maps MCP tools to tool_calls and surfaces resources by reading them and injecting the text itself.

One host runs many clients; each server offers Resources, Tools, and Prompts. Data vs action vs template.

For open-source hosts, tools map straight onto tool_calls; resources you read and inject yourself.

Concept 3 of 6

One message format, two pipes

Every MCP message is JSON-RPC 2.0 — a tiny request/response format with a method (tools/call), params, and a matching id. That never changes.

What changes is the pipe the JSON flows through. stdio launches the server as a subprocess and talks over its stdin/stdout — the default for local dev. HTTP+SSE reaches a remote or shared server over the network. Same conversation, different transport.

Concept 3 of 6

Same letter, different delivery

BEFORE: You need to send a precise, structured message — the contents matter exactly.

PAIN: If the wording changed depending on how you delivered it, nothing downstream could rely on it. The message must be identical whether hand-carried or mailed.

MAPPING: JSON-RPC is the letter — always the same format. stdio is hand-carrying it to someone in the same building (local subprocess); HTTP+SSE is posting it across town (remote service). The envelope changes; the letter doesn't.

Concept 3 of 6

Discover, then invoke

  1. Client asks tools/listIt requests the server's available tools.
  2. Server answersIt returns the tools and their JSON-Schema argument shapes.
  3. Client calls tools/callIt invokes one tool by name with arguments.
  4. Server returns the resultAll four messages are JSON-RPC over the chosen transport. A framework like FastMCP hides every byte.
Concept 3 of 6

stdio or HTTP+SSE?

# Pick the transport by WHERE the server runs

IF server runs locally AND the host can
   launch it as a subprocess:
  USE stdio          # default for dev
  # talks over the process stdin/stdout

ELIF server is remote, shared, or
     serves many users:
  USE HTTP + SSE      # over the network

# Either way the messages are JSON-RPC 2.0
Concept 3 of 6

Misconceptions

"stdio is only for tiny toy servers."
stdio is the standard local transport — filesystem, database, and most dev servers use it. The host just launches the server as a subprocess.
"Different transports use different message formats."
No — both carry the same JSON-RPC 2.0 messages. Only the pipe differs (subprocess pipe vs HTTP stream).

JSON-RPC is the format; stdio or HTTP+SSE is the pipe. The interaction is always discover (tools/list) then invoke (tools/call).

Use stdio locally, HTTP+SSE for remote — a framework hides the wire details.

Concept 4 of 6

Decorate a function, get a tool

With FastMCP (the official Python mcp package) you write ordinary functions and decorate them: @mcp.tool() makes a callable tool, @mcp.resource(...) makes a URI-addressed resource. You never touch JSON-RPC.

The magic is the type hints: query: str, limit: int = 5 automatically becomes the tool's argument schema, and the docstring becomes its description. mcp.run() serves it over stdio, ready for a host to launch.

Concept 4 of 6

Labeling shelves for a stockroom

BEFORE: You have useful functions, but no one outside your code knows they exist or how to call them.

PAIN: Hand-writing a JSON-Schema description for each one — arguments, types, required fields — is tedious and drifts out of sync with the real function the moment you change a parameter.

MAPPING: The decorator is a shelf label printed straight from the contents. Your type hints are the schema, your docstring is the description — so the catalog can never disagree with what's actually on the shelf.

Concept 4 of 6

Functions in, protocol out

  1. Create the servermcp = FastMCP("ucc-tools") — one object holds everything.
  2. Decorate tools & resources@mcp.tool() on an action, @mcp.resource("filing://{name}") on read-only data.
  3. Type hints become the schemaThis is the only documentation the model gets — precise hints and docstrings drive correct tool calls.
  4. Run over stdiomcp.run() serves on stdin/stdout. Web search is just another @mcp.tool() — that's how an offline agent reaches today's web.
Concept 4 of 6

Pseudocode

CREATE server = FastMCP("ucc-tools")

# a TOOL: type hints -> schema, docstring -> description
@tool
DEFINE search_filings(query: str, limit=5):
  """Search filings for a keyword."""
  RETURN rows matching query

# web search is just another tool (#12)
@tool
DEFINE web_search(query: str): ...

# a RESOURCE: read-only, addressed by URI
@resource("filing://{name}")
DEFINE read_filing(name): RETURN text of name

server.run()        # serve over stdio
Concept 4 of 6

Misconceptions

"I still have to write JSON-Schema for each tool."
FastMCP infers the schema from your type hints. Vague hints, though, produce bad tool calls — the hints and docstrings are the model's documentation.
"An offline model can't reach the web."
You don't need a model with a built-in browser — expose search as a tool. An AI-native search API returns clean snippets the agent can use; self-host SearXNG to stay fully offline.

Write typed functions; decorators turn them into protocol-correct tools and resources. Type hints become the schema, docstrings the description.

The same file serves over stdio for dev or HTTP later — the tool code doesn't change.

Concept 5 of 6

The adapter Claude Desktop hides

Your Mistral agent can't talk to an MCP server directly — the OpenAI chat API has no idea what MCP is. The bridge is the adapter that connects them.

It does three jobs: connect to the server and list its tools, translate each MCP tool's schema into the OpenAI tool_calls format Mistral expects, and run the normal tool-calling loop — but dispatch each call to the MCP server instead of a local function.

Concept 5 of 6

A travel plug adapter

BEFORE: Your laptop charger (Mistral, speaking tool_calls) works perfectly — at home.

PAIN: Abroad, the wall socket (the MCP server) has a different shape. The electricity is the same; the plug isn't. Nothing connects.

MAPPING: The bridge is the travel adapter. It doesn't change the laptop or the socket — it just reshapes the connection so they fit. MCP tool schemas go in one side as OpenAI tools; tool calls come back out and get routed to the server.

Concept 5 of 6

List, translate, dispatch

  1. Connect & listLaunch the server, open a session, call list_tools() to discover what it offers.
  2. Translate the schemaEach MCP tool's inputSchema is already JSON-Schema — drop it into OpenAI's {"type":"function",...} envelope.
  3. Run the M06 loopCall Mistral with the tools; if it wants none, return; otherwise execute the calls.
  4. Dispatch to MCPInstead of a local function, call session.call_tool(name, args) and append the result as a role:"tool" message keyed by tool_call_id.
Concept 5 of 6

Pseudocode

CONNECT session to mcp server (stdio)
tools = session.list_tools()

# translate: MCP schema -> OpenAI tool
openai_tools = [
  { type: "function", function: {
     name: t.name, description: t.desc,
     parameters: t.inputSchema } }      # already JSON-Schema
  FOR t IN tools ]

FOR step IN bounded loop:
  reply = ASK Mistral(messages, tools=openai_tools)
  IF no tool_calls: RETURN reply.text
  FOR call IN reply.tool_calls:
    result = session.call_tool(call.name, call.args)
    append { role: "tool",
             tool_call_id: call.id, content: result }
Concept 5 of 6

Misconceptions

"Mistral speaks MCP, so no translation is needed."
Open models speak OpenAI tool_calls, not MCP. The bridge translates the schema and routes calls via session.call_tool — that adapter is the whole point.
"The reply role doesn't matter."
It does: the tool result must be a role:"tool" message carrying the matching tool_call_id. Get the id wrong and the model loses track of which result answers which call — the most common bridge bug.

The bridge maps MCP tool schemas to tool_calls and dispatches calls back to the server. It reuses the M06 loop — only the dispatch target changes.

Point it at a community server (e.g. GitHub) and your agent gains that capability with zero new code.

Concept 6 of 6

A tool returns a chart, not a sentence

An MCP tool result can be an image content block, not only text. So a tool can render a chart and hand it back as the answer. "Plot filings by month" ends with an actual picture, not a description.

The model doesn't draw pixels. It decides when and with what data to call the chart tool; deterministic code (matplotlib) renders the image. Orchestration from the model, drawing from the code.

Concept 6 of 6

Use the printer, don't draw freehand

BEFORE: You ask someone to "show the trend." They could sketch a chart freehand from memory.

PAIN: A freehand sketch — a small model emitting SVG or ASCII art — is wonky, inconsistent, and burns tokens getting the axes wrong. The drawing depends on the artist's steadiness, which a 7B model doesn't have.

MAPPING: Hand the numbers to a printer. The model supplies data and intent; matplotlib prints a correct chart every time. The agent decides what to chart; the tool draws it right.

Concept 6 of 6

Data in, image out

  1. Define a render toolmake_bar_chart(labels, values, title) — its job is to draw, not decide.
  2. The agent gathers dataIt calls search_filings to count things, then reasons it needs a visual.
  3. Deterministic code rendersmatplotlib (headless Agg backend) draws a PNG into memory — correct and consistent.
  4. Return an image artifactThe tool returns an MCP Image (the bytes), not a file path — the host may not share your filesystem.
Concept 6 of 6

Pseudocode

@tool
DEFINE make_bar_chart(labels[], values[], title):
  fig = DRAW BAR CHART(labels, values)  # matplotlib, Agg
  png = render(fig) TO memory buffer
  RETURN Image(png, format="png")   # NOT a file path

# the agent orchestrates:
data = call search_filings(...)        # gather
img  = call make_bar_chart(            # render
         labels=[...], values=[...])
RETURN img   # a chart, generated by a local model's agent
Concept 6 of 6

Misconceptions

"Just ask the model to emit SVG or ASCII art."
Unreliable and token-hungry, especially for a small model. The pixels should never depend on the model's drawing ability — let deterministic code produce them.
"Return the chart as a file path."
The host may not share your filesystem. Return the image bytes as an MCP Image block so the host can render it inline, wherever it runs.

Orchestrate, don't hallucinate. The model supplies structured data and intent; a deterministic tool renders the image and returns it as an artifact.

That separation is why even a small local model can ship polished visual output.

Test the core insight

One question per concept. Tap to reveal the answer.

Open the full module on desktop

The desktop version builds a real FastMCP server (filing search + web search + a chart tool), the MCP→Ollama bridge, and runs it all against a local Mistral — runnable Python and Node, with a lab that wires a filesystem + chart server into one talking agent.

Open M07 on Desktop → FastMCP server · the bridge · rich output · ~65 min

Module 7 of 26 · Open Source Track · Tool Use