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:
32
use-cases/mcp-server/PRPs/INITIAL.md
Normal file
32
use-cases/mcp-server/PRPs/INITIAL.md
Normal file
@@ -0,0 +1,32 @@
|
||||
## FEATURE:
|
||||
|
||||
We want to create a MCP server using this repos template
|
||||
|
||||
The goal of the MCP server is to create a simple version of taskmaster mcp that instead of parsing PRDs we parse PRPs.
|
||||
|
||||
Additional features:
|
||||
|
||||
- LLM powered PRP information extraction using anthropic
|
||||
- Crud operation on tasks, documentation, tags, etc to and from the DB
|
||||
|
||||
We need tools for parsing PRPs this tool should take a filled PRP and use anthropic to extract the tasks into tasks and save them to the db, including surrounding documentation from the prp like the goals what whys, target users, etc.
|
||||
|
||||
We need:
|
||||
|
||||
- To be able to perform CRUD operations on tasks, documentation, tags, etc
|
||||
- A task fetch tool to get the tasks from the
|
||||
- To be able to list all tasks
|
||||
- To be able to add information to a task
|
||||
- To be able to fetch the additional documentation from the db
|
||||
- To be able to modify the additional documentation
|
||||
- DB tables needs to be updated to match our new data models
|
||||
|
||||
## EXAMPLES & DOCUMENTATION:
|
||||
|
||||
All examples are already referenced in prp_mcp_base.md - do any additional research as needed.
|
||||
|
||||
## OTHER CONSIDERATIONS:
|
||||
|
||||
- Do not use complex regex or complex parsing patterns, we use an LLM to parse PRPs.
|
||||
- Model and API key for Anthropic both need to be environment variables - these are set up in .dev.vars.example
|
||||
- It's very important that we create one task per file to keep concerns separate
|
||||
44
use-cases/mcp-server/PRPs/README.md
Normal file
44
use-cases/mcp-server/PRPs/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Product Requirement Prompt (PRP) Concept
|
||||
|
||||
"Over-specifying what to build while under-specifying the context, and how to build it, is why so many AI-driven coding attempts stall at 80%. A Product Requirement Prompt (PRP) fixes that by fusing the disciplined scope of a classic Product Requirements Document (PRD) with the “context-is-king” mindset of modern prompt engineering."
|
||||
|
||||
## What is a PRP?
|
||||
|
||||
Product Requirement Prompt (PRP)
|
||||
A PRP is a structured prompt that supplies an AI coding agent with everything it needs to deliver a vertical slice of working software—no more, no less.
|
||||
|
||||
### How it differs from a PRD
|
||||
|
||||
A traditional PRD clarifies what the product must do and why customers need it, but deliberately avoids how it will be built.
|
||||
|
||||
A PRP keeps the goal and justification sections of a PRD yet adds three AI-critical layers:
|
||||
|
||||
### Context
|
||||
|
||||
- Precise file paths and content, library versions and library context, code snippets examples. LLMs generate higher-quality code when given direct, in-prompt references instead of broad descriptions. Usage of a ai_docs/ directory to pipe in library and other docs.
|
||||
|
||||
### Implementation Details and Strategy
|
||||
|
||||
- In contrast of a traditional PRD, a PRP explicitly states how the product will be built. This includes the use of API endpoints, test runners, or agent patterns (ReAct, Plan-and-Execute) to use. Usage of typehints, dependencies, architectural patterns and other tools to ensure the code is built correctly.
|
||||
|
||||
### Validation Gates
|
||||
|
||||
- Deterministic checks such as pytest, ruff, or static type passes “Shift-left” quality controls catch defects early and are cheaper than late re-work.
|
||||
Example: Each new funtion should be individaully tested, Validation gate = all tests pass.
|
||||
|
||||
### PRP Layer Why It Exists
|
||||
|
||||
- The PRP folder is used to prepare and pipe PRPs to the agentic coder.
|
||||
|
||||
## Why context is non-negotiable
|
||||
|
||||
Large-language-model outputs are bounded by their context window; irrelevant or missing context literally squeezes out useful tokens
|
||||
|
||||
The industry mantra “Garbage In → Garbage Out” applies doubly to prompt engineering and especially in agentic engineering: sloppy input yields brittle code
|
||||
|
||||
## In short
|
||||
|
||||
A PRP is PRD + curated codebase intelligence + agent/runbook—the minimum viable packet an AI needs to plausibly ship production-ready code on the first pass.
|
||||
|
||||
The PRP can be small and focusing on a single task or large and covering multiple tasks.
|
||||
The true power of PRP is in the ability to chain tasks together in a PRP to build, self-validate and ship complex features.
|
||||
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.
|
||||
538
use-cases/mcp-server/PRPs/templates/prp_mcp_base.md
Normal file
538
use-cases/mcp-server/PRPs/templates/prp_mcp_base.md
Normal file
@@ -0,0 +1,538 @@
|
||||
---
|
||||
name: "MCP Server PRP Template"
|
||||
description: This template is designed to provide a production-ready Model Context Protocol (MCP) server using the proven patterns from this codebase.
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Template optimized for AI agents to implement production-ready Model Context Protocol (MCP) servers with GitHub OAuth authentication, database integration, and Cloudflare Workers deployment using the proven patterns from this codebase.
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Context is King**: Include ALL necessary MCP patterns, authentication flows, and deployment configurations
|
||||
2. **Validation Loops**: Provide executable tests from TypeScript compilation to production deployment
|
||||
3. **Security First**: Build-in authentication, authorization, and SQL injection protection
|
||||
4. **Production Ready**: Include monitoring, error handling, and deployment automation
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Build a production-ready MCP (Model Context Protocol) server with:
|
||||
|
||||
- [SPECIFIC MCP FUNCTIONALITY] - describe the specific tools and resources to implement
|
||||
- GitHub OAuth authentication with role-based access control
|
||||
- Cloudflare Workers deployment with monitoring
|
||||
- [ADDITIONAL FEATURES] - any specific features beyond the base authentication/database
|
||||
|
||||
## Why
|
||||
|
||||
- **Developer Productivity**: Enable secure AI assistant access to [SPECIFIC DATA/OPERATIONS]
|
||||
- **Enterprise Security**: GitHub OAuth with granular permission system
|
||||
- **Scalability**: Cloudflare Workers global edge deployment
|
||||
- **Integration**: [HOW THIS FITS WITH EXISTING SYSTEMS]
|
||||
- **User Value**: [SPECIFIC BENEFITS TO END USERS]
|
||||
|
||||
## What
|
||||
|
||||
### MCP Server Features
|
||||
|
||||
**Core MCP Tools:**
|
||||
|
||||
- Tools are organized in modular files and registered via `src/tools/register-tools.ts`
|
||||
- Each feature/domain gets its own tool registration file (e.g., `database-tools.ts`, `analytics-tools.ts`)
|
||||
- [LIST SPECIFIC TOOLS] - e.g., "queryDatabase", "listTables", "executeOperations"
|
||||
- User authentication and permission validation happens during tool registration
|
||||
- Comprehensive error handling and logging
|
||||
- [DOMAIN-SPECIFIC TOOLS] - tools specific to your use case
|
||||
|
||||
**Authentication & Authorization:**
|
||||
|
||||
- GitHub OAuth 2.0 integration with signed cookie approval system
|
||||
- Role-based access control (read-only vs privileged users)
|
||||
- User context propagation to all MCP tools
|
||||
- Secure session management with HMAC-signed cookies
|
||||
|
||||
**Database Integration:**
|
||||
|
||||
- PostgreSQL connection pooling with automatic cleanup
|
||||
- SQL injection protection and query validation
|
||||
- Read/write operation separation based on user permissions
|
||||
- Error sanitization to prevent information leakage
|
||||
|
||||
**Deployment & Monitoring:**
|
||||
|
||||
- Cloudflare Workers with Durable Objects for state management
|
||||
- Optional Sentry integration for error tracking and performance monitoring
|
||||
- Environment-based configuration (development vs production)
|
||||
- Real-time logging and alerting
|
||||
|
||||
### Success Criteria
|
||||
|
||||
- [ ] MCP server passes validation with MCP Inspector
|
||||
- [ ] GitHub OAuth flow works end-to-end (authorization → callback → MCP access)
|
||||
- [ ] TypeScript compilation succeeds with no errors
|
||||
- [ ] Local development server starts and responds correctly
|
||||
- [ ] Production deployment to Cloudflare Workers succeeds
|
||||
- [ ] Authentication prevents unauthorized access to sensitive operations
|
||||
- [ ] Error handling provides user-friendly messages without leaking system details
|
||||
- [ ] [DOMAIN-SPECIFIC SUCCESS CRITERIA]
|
||||
|
||||
## All Needed Context
|
||||
|
||||
### Documentation & References (MUST READ)
|
||||
|
||||
```yaml
|
||||
# CRITICAL MCP PATTERNS - Read these first
|
||||
- docfile: PRPs/ai_docs/mcp_patterns.md
|
||||
why: Core MCP development patterns, security practices, and error handling
|
||||
|
||||
# Critial code examples
|
||||
- docfile: PRPs/ai_docs/claude_api_usage.md
|
||||
why: How to use the Anthropic API to get a response from an LLM
|
||||
|
||||
# TOOL REGISTRATION SYSTEM - Understand the modular approach
|
||||
- file: src/tools/register-tools.ts
|
||||
why: Central registry showing how all tools are imported and registered - STUDY this pattern
|
||||
|
||||
# EXAMPLE MCP TOOLS - Look here how to create and register new tools
|
||||
- file: examples/database-tools.ts
|
||||
why: Example tools for a Postgres MCP server showing best practices for tool creation and registration
|
||||
|
||||
- file: examples/database-tools-sentry.ts
|
||||
why: Example tools for the Postgres MCP server but with the Sentry integration for production monitoring
|
||||
|
||||
# EXISTING CODEBASE PATTERNS - Study these implementations
|
||||
- file: src/index.ts
|
||||
why: Complete MCP server with authentication, database, and tools - MIRROR this pattern
|
||||
|
||||
- file: src/github-handler.ts
|
||||
why: OAuth flow implementation - USE this exact pattern for authentication
|
||||
|
||||
- file: src/database.ts
|
||||
why: Database security, connection pooling, SQL validation - FOLLOW these patterns
|
||||
|
||||
- file: wrangler.jsonc
|
||||
why: Cloudflare Workers configuration - COPY this pattern for deployment
|
||||
|
||||
# OFFICIAL MCP DOCUMENTATION
|
||||
- url: https://modelcontextprotocol.io/docs/concepts/tools
|
||||
why: MCP tool registration and schema definition patterns
|
||||
|
||||
- url: https://modelcontextprotocol.io/docs/concepts/resources
|
||||
why: MCP resource implementation if needed
|
||||
|
||||
# Add n documentation related to the users use case as needed below
|
||||
```
|
||||
|
||||
### Current Codebase Tree (Run `tree -I node_modules` in project root)
|
||||
|
||||
```bash
|
||||
# INSERT ACTUAL TREE OUTPUT HERE
|
||||
/
|
||||
├── src/
|
||||
│ ├── index.ts # Main authenticated MCP server ← STUDY THIS
|
||||
│ ├── index_sentry.ts # Sentry monitoring version
|
||||
│ ├── simple-math.ts # Basic MCP example ← GOOD STARTING POINT
|
||||
│ ├── github-handler.ts # OAuth implementation ← USE THIS PATTERN
|
||||
│ ├── database.ts # Database utilities ← SECURITY PATTERNS
|
||||
│ ├── utils.ts # OAuth helpers
|
||||
│ ├── workers-oauth-utils.ts # Cookie security system
|
||||
│ └── tools/ # Tool registration system
|
||||
│ └── register-tools.ts # Central tool registry ← UNDERSTAND THIS
|
||||
├── PRPs/
|
||||
│ ├── templates/prp_mcp_base.md # This template
|
||||
│ └── ai_docs/ # Implementation guides ← READ ALL
|
||||
├── examples/ # Example tool implementations
|
||||
│ ├── database-tools.ts # Database tools example ← FOLLOW PATTERN
|
||||
│ └── database-tools-sentry.ts # With Sentry monitoring
|
||||
├── wrangler.jsonc # Cloudflare config ← COPY PATTERNS
|
||||
├── package.json # Dependencies
|
||||
└── tsconfig.json # TypeScript config
|
||||
```
|
||||
|
||||
### Desired Codebase Tree (Files to add/modify) related to the users use case as needed below
|
||||
|
||||
```bash
|
||||
|
||||
```
|
||||
|
||||
### Known Gotchas & Critical MCP/Cloudflare Patterns
|
||||
|
||||
```typescript
|
||||
// CRITICAL: Cloudflare Workers require specific patterns
|
||||
// 1. ALWAYS implement cleanup for Durable Objects
|
||||
export class YourMCP extends McpAgent<Env, Record<string, never>, Props> {
|
||||
async cleanup(): Promise<void> {
|
||||
await closeDb(); // CRITICAL: Close database connections
|
||||
}
|
||||
|
||||
async alarm(): Promise<void> {
|
||||
await this.cleanup(); // CRITICAL: Handle Durable Object alarms
|
||||
}
|
||||
}
|
||||
|
||||
// 2. ALWAYS validate SQL to prevent injection (use existing patterns)
|
||||
const validation = validateSqlQuery(sql); // from src/database.ts
|
||||
if (!validation.isValid) {
|
||||
return createErrorResponse(validation.error);
|
||||
}
|
||||
|
||||
// 3. ALWAYS check permissions before sensitive operations
|
||||
const ALLOWED_USERNAMES = new Set(["admin1", "admin2"]);
|
||||
if (!ALLOWED_USERNAMES.has(this.props.login)) {
|
||||
return createErrorResponse("Insufficient permissions");
|
||||
}
|
||||
|
||||
// 4. ALWAYS use withDatabase wrapper for connection management
|
||||
return await withDatabase(this.env.DATABASE_URL, async (db) => {
|
||||
// Database operations here
|
||||
});
|
||||
|
||||
// 5. ALWAYS use Zod for input validation
|
||||
import { z } from "zod";
|
||||
const schema = z.object({
|
||||
param: z.string().min(1).max(100),
|
||||
});
|
||||
|
||||
// 6. TypeScript compilation requires exact interface matching
|
||||
interface Env {
|
||||
DATABASE_URL: string;
|
||||
GITHUB_CLIENT_ID: string;
|
||||
GITHUB_CLIENT_SECRET: string;
|
||||
OAUTH_KV: KVNamespace;
|
||||
// Add your environment variables here
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Blueprint
|
||||
|
||||
### Data Models & Types
|
||||
|
||||
Define TypeScript interfaces and Zod schemas for type safety and validation.
|
||||
|
||||
```typescript
|
||||
// User authentication props (inherited from OAuth)
|
||||
type Props = {
|
||||
login: string; // GitHub username
|
||||
name: string; // Display name
|
||||
email: string; // Email address
|
||||
accessToken: string; // GitHub access token
|
||||
};
|
||||
|
||||
// MCP tool input schemas (customize for your tools)
|
||||
const YourToolSchema = z.object({
|
||||
param1: z.string().min(1, "Parameter cannot be empty"),
|
||||
param2: z.number().int().positive().optional(),
|
||||
options: z.object({}).optional(),
|
||||
});
|
||||
|
||||
// Environment interface (add your variables)
|
||||
interface Env {
|
||||
DATABASE_URL: string;
|
||||
GITHUB_CLIENT_ID: string;
|
||||
GITHUB_CLIENT_SECRET: string;
|
||||
OAUTH_KV: KVNamespace;
|
||||
// YOUR_SPECIFIC_ENV_VAR: string;
|
||||
}
|
||||
|
||||
// Permission levels (customize for your use case)
|
||||
enum Permission {
|
||||
READ = "read",
|
||||
WRITE = "write",
|
||||
ADMIN = "admin",
|
||||
}
|
||||
```
|
||||
|
||||
### List of Tasks (Complete in order)
|
||||
|
||||
```yaml
|
||||
Task 1 - Project Setup:
|
||||
COPY wrangler.jsonc to wrangler-[server-name].jsonc:
|
||||
- MODIFY name field to "[server-name]"
|
||||
- ADD any new environment variables to vars section
|
||||
- KEEP existing OAuth and database configuration
|
||||
|
||||
CREATE .dev.vars file (if not exists):
|
||||
- ADD GITHUB_CLIENT_ID=your_client_id
|
||||
- ADD GITHUB_CLIENT_SECRET=your_client_secret
|
||||
- ADD DATABASE_URL=postgresql://...
|
||||
- ADD COOKIE_ENCRYPTION_KEY=your_32_byte_key
|
||||
- ADD any domain-specific environment variables
|
||||
|
||||
Task 2 - GitHub OAuth App:
|
||||
CREATE new GitHub OAuth app:
|
||||
- SET homepage URL: https://your-worker.workers.dev
|
||||
- SET callback URL: https://your-worker.workers.dev/callback
|
||||
- COPY client ID and secret to .dev.vars
|
||||
|
||||
OR REUSE existing OAuth app:
|
||||
- UPDATE callback URL if using different subdomain
|
||||
- VERIFY client ID and secret in environment
|
||||
|
||||
Task 3 - MCP Server Implementation:
|
||||
CREATE src/[server-name].ts OR MODIFY src/index.ts:
|
||||
- COPY class structure from src/index.ts
|
||||
- MODIFY server name and version in McpServer constructor
|
||||
- CALL registerAllTools(server, env, props) in init() method
|
||||
- KEEP authentication and database patterns identical
|
||||
|
||||
CREATE tool modules:
|
||||
- CREATE new tool files following examples/database-tools.ts pattern
|
||||
- EXPORT registration functions that accept (server, env, props)
|
||||
- USE Zod schemas for input validation
|
||||
- IMPLEMENT proper error handling with createErrorResponse
|
||||
- ADD permission checking during tool registration
|
||||
|
||||
UPDATE tool registry:
|
||||
- MODIFY src/tools/register-tools.ts to import your new tools
|
||||
- ADD your registration function call in registerAllTools()
|
||||
|
||||
Task 4 - Database Integration (if needed):
|
||||
USE existing database patterns from src/database.ts:
|
||||
- IMPORT withDatabase, validateSqlQuery, isWriteOperation
|
||||
- IMPLEMENT database operations with security validation
|
||||
- SEPARATE read vs write operations based on user permissions
|
||||
- USE formatDatabaseError for user-friendly error messages
|
||||
|
||||
Task 5 - Environment Configuration:
|
||||
SETUP Cloudflare KV namespace:
|
||||
- RUN: wrangler kv namespace create "OAUTH_KV"
|
||||
- UPDATE wrangler.jsonc with returned namespace ID
|
||||
|
||||
SET production secrets:
|
||||
- RUN: wrangler secret put GITHUB_CLIENT_ID
|
||||
- RUN: wrangler secret put GITHUB_CLIENT_SECRET
|
||||
- RUN: wrangler secret put DATABASE_URL
|
||||
- RUN: wrangler secret put COOKIE_ENCRYPTION_KEY
|
||||
|
||||
Task 6 - Local Testing:
|
||||
TEST basic functionality:
|
||||
- RUN: wrangler dev
|
||||
- VERIFY server starts without errors
|
||||
- TEST OAuth flow: http://localhost:8792/authorize
|
||||
- VERIFY MCP endpoint: http://localhost:8792/mcp
|
||||
|
||||
Task 7 - Production Deployment:
|
||||
DEPLOY to Cloudflare Workers:
|
||||
- RUN: wrangler deploy
|
||||
- VERIFY deployment success
|
||||
- TEST production OAuth flow
|
||||
- VERIFY MCP endpoint accessibility
|
||||
```
|
||||
|
||||
### Per Task Implementation Details
|
||||
|
||||
```typescript
|
||||
// Task 3 - MCP Server Implementation Pattern
|
||||
export class YourMCP extends McpAgent<Env, Record<string, never>, Props> {
|
||||
server = new McpServer({
|
||||
name: "Your MCP Server Name",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
// CRITICAL: Always implement cleanup
|
||||
async cleanup(): Promise<void> {
|
||||
try {
|
||||
await closeDb();
|
||||
console.log("Database connections closed successfully");
|
||||
} catch (error) {
|
||||
console.error("Error during database cleanup:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async alarm(): Promise<void> {
|
||||
await this.cleanup();
|
||||
}
|
||||
|
||||
async init() {
|
||||
// PATTERN: Use centralized tool registration
|
||||
registerAllTools(this.server, this.env, this.props);
|
||||
}
|
||||
}
|
||||
|
||||
// Task 3 - Tool Module Pattern (e.g., src/tools/your-feature-tools.ts)
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { Props } from "../types";
|
||||
import { z } from "zod";
|
||||
|
||||
const PRIVILEGED_USERS = new Set(["admin1", "admin2"]);
|
||||
|
||||
export function registerYourFeatureTools(server: McpServer, env: Env, props: Props) {
|
||||
// Tool 1: Available to all authenticated users
|
||||
server.tool(
|
||||
"yourBasicTool",
|
||||
"Description of your basic tool",
|
||||
YourToolSchema, // Zod validation schema
|
||||
async ({ param1, param2, options }) => {
|
||||
try {
|
||||
// PATTERN: Tool implementation with error handling
|
||||
const result = await performOperation(param1, param2, options);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `**Success**\n\nOperation completed\n\n**Result:**\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\``,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return createErrorResponse(`Operation failed: ${error.message}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Tool 2: Only for privileged users
|
||||
if (PRIVILEGED_USERS.has(props.login)) {
|
||||
server.tool(
|
||||
"privilegedTool",
|
||||
"Administrative tool for privileged users",
|
||||
{ action: z.string() },
|
||||
async ({ action }) => {
|
||||
// Implementation
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Admin action '${action}' executed by ${props.login}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Task 3 - Update Tool Registry (src/tools/register-tools.ts)
|
||||
import { registerYourFeatureTools } from "./your-feature-tools";
|
||||
|
||||
export function registerAllTools(server: McpServer, env: Env, props: Props) {
|
||||
// Existing registrations
|
||||
registerDatabaseTools(server, env, props);
|
||||
|
||||
// Add your new registration
|
||||
registerYourFeatureTools(server, env, props);
|
||||
}
|
||||
|
||||
// PATTERN: Export OAuth provider with MCP endpoints
|
||||
export default new OAuthProvider({
|
||||
apiHandlers: {
|
||||
"/sse": YourMCP.serveSSE("/sse") as any,
|
||||
"/mcp": YourMCP.serve("/mcp") as any,
|
||||
},
|
||||
authorizeEndpoint: "/authorize",
|
||||
clientRegistrationEndpoint: "/register",
|
||||
defaultHandler: GitHubHandler as any,
|
||||
tokenEndpoint: "/token",
|
||||
});
|
||||
```
|
||||
|
||||
### Integration Points
|
||||
|
||||
```yaml
|
||||
CLOUDFLARE_WORKERS:
|
||||
- wrangler.jsonc: Update name, environment variables, KV bindings
|
||||
- Environment secrets: GitHub OAuth credentials, database URL, encryption key
|
||||
- Durable Objects: Configure MCP agent binding for state persistence
|
||||
|
||||
GITHUB_OAUTH:
|
||||
- GitHub App: Create with callback URL matching your Workers domain
|
||||
- Client credentials: Store as Cloudflare Workers secrets
|
||||
- Callback URL: Must match exactly: https://your-worker.workers.dev/callback
|
||||
|
||||
DATABASE:
|
||||
- PostgreSQL connection: Use existing connection pooling patterns
|
||||
- Environment variable: DATABASE_URL with full connection string
|
||||
- Security: Use validateSqlQuery and isWriteOperation for all SQL
|
||||
|
||||
ENVIRONMENT_VARIABLES:
|
||||
- Development: .dev.vars file for local testing
|
||||
- Production: Cloudflare Workers secrets for deployment
|
||||
- Required: GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, DATABASE_URL, COOKIE_ENCRYPTION_KEY
|
||||
|
||||
KV_STORAGE:
|
||||
- OAuth state: Used by OAuth provider for state management
|
||||
- Namespace: Create with `wrangler kv namespace create "OAUTH_KV"`
|
||||
- Configuration: Add namespace ID to wrangler.jsonc bindings
|
||||
```
|
||||
|
||||
## Validation Gate
|
||||
|
||||
### Level 1: TypeScript & Configuration
|
||||
|
||||
```bash
|
||||
# CRITICAL: Run these FIRST - fix any errors before proceeding
|
||||
npm run type-check # TypeScript compilation
|
||||
wrangler types # Generate Cloudflare Workers types
|
||||
|
||||
# Expected: No TypeScript errors
|
||||
# If errors: Fix type issues, missing interfaces, import problems
|
||||
```
|
||||
|
||||
### Level 2: Local Development Testing
|
||||
|
||||
```bash
|
||||
# Start local development server
|
||||
wrangler dev
|
||||
|
||||
# Test OAuth flow (should redirect to GitHub)
|
||||
curl -v http://localhost:8792/authorize
|
||||
|
||||
# Test MCP endpoint (should return server info)
|
||||
curl -v http://localhost:8792/mcp
|
||||
|
||||
# Expected: Server starts, OAuth redirects to GitHub, MCP responds with server info
|
||||
# If errors: Check console output, verify environment variables, fix configuration
|
||||
```
|
||||
|
||||
### Level 3: Unit test each feature, function, and file, following existing testing patterns if they are there.
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
Run unit tests with the above command (Vitest) to make sure all functionality is working.
|
||||
|
||||
### Level 4: Database Integration Testing (if applicable)
|
||||
|
||||
```bash
|
||||
# Test database connection
|
||||
curl -X POST http://localhost:8792/mcp \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"method": "tools/call", "params": {"name": "listTables", "arguments": {}}}'
|
||||
|
||||
# Test permission validation
|
||||
# Test SQL injection protection and other kinds of security if applicable
|
||||
# Test error handling for database failures
|
||||
|
||||
# Expected: Database operations work, permissions enforced, errors handled gracefully, etc.
|
||||
# If errors: Check DATABASE_URL, connection settings, permission logic
|
||||
```
|
||||
|
||||
## Final Validation Checklist
|
||||
|
||||
### Core Functionality
|
||||
|
||||
- [ ] TypeScript compilation: `npm run type-check` passes
|
||||
- [ ] Unit tests pass: `npm run test` passes
|
||||
- [ ] Local server starts: `wrangler dev` runs without errors
|
||||
- [ ] MCP endpoint responds: `curl http://localhost:8792/mcp` returns server info
|
||||
- [ ] OAuth flow works: Authentication redirects and completes successfully
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### MCP-Specific
|
||||
|
||||
- ❌ Don't skip input validation with Zod - always validate tool parameters
|
||||
- ❌ Don't forget to implement cleanup() method for Durable Objects
|
||||
- ❌ Don't hardcode user permissions - use configurable permission systems
|
||||
|
||||
### Development Process
|
||||
|
||||
- ❌ Don't skip the validation loops - each level catches different issues
|
||||
- ❌ Don't guess about OAuth configuration - test the full flow
|
||||
- ❌ Don't deploy without monitoring - implement logging and error tracking
|
||||
- ❌ Don't ignore TypeScript errors - fix all type issues before deployment
|
||||
Reference in New Issue
Block a user