Skip to main content
ai

The Core Concepts of MCP Explained with Examples

Angry Shark Studio
14 min
MCP Model Context Protocol AI Integration Tutorial Core Concepts Tools Resources Prompts Sessions

Your AI assistant just searched your database, read a configuration file, applied a template, and remembered your preference from five minutes ago. Four simple concepts made all of that possible.

Model Context Protocol has exactly four core concepts. Not forty. Not four hundred. Four.

Everything else in MCP builds on these foundations. Master these four concepts, and you understand how the entire protocol works.

This post explains each concept with practical code examples you can actually run. No theory. No abstractions. Real code that does real things.

The Four Core Concepts

Here’s the complete picture:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚             MCP CORE CONCEPTS           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Tools   β”‚Resources β”‚Prompts β”‚Sessions β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Actions  β”‚  Data    β”‚Templatesβ”‚ State  β”‚
β”‚ Execute  β”‚  Read    β”‚  Guide  β”‚ Persist β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

MCP Four Core Concepts Overview

One-line definitions:

  • Tools: Functions the AI can execute
  • Resources: Data the AI can read
  • Prompts: Templates for AI interactions
  • Sessions: Stateful connections between client and server

Think of MCP like a workshop:

  • Tools are your hammers and saws (actions you perform)
  • Resources are your materials and blueprints (data you reference)
  • Prompts are your instruction manuals (guidance you follow)
  • Sessions are your workbench state (context you maintain)

Let’s examine each concept in detail.

Concept 1: Tools - AI That Does Things

What Are Tools?

Tools are functions you expose to the AI. The AI can call these functions, pass parameters, and get results back.

Tools are the β€œverbs” of MCP. They perform actions: create, update, delete, calculate, send, execute.

Simple Tool Example

Here’s the most basic tool possible:

@mcp.tool()
async def add_numbers(a: float, b: float) -> float:
    """Add two numbers together"""
    return a + b

When you register this tool, the AI sees this description:

{
    "name": "add_numbers",
    "description": "Add two numbers together",
    "inputSchema": {
        "type": "object",
        "properties": {
            "a": {"type": "number"},
            "b": {"type": "number"}
        },
        "required": ["a", "b"]
    }
}

The AI understands: β€œI can call a function named add_numbers. It needs two numbers. It returns a number.”

Practical Tool Example

Real tools do useful things. Here’s a contact search tool:

@mcp.tool()
async def search_contacts(
    query: str,
    limit: int = 10
) -> list[dict[str, any]]:
    """Search contacts by name, email, or phone"""

    # Connect to database
    async with get_db_connection() as db:
        results = await db.fetch(
            """
            SELECT id, name, email, phone
            FROM contacts
            WHERE name ILIKE $1
               OR email ILIKE $1
               OR phone LIKE $1
            LIMIT $2
            """,
            f"%{query}%",
            limit
        )

    return [
        {
            "id": r["id"],
            "name": r["name"],
            "email": r["email"],
            "phone": r["phone"]
        }
        for r in results
    ]

This tool searches a database and returns matching contacts.

Tool Execution Flow

Here’s what happens when the AI uses a tool:

# 1. AI decides to use the tool
ai_request = {
    "method": "tools/call",
    "params": {
        "name": "search_contacts",
        "arguments": {
            "query": "john",
            "limit": 5
        }
    }
}

# 2. MCP server executes the function
result = await search_contacts("john", 5)

# 3. Server returns results to AI
{
    "result": [
        {"id": 1, "name": "John Smith", "email": "john.smith@example.com", ...},
        {"id": 2, "name": "John Doe", "email": "john.doe@example.com", ...}
    ]
}

The AI receives structured data it can use for the next step.

Common Tool Patterns

CRUD Operations - Create, read, update, delete data:

@mcp.tool()
async def create_task(title: str, due_date: str):
    """Create a new task"""
    return await db.execute(
        "INSERT INTO tasks (title, due_date) VALUES ($1, $2)",
        title, due_date
    )

External APIs - Fetch data from services:

@mcp.tool()
async def get_weather(city: str) -> dict:
    """Get current weather for a city"""
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"https://api.weather.com/current?city={city}"
        )
        return response.json()

System Operations - Interact with the system:

@mcp.tool()
async def list_files(directory: str) -> list[str]:
    """List files in a directory"""
    path = Path(directory)
    return [f.name for f in path.iterdir() if f.is_file()]

Calculations - Perform complex computations:

