| name | mcp-complete-guide |
| description | Complete 11-phase guide for building production-ready MCP (Model Context Protocol) servers with semantic layer integration. Covers foundation to deployment, including agent-centric design, tool development, testing, error handling, performance optimization, monitoring, security, governance, and semantic layer integration for business metrics. Use when building enterprise-grade MCP servers that integrate with dbt, Tableau, or other semantic layers for Finance SSC, business analytics, or data governance use cases. |
Complete MCP Development Guide: Phases 1-11
A Comprehensive Guide to Building Production-Ready MCP Servers
From Foundation to Semantic Layer Integration
Table of Contents
- Phase 1: Foundation & Planning
- Phase 2: Core Implementation
- Phase 3: Tool Development
- Phase 4: Testing & Validation
- Phase 5: Error Handling & Resilience
- Phase 6: Performance Optimization
- Phase 7: Monitoring & Observability
- Phase 8: Documentation & Examples
- Phase 9: Security & Governance
- Phase 10: Production Deployment
- Phase 11: Semantic Layer Integration
Phase 1: Foundation & Planning
Overview
Before writing any code, invest time in deep research and strategic planning. This phase sets the foundation for a high-quality MCP server.
1.1 Understand Agent-Centric Design
Build for Workflows, Not Just API Endpoints:
- Don't simply wrap existing API endpoints - build thoughtful, high-impact workflow tools
- Consolidate related operations (e.g.,
schedule_event that both checks availability and creates event)
- Focus on tools that enable complete tasks, not just individual API calls
- Consider what workflows agents actually need to accomplish
Optimize for Limited Context:
- Agents have constrained context windows - make every token count
- Return high-signal information, not exhaustive data dumps
- Provide "concise" vs "detailed" response format options
- Default to human-readable identifiers over technical codes (names over IDs)
Design Actionable Error Messages:
- Error messages should guide agents toward correct usage patterns
- Suggest specific next steps: "Try using filter='active_only' to reduce results"
- Make errors educational, not just diagnostic
Follow Natural Task Subdivisions:
- Tool names should reflect how humans think about tasks
- Group related tools with consistent prefixes for discoverability
- Design tools around natural workflows, not just API structure
1.2 Study MCP Protocol Documentation
Load the complete MCP specification:
https://modelcontextprotocol.io/llms-full.txt
This comprehensive document contains the complete MCP specification and guidelines.
1.3 Study Framework Documentation
For Python implementations:
- Python SDK Documentation:
https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md
- Review FastMCP patterns and best practices
For Node/TypeScript implementations:
- TypeScript SDK Documentation:
https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md
- Review MCP SDK patterns
1.4 Exhaustive API Research
To integrate a service, read through ALL available API documentation:
- Official API reference documentation
- Authentication and authorization requirements
- Rate limiting and pagination patterns
- Error responses and status codes
- Available endpoints and their parameters
- Data models and schemas
1.5 Create Implementation Plan
Based on your research, create a detailed plan:
Tool Selection:
- List the most valuable endpoints/operations to implement
- Prioritize tools that enable the most common and important use cases
- Consider which tools work together to enable complex workflows
Shared Utilities:
- Identify common API request patterns
- Plan pagination helpers
- Design filtering and formatting utilities
- Plan error handling strategies
Input/Output Design:
- Define input validation models (Pydantic for Python, Zod for TypeScript)
- Design consistent response formats (JSON or Markdown)
- Plan for large-scale usage (thousands of users/resources)
- Implement character limits and truncation strategies (e.g., 25,000 tokens)
Error Handling Strategy:
- Plan graceful failure modes
- Design clear, actionable, LLM-friendly error messages
- Consider rate limiting and timeout scenarios
- Handle authentication and authorization errors
Phase 2: Core Implementation
Overview
With a comprehensive plan in place, begin systematic implementation following language-specific best practices.
2.1 Project Structure Setup
Python Structure
project/
├── src/
│ ├── __init__.py
│ ├── server.py
│ ├── tools.py
│ ├── utils.py
│ └── models.py
├── tests/
│ ├── test_tools.py
│ └── test_utils.py
├── requirements.txt
├── pyproject.toml
└── README.md
TypeScript Structure
project/
├── src/
│ ├── index.ts # Main MCP server
│ ├── tools/ # Tool implementations
│ ├── utils/ # Shared utilities
│ └── types.ts # Type definitions
├── tests/
├── package.json
├── tsconfig.json
└── README.md
2.2 Server Initialization
Python (FastMCP)
from mcp import FastMCP
from pydantic import BaseModel, Field
import httpx
import asyncio
mcp = FastMCP("your-service-name")
CHARACTER_LIMIT = 25000
API_BASE_URL = "https://api.example.com"
TypeScript (MCP SDK)
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server({
name: "your-service-name",
version: "1.0.0"
}, {
capabilities: {
tools: {}
}
});
2.3 Implement Core Infrastructure
Create shared utilities before implementing tools:
API Request Helper
async def make_api_request(
endpoint: str,
method: str = "GET",
params: dict | None = None,
data: dict | None = None
) -> dict:
"""Make authenticated API request with error handling."""
async with httpx.AsyncClient() as client:
response = await client.request(
method=method,
url=f"{API_BASE_URL}/{endpoint}",
params=params,
json=data,
headers={"Authorization": f"Bearer {API_TOKEN}"},
timeout=30.0
)
response.raise_for_status()
return response.json()
Response Formatting
def format_response(data: dict, format_type: str = "json") -> str:
"""Format response as JSON or Markdown."""
if format_type == "json":
return json.dumps(data, indent=2)
else:
return convert_to_markdown(data)
def truncate_content(content: str, max_chars: int = CHARACTER_LIMIT) -> str:
"""Truncate content with ellipsis if exceeds limit."""
if len(content) <= max_chars:
return content
return content[:max_chars] + "\n\n[Content truncated...]"
Pagination Helper
async def paginate_results(
endpoint: str,
max_results: int = 100,
page_size: int = 50
) -> list:
"""Fetch paginated results up to max_results."""
results = []
page = 1
while len(results) < max_results:
data = await make_api_request(
endpoint,
params={"page": page, "per_page": page_size}
)
if not data.get("items"):
break
results.extend(data["items"])
if not data.get("has_more"):
break
page += 1
return results[:max_results]
Phase 3: Tool Development
Overview
Implement tools systematically, following consistent patterns and best practices.
3.1 Tool Implementation Pattern
Complete Tool Example (Python)
from pydantic import BaseModel, Field
from typing import Literal
class SearchInput(BaseModel):
"""Input schema for search tool."""
query: str = Field(
description="Search query string",
min_length=1,
max_length=200
)
filter_type: Literal["all", "active", "archived"] = Field(
default="active",
description="Filter results by status"
)
max_results: int = Field(
default=50,
ge=1,
le=100,
description="Maximum number of results to return"
)
response_format: Literal["json", "markdown"] = Field(
default="markdown",
description="Format for response data"
)
@mcp.tool()
async def search_items(
query: str,
filter_type: str = "active",
max_results: int = 50,
response_format: str = "markdown"
) -> str:
"""
Search for items matching the query.
This tool searches across all items in the system and returns
matching results with their key attributes.
Args:
query: Search query string
filter_type: Filter by status (all/active/archived)
max_results: Maximum number of results (1-100)
response_format: Response format (json/markdown)
Returns:
Formatted search results with item details
Example:
>>> search_items("project alpha", filter_type="active", max_results=10)
Returns active items matching "project alpha"
Hints:
readOnly: true
destructive: false
idempotent: true
openWorld: true
"""
try:
params = {
"q": query,
"status": filter_type,
"limit": min(max_results, 100)
}
results = await make_api_request("search", params=params)
formatted = format_response(results, response_format)
return truncate_content(formatted)
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return "No items found matching your query. Try broader search terms."
elif e.response.status_code == 401:
return "Authentication failed. Please check API credentials."
else:
return f"Search failed: {str(e)}. Please try again."
except Exception as e:
return f"Unexpected error during search: {str(e)}"
3.2 Tool Design Checklist
For each tool, ensure:
- ✅ Clear Purpose: One-line description of what the tool does
- ✅ Input Validation: Pydantic/Zod schema with constraints
- ✅ Error Handling: Graceful handling of all error cases
- ✅ Actionable Errors: Error messages guide next steps
- ✅ Response Formats: Support JSON and Markdown
- ✅ Character Limits: Truncate long responses
- ✅ Tool Hints: Add readOnly, destructive, idempotent, openWorld
- ✅ Type Safety: Full type hints/types
- ✅ Documentation: Comprehensive docstrings/descriptions
- ✅ Examples: Usage examples in documentation
3.3 Common Tool Patterns
Read-Only List Tool
@mcp.tool()
async def list_resources(
limit: int = 50,
offset: int = 0,
response_format: str = "markdown"
) -> str:
"""
List all available resources.
Hints:
readOnly: true
destructive: false
idempotent: true
"""
results = await paginate_results("resources", max_results=limit)
return format_response(results, response_format)
Create/Update Tool (Destructive)
@mcp.tool()
async def create_resource(
name: str,
description: str,
metadata: dict | None = None
) -> str:
"""
Create a new resource.
Hints:
readOnly: false
destructive: true
idempotent: false
"""
data = {
"name": name,
"description": description,
"metadata": metadata or {}
}
result = await make_api_request("resources", method="POST", data=data)
return f"✅ Resource created successfully: {result['id']}"
Phase 4: Testing & Validation
Overview
Comprehensive testing ensures your MCP server works reliably in production scenarios.
4.1 Unit Testing
Python (pytest)
import pytest
from unittest.mock import AsyncMock, patch
from your_server import search_items, make_api_request
@pytest.mark.asyncio
async def test_search_items_success():
"""Test successful search returns formatted results."""
mock_response = {
"items": [
{"id": "1", "name": "Item 1"},
{"id": "2", "name": "Item 2"}
]
}
with patch('your_server.make_api_request', return_value=mock_response):
result = await search_items("test query")
assert "Item 1" in result
assert "Item 2" in result
assert len(result) < CHARACTER_LIMIT
@pytest.mark.asyncio
async def test_search_items_not_found():
"""Test 404 returns helpful error message."""
with patch('your_server.make_api_request', side_effect=httpx.HTTPStatusError(
"Not Found", request=None, response=AsyncMock(status_code=404)
)):
result = await search_items("nonexistent")
assert "No items found" in result
assert "broader search terms" in result
4.2 Evaluation Creation
Create 10 realistic evaluation questions to test agent effectiveness:
<evaluation>
<qa_pair>
<question>What are the top 5 most active projects in the last 30 days, and who are their lead contributors?</question>
<answer>Project Alpha (John Doe), Project Beta (Jane Smith), Project Gamma (Bob Johnson), Project Delta (Alice Williams), Project Epsilon (Charlie Brown)</answer>
</qa_pair>
</evaluation>
Evaluation Requirements
Each question must be:
- Independent: Not dependent on other questions
- Read-only: Only non-destructive operations required