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 β
ββββββββββββ΄βββββββββββ΄βββββββββ΄ββββββββββ
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.
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
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:
- Tools execute actions
- Resources provide data
- Prompts guide behavior
- 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:
- Standardization vs Flexibility: Can MCP Do Both? - How MCP balances standards with flexibility
- Sessions, States, and Continuity: MCPβs Secret Sauce - Deep dive into session management
- Breaking Down AI Silos: How MCP Creates Interoperability - How these concepts enable AI tool sharing
The foundation is set. Everything else builds on these four concepts.

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