@mcp.tool()
async def calculate_roi(
    initial_investment: float,
    final_value: float,
    years: float
) -> dict:
    """Calculate return on investment"""
    total_return = ((final_value - initial_investment) / initial_investment) * 100
    annual_return = ((final_value / initial_investment) ** (1/years) - 1) * 100

    return {
        "total_return_percent": round(total_return, 2),
        "annual_return_percent": round(annual_return, 2),
        "profit": round(final_value - initial_investment, 2)
    }

Tools perform actions. If you want the AI to do something, create a tool.

Concept 2: Resources - AI That Reads

What Are Resources?

Resources are data sources the AI can access. Unlike tools, resources are read-only. They provide information without changing anything.

Resources are the β€œnouns” of MCP. They represent: files, configurations, metrics, lists, states.

Simple Resource Example

Here’s a basic configuration resource:

@mcp.resource(
    uri="config://app/settings",
    name="Application Settings"
)
async def get_app_settings() -> dict[str, any]:
    """Current application configuration"""
    return {
        "version": "1.0.0",
        "environment": "production",
        "features": {
            "dark_mode": True,
            "notifications": True
        }
    }

The AI can read this resource to understand your application’s configuration.

Dynamic Resource Example

Resources can provide live, changing data:

import psutil
from datetime import datetime

@mcp.resource(
    uri="metrics://system/performance",
    name="System Performance Metrics"
)
async def get_system_metrics() -> dict[str, any]:
    """Real-time system performance data"""

    return {
        "timestamp": datetime.now().isoformat(),
        "cpu_usage": psutil.cpu_percent(interval=1),
        "memory": {
            "used_percent": psutil.virtual_memory().percent,
            "available_gb": round(
                psutil.virtual_memory().available / (1024**3), 2
            )
        },
        "disk": {
            "used_percent": psutil.disk_usage('/').percent
        }
    }

Every time the AI reads this resource, it gets current system metrics.

Resource Templates

Resources can use URI templates to handle parameters:

import aiofiles

@mcp.resource(
    uri_template="file:///{path}",
    name="File Contents"
)
async def read_file(path: str) -> str:
    """Read contents of a text file"""

    # Validate path for security
    safe_path = validate_file_path(path)

    async with aiofiles.open(safe_path, 'r') as f:
        return await f.read()

The AI can request specific files: file:///docs/readme.md, file:///config/settings.json.

Resource vs Tool: When to Use Each

This is a common point of confusion. Here’s the rule:

Use a Resource when:

  • Reading data
  • No side effects
  • Idempotent (same result every time for same input)

Use a Tool when:

  • Modifying data
  • Has side effects
  • Performing an action

Examples:

# Resource: Read-only data access
@mcp.resource(uri="users://list")
async def list_users():
    """Get all users"""
    return await db.fetch("SELECT * FROM users")

# Tool: Performs an action
@mcp.tool()
async def create_user(name: str, email: str):
    """Create a new user"""
    return await db.execute(
        "INSERT INTO users (name, email) VALUES ($1, $2)",
        name, email
    )

If it changes something, use a tool. If it just reads something, use a resource.

MCP Tool vs Resource Decision Tree

Concept 3: Prompts - AI Guidance Templates

What Are Prompts?

Prompts are reusable interaction templates. They guide AI behavior consistently across multiple uses.

Prompts are the β€œinstructions” of MCP. They provide: formats, styles, constraints, examples.

Simple Prompt Example

Here’s a basic summarization prompt:

from typing import Literal

@mcp.prompt(
    name="summarize_text",
    description="Summarize text in a specific style"
)
async def summarize_prompt(
    text: str,
    style: Literal["brief", "detailed", "bullet_points"] = "brief"
) -> str:
    """Generate a summary prompt"""

    style_instructions = {
        "brief": "in 2-3 sentences",
        "detailed": "with key points and supporting details",
        "bullet_points": "as a bulleted list of main ideas"
    }

    return f"""
Please summarize the following text {style_instructions[style]}:

{text}

Summary:"""

This prompt template ensures consistent summarization across different texts.

Complex Prompt Template

Prompts can return multiple messages for conversation-style interactions:

@mcp.prompt(
    name="code_review",
    description="Perform code review with specific focus areas"
)
async def code_review_prompt(
    code: str,
    language: str,
    focus_areas: list[str] = None
) -> list[dict]:
    """Multi-message code review prompt"""

    messages = [
        {
            "role": "system",
            "content": f"You are a senior {language} developer performing a code review."
        },
        {
            "role": "user",
            "content": f"Please review this {language} code:"
        },
        {
            "role": "user",
            "content": f"```{language}\n{code}\n```"
        }
    ]

    if focus_areas:
        messages.append({
            "role": "user",
            "content": f"Focus especially on: {', '.join(focus_areas)}"
        })

    return messages

