Capstone: Build and Deploy a Multi-Tool MCP Server
Apply everything from the course to build a complete multi-tool MCP server. Combine tools, resources, and prompts into a production-ready project assistant.
Premium Course Content
This lesson is part of a premium course. Upgrade to Pro to unlock all premium courses and content.
- Access all premium courses
- 1000+ AI skill templates included
- New content added weekly
You’ve learned the architecture, built tools, resources, and prompts, connected to databases and APIs, and secured your servers. Now bring it all together.
In this capstone, you’ll build a complete “Project Assistant” MCP server that helps developers manage a software project — combining every concept from the course into one deployable package.
🔄 Quick Recall: Across this course, you’ve learned MCP architecture (Lesson 2), built your first server (Lesson 3), mastered tools (Lesson 4), resources and prompts (Lesson 5), real-world patterns (Lesson 6), and security (Lesson 7). This capstone integrates all of them.
The Project: Developer Project Assistant
You’ll build an MCP server that helps a developer manage their project by combining:
- Tools: Query a project database, manage tasks, search code files
- Resources: Project README, database schema, team directory
- Prompts: Code review template, sprint planning template
- Security: Read-only database by default, path-restricted file access, input validation
Server Blueprint
Project Assistant MCP Server
├── Tools
│ ├── query_tasks — Search and filter project tasks
│ ├── create_task — Add new tasks to the database
│ ├── search_code — Find code patterns in project files
│ └── get_git_log — Show recent commits
├── Resources
│ ├── project://readme — Project documentation
│ ├── project://schema — Database table structure
│ └── project://team — Team member directory
└── Prompts
├── code_review — Structured code review template
└── sprint_plan — Sprint planning conversation starter
✅ Quick Check: Looking at the blueprint above, which components are model-controlled (AI decides when to use), which are app-controlled (client fetches), and which are user-controlled (user selects)? (Answer: Tools are model-controlled — the AI calls query_tasks or search_code when needed. Resources are app-controlled — the client loads README and schema as context. Prompts are user-controlled — the developer explicitly selects code_review or sprint_plan to start a workflow.)
Building the Server
Here’s the complete server structure, applying patterns from every lesson:
Core Setup
import os
import sqlite3
import subprocess
from mcp.server.fastmcp import FastMCP
from mcp.types import UserMessage
mcp = FastMCP("Project Assistant")
# Configuration from environment
PROJECT_DIR = os.environ.get("PROJECT_DIR", os.getcwd())
DB_PATH = os.environ.get("DB_PATH", "tasks.db")
Resources (Lesson 5)
@mcp.resource("project://readme")
def get_readme() -> str:
"""Project README for context."""
readme_path = os.path.join(PROJECT_DIR, "README.md")
if os.path.exists(readme_path):
return open(readme_path).read()
return "No README.md found in project directory."
@mcp.resource("project://schema")
def get_schema() -> str:
"""Database schema for task queries."""
return """
Table: tasks
- id INTEGER PRIMARY KEY
- title TEXT NOT NULL
- status TEXT (open, in_progress, done)
- assignee TEXT
- priority TEXT (low, medium, high, critical)
- created_at TIMESTAMP
- updated_at TIMESTAMP
"""
Tools (Lessons 4, 6)
@mcp.tool()
def query_tasks(
status: str = "",
assignee: str = "",
priority: str = ""
) -> str:
"""Search project tasks. Filter by status, assignee, or priority."""
conditions = []
params = []
if status:
conditions.append("status = ?")
params.append(status)
if assignee:
conditions.append("assignee = ?")
params.append(assignee)
if priority:
conditions.append("priority = ?")
params.append(priority)
where = " AND ".join(conditions)
sql = "SELECT id, title, status, assignee, priority FROM tasks"
if where:
sql += f" WHERE {where}"
sql += " ORDER BY created_at DESC LIMIT 20"
conn = sqlite3.connect(DB_PATH)
rows = conn.execute(sql, params).fetchall()
conn.close()
if not rows:
return "No tasks match your filters."
lines = ["ID | Title | Status | Assignee | Priority"]
for row in rows:
lines.append(f"{row[0]} | {row[1]} | {row[2]} | {row[3]} | {row[4]}")
return "\n".join(lines)
Notice: parameterized queries (? placeholders) prevent SQL injection, and results are limited to 20 rows.
File Access with Security (Lesson 7)
@mcp.tool()
def search_code(pattern: str, file_extension: str = "") -> str:
"""Search for a text pattern in project files."""
if len(pattern) < 2:
return "Error: search pattern must be at least 2 characters"
cmd = ["grep", "-rn", "--include", f"*.{file_extension}" if file_extension else "*", pattern]
try:
result = subprocess.run(
cmd, cwd=PROJECT_DIR,
capture_output=True, text=True, timeout=10
)
if result.stdout:
lines = result.stdout.strip().split("\n")[:20]
return "\n".join(lines)
return f"No matches found for '{pattern}'"
except subprocess.TimeoutExpired:
return "Search timed out — try a more specific pattern"
The search runs only within PROJECT_DIR (set via environment variable) and has a 10-second timeout.
Prompts (Lesson 5)
@mcp.prompt()
def code_review(filepath: str) -> list:
"""Perform a structured code review."""
return [UserMessage(
content=f"""Review the file at {filepath}. Analyze:
1. **Bugs** — Logic errors, edge cases, null/undefined handling
2. **Security** — Input validation, injection risks, auth issues
3. **Performance** — Unnecessary allocations, algorithmic complexity
4. **Readability** — Naming clarity, function length, comments
For each issue, cite the line number and suggest a specific fix."""
)]
@mcp.prompt()
def sprint_plan(sprint_name: str) -> list:
"""Plan a new sprint with task prioritization."""
return [UserMessage(
content=f"""Let's plan sprint '{sprint_name}'. Help me:
1. Review open tasks (use query_tasks to see what's available)
2. Prioritize by impact and urgency
3. Assign tasks based on team capacity
4. Identify blockers and dependencies
5. Set sprint goals (2-3 measurable outcomes)"""
)]
Testing Your Server
Test every aspect before deploying:
Functional Tests
| Test | What to Verify |
|---|---|
| Query tasks with no filters | Returns all tasks (up to 20) |
| Query with status=“open” | Only returns open tasks |
| Search code for a known pattern | Finds the right files |
| Get README resource | Returns project documentation |
| Use code_review prompt | Creates structured review messages |
Security Tests
| Test | Expected Result |
|---|---|
Search with ../../etc/passwd pattern | Stays within PROJECT_DIR |
Query with SQL injection: '; DROP TABLE tasks; -- | Uses parameterized query, no injection |
| Very long input (10,000 characters) | Rejected or truncated |
| Search with 1-character pattern | Returns error (minimum 2 chars) |
Edge Cases
| Test | Expected Result |
|---|---|
| Query tasks when database is empty | “No tasks match your filters” |
| Search for pattern that doesn’t exist | “No matches found” |
| README resource when file doesn’t exist | “No README.md found” |
Deploying for Your Team
Step 1: Containerize
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY server.py .
EXPOSE 8080
CMD ["python", "server.py", "--transport", "http", "--port", "8080"]
Step 2: Share Configuration
Create a sample claude_desktop_config.json for teammates:
{
"mcpServers": {
"project-assistant": {
"command": "python",
"args": ["/path/to/server.py"],
"env": {
"PROJECT_DIR": "/path/to/your/project",
"DB_PATH": "/path/to/tasks.db"
}
}
}
}
Step 3: Document
Write a README covering: what the server does, how to install it, which tools are available, and how to configure it.
Course Recap
| Lesson | Core Skill | Key Takeaway |
|---|---|---|
| 1. Welcome | Understanding MCP | MCP standardizes AI-tool connections (M+N vs M×N) |
| 2. Architecture | Protocol knowledge | Host → Client → Server, JSON-RPC 2.0, stdio vs HTTP |
| 3. First Server | Building basics | @mcp.tool(), type hints, Claude Desktop config |
| 4. Tools Deep Dive | Production tools | Input validation, error handling, async, design patterns |
| 5. Resources & Prompts | All three primitives | Resources for data, Prompts for templates, Tools for actions |
| 6. Real-World Servers | System integration | Database, API, and filesystem connection patterns |
| 7. Security | Production readiness | OAuth 2.1, least privilege, input validation, deployment |
| 8. Capstone | Full integration | Multi-tool server combining all patterns |
Where to Go Next
Explore existing servers: Browse the 3,000+ community MCP servers at mcp.so or the official GitHub repository.
Build for your team: Identify the top 3 manual tasks in your workflow and build MCP tools for them.
Contribute to the ecosystem: Publish your server to the MCP registry. The community is growing fast, and every new server makes the ecosystem more valuable.
Key Takeaways
- A well-designed MCP server combines Tools, Resources, and Prompts for a complete experience
- Single-responsibility tools with clear names outperform do-everything tools
- Test functional behavior, security boundaries, and edge cases before deploying
- Containerize with Docker and provide config templates for team distribution
- MCP is the emerging standard — the skills you’ve built transfer across Claude, ChatGPT, Gemini, and every future MCP client
Knowledge Check
Complete the quiz above first
Lesson completed!