FranΓ§ais

Applied AI β€” Advanced Level

Session 5: MCP in depth

Yann Isola β€” Professional training in AI
Preparation Claude Certified Architect

🎯 Architecture · Transport · Primitives · Servers & Clients · Security

Objectives of the session

  1. Master the architecture Host ↔ Client ↔ Server and JSON-RPC 2.0
  2. Choose a transport: stdio vs Streamable HTTP
  3. Distinguish Tools / Resources / Prompts by their controller ← exam point #1
  4. Build an MCP server and client (Python SDK)
  5. Apply the security model and advanced patterns

3:30 a.m.: 2 hours of lessons + 90 minutes of exercises + quizzes

The MΓ—N problem

Before MCP: each AI application Γ— each tool = a specific connector

  • 10 applications Γ— 20 tools = 200 integrations to write and maintain
  • Each publisher reinvents authentication, schemes, errors

With MCP: M + N

  • Each tool exposes one MCP server
  • Each application implements one MCP client
  • Any server works with any compatible host

Official analogy: MCP is to AI what USB-C is to hardware β€” a universal port.
MCP = Model Context Protocol, open standard initiated by Anthropic ⚠ (rapidly evolving ecosystem)

Architecture: the three components

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ HOST (IDE, chat app) ───────────────────┐
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚
β”‚   β”‚ Client 1 β”‚     β”‚ Client 2 β”‚     β”‚ Client 3 β”‚        β”‚
β”‚   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚ JSON-RPC       β”‚ JSON-RPC       β”‚ JSON-RPC
    β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”
    β”‚filesystemβ”‚     β”‚  GitHub  β”‚     β”‚PostgreSQLβ”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Golden rule: 1 client ↔ 1 server. 3 servers β‡’ 3 clients.

Specific roles

Component Responsibilities
Host Embeds the model, chooses servers, applies consent & security policies
Customer SDK-managed Β· session Β· capacity negotiation Β· message routing
Server Independent process Β· expose Tools, Resources, Prompts

❗ Classic error: believing that a client connects to several servers. No. It is the host who multiplies the customers.

JSON-RPC 2.0: the common language

3 types of messages, on all transports:

Type id ? Expected response?
Query βœ… βœ…
Answer βœ… (the same) β€”
Notification ❌ ❌
{ "jsonrpc": "2.0", "id": 42, "method": "tools/call",
  "params": { "name": "search_commande",
              "arguments": { "order_id": "CMD-2026-0193" } } }

Session lifecycle

  1. initialize β€” client β†’ server: version + capabilities
  2. Server β†’ client: its own capabilities (tools, resources, prompts, subscriptions...)
  3. notifications/initialized β€” open session
  4. Discovery: tools/list Β· resources/list Β· prompts/list
  5. Operations: tools/call Β· resources/read Β· prompts/get
  6. Clean transport closure

Capacity negotiation β€” why?

Guaranteed exam question.

  • Each party declares what it can do in the handshake
  • We only use the intersection of the abilities
  • A server never sends a notification that the client has not declared to support

πŸŽ“ Purpose: protocol evolution without breaking compatibility.
Old client + recent server = cooperation on the common base.

Transport 1: stdio

The server = subprocess of the host. JSON-RPC to stdin/stdout.

  • ⚑ Minimum latency, zero network
  • πŸ”’ No open ports Β· permissions inherited from the OS
  • πŸ” Host-related life cycle
  • πŸ““ Logs on stderr β€” never stdout!
{ "mcpServers": { "commandes": {
    "command": "python",
    "args": ["/opt/mcp/serveur_commandes.py"] } } }

The stdio trap (exercise demo)

@mcp.tool()
def search(order_id: str) -> dict:
    print("debug: step 2 OK")     # ← πŸ’₯ BOOM
    ...
  • print() written to stdout β†’ corrupts JSON-RPC stream
  • Client fails to parse messages β†’ broken session

βœ… Solution: logging configured to stderr, or file.

