Skip to main content

MCP Core Concepts — Breaking it Down

Calculating read time…

Imagine walking into a massive library with millions of books, but there's no librarian, no catalog system, and no way to know what's available.

You want to find a specific book, but how would you even know where to start? Which shelves to check? Which books exist?

This is exactly the problem AI assistants faced before structured protocols like MCP existed. They had potential access to tools and data, but no standardized way to discover what's available, understand how to use it, or coordinate actions.

MCP solves this with a clever vocabulary and lifecycle system that lets AI "ask the librarian" — discovering available capabilities, understanding how they work, and using them effectively.

In this guide, we'll break down the core concepts that make MCP work: the roles (Client vs Server), the capabilities (Tools vs Resources), how everything connects (Context Lifecycle), and how discovery happens (Capability Discovery).

By the end, you'll understand these concepts so well, you'll be able to design your own MCP integrations confidently! 🚀

🎯 Why Understanding MCP Vocabulary Matters

Before diving into technical details, let's understand why MCP needs its own vocabulary.

The Communication Problem

When two systems need to work together, they need a shared language. Imagine trying to build IKEA furniture where the instructions use completely different terms than the actual parts!

In the AI integration world, this problem was everywhere:

  • One system calls something a "function," another calls it an "action," and a third calls it a "capability"
  • Data could be "context," "resources," "files," or "attachments" depending on who built the system
  • No one knew if the AI should automatically use something or wait for permission

MCP establishes precise terminology that everyone follows. When you say "Tool" in MCP, everyone knows exactly what you mean, how it works, and what to expect.

✅ The Power of Shared Vocabulary

• Eliminates ambiguity in design discussions
• Makes documentation universally understandable
• Enables different teams to build compatible systems
• Reduces integration bugs from misunderstood concepts

🔄 The Big Picture: Client-Server Architecture

Let's start with the foundational concept: MCP uses a client-server model.

What Does Client-Server Mean?

Think about ordering food at a restaurant:

  • You (the customer) → You make requests ("I'd like the pasta")
  • The waiter → Communicates your request to the kitchen
  • The kitchen (the server) → Prepares what you ordered and sends it back

In MCP, it works similarly:

  • Client → Makes requests for data or actions
  • Server → Provides capabilities and handles requests

But there's an important twist in MCP that makes it different from typical client-server systems!

The Three-Layer Architecture

MCP actually has three components, not just two:

┌─────────────────────────────────────────┐ │ HOST APPLICATION │ │ (Claude Desktop, VS Code, Custom App) │ │ │ │ ┌────────────────────────────────┐ │ │ │ MCP CLIENT │ │ │ │ (Connection Manager Inside │ │ │ │ the Host Application) │ │ │ └────────────────────────────────┘ │ │ ⬍ ⬍ ⬍ │ └──────────────│─│─│──────────────────────┘ │ │ │ JSON-RPC Messages │ │ │ ⬍ ⬍ ⬍ ┌──────────────────────────────────────────┐ │ MCP SERVER │ │ (Weather Server, Database Server, etc.) │ │ │ │ Provides: Tools, Resources, Prompts │ └──────────────────────────────────────────┘

Let's understand each layer:

1. The Host Application 🏠

This is what you actually see and interact with.

Examples:

  • Claude Desktop app (chat interface for Claude AI)
  • VS Code with Copilot (code editor)
  • Cursor IDE (AI-powered development environment)
  • Any custom application you build

Responsibilities:

  • Displays the user interface
  • Manages user authentication and permissions
  • Runs the AI model (or connects to one)
  • Creates and manages MCP Clients
  • Coordinates between AI and external capabilities

Think of the Host as the "main program" that users interact with.

2. The MCP Client 🔌

This is a component inside the Host application. Most users never see it directly — it works behind the scenes.

Key Characteristics:

  • Each Client connects to exactly one Server (1:1 relationship)
  • The Host can run multiple Clients simultaneously (one per server)
  • Handles the technical communication protocol (JSON-RPC)
  • Maintains the connection throughout the session
  • Translates between the Host's needs and the Server's capabilities

Think of Clients as "connector cables" — each one links the Host to a specific Server.

💡 Why Separate Clients?

