& 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.
The 6 concepts of MCP
Tap any concept to jump to its 5-card cluster.
- 1The N×M ProblemWhy MCP exists — USB-C for agents
- 2MCP ArchitectureClient, server, and three primitives
- 3Transports & JSON-RPCstdio vs HTTP+SSE
- 4Build an MCP ServerFastMCP: functions become tools
- 5The MCP→Ollama BridgeMCP tools as OpenAI
tool_calls - 6Rich & Visual OutputA tool returns a chart, not text
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.
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.
Wrap once, plug in everywhere
- Wrap the capability onceA database or folder becomes an MCP server that speaks the standard protocol — written by whoever owns it.
- Any agent plugs inAn MCP-speaking client connects to the server with no integration code on the agent side.
- +1 server reaches all agentsPublish a server (Postgres, Slack, web search) once and every MCP host on earth gains it for free.
- +1 client reaches all serversA new agent that speaks MCP instantly reaches every server already published.
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.
Misconceptions
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.
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.
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).
The three primitives
- 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. - Tool — an action the model callsTyped arguments, may have side effects, the model chooses to invoke it during reasoning (e.g.
search_filings(query)). - Prompt — a reusable templateA parameterized prompt the user selects (like a slash command), e.g.
/summarize-filing. - One host, many clientsThe host opens a client per server, so one agent reaches several capabilities at once.
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.
Misconceptions
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.
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.
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.
Discover, then invoke
- Client asks
tools/listIt requests the server's available tools. - Server answersIt returns the tools and their JSON-Schema argument shapes.
- Client calls
tools/callIt invokes one tool by name with arguments. - Server returns the resultAll four messages are JSON-RPC over the chosen transport. A framework like FastMCP hides every byte.
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
Misconceptions
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.
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.
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.
Functions in, protocol out
- Create the server
mcp = FastMCP("ucc-tools")— one object holds everything. - Decorate tools & resources
@mcp.tool()on an action,@mcp.resource("filing://{name}")on read-only data. - Type hints become the schemaThis is the only documentation the model gets — precise hints and docstrings drive correct tool calls.
- Run over stdio
mcp.run()serves on stdin/stdout. Web search is just another@mcp.tool()— that's how an offline agent reaches today's web.
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
Misconceptions
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.
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.
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.
List, translate, dispatch
- Connect & listLaunch the server, open a session, call
list_tools()to discover what it offers. - Translate the schemaEach MCP tool's
inputSchemais already JSON-Schema — drop it into OpenAI's{"type":"function",...}envelope. - Run the M06 loopCall Mistral with the tools; if it wants none, return; otherwise execute the calls.
- Dispatch to MCPInstead of a local function, call
session.call_tool(name, args)and append the result as arole:"tool"message keyed bytool_call_id.
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 }
Misconceptions
tool_calls, not MCP. The bridge translates the schema and routes calls via session.call_tool — that adapter is the whole point.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.
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.
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.
Data in, image out
- Define a render tool
make_bar_chart(labels, values, title)— its job is to draw, not decide. - The agent gathers dataIt calls
search_filingsto count things, then reasons it needs a visual. - Deterministic code rendersmatplotlib (headless
Aggbackend) draws a PNG into memory — correct and consistent. - Return an image artifactThe tool returns an MCP Image (the bytes), not a file path — the host may not share your filesystem.
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
Misconceptions
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.
tool_calls format, runs the tool-calling loop, and dispatches each call back via session.call_tool — appending the result as a role:"tool" message. Mistral does not speak MCP natively.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 minModule 7 of 26 · Open Source Track · Tool Use