Classic exam trick question.

Transport 2: Streamable HTTP

Replaces SSE (deprecated) for remote servers.

  • POST JSON-RPC messages to a single endpoint (/mcp)
  • The server can respond in simple JSON or open a feed (progressive results, notifications)
  • Sessions via header Mcp-Session-Id, resumed after interruption
  • πŸ” Web authentication: OAuth 2.1, bearer tokens

A deployment β†’ thousands of clients.

Transport decision matrix

Criterion studio HTTP Streamable
Location Local Remote / cloud
Users 1 N (shared)
Auth Legacy from OS OAuth 2.1 / tokens
Local files βœ… direct ❌
Deployment With the host app Operated web service

πŸŽ“ Pocket rule: single-user local β†’ stdio Β· authenticated shared service β†’ HTTP Streamable Β· SSE only β†’ false response.

The three primitives

The heart of certification

The right question is not β€œwhat does she do?”
but β€œWHO decides to invoke it?”

The table to memorize

Primitive Controller Trigger Analogy
Tool πŸ€– The model The LLM decides during its reasoning Hands
Resource πŸ–₯️ The application The host chooses what to inject into the context Eyes (read only)
Prompt πŸ‘€ The user Explicit slash menu/command Pre-filled form

model-controlled Β· application-controlled Β· user-controlled

Tools β€” invoked by the model

  • Input: JSON Schema β€” the model knows which arguments to provide
  • Output: structured content blocks (text, image, embedded resource)
  • Side effects allowed β‡’ user consent required
{ "name": "refund_order",
  "description": "Refunds an order. Refused > $500.",
  "inputSchema": { "type": "object",
    "properties": { "order_id": {"type": "string"},
                    "amount": {"type": "number"} },
    "required": ["order_id", "amount"] } }

Resources β€” exposed by the application

  • Identified by URI: file:///rapports/q2.pdf Β· db://clients/12345 Β· api://meteo/paris
  • Read only β€” if it modifies a state, it's a Tool
  • Templates: db://commandes/{order_id} β€” resource families
  • Subscriptions: resources/subscribe β†’ notifications/resources/updated

Why β€œapplication-controlled”? The host filters what enters the context: window protection and privacy.

Prompts β€” triggered by the user

  • Configured prompt templates: name, description, arguments
  • Presented by the host: slash command, drop-down menu
  • Use cases: repeatable and structured flows
{ "name": "rapport_incident",
  "arguments": [
    { "name": "severite", "required": true },
    { "name": "systeme",  "required": true } ] }

Examples: PR analysis Β· contract review Β· incident report

Exam Trap #1

β€œA database search is a reading, therefore it is a Resource” β€” FALSE ❌

  • If the model dynamically decides to search β‡’ Tool (even when reading)
  • If the application pre-selects the data to inject β‡’ Resource

The boundary is through the controller, not through read/write.

Decision tree

  1. Side effect / state modification? β†’ Tool
  2. Data to read, injected by decision of the application? β†’ Resource
  3. Flow triggered explicitly by the human, with parameters? β†’ Prompt
  4. The model must decide alone to go searching? β†’ Tool, even in reading

(Interactive selector in the session web page)

MCP server in Python β€” skeleton

from mcp.server.fastmcp import FastMCP
mcp = FastMCP("commandes")

@mcp.tool()
def search_commande(order_id: str) -> dict:
    """The model decides WHEN to call β€” write it for the model."""
    ...

@mcp.resource("db://commandes/{order_id}")
def fiche(order_id: str) -> str: ...

@mcp.prompt()
def analyse_litige(order_id: str, motif: str) -> str: ...

mcp.run()   # stdio by default

⚠ SDK API evolving β€” check version.

Server-side safeguard: the $500

MAX_REFUND = 500.0

@mcp.tool()
def refund_order(order_id: str, amount: float) -> dict:
    if amount > MAX_REFUND:
        return { "status": "refused", "escalate": True,
                 "reason": "Human validation required" }
    return { "status": "completed", ... }

