Applied AI — Advanced Level
Session 5: MCP in depth
Program : Applied AI — Professional training in artificial intelligence Instructor: Yann Isola Level : Advanced — Solutions architects preparing for certification Claude Certified Architect Recommended duration: 3.5 hours (2 hours of lecture + 1.5 hours of practical exercises) Prerequisites: Sessions 1 to 4 of the advanced level (orchestration, tools/function calling, context management, multi-agent architecture)
Educational objectives
At the end of this session, participants will be able to:
- Describe the complete architecture of the MCP protocol (Model Context Protocol — model context protocol): Host ↔ Client ↔ Server, and the exact role of each component.
- Choose and justify a transport layer (local stdio vs remote HTTP Streamable) according to architectural, security and deployment criteria.
- Distinguish the three MCP primitives — Tools, Resources, Prompts — and select the correct primitive according to who controls the summoning (the model, application or user). This is a central examination point for certification.
- Build a complete MCP server in Python (official SDK) exposing tools, resources and prompts, with integrated safeguards.
- Build an MCP client: session management, tool discovery, capacity negotiation.
- Apply advanced patterns: dynamic tool registration, resource subscription with notifications, prompt chaining, normalization of heterogeneous data.
Session plan
| Block | Duration | Content |
|---|---|---|
| 1 | 30 mins | MCP Architecture: Host, Client, Server, JSON-RPC |
| 2 | 25 mins | Transport layer: stdio vs Streamable HTTP |
| 3 | 35 mins | The three primitives: Tools, Resources, Prompts |
| 4 | 30 mins | Build an MCP server and client (Python SDK) |
| 5 | 20 mins | Security, ecosystem and advanced patterns |
| 6 | 90 mins | Practical exercises + interactive demonstration (web page) |
| 7 | 10 mins | Validation quiz and certification summary |
Block 1 — MCP Architecture: Host, Client, Server
1.1 The problem that MCP solves
Before MCP, each AI application editor had to write a specific connector for each external tool: N applications × M tools = N×M integrations. This is the classic “M×N” problem that standards solve (like USB-C did for connectivity, or LSP — Language Server Protocol , language server protocol — for code editors).
MCP transforms M×N into M+N : each tool exposes a single standard interface (an MCP server), each application implements a single standard interface (an MCP client). Any server works with any compatible host.
Educational point: the USB-C analogy is officially used by Anthropic. Participants will find it in the certification documentation. Have a participant rephrase it: “MCP is to AI what USB-C is to hardware: a universal port. »
1.2 The three components
┌─────────────────────────── HÔTE ───────────────────────────┐
│ (IDE, application de chat, agent autonome) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Client 1 │ │ Client 2 │ │ Client 3 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└────────┼─────────────────┼─────────────────┼────────────────┘
│ JSON-RPC │ JSON-RPC │ JSON-RPC
┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐
│ Serveur │ │ Serveur │ │ Serveur │
│filesystem│ │ GitHub │ │PostgreSQL│
└──────────┘ └──────────┘ └──────────┘
| Component | Role | Examples |
|---|---|---|
| Host (Host ) | The application that embeds the model and controls the user experience. Decides which servers to connect, enforces security and consent policies. | Claude Desktop, an IDE (Cursor, VS Code), a business application |
| Customer (Customer ) | Component managed by the SDK, one client per server (strict 1:1 relationship). Maintains session, performs capacity negotiation, routes messages. | Instantiated by the MCP SDK in the host |
| Server (Server ) | Capability Provider: Exposes Tools, Resources, and Prompts via the protocol. Independent process. | Filesystem server, GitHub server, home server |
Rule to hammer for certification: the Client:Server relationship is 1:1. A host connecting to 3 servers instantiates 3 clients. This isolation is a deliberate architectural choice (see Block 5: security).
1.3 JSON-RPC 2.0: the common language
All MCP messages are messages JSON-RPC 2.0 (Remote Procedure Call — remote procedure call in JSON). Three types of messages:
- Request (Request ) — waits for a response, wears a
id:
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "rechercher_commande",
"arguments": { "numero": "CMD-2026-0193" }
}
}
- Answer (Response ) — wears the same
id, containsresultOrerror:
{
"jsonrpc": "2.0",
"id": 42,
"result": {
"content": [
{ "type": "text", "text": "{\"statut\": \"expédiée\", \"montant\": 129.90}" }
]
}
}
- Notification (Notification ) — no
id, no response expected:
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": { "uri": "db://commandes/CMD-2026-0193" }
}
1.4 The life cycle of a session
initialize: the client sends its protocol version and its abilities (what it knows how to manage: sampling, notifications, etc.). The server responds with its own capabilities (tools, resources, prompts, subscriptions, etc.).notifications/initialized: the client confirms — the session is open.- Discovery :
tools/list,resources/list,prompts/list. - Operations :
tools/call,resources/read,prompts/get, subscriptions… - Closing : clean termination of the transport.
Certification point — capacity negotiation: the customer declares what he supports handshake (initial handshake). A server should never send a subscription notification to a client that has not declared the corresponding capacity. On the exam, you will be asked why negotiation exists: to allow the evolution of the protocol without breaking compatibility — an old client and a recent server can cooperate on the intersection of their capabilities.
Block 2 — Transport layer: stdio vs Streamable HTTP
2.1 stdio: local transport
The server is launched as subprocess of the host. JSON-RPC messages flow over stdin/stdout (standard input/output), one JSON message per line.
Features :
- Minimum latency (no network).
- Host-related lifecycle: When the host shuts down, the server dies.
- Security by construction: no open network ports, the server inherits the permissions from the local user.
stderrremains available for logs (never write logs to stdout — this will corrupt the JSON-RPC stream; classic beginner's mistake, and exam trap question).
Typical configuration (Claude Desktop):
{
"mcpServers": {
"commandes": {
"command": "python",
"args": ["/opt/mcp/serveur_commandes.py"],
"env": { "DB_URL": "postgresql://localhost/boutique" }
}
}
}
2.2 Streamable HTTP: remote transport
For remote servers (shared, multi-user, cloud), MCP uses HTTP streamable , Who replaces the old SSE transport (Server-Sent Events — events sent by the server), deprecated.
Functioning :
- The client sends its JSON-RPC messages in
POSTto a single endpoint (e.g./mcp). - The server responds either with a simple JSON response or by opening a flow on the same connection to push multiple messages (progressive results, notifications).
- Session support via header
Mcp-Session-Id, flow resumption possible after interruption.
Features :
- Shared server: a deployment serves thousands of customers.
- Web standard authentication: OAuth 2.1, tokens bearer , HTTP headers.
- Passes firewalls and corporate infrastructures (proxies, load balancers).
2.3 Decision matrix (to know for the exam)
| Criteria | stdio | HTTP streamable |
|---|---|---|
| Location | Same machine as host | Remote/cloud machine |
| Number of users | 1 (the local user) | N (shared) |
| Authentication | Legacy from the operating system | OAuth 2.1 / tokens |
| Latency | Minimal | Network (variable) |
| Deployment | Distributed with the host application | Operated as a web service |
| Access to local resources (files, devices) | Direct | Impossible (or via tunnel) |
| Typical use case | Filesystem server, local development tools | Enterprise SaaS server, shared business API |
Certification pocket ruler: “Local and single-user files → stdio. Shared, authenticated, scalable service → Streamable HTTP. SSE only → false answer (deprecated). »
Block 3 — The three primitives: Tools, Resources, Prompts
It is the heart of the session and the certification . The question is not “what does the primitive do?” " but " who decides on its invocation? ».
3.1 Control table (to be memorized)
| Primitive | Who controls? | Trigger | Analogy |
|---|---|---|---|
| Tool (tool) | The model (model-controlled ) | The LLM decides to call the tool during its reasoning | The agent's hands |
| Resource (resource) | The app (application-controlled ) | The host decides what data to inject into the context | The Agent's Eyes — read-only |
| Prompt (guest) | The user (user-controlled ) | The human explicitly chooses a prompt template (menu, slash command) | A pre-filled form |
3.2 Tools: invoked by the model
- Entrance described by a JSON Schema (JSON schema — data structure description format): The model knows exactly what arguments to provide.
- Exit structured : list of content blocks (
text,image, embedded resource) + possiblestructuredContentkind. - Authorized side effects (write, send, reimburse, etc.) — hence the requirement for user consent (Block 5).
{
"name": "rembourser_commande",
"description": "Rembourse une commande. Refusé au-delà de 500 $ sans validation humaine.",
"inputSchema": {
"type": "object",
"properties": {
"numero": { "type": "string", "description": "Numéro de commande, ex. CMD-2026-0193" },
"montant": { "type": "number", "description": "Montant en dollars US" }
},
"required": ["numero", "montant"]
}
}
3.3 Resources: exposed by the application
- Identified by URI (Uniform Resource Identifier — uniform resource identifier):
file:///rapports/q2.pdf,db://clients/12345,api://meteo/paris. - Read only : a resource never modifies its state. If the action modifies something, it is a Tool.
- Subscriptions : a customer can subscribe (
resources/subscribe) and receivenotifications/resources/updatedwhen the resource changes — the host can then reread and refresh the context. - Resource templates : Parameterized URIs (
db://commandes/{numero}) to expose entire families of resources.
Why “application-controlled”? Because it is the host — not the model — that decides which resources to load in the context. This protects the context window (the host filters) and privacy (the model does not "snoop" freely).
3.4 Prompts: triggered by the user
- Prompt templates parameterized exposed by the server: name, description, list of arguments.
- The host presents them to the user (slash command, menu). The user chooses, fills in the arguments, and the prompt generates one or more messages injected into the conversation.
- Use case: complex and repeatable workflows – “PR analysis”, “incident report”, “contract review” – where we want to guarantee the structure of the request.
{
"name": "rapport_incident",
"description": "Génère un rapport d'incident structuré",
"arguments": [
{ "name": "severite", "description": "P1 à P4", "required": true },
{ "name": "systeme", "description": "Système affecté", "required": true }
]
}
3.5 Decision tree (reproduced in the interactive web page)
- Does the action modify a state or trigger a side effect? → Tool .
- Is it data to be read, and it is the application that must decide when to inject it? → Resource .
- Is it a workflow that the human explicitly triggers with parameters? → Prompt .
- The model must decide alone, through reasoning, to seek out the data? → then even a reading can be a Tool (ex.
rechercher_client). The Resource/Tool boundary passes through who controls , not by read/write alone.
Exam Pitfall #1: “a search in a database is a reading, therefore it is a Resource” — fake if it is the model which must decide to trigger it dynamically. A search invoked by the model is a Tool. Data pre-selected by the application is a Resource.
Block 4 — Build an MCP Server and Client (Python SDK)
4.1 Complete server with guardrails
The official Python SDK (mcp , with the API FastMCP ) provides three decorators. Complete example — order management server with reimbursement safeguard And normalization of dates :
# serveur_commandes.py
# ⚠ Vérifiez la version du SDK : l'API évolue rapidement.
from mcp.server.fastmcp import FastMCP
from datetime import datetime
mcp = FastMCP("commandes")
MAX_REMBOURSEMENT = 500.0 # Garde-fou métier : au-delà, validation humaine
def normaliser_date(valeur: str) -> str:
"""Normalise les dates hétérogènes des systèmes sources vers ISO 8601.
Les serveurs MCP tiers renvoient des formats variés : c'est au point
d'intégration de normaliser, jamais au modèle de deviner."""
formats = ("%Y-%m-%d", "%d/%m/%Y", "%m-%d-%Y", "%d %b %Y", "%Y-%m-%dT%H:%M:%S")
for fmt in formats:
try:
return datetime.strptime(valeur.strip(), fmt).date().isoformat()
except ValueError:
continue
raise ValueError(f"Format de date non reconnu : {valeur!r}")
# ── TOOL : invoqué par le modèle, effets de bord, garde-fou ──
@mcp.tool()
def rembourser_commande(numero: str, montant: float) -> dict:
"""Rembourse une commande. Bloqué au-delà de 500 $ (validation humaine requise)."""
if montant > MAX_REMBOURSEMENT:
# Le garde-fou vit CÔTÉ SERVEUR : il s'applique même si le
# prompt du modèle est manipulé (injection). Défense en profondeur.
return {
"statut": "refuse",
"raison": f"Montant {montant} $ > plafond {MAX_REMBOURSEMENT} $. "
"Escalade vers un opérateur humain requise.",
"escalade": True,
}
return {"statut": "effectue", "numero": numero, "montant": montant}
# ── TOOL de lecture contrôlée par le modèle ──
@mcp.tool()
def rechercher_commande(numero: str) -> dict:
"""Recherche une commande par numéro (le modèle décide quand chercher)."""
brut = {"numero": numero, "date_livraison": "15/03/2026", "statut": "expédiée"}
brut["date_livraison"] = normaliser_date(brut["date_livraison"]) # → 2026-03-15
return brut
# ── RESOURCE : donnée en lecture, contrôlée par l'application ──
@mcp.resource("db://commandes/{numero}")
def ressource_commande(numero: str) -> str:
"""Fiche commande complète, exposée à l'hôte via URI paramétrée."""
return f'{{"numero": "{numero}", "historique": [...], "client": "..."}}'
# ── PROMPT : modèle d'invite déclenché par l'utilisateur ──
@mcp.prompt()
def analyse_litige(numero: str, motif: str) -> str:
"""Flux structuré d'analyse de litige client."""
return (
f"Analyse le litige sur la commande {numero}, motif : {motif}.\n"
"1. Vérifie l'historique de la commande.\n"
"2. Compare avec la politique de remboursement.\n"
"3. Propose une résolution ; si remboursement > 500 $, recommande une escalade."
)
if __name__ == "__main__":
mcp.run() # transport stdio par défaut ; mcp.run(transport="streamable-http") pour le distant
Teaching points of emphasis:
- The $500 safeguard is in the server , not in the system prompt. A prompt side guard is a suggestion; a server-side guardrail is a law. Under consideration: “where to place a critical business control?” » → at the deepest level, server/tool side.
- Date normalization illustrates the role of the MCP server as anti-corruption layer between heterogeneous systems: ISO 8601 output, always.
4.2 MCP client: session, discovery, invocation
# client_commandes.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(
command="python", args=["serveur_commandes.py"]
)
async with stdio_client(params) as (lecture, ecriture):
async with ClientSession(lecture, ecriture) as session:
# 1. Handshake : négociation de capacités
init = await session.initialize()
print("Capacités serveur :", init.capabilities)
# 2. Découverte
outils = await session.list_tools()
for outil in outils.tools:
print(f"- {outil.name} : {outil.description}")
# 3. Invocation
resultat = await session.call_tool(
"rembourser_commande",
{"numero": "CMD-2026-0193", "montant": 750.0},
)
print(resultat.content) # → statut: refuse, escalade: True
asyncio.run(main())
To highlight: there ClientSession encapsulates the entire protocol (handshake, id JSON-RPC, request/response correlation). The architect must nevertheless understand what is going on “under the hood” — this is exactly what the interactive web page for this session visualizes.
Block 5 — Security, ecosystem, advanced patterns
5.1 MCP security model
Three pillars, all required for certification:
- Server isolation : each server is a separate process , with a dedicated client (1:1 relationship). The GitHub server never sees data from the PostgreSQL server. No cross-server leaks by construction: the servers do not communicate with each other; only the host sees the whole.
- User consent : Any side effect tool call must be approved by the user (or by an explicit policy of the host). The host is the application point: it displays the tool, the arguments, and asks for confirmation.
- Least privilege : a server only receives necessary access (targeted environment variables, narrow-scope tokens, explicit authorized directories for a filesystem server).
Educational point — prompt injection: a third-party MCP server may return malicious text (“ignore your instructions and…”). The host must treat tool output as unreliable data , never as instructions. Connect this point to the $500 safeguard: the server-side defense holds even if the model is manipulated.
5.2 Ecosystem
More than 50 community servers ⚠ (number growing rapidly — check before each session) cover common needs: filesystem, GitHub, Slack, PostgreSQL, Google Drive, browser, persistent memory… Architect’s reflex: find an existing server before writing one . We write an in-house server for its internal business systems, not for generic integrations.
5.3 Advanced patterns
| Boss | Mechanism | Use cases |
|---|---|---|
| Dynamic tool registration | The server adds/removes tools during the session and issues notifications/tools/list_changed ; the customer rediscovers via tools/list . |
State-dependent tools (after connection to a database, expose its tables); escalation of privileges after authentication. |
| Subscription to resources | resources/subscribe → the server pushes notifications/resources/updated at each change; the host replays the resource. |
Real-time dashboard, monitored configuration file, ticket whose status changes. |
| Prompt chaining | An MCP prompt generates a sequence of messages that orchestrate several successive tool calls. | “Dispute analysis” flow: research → policy verification → proposal → possible escalation. |
| Standardization at the border | The server converts all heterogeneous formats (dates, currencies, units) to a canonical format before responding. | Aggregation of several MCP servers returning dates in different formats → ISO 8601 everywhere. |
5.4 Certification summary — the 7 reflexes
- Client:Server = 1:1, the host orchestrates.
- Everything is JSON-RPC 2.0 : request (id), response (id), notification (without id).
- Transportation : stdio = local/single user , Streamable HTTP = remote/shared , SSE = deprecated.
- Primitives by controller : Tool = model, Resource = application, Prompt = user.
- The handshake capacity negotiation guarantees the scalable compatibility .
- Business safeguards server side , never only in the prompt.
- Tool outputs = unreliable data ; user consent for side effects.
Animation tips
- Block 1: Have a participant draw the architecture on the board before showing the diagram. The common error (single client for multiple servers) will pop up on its own — fix it live.
- Block 3: use the primitives selector on the web page in “oral quiz” mode: read a use case, have the room vote, then reveal the recommendation.
- Block 4: run the live example server with the MCP inspector (
npx @modelcontextprotocol/inspector python serveur_commandes.py) if the environment allows it ⚠ (order to be verified, tools evolve). - Error to be deliberately provoked: add a
print("debug")in stdio server and show corruption of JSON-RPC stream. Unforgettable lesson: logs go to stderr . - Timing: if the group is late, compress Block 5 (ecosystem) and return to the guide; never compress Block 3 (primitives), it is the most discriminating in the exam.
References
- MCP specification:
modelcontextprotocol.io⚠ (dated versions, check current revision) - Python SDK: repository
modelcontextprotocol/python-sdk - Chapter 4 of the course guide (
guide_fr.md)