🔬 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:
- "List my Azure VMs" → Copilot calls
list_vmstool → gets results - "Now delete the first one" → Copilot needs to call
delete_vmwith 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:
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.