This creates a structured conversation for code review with customizable focus areas.

Prompt Arguments and Usage

The server defines available prompts:

# Server advertises prompts
prompts = [
    {
        "name": "debug_error",
        "description": "Help debug an error message",
        "arguments": [
            {
                "name": "error_message",
                "description": "The error text",
                "required": True
            },
            {
                "name": "context",
                "description": "Code context where error occurred",
                "required": False
            }
        ]
    }
]

The client uses prompts with arguments:

result = await client.get_prompt(
    "debug_error",
    arguments={
        "error_message": "TypeError: Cannot read property 'x' of undefined",
        "context": "const result = data.value.x + 1;"
    }
)

Prompts ensure AI interactions follow consistent patterns your team defines.

Concept 4: Sessions - Stateful Connections

What Are Sessions?

Sessions are persistent connections between client and server. They maintain state across multiple requests, enabling context-aware interactions.

Sessions are the β€œmemory” of MCP. They track: user preferences, conversation history, resource subscriptions, capabilities.

Session Lifecycle

Here’s the complete lifecycle of an MCP session:

# 1. Initialize connection
session = await client.initialize({
    "protocolVersion": "0.1.0",
    "capabilities": {
        "tools": {},
        "resources": {"subscribe": True}
    }
})

# 2. Session is active
print(f"Connected to {session.server_info.name}")
print(f"Session ID: {session.id}")

# 3. Use session for multiple operations
await session.call_tool("search_contacts", {"query": "jane"})
await session.read_resource("config://app/settings")
await session.call_tool("create_task", {"title": "Follow up with Jane"})

# 4. Clean shutdown
await session.close()

The session maintains context throughout all these operations.

Session State Management

Sessions can store and retrieve context:

from datetime import datetime
from typing import Any

class MCPSession:
    def __init__(self, session_id: str):
        self.id = session_id
        self.state = {}  # Session-specific state
        self.created_at = datetime.now()
        self.last_activity = datetime.now()

    async def set_context(self, key: str, value: Any):
        """Store session context"""
        self.state[key] = value
        self.last_activity = datetime.now()

    async def get_context(self, key: str) -> Any:
        """Retrieve session context"""
        return self.state.get(key)

# Usage in tools
@mcp.tool()
async def set_user_preference(
    session: MCPSession,
    preference: str,
    value: Any
):
    """Remember user preference for this session"""
    await session.set_context(f"pref_{preference}", value)
    return {"status": "preference saved"}

@mcp.tool()
async def get_user_preference(
    session: MCPSession,
    preference: str
):
    """Retrieve user preference"""
    value = await session.get_context(f"pref_{preference}")
    return {"preference": preference, "value": value}

The AI can set preferences once and have them remembered for the entire session.

Session Capabilities Negotiation

When a session starts, client and server negotiate what features to use:

# Client declares what it supports
client_capabilities = {
    "tools": {},  # Can call tools
    "resources": {"subscribe": True},  # Can subscribe to resources
    "prompts": {}  # Can use prompts
}

# Server responds with what it provides
server_capabilities = {
    "tools": {"descriptions": True},
    "resources": {"subscribe": True, "templates": True},
    "prompts": {"templates": True}
}

# Only features both support are active
# In this case: tools, resource subscription, prompts

This negotiation ensures compatibility even when client and server have different capabilities.

How Concepts Work Together

The four concepts combine to create complete workflows. Here’s a practical example: an email assistant.

Complete Example: Email Assistant

# 1. Session maintains user context
session = await client.initialize({
    "protocolVersion": "0.1.0",
    "capabilities": {
        "tools": {},
        "resources": {},
        "prompts": {}
    }
})

# 2. Use tool to search emails
emails = await session.call_tool(
    "search_emails",
    {"from": "boss@company.com", "unread": True}
)

# 3. Read resource for email templates
templates = await session.read_resource(
    "templates://email/responses"
)

# 4. Use prompt to draft response
draft = await session.get_prompt(
    "draft_email_response",
    {
        "original_email": emails[0],
        "template_style": "professional",
        "tone": "friendly"
    }
)

# 5. Tool to send the email
result = await session.call_tool(
    "send_email",
    {
        "to": emails[0]["from"],
        "subject": f"Re: {emails[0]['subject']}",
        "body": draft
    }
)

