import { createMCPServer } from '../src'; import { InMemoryStore } from '../src/stores'; import crypto from 'crypto'; /** * GitHub MCP Server Example * Demonstrates webhook-based subscriptions */ const store = new InMemoryStore(); const server = createMCPServer({ name: 'github-mcp', version: '1.0.0', publicUrl: process.env.PUBLIC_URL || 'https://mcp.example.com', port: 3000, store, tools: [ { name: 'create_issue', description: 'Create a GitHub issue', inputSchema: { type: 'object', properties: { owner: { type: 'string', description: 'Repository owner' }, repo: { type: 'string', description: 'Repository name' }, title: { type: 'string', description: 'Issue title' }, body: { type: 'string', description: 'Issue body' }, }, required: ['owner', 'repo', 'title'], }, handler: async (input, context) => { // Simulate GitHub API call console.log('Creating issue:', input); return { issue: { number: 42, title: input.title, state: 'open', html_url: `https://github.com/${input.owner}/${input.repo}/issues/42`, }, }; }, }, ], resources: [ { uri: 'github://repo/{owner}/{repo}/issues', name: 'GitHub Repository Issues', description: 'All issues in a GitHub repository', mimeType: 'application/json', read: async (uri, context) => { const parts = uri.split('/'); const owner = parts[3]; const repo = parts[4]; console.log(`Reading issues for ${owner}/${repo}`); // Simulate GitHub API call return { contents: [ { number: 1, title: 'Example issue', state: 'open', }, ], }; }, list: async (context) => { return [ { uri: 'github://repo/octocat/hello-world/issues', name: 'octocat/hello-world Issues', description: 'Issues for hello-world repository', }, ]; }, subscription: { onSubscribe: async (uri, subscriptionId, thirdPartyWebhookUrl, context) => { console.log('Subscribing to:', uri); console.log('Third-party webhook URL:', thirdPartyWebhookUrl); // In production, this would: // 1. Call GitHub API to create webhook // 2. Store webhook ID // Example: // const webhook = await octokit.repos.createWebhook({ // owner, // repo, // config: { // url: thirdPartyWebhookUrl, // content_type: 'json', // secret: process.env.GITHUB_WEBHOOK_SECRET // }, // events: ['issues'] // }); return { thirdPartyWebhookId: 'webhook_123', metadata: { owner: 'octocat', repo: 'hello-world', }, }; }, onUnsubscribe: async (uri, subscriptionId, storedData, context) => { console.log('Unsubscribing from:', uri); console.log('Removing webhook:', storedData.thirdPartyWebhookId); // In production, this would: // await octokit.repos.deleteWebhook({ // owner, // repo, // hook_id: storedData.thirdPartyWebhookId // }); }, onWebhook: async (subscriptionId, payload, headers) => { console.log('Received GitHub webhook:', subscriptionId); // Verify signature const signature = headers['x-hub-signature-256']; if (signature) { // Verification logic here } const event = headers['x-github-event']; if (event === 'issues') { const { action, issue, repository } = payload; if (['opened', 'edited', 'closed'].includes(action)) { return { resourceUri: `github://repo/${repository.owner.login}/${repository.name}/issues`, changeType: action === 'opened' ? 'created' : action === 'closed' ? 'deleted' : 'updated', data: { issueNumber: issue.number, title: issue.title, state: issue.state, action, }, }; } } return null; }, }, }, ], webhooks: { incomingPath: '/webhooks/incoming', incomingSecret: process.env.GITHUB_WEBHOOK_SECRET, 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)); }, outgoing: { timeout: 5000, retries: 3, retryDelay: 1000, signPayload: (payload, secret) => { const hmac = crypto.createHmac('sha256', secret); hmac.update(JSON.stringify(payload)); return `sha256=${hmac.digest('hex')}`; }, }, }, authenticate: async (req) => { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { throw new Error('Missing authorization token'); } // In production, verify JWT token return { userId: 'user-123', githubToken: token, }; }, logLevel: 'debug', }); server.start().then(() => { console.log('GitHub MCP Server running!'); console.log(''); console.log('Subscribe to issues:'); console.log('POST http://localhost:3000/mcp/resources/subscribe'); console.log(' Body:'); console.log(' {'); console.log(' "method": "resources/subscribe",'); console.log(' "params": {'); console.log(' "uri": "github://repo/octocat/hello-world/issues",'); console.log(' "callbackUrl": "https://client.example.com/webhook",'); console.log(' "callbackSecret": "client-secret"'); console.log(' }'); console.log(' }'); console.log(''); console.log('Webhook endpoint:'); console.log(`${process.env.PUBLIC_URL}/webhooks/incoming/{subscriptionId}`); }); process.on('SIGTERM', async () => { await server.stop(); store.destroy(); process.exit(0); });