Skip to content

🔬 Under the Hood — How MCP Actually Works

You've learned what MCP is (the concierge analogy), what it's made of (tools, resources, prompts), and how it connects (stdio vs HTTP). Now let's open the bonnet and see how the engine actually runs.


The Language MCP Speaks: JSON-RPC

Every conversation between Copilot and an MCP server happens in a language called JSON-RPC 2.0. Don't let the name scare you — it's surprisingly simple.

The Café Walkie-Talkie Analogy 📻

Imagine the café kitchen has a walkie-talkie system with a strict radio protocol:

Radio Rule JSON-RPC Rule
Every message has a channel number Every message has an id (so you know which reply goes with which question)
You say "requesting" before your question The message has a method (what you're asking for)
You include details after your request The message has params (the specifics of your request)
The kitchen replies on the same channel The response has the same id so you can match it

What a Real Message Looks Like

When you ask "List users in my lab", here's what actually flows between Copilot and your M365 MCP server:

➡️ Copilot sends this to the MCP server:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_users",
    "arguments": {
      "top": 25
    }
  }
}

⬅️ The MCP server replies:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Found 8 users: MOD Administrator, Adele Vance, Alex Wilber..."
      }
    ]
  }
}

That's it. Every single MCP interaction is a message like this — a request and a response, each carrying a matching id.

Why does this matter?

You'll never write these messages yourself — Copilot handles it all. But understanding the format helps you:

  • Debug when an MCP server isn't responding (you'll know what to look for in logs)
  • Understand error messages when something breaks
  • Build your own MCP server someday (you'll know what format to speak)

The Connection Lifecycle — A Conversation in 5 Acts

When Copilot starts an MCP server, they don't just jump straight into tool calls. There's a handshake first — like two people meeting for the first time.

☕ The New Staff Member Analogy

Imagine a new staff member arrives at Cloud Café on their first day:

Act 1: 🤝 INTRODUCTION
   New staff: "Hi, I'm the inventory specialist. I can do these things..."
   Manager:   "Great! Here's how we work here..."

Act 2: 📋 CAPABILITY EXCHANGE
   New staff: "I can check stock, reorder items, and run reports."
   Manager:   "Perfect. I might ask you to do any of those."

Act 3: 🔧 WORKING TOGETHER
   Manager:   "Check how much oat milk we have."
   New staff: "3 cartons left."
   Manager:   "Reorder 20 more."
   New staff: "Done! Order #4521 placed."

Act 4: 🔄 ONGOING (as long as the café is open)
   (More questions and answers, back and forth...)

Act 5: 👋 GOODBYE
   Manager:   "We're closing up. Thanks for today!"
   New staff: "See you tomorrow!"

The Real Protocol Lifecycle

Here's what actually happens, mapped to that analogy:

sequenceDiagram
    participant C as 🤖 Copilot CLI
    participant M as 🔌 MCP Server

    Note over C,M: Act 1 — Introduction
    C->>M: initialize (protocol version, my capabilities)
    M-->>C: initialize response (your capabilities, server info)
    C->>M: initialized (acknowledged!)

    Note over C,M: Act 2 — Discovery
    C->>M: tools/list (what tools do you have?)
    M-->>C: Here are my 47 tools with descriptions

    Note over C,M: Act 3 — Working Together
    C->>M: tools/call → "list_users"
    M-->>C: Result: 8 users found...
    C->>M: tools/call → "get_user_details"
    M-->>C: Result: Adele Vance, Licensed...

    Note over C,M: Act 5 — Goodbye
    C->>M: shutdown
    M-->>C: OK, shutting down

What Happens in Each Act

Act What Happens Real Message Why It Matters
1. Initialize Copilot and the MCP server introduce themselves initialize with protocol version They agree on which version of MCP to speak
2. Discovery Copilot asks "what can you do?" tools/list Copilot learns ALL available tools so it knows what to call later
3. Tool Calls Copilot asks the server to do things tools/call with tool name + arguments The actual work happens here
4. Ongoing More tool calls as needed Multiple tools/call messages A single question might trigger 3-4 tool calls
5. Shutdown Copilot tells the server to stop shutdown or process termination Cleans up resources and connections

Act 2 is the Magic Moment

When Copilot runs tools/list, the MCP server sends back a complete catalogue of everything it can do — every tool name, what it does, and what parameters it needs. This is how Copilot "knows" what to ask for. It's like getting the full menu before ordering.


How Copilot Decides Which Tool to Call

This is the question everyone asks: "How does Copilot know to call list_users when I say 'show me the users'?"

The Answer: Tool Descriptions

Every tool comes with a description — a plain-English explanation of what it does. When you ask a question, Copilot reads ALL the descriptions and picks the best match.

Here's what the tools/list response actually looks like (simplified):

{
  "tools": [
    {
      "name": "list_users",
      "description": "List all users in the tenant with license and status info",
      "inputSchema": {
        "type": "object",
        "properties": {
          "top": {
            "type": "number",
            "description": "Number of users to return (default 25, max 100)"
          }
        }
      }
    },
    {
      "name": "get_user_details",
      "description": "Get detailed info about a specific user",
      "inputSchema": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "description": "User ID or UPN (e.g., user@domain.com)"
          }
        },
        "required": ["userId"]
      }
    }
  ]
}

The Matching Process

You say: "Show me all the users in my lab"

Copilot thinks:
  📋 Scanning 47 tools from m365-admin-graph...
  📋 Scanning 85 tools from azure...

  🎯 Best match: "list_users" 
     → description says "List all users in the tenant"
     → matches your intent perfectly!

  📤 Calling: tools/call → list_users

☕ The Café Analogy

Imagine the manager has a staff directory:

