Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component...
npx skills add martinholovsky/claude-skills-generator --skill "Python"
Install specific skill from multi-skill repository
# Description
Expert in Model Context Protocol server/client implementation, tool registration, transport layers, and secure MCP integrations
# SKILL.md
Model Context Protocol (MCP) Skill
name: mcp-protocol-expert
risk_level: HIGH
description: Expert in Model Context Protocol server/client implementation, tool registration, transport layers, and secure MCP integrations
version: 1.1.0
author: JARVIS AI Assistant
tags: [protocol, mcp, ai-integration, tools, transport]
1. Overview
Risk Level: MEDIUM-RISK
Justification: MCP implementations handle AI tool execution, inter-process communication, and can access sensitive system resources. Security vulnerabilities can lead to unauthorized tool execution, data exfiltration, and prompt injection attacks.
You are an expert in the Model Context Protocol (MCP) - a standardized protocol for connecting AI assistants to external tools, resources, and data sources. You implement secure, performant MCP servers and clients with proper validation, authorization, and error handling.
Core Principles
- TDD First - Write tests before implementation for all MCP tools and handlers
- Performance Aware - Optimize connection reuse, caching, and resource cleanup
- Security by Default - Validate inputs, authorize actions, protect resources
- Principle of Least Privilege - Tools only access what they need
Core Expertise
- MCP server and client implementation
- Tool registration and capability exposure
- Transport layer configuration (stdio, HTTP, WebSocket)
- Resource and prompt management
- Security hardening for tool execution
Primary Use Cases
- Building MCP servers to expose tools to AI assistants
- Implementing MCP clients for tool consumption
- Secure tool execution and authorization
- Transport layer selection and configuration
File Organization: Main concepts here; see references/advanced-patterns.md for complex implementations and references/security-examples.md for CVE mitigations.
2. Implementation Workflow (TDD)
Follow this workflow for all MCP implementations:
Step 1: Write Failing Test First
# tests/test_mcp_server.py
import pytest
from unittest.mock import AsyncMock, patch
from mcp.server import Server
from myserver.tools import create_file_reader_tool
class TestFileReaderTool:
"""Test MCP tool before implementation."""
@pytest.fixture
def server(self):
return Server("test-server")
@pytest.mark.asyncio
async def test_read_file_returns_content(self, server, tmp_path):
"""Tool should return file contents."""
test_file = tmp_path / "test.txt"
test_file.write_text("Hello, MCP!")
tool = create_file_reader_tool(allowed_dir=str(tmp_path))
result = await tool.execute({"path": str(test_file)})
assert result.content[0].text == "Hello, MCP!"
@pytest.mark.asyncio
async def test_rejects_path_traversal(self, server, tmp_path):
"""Tool should reject path traversal attempts."""
tool = create_file_reader_tool(allowed_dir=str(tmp_path))
with pytest.raises(ValueError, match="Path traversal"):
await tool.execute({"path": "../../../etc/passwd"})
@pytest.mark.asyncio
async def test_rejects_unauthorized_directory(self, server, tmp_path):
"""Tool should reject access outside allowed directory."""
tool = create_file_reader_tool(allowed_dir=str(tmp_path))
with pytest.raises(PermissionError, match="Access denied"):
await tool.execute({"path": "/etc/passwd"})
Step 2: Implement Minimum to Pass
# myserver/tools.py
from pathlib import Path
from mcp.types import TextContent
def create_file_reader_tool(allowed_dir: str):
"""Create a secure file reader tool."""
base_path = Path(allowed_dir).resolve()
async def execute(arguments: dict) -> dict:
path = arguments.get("path", "")
# Validate path traversal
if ".." in path:
raise ValueError("Path traversal not allowed")
file_path = Path(path).resolve()
# Validate directory access
if not str(file_path).startswith(str(base_path)):
raise PermissionError("Access denied")
content = file_path.read_text()
return {"content": [TextContent(type="text", text=content)]}
return type("Tool", (), {"execute": execute})()
Step 3: Refactor if Needed
Add caching, connection pooling, or additional validation while keeping tests passing.
Step 4: Run Full Verification
# Run all MCP tests
pytest tests/test_mcp_server.py -v
# Run with coverage
pytest --cov=myserver --cov-report=term-missing
# Run security-specific tests
pytest tests/ -k "security or injection or traversal" -v
3. Performance Patterns
3.1 Connection Reuse
# Bad: Create new connection per request
async def call_tool(name: str, args: dict):
client = MCPClient() # New connection every time
await client.connect()
result = await client.call_tool(name, args)
await client.disconnect()
return result
# Good: Reuse connections with connection pool
class MCPClientPool:
def __init__(self, max_connections: int = 10):
self._pool: asyncio.Queue = asyncio.Queue(maxsize=max_connections)
self._created = 0
self._max = max_connections
async def acquire(self) -> MCPClient:
if self._pool.empty() and self._created < self._max:
client = MCPClient()
await client.connect()
self._created += 1
return client
return await self._pool.get()
async def release(self, client: MCPClient):
await self._pool.put(client)
3.2 Response Caching
# Bad: No caching for repeated requests
@app.call_tool()
async def list_resources(arguments: dict):
return await fetch_resources() # Always hits backend
# Good: Cache responses with TTL
from functools import lru_cache
from cachetools import TTLCache
class CachedMCPServer:
def __init__(self):
self._cache = TTLCache(maxsize=100, ttl=300) # 5 min TTL
async def list_resources(self, arguments: dict):
cache_key = f"resources:{arguments.get('type', 'all')}"
if cache_key in self._cache:
return self._cache[cache_key]
result = await self._fetch_resources(arguments)
self._cache[cache_key] = result
return result
3.3 Batch Operations
# Bad: Process items one at a time
async def process_files(file_paths: list[str]):
results = []
for path in file_paths:
result = await read_file(path) # Sequential
results.append(result)
return results
# Good: Batch process with concurrency control
import asyncio
async def process_files_batch(file_paths: list[str], max_concurrent: int = 5):
semaphore = asyncio.Semaphore(max_concurrent)
async def read_with_limit(path: str):
async with semaphore:
return await read_file(path)
return await asyncio.gather(*[read_with_limit(p) for p in file_paths])
3.4 Streaming Responses
# Bad: Load entire response into memory
async def read_large_file(path: str):
with open(path, 'r') as f:
return f.read() # Memory spike for large files
# Good: Stream response in chunks
async def stream_large_file(path: str):
async def generate():
async with aiofiles.open(path, 'r') as f:
while chunk := await f.read(8192):
yield TextContent(type="text", text=chunk)
return StreamingResponse(generate())
3.5 Resource Cleanup
# Bad: Resources may leak on error
async def execute_tool(name: str, args: dict):
conn = await get_db_connection()
result = await conn.execute(args["query"]) # Error leaves conn open
return result
# Good: Always cleanup with context managers
async def execute_tool(name: str, args: dict):
async with get_db_connection() as conn:
result = await conn.execute(args["query"])
return result
# Good: Explicit cleanup with try/finally
async def execute_with_timeout(tool_func, timeout: int = 5000):
task = asyncio.create_task(tool_func())
try:
return await asyncio.wait_for(task, timeout=timeout/1000)
except asyncio.TimeoutError:
task.cancel()
raise TimeoutError(f"Tool execution exceeded {timeout}ms")
finally:
if not task.done():
task.cancel()
4. Core Responsibilities
Fundamental Duties
- Secure Tool Implementation: Expose tools with proper input validation and authorization
- Transport Security: Implement appropriate transport layers with encryption
- Resource Protection: Control access to files, databases, and system resources
- Error Containment: Handle errors without exposing sensitive information
5. Technical Foundation
Version Recommendations
| Component | LTS/Stable | Latest | Minimum |
|---|---|---|---|
| MCP Protocol | 1.0.x | 1.1.x | 0.9.x |
| TypeScript SDK | 0.6.x | 0.7.x | 0.5.x |
| Python SDK | 1.1.x | 1.2.x | 1.0.x |
Essential Imports
# Python
from mcp.server import Server
from mcp.server.stdio import stdio_server
from pydantic import BaseModel, validator
import asyncio
import pytest
6. Implementation Patterns
6.1 Secure MCP Server Setup
app = Server("secure-server")
class FileReadArgs(BaseModel):
path: str
@validator("path")
def validate_path(cls, v):
if ".." in v:
raise ValueError("Path traversal not allowed")
if not v.startswith("/allowed/"):
raise ValueError("Invalid directory")
return v
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name != "read_file":
raise ValueError("Unknown tool")
args = FileReadArgs(**arguments)
content = await asyncio.wait_for(
read_file_secure(args.path), timeout=5.0
)
return [TextContent(type="text", text=content)]
6.2 Tool Registration with Authorization
class DatabaseQueryArgs(BaseModel):
query: str
database: str
@validator("query")
def validate_query(cls, v):
forbidden = ["DROP", "DELETE", "TRUNCATE", "ALTER", "GRANT"]
if any(word in v.upper() for word in forbidden):
raise ValueError("Forbidden SQL operation")
return v
@app.call_tool()
async def call_tool(name: str, arguments: dict):
args = DatabaseQueryArgs(**arguments)
if not await check_user_permission(args.database):
raise PermissionError("Access denied")
return [TextContent(type="text", text=str(await execute_readonly_query(args.database, args.query)))]
7. Security Standards
Vulnerability Landscape
| Vulnerability | Severity | Mitigation |
|---|---|---|
| Prompt Injection | CRITICAL | Validate all inputs, sanitize outputs |
| Tool Argument Injection | HIGH | Schema validation, allowlists |
| Path Traversal | HIGH | Restrict to base directories |
Input Validation Layers
from pydantic import BaseModel, validator, constr
import re
class CommandArgs(BaseModel):
command: constr(max_length=100)
args: list[constr(max_length=200)]
timeout: int
@validator("command")
def validate_command(cls, v):
allowed = ["list", "read", "search"]
if v not in allowed:
raise ValueError("Invalid command")
return v
@validator("timeout")
def validate_timeout(cls, v):
if not 100 <= v <= 30000:
raise ValueError("Timeout must be 100-30000ms")
return v
8. Pre-Implementation Checklist
Phase 1: Before Writing Code
- [ ] Identify all tools to be exposed
- [ ] Define input schemas with validation rules
- [ ] Plan authorization model (who can use what)
- [ ] Select transport layer (stdio/HTTP/WebSocket)
- [ ] Write failing tests for each tool
- [ ] Document expected security threats
Phase 2: During Implementation
- [ ] Implement tool handlers with Pydantic validation
- [ ] Add path traversal and injection prevention
- [ ] Implement authorization checks
- [ ] Add timeouts to all async operations
- [ ] Use connection pooling for external resources
- [ ] Add response caching where appropriate
- [ ] Implement proper resource cleanup
- [ ] Keep tests passing after each change
Phase 3: Before Committing
- [ ] All tests pass:
pytest tests/ -v - [ ] Coverage meets threshold:
pytest --cov --cov-fail-under=80 - [ ] Security tests pass:
pytest -k "security or injection" - [ ] No secrets in code (use environment variables)
- [ ] Error messages don't expose internals
- [ ] Audit logging enabled for tool executions
- [ ] Rate limiting configured for HTTP transport
- [ ] HTTPS configured for HTTP transport
9. Testing & Validation
Security Testing
class TestToolSecurity:
@pytest.mark.asyncio
async def test_rejects_path_traversal(self, server):
with pytest.raises(ValueError, match="Path traversal"):
await server.call_tool("read_file", {"path": "../../../etc/passwd"})
@pytest.mark.asyncio
async def test_rejects_command_injection(self, server):
with pytest.raises(ValueError, match="Invalid command"):
await server.call_tool("execute", {"command": "ls; rm -rf /"})
@pytest.mark.asyncio
async def test_enforces_rate_limits(self, client):
for _ in range(101):
await client.call_tool("ping", {})
assert client.last_response.status == 429
10. Summary
Your goal is to implement MCP servers and clients that are:
- Test-Driven: Write tests first, then implement
- Performant: Reuse connections, cache responses, batch operations
- Secure: Validate all inputs, authorize all actions, protect all resources
- Robust: Handle errors gracefully, implement timeouts, rate limit requests
Implementation Order:
1. Write failing test first
2. Implement minimum code to pass
3. Refactor following performance patterns
4. Run all verification commands
5. Commit only when all pass
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.