The Model Context Protocol (MCP) is revolutionizing how we build AI-powered tools by providing a standardized way for LLMs to interact with external systems. In this guide, we’ll build a sophisticated task management system that uses agentic patterns to automatically organize, prioritize, and manage tasks intelligently.
What is MCP?
Model Context Protocol is an open protocol that enables seamless integration between LLM applications and external data sources. Instead of building custom integrations for every tool, MCP provides a universal interface that any LLM can use to access your tools and data.
MCP servers act as bridges between AI assistants (like Claude) and your applications. They expose tools, resources, and prompts that LLMs can discover and use automatically.
Architecture Overview
Our task manager will implement the following agentic pattern:
# High-level architecture
Task Input → Agent Analysis → Project Assignment → Task Creation → Dependency Resolution
Key Components
- MCP Server: Exposes task management tools to the LLM
- Agent Layer: Analyzes tasks and determines optimal project structure
- Task Engine: Manages task lifecycle and dependencies
- Storage Layer: Persists tasks and projects
Setting Up the MCP Server
First, let’s install the required dependencies:
pip install mcp anthropic-sdk pydantic sqlite-utils
Use a virtual environment to isolate dependencies. I recommend uv for fast Python package management.
Creating the Server Foundation
# task_manager_mcp.py
from mcp.server import Server
from mcp.types import Tool, TextContent
from pydantic import BaseModel
from typing import List, Optional
import sqlite3
import json
from datetime import datetime
# Data models
class Task(BaseModel):
id: Optional[int] = None
title: str
description: str
project_id: Optional[int] = None
priority: str = "medium" # low, medium, high, urgent
status: str = "pending" # pending, in_progress, completed
depends_on: List[int] = []
created_at: Optional[str] = None
class Project(BaseModel):
id: Optional[int] = None
name: str
description: str
created_at: Optional[str] = None
# Initialize MCP server
app = Server("task-manager")
# Database setup
def init_db():
conn = sqlite3.connect('tasks.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS projects
(id INTEGER PRIMARY KEY, name TEXT, description TEXT,
created_at TEXT)''')
c.execute('''CREATE TABLE IF NOT EXISTS tasks
(id INTEGER PRIMARY KEY, title TEXT, description TEXT,
project_id INTEGER, priority TEXT, status TEXT,
depends_on TEXT, created_at TEXT,
FOREIGN KEY (project_id) REFERENCES projects (id))''')
conn.commit()
conn.close()
init_db()
Implementing Agentic Tools
Now let’s create the tools that enable agentic behavior:
1. Create Project Tool
@app.tool()
async def create_project(name: str, description: str) -> str:
"""
Create a new project to organize related tasks.
The agent uses this when detecting multiple related tasks.
"""
conn = sqlite3.connect('tasks.db')
c = conn.cursor()
created_at = datetime.now().isoformat()
c.execute(
"INSERT INTO projects (name, description, created_at) VALUES (?, ?, ?)",
(name, description, created_at)
)
project_id = c.lastrowid
conn.commit()
conn.close()
return json.dumps({
"success": True,
"project_id": project_id,
"message": f"Created project '{name}' with ID {project_id}"
})
2. Create Task with Dependencies
@app.tool()
async def create_task(
title: str,
description: str,
project_id: Optional[int] = None,
priority: str = "medium",
depends_on: List[int] = None
) -> str:
"""
Create a new task. Supports dependency tracking.
Agent automatically infers priority and dependencies.
"""
conn = sqlite3.connect('tasks.db')
c = conn.cursor()
created_at = datetime.now().isoformat()
depends_on_json = json.dumps(depends_on or [])
c.execute(
"""INSERT INTO tasks
(title, description, project_id, priority, status, depends_on, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
(title, description, project_id, priority, "pending", depends_on_json, created_at)
)
task_id = c.lastrowid
conn.commit()
conn.close()
return json.dumps({
"success": True,
"task_id": task_id,
"message": f"Created task '{title}' with ID {task_id}"
})
3. Intelligent Task Analysis
@app.tool()
async def analyze_tasks(task_descriptions: List[str]) -> str:
"""
Analyze a list of task descriptions and suggest optimal structure.
This is the core agentic function that determines project grouping.
"""
# This tool returns analysis that helps the LLM organize tasks
# In practice, you might use embeddings or classification here
analysis = {
"suggested_projects": [],
"task_relationships": [],
"priority_recommendations": []
}
# Simple heuristic: group by common keywords
keywords = {}
for idx, desc in enumerate(task_descriptions):
words = desc.lower().split()
for word in words:
if len(word) > 4: # Ignore short words
if word not in keywords:
keywords[word] = []
keywords[word].append(idx)
# Find common themes
for keyword, task_indices in keywords.items():
if len(task_indices) >= 2:
analysis["suggested_projects"].append({
"theme": keyword.capitalize(),
"tasks": task_indices,
"reasoning": f"Tasks {task_indices} share the theme '{keyword}'"
})
return json.dumps(analysis, indent=2)
In production, use proper embeddings (OpenAI, Cohere) or a classification model for better task grouping. This example uses simple keyword matching for demonstration.
4. Get Task Dependencies
@app.tool()
async def get_task_graph(project_id: Optional[int] = None) -> str:
"""
Get task dependency graph for visualization and planning.
Helps identify blockers and critical path.
"""
conn = sqlite3.connect('tasks.db')
c = conn.cursor()
if project_id:
c.execute("SELECT * FROM tasks WHERE project_id = ?", (project_id,))
else:
c.execute("SELECT * FROM tasks")
tasks = c.fetchall()
conn.close()
graph = {
"nodes": [],
"edges": []
}
for task in tasks:
task_id, title, desc, proj_id, priority, status, deps, created = task
graph["nodes"].append({
"id": task_id,
"title": title,
"priority": priority,
"status": status
})
# Parse dependencies
if deps:
depends_on = json.loads(deps)
for dep_id in depends_on:
graph["edges"].append({
"from": dep_id,
"to": task_id
})
return json.dumps(graph, indent=2)
Running the Server
Create a runner script:
# run_server.py
import asyncio
from task_manager_mcp import app
async def main():
async with app.run_stdio():
await app.serve()
if __name__ == "__main__":
asyncio.run(main())
Start the server:
python run_server.py
Using with Claude Desktop
Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"task-manager": {
"command": "python",
"args": ["/path/to/run_server.py"]
}
}
}
Agentic Usage Examples
Now you can interact with Claude naturally:
Example 1: Simple Task Creation
You: I need to build a new API, write tests, and deploy it
Claude: I'll help organize this. Let me create a project and tasks with dependencies...
[Creates "API Development" project]
[Creates tasks: 1. Build API, 2. Write tests (depends on 1), 3. Deploy (depends on 2)]
Example 2: Complex Project
You: Create tasks for:
- Research user authentication methods
- Implement OAuth2 flow
- Add rate limiting
- Write API documentation
- Set up monitoring
- Create user dashboard
Claude: I've analyzed these tasks and identified two distinct projects...
[Creates "Backend Infrastructure" project for auth, rate limiting, monitoring]
[Creates "Frontend Development" project for dashboard]
[Automatically sets dependencies and priorities]
Advanced: Smart Priority Detection
Enhance the agent with priority inference:
@app.tool()
async def infer_priority(title: str, description: str, context: str) -> str:
"""
Use LLM to infer task priority based on context and keywords.
"""
# Keywords that indicate urgency
urgent_keywords = ["urgent", "asap", "critical", "immediately", "hotfix"]
high_keywords = ["important", "soon", "security", "bug", "broken"]
text = f"{title} {description} {context}".lower()
if any(kw in text for kw in urgent_keywords):
return "urgent"
elif any(kw in text for kw in high_keywords):
return "high"
elif "later" in text or "eventually" in text:
return "low"
else:
return "medium"
Testing the System
Create a test suite:
# test_task_manager.py
import pytest
import asyncio
from task_manager_mcp import create_project, create_task, analyze_tasks
@pytest.mark.asyncio
async def test_create_project():
result = await create_project("Test Project", "A test project")
data = json.loads(result)
assert data["success"] is True
assert "project_id" in data
@pytest.mark.asyncio
async def test_task_dependencies():
# Create project
proj = await create_project("Dev", "Development tasks")
proj_data = json.loads(proj)
proj_id = proj_data["project_id"]
# Create tasks with dependencies
task1 = await create_task("Setup", "Setup environment", proj_id)
task1_id = json.loads(task1)["task_id"]
task2 = await create_task(
"Build",
"Build feature",
proj_id,
depends_on=[task1_id]
)
# Verify dependency
task2_data = json.loads(task2)
assert task2_data["success"] is True
Production Deployment
For production use:
- Add authentication and rate limiting
- Use PostgreSQL instead of SQLite
- Implement proper error handling and logging
- Add webhooks for task status changes
- Create backup and recovery procedures
- Set up monitoring and alerting
Docker Deployment
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "run_server.py"]
Conclusion
You’ve now built a fully functional MCP task manager with agentic capabilities! The agent can:
- ✅ Automatically group related tasks into projects
- ✅ Infer task priorities from context
- ✅ Manage complex dependency graphs
- ✅ Provide intelligent task analysis
This foundation can be extended with features like:
- Time tracking and estimates
- Team member assignment
- Recurring tasks
- Integration with calendars and notification systems
- Advanced AI analysis using RAG or fine-tuned models
References
- Model Context Protocol Documentation Official MCP specification and implementation guide
- Anthropic MCP Python SDK Python SDK for building MCP servers
- Building Agentic Systems - Anthropic Best practices for designing agentic AI workflows
- Claude Desktop MCP Configuration How to integrate MCP servers with Claude Desktop
- SQLite JSON Functions SQLite documentation for handling JSON data types