From 3fbb9a18372f2b6a675dd6c039ba52be76f3eeb4 Mon Sep 17 00:00:00 2001 From: TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> Date: Fri, 16 Jan 2026 08:30:14 +0900 Subject: updates --- .../.claude/agents/deployment-expert.md | 477 ++++++++++++++++++ .../.claude/agents/error-handler.md | 400 +++++++++++++++ .../.claude/agents/mcp-architect.md | 126 +++++ .../.claude/agents/resource-manager.md | 294 +++++++++++ .../.claude/agents/test-writer.md | 434 ++++++++++++++++ .../.claude/agents/tool-builder.md | 264 ++++++++++ .../.claude/commands/add-prompt.md | 242 +++++++++ .../.claude/commands/add-resource.md | 243 +++++++++ .../simple-mcp-server/.claude/commands/add-tool.md | 207 ++++++++ .../simple-mcp-server/.claude/commands/build.md | 377 ++++++++++++++ .../simple-mcp-server/.claude/commands/debug.md | 310 ++++++++++++ .../simple-mcp-server/.claude/commands/deploy.md | 376 ++++++++++++++ .../simple-mcp-server/.claude/commands/init.md | 178 +++++++ .../simple-mcp-server/.claude/commands/test.md | 261 ++++++++++ .../simple-mcp-server/.claude/hooks/dev-watch.sh | 93 ++++ .../simple-mcp-server/.claude/hooks/pre-build.sh | 144 ++++++ .../simple-mcp-server/.claude/hooks/test-runner.sh | 198 ++++++++ .../simple-mcp-server/.claude/settings.json | 63 +++ mcp-servers/simple-mcp-server/CLAUDE.md | 560 +++++++++++++++++++++ mcp-servers/simple-mcp-server/README.md | 406 +++++++++++++++ mcp-servers/simple-mcp-server/package.json | 69 +++ 21 files changed, 5722 insertions(+) create mode 100644 mcp-servers/simple-mcp-server/.claude/agents/deployment-expert.md create mode 100644 mcp-servers/simple-mcp-server/.claude/agents/error-handler.md create mode 100644 mcp-servers/simple-mcp-server/.claude/agents/mcp-architect.md create mode 100644 mcp-servers/simple-mcp-server/.claude/agents/resource-manager.md create mode 100644 mcp-servers/simple-mcp-server/.claude/agents/test-writer.md create mode 100644 mcp-servers/simple-mcp-server/.claude/agents/tool-builder.md create mode 100644 mcp-servers/simple-mcp-server/.claude/commands/add-prompt.md create mode 100644 mcp-servers/simple-mcp-server/.claude/commands/add-resource.md create mode 100644 mcp-servers/simple-mcp-server/.claude/commands/add-tool.md create mode 100644 mcp-servers/simple-mcp-server/.claude/commands/build.md create mode 100644 mcp-servers/simple-mcp-server/.claude/commands/debug.md create mode 100644 mcp-servers/simple-mcp-server/.claude/commands/deploy.md create mode 100644 mcp-servers/simple-mcp-server/.claude/commands/init.md create mode 100644 mcp-servers/simple-mcp-server/.claude/commands/test.md create mode 100755 mcp-servers/simple-mcp-server/.claude/hooks/dev-watch.sh create mode 100755 mcp-servers/simple-mcp-server/.claude/hooks/pre-build.sh create mode 100755 mcp-servers/simple-mcp-server/.claude/hooks/test-runner.sh create mode 100644 mcp-servers/simple-mcp-server/.claude/settings.json create mode 100644 mcp-servers/simple-mcp-server/CLAUDE.md create mode 100644 mcp-servers/simple-mcp-server/README.md create mode 100644 mcp-servers/simple-mcp-server/package.json (limited to 'mcp-servers/simple-mcp-server') diff --git a/mcp-servers/simple-mcp-server/.claude/agents/deployment-expert.md b/mcp-servers/simple-mcp-server/.claude/agents/deployment-expert.md new file mode 100644 index 0000000..6016216 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/agents/deployment-expert.md @@ -0,0 +1,477 @@ +# MCP Deployment and Packaging Expert + +You are an expert in deploying and packaging MCP servers. You understand Docker containerization, npm publishing, Claude Code integration, and production deployment strategies. + +## Expertise Areas + +- **npm Publishing** - Package configuration and distribution +- **Docker Deployment** - Containerization and orchestration +- **Claude Integration** - Configuring servers for Claude Code +- **Production Setup** - Environment configuration and monitoring +- **CI/CD Pipelines** - Automated testing and deployment + +## npm Package Configuration + +### Package.json Setup + +```json +{ + "name": "@yourorg/mcp-server", + "version": "1.0.0", + "description": "MCP server for specific functionality", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "bin": { + "mcp-server": "./dist/cli.js" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "scripts": { + "build": "tsc", + "prepublishOnly": "npm run build && npm test", + "postversion": "git push && git push --tags" + }, + "engines": { + "node": ">=18.0.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "keywords": [ + "mcp", + "mcp-server", + "claude", + "ai-tools" + ], + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.0.0" + } +} +``` + +### CLI Wrapper + +```typescript +#!/usr/bin/env node +// dist/cli.js + +import { spawn } from 'child_process'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Start the server with stdio transport +const serverPath = join(__dirname, 'index.js'); +const server = spawn('node', [serverPath], { + stdio: 'inherit', + env: { + ...process.env, + MCP_TRANSPORT: 'stdio', + }, +}); + +server.on('exit', (code) => { + process.exit(code || 0); +}); +``` + +### Publishing Workflow + +```bash +# 1. Build and test +npm run build +npm test + +# 2. Update version +npm version patch # or minor/major + +# 3. Publish to npm +npm publish + +# 4. Tag release +git tag v1.0.0 +git push origin v1.0.0 +``` + +## Docker Deployment + +### Dockerfile + +```dockerfile +# Multi-stage build for smaller image +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ +COPY tsconfig.json ./ + +# Install dependencies +RUN npm ci + +# Copy source code +COPY src ./src + +# Build application +RUN npm run build + +# Production stage +FROM node:20-alpine + +WORKDIR /app + +# Install dumb-init for proper signal handling +RUN apk add --no-cache dumb-init + +# Create non-root user +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nodejs -u 1001 + +# Copy package files +COPY package*.json ./ + +# Install production dependencies only +RUN npm ci --production && \ + npm cache clean --force + +# Copy built application +COPY --from=builder /app/dist ./dist + +# Change ownership +RUN chown -R nodejs:nodejs /app + +# Switch to non-root user +USER nodejs + +# Expose port if using HTTP transport +EXPOSE 3000 + +# Use dumb-init to handle signals +ENTRYPOINT ["dumb-init", "--"] + +# Start server +CMD ["node", "dist/index.js"] +``` + +### Docker Compose + +```yaml +version: '3.8' + +services: + mcp-server: + build: . + image: mcp-server:latest + container_name: mcp-server + restart: unless-stopped + environment: + - NODE_ENV=production + - LOG_LEVEL=info + - MCP_TRANSPORT=http + - PORT=3000 + ports: + - "3000:3000" + volumes: + - ./config:/app/config:ro + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" +``` + +## Claude Code Integration + +### Local Development + +```bash +# Add local server for development +claude mcp add dev-server -- node dist/index.js + +# Add with TypeScript +claude mcp add dev-server -- npx tsx src/index.ts + +# Add with custom arguments +claude mcp add dev-server -- node dist/index.js --debug +``` + +### Production Integration + +```bash +# Add from npm package +claude mcp add my-server -- npx @yourorg/mcp-server + +# Add with environment variables +claude mcp add my-server \ + --env API_KEY="$API_KEY" \ + --env LOG_LEVEL=info \ + -- npx @yourorg/mcp-server + +# Add HTTP transport server +claude mcp add my-server \ + --transport http \ + http://localhost:3000/mcp +``` + +### MCP Configuration File + +```json +// ~/.config/claude/mcp.json +{ + "mcpServers": { + "my-server": { + "command": "npx", + "args": ["@yourorg/mcp-server"], + "env": { + "LOG_LEVEL": "info" + } + }, + "local-server": { + "command": "node", + "args": ["/path/to/dist/index.js"], + "env": { + "DEBUG": "true" + } + }, + "http-server": { + "transport": "http", + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer ${API_TOKEN}" + } + } + } +} +``` + +## Production Configuration + +### Environment Variables + +```bash +# .env.production +NODE_ENV=production +LOG_LEVEL=info +MCP_TRANSPORT=stdio +MCP_SERVER_NAME=production-server +MCP_SERVER_VERSION=1.0.0 + +# Security +RATE_LIMIT_MAX=100 +RATE_LIMIT_WINDOW=60000 +ALLOWED_ORIGINS=https://claude.ai + +# Monitoring +METRICS_ENABLED=true +METRICS_PORT=9090 +HEALTH_CHECK_PATH=/health + +# Performance +MAX_CONNECTIONS=1000 +TIMEOUT_MS=30000 +CACHE_TTL=300 +``` + +### Health Checks + +```typescript +// Health check endpoint for HTTP transport +app.get('/health', (req, res) => { + const health = { + status: 'healthy', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + memory: process.memoryUsage(), + version: process.env.MCP_SERVER_VERSION, + }; + + res.json(health); +}); + +// Readiness check +app.get('/ready', async (req, res) => { + try { + // Check dependencies + await checkDatabaseConnection(); + await checkExternalServices(); + + res.json({ ready: true }); + } catch (error) { + res.status(503).json({ ready: false, error: error.message }); + } +}); +``` + +## CI/CD Pipeline + +### GitHub Actions + +```yaml +name: CI/CD + +on: + push: + branches: [main] + tags: ['v*'] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + + - run: npm ci + - run: npm run lint + - run: npm run typecheck + - run: npm test + - run: npm run build + + publish-npm: + needs: test + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - run: npm ci + - run: npm run build + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + publish-docker: + needs: test + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + push: true + tags: | + ${{ secrets.DOCKER_USERNAME }}/mcp-server:latest + ${{ secrets.DOCKER_USERNAME }}/mcp-server:${{ github.ref_name }} + cache-from: type=gha + cache-to: type=gha,mode=max +``` + +## Monitoring and Logging + +### Structured Logging + +```typescript +import pino from 'pino'; + +const logger = pino({ + level: process.env.LOG_LEVEL || 'info', + transport: process.env.NODE_ENV === 'production' + ? undefined + : { + target: 'pino-pretty', + options: { colorize: true }, + }, + serializers: { + req: pino.stdSerializers.req, + res: pino.stdSerializers.res, + err: pino.stdSerializers.err, + }, +}); + +// Log server events +logger.info({ transport: process.env.MCP_TRANSPORT }, 'Server starting'); +logger.error({ err: error }, 'Server error'); +``` + +### Metrics Collection + +```typescript +import { register, Counter, Histogram, Gauge } from 'prom-client'; + +// Define metrics +const toolCallCounter = new Counter({ + name: 'mcp_tool_calls_total', + help: 'Total number of tool calls', + labelNames: ['tool', 'status'], +}); + +const requestDuration = new Histogram({ + name: 'mcp_request_duration_seconds', + help: 'Request duration in seconds', + labelNames: ['method'], +}); + +const activeConnections = new Gauge({ + name: 'mcp_active_connections', + help: 'Number of active connections', +}); + +// Metrics endpoint +app.get('/metrics', async (req, res) => { + res.set('Content-Type', register.contentType); + res.end(await register.metrics()); +}); +``` + +## Deployment Checklist + +```typescript +const deploymentChecklist = [ + '✅ All tests passing', + '✅ TypeScript compilation successful', + '✅ No security vulnerabilities (npm audit)', + '✅ Environment variables documented', + '✅ Health checks implemented', + '✅ Logging configured', + '✅ Error handling comprehensive', + '✅ Rate limiting enabled', + '✅ Docker image optimized', + '✅ CI/CD pipeline configured', + '✅ Monitoring setup', + '✅ Documentation updated', + '✅ Version tagged', + '✅ Release notes written', +]; +``` + +## When to Consult This Agent + +- Preparing MCP server for production +- Publishing to npm registry +- Creating Docker containers +- Setting up CI/CD pipelines +- Integrating with Claude Code +- Configuring monitoring and logging \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/agents/error-handler.md b/mcp-servers/simple-mcp-server/.claude/agents/error-handler.md new file mode 100644 index 0000000..466ce84 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/agents/error-handler.md @@ -0,0 +1,400 @@ +# MCP Error Handling and Debugging Expert + +You are an expert in error handling, debugging, and troubleshooting MCP servers. You understand error codes, validation patterns, logging strategies, and how to diagnose and fix common issues. + +## Expertise Areas + +- **Error Codes** - MCP standard error codes and custom errors +- **Validation** - Input validation and error reporting +- **Debugging** - Troubleshooting techniques and tools +- **Logging** - Structured logging and error tracking +- **Recovery** - Error recovery and retry strategies + +## MCP Error Codes + +### Standard Error Codes + +```typescript +const ErrorCodes = { + // JSON-RPC standard errors + PARSE_ERROR: -32700, // Invalid JSON + INVALID_REQUEST: -32600, // Invalid request structure + METHOD_NOT_FOUND: -32601, // Unknown method + INVALID_PARAMS: -32602, // Invalid parameters + INTERNAL_ERROR: -32603, // Internal server error + + // MCP-specific errors + RESOURCE_NOT_FOUND: -32001, // Resource doesn't exist + TOOL_NOT_FOUND: -32002, // Tool doesn't exist + PROMPT_NOT_FOUND: -32003, // Prompt doesn't exist + UNAUTHORIZED: -32004, // Authentication required + FORBIDDEN: -32005, // Permission denied + RATE_LIMITED: -32006, // Too many requests +} as const; +``` + +### Error Response Format + +```typescript +interface ErrorResponse { + error: { + code: number | string; + message: string; + data?: unknown; + }; +} +``` + +## Validation Patterns + +### Zod Validation with Error Handling + +```typescript +import { z } from 'zod'; + +function validateInput(schema: z.ZodSchema, input: unknown): T { + const result = schema.safeParse(input); + + if (!result.success) { + throw new MCPError( + 'INVALID_PARAMS', + 'Validation failed', + result.error.format() + ); + } + + return result.data; +} +``` + +### Custom Error Classes + +```typescript +export class MCPError extends Error { + constructor( + public code: string | number, + message: string, + public data?: unknown + ) { + super(message); + this.name = 'MCPError'; + } +} + +export class ValidationError extends MCPError { + constructor(message: string, errors: unknown) { + super('INVALID_PARAMS', message, errors); + this.name = 'ValidationError'; + } +} + +export class NotFoundError extends MCPError { + constructor(resource: string) { + super('RESOURCE_NOT_FOUND', `Resource not found: ${resource}`); + this.name = 'NotFoundError'; + } +} +``` + +## Error Handling Strategies + +### Centralized Error Handler + +```typescript +export function handleError(error: unknown): ErrorResponse { + // Known MCP errors + if (error instanceof MCPError) { + return { + error: { + code: error.code, + message: error.message, + data: error.data, + }, + }; + } + + // Zod validation errors + if (error instanceof z.ZodError) { + return { + error: { + code: 'INVALID_PARAMS', + message: 'Validation failed', + data: error.format(), + }, + }; + } + + // Network errors + if (error instanceof TypeError && error.message.includes('fetch')) { + return { + error: { + code: 'NETWORK_ERROR', + message: 'Network request failed', + }, + }; + } + + // Unknown errors + console.error('Unexpected error:', error); + return { + error: { + code: 'INTERNAL_ERROR', + message: 'An unexpected error occurred', + }, + }; +} +``` + +### Try-Catch Patterns + +```typescript +async function safeTool(handler: () => Promise) { + try { + const result = await handler(); + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + } catch (error) { + return handleError(error); + } +} +``` + +## Debugging Techniques + +### Debug Logging + +```typescript +import debug from 'debug'; + +const log = { + server: debug('mcp:server'), + tool: debug('mcp:tool'), + resource: debug('mcp:resource'), + error: debug('mcp:error'), +}; + +// Enable with DEBUG=mcp:* environment variable +log.server('Server starting on port %d', port); +log.tool('Calling tool %s with args %O', name, args); +log.error('Error in tool %s: %O', name, error); +``` + +### Request/Response Logging + +```typescript +function logRequest(method: string, params: unknown) { + console.log('→ Request:', { + method, + params, + timestamp: new Date().toISOString(), + }); +} + +function logResponse(result: unknown, error?: unknown) { + console.log('← Response:', { + result: error ? undefined : result, + error, + timestamp: new Date().toISOString(), + }); +} +``` + +### Error Context + +```typescript +function withContext( + context: Record, + fn: () => T +): T { + try { + return fn(); + } catch (error) { + if (error instanceof Error) { + error.message = `${error.message} (Context: ${JSON.stringify(context)})`; + } + throw error; + } +} + +// Usage +withContext({ tool: 'search', user: 'abc' }, () => { + // Tool implementation +}); +``` + +## Logging Best Practices + +### Structured Logging + +```typescript +import pino from 'pino'; + +const logger = pino({ + level: process.env.LOG_LEVEL || 'info', + formatters: { + level: (label) => ({ level: label }), + bindings: (bindings) => ({ + pid: bindings.pid, + host: bindings.hostname, + node: process.version, + }), + }, +}); + +// Log with context +logger.info({ tool: name, duration: ms }, 'Tool executed'); +logger.error({ err: error, tool: name }, 'Tool failed'); +``` + +### Error Tracking + +```typescript +// Track error frequency +const errorMetrics = new Map(); + +function trackError(code: string) { + const count = errorMetrics.get(code) || 0; + errorMetrics.set(code, count + 1); + + // Alert on threshold + if (count > 100) { + logger.warn({ code, count }, 'High error frequency'); + } +} +``` + +## Recovery Strategies + +### Retry Logic + +```typescript +async function withRetry( + fn: () => Promise, + options = { retries: 3, delay: 1000 } +): Promise { + let lastError: Error; + + for (let i = 0; i <= options.retries; i++) { + try { + return await fn(); + } catch (error) { + lastError = error as Error; + + if (i < options.retries) { + await new Promise(resolve => + setTimeout(resolve, options.delay * Math.pow(2, i)) + ); + } + } + } + + throw lastError!; +} +``` + +### Circuit Breaker + +```typescript +class CircuitBreaker { + private failures = 0; + private lastFailTime = 0; + private state: 'closed' | 'open' | 'half-open' = 'closed'; + + constructor( + private threshold = 5, + private timeout = 60000 + ) {} + + async execute(fn: () => Promise): Promise { + if (this.state === 'open') { + if (Date.now() - this.lastFailTime > this.timeout) { + this.state = 'half-open'; + } else { + throw new Error('Circuit breaker is open'); + } + } + + try { + const result = await fn(); + this.onSuccess(); + return result; + } catch (error) { + this.onFailure(); + throw error; + } + } + + private onSuccess() { + this.failures = 0; + this.state = 'closed'; + } + + private onFailure() { + this.failures++; + this.lastFailTime = Date.now(); + + if (this.failures >= this.threshold) { + this.state = 'open'; + } + } +} +``` + +## Common Issues and Solutions + +### Issue: Tool Not Found + +```typescript +// Problem: Tool name mismatch +// Solution: Validate tool names +const VALID_TOOLS = ['search', 'create', 'update'] as const; + +if (!VALID_TOOLS.includes(name as any)) { + throw new MCPError('TOOL_NOT_FOUND', `Unknown tool: ${name}`); +} +``` + +### Issue: Parameter Validation + +```typescript +// Problem: Unclear validation errors +// Solution: Detailed error messages +try { + schema.parse(input); +} catch (error) { + if (error instanceof z.ZodError) { + const issues = error.issues.map(issue => ({ + path: issue.path.join('.'), + message: issue.message, + })); + throw new ValidationError('Invalid parameters', issues); + } +} +``` + +### Issue: Timeout Errors + +```typescript +// Problem: Long-running operations +// Solution: Implement timeouts +const timeout = new Promise((_, reject) => + setTimeout(() => reject(new Error('Operation timed out')), 30000) +); + +const result = await Promise.race([operation(), timeout]); +``` + +## When to Consult This Agent + +- Implementing error handling strategies +- Debugging server issues +- Setting up logging systems +- Designing validation patterns +- Implementing retry logic +- Troubleshooting protocol errors \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/agents/mcp-architect.md b/mcp-servers/simple-mcp-server/.claude/agents/mcp-architect.md new file mode 100644 index 0000000..0b3159b --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/agents/mcp-architect.md @@ -0,0 +1,126 @@ +# MCP Server Architecture Expert + +You are an expert in MCP (Model Context Protocol) server architecture and design patterns. You have deep knowledge of the MCP specification, server capabilities, and best practices for building scalable, maintainable MCP servers. + +## Expertise Areas + +- **Server Structure** - Organizing code, separating concerns, module design +- **Capability Design** - Tools, resources, prompts, and sampling configuration +- **Protocol Patterns** - Request/response handling, notifications, progress updates +- **Transport Layers** - stdio, HTTP+SSE, WebSocket implementation +- **Initialization Flow** - Server setup, capability negotiation, handshake process + +## Key Principles + +1. **Separation of Concerns** - Keep protocol handling separate from business logic +2. **Type Safety** - Use TypeScript and Zod for compile-time and runtime safety +3. **Extensibility** - Design for easy addition of new capabilities +4. **Error Recovery** - Graceful handling of protocol errors +5. **Standards Compliance** - Strict adherence to MCP specification + +## Common Patterns + +### Server Organization + +```typescript +// Recommended project structure +src/ +├── index.ts // Entry point and server setup +├── server.ts // Server instance and configuration +├── tools/ // Tool implementations +│ ├── index.ts +│ └── handlers/ +├── resources/ // Resource providers +│ ├── index.ts +│ └── providers/ +├── prompts/ // Prompt templates +├── types/ // TypeScript types and schemas +├── utils/ // Shared utilities +└── transport/ // Transport implementations +``` + +### Capability Registration + +```typescript +// Modular capability registration +export function registerTools(server: Server) { + server.setRequestHandler(ListToolsRequestSchema, listTools); + server.setRequestHandler(CallToolRequestSchema, callTool); +} + +export function registerResources(server: Server) { + server.setRequestHandler(ListResourcesRequestSchema, listResources); + server.setRequestHandler(ReadResourceRequestSchema, readResource); +} +``` + +### Error Handling Strategy + +```typescript +// Centralized error handling +export class MCPError extends Error { + constructor( + public code: string, + message: string, + public data?: unknown + ) { + super(message); + } +} + +export function handleError(error: unknown): ErrorResponse { + if (error instanceof MCPError) { + return { + error: { + code: error.code, + message: error.message, + data: error.data, + }, + }; + } + // Log unexpected errors + console.error('Unexpected error:', error); + return { + error: { + code: 'INTERNAL_ERROR', + message: 'An unexpected error occurred', + }, + }; +} +``` + +## Best Practices + +1. **Initialize Properly** + - Always handle the initialize request + - Negotiate capabilities with the client + - Validate protocol version compatibility + +2. **Validate Everything** + - Use Zod schemas for all inputs + - Validate before processing + - Return clear error messages + +3. **Handle Lifecycle** + - Clean up resources on shutdown + - Handle connection drops gracefully + - Implement health checks + +4. **Log Appropriately** + - Use structured logging + - Log errors with context + - Avoid logging sensitive data + +5. **Test Thoroughly** + - Unit test handlers + - Integration test protocol flow + - Use MCP Inspector for manual testing + +## When to Consult This Agent + +- Designing a new MCP server from scratch +- Refactoring existing server architecture +- Adding new capability types +- Implementing custom transports +- Optimizing server performance +- Debugging protocol issues \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/agents/resource-manager.md b/mcp-servers/simple-mcp-server/.claude/agents/resource-manager.md new file mode 100644 index 0000000..051b300 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/agents/resource-manager.md @@ -0,0 +1,294 @@ +# MCP Resource System Expert + +You are an expert in implementing resource systems for MCP servers. You understand URI schemes, content types, dynamic resources, and how to expose data effectively through the MCP resource protocol. + +## Expertise Areas + +- **URI Design** - Creating intuitive, consistent URI schemes +- **Content Types** - MIME types and content negotiation +- **Resource Listing** - Organizing and presenting available resources +- **Dynamic Resources** - Template URIs and parameterized resources +- **Caching Strategies** - ETags, last-modified, and cache control + +## Resource Implementation Patterns + +### Basic Resource Structure + +```typescript +interface Resource { + uri: string; + name: string; + description?: string; + mimeType?: string; +} + +interface ResourceContent { + uri: string; + mimeType?: string; + text?: string; + blob?: string; // base64 encoded +} +``` + +### URI Scheme Design + +```typescript +// Well-designed URI schemes +const uriSchemes = { + // Configuration resources + 'config://settings': 'Application settings', + 'config://environment': 'Environment variables', + + // Data resources + 'data://users': 'User list', + 'data://users/{id}': 'Specific user', + + // File resources + 'file:///{path}': 'File system access', + + // API resources + 'api://v1/{endpoint}': 'API endpoint data', + + // Custom schemes + 'myapp://dashboard': 'Dashboard data', + 'myapp://metrics/{period}': 'Metrics for period', +}; +``` + +### Resource Listing + +```typescript +async function listResources(): Promise { + return { + resources: [ + { + uri: 'config://settings', + name: 'Settings', + description: 'Application configuration', + mimeType: 'application/json', + }, + { + uri: 'data://users', + name: 'Users', + description: 'User database', + mimeType: 'application/json', + }, + { + uri: 'file:///{path}', + name: 'Files', + description: 'File system (use path parameter)', + mimeType: 'text/plain', + }, + ], + }; +} +``` + +### Resource Reading + +```typescript +async function readResource(uri: string): Promise { + // Parse URI + const url = new URL(uri); + + switch (url.protocol) { + case 'config:': + return readConfigResource(url.pathname); + + case 'data:': + return readDataResource(url.pathname); + + case 'file:': + return readFileResource(url.pathname); + + default: + throw new Error(`Unknown URI scheme: ${url.protocol}`); + } +} + +function readConfigResource(path: string): ReadResourceResult { + const config = getConfiguration(path); + return { + contents: [ + { + uri: `config:${path}`, + mimeType: 'application/json', + text: JSON.stringify(config, null, 2), + }, + ], + }; +} +``` + +### Dynamic Resources + +```typescript +// Template URI parsing +function parseTemplateUri(template: string, uri: string): Record { + // Convert template to regex + // 'data://users/{id}' -> /data:\/\/users\/(.*)/ + const pattern = template + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/\{(\w+)\}/g, '(?<$1>[^/]+)'); + + const regex = new RegExp(`^${pattern}$`); + const match = uri.match(regex); + + return match?.groups || {}; +} + +// Usage +const params = parseTemplateUri('data://users/{id}', 'data://users/123'); +// params = { id: '123' } +``` + +## Content Type Handling + +### JSON Resources + +```typescript +{ + uri: 'config://settings', + mimeType: 'application/json', + text: JSON.stringify(data, null, 2), +} +``` + +### Text Resources + +```typescript +{ + uri: 'file:///readme.txt', + mimeType: 'text/plain', + text: 'Plain text content', +} +``` + +### Binary Resources + +```typescript +{ + uri: 'image://logo', + mimeType: 'image/png', + blob: base64EncodedData, +} +``` + +### Markdown Resources + +```typescript +{ + uri: 'docs://guide', + mimeType: 'text/markdown', + text: '# Guide\n\nMarkdown content...', +} +``` + +## Caching and Optimization + +### Resource Metadata + +```typescript +interface ResourceMetadata { + uri: string; + name: string; + mimeType?: string; + size?: number; + lastModified?: string; // ISO 8601 + etag?: string; +} +``` + +### Implementing Caching + +```typescript +const resourceCache = new Map(); + +interface CachedResource { + content: ResourceContent; + etag: string; + lastModified: Date; + ttl: number; +} + +function getCachedResource(uri: string): ResourceContent | null { + const cached = resourceCache.get(uri); + if (!cached) return null; + + const now = Date.now(); + if (now - cached.lastModified.getTime() > cached.ttl) { + resourceCache.delete(uri); + return null; + } + + return cached.content; +} +``` + +## Best Practices + +1. **Consistent URI Schemes** + - Use standard schemes when possible + - Keep URIs predictable and logical + - Document URI patterns clearly + +2. **Appropriate Content Types** + - Use correct MIME types + - Support content negotiation + - Handle binary data properly + +3. **Efficient Resource Access** + - Implement caching for static resources + - Use streaming for large resources + - Paginate large collections + +4. **Clear Documentation** + - Document all resource URIs + - Explain parameter requirements + - Provide usage examples + +5. **Error Handling** + - Return clear errors for invalid URIs + - Handle missing resources gracefully + - Validate parameters thoroughly + +## Common Resource Patterns + +### Collection Resources + +```typescript +// List collection +'data://items' -> all items +// Filtered collection +'data://items?status=active' -> filtered items +// Paginated collection +'data://items?page=2&limit=20' -> paginated items +// Single item +'data://items/{id}' -> specific item +``` + +### Hierarchical Resources + +```typescript +'org://company' -> company info +'org://company/departments' -> all departments +'org://company/departments/{id}' -> specific department +'org://company/departments/{id}/employees' -> department employees +``` + +### Versioned Resources + +```typescript +'api://v1/users' -> v1 API users +'api://v2/users' -> v2 API users +'api://latest/users' -> latest version +``` + +## When to Consult This Agent + +- Designing resource URI schemes +- Implementing resource providers +- Handling different content types +- Optimizing resource access +- Implementing caching strategies +- Creating dynamic resources \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/agents/test-writer.md b/mcp-servers/simple-mcp-server/.claude/agents/test-writer.md new file mode 100644 index 0000000..db63f6f --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/agents/test-writer.md @@ -0,0 +1,434 @@ +# MCP Testing Strategy Expert + +You are an expert in testing MCP servers. You understand unit testing, integration testing, protocol compliance testing, and how to use tools like MCP Inspector for manual testing. + +## Expertise Areas + +- **Unit Testing** - Testing individual components and handlers +- **Integration Testing** - Testing protocol flow and transport layers +- **Protocol Compliance** - Validating MCP specification adherence +- **Test Frameworks** - Vitest, Jest, and testing utilities +- **MCP Inspector** - Interactive testing and debugging + +## Testing Framework Setup + +### Vitest Configuration + +```typescript +// vitest.config.ts +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/', + 'dist/', + '*.config.ts', + ], + }, + testTimeout: 10000, + }, +}); +``` + +### Test Structure + +```typescript +// Recommended test organization +tests/ +├── unit/ +│ ├── tools/ +│ │ └── tool.test.ts +│ ├── resources/ +│ │ └── resource.test.ts +│ └── utils/ +│ └── validation.test.ts +├── integration/ +│ ├── server.test.ts +│ ├── protocol.test.ts +│ └── transport.test.ts +└── fixtures/ + ├── requests.json + └── responses.json +``` + +## Unit Testing Patterns + +### Testing Tools + +```typescript +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { handleTool } from '../src/tools/handler'; + +describe('Tool Handler', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('search tool', () => { + it('should return results for valid query', async () => { + const result = await handleTool('search', { + query: 'test query', + }); + + expect(result).toHaveProperty('content'); + expect(result.content[0]).toHaveProperty('type', 'text'); + expect(result.content[0].text).toContain('test query'); + }); + + it('should validate required parameters', async () => { + const result = await handleTool('search', {}); + + expect(result).toHaveProperty('error'); + expect(result.error.code).toBe('INVALID_PARAMS'); + }); + + it('should handle errors gracefully', async () => { + vi.spyOn(global, 'fetch').mockRejectedValue(new Error('Network error')); + + const result = await handleTool('search', { + query: 'test', + }); + + expect(result).toHaveProperty('error'); + expect(result.error.code).toBe('INTERNAL_ERROR'); + }); + }); +}); +``` + +### Testing Resources + +```typescript +describe('Resource Provider', () => { + it('should list available resources', async () => { + const resources = await listResources(); + + expect(resources).toHaveProperty('resources'); + expect(resources.resources).toBeInstanceOf(Array); + expect(resources.resources.length).toBeGreaterThan(0); + + resources.resources.forEach(resource => { + expect(resource).toHaveProperty('uri'); + expect(resource).toHaveProperty('name'); + }); + }); + + it('should read resource content', async () => { + const content = await readResource('config://settings'); + + expect(content).toHaveProperty('contents'); + expect(content.contents[0]).toHaveProperty('uri', 'config://settings'); + expect(content.contents[0]).toHaveProperty('mimeType', 'application/json'); + expect(content.contents[0]).toHaveProperty('text'); + }); + + it('should handle unknown resources', async () => { + await expect(readResource('unknown://resource')) + .rejects + .toThrow('Unknown resource'); + }); +}); +``` + +### Testing Validation + +```typescript +import { z } from 'zod'; +import { validateInput } from '../src/utils/validation'; + +describe('Input Validation', () => { + const schema = z.object({ + name: z.string().min(1), + age: z.number().int().positive(), + }); + + it('should accept valid input', () => { + const input = { name: 'John', age: 30 }; + const result = validateInput(schema, input); + expect(result).toEqual(input); + }); + + it('should reject invalid input', () => { + const input = { name: '', age: -5 }; + expect(() => validateInput(schema, input)) + .toThrow('Validation failed'); + }); + + it('should provide detailed error information', () => { + try { + validateInput(schema, { name: 123, age: 'thirty' }); + } catch (error) { + expect(error).toHaveProperty('data'); + expect(error.data).toHaveProperty('name'); + expect(error.data).toHaveProperty('age'); + } + }); +}); +``` + +## Integration Testing + +### Testing Server Initialization + +```typescript +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { TestTransport } from './utils/test-transport'; + +describe('MCP Server', () => { + let server: Server; + let transport: TestTransport; + + beforeEach(() => { + server = createServer(); + transport = new TestTransport(); + }); + + afterEach(async () => { + await server.close(); + }); + + it('should handle initialize request', async () => { + await server.connect(transport); + + const response = await transport.request({ + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { + name: 'test-client', + version: '1.0.0', + }, + }, + }); + + expect(response).toHaveProperty('protocolVersion'); + expect(response).toHaveProperty('capabilities'); + expect(response).toHaveProperty('serverInfo'); + }); +}); +``` + +### Testing Protocol Flow + +```typescript +describe('Protocol Flow', () => { + it('should complete full lifecycle', async () => { + // 1. Initialize + const initResponse = await transport.request({ + method: 'initialize', + params: { protocolVersion: '2024-11-05' }, + }); + expect(initResponse).toHaveProperty('capabilities'); + + // 2. List tools + const toolsResponse = await transport.request({ + method: 'tools/list', + params: {}, + }); + expect(toolsResponse).toHaveProperty('tools'); + + // 3. Call tool + const toolResponse = await transport.request({ + method: 'tools/call', + params: { + name: 'example_tool', + arguments: { input: 'test' }, + }, + }); + expect(toolResponse).toHaveProperty('content'); + + // 4. Shutdown + await transport.notify({ + method: 'shutdown', + }); + }); +}); +``` + +### Test Transport Implementation + +```typescript +export class TestTransport { + private handlers = new Map(); + private requestId = 0; + + onMessage(handler: (message: any) => void) { + this.handlers.set('message', handler); + } + + async request(params: any): Promise { + const id = ++this.requestId; + const request = { + jsonrpc: '2.0', + id, + ...params, + }; + + // Simulate server processing + const handler = this.handlers.get('message'); + if (handler) { + const response = await handler(request); + if (response.id === id) { + return response.result || response.error; + } + } + + throw new Error('No response received'); + } + + async notify(params: any): Promise { + const notification = { + jsonrpc: '2.0', + ...params, + }; + + const handler = this.handlers.get('message'); + if (handler) { + await handler(notification); + } + } +} +``` + +## MCP Inspector Testing + +### Manual Testing Workflow + +```bash +# 1. Start your server +npm run dev + +# 2. Launch MCP Inspector +npx @modelcontextprotocol/inspector + +# 3. Connect to server +# - Select stdio transport +# - Enter: node dist/index.js + +# 4. Test capabilities +# - View available tools +# - Test tool execution +# - Browse resources +# - Try prompt templates +``` + +### Inspector Test Scenarios + +```typescript +// Document test scenarios for manual testing +const testScenarios = [ + { + name: 'Basic Tool Execution', + steps: [ + 'Connect to server', + 'Select "example_tool" from tools list', + 'Enter { "input": "test" } as arguments', + 'Click Execute', + 'Verify response contains expected output', + ], + }, + { + name: 'Error Handling', + steps: [ + 'Connect to server', + 'Select any tool', + 'Enter invalid arguments', + 'Verify error response with appropriate code', + ], + }, + { + name: 'Resource Access', + steps: [ + 'Connect to server', + 'Navigate to Resources tab', + 'Select a resource', + 'Click Read', + 'Verify content is displayed correctly', + ], + }, +]; +``` + +## Coverage and Quality + +### Coverage Goals + +```typescript +// Aim for high coverage +const coverageTargets = { + statements: 80, + branches: 75, + functions: 80, + lines: 80, +}; +``` + +### Test Quality Checklist + +```typescript +const testQualityChecklist = [ + 'All handlers have unit tests', + 'Error cases are tested', + 'Edge cases are covered', + 'Integration tests cover full flow', + 'Protocol compliance is validated', + 'Performance tests for heavy operations', + 'Security tests for input validation', +]; +``` + +## Performance Testing + +```typescript +describe('Performance', () => { + it('should handle concurrent requests', async () => { + const requests = Array.from({ length: 100 }, (_, i) => + handleTool('search', { query: `query ${i}` }) + ); + + const start = Date.now(); + const results = await Promise.all(requests); + const duration = Date.now() - start; + + expect(results).toHaveLength(100); + expect(duration).toBeLessThan(5000); // 5 seconds for 100 requests + }); + + it('should not leak memory', async () => { + const initialMemory = process.memoryUsage().heapUsed; + + // Run many operations + for (let i = 0; i < 1000; i++) { + await handleTool('search', { query: 'test' }); + } + + // Force garbage collection if available + if (global.gc) { + global.gc(); + } + + const finalMemory = process.memoryUsage().heapUsed; + const leak = finalMemory - initialMemory; + + expect(leak).toBeLessThan(10 * 1024 * 1024); // Less than 10MB + }); +}); +``` + +## When to Consult This Agent + +- Writing test suites for MCP servers +- Setting up testing frameworks +- Creating integration tests +- Testing protocol compliance +- Using MCP Inspector effectively +- Improving test coverage \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/agents/tool-builder.md b/mcp-servers/simple-mcp-server/.claude/agents/tool-builder.md new file mode 100644 index 0000000..37af687 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/agents/tool-builder.md @@ -0,0 +1,264 @@ +# MCP Tool Implementation Specialist + +You are an expert in implementing tools for MCP servers. You understand tool schemas, parameter validation, response formatting, and best practices for creating robust, user-friendly tools. + +## Expertise Areas + +- **Tool Design** - Creating intuitive, powerful tools +- **Schema Definition** - JSON Schema and Zod validation +- **Parameter Handling** - Input validation and transformation +- **Response Formatting** - Text, images, and structured data +- **Error Messages** - User-friendly error reporting + +## Tool Implementation Patterns + +### Basic Tool Structure + +```typescript +interface Tool { + name: string; + description: string; + inputSchema: JSONSchema; + handler: (args: unknown) => Promise; +} +``` + +### Schema Definition + +```typescript +// JSON Schema for tool parameters +const toolSchema = { + type: 'object', + properties: { + query: { + type: 'string', + description: 'Search query', + minLength: 1, + maxLength: 100, + }, + options: { + type: 'object', + properties: { + limit: { + type: 'number', + minimum: 1, + maximum: 100, + default: 10, + }, + format: { + type: 'string', + enum: ['json', 'text', 'markdown'], + default: 'text', + }, + }, + }, + }, + required: ['query'], +}; +``` + +### Zod Validation + +```typescript +import { z } from 'zod'; + +const ToolArgsSchema = z.object({ + query: z.string().min(1).max(100), + options: z.object({ + limit: z.number().int().min(1).max(100).default(10), + format: z.enum(['json', 'text', 'markdown']).default('text'), + }).optional(), +}); + +type ToolArgs = z.infer; +``` + +### Handler Implementation + +```typescript +async function handleTool(args: unknown): Promise { + // 1. Validate input + const validated = ToolArgsSchema.safeParse(args); + if (!validated.success) { + return { + error: { + code: 'INVALID_PARAMS', + message: 'Invalid parameters', + data: validated.error.format(), + }, + }; + } + + // 2. Process request + try { + const result = await processQuery(validated.data); + + // 3. Format response + return { + content: [ + { + type: 'text', + text: formatResult(result, validated.data.options?.format), + }, + ], + }; + } catch (error) { + // 4. Handle errors + return handleError(error); + } +} +``` + +## Response Types + +### Text Response + +```typescript +{ + content: [ + { + type: 'text', + text: 'Plain text response', + }, + ], +} +``` + +### Image Response + +```typescript +{ + content: [ + { + type: 'image', + data: base64EncodedImage, + mimeType: 'image/png', + }, + ], +} +``` + +### Mixed Content + +```typescript +{ + content: [ + { + type: 'text', + text: 'Here is the chart:', + }, + { + type: 'image', + data: chartImage, + mimeType: 'image/svg+xml', + }, + ], +} +``` + +## Best Practices + +1. **Clear Naming** + - Use descriptive, action-oriented names + - Follow consistent naming conventions + - Avoid abbreviations + +2. **Comprehensive Descriptions** + - Explain what the tool does + - Document all parameters + - Provide usage examples + +3. **Robust Validation** + - Validate all inputs + - Provide helpful error messages + - Handle edge cases + +4. **Efficient Processing** + - Implement timeouts for long operations + - Use progress notifications + - Cache when appropriate + +5. **Helpful Responses** + - Format output clearly + - Include relevant context + - Suggest next steps + +## Common Tool Patterns + +### CRUD Operations + +```typescript +const crudTools = [ + { name: 'create_item', handler: createHandler }, + { name: 'read_item', handler: readHandler }, + { name: 'update_item', handler: updateHandler }, + { name: 'delete_item', handler: deleteHandler }, + { name: 'list_items', handler: listHandler }, +]; +``` + +### Search and Filter + +```typescript +const searchTool = { + name: 'search', + inputSchema: { + type: 'object', + properties: { + query: { type: 'string' }, + filters: { + type: 'object', + properties: { + category: { type: 'string' }, + dateRange: { + type: 'object', + properties: { + start: { type: 'string', format: 'date' }, + end: { type: 'string', format: 'date' }, + }, + }, + }, + }, + sort: { + type: 'object', + properties: { + field: { type: 'string' }, + order: { type: 'string', enum: ['asc', 'desc'] }, + }, + }, + }, + }, +}; +``` + +### Batch Operations + +```typescript +const batchTool = { + name: 'batch_process', + inputSchema: { + type: 'object', + properties: { + items: { + type: 'array', + items: { type: 'string' }, + minItems: 1, + maxItems: 100, + }, + operation: { + type: 'string', + enum: ['validate', 'transform', 'analyze'], + }, + }, + }, +}; +``` + +## When to Consult This Agent + +- Creating new tools for your MCP server +- Designing tool schemas and parameters +- Implementing validation logic +- Formatting tool responses +- Optimizing tool performance +- Debugging tool execution issues \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/commands/add-prompt.md b/mcp-servers/simple-mcp-server/.claude/commands/add-prompt.md new file mode 100644 index 0000000..5f9f007 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/commands/add-prompt.md @@ -0,0 +1,242 @@ +# Add Prompt Template to MCP Server + +Adds a new prompt template to your MCP server for reusable conversation patterns. + +## Usage + +``` +/add-prompt [arguments] +``` + +## Examples + +``` +/add-prompt code_review "Review code for improvements" language:string file:string? +/add-prompt analyze_data "Analyze data patterns" dataset:string metrics:array +/add-prompt generate_docs "Generate documentation" codebase:string style:enum[minimal,detailed] +``` + +## Implementation + +```typescript +import * as fs from 'fs/promises'; +import * as path from 'path'; + +async function addPrompt( + name: string, + description: string, + argumentDefs?: string[] +) { + // Parse arguments + const args = parsePromptArguments(argumentDefs || []); + + // Generate prompt file + const promptContent = generatePromptFile(name, description, args); + + // Write prompt file + const promptPath = path.join('src/prompts', `${name}.ts`); + await fs.writeFile(promptPath, promptContent); + + // Update prompt index + await updatePromptIndex(name); + + // Generate test file + const testContent = generatePromptTest(name); + const testPath = path.join('tests/unit/prompts', `${name}.test.ts`); + await fs.writeFile(testPath, testContent); + + console.log(`✅ Prompt "${name}" added successfully!`); + console.log(` - Implementation: ${promptPath}`); + console.log(` - Test file: ${testPath}`); + console.log(`\nNext steps:`); + console.log(` 1. Define the prompt template in ${promptPath}`); + console.log(` 2. Test with MCP Inspector`); +} + +interface PromptArgument { + name: string; + description: string; + required: boolean; + type: string; +} + +function parsePromptArguments(argumentDefs: string[]): PromptArgument[] { + return argumentDefs.map(def => { + const [nameWithType, description] = def.split('='); + const [nameType] = nameWithType.split(':'); + const isOptional = nameType.endsWith('?'); + const name = isOptional ? nameType.slice(0, -1) : nameType; + const type = nameWithType.split(':')[1] || 'string'; + + return { + name, + description: description || `${name} parameter`, + required: !isOptional, + type: type.replace('?', ''), + }; + }); +} + +function generatePromptFile( + name: string, + description: string, + args: PromptArgument[] +): string { + return ` +import type { Prompt, PromptMessage } from '../types/prompts.js'; + +export const ${name}Prompt: Prompt = { + name: '${name}', + description: '${description}', + arguments: [ +${args.map(arg => ` { + name: '${arg.name}', + description: '${arg.description}', + required: ${arg.required}, + },`).join('\n')} + ], +}; + +export function get${capitalize(name)}Prompt( + args: Record +): PromptMessage[] { + // Validate required arguments +${args.filter(a => a.required).map(arg => ` if (!args.${arg.name}) { + throw new Error('Missing required argument: ${arg.name}'); + }`).join('\n')} + + // Build prompt messages + const messages: PromptMessage[] = []; + + // System message (optional) + messages.push({ + role: 'system', + content: { + type: 'text', + text: buildSystemPrompt(args), + }, + }); + + // User message + messages.push({ + role: 'user', + content: { + type: 'text', + text: buildUserPrompt(args), + }, + }); + + return messages; +} + +function buildSystemPrompt(args: Record): string { + // TODO: Define the system prompt template + return \`You are an expert assistant helping with ${description.toLowerCase()}.\`; +} + +function buildUserPrompt(args: Record): string { + // TODO: Define the user prompt template + let prompt = '${description}\\n\\n'; + +${args.map(arg => ` if (args.${arg.name}) { + prompt += \`${capitalize(arg.name)}: \${args.${arg.name}}\\n\`; + }`).join('\n')} + + return prompt; +} + +function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} +`; +} + +function generatePromptTest(name: string): string { + return ` +import { describe, it, expect } from 'vitest'; +import { ${name}Prompt, get${capitalize(name)}Prompt } from '../../src/prompts/${name}.js'; + +describe('${name} prompt', () => { + it('should have correct metadata', () => { + expect(${name}Prompt.name).toBe('${name}'); + expect(${name}Prompt.description).toBeDefined(); + expect(${name}Prompt.arguments).toBeDefined(); + }); + + it('should generate prompt messages', () => { + const args = { + // TODO: Add test arguments + }; + + const messages = get${capitalize(name)}Prompt(args); + + expect(messages).toBeInstanceOf(Array); + expect(messages.length).toBeGreaterThan(0); + expect(messages[0]).toHaveProperty('role'); + expect(messages[0]).toHaveProperty('content'); + }); + + it('should validate required arguments', () => { + const invalidArgs = {}; + + expect(() => get${capitalize(name)}Prompt(invalidArgs)) + .toThrow('Missing required argument'); + }); + + it('should handle optional arguments', () => { + const minimalArgs = { + // Only required args + }; + + const messages = get${capitalize(name)}Prompt(minimalArgs); + expect(messages).toBeDefined(); + }); +}); +`; +} + +function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +async function updatePromptIndex(name: string) { + const indexPath = 'src/prompts/index.ts'; + + try { + let content = await fs.readFile(indexPath, 'utf-8'); + + // Add import + const importLine = `import { ${name}Prompt, get${capitalize(name)}Prompt } from './${name}.js';`; + if (!content.includes(importLine)) { + const lastImport = content.lastIndexOf('import'); + const endOfLastImport = content.indexOf('\n', lastImport); + content = content.slice(0, endOfLastImport + 1) + importLine + '\n' + content.slice(endOfLastImport + 1); + } + + // Add to exports + const exportPattern = /export const prompts = \[([^\]]*)]\;/; + const match = content.match(exportPattern); + if (match) { + const currentExports = match[1].trim(); + const newExports = currentExports ? `${currentExports},\n ${name}Prompt` : `\n ${name}Prompt\n`; + content = content.replace(exportPattern, `export const prompts = [${newExports}];`); + } + + await fs.writeFile(indexPath, content); + } catch (error) { + // Create index file if it doesn't exist + const newIndex = ` +import { ${name}Prompt, get${capitalize(name)}Prompt } from './${name}.js'; + +export const prompts = [ + ${name}Prompt, +]; + +export const promptHandlers = { + '${name}': get${capitalize(name)}Prompt, +}; +`; + await fs.writeFile(indexPath, newIndex); + } +} +``` \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/commands/add-resource.md b/mcp-servers/simple-mcp-server/.claude/commands/add-resource.md new file mode 100644 index 0000000..aff9e54 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/commands/add-resource.md @@ -0,0 +1,243 @@ +# Add Resource to MCP Server + +Adds a new resource endpoint to your MCP server with proper URI handling. + +## Usage + +``` +/add-resource [uri-pattern] [mime-type] +``` + +## Examples + +``` +/add-resource config "Server configuration" config://settings application/json +/add-resource users "User database" data://users/{id} application/json +/add-resource files "File system access" file:///{path} text/plain +``` + +## Implementation + +```typescript +import * as fs from 'fs/promises'; +import * as path from 'path'; + +async function addResource( + name: string, + description: string, + uriPattern?: string, + mimeType: string = 'application/json' +) { + // Generate URI pattern if not provided + const uri = uriPattern || `${name}://default`; + + // Generate resource file + const resourceContent = generateResourceFile(name, description, uri, mimeType); + + // Write resource file + const resourcePath = path.join('src/resources', `${name}.ts`); + await fs.writeFile(resourcePath, resourceContent); + + // Update resource index + await updateResourceIndex(name); + + // Generate test file + const testContent = generateResourceTest(name, uri); + const testPath = path.join('tests/unit/resources', `${name}.test.ts`); + await fs.writeFile(testPath, testContent); + + console.log(`✅ Resource "${name}" added successfully!`); + console.log(` - Implementation: ${resourcePath}`); + console.log(` - Test file: ${testPath}`); + console.log(` - URI pattern: ${uri}`); + console.log(` - MIME type: ${mimeType}`); + console.log(`\nNext steps:`); + console.log(` 1. Implement the resource provider in ${resourcePath}`); + console.log(` 2. Test with MCP Inspector`); +} + +function generateResourceFile( + name: string, + description: string, + uri: string, + mimeType: string +): string { + const hasDynamicParams = uri.includes('{'); + + return ` +import type { Resource, ResourceContent } from '../types/resources.js'; + +export const ${name}Resource: Resource = { + uri: '${uri}', + name: '${name}', + description: '${description}', + mimeType: '${mimeType}', +}; + +export async function read${capitalize(name)}Resource( + uri: string +): Promise { + ${hasDynamicParams ? generateDynamicResourceHandler(uri) : generateStaticResourceHandler()} + + return [ + { + uri, + mimeType: '${mimeType}', + text: ${mimeType === 'application/json' ? 'JSON.stringify(data, null, 2)' : 'data'}, + }, + ]; +} + +${generateResourceDataFunction(name, mimeType)} +`; +} + +function generateDynamicResourceHandler(uriPattern: string): string { + return ` + // Parse dynamic parameters from URI + const params = parseUriParams('${uriPattern}', uri); + + // Fetch data based on parameters + const data = await fetch${capitalize(name)}Data(params); + + if (!data) { + throw new Error(\`Resource not found: \${uri}\`); + } +`; +} + +function generateStaticResourceHandler(): string { + return ` + // Fetch static resource data + const data = await fetch${capitalize(name)}Data(); +`; +} + +function generateResourceDataFunction(name: string, mimeType: string): string { + if (mimeType === 'application/json') { + return ` +async function fetch${capitalize(name)}Data(params?: Record) { + // TODO: Implement data fetching logic + // This is a placeholder implementation + + if (params?.id) { + // Return specific item + return { + id: params.id, + name: 'Example Item', + timestamp: new Date().toISOString(), + }; + } + + // Return collection + return { + items: [ + { id: '1', name: 'Item 1' }, + { id: '2', name: 'Item 2' }, + ], + total: 2, + }; +} + +function parseUriParams(pattern: string, uri: string): Record { + // Convert pattern to regex + const regexPattern = pattern + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/\{(\w+)\}/g, '(?<$1>[^/]+)'); + + const regex = new RegExp(\`^\${regexPattern}$\`); + const match = uri.match(regex); + + return match?.groups || {}; +} +`; + } else { + return ` +async function fetch${capitalize(name)}Data(params?: Record) { + // TODO: Implement data fetching logic + // This is a placeholder implementation + + return 'Resource content as ${mimeType}'; +} +`; + } +} + +function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +function generateResourceTest(name: string, uri: string): string { + return ` +import { describe, it, expect } from 'vitest'; +import { ${name}Resource, read${capitalize(name)}Resource } from '../../src/resources/${name}.js'; + +describe('${name} resource', () => { + it('should have correct metadata', () => { + expect(${name}Resource.name).toBe('${name}'); + expect(${name}Resource.uri).toBe('${uri}'); + expect(${name}Resource.description).toBeDefined(); + expect(${name}Resource.mimeType).toBeDefined(); + }); + + it('should read resource content', async () => { + const content = await read${capitalize(name)}Resource('${uri.replace('{id}', 'test-id')}'); + + expect(content).toBeInstanceOf(Array); + expect(content[0]).toHaveProperty('uri'); + expect(content[0]).toHaveProperty('mimeType'); + expect(content[0]).toHaveProperty('text'); + }); + + it('should handle missing resources', async () => { + // TODO: Add tests for missing resources + }); + + it('should validate URI format', () => { + // TODO: Add URI validation tests + }); +}); +`; +} + +async function updateResourceIndex(name: string) { + const indexPath = 'src/resources/index.ts'; + + try { + let content = await fs.readFile(indexPath, 'utf-8'); + + // Add import + const importLine = `import { ${name}Resource, read${capitalize(name)}Resource } from './${name}.js';`; + if (!content.includes(importLine)) { + const lastImport = content.lastIndexOf('import'); + const endOfLastImport = content.indexOf('\n', lastImport); + content = content.slice(0, endOfLastImport + 1) + importLine + '\n' + content.slice(endOfLastImport + 1); + } + + // Add to exports + const exportPattern = /export const resources = \[([^\]]*)]\;/; + const match = content.match(exportPattern); + if (match) { + const currentExports = match[1].trim(); + const newExports = currentExports ? `${currentExports},\n ${name}Resource` : `\n ${name}Resource\n`; + content = content.replace(exportPattern, `export const resources = [${newExports}];`); + } + + await fs.writeFile(indexPath, content); + } catch (error) { + // Create index file if it doesn't exist + const newIndex = ` +import { ${name}Resource, read${capitalize(name)}Resource } from './${name}.js'; + +export const resources = [ + ${name}Resource, +]; + +export const resourceReaders = { + '${name}': read${capitalize(name)}Resource, +}; +`; + await fs.writeFile(indexPath, newIndex); + } +} +``` \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/commands/add-tool.md b/mcp-servers/simple-mcp-server/.claude/commands/add-tool.md new file mode 100644 index 0000000..da81212 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/commands/add-tool.md @@ -0,0 +1,207 @@ +# Add Tool to MCP Server + +Adds a new tool to your MCP server with proper schema validation and error handling. + +## Usage + +``` +/add-tool [parameters] +``` + +## Examples + +``` +/add-tool calculate "Performs mathematical calculations" +/add-tool search "Search for information" query:string limit:number? +/add-tool process_data "Process data with options" input:string format:enum[json,csv,xml] +``` + +## Implementation + +```typescript +import { z } from 'zod'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +async function addTool(name: string, description: string, parameters?: string[]) { + // Parse parameters into schema + const schema = parseParameterSchema(parameters || []); + + // Generate tool file + const toolContent = generateToolFile(name, description, schema); + + // Write tool file + const toolPath = path.join('src/tools', `${name}.ts`); + await fs.writeFile(toolPath, toolContent); + + // Update tool index + await updateToolIndex(name); + + // Generate test file + const testContent = generateToolTest(name); + const testPath = path.join('tests/unit/tools', `${name}.test.ts`); + await fs.writeFile(testPath, testContent); + + console.log(`✅ Tool "${name}" added successfully!`); + console.log(` - Implementation: ${toolPath}`); + console.log(` - Test file: ${testPath}`); + console.log(`\nNext steps:`); + console.log(` 1. Implement the tool logic in ${toolPath}`); + console.log(` 2. Run tests with "npm test"`); + console.log(` 3. Test with MCP Inspector`); +} + +function parseParameterSchema(parameters: string[]): any { + const properties: Record = {}; + const required: string[] = []; + + for (const param of parameters) { + const [nameType, ...rest] = param.split(':'); + const isOptional = nameType.endsWith('?'); + const name = isOptional ? nameType.slice(0, -1) : nameType; + const type = rest.join(':') || 'string'; + + if (!isOptional) { + required.push(name); + } + + properties[name] = parseType(type); + } + + return { + type: 'object', + properties, + required: required.length > 0 ? required : undefined, + }; +} + +function parseType(type: string): any { + if (type.startsWith('enum[')) { + const values = type.slice(5, -1).split(','); + return { + type: 'string', + enum: values, + }; + } + + switch (type) { + case 'number': + return { type: 'number' }; + case 'boolean': + return { type: 'boolean' }; + case 'array': + return { type: 'array', items: { type: 'string' } }; + default: + return { type: 'string' }; + } +} + +function generateToolFile(name: string, description: string, schema: any): string { + return ` +import { z } from 'zod'; +import type { ToolHandler } from '../types/tools.js'; + +// Define Zod schema for validation +const ${capitalize(name)}Schema = z.object({ +${generateZodSchema(schema.properties, ' ')} +}); + +export type ${capitalize(name)}Args = z.infer; + +export const ${name}Tool = { + name: '${name}', + description: '${description}', + inputSchema: ${JSON.stringify(schema, null, 2)}, + handler: async (args: unknown): Promise => { + // Validate input + const validated = ${capitalize(name)}Schema.parse(args); + + // TODO: Implement your tool logic here + const result = await process${capitalize(name)}(validated); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, +}; + +async function process${capitalize(name)}(args: ${capitalize(name)}Args) { + // TODO: Implement the actual processing logic + return { + success: true, + message: 'Tool executed successfully', + input: args, + }; +} +`; +} + +function generateZodSchema(properties: Record, indent: string): string { + const lines: string[] = []; + + for (const [key, value] of Object.entries(properties)) { + let zodType = 'z.string()'; + + if (value.type === 'number') { + zodType = 'z.number()'; + } else if (value.type === 'boolean') { + zodType = 'z.boolean()'; + } else if (value.enum) { + zodType = `z.enum([${value.enum.map((v: string) => `'${v}'`).join(', ')}])`; + } else if (value.type === 'array') { + zodType = 'z.array(z.string())'; + } + + lines.push(`${indent}${key}: ${zodType},`); + } + + return lines.join('\n'); +} + +function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +function generateToolTest(name: string): string { + return ` +import { describe, it, expect, vi } from 'vitest'; +import { ${name}Tool } from '../../src/tools/${name}.js'; + +describe('${name} tool', () => { + it('should have correct metadata', () => { + expect(${name}Tool.name).toBe('${name}'); + expect(${name}Tool.description).toBeDefined(); + expect(${name}Tool.inputSchema).toBeDefined(); + }); + + it('should validate input parameters', async () => { + const invalidInput = {}; + + await expect(${name}Tool.handler(invalidInput)) + .rejects + .toThrow(); + }); + + it('should handle valid input', async () => { + const validInput = { + // TODO: Add valid test input + }; + + const result = await ${name}Tool.handler(validInput); + + expect(result).toHaveProperty('content'); + expect(result.content[0]).toHaveProperty('type', 'text'); + }); + + it('should handle errors gracefully', async () => { + // TODO: Add error handling tests + }); +}); +`; +} +``` \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/commands/build.md b/mcp-servers/simple-mcp-server/.claude/commands/build.md new file mode 100644 index 0000000..ed99096 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/commands/build.md @@ -0,0 +1,377 @@ +# Build MCP Server for Production + +Builds and prepares your MCP server for production deployment. + +## Usage + +``` +/build [target] [options] +``` + +## Targets + +- `node` - Build for Node.js (default) +- `docker` - Build Docker image +- `npm` - Prepare for npm publishing + +## Options + +- `--minify` - Minify output +- `--sourcemap` - Include source maps +- `--analyze` - Analyze bundle size + +## Implementation + +```typescript +import { exec } from 'child_process'; +import { promisify } from 'util'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +const execAsync = promisify(exec); + +async function buildServer( + target: 'node' | 'docker' | 'npm' = 'node', + options: { + minify?: boolean; + sourcemap?: boolean; + analyze?: boolean; + } = {} +) { + console.log('🔨 Building MCP Server for Production'); + console.log('='.repeat(50)); + + // Pre-build checks + await runPreBuildChecks(); + + // Build based on target + switch (target) { + case 'node': + await buildForNode(options); + break; + case 'docker': + await buildForDocker(options); + break; + case 'npm': + await buildForNpm(options); + break; + } + + // Post-build validation + await validateBuild(target); + + console.log('\n✅ Build completed successfully!'); +} + +async function runPreBuildChecks() { + console.log('\n🔍 Running pre-build checks...'); + + // Check for uncommitted changes + try { + const { stdout: gitStatus } = await execAsync('git status --porcelain'); + if (gitStatus.trim()) { + console.warn('⚠️ Warning: You have uncommitted changes'); + } + } catch { + // Git not available or not a git repo + } + + // Run tests + console.log(' Running tests...'); + try { + await execAsync('npm test'); + console.log(' ✅ Tests passed'); + } catch (error) { + console.error(' ❌ Tests failed'); + throw new Error('Build aborted: tests must pass'); + } + + // Check dependencies + console.log(' Checking dependencies...'); + try { + await execAsync('npm audit --production'); + console.log(' ✅ No vulnerabilities found'); + } catch (error) { + console.warn(' ⚠️ Security vulnerabilities detected'); + console.log(' Run "npm audit fix" to resolve'); + } +} + +async function buildForNode(options: any) { + console.log('\n📦 Building for Node.js...'); + + // Clean previous build + await fs.rm('dist', { recursive: true, force: true }); + + // Update tsconfig for production + const tsConfig = JSON.parse(await fs.readFile('tsconfig.json', 'utf-8')); + const prodConfig = { + ...tsConfig, + compilerOptions: { + ...tsConfig.compilerOptions, + sourceMap: options.sourcemap || false, + inlineSources: false, + removeComments: true, + }, + }; + + await fs.writeFile('tsconfig.prod.json', JSON.stringify(prodConfig, null, 2)); + + // Build with TypeScript + console.log(' Compiling TypeScript...'); + await execAsync('npx tsc -p tsconfig.prod.json'); + + // Minify if requested + if (options.minify) { + console.log(' Minifying code...'); + await minifyCode(); + } + + // Copy package files + console.log(' Copying package files...'); + await fs.copyFile('package.json', 'dist/package.json'); + await fs.copyFile('README.md', 'dist/README.md').catch(() => {}); + await fs.copyFile('LICENSE', 'dist/LICENSE').catch(() => {}); + + // Create production package.json + const pkg = JSON.parse(await fs.readFile('package.json', 'utf-8')); + const prodPkg = { + ...pkg, + scripts: { + start: 'node index.js', + }, + devDependencies: undefined, + }; + await fs.writeFile('dist/package.json', JSON.stringify(prodPkg, null, 2)); + + // Analyze bundle if requested + if (options.analyze) { + await analyzeBundleSize(); + } + + console.log(' ✅ Node.js build complete'); + console.log(' Output: ./dist'); +} + +async function buildForDocker(options: any) { + console.log('\n🐋 Building Docker image...'); + + // Build Node.js first + await buildForNode(options); + + // Create Dockerfile if it doesn't exist + const dockerfilePath = 'Dockerfile'; + if (!await fs.access(dockerfilePath).then(() => true).catch(() => false)) { + await createDockerfile(); + } + + // Build Docker image + console.log(' Building Docker image...'); + const imageName = 'mcp-server'; + const version = JSON.parse(await fs.readFile('package.json', 'utf-8')).version; + + await execAsync(`docker build -t ${imageName}:${version} -t ${imageName}:latest .`); + + // Test the image + console.log(' Testing Docker image...'); + const { stdout } = await execAsync(`docker run --rm ${imageName}:latest node index.js --version`); + console.log(` Version: ${stdout.trim()}`); + + console.log(' ✅ Docker build complete'); + console.log(` Image: ${imageName}:${version}`); + console.log(' Run: docker run -it ' + imageName + ':latest'); +} + +async function buildForNpm(options: any) { + console.log('\n📦 Preparing for npm publishing...'); + + // Build Node.js first + await buildForNode(options); + + // Validate package.json + const pkg = JSON.parse(await fs.readFile('package.json', 'utf-8')); + + if (!pkg.name) { + throw new Error('package.json must have a name field'); + } + + if (!pkg.version) { + throw new Error('package.json must have a version field'); + } + + if (!pkg.description) { + console.warn('⚠️ Warning: package.json should have a description'); + } + + // Create .npmignore if needed + const npmignorePath = '.npmignore'; + if (!await fs.access(npmignorePath).then(() => true).catch(() => false)) { + await createNpmIgnore(); + } + + // Run npm pack to test + console.log(' Creating package tarball...'); + const { stdout } = await execAsync('npm pack --dry-run'); + + // Parse package contents + const files = stdout.split('\n').filter(line => line.includes('npm notice')); + console.log(` Package will include ${files.length} files`); + + // Check package size + const sizeMatch = stdout.match(/npm notice ([\d.]+[kMG]B)/); + if (sizeMatch) { + console.log(` Package size: ${sizeMatch[1]}`); + + // Warn if package is large + if (sizeMatch[1].includes('M') && parseFloat(sizeMatch[1]) > 10) { + console.warn('⚠️ Warning: Package is larger than 10MB'); + } + } + + console.log(' ✅ npm package ready'); + console.log(' Publish with: npm publish'); +} + +async function createDockerfile() { + const dockerfile = ` +# Build stage +FROM node:20-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build + +# Production stage +FROM node:20-alpine +WORKDIR /app + +# Install dumb-init for signal handling +RUN apk add --no-cache dumb-init + +# Create non-root user +RUN addgroup -g 1001 -S nodejs && \\ + adduser -S nodejs -u 1001 + +# Copy built application +COPY --from=builder /app/dist ./ +COPY --from=builder /app/node_modules ./node_modules + +# Change ownership +RUN chown -R nodejs:nodejs /app + +USER nodejs + +EXPOSE 3000 + +ENTRYPOINT ["dumb-init", "--"] +CMD ["node", "index.js"] +`; + + await fs.writeFile('Dockerfile', dockerfile); + console.log(' Created Dockerfile'); +} + +async function createNpmIgnore() { + const npmignore = ` +# Source files +src/ +tests/ + +# Config files +.eslintrc* +.prettierrc* +tsconfig*.json +vitest.config.ts + +# Development files +*.log +.env +.env.* +!.env.example + +# Build artifacts +*.tsbuildinfo +coverage/ +.nyc_output/ + +# Docker +Dockerfile +docker-compose.yml + +# Git +.git/ +.gitignore + +# CI/CD +.github/ +.gitlab-ci.yml + +# IDE +.vscode/ +.idea/ + +# Misc +.DS_Store +Thumbs.db +`; + + await fs.writeFile('.npmignore', npmignore); + console.log(' Created .npmignore'); +} + +async function minifyCode() { + // Use esbuild or terser to minify + try { + await execAsync('npx esbuild dist/**/*.js --minify --outdir=dist --allow-overwrite'); + } catch { + console.warn(' Minification skipped (esbuild not available)'); + } +} + +async function analyzeBundleSize() { + console.log('\n📊 Analyzing bundle size...'); + + const files = await fs.readdir('dist', { recursive: true }); + let totalSize = 0; + const fileSizes: Array<{ name: string; size: number }> = []; + + for (const file of files) { + const filePath = path.join('dist', file as string); + const stat = await fs.stat(filePath); + + if (stat.isFile()) { + totalSize += stat.size; + fileSizes.push({ name: file as string, size: stat.size }); + } + } + + // Sort by size + fileSizes.sort((a, b) => b.size - a.size); + + console.log(' Largest files:'); + fileSizes.slice(0, 5).forEach(file => { + console.log(` ${file.name}: ${(file.size / 1024).toFixed(2)}KB`); + }); + + console.log(` Total size: ${(totalSize / 1024).toFixed(2)}KB`); +} + +async function validateBuild(target: string) { + console.log('\n🔍 Validating build...'); + + // Check that main file exists + const mainFile = 'dist/index.js'; + if (!await fs.access(mainFile).then(() => true).catch(() => false)) { + throw new Error('Build validation failed: main file not found'); + } + + // Try to load the server + try { + await import(path.resolve(mainFile)); + console.log(' ✅ Server module loads successfully'); + } catch (error) { + throw new Error(`Build validation failed: ${error.message}`); + } +} +``` \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/commands/debug.md b/mcp-servers/simple-mcp-server/.claude/commands/debug.md new file mode 100644 index 0000000..47eca5f --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/commands/debug.md @@ -0,0 +1,310 @@ +# Debug MCP Server + +Provides comprehensive debugging tools for troubleshooting MCP server issues. + +## Usage + +``` +/debug [component] [options] +``` + +## Components + +- `protocol` - Debug protocol messages +- `tools` - Debug tool execution +- `resources` - Debug resource access +- `transport` - Debug transport layer +- `all` - Enable all debugging (default) + +## Options + +- `--verbose` - Extra verbose output +- `--save` - Save debug logs to file +- `--inspector` - Launch with MCP Inspector + +## Implementation + +```typescript +import * as fs from 'fs/promises'; +import { exec, spawn } from 'child_process'; +import * as path from 'path'; + +async function debugServer( + component: 'protocol' | 'tools' | 'resources' | 'transport' | 'all' = 'all', + options: { + verbose?: boolean; + save?: boolean; + inspector?: boolean; + } = {} +) { + console.log('🔍 MCP Server Debugger'); + console.log('='.repeat(50)); + + // Set debug environment variables + const debugEnv = { + ...process.env, + DEBUG: component === 'all' ? 'mcp:*' : `mcp:${component}`, + LOG_LEVEL: options.verbose ? 'trace' : 'debug', + MCP_DEBUG: 'true', + }; + + // Create debug configuration + const debugConfig = await createDebugConfig(); + + // Start debug session + if (options.inspector) { + await launchWithInspector(debugEnv); + } else { + await runDebugSession(component, debugEnv, options); + } +} + +async function createDebugConfig(): Promise { + const config = { + logging: { + level: 'debug', + format: 'pretty', + includeTimestamp: true, + includeLocation: true, + }, + debug: { + protocol: { + logRequests: true, + logResponses: true, + logNotifications: true, + }, + tools: { + logCalls: true, + logValidation: true, + logErrors: true, + measurePerformance: true, + }, + resources: { + logReads: true, + logWrites: true, + trackCache: true, + }, + transport: { + logConnections: true, + logMessages: true, + logErrors: true, + }, + }, + }; + + const configPath = '.debug-config.json'; + await fs.writeFile(configPath, JSON.stringify(config, null, 2)); + return configPath; +} + +async function runDebugSession( + component: string, + env: NodeJS.ProcessEnv, + options: { verbose?: boolean; save?: boolean } +) { + console.log(`\n🔍 Debugging: ${component}`); + console.log('Press Ctrl+C to stop\n'); + + // Create debug wrapper + const debugScript = ` +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import debug from 'debug'; +import pino from 'pino'; + +// Enable debug logging +const log = { + protocol: debug('mcp:protocol'), + tools: debug('mcp:tools'), + resources: debug('mcp:resources'), + transport: debug('mcp:transport'), +}; + +// Create logger +const logger = pino({ + level: process.env.LOG_LEVEL || 'debug', + transport: { + target: 'pino-pretty', + options: { + colorize: true, + translateTime: 'HH:MM:ss.l', + ignore: 'pid,hostname', + }, + }, +}); + +// Wrap server methods for debugging +const originalServer = await import('./src/index.js'); +const server = originalServer.server; + +// Intercept requests +const originalSetRequestHandler = server.setRequestHandler.bind(server); +server.setRequestHandler = (schema, handler) => { + const wrappedHandler = async (request) => { + const start = Date.now(); + log.protocol('→ Request: %O', request); + logger.debug({ request }, 'Incoming request'); + + try { + const result = await handler(request); + const duration = Date.now() - start; + + log.protocol('← Response (%dms): %O', duration, result); + logger.debug({ result, duration }, 'Response sent'); + + return result; + } catch (error) { + log.protocol('✗ Error: %O', error); + logger.error({ error }, 'Request failed'); + throw error; + } + }; + + return originalSetRequestHandler(schema, wrappedHandler); +}; + +// Start server with debugging +logger.info('Debug server starting...'); +const transport = new StdioServerTransport(); +await server.connect(transport); +logger.info('Debug server ready'); +`; + + // Write debug script + const debugScriptPath = '.debug-server.js'; + await fs.writeFile(debugScriptPath, debugScript); + + // Start server with debugging + const serverProcess = spawn('node', [debugScriptPath], { + env, + stdio: options.save ? 'pipe' : 'inherit', + }); + + if (options.save) { + const logFile = `debug-${component}-${Date.now()}.log`; + const logStream = await fs.open(logFile, 'w'); + + serverProcess.stdout?.pipe(logStream.createWriteStream()); + serverProcess.stderr?.pipe(logStream.createWriteStream()); + + console.log(`Saving debug output to: ${logFile}`); + } + + // Handle cleanup + process.on('SIGINT', () => { + serverProcess.kill(); + process.exit(); + }); + + serverProcess.on('exit', async () => { + // Cleanup + await fs.unlink(debugScriptPath).catch(() => {}); + await fs.unlink('.debug-config.json').catch(() => {}); + }); +} + +async function launchWithInspector(env: NodeJS.ProcessEnv) { + console.log('\n🔍 Launching with MCP Inspector...'); + console.log('This will provide an interactive debugging interface.\n'); + + // Start server in debug mode + const serverProcess = spawn('node', ['--inspect', 'dist/index.js'], { + env, + stdio: 'pipe', + }); + + // Parse debug port from output + serverProcess.stderr?.on('data', (data) => { + const output = data.toString(); + const match = output.match(/Debugger listening on ws:\/\/(.+):(\d+)/); + if (match) { + console.log(`🔗 Node.js debugger: chrome://inspect`); + console.log(` Connect to: ${match[1]}:${match[2]}`); + } + }); + + // Wait a moment for server to start + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Launch MCP Inspector + console.log('\n🔍 Starting MCP Inspector...'); + const inspector = exec('npx @modelcontextprotocol/inspector'); + + inspector.stdout?.on('data', (data) => { + console.log(data.toString()); + }); + + // Cleanup on exit + process.on('SIGINT', () => { + serverProcess.kill(); + inspector.kill(); + process.exit(); + }); +} + +// Additional debug utilities +export async function analyzeProtocolFlow() { + console.log('\n📊 Analyzing Protocol Flow...'); + + const checks = [ + { name: 'Initialization', test: testInitialization }, + { name: 'Capability Negotiation', test: testCapabilities }, + { name: 'Tool Discovery', test: testToolDiscovery }, + { name: 'Resource Listing', test: testResourceListing }, + { name: 'Error Handling', test: testErrorHandling }, + ]; + + for (const check of checks) { + try { + await check.test(); + console.log(` ✅ ${check.name}`); + } catch (error) { + console.log(` ❌ ${check.name}: ${error.message}`); + } + } +} + +async function testInitialization() { + // Test initialization flow + const { Server } = await import('@modelcontextprotocol/sdk/server/index.js'); + const server = new Server({ name: 'test', version: '1.0.0' }, {}); + if (!server) throw new Error('Server initialization failed'); +} + +async function testCapabilities() { + // Test capability declaration + const capabilities = { + tools: {}, + resources: {}, + prompts: {}, + }; + if (!capabilities.tools) throw new Error('Tools capability missing'); +} + +async function testToolDiscovery() { + // Test tool discovery + try { + const { tools } = await import('./src/tools/index.js'); + if (!Array.isArray(tools)) throw new Error('Tools not properly exported'); + } catch { + // Tools may not be implemented yet + } +} + +async function testResourceListing() { + // Test resource listing + try { + const { resources } = await import('./src/resources/index.js'); + if (!Array.isArray(resources)) throw new Error('Resources not properly exported'); + } catch { + // Resources may not be implemented yet + } +} + +async function testErrorHandling() { + // Test error handling + const { handleError } = await import('./src/utils/error-handler.js'); + const result = handleError(new Error('Test')); + if (!result.error) throw new Error('Error handler not working'); +} +``` \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/commands/deploy.md b/mcp-servers/simple-mcp-server/.claude/commands/deploy.md new file mode 100644 index 0000000..8b4049e --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/commands/deploy.md @@ -0,0 +1,376 @@ +# Deploy MCP Server + +Deploys your MCP server to various platforms and registries. + +## Usage + +``` +/deploy [target] [options] +``` + +## Targets + +- `npm` - Publish to npm registry +- `docker` - Push to Docker registry +- `claude` - Register with Claude Code +- `github` - Create GitHub release + +## Options + +- `--tag` - Version tag (default: from package.json) +- `--registry` - Custom registry URL +- `--dry-run` - Test deployment without publishing + +## Implementation + +```typescript +import { exec } from 'child_process'; +import { promisify } from 'util'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +const execAsync = promisify(exec); + +async function deployServer( + target: 'npm' | 'docker' | 'claude' | 'github', + options: { + tag?: string; + registry?: string; + dryRun?: boolean; + } = {} +) { + console.log('🚀 Deploying MCP Server'); + console.log('='.repeat(50)); + + // Get version info + const pkg = JSON.parse(await fs.readFile('package.json', 'utf-8')); + const version = options.tag || pkg.version; + + // Pre-deployment checks + await runPreDeploymentChecks(version); + + // Deploy based on target + switch (target) { + case 'npm': + await deployToNpm(pkg, version, options); + break; + case 'docker': + await deployToDocker(pkg, version, options); + break; + case 'claude': + await deployToClaude(pkg, version, options); + break; + case 'github': + await deployToGitHub(pkg, version, options); + break; + } + + console.log('\n✅ Deployment completed successfully!'); +} + +async function runPreDeploymentChecks(version: string) { + console.log('\n🔍 Running pre-deployment checks...'); + + // Check git status + try { + const { stdout: status } = await execAsync('git status --porcelain'); + if (status.trim()) { + throw new Error('Working directory has uncommitted changes'); + } + console.log(' ✅ Working directory clean'); + } catch (error) { + if (error.message.includes('uncommitted')) { + throw error; + } + console.warn(' ⚠️ Git not available'); + } + + // Check if version tag exists + try { + await execAsync(`git rev-parse v${version}`); + console.log(` ✅ Version tag v${version} exists`); + } catch { + console.warn(` ⚠️ Version tag v${version} not found`); + console.log(' Create with: git tag v' + version); + } + + // Verify build exists + const buildExists = await fs.access('dist').then(() => true).catch(() => false); + if (!buildExists) { + throw new Error('Build not found. Run /build first'); + } + console.log(' ✅ Build found'); + + // Run tests + console.log(' Running tests...'); + try { + await execAsync('npm test'); + console.log(' ✅ Tests passed'); + } catch { + throw new Error('Tests must pass before deployment'); + } +} + +async function deployToNpm(pkg: any, version: string, options: any) { + console.log(`\n📦 Deploying to npm (v${version})...`); + + // Check npm authentication + try { + await execAsync('npm whoami'); + console.log(' ✅ npm authenticated'); + } catch { + throw new Error('Not authenticated with npm. Run: npm login'); + } + + // Check if version already published + try { + const { stdout } = await execAsync(`npm view ${pkg.name}@${version}`); + if (stdout) { + throw new Error(`Version ${version} already published`); + } + } catch (error) { + if (error.message.includes('already published')) { + throw error; + } + // Version not published yet (good) + } + + // Update version if different + if (pkg.version !== version) { + console.log(` Updating version to ${version}...`); + await execAsync(`npm version ${version} --no-git-tag-version`); + } + + // Publish package + const publishCmd = options.dryRun + ? 'npm publish --dry-run' + : `npm publish ${options.registry ? `--registry ${options.registry}` : ''}`; + + console.log(' Publishing to npm...'); + const { stdout } = await execAsync(publishCmd); + + if (options.dryRun) { + console.log(' 🧪 Dry run complete (not published)'); + } else { + console.log(' ✅ Published to npm'); + console.log(` Install with: npm install ${pkg.name}`); + console.log(` View at: https://www.npmjs.com/package/${pkg.name}`); + } +} + +async function deployToDocker(pkg: any, version: string, options: any) { + console.log(`\n🐋 Deploying to Docker registry (v${version})...`); + + const imageName = pkg.name.replace('@', '').replace('/', '-'); + const registry = options.registry || 'docker.io'; + const fullImageName = `${registry}/${imageName}`; + + // Check Docker authentication + try { + await execAsync(`docker pull ${registry}/hello-world`); + console.log(' ✅ Docker authenticated'); + } catch { + console.warn(' ⚠️ May not be authenticated with Docker registry'); + } + + // Build image if not exists + try { + await execAsync(`docker image inspect ${imageName}:${version}`); + console.log(' ✅ Docker image exists'); + } catch { + console.log(' Building Docker image...'); + await execAsync(`docker build -t ${imageName}:${version} -t ${imageName}:latest .`); + } + + // Tag for registry + console.log(' Tagging image...'); + await execAsync(`docker tag ${imageName}:${version} ${fullImageName}:${version}`); + await execAsync(`docker tag ${imageName}:latest ${fullImageName}:latest`); + + // Push to registry + if (!options.dryRun) { + console.log(' Pushing to registry...'); + await execAsync(`docker push ${fullImageName}:${version}`); + await execAsync(`docker push ${fullImageName}:latest`); + console.log(' ✅ Pushed to Docker registry'); + console.log(` Pull with: docker pull ${fullImageName}:${version}`); + } else { + console.log(' 🧪 Dry run complete (not pushed)'); + } +} + +async function deployToClaude(pkg: any, version: string, options: any) { + console.log(`\n🤖 Registering with Claude Code...`); + + // Create Claude integration instructions + const instructions = ` +# Claude Code Integration + +## Installation + +### From npm +\`\`\`bash +claude mcp add ${pkg.name} -- npx ${pkg.name} +\`\`\` + +### From local build +\`\`\`bash +claude mcp add ${pkg.name} -- node ${path.resolve('dist/index.js')} +\`\`\` + +### With custom configuration +\`\`\`bash +claude mcp add ${pkg.name} \\ + --env LOG_LEVEL=info \\ + --env CUSTOM_CONFIG=/path/to/config \\ + -- npx ${pkg.name} +\`\`\` + +## Available Capabilities + +- Tools: ${await countTools()} +- Resources: ${await countResources()} +- Prompts: ${await countPrompts()} + +## Version: ${version} +`; + + // Save integration instructions + const instructionsPath = 'CLAUDE_INTEGRATION.md'; + await fs.writeFile(instructionsPath, instructions); + + console.log(' ✅ Integration instructions created'); + console.log(` View: ${instructionsPath}`); + + // Test local integration + if (!options.dryRun) { + console.log(' Testing local integration...'); + try { + const { stdout } = await execAsync('claude mcp list'); + if (stdout.includes(pkg.name)) { + console.log(' ✅ Already registered with Claude Code'); + } else { + console.log(' Register with:'); + console.log(` claude mcp add ${pkg.name} -- node ${path.resolve('dist/index.js')}`); + } + } catch { + console.log(' Claude Code CLI not available'); + } + } +} + +async function deployToGitHub(pkg: any, version: string, options: any) { + console.log(`\n💙 Creating GitHub release (v${version})...`); + + // Check gh CLI + try { + await execAsync('gh --version'); + } catch { + throw new Error('GitHub CLI not installed. Install from: https://cli.github.com'); + } + + // Check authentication + try { + await execAsync('gh auth status'); + console.log(' ✅ GitHub authenticated'); + } catch { + throw new Error('Not authenticated with GitHub. Run: gh auth login'); + } + + // Create release notes + const releaseNotes = await generateReleaseNotes(version); + const notesPath = `.release-notes-${version}.md`; + await fs.writeFile(notesPath, releaseNotes); + + // Create release + if (!options.dryRun) { + const releaseCmd = `gh release create v${version} \\ + --title "v${version}" \\ + --notes-file ${notesPath} \\ + --generate-notes`; + + try { + await execAsync(releaseCmd); + console.log(' ✅ GitHub release created'); + + // Get release URL + const { stdout } = await execAsync(`gh release view v${version} --json url`); + const { url } = JSON.parse(stdout); + console.log(` View at: ${url}`); + } catch (error) { + if (error.message.includes('already exists')) { + console.log(' ℹ️ Release already exists'); + } else { + throw error; + } + } + } else { + console.log(' 🧪 Dry run complete (release not created)'); + console.log(` Release notes saved to: ${notesPath}`); + } + + // Cleanup + await fs.unlink(notesPath).catch(() => {}); +} + +async function generateReleaseNotes(version: string): Promise { + const pkg = JSON.parse(await fs.readFile('package.json', 'utf-8')); + + return ` +# ${pkg.name} v${version} + +## 🎆 What's New + +- [Add your changes here] + +## 📦 Installation + +### npm +\`\`\`bash +npm install ${pkg.name}@${version} +\`\`\` + +### Claude Code +\`\`\`bash +claude mcp add ${pkg.name} -- npx ${pkg.name}@${version} +\`\`\` + +## 📄 Documentation + +See [README.md](README.md) for usage instructions. + +## 🔧 MCP Capabilities + +- Tools: ${await countTools()} +- Resources: ${await countResources()} +- Prompts: ${await countPrompts()} +`; +} + +async function countTools(): Promise { + try { + const files = await fs.readdir('src/tools').catch(() => []); + return files.filter(f => f.endsWith('.ts') && f !== 'index.ts').length; + } catch { + return 0; + } +} + +async function countResources(): Promise { + try { + const files = await fs.readdir('src/resources').catch(() => []); + return files.filter(f => f.endsWith('.ts') && f !== 'index.ts').length; + } catch { + return 0; + } +} + +async function countPrompts(): Promise { + try { + const files = await fs.readdir('src/prompts').catch(() => []); + return files.filter(f => f.endsWith('.ts') && f !== 'index.ts').length; + } catch { + return 0; + } +} +``` \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/commands/init.md b/mcp-servers/simple-mcp-server/.claude/commands/init.md new file mode 100644 index 0000000..885af94 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/commands/init.md @@ -0,0 +1,178 @@ +# Initialize MCP Server Project + +Sets up a new MCP server project with the specified configuration level. + +## Usage + +``` +/init [basic|standard|full] +``` + +## Options + +- `basic` - Minimal server with one example tool +- `standard` - Server with tools and resources (default) +- `full` - Complete server with all capabilities + +## Implementation + +```typescript +async function initializeProject(level: 'basic' | 'standard' | 'full' = 'standard') { + // Create project structure + const dirs = [ + 'src', + 'src/tools', + 'src/resources', + 'src/prompts', + 'src/utils', + 'src/types', + 'tests', + 'tests/unit', + 'tests/integration', + ]; + + for (const dir of dirs) { + await fs.mkdir(dir, { recursive: true }); + } + + // Create package.json + const packageJson = { + name: 'mcp-server', + version: '1.0.0', + type: 'module', + scripts: { + 'dev': 'tsx watch src/index.ts', + 'build': 'tsc', + 'start': 'node dist/index.js', + 'test': 'vitest', + 'lint': 'eslint src', + 'typecheck': 'tsc --noEmit', + }, + dependencies: { + '@modelcontextprotocol/sdk': '^1.0.0', + 'zod': '^3.22.0', + }, + devDependencies: { + '@types/node': '^20.0.0', + 'typescript': '^5.0.0', + 'tsx': '^4.0.0', + 'vitest': '^1.0.0', + 'eslint': '^8.0.0', + }, + }; + + await fs.writeFile('package.json', JSON.stringify(packageJson, null, 2)); + + // Create tsconfig.json + const tsConfig = { + compilerOptions: { + target: 'ES2022', + module: 'NodeNext', + moduleResolution: 'NodeNext', + outDir: './dist', + rootDir: './src', + strict: true, + esModuleInterop: true, + skipLibCheck: true, + forceConsistentCasingInFileNames: true, + }, + include: ['src/**/*'], + exclude: ['node_modules', 'dist'], + }; + + await fs.writeFile('tsconfig.json', JSON.stringify(tsConfig, null, 2)); + + // Create main server file + let serverContent = ''; + + if (level === 'basic') { + serverContent = generateBasicServer(); + } else if (level === 'standard') { + serverContent = generateStandardServer(); + } else { + serverContent = generateFullServer(); + } + + await fs.writeFile('src/index.ts', serverContent); + + // Install dependencies + console.log('Installing dependencies...'); + await exec('npm install'); + + console.log('✅ MCP server initialized successfully!'); + console.log('\nNext steps:'); + console.log('1. Run "npm run dev" to start development server'); + console.log('2. Use "/add-tool" to add custom tools'); + console.log('3. Test with MCP Inspector: npx @modelcontextprotocol/inspector'); +} + +function generateBasicServer(): string { + return ` +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { z } from 'zod'; + +const server = new Server({ + name: 'my-mcp-server', + version: '1.0.0', +}, { + capabilities: { + tools: {}, + }, +}); + +// List available tools +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: 'hello', + description: 'Say hello to someone', + inputSchema: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'Name to greet', + }, + }, + required: ['name'], + }, + }, + ], + }; +}); + +// Handle tool calls +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + if (name === 'hello') { + const validated = z.object({ + name: z.string(), + }).parse(args); + + return { + content: [ + { + type: 'text', + text: \`Hello, \${validated.name}!\`, + }, + ], + }; + } + + throw new Error(\`Unknown tool: \${name}\`); +}); + +// Start server +const transport = new StdioServerTransport(); +await server.connect(transport); +console.error('MCP server running on stdio'); +`; +} +``` \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/commands/test.md b/mcp-servers/simple-mcp-server/.claude/commands/test.md new file mode 100644 index 0000000..eff8fe9 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/commands/test.md @@ -0,0 +1,261 @@ +# Test MCP Server + +Runs comprehensive tests for your MCP server including unit tests, integration tests, and protocol compliance validation. + +## Usage + +``` +/test [type] [options] +``` + +## Options + +- `type` - Test type: `unit`, `integration`, `all` (default: `all`) +- `--coverage` - Generate coverage report +- `--watch` - Run tests in watch mode +- `--inspector` - Launch MCP Inspector for manual testing + +## Implementation + +```typescript +import { exec } from 'child_process'; +import { promisify } from 'util'; +import * as fs from 'fs/promises'; + +const execAsync = promisify(exec); + +async function runTests( + type: 'unit' | 'integration' | 'all' = 'all', + options: { + coverage?: boolean; + watch?: boolean; + inspector?: boolean; + } = {} +) { + console.log('🧪 Running MCP Server Tests...'); + + // Run linting first + console.log('\n🔍 Running linter...'); + try { + await execAsync('npm run lint'); + console.log('✅ Linting passed'); + } catch (error) { + console.error('❌ Linting failed:', error.message); + return; + } + + // Run type checking + console.log('\n📝 Type checking...'); + try { + await execAsync('npm run typecheck'); + console.log('✅ Type checking passed'); + } catch (error) { + console.error('❌ Type checking failed:', error.message); + return; + } + + // Run tests + console.log(`\n🧪 Running ${type} tests...`); + + let testCommand = 'npx vitest'; + + if (type === 'unit') { + testCommand += ' tests/unit'; + } else if (type === 'integration') { + testCommand += ' tests/integration'; + } + + if (options.coverage) { + testCommand += ' --coverage'; + } + + if (options.watch) { + testCommand += ' --watch'; + } else { + testCommand += ' --run'; + } + + try { + const { stdout } = await execAsync(testCommand); + console.log(stdout); + + // Run protocol compliance check + if (type === 'all' || type === 'integration') { + console.log('\n🔌 Checking MCP protocol compliance...'); + await checkProtocolCompliance(); + } + + // Generate test report + if (options.coverage) { + console.log('\n📊 Coverage report generated:'); + console.log(' - HTML: coverage/index.html'); + console.log(' - JSON: coverage/coverage-final.json'); + } + + console.log('\n✅ All tests passed!'); + + // Launch inspector if requested + if (options.inspector) { + console.log('\n🔍 Launching MCP Inspector...'); + await launchInspector(); + } + } catch (error) { + console.error('\n❌ Tests failed:', error.message); + process.exit(1); + } +} + +async function checkProtocolCompliance() { + const tests = [ + checkInitialization, + checkToolsCapability, + checkResourcesCapability, + checkPromptsCapability, + checkErrorHandling, + ]; + + for (const test of tests) { + try { + await test(); + console.log(` ✅ ${test.name.replace('check', '')} compliance`); + } catch (error) { + console.log(` ❌ ${test.name.replace('check', '')} compliance: ${error.message}`); + throw error; + } + } +} + +async function checkInitialization() { + // Test that server properly handles initialization + const { Server } = await import('@modelcontextprotocol/sdk/server/index.js'); + const { TestTransport } = await import('../tests/utils/test-transport.js'); + + const server = new Server({ + name: 'test-server', + version: '1.0.0', + }, { + capabilities: { + tools: {}, + resources: {}, + prompts: {}, + }, + }); + + const transport = new TestTransport(); + await server.connect(transport); + + const response = await transport.request({ + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { + name: 'test-client', + version: '1.0.0', + }, + }, + }); + + if (!response.protocolVersion) { + throw new Error('Server did not return protocol version'); + } + + await server.close(); +} + +async function checkToolsCapability() { + // Verify tools capability is properly implemented + const toolsExist = await fs.access('src/tools') + .then(() => true) + .catch(() => false); + + if (!toolsExist) { + console.log(' (No tools implemented yet)'); + return; + } + + // Check that tools are properly exported + const { tools } = await import('../src/tools/index.js'); + if (!Array.isArray(tools)) { + throw new Error('Tools must be exported as an array'); + } +} + +async function checkResourcesCapability() { + // Verify resources capability is properly implemented + const resourcesExist = await fs.access('src/resources') + .then(() => true) + .catch(() => false); + + if (!resourcesExist) { + console.log(' (No resources implemented yet)'); + return; + } + + // Check that resources are properly exported + const { resources } = await import('../src/resources/index.js'); + if (!Array.isArray(resources)) { + throw new Error('Resources must be exported as an array'); + } +} + +async function checkPromptsCapability() { + // Verify prompts capability is properly implemented + const promptsExist = await fs.access('src/prompts') + .then(() => true) + .catch(() => false); + + if (!promptsExist) { + console.log(' (No prompts implemented yet)'); + return; + } + + // Check that prompts are properly exported + const { prompts } = await import('../src/prompts/index.js'); + if (!Array.isArray(prompts)) { + throw new Error('Prompts must be exported as an array'); + } +} + +async function checkErrorHandling() { + // Test that server properly handles errors + const { handleError } = await import('../src/utils/error-handler.js'); + + // Test known error + const knownError = new Error('Test error'); + knownError.code = 'TEST_ERROR'; + const response1 = handleError(knownError); + if (!response1.error || response1.error.code !== 'TEST_ERROR') { + throw new Error('Error handler does not preserve error codes'); + } + + // Test unknown error + const unknownError = new Error('Unknown error'); + const response2 = handleError(unknownError); + if (!response2.error || response2.error.code !== 'INTERNAL_ERROR') { + throw new Error('Error handler does not handle unknown errors'); + } +} + +async function launchInspector() { + console.log('Starting MCP Inspector...'); + console.log('This will open an interactive testing UI in your browser.'); + console.log('Press Ctrl+C to stop the inspector.\n'); + + const inspector = exec('npx @modelcontextprotocol/inspector'); + + inspector.stdout.on('data', (data) => { + console.log(data.toString()); + }); + + inspector.stderr.on('data', (data) => { + console.error(data.toString()); + }); + + inspector.on('close', (code) => { + console.log(`Inspector exited with code ${code}`); + }); +} +``` \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/.claude/hooks/dev-watch.sh b/mcp-servers/simple-mcp-server/.claude/hooks/dev-watch.sh new file mode 100755 index 0000000..1d2b5fc --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/hooks/dev-watch.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# Development Watch Hook for Simple MCP Server +# Automatically triggered on TypeScript file changes + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}🔄 Development Watch Hook Triggered${NC}" + +# Get the modified file +MODIFIED_FILE="$1" + +# Skip if not a TypeScript file +if [[ ! "$MODIFIED_FILE" =~ \.ts$ ]]; then + exit 0 +fi + +# Skip node_modules and dist +if [[ "$MODIFIED_FILE" =~ node_modules|dist|coverage ]]; then + exit 0 +fi + +echo "📝 File changed: $MODIFIED_FILE" + +# Type checking +echo -e "${YELLOW}✅ Running type check...${NC}" +if npx tsc --noEmit 2>/dev/null; then + echo -e "${GREEN} ✓ Type checking passed${NC}" +else + echo -e "${RED} ✗ Type checking failed${NC}" + exit 1 +fi + +# Format with prettier +if command -v prettier &> /dev/null; then + echo -e "${YELLOW}🎨 Formatting with Prettier...${NC}" + npx prettier --write "$MODIFIED_FILE" 2>/dev/null || true + echo -e "${GREEN} ✓ Formatted${NC}" +fi + +# Lint with ESLint +if [ -f .eslintrc.json ] || [ -f .eslintrc.js ]; then + echo -e "${YELLOW}🔍 Linting with ESLint...${NC}" + if npx eslint "$MODIFIED_FILE" --fix 2>/dev/null; then + echo -e "${GREEN} ✓ Linting passed${NC}" + else + echo -e "${YELLOW} ⚠ Linting warnings${NC}" + fi +fi + +# Run tests if it's a test file or if the corresponding test exists +if [[ "$MODIFIED_FILE" =~ \.test\.ts$ ]] || [[ "$MODIFIED_FILE" =~ \.spec\.ts$ ]]; then + echo -e "${YELLOW}🧪 Running tests for $MODIFIED_FILE...${NC}" + if npx vitest run "$MODIFIED_FILE" 2>/dev/null; then + echo -e "${GREEN} ✓ Tests passed${NC}" + else + echo -e "${RED} ✗ Tests failed${NC}" + exit 1 + fi +else + # Check if corresponding test file exists + TEST_FILE="${MODIFIED_FILE%.ts}.test.ts" + TEST_FILE_SPEC="${MODIFIED_FILE%.ts}.spec.ts" + + if [ -f "$TEST_FILE" ]; then + echo -e "${YELLOW}🧪 Running related tests...${NC}" + npx vitest run "$TEST_FILE" 2>/dev/null || true + elif [ -f "$TEST_FILE_SPEC" ]; then + echo -e "${YELLOW}🧪 Running related tests...${NC}" + npx vitest run "$TEST_FILE_SPEC" 2>/dev/null || true + fi +fi + +# Update tool/resource counts if applicable +if [[ "$MODIFIED_FILE" =~ src/tools/ ]] || [[ "$MODIFIED_FILE" =~ src/resources/ ]] || [[ "$MODIFIED_FILE" =~ src/prompts/ ]]; then + echo -e "${YELLOW}📊 Updating capability counts...${NC}" + + TOOL_COUNT=$(find src/tools -name "*.ts" -not -name "index.ts" 2>/dev/null | wc -l || echo 0) + RESOURCE_COUNT=$(find src/resources -name "*.ts" -not -name "index.ts" 2>/dev/null | wc -l || echo 0) + PROMPT_COUNT=$(find src/prompts -name "*.ts" -not -name "index.ts" 2>/dev/null | wc -l || echo 0) + + echo " Tools: $TOOL_COUNT" + echo " Resources: $RESOURCE_COUNT" + echo " Prompts: $PROMPT_COUNT" +fi + +echo -e "${GREEN}✅ Development checks complete${NC}" diff --git a/mcp-servers/simple-mcp-server/.claude/hooks/pre-build.sh b/mcp-servers/simple-mcp-server/.claude/hooks/pre-build.sh new file mode 100755 index 0000000..a72ff45 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/hooks/pre-build.sh @@ -0,0 +1,144 @@ +#!/bin/bash + +# Pre-Build Hook for Simple MCP Server +# Runs before building for production + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}🔨 Pre-Build Hook${NC}" +echo "======================================" + +# Check for uncommitted changes +if git diff --quiet && git diff --staged --quiet; then + echo -e "${GREEN}✓ Working directory clean${NC}" +else + echo -e "${YELLOW}⚠ Warning: Uncommitted changes detected${NC}" + git status --short +fi + +# Lint check +echo -e "\n${YELLOW}🔍 Running lint check...${NC}" +if npx eslint src --ext .ts 2>/dev/null; then + echo -e "${GREEN} ✓ Linting passed${NC}" +else + echo -e "${RED} ✗ Linting failed${NC}" + echo " Run 'npm run lint:fix' to fix issues" + exit 1 +fi + +# Type validation +echo -e "\n${YELLOW}📝 Running type check...${NC}" +if npx tsc --noEmit; then + echo -e "${GREEN} ✓ Type checking passed${NC}" +else + echo -e "${RED} ✗ Type checking failed${NC}" + exit 1 +fi + +# Test suite +echo -e "\n${YELLOW}🧪 Running tests...${NC}" +if npm test 2>/dev/null; then + echo -e "${GREEN} ✓ All tests passed${NC}" +else + echo -e "${RED} ✗ Tests failed${NC}" + exit 1 +fi + +# Dependency audit +echo -e "\n${YELLOW}🔒 Checking dependencies...${NC}" +AUDIT_RESULT=$(npm audit --production 2>&1) +if echo "$AUDIT_RESULT" | grep -q "found 0 vulnerabilities"; then + echo -e "${GREEN} ✓ No vulnerabilities found${NC}" +else + echo -e "${YELLOW} ⚠ Security vulnerabilities detected${NC}" + echo " Run 'npm audit fix' to resolve" +fi + +# Version validation +echo -e "\n${YELLOW}🏷️ Checking version...${NC}" +PACKAGE_VERSION=$(node -p "require('./package.json').version") +echo " Current version: $PACKAGE_VERSION" + +# Check if version tag exists +if git rev-parse "v$PACKAGE_VERSION" >/dev/null 2>&1; then + echo -e "${GREEN} ✓ Version tag exists${NC}" +else + echo -e "${YELLOW} ⚠ Version tag v$PACKAGE_VERSION not found${NC}" + echo " Create with: git tag v$PACKAGE_VERSION" +fi + +# Check package.json required fields +echo -e "\n${YELLOW}📦 Validating package.json...${NC}" +NAME=$(node -p "require('./package.json').name" 2>/dev/null) +DESCRIPTION=$(node -p "require('./package.json').description" 2>/dev/null) +MAIN=$(node -p "require('./package.json').main" 2>/dev/null) + +if [ -z "$NAME" ]; then + echo -e "${RED} ✗ Missing 'name' field${NC}" + exit 1 +fi + +if [ -z "$DESCRIPTION" ]; then + echo -e "${YELLOW} ⚠ Missing 'description' field${NC}" +fi + +if [ -z "$MAIN" ]; then + echo -e "${YELLOW} ⚠ Missing 'main' field${NC}" +fi + +echo -e "${GREEN} ✓ Package metadata valid${NC}" + +# MCP specific checks +echo -e "\n${YELLOW}🔌 Checking MCP implementation...${NC}" + +# Check for required MCP files +if [ -f "src/index.ts" ]; then + echo -e "${GREEN} ✓ Entry point exists${NC}" +else + echo -e "${RED} ✗ Missing src/index.ts${NC}" + exit 1 +fi + +# Count capabilities +TOOL_COUNT=0 +RESOURCE_COUNT=0 +PROMPT_COUNT=0 + +if [ -d "src/tools" ]; then + TOOL_COUNT=$(find src/tools -name "*.ts" -not -name "index.ts" | wc -l) +fi + +if [ -d "src/resources" ]; then + RESOURCE_COUNT=$(find src/resources -name "*.ts" -not -name "index.ts" | wc -l) +fi + +if [ -d "src/prompts" ]; then + PROMPT_COUNT=$(find src/prompts -name "*.ts" -not -name "index.ts" | wc -l) +fi + +echo " Capabilities:" +echo " - Tools: $TOOL_COUNT" +echo " - Resources: $RESOURCE_COUNT" +echo " - Prompts: $PROMPT_COUNT" + +if [ $TOOL_COUNT -eq 0 ] && [ $RESOURCE_COUNT -eq 0 ] && [ $PROMPT_COUNT -eq 0 ]; then + echo -e "${YELLOW} ⚠ No MCP capabilities implemented${NC}" +fi + +# Final summary +echo "" +echo "======================================" +echo -e "${GREEN}✅ Pre-build checks complete${NC}" +echo "Ready to build for production!" +echo "" +echo "Next steps:" +echo " 1. npm run build" +echo " 2. npm test" +echo " 3. npm publish (if deploying to npm)" diff --git a/mcp-servers/simple-mcp-server/.claude/hooks/test-runner.sh b/mcp-servers/simple-mcp-server/.claude/hooks/test-runner.sh new file mode 100755 index 0000000..964f80c --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/hooks/test-runner.sh @@ -0,0 +1,198 @@ +#!/bin/bash + +# Test Runner Hook for Simple MCP Server +# Enhanced test execution with coverage and protocol validation + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +MAGENTA='\033[0;35m' +NC='\033[0m' # No Color + +echo -e "${BLUE}🧪 Test Runner Hook${NC}" +echo "======================================" + +# Parse test type from arguments +TEST_TYPE="${1:-all}" +WATCH_MODE="${2:-false}" + +# Function to run tests +run_tests() { + local type=$1 + local title=$2 + + echo -e "\n${YELLOW}Running $title...${NC}" + + if [ "$type" = "unit" ]; then + TEST_CMD="npx vitest run tests/unit" + elif [ "$type" = "integration" ]; then + TEST_CMD="npx vitest run tests/integration" + else + TEST_CMD="npx vitest run" + fi + + if [ "$WATCH_MODE" = "true" ]; then + TEST_CMD="${TEST_CMD/run/}" + fi + + if $TEST_CMD; then + echo -e "${GREEN} ✓ $title passed${NC}" + return 0 + else + echo -e "${RED} ✗ $title failed${NC}" + return 1 + fi +} + +# Function to check MCP protocol compliance +check_protocol_compliance() { + echo -e "\n${YELLOW}🔌 Checking MCP Protocol Compliance...${NC}" + + # Check server initialization + echo " Checking server initialization..." + if node -e "require('./dist/index.js')" 2>/dev/null; then + echo -e "${GREEN} ✓ Server module loads${NC}" + else + echo -e "${YELLOW} ⚠ Server not built (run 'npm run build')${NC}" + fi + + # Check for required handlers + echo " Checking protocol handlers..." + + # This would normally check the actual implementation + # For now, we'll check if the files exist + if [ -f "src/index.ts" ]; then + if grep -q "ListToolsRequestSchema\|ListResourcesRequestSchema\|ListPromptsRequestSchema" src/index.ts 2>/dev/null; then + echo -e "${GREEN} ✓ Protocol handlers found${NC}" + else + echo -e "${YELLOW} ⚠ Some protocol handlers may be missing${NC}" + fi + fi + + # Check capabilities + echo " Checking capabilities..." + local has_capability=false + + if [ -d "src/tools" ] && [ "$(ls -A src/tools 2>/dev/null)" ]; then + echo -e "${GREEN} ✓ Tools capability${NC}" + has_capability=true + fi + + if [ -d "src/resources" ] && [ "$(ls -A src/resources 2>/dev/null)" ]; then + echo -e "${GREEN} ✓ Resources capability${NC}" + has_capability=true + fi + + if [ -d "src/prompts" ] && [ "$(ls -A src/prompts 2>/dev/null)" ]; then + echo -e "${GREEN} ✓ Prompts capability${NC}" + has_capability=true + fi + + if [ "$has_capability" = false ]; then + echo -e "${YELLOW} ⚠ No capabilities implemented yet${NC}" + fi +} + +# Function to generate coverage report +generate_coverage() { + echo -e "\n${YELLOW}📊 Generating Coverage Report...${NC}" + + if npx vitest run --coverage 2>/dev/null; then + echo -e "${GREEN} ✓ Coverage report generated${NC}" + + # Parse coverage summary if available + if [ -f "coverage/coverage-summary.json" ]; then + echo " Coverage Summary:" + node -e " + const coverage = require('./coverage/coverage-summary.json'); + const total = coverage.total; + const metrics = ['statements', 'branches', 'functions', 'lines']; + metrics.forEach(metric => { + const pct = total[metric].pct; + const color = pct >= 80 ? '\\033[0;32m' : pct >= 60 ? '\\033[1;33m' : '\\033[0;31m'; + console.log(' ' + metric + ': ' + color + pct.toFixed(1) + '%\\033[0m'); + }); + " 2>/dev/null || echo " (Could not parse coverage summary)" + fi + + echo " View detailed report: coverage/index.html" + else + echo -e "${YELLOW} ⚠ Coverage generation failed${NC}" + fi +} + +# Main execution +echo "Test configuration:" +echo " Type: $TEST_TYPE" +echo " Watch: $WATCH_MODE" + +# Pre-test checks +echo -e "\n${YELLOW}📋 Pre-test checks...${NC}" + +# Check if test framework is installed +if ! command -v vitest &> /dev/null && ! npx vitest --version &> /dev/null; then + echo -e "${RED} ✗ Vitest not installed${NC}" + echo " Install with: npm install -D vitest" + exit 1 +fi + +# Check if test directory exists +if [ ! -d "tests" ] && [ ! -d "src/__tests__" ]; then + echo -e "${YELLOW} ⚠ No test directory found${NC}" + echo " Create tests in 'tests/' directory" +fi + +# Run appropriate tests +case $TEST_TYPE in + unit) + run_tests "unit" "Unit Tests" + ;; + integration) + run_tests "integration" "Integration Tests" + ;; + coverage) + generate_coverage + ;; + protocol) + check_protocol_compliance + ;; + all) + FAILED=false + + run_tests "unit" "Unit Tests" || FAILED=true + run_tests "integration" "Integration Tests" || FAILED=true + check_protocol_compliance + + if [ "$FAILED" = true ]; then + echo -e "\n${RED}❌ Some tests failed${NC}" + exit 1 + fi + ;; + *) + echo -e "${RED}Unknown test type: $TEST_TYPE${NC}" + echo "Valid options: unit, integration, coverage, protocol, all" + exit 1 + ;; +esac + +# Test summary +echo "" +echo "======================================" + +if [ "$WATCH_MODE" = "true" ]; then + echo -e "${BLUE}👁️ Watching for changes...${NC}" + echo "Press Ctrl+C to stop" +else + echo -e "${GREEN}✅ Test run complete${NC}" + + # Provide helpful next steps + echo "" + echo "Next steps:" + echo " • Fix any failing tests" + echo " • Run with coverage: npm test -- coverage" + echo " • Test with MCP Inspector: npx @modelcontextprotocol/inspector" +fi diff --git a/mcp-servers/simple-mcp-server/.claude/settings.json b/mcp-servers/simple-mcp-server/.claude/settings.json new file mode 100644 index 0000000..557b4f0 --- /dev/null +++ b/mcp-servers/simple-mcp-server/.claude/settings.json @@ -0,0 +1,63 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run dev:*)", + "Bash(npm run build:*)", + "Bash(npm run test:*)", + "Bash(npm run lint:*)", + "Bash(npm run typecheck:*)", + "Bash(npx vitest:*)", + "Bash(npx tsx:*)", + "Bash(npx tsc:*)", + "Bash(npx @modelcontextprotocol/inspector)", + "Write(src/**/*)", + "Write(tests/**/*)", + "Write(dist/**/*)", + "Read(package.json)", + "Read(tsconfig.json)", + "Edit(package.json)", + "Edit(tsconfig.json)" + ], + "deny": [ + "Read(.env.production)", + "Read(.env.local)", + "Write(.env)", + "Bash(rm -rf:*)", + "Bash(npm publish:*)", + "Read(node_modules/**)", + "Write(node_modules/**)" + ] + }, + "env": { + "NODE_ENV": "development", + "LOG_LEVEL": "info", + "MCP_SERVER_NAME": "simple-mcp-server", + "MCP_SERVER_VERSION": "1.0.0" + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [ + { + "type": "command", + "command": "npx prettier --write", + "timeout": 10 + } + ] + } + ] + }, + "statusLine": { + "type": "command", + "command": "echo '🚀 MCP Server | $(basename $(pwd))'" + }, + "_metadata": { + "name": "Simple MCP Server", + "version": "1.0.0", + "category": "mcp-server", + "generated": "2025-08-21T00:00:00.000Z", + "generator": "manual", + "note": "Generic MCP server configuration" + } +} \ No newline at end of file diff --git a/mcp-servers/simple-mcp-server/CLAUDE.md b/mcp-servers/simple-mcp-server/CLAUDE.md new file mode 100644 index 0000000..b58f174 --- /dev/null +++ b/mcp-servers/simple-mcp-server/CLAUDE.md @@ -0,0 +1,560 @@ +# Simple MCP Server Development Assistant + +You are an expert in building clean, well-structured MCP (Model Context Protocol) servers following best practices. You have deep expertise in the MCP SDK, TypeScript, and creating robust server implementations. + +## Memory Integration + +This CLAUDE.md follows official Claude Code patterns for MCP server development: + +- **MCP protocol compliance** - Follows @modelcontextprotocol/sdk standards +- **Project memory** - Instructions shared with development team +- **Tool integration** - Works with Claude Code's MCP commands +- **Automated discovery** - Available when MCP server is configured + +## MCP Configuration + +To use this server with Claude Code: + +```bash +# Add local MCP server +claude mcp add my-server -- node dist/index.js + +# Add with npm/npx +claude mcp add my-server -- npx my-mcp-server + +# Add with custom arguments +claude mcp add my-server -- node dist/index.js --port 3000 + +# Check server status +claude mcp list + +# Remove server +claude mcp remove my-server +``` + +## Available MCP Tools + +When connected, your server can provide these capabilities to Claude Code: + +- **Tools** - Custom functions that Claude can invoke +- **Resources** - Data sources Claude can read +- **Prompts** - Reusable prompt templates +- **Sampling** - Custom completion behavior + +## Project Context + +This is a Simple MCP Server project focused on: + +- **Clean architecture** with separation of concerns +- **Type safety** using TypeScript and Zod validation +- **Robust error handling** with proper error codes +- **Extensible design** for easy feature addition +- **MCP protocol compliance** using @modelcontextprotocol/sdk + +## Technology Stack + +### Core Technologies + +- **TypeScript** - Type-safe development +- **Node.js** - Runtime environment +- **@modelcontextprotocol/sdk** - Official MCP implementation +- **Zod** - Runtime type validation + +### Transport Options + +- **stdio** - Standard input/output (default) +- **HTTP + SSE** - Server-sent events for web clients +- **WebSocket** - Bidirectional communication + +## Architecture Patterns + +### Basic MCP Server Structure + +```typescript +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; +import { z } from 'zod'; + +// Create server instance +const server = new Server({ + name: 'my-mcp-server', + version: '1.0.0', +}, { + capabilities: { + tools: {}, + resources: {}, + prompts: {}, + }, +}); + +// Define tools +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: 'example_tool', + description: 'An example tool that processes input', + inputSchema: { + type: 'object', + properties: { + input: { type: 'string', description: 'Input to process' }, + }, + required: ['input'], + }, + }, + ], + }; +}); + +// Handle tool calls +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + switch (name) { + case 'example_tool': + const validated = z.object({ + input: z.string(), + }).parse(args); + + return { + content: [ + { + type: 'text', + text: `Processed: ${validated.input}`, + }, + ], + }; + + default: + throw new Error(`Unknown tool: ${name}`); + } +}); + +// Start server +const transport = new StdioServerTransport(); +await server.connect(transport); +``` + +### Resource Implementation + +```typescript +import { ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js'; + +// List available resources +server.setRequestHandler(ListResourcesRequestSchema, async () => { + return { + resources: [ + { + uri: 'config://settings', + name: 'Application Settings', + description: 'Current configuration values', + mimeType: 'application/json', + }, + ], + }; +}); + +// Read resource content +server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const { uri } = request.params; + + if (uri === 'config://settings') { + return { + contents: [ + { + uri: 'config://settings', + mimeType: 'application/json', + text: JSON.stringify(getSettings(), null, 2), + }, + ], + }; + } + + throw new Error(`Unknown resource: ${uri}`); +}); +``` + +### Prompt Templates + +```typescript +import { ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js'; + +// List available prompts +server.setRequestHandler(ListPromptsRequestSchema, async () => { + return { + prompts: [ + { + name: 'analyze_code', + description: 'Analyze code for improvements', + arguments: [ + { + name: 'language', + description: 'Programming language', + required: true, + }, + ], + }, + ], + }; +}); + +// Get prompt content +server.setRequestHandler(GetPromptRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + if (name === 'analyze_code') { + return { + messages: [ + { + role: 'user', + content: { + type: 'text', + text: `Analyze this ${args?.language || 'code'} for improvements...`, + }, + }, + ], + }; + } + + throw new Error(`Unknown prompt: ${name}`); +}); +``` + +## Critical Implementation Details + +### 1. Input Validation + +```typescript +// Always validate inputs with Zod +const InputSchema = z.object({ + query: z.string().min(1).max(1000), + options: z.object({ + format: z.enum(['json', 'text', 'markdown']).optional(), + limit: z.number().int().min(1).max(100).optional(), + }).optional(), +}); + +// Validate and handle errors +try { + const validated = InputSchema.parse(args); + return processQuery(validated); +} catch (error) { + if (error instanceof z.ZodError) { + return { + error: { + code: 'INVALID_PARAMS', + message: 'Invalid parameters', + data: error.errors, + }, + }; + } + throw error; +} +``` + +### 2. Error Handling + +```typescript +// Comprehensive error handling +class MCPError extends Error { + constructor( + public code: string, + message: string, + public data?: unknown + ) { + super(message); + } +} + +// Use specific error codes +try { + const result = await operation(); + return { content: [{ type: 'text', text: JSON.stringify(result) }] }; +} catch (error) { + if (error instanceof MCPError) { + return { + error: { + code: error.code, + message: error.message, + data: error.data, + }, + }; + } + + // Log unexpected errors + console.error('Unexpected error:', error); + return { + error: { + code: 'INTERNAL_ERROR', + message: 'An unexpected error occurred', + }, + }; +} +``` + +### 3. Logging and Debugging + +```typescript +// Structured logging +import pino from 'pino'; + +const logger = pino({ + level: process.env.LOG_LEVEL || 'info', + transport: { + target: 'pino-pretty', + options: { + colorize: true, + }, + }, +}); + +// Log server lifecycle +server.onerror = (error) => { + logger.error({ error }, 'Server error'); +}; + +// Log tool calls +logger.info({ tool: name, args }, 'Tool called'); +``` + +### 4. Progress Notifications + +```typescript +// Report progress for long-running operations +import { CreateMessageRequestSchema } from '@modelcontextprotocol/sdk/types.js'; + +async function longOperation(server: Server) { + // Send progress updates + await server.sendNotification({ + method: 'notifications/progress', + params: { + progress: 0.25, + message: 'Processing step 1 of 4...', + }, + }); + + // Continue operation... + + await server.sendNotification({ + method: 'notifications/progress', + params: { + progress: 1.0, + message: 'Operation complete!', + }, + }); +} +``` + +## Testing Strategy + +### Unit Tests + +```typescript +// Test tool handlers +describe('ToolHandlers', () => { + it('should validate input parameters', async () => { + const result = await handleTool('example_tool', { + input: 'test' + }); + expect(result.content[0].text).toContain('Processed'); + }); + + it('should reject invalid parameters', async () => { + const result = await handleTool('example_tool', {}); + expect(result.error?.code).toBe('INVALID_PARAMS'); + }); +}); +``` + +### Integration Tests + +```typescript +// Test MCP protocol compliance +describe('MCP Server', () => { + let server: Server; + let transport: TestTransport; + + beforeEach(() => { + transport = new TestTransport(); + server = createServer(); + server.connect(transport); + }); + + it('should handle initialize request', async () => { + const response = await transport.request({ + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { + name: 'test-client', + version: '1.0.0', + }, + }, + }); + + expect(response.protocolVersion).toBe('2024-11-05'); + expect(response.capabilities).toBeDefined(); + }); +}); +``` + +## Deployment Configuration + +### Package Scripts + +```json +{ + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "test": "vitest", + "lint": "eslint src", + "typecheck": "tsc --noEmit", + "format": "prettier --write src" + } +} +``` + +### Docker Setup + +```dockerfile +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci --production +COPY dist ./dist +CMD ["node", "dist/index.js"] +``` + +### Environment Variables + +```env +NODE_ENV=production +LOG_LEVEL=info +MCP_SERVER_NAME=my-mcp-server +MCP_SERVER_VERSION=1.0.0 +``` + +## Performance Optimization + +### Connection Management + +```typescript +// Reuse connections and handle cleanup +class ConnectionManager { + private connections = new Map(); + + async getConnection(id: string) { + if (!this.connections.has(id)) { + this.connections.set(id, await createConnection()); + } + return this.connections.get(id); + } + + async cleanup() { + for (const conn of this.connections.values()) { + await conn.close(); + } + this.connections.clear(); + } +} +``` + +### Response Caching + +```typescript +// Cache frequently requested data +const cache = new Map(); +const CACHE_TTL = 60000; // 1 minute + +function getCached(key: string) { + const cached = cache.get(key); + if (cached && Date.now() - cached.timestamp < CACHE_TTL) { + return cached.data; + } + return null; +} + +function setCached(key: string, data: unknown) { + cache.set(key, { + data, + timestamp: Date.now(), + }); +} +``` + +## Security Best Practices + +### Input Sanitization + +```typescript +// Sanitize user inputs +import { sanitize } from 'dompurify'; + +function sanitizeInput(input: string): string { + // Remove potential script tags and dangerous content + return sanitize(input, { + ALLOWED_TAGS: [], + ALLOWED_ATTR: [], + }); +} +``` + +### Rate Limiting + +```typescript +// Implement rate limiting +const rateLimiter = new Map(); +const MAX_REQUESTS = 100; +const TIME_WINDOW = 60000; // 1 minute + +function checkRateLimit(clientId: string): boolean { + const now = Date.now(); + const client = rateLimiter.get(clientId) || { count: 0, resetTime: now + TIME_WINDOW }; + + if (now > client.resetTime) { + client.count = 0; + client.resetTime = now + TIME_WINDOW; + } + + if (client.count >= MAX_REQUESTS) { + return false; + } + + client.count++; + rateLimiter.set(clientId, client); + return true; +} +``` + +## Common Commands + +```bash +# Development +npm run dev # Start development server with hot reload +npm run build # Build for production +npm run test # Run tests +npm run lint # Lint code +npm run typecheck # Type check without building + +# Testing MCP +npx @modelcontextprotocol/inspector # Interactive testing UI +npm run test:integration # Run integration tests + +# Production +npm start # Start production server +npm run docker:build # Build Docker image +npm run docker:run # Run in container +``` + +## Resources + +- [MCP Documentation](https://modelcontextprotocol.io) +- [MCP SDK Reference](https://github.com/modelcontextprotocol/typescript-sdk) +- [MCP Inspector](https://github.com/modelcontextprotocol/inspector) +- [MCP Examples](https://github.com/modelcontextprotocol/servers) + +Remember: **Simplicity, Reliability, and Standards Compliance** are key to building great MCP servers! diff --git a/mcp-servers/simple-mcp-server/README.md b/mcp-servers/simple-mcp-server/README.md new file mode 100644 index 0000000..ad65358 --- /dev/null +++ b/mcp-servers/simple-mcp-server/README.md @@ -0,0 +1,406 @@ +# Simple MCP Server Claude Code Configuration 🚀 + +A clean, focused Claude Code configuration for building standard MCP (Model Context Protocol) servers. Perfect for developers who want to create MCP servers without the complexity of specialized features like databases or authentication. + +## ✨ Features + +This configuration provides comprehensive support for: + +- **MCP Protocol Implementation** - Complete server setup with tools, resources, and prompts +- **Type-Safe Development** - TypeScript with Zod validation +- **Multiple Transport Options** - stdio, HTTP+SSE, WebSocket +- **Testing & Debugging** - Integration with MCP Inspector +- **Production Ready** - Docker support, logging, error handling + +## 📦 Installation + +1. Copy the configuration to your MCP server project: + +```bash +cp -r simple-mcp-server/.claude your-mcp-project/ +cp simple-mcp-server/CLAUDE.md your-mcp-project/ + +# Make hook scripts executable +chmod +x your-mcp-project/.claude/hooks/*.sh +``` + +2. The configuration will be automatically loaded when you start Claude Code. + +## 🤖 Specialized Agents (6 total) + +### Core MCP Development + +| Agent | Description | Use Cases | +|-------|-------------|-----------| +| `mcp-architect` | MCP server architecture expert | Server structure, capability design, protocol patterns | +| `tool-builder` | Tool implementation specialist | Creating tools, parameter schemas, response formats | +| `resource-manager` | Resource system expert | URI schemes, content types, dynamic resources | + +### Development & Operations + +| Agent | Description | Use Cases | +|-------|-------------|-----------| +| `error-handler` | Error handling and debugging | Error codes, validation, troubleshooting | +| `test-writer` | Testing strategy expert | Unit tests, integration tests, MCP Inspector | +| `deployment-expert` | Deployment and packaging | Docker, npm publishing, Claude integration | + +## 🛠️ Commands (7 total) + +### Setup & Initialization + +```bash +/init basic # Basic MCP server with one tool +/init standard # Standard server with tools and resources +/init full # Complete server with all capabilities +``` + +### Development Workflow + +```bash +/add-tool # Add a new tool to the server +/add-resource # Add a new resource endpoint +/add-prompt # Add a new prompt template +``` + +### Testing & Deployment + +```bash +/test # Run tests and validate protocol compliance +/debug # Debug server issues with detailed logging +/build # Build and prepare for deployment +/deploy # Deploy to npm or Docker registry +``` + +## 🪝 Automation Hooks + +### Development Hook (`dev-watch.sh`) + +Automatically triggered on TypeScript file changes: + +- ✅ Type checking with `tsc --noEmit` +- 🎨 Prettier formatting +- 🔍 ESLint linting +- 🧪 Test execution for changed files +- 📝 Update tool/resource counts + +### Build Hook (`pre-build.sh`) + +Runs before building: + +- 🔍 Lint check +- ✅ Type validation +- 🧪 Test suite execution +- 📦 Dependency audit +- 🏷️ Version validation + +### Test Hook (`test-runner.sh`) + +Enhances test execution: + +- 🧪 Run appropriate test suite +- 📊 Coverage reporting +- 🔍 MCP protocol validation +- 📝 Test results summary + +## ⚙️ Configuration Details + +### Security Permissions + +```json +{ + "permissions": { + "allow": [ + "Read", "Write", "Edit", "MultiEdit", + "Grep", "Glob", "LS", + "Bash(npm run:*)", + "Bash(npx @modelcontextprotocol/inspector)", + "Bash(node dist/*.js)", + "Bash(tsx src/*.ts)" + ], + "deny": [ + "Bash(rm -rf)", + "Bash(sudo:*)", + "Read(**/*secret*)", + "Write(**/*secret*)" + ] + } +} +``` + +### Environment Variables + +Pre-configured for MCP development: + +- `NODE_ENV` - Environment mode +- `LOG_LEVEL` - Logging verbosity +- `MCP_SERVER_NAME` - Server identifier +- `MCP_SERVER_VERSION` - Server version +- `DEBUG` - Debug mode flag + +## 🚀 Usage Examples + +### Creating a Basic MCP Server + +```bash +# 1. Initialize the project +> /init standard + +# 2. Add a custom tool +> /add-tool calculate "Performs calculations" + +# 3. Add a resource +> /add-resource config "Server configuration" + +# 4. Test the implementation +> /test + +# 5. Build for production +> /build +``` + +### Quick Server Setup + +```typescript +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; + +const server = new Server({ + name: 'my-server', + version: '1.0.0', +}, { + capabilities: { + tools: {}, + resources: {}, + }, +}); + +// Add your tools +server.addTool({ + name: 'hello', + description: 'Say hello', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + handler: async (args) => ({ + content: [{ + type: 'text', + text: `Hello, ${args.name}!`, + }], + }), +}); + +// Connect transport +const transport = new StdioServerTransport(); +await server.connect(transport); +``` + +### Testing with MCP Inspector + +```bash +# Launch the MCP Inspector +> npx @modelcontextprotocol/inspector + +# The inspector will: +# - Connect to your server +# - Display available tools/resources +# - Allow interactive testing +# - Show protocol messages +``` + +## 📊 Technology Stack + +Optimized for: + +- **TypeScript** - Type-safe development +- **Node.js** - JavaScript runtime +- **@modelcontextprotocol/sdk** - Official MCP SDK +- **Zod** - Runtime validation +- **Vitest** - Fast unit testing +- **Docker** - Containerization + +## 🎯 Key Features + +### MCP Protocol Support + +- Tools, Resources, and Prompts +- Multiple transport options +- Progress notifications +- Error handling with proper codes + +### Developer Experience + +- Type-safe with TypeScript +- Automatic validation with Zod +- Hot reload in development +- Comprehensive testing + +### Production Ready + +- Docker containerization +- Structured logging +- Error monitoring +- Rate limiting support + +## 🔧 Customization + +Edit `.claude/settings.json` to customize: + +- Tool and resource definitions +- Environment variables +- Hook configurations +- Permission settings + +## 📝 Best Practices + +This configuration enforces: + +1. **Protocol Compliance** - Strict MCP specification adherence +2. **Type Safety** - Full TypeScript with runtime validation +3. **Error Handling** - Proper error codes and messages +4. **Testing** - Comprehensive test coverage +5. **Documentation** - Clear code comments and API docs +6. **Security** - Input validation and sanitization + +## 🐛 Troubleshooting + +### Common Issues + +**Server not starting:** + +```bash +# Check TypeScript compilation +npm run typecheck + +# Check for missing dependencies +npm install + +# Verify Node.js version +node --version # Should be >= 18 +``` + +**Tools not appearing:** + +```bash +# Validate tool registration +/debug + +# Check MCP Inspector +npx @modelcontextprotocol/inspector +``` + +**Transport issues:** + +```bash +# Test with stdio transport first +node dist/index.js + +# For HTTP transport, check port +lsof -i :3000 +``` + +## 🌟 Example Projects + +### Weather Tool Server + +```typescript +server.addTool({ + name: 'get_weather', + description: 'Get weather for a location', + inputSchema: { + type: 'object', + properties: { + location: { type: 'string' }, + }, + required: ['location'], + }, + handler: async ({ location }) => { + const weather = await fetchWeather(location); + return { + content: [{ + type: 'text', + text: `Weather in ${location}: ${weather}`, + }], + }; + }, +}); +``` + +### File System Resource Server + +```typescript +server.addResource({ + uri: 'file:///{path}', + name: 'File System', + handler: async ({ path }) => { + const content = await fs.readFile(path, 'utf-8'); + return { + contents: [{ + uri: `file:///${path}`, + mimeType: 'text/plain', + text: content, + }], + }; + }, +}); +``` + +## 📚 Resources + +- [MCP Specification](https://spec.modelcontextprotocol.io) +- [MCP SDK Documentation](https://github.com/modelcontextprotocol/typescript-sdk) +- [MCP Inspector](https://github.com/modelcontextprotocol/inspector) +- [Example MCP Servers](https://github.com/modelcontextprotocol/servers) +- [Claude Code MCP Guide](https://docs.anthropic.com/claude-code/mcp) + +## 🎉 Quick Start + +```bash +# 1. Create a new MCP server project +mkdir my-mcp-server && cd my-mcp-server +npm init -y + +# 2. Install dependencies +npm install @modelcontextprotocol/sdk zod +npm install -D typescript tsx @types/node vitest + +# 3. Copy this configuration +cp -r path/to/simple-mcp-server/.claude . +cp path/to/simple-mcp-server/CLAUDE.md . + +# 4. Initialize TypeScript +npx tsc --init + +# 5. Create your server +touch src/index.ts + +# 6. Start development +npm run dev +``` + +## 🎯 What Makes This Configuration Special + +### Focused on Fundamentals + +- **No complexity** - Just pure MCP server development +- **No dependencies** - No databases, no authentication, no external services +- **Clean architecture** - Clear separation of concerns +- **Best practices** - Industry-standard patterns and conventions + +### Perfect For + +- Building your first MCP server +- Creating utility servers for Claude Code +- Learning MCP protocol implementation +- Prototyping new MCP capabilities +- Building production-ready MCP servers + +--- + +**Built for clean, simple MCP server development** 🚀 + +*Create robust MCP servers with best practices and minimal complexity.* + +**Configuration Version:** 1.0.0 | **Compatible with:** @modelcontextprotocol/sdk >=1.0.0 diff --git a/mcp-servers/simple-mcp-server/package.json b/mcp-servers/simple-mcp-server/package.json new file mode 100644 index 0000000..f582705 --- /dev/null +++ b/mcp-servers/simple-mcp-server/package.json @@ -0,0 +1,69 @@ +{ + "name": "simple-mcp-server-claude-config", + "version": "1.0.0", + "description": "Clean, focused Claude Code configuration for building standard MCP servers", + "keywords": [ + "mcp", + "mcp-server", + "claude-code", + "model-context-protocol", + "ai-tools", + "typescript", + "development" + ], + "author": "Matt Dionis ", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Matt-Dionis/claude-code-configs.git" + }, + "engines": { + "node": ">=18.0.0" + }, + "claude-config": { + "version": "1.0.0", + "compatible": { + "claude-code": ">=1.0.0", + "@modelcontextprotocol/sdk": ">=1.0.0", + "typescript": ">=5.0.0" + }, + "features": { + "agents": 6, + "commands": 8, + "hooks": 3, + "capabilities": [ + "tools", + "resources", + "prompts", + "error-handling", + "testing", + "deployment" + ] + } + }, + "scripts": { + "validate": "node -e \"console.log('✅ Configuration is valid')\"", + "info": "node -e \"console.log(JSON.stringify(require('./package.json')['claude-config'], null, 2))\"", + "check-hooks": "ls -la .claude/hooks/*.sh && echo '✅ All hooks are present'", + "check-agents": "ls .claude/agents/*.md | wc -l | xargs -I {} echo 'Agents: {}'", + "check-commands": "ls .claude/commands/*.md | wc -l | xargs -I {} echo 'Commands: {}'" + }, + "dependencies": {}, + "devDependencies": {}, + "peerDependencies": { + "@modelcontextprotocol/sdk": ">=1.0.0", + "typescript": ">=5.0.0", + "zod": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": false + }, + "typescript": { + "optional": false + }, + "zod": { + "optional": false + } + } +} \ No newline at end of file -- cgit v1.2.3