## MCP Development Framework
### Phase 1: Deep Research and Planning
```
Before building an MCP server, clarify:
**Target Integration:**
- What service/API are you connecting?
- What operations should the AI be able to perform?
- What data formats are involved?
**Tool Design:**
1. List all tools the server will expose
2. Define input schemas (JSON Schema format)
3. Define expected outputs
4. Identify error conditions
**Security Considerations:**
- What credentials are needed?
- How will secrets be managed?
- What permissions/scopes are required?
- Rate limiting requirements?
```
### Phase 2: Server Structure
#### Basic MCP Server Template
```python
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
app = Server("my-mcp-server")
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="my_tool",
description="Clear description of what this tool does",
inputSchema={
"type": "object",
"properties": {
"param1": {
"type": "string",
"description": "What this parameter controls"
},
"param2": {
"type": "integer",
"description": "Numeric parameter with constraints",
"minimum": 1,
"maximum": 100
}
},
"required": ["param1"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "my_tool":
result = await process_my_tool(arguments)
return [TextContent(type="text", text=result)]
raise ValueError(f"Unknown tool: {name}")
async def main():
async with stdio_server() as (read_stream, write_stream):
await app.run(read_stream, write_stream)
if __name__ == "__main__":
asyncio.run(main())
```
### Phase 3: Tool Design Best Practices
```
Tool Design Principles:
1. **Clear Naming**
- Use verb_noun format: get_user, create_issue, search_documents
- Be specific: search_github_issues > search
2. **Descriptive Schemas**
- Every parameter needs a description
- Use constraints (min, max, enum, pattern)
- Mark required vs optional clearly
3. **Graceful Error Handling**
- Return structured error messages
- Include actionable guidance
- Don't expose internal implementation details
4. **Idempotency**
- Design operations to be safely retryable
- Use idempotency keys where appropriate
- Document side effects clearly
```
#### Example: GitHub Integration
```python
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="search_github_issues",
description="Search for issues in a GitHub repository by query string",
inputSchema={
"type": "object",
"properties": {
"owner": {
"type": "string",
"description": "Repository owner (user or organization)"
},
"repo": {
"type": "string",
"description": "Repository name"
},
"query": {
"type": "string",
"description": "Search query (supports GitHub search syntax)"
},
"state": {
"type": "string",
"enum": ["open", "closed", "all"],
"default": "open",
"description": "Filter by issue state"
},
"limit": {
"type": "integer",
"default": 10,
"minimum": 1,
"maximum": 100,
"description": "Maximum results to return"
}
},
"required": ["owner", "repo", "query"]
}
),
Tool(
name="create_github_issue",
description="Create a new issue in a GitHub repository",
inputSchema={
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"title": {
"type": "string",
"description": "Issue title",
"minLength": 1,
"maxLength": 256
},
"body": {
"type": "string",
"description": "Issue description (Markdown supported)"
},
"labels": {
"type": "array",
"items": {"type": "string"},
"description": "Labels to apply"
}
},
"required": ["owner", "repo", "title"]
}
)
]
```
### Phase 4: Security Implementation
```
Security Checklist:
1. **Credential Management**
- Never hardcode secrets
- Use environment variables
- Support secure credential storage
2. **Input Validation**
- Validate all inputs against schema
- Sanitize before use
- Reject malformed requests
3. **Rate Limiting**
- Implement request throttling
- Respect API rate limits
- Provide clear feedback on limits
4. **Logging**
- Log tool invocations (without secrets)
- Track errors for debugging
- Audit security-sensitive operations
```
```python
import os
from functools import wraps
def require_auth(func):
"""Decorator to ensure authentication is configured"""
@wraps(func)
async def wrapper(*args, **kwargs):
token = os.environ.get("GITHUB_TOKEN")
if not token:
raise RuntimeError(
"GITHUB_TOKEN environment variable not set. "
"Please configure authentication before using this tool."
)
return await func(*args, **kwargs)
return wrapper
@require_auth
async def call_github_api(endpoint: str, method: str = "GET", **kwargs):
"""Secure API call with authentication"""
headers = {
"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}",
"Accept": "application/vnd.github+json"
}
```
### Phase 5: Testing and Evaluation
```
Testing Strategy:
1. **Unit Tests**
- Test each tool in isolation
- Mock external API calls
- Verify error handling
2. **Integration Tests**
- Test actual API connectivity
- Verify authentication flows
- Test rate limiting behavior
3. **MCP Protocol Tests**
- Verify tool listing
- Test schema validation
- Ensure proper response format
```
```python
import pytest
from unittest.mock import AsyncMock, patch
@pytest.mark.asyncio
async def test_search_github_issues():
"""Test GitHub issue search returns expected format"""
mock_response = {
"items": [
{"number": 1, "title": "Bug report", "state": "open"},
{"number": 2, "title": "Feature request", "state": "open"}
]
}
with patch("aiohttp.ClientSession.get") as mock_get:
mock_get.return_value.__aenter__.return_value.json = AsyncMock(
return_value=mock_response
)
result = await search_github_issues(
owner="anthropics",
repo="claude-code",
query="bug"
)
assert len(result) == 2
assert result[0]["number"] == 1
```
## Project Structure
```
my-mcp-server/
├── pyproject.toml # Dependencies and metadata
├── README.md # Documentation
├── src/
│ └── my_mcp_server/
│ ├── __init__.py
│ ├── server.py # Main MCP server
│ ├── tools/ # Tool implementations
│ │ ├── __init__.py
│ │ ├── github.py
│ │ └── slack.py
│ └── utils/
│ ├── auth.py
│ └── rate_limit.py
├── tests/
│ ├── test_tools.py
│ └── test_integration.py
└── claude_desktop_config.json # Example config
```
## Configuration Example
```json
{
"mcpServers": {
"my-mcp-server": {
"command": "python",
"args": ["-m", "my_mcp_server"],
"env": {
"GITHUB_TOKEN": "${GITHUB_TOKEN}",
"LOG_LEVEL": "INFO"
}
}
}
}
```
## Best Practices
1. **Keep Tools Focused**: One tool, one purpose
2. **Fail Gracefully**: Helpful error messages over silent failures
3. **Document Everything**: Clear descriptions for AI understanding
4. **Version Your API**: Plan for backward compatibility
5. **Monitor Usage**: Log and track tool invocations
6. **Test Thoroughly**: Both unit and integration tests
## Related Resources
- [MCP Specification](https://modelcontextprotocol.io/)
- [MCP Python SDK](https://github.com/anthropics/mcp-python-sdk)
- [MCP TypeScript SDK](https://github.com/anthropics/mcp-typescript-sdk)
- [Example MCP Servers](https://github.com/anthropics/mcp-servers)