Security isolation! If one server misbehaves or gets compromised, it only affects that one Client.
Other connections remain safe and independent.

3. The MCP Server 🛠️

This is where the actual work happens — the provider of capabilities.

Examples:

  • File System Server → Read/write local files
  • Database Server → Query PostgreSQL, MongoDB, etc.
  • Slack Server → Send messages, read channels
  • GitHub Server → Manage repos, create PRs
  • Weather API Server → Get current weather data

Responsibilities:

  • Exposes capabilities (Tools, Resources, Prompts)
  • Handles incoming requests from Clients
  • Performs actual operations (database queries, API calls, file operations)
  • Returns results in standardized format
  • Can run locally (same machine) or remotely (cloud)

The Flow: How They Work Together

Let's trace a simple example: "What's the weather in Tokyo?"

Step 1: User Input
User types in Claude Desktop: "What's the weather in Tokyo?"

Step 2: Host Processing
Claude Desktop's AI analyzes the question and thinks: "I need current weather data"

Step 3: Capability Check
Host checks its connected Clients: "Do any of you have a weather capability?"
Weather Client responds: "Yes! I have a get_weather tool"

Step 4: Permission Request
Host asks user: "Allow Weather Server to fetch data?"
User clicks "Yes"

Step 5: Client → Server Communication
Weather Client sends JSON-RPC message: {"method": "tools/call", "params": {"name": "get_weather", "arguments": {"city": "Tokyo"}}}

Step 6: Server Execution
Weather Server calls actual weather API, gets: {"temp": 18, "condition": "Cloudy"}

Step 7: Response Chain
Server → Client → Host → AI

Step 8: User Sees Answer
"The weather in Tokyo is cloudy and 18°C"
✅ Key Insight: Clear Separation of Concerns

• Host = User experience and AI orchestration
• Client = Connection management and protocol handling
• Server = Actual capability implementation

This separation makes systems easier to build, test, and maintain!

🧩 Understanding MCP Capabilities: Tools vs Resources

Now that you understand who the players are (Client, Server, Host), let's understand what Servers can provide.

MCP Servers expose three types of capabilities, but we'll focus on the two most important (and most confusing) ones: Tools and Resources.

The Confusion: They Sound Similar!

When beginners first learn MCP, this question always comes up:

"Both Tools and Resources give AI access to data... so what's the actual difference?"

Great question! Let's break it down clearly.

Resources 📚: Application-Controlled Data

Simple Definition: Resources are like books on a shelf — the application or user decides which ones to pull down and hand to the AI.

Key Characteristics

  • Read-only: Resources provide information but don't perform actions
  • Application-controlled: The Host/User chooses when to use them, not the AI
  • Identified by URI: Each has a unique address like file:///docs/report.pdf
  • Static or dynamic: Can be fixed data or generated on request

Real-World Analogy

Think of Resources like documents in your company's shared drive:

  • The documents exist and are available
  • But you decide which ones to open and share with your team
  • The document itself doesn't jump up and say "Read me!"
  • You explicitly select: "Let's review the Q4 sales report"

Example Scenarios for Resources

✅ Use Resources When:

Scenario 1: Code Documentation
You have internal API documentation as Resources.
User explicitly says: "Use the authentication docs to help me"
Application loads that specific Resource into context

Scenario 2: Company Policies
HR policies stored as Resources (PDF, markdown files)
User asks about vacation policy
Application presents list of policy docs
User selects "Vacation Policy" Resource
AI can now reference it in its response

Scenario 3: Project Files
Your codebase files exposed as Resources
User: "Help me understand how authentication works in this project"
IDE shows file tree, user selects auth.py
That file becomes available as context

Technical Structure of a Resource

When a Server advertises a Resource, it looks like this:

{
  "uri": "file:///home/user/documents/api_spec.md",
  "name": "API Specification",
  "description": "Complete REST API documentation with examples",
  "mimeType": "text/markdown"
}

When a Client reads this Resource, it gets:

{
  "contents": [
    {
      "uri": "file:///home/user/documents/api_spec.md",
      "mimeType": "text/markdown",
      "text": "# API Documentation\n\n## Authentication\n..."
    }
  ]
}