This workflow uses all four concepts:

  • Session: Maintains user email account context
  • Tools: Search emails, send email (actions)
  • Resources: Email templates (data)
  • Prompts: Response drafting guide (templates)

Concept Interaction Diagram

User Request: "Reply to unread emails from my boss"
           β”‚
           β–Ό
     [SESSION CONTEXT]
    Maintains user prefs,
    email account info
           β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β–Ό             β–Ό             β–Ό
  TOOL:      RESOURCE:      PROMPT:
  search      email         reply
  emails     templates     generator
    β”‚             β”‚             β”‚
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β–Ό
        TOOL:
      send_email

MCP Concepts Working Together

Each concept plays a specific role in the complete interaction.

Common Pitfalls and Solutions

Pitfall 1: Tool vs Resource Confusion

Wrong: Tool for reading data

@mcp.tool()
async def get_user_count():
    """Get total user count"""
    return await db.fetch("SELECT COUNT(*) FROM users")

This should be a resource because it only reads data.

Right: Resource for reading data

@mcp.resource(uri="stats://users/count")
async def get_user_count():
    """Get total user count"""
    result = await db.fetch("SELECT COUNT(*) FROM users")
    return {"count": result[0]["count"]}

Pitfall 2: Stateless Thinking

Wrong: Forgetting session context

@mcp.tool()
async def process_data(data: str):
    """Process data"""
    # Where's the user preference?
    return process(data)

Right: Using session state

@mcp.tool()
async def process_data(session: MCPSession, data: str):
    """Process data with user preferences"""
    preferences = await session.get_context("user_prefs")
    return process(data, preferences)

Pitfall 3: Overly Complex Prompts

Wrong: Kitchen sink prompt

@mcp.prompt()
async def do_everything(
    text, style, format, language, tone, length,
    audience, purpose, examples, constraints, ...
):
    # 20 parameters later...

This prompt tries to do too much.

Right: Focused, single-purpose

@mcp.prompt()
async def translate_text(text: str, target_language: str):
    """Simple translation prompt"""
    return f"Translate this text to {target_language}:\n\n{text}"

Keep prompts focused on one task.

Quick Reference Guide

Concept Cheatsheet

# TOOLS: Do things (actions)
@mcp.tool()
async def action(param):
    return do_something(param)

# RESOURCES: Read things (data)
@mcp.resource(uri="type://path")
async def data():
    return get_something()

# PROMPTS: Guide things (templates)
@mcp.prompt()
async def template(input):
    return f"Instruction: {input}"

# SESSIONS: Remember things (state)
await session.set_context("key", value)
value = await session.get_context("key")

Decision Tree

Does it change data?

  • Yes β†’ Use Tool
  • No β†’ Continue

Does it provide data?

  • Yes β†’ Use Resource
  • No β†’ Continue

Does it guide AI behavior?

  • Yes β†’ Use Prompt
  • No β†’ Continue

Does it need to persist across requests?

  • Yes β†’ Use Session state

Frequently Asked Questions

Q: Can a tool also be a resource?

No. Tools perform actions, resources provide data. If you need both, create two separate implementations.

Q: How long do sessions last?

Sessions last until explicitly closed or until timeout. Typical implementations use 30-minute to 1-hour timeouts.

Q: Can prompts call tools?

No. Prompts generate text that guides the AI. The AI then decides whether to call tools based on that guidance.

Q: Are sessions required?

Yes. MCP requires a session for all interactions. Sessions are how clients and servers communicate.

Q: Can resources subscribe to changes?

Yes, if both client and server support the subscribe capability. Resources can notify clients when data changes.

Next Steps

You now understand the four core concepts that power MCP:

  1. Tools execute actions
  2. Resources provide data
  3. Prompts guide behavior
  4. Sessions maintain state

These concepts combine to create AI integrations that are consistent, maintainable, and reliable.

In the next post, we’ll examine how these concepts form MCP’s architecture and how clients and servers use them to communicate.

Related Posts:

The foundation is set. Everything else builds on these four concepts.

Angry Shark Studio Logo

About Angry Shark Studio

Angry Shark Studio is a professional Unity AR/VR development studio specializing in mobile multiplatform applications and AI solutions. Our team includes Unity Certified Expert Programmers with extensive experience in AR/VR development.

Related Articles

More Articles

Explore more insights on Unity AR/VR development, mobile apps, and emerging technologies.

View All Articles

Need Help?

Have questions about this article or need assistance with your project?

Get in Touch