πŸ›‘οΈ In the prompt: a suggestion, bypassable by injection.
In the server: a law, which holds even if the model is manipulated.
Defense in Depth β€” exam question.

Standardization at the border

Two MCP servers, two date formats:
15/03/2026 (internal) Β· 27 Mar 2026 (carrier)

def normaliser_date(valeur: str) -> str:
    for fmt in ("%Y-%m-%d", "%d/%m/%Y", "%d %b %Y"):
        try:
            return datetime.strptime(valeur, fmt).date().isoformat()
        except ValueError: continue
    raise ValueError(f"Format inconnu : {valeur!r}")

βœ… ISO 8601 everywhere, in code, at the border.
❌ Never β€œthe model will guess” β€” ambiguity 03/04 = April 3 or March 4?

MCP client in Python

async with stdio_client(params) as (read, write):
    async with ClientSession(read, write) as session:
        init = await session.initialize()        # negotiation
        tools = await session.list_tools()      # discovery
        result = await session.call_tool(      # invocation
            "refund_order",
            {"order_id": "CMD-1", "amount": 750.0})
  • ClientSession manages handshake, id, request/response correlation
  • Multiple servers β‡’ multiple sessions + namespacing tools
    (nordcommerce.search / carrier.status)

Security model β€” 3 pillars

  1. Server isolation β€” separate processes, 1:1 dedicated client, no inter-server leaks: they never talk to each other, only the host sees everything
  2. User consent β€” any side effect tool call is presented and approved
  3. Least privilege β€” narrow-scope tokens, explicitly allowed directories

Third-party tool output = unreliable data, never instructions (anti-prompt injection).

The ecosystem

50+ community servers ⚠ (rapid growing β€” check)

filesystem Β· GitHub Β· Slack Β· PostgreSQL Β· Google Drive Β· browser Β· memory...

🧭 Architect’s reflex:
search for an existing server before writing one.
We develop for our internal business systems, not for generic integrations.

Advanced patterns (1/2)

Dynamic tool registration

  • The server adds/removes tools in session
  • Issues notifications/tools/list_changed β†’ the client restarts tools/list
  • Case: admin tools exposed after authentication

Resource Subscription

  • resources/subscribe β†’ notifications/resources/updated
  • Case: monitored configuration, evolving ticket, dashboard

Advanced patterns (2/2)

Prompt chaining

  • A Prompt MCP orchestrates a sequence: research β†’ verification β†’ proposal β†’ escalation

Standardization at the border

  • The server/client converts dates, currencies, units to a canon (ISO 8601)
  • The MCP server = anti-corruption layer between heterogeneous systems

Certification summary β€” 7 reflexes

  1. Client:Server = 1:1, host orchestrates
  2. Everything is JSON-RPC 2.0 (request/response/notification)
  3. stdio = local Β· Streamable HTTP = remote Β· SSE = deprecated
  4. Tool = model Β· Resource = application Β· Prompt = user
  5. Capacity negotiation = evolutionary compatibility
  6. Business guardrails server side (the $500!)
  7. Tool outputs = unreliable data + consent for side effects

It’s up to you πŸ› οΈ

Exercise 1 β€” Building the NordCommerce server (40 min)
Tools + safeguard $500 + date standardization + Resource + Prompt

Exercise 2 β€” Multi-server client (30 min)
2 sessions, namespacing, data fusion, ISO 8601 everywhere

Exercise 3 β€” Tools/Resources/Prompts decision workshop (20 min)

Then: validation quiz (10 multiple choice questions) β€” certification threshold: 8/10

Session 5 β€” finished βœ…

Next session: MCP integration into production agent architectures

πŸ“„ Complete guide: doc-prof/guide.md
🌐 Interactive demo: webpage/index.html
❓ Quiz: quiz/quiz.md

Applied AI β€” Yann Isola