Tools 🔧: Model-Controlled Actions

Simple Definition: Tools are like power tools in a workshop — the AI itself decides when it needs them and automatically reaches for the right one.

Key Characteristics

  • Action-oriented: Tools DO things (fetch data, update records, send messages)
  • Model-controlled: The AI decides when to call them based on context
  • Can have side effects: Can modify state, trigger actions, change data
  • Schema-defined: Have clear input parameters and output formats

Real-World Analogy

Think of Tools like a smart assistant with access to your phone:

  • You say: "Send a message to John about tomorrow's meeting"
  • The assistant automatically knows to use the "send message" tool
  • It figures out the parameters (recipient: John, message: meeting reminder)
  • It executes the action (after getting your permission)
  • You didn't have to tell it how — it figured it out from context

Example Scenarios for Tools

✅ Use Tools When:

Scenario 1: Database Query
User: "How many customers signed up last week?"
AI recognizes it needs to query database
AI automatically calls query_database tool
Parameters: {query: "SELECT COUNT(*) FROM users WHERE signup_date >= '2024-01-15'}"}
Returns: {count: 147}

Scenario 2: Sending Notifications
User: "Alert the team that deployment is complete"
AI determines it should use send_slack_message tool
Parameters: {channel: "#engineering", message: "✅ Production deployment complete"}
Executes after user confirms

Scenario 3: File Operations
User: "Create a summary.txt with the key points we discussed"
AI chooses write_file tool
Parameters: {path: "summary.txt", content: "Key Points:\n1. ..."}
File gets created

Technical Structure of a Tool

When a Server advertises a Tool, it includes a schema:

{
  "name": "send_email",
  "description": "Send an email to specified recipient",
  "inputSchema": {
    "type": "object",
    "properties": {
      "to": {
        "type": "string",
        "description": "Recipient email address"
      },
      "subject": {
        "type": "string",
        "description": "Email subject line"
      },
      "body": {
        "type": "string",
        "description": "Email body content"
      }
    },
    "required": ["to", "subject", "body"]
  }
}

When the AI calls this Tool:

Request:
{
  "method": "tools/call",
  "params": {
    "name": "send_email",
    "arguments": {
      "to": "john@company.com",
      "subject": "Meeting Tomorrow",
      "body": "Hi John, just confirming our 2pm meeting..."
    }
  }
}

Response:
{
  "content": [
    {
      "type": "text",
      "text": "Email sent successfully to john@company.com"
    }
  ]
}

The Critical Difference: Who's in Control?

Here's the fundamental distinction that determines whether something should be a Resource or a Tool:

Aspect Resources Tools
Who Decides? User or Application AI Model
Selection Method Explicit selection (UI, menu, attachment) Automatic invocation based on context
Purpose Provide reference material/context Perform actions/fetch dynamic data
Can Modify Data? ❌ No (read-only) ✅ Yes (can have side effects)
Discovery Browseable list for users Schema for AI to understand
Example Company handbook PDF Database query function
💡 Quick Decision Guide

Ask yourself: "Should the AI automatically access this when it thinks it's relevant?"

• YES → Make it a Tool (AI will auto-invoke)
• NO → Make it a Resource (User/app will explicitly provide)

Why This Distinction Matters

Getting this right affects both user experience and security:

❌ Wrong Choice Example 1: Database as Resource

If you expose your entire database as a Resource:
• User must manually tell AI "use the database" every single time
• AI can't proactively fetch needed data
• Results in clunky, frustrating experience

Better: Make specific queries Tools so AI can fetch data when needed
❌ Wrong Choice Example 2: Sensitive Docs as Tools

If you expose confidential documents as Tools:
• AI might automatically access sensitive salary data
• User has no explicit control over what gets read
• Potential privacy violations

Better: Make them Resources so users consciously choose what to share

🔄 Context Lifecycle: From Connection to Completion

Now let's understand how an MCP session actually works from start to finish.

Every MCP connection follows a strict lifecycle — a series of phases that ensure everything works correctly.

The Five Phases

Phase 1: INITIALIZATION ↓ Phase 2: CAPABILITY NEGOTIATION ↓ Phase 3: DISCOVERY ↓ Phase 4: OPERATION (Active Use) ↓ Phase 5: SHUTDOWN

