# MCP HTTP Webhook Server Library

![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)
![License](https://img.shields.io/badge/license-MIT-green.svg)

Production-ready TypeScript library for building Model Context Protocol (MCP) servers using the **official `@modelcontextprotocol/sdk`** with HTTP transport and webhook-based resource subscriptions.

## ✨ Key Highlights

- **🎯 Standard MCP Compliant**: Built on official `@modelcontextprotocol/sdk` - works with all MCP clients
- **🔌 Universal Client Support**: Compatible with MCP Inspector, Claude Desktop, VS Code, Cursor, and more
- **📡 Webhook Subscriptions**: Native support for third-party webhooks (GitHub, Google Drive, Slack, etc.)
- **🚀 Horizontally Scalable**: Stateless HTTP design for trivial multi-instance deployment
- **🔒 Production Ready**: Built-in retry logic, signature verification, and persistent storage
- **📘 Type Safe**: Full TypeScript support with comprehensive type definitions
- **💾 Flexible Storage**: Pluggable key-value store (Redis, in-memory, or custom)

## 🆕 v1.0 Update

This library now uses the **standard MCP SDK** (`@modelcontextprotocol/sdk`) while preserving all webhook subscription, authentication, and persistence features.

**[📖 Migration Guide](./MIGRATION_TO_SDK.md)** - Upgrading from custom protocol? Read this first!

## 📦 Installation

```bash
npm install mcp-http-webhook zod
# or
pnpm add mcp-http-webhook zod
# or
yarn add mcp-http-webhook zod
```

### Peer Dependencies

```bash
npm install @modelcontextprotocol/sdk express
```

### Optional Dependencies

```bash
npm install ioredis        # For Redis store
npm install prom-client    # For Prometheus metrics
```

## 🎯 Quick Start

```typescript
import { createMCPServer, InMemoryStore } from 'mcp-http-webhook';

const server = createMCPServer({
  name: 'my-mcp-server',
  version: '1.0.0',
  publicUrl: 'https://mcp.example.com',
  store: new InMemoryStore(),

  tools: [
    {
      name: 'echo',
      description: 'Echo back the input',
      inputSchema: {
        type: 'object',
        properties: {
          message: { type: 'string' }
        },
        required: ['message']
      },
      handler: async (input) => {
        return { echo: input.message };
      }
    }
  ],

  resources: [
    {
      uri: 'example://resource',
      name: 'Example Resource',
      description: 'An example resource',
      read: async (uri) => {
        return { contents: [{ uri, text: 'Hello, World!' }] };
      }
    }
  ]
});

await server.start();
console.log('Server running on http://localhost:3000');
```

## 🔌 Client Integration

### With MCP Inspector

```bash
npx @modelcontextprotocol/inspector http://localhost:3000/mcp
```

### With Claude Desktop / VS Code

Add to your MCP configuration:

```json
{
  "mcpServers": {
    "my-server": {
      "url": "http://localhost:3000/mcp"
    }
  }
}
```

## 📚 Documentation

### Configuration

```typescript
interface MCPServerConfig {
  // Server Identity
  name: string;
  version: string;

  // HTTP Configuration
  port?: number;              // Default: 3000
  host?: string;              // Default: '0.0.0.0'
  basePath?: string;          // Default: '/mcp'
  publicUrl: string;          // Required

  // Core Components
  tools: ToolDefinition[];
  resources: ResourceDefinition[];
  prompts?: PromptDefinition[];

  // Storage (Required)
  store: KeyValueStore;

  // Authentication
  authenticate?: (req: Request) => Promise<AuthContext>;

  // Webhook Configuration
  webhooks?: WebhookConfig;

  // Logging
  logger?: Logger;
  logLevel?: 'debug' | 'info' | 'warn' | 'error';
}
```

### Tools

Tools are functions that can be called by clients:

```typescript
{
  name: 'create_issue',
  description: 'Create a GitHub issue',
  inputSchema: {
    type: 'object',
    properties: {
      title: { type: 'string' },
      body: { type: 'string' }
    },
    required: ['title']
  },
  handler: async (input, context) => {
    // Your logic here
    return { issue_id: 123 };
  }
}
```

### Resources

Resources represent data that can be read and subscribed to:

```typescript
{
  uri: 'github://repo/{owner}/{repo}/issues',
  name: 'GitHub Issues',
  description: 'Repository issues',
  
  read: async (uri, context, options) => {
    // Access pagination options
    const page = options?.pagination?.page || 1;
    const limit = options?.pagination?.limit || 10;
    
    return { 
      contents: [...],
      pagination: {
        page,
        limit,
        total: 100,
        hasMore: true
      }
    };
  },
  
  list: async (context, options) => {
    // Pagination support in list
    const page = options?.pagination?.page || 1;
    const limit = options?.pagination?.limit || 10;
    
    return {
      resources: [
        { uri: '...', name: '...' }
      ],
      pagination: {
        page,
        limit,
        total: 50,
        hasMore: true
      }
    };
  },
  
  subscription: {
    onSubscribe: async (uri, subscriptionId, webhookUrl, context) => {
      // Register webhook with third-party service
      return { thirdPartyWebhookId: 'webhook_123' };
    },
    
    onUnsubscribe: async (uri, subscriptionId, storedData, context) => {
      // Remove webhook from third-party service
    },
    
    onWebhook: async (subscriptionId, payload, headers) => {
      // Process incoming webhook
      return {
        resourceUri: uri,
        changeType: 'created',
        data: { ... }
      };
    }
  }
}
```

### Storage

The library requires a key-value store for subscription persistence:

**In-Memory (Development Only):**
```typescript
import { InMemoryStore } from 'mcp-http-webhook/stores';
const store = new InMemoryStore();
```

**Redis:**
```typescript
import { createRedisStore } from 'mcp-http-webhook/stores';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);
const store = createRedisStore(redis);
```

**Custom Implementation:**
```typescript
import { KeyValueStore } from 'mcp-http-webhook';

class MyStore implements KeyValueStore {
  async get(key: string): Promise<string | null> { ... }
  async set(key: string, value: string, ttl?: number): Promise<void> { ... }
  async delete(key: string): Promise<void> { ... }
  async scan?(pattern: string): Promise<string[]> { ... }
}
```

### Pagination

Resources support pagination for both `list()` and `read()` operations:

**Client Request:**
```typescript
// Send pagination params in _meta
{
  "jsonrpc": "2.0",
  "method": "resources/list",
  "params": {},
  "_meta": {
    "page": 1,
    "limit": 10,
    "cursor": "optional-cursor"
  }
}
```

**Server Implementation:**
```typescript
{
  list: async (context, options) => {
    const page = options?.pagination?.page || 1;
    const limit = options?.pagination?.limit || 10;
    
    // Fetch paginated data
    const { items, total } = await fetchItems(page, limit);
    
    return {
      resources: items,
      pagination: {
        page,
        limit,
        total,
        hasMore: page * limit < total,
        nextCursor: 'next-page-token', // Optional
      }
    };
  },
  
  read: async (uri, context, options) => {
    // Pagination also works for large content in read()
    const cursor = options?.pagination?.cursor;
    const limit = options?.pagination?.limit || 50;
    
    return {
      contents: paginatedData,
      pagination: {
        page: 1,
        limit,
        hasMore: true,
        nextCursor: 'token-for-next-page',
      }
    };
  }
}
```

**Response Format:**
```json
{
  "resources": [...],
  "_meta": {
    "pagination": {
      "page": 1,
      "limit": 10,
      "total": 100,
      "hasMore": true,
      "nextCursor": "...",
      "prevCursor": "..."
    }
  }
}
```

See [examples/pagination-example.ts](./examples/pagination-example.ts) for complete examples including cursor-based pagination.

### Completions

Completions provide autocomplete suggestions for prompt arguments and resource URI parameters:

**Prompt with Completion:**

```typescript
{
  name: 'generate-code',
  arguments: [
    { name: 'language', required: true },
    { name: 'framework', required: false }
  ],
  handler: async (args, context) => { /* ... */ },
  
  // Completion handler for arguments
  completion: async (ref, argument, context) => {
    if (argument === 'language') {
      return [
        { value: 'python', label: 'Python' },
        { value: 'javascript', label: 'JavaScript' },
        { value: 'typescript', label: 'TypeScript' }
      ];
    }
    
    // Context-aware: suggest frameworks based on language
    if (argument === 'framework') {
      const lang = ref.arguments?.language;
      if (lang === 'python') {
        return [
          { value: 'django', label: 'Django' },
          { value: 'flask', label: 'Flask' }
        ];
      }
    }
    
    return [];
  }
}
```

**Resource with Completion:**

```typescript
{
  uri: 'github://repos/{owner}/{repo}',
  name: 'github-repos',
  /* ... */
  
  // Completion for URI parameters
  completion: async (ref, argument, context) => {
    if (argument === 'owner') {
      return [
        { value: 'facebook', label: 'Facebook' },
        { value: 'microsoft', label: 'Microsoft' }
      ];
    }
    
    if (argument === 'repo') {
      // Get owner from current URI
      const owner = ref.uri.match(/repos\/([^/]+)/)?.[1];
      // Return repos for that owner
      return fetchReposForOwner(owner);
    }
    
    return [];
  }
}
```

**Global Completion Handler:**

```typescript
createMCPServer({
  // ...
  completions: async (ref, argument, context) => {
    // Fallback for any prompt/resource without specific handler
    return [
      { value: 'default', label: 'Default Value' }
    ];
  }
});
```

See [examples/completion-example.ts](./examples/completion-example.ts) for comprehensive completion examples.

### Authentication

```typescript
authenticate: async (req) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  // Verify token
  const payload = await verifyJWT(token);
  
  return {
    userId: payload.sub,
    // Add any custom context
    permissions: payload.permissions
  };
}
```

### Webhooks

Configure webhook behavior:

```typescript
webhooks: {
  incomingPath: '/webhooks/incoming',
  incomingSecret: process.env.WEBHOOK_SECRET,
  
  verifyIncomingSignature: (payload, signature, secret) => {
    // Verify third-party webhook signature
    return verifyHmacSignature(payload, signature, secret);
  },
  
  outgoing: {
    timeout: 5000,
    retries: 3,
    retryDelay: 1000,
    
    signPayload: (payload, secret) => {
      // Sign outgoing notifications
      return createHmacSignature(payload, secret);
    }
  }
}
```

## 🔧 API Endpoints

The server automatically exposes these endpoints:

### Health Checks
- `GET /health` - Basic health check
- `GET /ready` - Readiness check (includes store connectivity)

### MCP Protocol
- `POST /mcp/tools/list` - List available tools
- `POST /mcp/tools/call` - Call a tool
- `POST /mcp/resources/list` - List available resources
- `POST /mcp/resources/read` - Read a resource
- `POST /mcp/resources/subscribe` - Subscribe to resource changes
- `POST /mcp/resources/unsubscribe` - Unsubscribe from resource
- `POST /mcp/prompts/list` - List available prompts
- `POST /mcp/prompts/get` - Get a prompt

### Webhooks
- `POST /webhooks/incoming/{subscriptionId}` - Receive third-party webhooks

## 📖 Examples

See the [`examples/`](./examples) directory for complete examples:

- **[basic-setup.ts](./examples/basic-setup.ts)** - Simple server with tools and resources
- **[github-server.ts](./examples/github-server.ts)** - GitHub integration with webhooks

## 🔒 Security

### HTTPS Only in Production

```typescript
// Automatically enforced in production
publicUrl: 'https://mcp.example.com'  // Must use HTTPS
```

### Signature Verification

Always verify webhook signatures:

```typescript
import crypto from 'crypto';

verifyIncomingSignature: (payload, signature, secret) => {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(JSON.stringify(payload));
  const expected = `sha256=${hmac.digest('hex')}`;
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
```

### Rate Limiting

Add rate limiting middleware:

```typescript
import rateLimit from 'express-rate-limit';

const server = createMCPServer({
  // ...
  middleware: [
    rateLimit({
      windowMs: 15 * 60 * 1000,
      max: 100
    })
  ]
});
```

## 🚀 Deployment

### Docker

```dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]
```

### Docker Compose

```yaml
version: '3.8'
services:
  mcp-server:
    build: .
    ports:
      - "3000:3000"
    environment:
      - PUBLIC_URL=https://mcp.example.com
      - REDIS_URL=redis://redis:6379
    depends_on:
      - redis
  
  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data

volumes:
  redis-data:
```

### Environment Variables

```bash
# Server
PORT=3000
PUBLIC_URL=https://mcp.example.com
NODE_ENV=production

# Storage
REDIS_URL=redis://localhost:6379

# Webhooks
WEBHOOK_SECRET=your-secret-here

# Auth
JWT_SECRET=your-jwt-secret
```

## 🧪 Testing

```bash
# Run tests
npm test

# Run with coverage
npm test -- --coverage

# Run specific test
npm test -- SubscriptionManager
```

## 📊 Monitoring

### Logging

```typescript
import winston from 'winston';

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.Console()
  ]
});

const server = createMCPServer({
  logger,
  logLevel: 'info'
});
```

### Metrics

Enable Prometheus metrics:

```typescript
import promClient from 'prom-client';

const register = new promClient.Registry();
promClient.collectDefaultMetrics({ register });

const server = createMCPServer({
  metrics: {
    enabled: true,
    registry: register
  }
});

// Metrics available at GET /metrics
```

## 🤝 Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for details.

## 📄 License

MIT License - see [LICENSE](./LICENSE) file for details.

## 🔗 Links

- [MCP Specification](https://spec.modelcontextprotocol.io/)
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
- [Documentation](https://docs.mcp-http-webhook.dev)
- [Examples](./examples)

## 💬 Support

- [GitHub Issues](https://github.com/your-org/mcp-http-webhook/issues)
- [Discord Community](https://discord.gg/mcp-webhook)
- [Stack Overflow](https://stackoverflow.com/questions/tagged/mcp-http-webhook)

---

Made with ❤️ by the MCP Community