📁 Staff Directory:
  - Lisa (manager) → "Handles schedules, reports, and staff issues"
  - Adil (senior barista) → "Makes coffee, trains new staff"
  - Dakota (trainee) → "Stocks shelves, cleans, takes simple orders"

When a customer asks for a flat white, the manager doesn't think hard — they scan the directory, see Adil handles coffee, and send the order to him.

Copilot does exactly the same thing with tool descriptions.

When Copilot Picks the Wrong Tool

Sometimes Copilot picks the wrong tool — usually because two tools have similar descriptions. If you get unexpected results, try being more specific in your question. Instead of "show me users", say "list all users with their licence status".


What Happens When Things Go Wrong

MCP servers can fail — just like any software. Here's what different errors look like and what they mean:

Error Types

Error What It Means Café Analogy What to Do
Server not found MCP server didn't start The staff member didn't show up for work Check your mcp-config.json — is the command correct?
Tool not found Copilot asked for a tool that doesn't exist Asking the barista to fix the plumbing Usually a Copilot mistake — rephrase your question
Authentication failed Credentials are wrong or expired Staff member's key card doesn't work Re-authenticate (az login or refresh your client secret)
Timeout Server took too long to respond The kitchen is overwhelmed and not replying The API behind the MCP server might be slow — try again
Server crashed The MCP server process died Staff member fainted mid-shift 😵 Restart with /mcp → restart the server

What an Error Response Looks Like

When something goes wrong, the MCP server sends back an error instead of a result:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32601,
    "message": "Method not found: tools/call with name 'list_vms'",
    "data": "Available tools: list_users, get_user_details, list_licenses..."
  }
}

Copilot translates this into something readable: "That tool isn't available on this MCP server. Did you mean to use the Azure server instead?"

The /mcp Command is Your Friend

When an MCP server is misbehaving, /mcp shows you the status of all servers — running, stopped, or errored. It's the first thing to check when things don't work.


Stateless vs Stateful — Does the Server Remember?

Quick answer: MCP servers are stateless by default. Each tool call is independent — the server doesn't remember what you asked before.

☕ Café Analogy

Type Café Version What It Means
Stateless A walk-in café — no reservations, no memory of past visits Each tool call is independent. The server doesn't know what you asked 5 minutes ago
Stateful A café with a loyalty programme — they know your usual order The server remembers context across calls (rare, more complex)

Why This Matters

When you ask:

  1. "List my Azure VMs" → Copilot calls list_vms tool → gets results
  2. "Now delete the first one" → Copilot needs to call delete_vm with a specific VM name

Copilot remembers that "the first one" was VM-WebServer-01 (because it's in the conversation context). But the MCP server doesn't — it just receives delete_vm(name="VM-WebServer-01") as a fresh, independent request.

Copilot is the Memory

The AI model (Copilot) holds the conversation context — not the MCP server. The MCP server is like a calculator: you give it input, it gives you output, and it immediately forgets. Copilot is the one keeping track of the bigger picture.


Putting It All Together — A Complete Journey

Let's trace a complete real-world request from your keyboard to the answer:

You type: "How many free Copilot licences do I have in my lab?"
graph TB
    A([⌨️ You type your question]) --> B[🤖 Copilot receives the text]
    B --> C{Does this need<br/>external data?}
    C -->|Yes| D[📋 Scan all MCP tool descriptions]
    C -->|No| Z([💬 Answer from memory])
    D --> E[🎯 Best match: list_licenses<br/>on m365-admin-graph]
    E --> F[📤 Send JSON-RPC: tools/call<br/>name=list_licenses]
    F --> G[🔌 MCP server receives request]
    G --> H[🌐 MCP calls Microsoft Graph API]
    H --> I[☁️ Graph API returns licence data]
    I --> J[🔌 MCP formats response as JSON-RPC]
    J --> K[🤖 Copilot receives raw data]
    K --> L[🧠 AI interprets and formats<br/>a human-readable answer]
    L --> M([💬 You see: 1 free Copilot licence out of 25])

    style A fill:#1a1a2e,stroke:#66ffff,color:#fff
    style M fill:#1a1a2e,stroke:#66ffff,color:#fff
    style E fill:#1a1a2e,stroke:#ff66ff,color:#fff
    style G fill:#0f3460,stroke:#ff66ff,color:#fff
    style H fill:#0f3460,stroke:#66ffff,color:#fff

The whole journey takes 2-5 seconds. You typed one sentence. Behind the scenes: JSON-RPC message → MCP server → REST API call → cloud response → back through MCP → AI formats it → you see a clean answer.


Quick Summary

Concept One-liner Analogy
JSON-RPC The language MCP speaks (request + response with matching IDs) Walkie-talkie radio protocol
Lifecycle Initialize → Discover tools → Make calls → Shutdown New staff member's first day
Tool descriptions How Copilot knows which tool to use Staff directory with job descriptions
Error handling Structured error responses with codes and messages Kitchen sends back "we're out of oat milk"
Stateless Each tool call is independent — the server doesn't remember Walk-in café, no reservation
Copilot = Memory The AI model holds context, not the MCP server Manager remembers the conversation

What you now know

  • ✅ MCP speaks JSON-RPC 2.0 — structured request/response messages
  • ✅ The connection has a lifecycle: introduce → discover → work → goodbye
  • ✅ Copilot picks tools by reading descriptions (like scanning a staff directory)
  • ✅ Errors come back as structured messages — not just crashes
  • ✅ MCP servers are stateless — Copilot is the one with memory
  • ✅ A single question can trigger multiple tool calls behind the scenes

💡 Next up: Setting up MCP Servers — now that you know how the engine works, let's get hands-on and configure one from scratch.