Let's walk through each phase in detail with real message examples.

Phase 1: Initialization 🚀

This is the "handshake" phase where Client and Server introduce themselves.

What happens:

  • Client sends an initialize request
  • Both sides declare what protocol version they support
  • Both sides advertise their capabilities
  • They agree on what features to use together

Example Message Flow:

Client → Server:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": {
        "listChanged": true
      },
      "sampling": {}
    },
    "clientInfo": {
      "name": "Claude Desktop",
      "version": "1.2.0"
    }
  }
}

Server → Client:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {},
      "resources": {
        "subscribe": true,
        "listChanged": true
      },
      "prompts": {},
      "logging": {}
    },
    "serverInfo": {
      "name": "File System Server",
      "version": "2.0.1"
    }
  }
}

Client → Server (Confirmation):
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}
🎓 Why Capability Negotiation?

Imagine you're Client version 1.0 and Server version 2.0 has features you don't support.
During initialization, you both agree: "Let's only use the features we BOTH understand"

This prevents errors and allows old/new versions to work together!

Phase 2: Capability Negotiation ✅

Based on initialization, both sides now know what the other can do.

Example negotiation outcomes:

  • Server supports Tools ✅ → Client can call tools
  • Server supports Resources ✅ → Client can read resources
  • Server supports Prompts ✅ → Client can use prompt templates
  • Server supports Logging ✅ → Client can receive debug logs
  • Client supports Sampling ✅ → Server can request AI completions

Features both sides don't share? Simply not used in this session.

Phase 3: Discovery 🔍

Now that the connection is established, the Client asks: "What can you actually do?"

The Server responds with lists of available capabilities.

Discovering Tools:

Client → Server:
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}

Server → Client:
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "read_file",
        "description": "Read contents of a file from the file system",
        "inputSchema": {
          "type": "object",
          "properties": {
            "path": {
              "type": "string",
              "description": "Absolute path to the file"
            }
          },
          "required": ["path"]
        }
      },
      {
        "name": "write_file",
        "description": "Write content to a file",
        "inputSchema": {
          "type": "object",
          "properties": {
            "path": {"type": "string"},
            "content": {"type": "string"}
          },
          "required": ["path", "content"]
        }
      }
    ]
  }
}

Discovering Resources:

Client → Server:
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "resources/list"
}

Server → Client:
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "resources": [
      {
        "uri": "file:///home/user/docs/README.md",
        "name": "Project README",
        "description": "Main project documentation",
        "mimeType": "text/markdown"
      },
      {
        "uri": "file:///home/user/config.json",
        "name": "Configuration File",
        "description": "Application settings",
        "mimeType": "application/json"
      }
    ]
  }
}
✅ Dynamic Discovery Benefits

• Capabilities can change while session is active
• Server can add/remove tools based on user permissions
• Client doesn't need to know capabilities in advance
• Extremely flexible and adaptable

Phase 4: Operation (Active Use) 🎬

This is where the actual work happens! The Client uses the discovered capabilities.

Example: Calling a Tool

Client → Server:
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/home/user/docs/report.txt"
    }
  }
}

Server → Client:
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Q4 Sales Report\n\nTotal Revenue: $1.2M\nGrowth: +23%..."
      }
    ]
  }
}

Example: Reading a Resource

Client → Server:
{
  "jsonrpc": "2.0",
  "id": 11,
  "method": "resources/read",
  "params": {
    "uri": "file:///home/user/docs/README.md"
  }
}

Server → Client:
{
  "jsonrpc": "2.0",
  "id": 11,
  "result": {
    "contents": [
      {
        "uri": "file:///home/user/docs/README.md",
        "mimeType": "text/markdown",
        "text": "# Project Documentation\n\n## Overview\nThis project..."
      }
    ]
  }
}

During this phase, the session is stateful — the connection remains open and both sides can send messages back and forth.

Phase 5: Shutdown 🛑

When the session is done, it's gracefully terminated.

Proper shutdown sequence:

1. Client sends shutdown request:
{
  "jsonrpc": "2.0",
  "id": 99,
  "method": "shutdown"
}

