mirror of
https://github.com/coleam00/context-engineering-intro.git
synced 2025-12-29 16:14:56 +00:00
MCP Server Example with PRPs
This commit is contained in:
28
use-cases/mcp-server/PRPs/ai_docs/claude_api_usage.md
Normal file
28
use-cases/mcp-server/PRPs/ai_docs/claude_api_usage.md
Normal file
@@ -0,0 +1,28 @@
|
||||
### Example usage of the Anthropic API for Claude (model and API key are both environment variables)
|
||||
|
||||
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': this.apiKey,
|
||||
'anthropic-version': '2023-06-01'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.model,
|
||||
max_tokens: 3000,
|
||||
messages: [{
|
||||
role: 'user',
|
||||
content: this.buildPRPParsingPrompt(prpContent, projectContext, config)
|
||||
}]
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Anthropic API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const content = (result as any).content[0].text;
|
||||
|
||||
// Parse the JSON response
|
||||
const aiTasks = JSON.parse(content);
|
||||
491
use-cases/mcp-server/PRPs/ai_docs/mcp_patterns.md
Normal file
491
use-cases/mcp-server/PRPs/ai_docs/mcp_patterns.md
Normal file
@@ -0,0 +1,491 @@
|
||||
# MCP Server Development Patterns
|
||||
|
||||
This document contains proven patterns for developing Model Context Protocol (MCP) servers using TypeScript and Cloudflare Workers, based on the implementation in this codebase.
|
||||
|
||||
## Core MCP Server Architecture
|
||||
|
||||
### Base Server Class Pattern
|
||||
|
||||
```typescript
|
||||
import { McpAgent } from "agents/mcp";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
// Authentication props from OAuth flow
|
||||
type Props = {
|
||||
login: string;
|
||||
name: string;
|
||||
email: string;
|
||||
accessToken: string;
|
||||
};
|
||||
|
||||
export class CustomMCP extends McpAgent<Env, Record<string, never>, Props> {
|
||||
server = new McpServer({
|
||||
name: "Your MCP Server Name",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
// CRITICAL: Implement cleanup for Durable Objects
|
||||
async cleanup(): Promise<void> {
|
||||
try {
|
||||
// Close database connections
|
||||
await closeDb();
|
||||
console.log('Database connections closed successfully');
|
||||
} catch (error) {
|
||||
console.error('Error during database cleanup:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL: Durable Objects alarm handler
|
||||
async alarm(): Promise<void> {
|
||||
await this.cleanup();
|
||||
}
|
||||
|
||||
// Initialize all tools and resources
|
||||
async init() {
|
||||
// Register tools here
|
||||
this.registerTools();
|
||||
|
||||
// Register resources if needed
|
||||
this.registerResources();
|
||||
}
|
||||
|
||||
private registerTools() {
|
||||
// Tool registration logic
|
||||
}
|
||||
|
||||
private registerResources() {
|
||||
// Resource registration logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Tool Registration Pattern
|
||||
|
||||
```typescript
|
||||
// Basic tool registration
|
||||
this.server.tool(
|
||||
"toolName",
|
||||
"Tool description for the LLM",
|
||||
{
|
||||
param1: z.string().describe("Parameter description"),
|
||||
param2: z.number().optional().describe("Optional parameter"),
|
||||
},
|
||||
async ({ param1, param2 }) => {
|
||||
try {
|
||||
// Tool implementation
|
||||
const result = await performOperation(param1, param2);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Success: ${JSON.stringify(result, null, 2)}`
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Tool error:', error);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${error.message}`,
|
||||
isError: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Conditional Tool Registration (Based on Permissions)
|
||||
|
||||
```typescript
|
||||
// Permission-based tool availability
|
||||
const ALLOWED_USERNAMES = new Set<string>([
|
||||
'admin1',
|
||||
'admin2'
|
||||
]);
|
||||
|
||||
// Register privileged tools only for authorized users
|
||||
if (ALLOWED_USERNAMES.has(this.props.login)) {
|
||||
this.server.tool(
|
||||
"privilegedTool",
|
||||
"Tool only available to authorized users",
|
||||
{ /* parameters */ },
|
||||
async (params) => {
|
||||
// Privileged operation
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Privileged operation executed by: ${this.props.login}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Database Integration Patterns
|
||||
|
||||
### Database Connection Pattern
|
||||
|
||||
```typescript
|
||||
import { withDatabase, validateSqlQuery, isWriteOperation, formatDatabaseError } from "./database";
|
||||
|
||||
// Database operation with connection management
|
||||
async function performDatabaseOperation(sql: string) {
|
||||
try {
|
||||
// Validate SQL query
|
||||
const validation = validateSqlQuery(sql);
|
||||
if (!validation.isValid) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Invalid SQL query: ${validation.error}`,
|
||||
isError: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// Execute with automatic connection management
|
||||
return await withDatabase(this.env.DATABASE_URL, async (db) => {
|
||||
const results = await db.unsafe(sql);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `**Query Results**\n\`\`\`sql\n${sql}\n\`\`\`\n\n**Results:**\n\`\`\`json\n${JSON.stringify(results, null, 2)}\n\`\`\`\n\n**Rows returned:** ${Array.isArray(results) ? results.length : 1}`
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Database operation error:', error);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Database error: ${formatDatabaseError(error)}`,
|
||||
isError: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Read vs Write Operation Handling
|
||||
|
||||
```typescript
|
||||
// Check if operation is read-only
|
||||
if (isWriteOperation(sql)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Write operations are not allowed with this tool. Use the privileged tool if you have write permissions.",
|
||||
isError: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication & Authorization Patterns
|
||||
|
||||
### OAuth Integration Pattern
|
||||
|
||||
```typescript
|
||||
import OAuthProvider from "@cloudflare/workers-oauth-provider";
|
||||
import { GitHubHandler } from "./github-handler";
|
||||
|
||||
// OAuth configuration
|
||||
export default new OAuthProvider({
|
||||
apiHandlers: {
|
||||
'/sse': MyMCP.serveSSE('/sse') as any,
|
||||
'/mcp': MyMCP.serve('/mcp') as any,
|
||||
},
|
||||
authorizeEndpoint: "/authorize",
|
||||
clientRegistrationEndpoint: "/register",
|
||||
defaultHandler: GitHubHandler as any,
|
||||
tokenEndpoint: "/token",
|
||||
});
|
||||
```
|
||||
|
||||
### User Permission Checking
|
||||
|
||||
```typescript
|
||||
// Permission validation pattern
|
||||
function hasPermission(username: string, operation: string): boolean {
|
||||
const WRITE_PERMISSIONS = new Set(['admin1', 'admin2']);
|
||||
const READ_PERMISSIONS = new Set(['user1', 'user2', ...WRITE_PERMISSIONS]);
|
||||
|
||||
switch (operation) {
|
||||
case 'read':
|
||||
return READ_PERMISSIONS.has(username);
|
||||
case 'write':
|
||||
return WRITE_PERMISSIONS.has(username);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling Patterns
|
||||
|
||||
### Standardized Error Response
|
||||
|
||||
```typescript
|
||||
// Error response pattern
|
||||
function createErrorResponse(error: Error, operation: string) {
|
||||
console.error(`${operation} error:`, error);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `${operation} failed: ${error.message}`,
|
||||
isError: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Database Error Formatting
|
||||
|
||||
```typescript
|
||||
// Use the built-in database error formatter
|
||||
import { formatDatabaseError } from "./database";
|
||||
|
||||
try {
|
||||
// Database operation
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Database error: ${formatDatabaseError(error)}`,
|
||||
isError: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Resource Registration Patterns
|
||||
|
||||
### Basic Resource Pattern
|
||||
|
||||
```typescript
|
||||
// Resource registration
|
||||
this.server.resource(
|
||||
"resource://example/{id}",
|
||||
"Resource description",
|
||||
async (uri) => {
|
||||
const id = uri.path.split('/').pop();
|
||||
|
||||
try {
|
||||
const data = await fetchResourceData(id);
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri.href,
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(data, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to fetch resource: ${error.message}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### Tool Testing Pattern
|
||||
|
||||
```typescript
|
||||
// Test tool functionality
|
||||
async function testTool(toolName: string, params: any) {
|
||||
try {
|
||||
const result = await server.callTool(toolName, params);
|
||||
console.log(`${toolName} test passed:`, result);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`${toolName} test failed:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Database Connection Testing
|
||||
|
||||
```typescript
|
||||
// Test database connectivity
|
||||
async function testDatabaseConnection() {
|
||||
try {
|
||||
await withDatabase(process.env.DATABASE_URL, async (db) => {
|
||||
const result = await db`SELECT 1 as test`;
|
||||
console.log('Database connection test passed:', result);
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Database connection test failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Input Validation
|
||||
|
||||
```typescript
|
||||
// Always validate inputs with Zod
|
||||
const inputSchema = z.object({
|
||||
query: z.string().min(1).max(1000),
|
||||
parameters: z.array(z.string()).optional()
|
||||
});
|
||||
|
||||
// In tool handler
|
||||
try {
|
||||
const validated = inputSchema.parse(params);
|
||||
// Use validated data
|
||||
} catch (error) {
|
||||
return createErrorResponse(error, "Input validation");
|
||||
}
|
||||
```
|
||||
|
||||
### SQL Injection Prevention
|
||||
|
||||
```typescript
|
||||
// Use the built-in SQL validation
|
||||
import { validateSqlQuery } from "./database";
|
||||
|
||||
const validation = validateSqlQuery(sql);
|
||||
if (!validation.isValid) {
|
||||
return createErrorResponse(new Error(validation.error), "SQL validation");
|
||||
}
|
||||
```
|
||||
|
||||
### Access Control
|
||||
|
||||
```typescript
|
||||
// Always check permissions before executing sensitive operations
|
||||
if (!hasPermission(this.props.login, 'write')) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Access denied: insufficient permissions",
|
||||
isError: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Patterns
|
||||
|
||||
### Connection Pooling
|
||||
|
||||
```typescript
|
||||
// Use the built-in connection pooling
|
||||
import { withDatabase } from "./database";
|
||||
|
||||
// The withDatabase function handles connection pooling automatically
|
||||
await withDatabase(databaseUrl, async (db) => {
|
||||
// Database operations
|
||||
});
|
||||
```
|
||||
|
||||
### Resource Cleanup
|
||||
|
||||
```typescript
|
||||
// Implement proper cleanup in Durable Objects
|
||||
async cleanup(): Promise<void> {
|
||||
try {
|
||||
// Close database connections
|
||||
await closeDb();
|
||||
|
||||
// Clean up other resources
|
||||
await cleanupResources();
|
||||
|
||||
console.log('Cleanup completed successfully');
|
||||
} catch (error) {
|
||||
console.error('Cleanup error:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Gotchas
|
||||
|
||||
### 1. Missing Cleanup Implementation
|
||||
- Always implement `cleanup()` method in Durable Objects
|
||||
- Handle database connection cleanup properly
|
||||
- Set up alarm handler for automatic cleanup
|
||||
|
||||
### 2. SQL Injection Vulnerabilities
|
||||
- Always use `validateSqlQuery()` before executing SQL
|
||||
- Never concatenate user input directly into SQL strings
|
||||
- Use parameterized queries when possible
|
||||
|
||||
### 3. Permission Bypasses
|
||||
- Check permissions for every sensitive operation
|
||||
- Don't rely on tool registration alone for security
|
||||
- Always validate user identity from props
|
||||
|
||||
### 4. Error Information Leakage
|
||||
- Use `formatDatabaseError()` to sanitize error messages
|
||||
- Don't expose internal system details in error responses
|
||||
- Log detailed errors server-side, return generic messages to client
|
||||
|
||||
### 5. Resource Leaks
|
||||
- Always use `withDatabase()` for database operations
|
||||
- Implement proper error handling in async operations
|
||||
- Clean up resources in finally blocks
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
```typescript
|
||||
// Environment type definition
|
||||
interface Env {
|
||||
DATABASE_URL: string;
|
||||
GITHUB_CLIENT_ID: string;
|
||||
GITHUB_CLIENT_SECRET: string;
|
||||
OAUTH_KV: KVNamespace;
|
||||
// Add other bindings as needed
|
||||
}
|
||||
```
|
||||
|
||||
### Wrangler Configuration Pattern
|
||||
|
||||
```toml
|
||||
# wrangler.toml
|
||||
name = "mcp-server"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-01-01"
|
||||
|
||||
[[kv_namespaces]]
|
||||
binding = "OAUTH_KV"
|
||||
id = "your-kv-namespace-id"
|
||||
|
||||
[env.production]
|
||||
# Production-specific configuration
|
||||
```
|
||||
|
||||
This document provides the core patterns for building secure, scalable MCP servers using the proven architecture in this codebase.
|
||||
Reference in New Issue
Block a user