2. Server acknowledges:
{
  "jsonrpc": "2.0",
  "id": 99,
  "result": {}
}

3. Client sends final exit notification:
{
  "jsonrpc": "2.0",
  "method": "exit"
}

4. Connection closes
⚠️ Why Proper Shutdown Matters

Abrupt disconnection can leave resources locked, files open, or transactions incomplete.
Always shut down gracefully to ensure clean state!

🔍 Capability Discovery: How AI Knows What's Available

One of MCP's most powerful features is dynamic capability discovery.

Unlike traditional APIs where you need to read documentation, MCP lets Clients automatically discover what a Server can do — at runtime!

The Discovery Methods

Servers expose three discovery endpoints:

  • tools/list → Returns all available Tools
  • resources/list → Returns all available Resources
  • prompts/list → Returns all available Prompts

Each response includes rich metadata so the Client (and AI) can understand how to use each capability.

What Gets Discovered?

For each capability, the Server provides:

Metadata Description Example
Name Unique identifier "calculate_sum"
Description What it does "Adds two numbers together"
Schema Input parameters (Tools only) {a: number, b: number}
URI Address (Resources only) "file:///data/file.txt"
MIME Type Content type (Resources only) "text/plain"

How AI Uses Discovery Information

When the AI model receives discovery data, it can:

  • Understand available actions: "I can read files, send emails, and query databases"
  • Know required parameters: "To send email, I need: to, subject, and body"
  • Choose appropriate tools: "User asked about weather → I should use get_weather tool"
  • Validate inputs: "This tool requires a string, but I have a number — let me convert it"
🎓 Why This Is Revolutionary

Traditional approach: Write custom code for every integration
MCP approach: AI reads the schema and figures out how to use it automatically!

It's like giving AI the ability to read an instruction manual and follow it — no human coding required.

Dynamic Updates: When Capabilities Change

MCP supports real-time updates! If capabilities change during a session, the Server can notify the Client.

Example: New Tool Added

Server → Client (Notification):
{
  "jsonrpc": "2.0",
  "method": "notifications/tools/list_changed"
}

When the Client receives this notification, it can call tools/list again to get the updated list.

This enables scenarios like:

  • User authenticates → New private tools become available
  • Plugin installed → Additional capabilities exposed
  • Permission granted → Restricted resources unlocked

🎨 Visual Exercise: AI Interacting with Two MCP Tools

Let's solidify your understanding with a concrete example. We'll diagram how an AI model interacts with two different MCP tools: a Calculator and a File Reader.

Scenario: "Calculate the average of numbers in data.txt"

User asks Claude: "Calculate the average of the numbers in data.txt"

Let's trace the complete flow:

┌─────────────────────────────────────────────────────────┐ │ USER │ │ "Calculate the average of numbers in data.txt" │ └────────────────────┬────────────────────────────────────┘ │ ⬍ (1) User Query │ ┌────────────────────▼────────────────────────────────────┐ │ CLAUDE DESKTOP (HOST) │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ CLAUDE AI MODEL │ │ │ │ Analyzes: "I need to: │ │ │ │ 1. Read file data.txt │ │ │ │ 2. Parse the numbers │ │ │ │ 3. Calculate average" │ │ │ └──────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────┴───────────┐ │ │ │ │ │ │ ⬍ (2a) ⬍ (2b) │ │ "Need read_file" "Need calculate" │ │ │ │ │ │ ┌──────▼──────┐ ┌──────▼──────┐ │ │ │ CLIENT 1 │ │ CLIENT 2 │ │ │ │ (File Sys) │ │ (Calculator)│ │ │ └──────┬──────┘ └──────┬──────┘ │ └─────────┼───────────────────────┼───────────────────────┘ │ │ ⬍ (3a) ⬍ (3b) JSON-RPC Request JSON-RPC Request │ │ ┌─────────▼─────────┐ ┌─────────▼─────────┐ │ FILE SYSTEM │ │ CALCULATOR │ │ MCP SERVER │ │ MCP SERVER │ │ │ │ │ │ Tool: read_file │ │ Tool: calculate │ │ ├─ path │ │ ├─ operation │ │ └─ encoding │ │ ├─ numbers[] │ │ │ │ └─ precision │ └─────────┬─────────┘ └─────────┬─────────┘ │ │ ⬍ (4a) ⬍ (4b) "5\n10\n15\n20" "12.5" (file contents) (result) │ │ │ │ ┌─────────▼───────────────────────▼─────────────────────┐ │ CLAUDE DESKTOP (HOST) │ │ │ │ Claude AI receives both results: │ │ • File contents: "5\n10\n15\n20" │ │ • Calculation result: 12.5 │ │ │ │ Composes response: │ │ "I've calculated the average of the numbers in │ │ data.txt. The numbers are 5, 10, 15, and 20, │ │ and their average is 12.5" │ └───────────────────────┬───────────────────────────────┘ │ ⬍ (5) Final Response │ ┌───────────────────────▼───────────────────────────────┐ │ USER │ │ Sees: "...their average is 12.5" │ └───────────────────────────────────────────────────────┘

Breaking Down Each Step

Step 1: User Input

User types natural language request into Claude Desktop

Step 2a & 2b: AI Analysis

Claude's AI model breaks down the task:
→ Need to read file → Use File System Server's read_file tool
→ Need to calculate average → Use Calculator Server's calculate tool

Step 3a: File Read Request

{
  "jsonrpc": "2.0",
  "id": 101,
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/home/user/data.txt"
    }
  }
}

Step 4a: File Read Response

{
  "jsonrpc": "2.0",
  "id": 101,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "5\n10\n15\n20"
      }
    ]
  }
}

Step 3b: Calculate Request

{
  "jsonrpc": "2.0",
  "id": 102,
  "method": "tools/call",
  "params": {
    "name": "calculate",
    "arguments": {
      "operation": "average",
      "numbers": [5, 10, 15, 20]
    }
  }
}

Step 4b: Calculate Response

{
  "jsonrpc": "2.0",
  "id": 102,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "12.5"
      }
    ]
  }
}

Step 5: Final Response

Claude combines both results into natural language:
"I've read the file and calculated the average. The numbers are 5, 10, 15, and 20, and their average is 12.5."

✅ Key Observations

1. Parallel Clients: Host manages two separate Clients simultaneously
2. AI Orchestration: AI decides which tools to use and in what order
3. Isolation: Each server operates independently
4. Composition: AI combines results from multiple tools into coherent answer

🚫 Common Beginner Mistakes

Mistake #1: Confusing Resources and Tools

❌ Wrong:

"I want AI to automatically access my database, so I'll make it a Resource"

Problem: Resources are application-controlled, not model-controlled.
AI won't automatically access Resources — user must explicitly provide them.
✅ Correct:

Make database queries Tools so AI can invoke them when needed.
Make static database documentation Resources that users can attach if they want reference material.

Mistake #2: Skipping Initialization Handshake

❌ Wrong:

Immediately calling tools/list after connecting, without initializing.

Problem: Server hasn't negotiated capabilities yet.
Request will be rejected with "Session not initialized" error.
✅ Correct:

Always follow the lifecycle:
1. Send initialize
2. Wait for response
3. Send notifications/initialized
4. THEN start using capabilities

Mistake #3: Not Handling Dynamic Changes

❌ Wrong:

Calling tools/list once at startup and assuming the list never changes.

Problem: Servers can add/remove capabilities dynamically.
Your cached list becomes stale and incorrect.
✅ Correct:

Listen for change notifications:
• notifications/tools/list_changed
• notifications/resources/list_changed

When received, re-fetch the updated list.

Mistake #4: Forgetting User Permissions

❌ Wrong:

Auto-executing tools without asking user permission first.

Problem: Violates user trust and MCP security model.
AI could perform unintended destructive actions.
✅ Correct:

Before calling any tool:
1. Show user what will be executed
2. Ask for explicit approval
3. Only execute after confirmation

Exception: Tools marked as "safe" or "read-only" might have auto-approval policies.

Mistake #5: Not Validating Schemas

❌ Wrong:

Trusting that AI will always send correct parameters to tools.

Problem: AI can make mistakes or be given malformed data.
Your server crashes or behaves unpredictably.
✅ Correct:

Always validate incoming parameters against your schema:
• Check required fields are present
• Verify data types match
• Validate ranges and formats
• Return clear error messages if validation fails

🎓 Advanced Tips for Mastery

Tip #1: Design Tools for Composition

Build small, focused tools that can be combined rather than one giant "do everything" tool.

Example:

❌ Monolithic Tool:

analyze_and_send_report → Queries database, generates report, emails it

Problem: Can't reuse parts, hard to test, inflexible
✅ Composable Tools:

• query_sales_data → Get raw data
• generate_report → Format data into report
• send_email → Send via email

AI can now mix and match: query → save to file, query → show in chat, etc.

Tip #2: Use URI Templates for Dynamic Resources

Instead of listing thousands of individual files, expose a template pattern.

Example:

{
  "resourceTemplates": [
    {
      "uriTemplate": "file:///{path}",
      "name": "File System Access",
      "description": "Access any file by path",
      "mimeType": "text/plain"
    }
  ]
}

Now Clients can construct URIs like file:///home/user/docs/report.txt dynamically!

Tip #3: Implement Progress Notifications

For long-running operations, send progress updates to improve UX.

Server → Client (during tool execution):
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {
    "progressToken": "operation-123",
    "progress": 45,
    "total": 100,
    "message": "Processing file 45 of 100..."
  }
}

Users see: "⏳ Processing... 45%" instead of just waiting with no feedback.

Tip #4: Support Resource Subscriptions

For Resources that change frequently, allow Clients to subscribe to updates.

Client subscribes:
{
  "jsonrpc": "2.0",
  "id": 50,
  "method": "resources/subscribe",
  "params": {
    "uri": "stock://AAPL/price"
  }
}

Server sends updates automatically:
{
  "jsonrpc": "2.0",
  "method": "notifications/resources/updated",
  "params": {
    "uri": "stock://AAPL/price"
  }
}

Now the Client gets real-time updates without polling!

Tip #5: Provide Rich Error Context

When tools fail, give AI (and users) actionable information.

❌ Poor Error:

{"code": -32000, "message": "Error"}

Useless! AI can't help user fix the problem.
✅ Rich Error:

{
  "code": -32602,
  "message": "Invalid file path",
  "data": {
    "providedPath": "/invalid/path/file.txt",
    "reason": "Directory does not exist",
    "suggestion": "Check that /invalid/path/ exists and you have read permissions"
  }
}


AI can now tell user exactly what went wrong and how to fix it!

📊 Quick Summary & Key Takeaways

Client vs Server

• Host = Main application users interact with
• Client = Connector component (inside Host, one per Server)
• Server = Capability provider (Tools, Resources, Prompts)
• Each Client ↔ Server connection is isolated for security
Tools vs Resources

• Tools = Model-controlled actions (AI decides when to use)
• Resources = Application-controlled data (User/app decides what to provide)
• Tools can modify data, Resources are read-only
• Use Tools for dynamic actions, Resources for reference material
Context Lifecycle

1. Initialization → Handshake and capability negotiation
2. Discovery → Client asks "what can you do?"
3. Operation → Active use of capabilities
4. Shutdown → Graceful termination
Capability Discovery

• Dynamic, runtime discovery (no hardcoding needed)
• Rich metadata (names, descriptions, schemas)
• Supports real-time updates via notifications
• AI reads schemas and understands how to use tools automatically

✨ Final Thoughts

Understanding MCP's core concepts is like learning the grammar of a new language.

At first, the distinctions between Client and Server, Tools and Resources, might seem abstract. But as you use MCP, these concepts become second nature.

You'll start thinking in terms of:

  • "This should be a Tool because AI needs to invoke it automatically"
  • "This should be a Resource because users should explicitly choose it"
  • "I need to handle the initialization handshake before discovery"
  • "I should send notifications when my capabilities change"

These concepts aren't just theoretical — they're the foundation for building powerful, flexible AI integrations that work reliably at scale.

🎯 Remember the Core Principles:

1. Clear separation of concerns (Host, Client, Server)
2. Tools = AI-controlled, Resources = User-controlled
3. Always follow the lifecycle (init → discover → operate → shutdown)
4. Dynamic discovery enables flexibility
5. Security through isolation and permissions

Comments