mcp-http-webhook 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.eslintrc.json +16 -0
  2. package/.prettierrc.json +8 -0
  3. package/ARCHITECTURE.md +269 -0
  4. package/CONTRIBUTING.md +136 -0
  5. package/GETTING_STARTED.md +310 -0
  6. package/IMPLEMENTATION.md +294 -0
  7. package/LICENSE +21 -0
  8. package/MIGRATION_TO_SDK.md +263 -0
  9. package/README.md +496 -0
  10. package/SDK_INTEGRATION_COMPLETE.md +300 -0
  11. package/STANDARD_SUBSCRIPTIONS.md +268 -0
  12. package/STANDARD_SUBSCRIPTIONS_COMPLETE.md +309 -0
  13. package/SUMMARY.md +272 -0
  14. package/Spec.md +2778 -0
  15. package/dist/errors/index.d.ts +52 -0
  16. package/dist/errors/index.d.ts.map +1 -0
  17. package/dist/errors/index.js +81 -0
  18. package/dist/errors/index.js.map +1 -0
  19. package/dist/index.d.ts +9 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +37 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/protocol/ProtocolHandler.d.ts +37 -0
  24. package/dist/protocol/ProtocolHandler.d.ts.map +1 -0
  25. package/dist/protocol/ProtocolHandler.js +172 -0
  26. package/dist/protocol/ProtocolHandler.js.map +1 -0
  27. package/dist/server.d.ts +6 -0
  28. package/dist/server.d.ts.map +1 -0
  29. package/dist/server.js +502 -0
  30. package/dist/server.js.map +1 -0
  31. package/dist/stores/InMemoryStore.d.ts +27 -0
  32. package/dist/stores/InMemoryStore.d.ts.map +1 -0
  33. package/dist/stores/InMemoryStore.js +73 -0
  34. package/dist/stores/InMemoryStore.js.map +1 -0
  35. package/dist/stores/RedisStore.d.ts +18 -0
  36. package/dist/stores/RedisStore.d.ts.map +1 -0
  37. package/dist/stores/RedisStore.js +45 -0
  38. package/dist/stores/RedisStore.js.map +1 -0
  39. package/dist/stores/index.d.ts +3 -0
  40. package/dist/stores/index.d.ts.map +1 -0
  41. package/dist/stores/index.js +9 -0
  42. package/dist/stores/index.js.map +1 -0
  43. package/dist/subscriptions/SubscriptionManager.d.ts +49 -0
  44. package/dist/subscriptions/SubscriptionManager.d.ts.map +1 -0
  45. package/dist/subscriptions/SubscriptionManager.js +181 -0
  46. package/dist/subscriptions/SubscriptionManager.js.map +1 -0
  47. package/dist/types/index.d.ts +271 -0
  48. package/dist/types/index.d.ts.map +1 -0
  49. package/dist/types/index.js +16 -0
  50. package/dist/types/index.js.map +1 -0
  51. package/dist/utils/index.d.ts +51 -0
  52. package/dist/utils/index.d.ts.map +1 -0
  53. package/dist/utils/index.js +154 -0
  54. package/dist/utils/index.js.map +1 -0
  55. package/dist/webhooks/WebhookManager.d.ts +27 -0
  56. package/dist/webhooks/WebhookManager.d.ts.map +1 -0
  57. package/dist/webhooks/WebhookManager.js +174 -0
  58. package/dist/webhooks/WebhookManager.js.map +1 -0
  59. package/examples/GITHUB_LIVE_EXAMPLE.md +308 -0
  60. package/examples/GITHUB_LIVE_SETUP.md +253 -0
  61. package/examples/QUICKSTART.md +130 -0
  62. package/examples/basic-setup.ts +142 -0
  63. package/examples/github-server-live.ts +690 -0
  64. package/examples/github-server.ts +223 -0
  65. package/examples/google-drive-server-live.ts +773 -0
  66. package/examples/start-github-live.sh +53 -0
  67. package/jest.config.js +20 -0
  68. package/package.json +58 -0
  69. package/src/errors/index.ts +81 -0
  70. package/src/index.ts +19 -0
  71. package/src/server.ts +595 -0
  72. package/src/stores/InMemoryStore.ts +87 -0
  73. package/src/stores/RedisStore.ts +51 -0
  74. package/src/stores/index.ts +2 -0
  75. package/src/subscriptions/SubscriptionManager.ts +240 -0
  76. package/src/types/index.ts +341 -0
  77. package/src/utils/index.ts +156 -0
  78. package/src/webhooks/WebhookManager.ts +230 -0
  79. package/test-sdk-integration.sh +157 -0
  80. package/tsconfig.json +21 -0
package/Spec.md ADDED
@@ -0,0 +1,2778 @@
1
+ # MCP HTTP Webhook Server Library - Technical Specification
2
+
3
+ **Version:** 1.0.0
4
+ **Target SDK:** Model Context Protocol TypeScript SDK
5
+ **Transport Method:** HTTP + Webhooks (No SSE)
6
+ **Reference:** [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
7
+
8
+ ---
9
+
10
+ ## 1. Executive Summary
11
+
12
+ This library provides an opinionated, production-ready framework for building MCP servers that operate entirely over HTTP with webhook-based resource subscriptions. Unlike the standard MCP SSE transport, this library eliminates persistent connections in favor of stateless HTTP requests and webhook callbacks.
13
+
14
+ **Key Value Propositions:**
15
+ - **Horizontally Scalable:** No connection state = trivial multi-instance deployment
16
+ - **Third-Party Integration:** Native webhook support for GitHub, Google Drive, Slack, PostgreSQL, etc.
17
+ - **Client Flexibility:** Clients provide their own webhook endpoints
18
+ - **Production Ready:** Built-in retry logic, signature verification, and persistent storage
19
+
20
+ ---
21
+
22
+ ## 2. Architecture Overview
23
+
24
+ ### 2.1 Core Components
25
+
26
+ ```
27
+ ┌─────────────────────────────────────────────────────────────┐
28
+ │ MCP HTTP Server │
29
+ ├─────────────────────────────────────────────────────────────┤
30
+ │ │
31
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
32
+ │ │ Protocol │ │ Webhook │ │ Subscription │ │
33
+ │ │ Handler │ │ Manager │ │ Manager │ │
34
+ │ └──────────────┘ └──────────────┘ └──────────────┘ │
35
+ │ │
36
+ │ ┌──────────────────────────────────────────────────────┐ │
37
+ │ │ Key-Value Store Interface │ │
38
+ │ │ (Redis / DynamoDB / PostgreSQL) │ │
39
+ │ └──────────────────────────────────────────────────────┘ │
40
+ │ │
41
+ └─────────────────────────────────────────────────────────────┘
42
+ ▲ ▲ ▲
43
+ │ │ │
44
+ HTTP Requests Third-Party Client
45
+ (Tools/Resources) Webhooks Webhooks
46
+ ```
47
+
48
+ ### 2.2 Data Flow
49
+
50
+ **Standard Tool/Resource Access:**
51
+ ```
52
+ Client → HTTP POST → MCP Server → Tool/Resource Handler → Response
53
+ ```
54
+
55
+ **Subscription Flow:**
56
+ ```
57
+ 1. Client → POST /resources/subscribe (with callbackUrl)
58
+ 2. MCP Server → Generates subscriptionId
59
+ 3. MCP Server → Creates webhook URL for third-party
60
+ 4. MCP Server → Calls onSubscribe handler
61
+ 5. Handler → Registers webhook with third-party service
62
+ 6. MCP Server → Stores subscription in KV store
63
+ 7. MCP Server → Returns subscriptionId to client
64
+ ```
65
+
66
+ **Notification Flow:**
67
+ ```
68
+ 1. Third-Party → POST /webhooks/incoming/{subscriptionId}
69
+ 2. MCP Server → Loads subscription from KV store
70
+ 3. MCP Server → Calls onWebhook handler
71
+ 4. Handler → Parses payload, returns change info
72
+ 5. MCP Server → HTTP POST to client callbackUrl
73
+ 6. Client → Receives notification with subscriptionId
74
+ ```
75
+
76
+ ---
77
+
78
+ ## 3. Installation & Setup
79
+
80
+ ### 3.1 Installation
81
+
82
+ ```bash
83
+ npm install mcp-http-webhook
84
+ # or
85
+ pnpm add mcp-http-webhook
86
+ # or
87
+ yarn add mcp-http-webhook
88
+ ```
89
+
90
+ ### 3.2 Peer Dependencies
91
+
92
+ ```json
93
+ {
94
+ "@modelcontextprotocol/sdk": "^1.0.0",
95
+ "express": "^4.18.0",
96
+ "ioredis": "^5.3.0"
97
+ }
98
+ ```
99
+
100
+ ---
101
+
102
+ ## 4. Core API Reference
103
+
104
+ ### 4.1 Server Creation
105
+
106
+ **Function:** `createMCPServer(config: MCPServerConfig): MCPServer`
107
+
108
+ **Configuration Interface:**
109
+
110
+ ```typescript
111
+ interface MCPServerConfig {
112
+ // Server Identity (MCP Protocol)
113
+ name: string;
114
+ version: string;
115
+
116
+ // HTTP Server Configuration
117
+ port?: number; // Default: 3000
118
+ host?: string; // Default: '0.0.0.0'
119
+ basePath?: string; // Default: '/mcp'
120
+ publicUrl: string; // Required: e.g., 'https://mcp.example.com'
121
+
122
+ // Authentication
123
+ authenticate?: (req: Request) => Promise<AuthContext>;
124
+
125
+ // Core MCP Components
126
+ tools: ToolDefinition[];
127
+ resources: ResourceDefinition[];
128
+ prompts?: PromptDefinition[];
129
+
130
+ // Storage (Required for subscriptions)
131
+ store: KeyValueStore;
132
+
133
+ // Webhook Configuration
134
+ webhooks?: WebhookConfig;
135
+
136
+ // Logging
137
+ logger?: Logger;
138
+ logLevel?: 'debug' | 'info' | 'warn' | 'error';
139
+ }
140
+ ```
141
+
142
+ **Reference Implementation:**
143
+ See [examples/basic-setup.ts](#)
144
+
145
+ ---
146
+
147
+ ## 5. Tool Definition
148
+
149
+ Tools follow the standard MCP tool specification with HTTP transport.
150
+
151
+ **Interface:**
152
+
153
+ ```typescript
154
+ interface ToolDefinition<TInput = any, TOutput = any> {
155
+ name: string;
156
+ description: string;
157
+ inputSchema: JSONSchema;
158
+ handler: (input: TInput, context: AuthContext) => Promise<TOutput>;
159
+ }
160
+ ```
161
+
162
+ **Example:**
163
+
164
+ ```typescript
165
+ {
166
+ name: 'create_github_issue',
167
+ description: 'Creates a new issue in a GitHub repository',
168
+ inputSchema: {
169
+ type: 'object',
170
+ properties: {
171
+ owner: { type: 'string', description: 'Repository owner' },
172
+ repo: { type: 'string', description: 'Repository name' },
173
+ title: { type: 'string', description: 'Issue title' },
174
+ body: { type: 'string', description: 'Issue body' }
175
+ },
176
+ required: ['owner', 'repo', 'title']
177
+ },
178
+ handler: async (input, context) => {
179
+ const octokit = new Octokit({ auth: context.githubToken });
180
+ const { data } = await octokit.issues.create(input);
181
+ return { issue: data };
182
+ }
183
+ }
184
+ ```
185
+
186
+ **HTTP Endpoint:** `POST /mcp/tools/call`
187
+
188
+ **Request Format:**
189
+ ```json
190
+ {
191
+ "method": "tools/call",
192
+ "params": {
193
+ "name": "create_github_issue",
194
+ "arguments": {
195
+ "owner": "octocat",
196
+ "repo": "hello-world",
197
+ "title": "Bug found"
198
+ }
199
+ }
200
+ }
201
+ ```
202
+
203
+ **Reference:**
204
+ - [MCP Tool Specification](https://spec.modelcontextprotocol.io/specification/server/tools/)
205
+ - [Tool Examples](#)
206
+
207
+ ---
208
+
209
+ ## 6. Resource Definition
210
+
211
+ Resources represent data that can be read and optionally subscribed to.
212
+
213
+ ### 6.1 Basic Resource
214
+
215
+ **Interface:**
216
+
217
+ ```typescript
218
+ interface ResourceDefinition<TData = any> {
219
+ uri: string; // URI template, e.g., "github://repo/{owner}/{repo}/issues"
220
+ name: string;
221
+ description: string;
222
+ mimeType?: string;
223
+
224
+ read: (uri: string, context: AuthContext) => Promise<{
225
+ contents: TData;
226
+ metadata?: Record<string, any>;
227
+ }>;
228
+
229
+ list?: (context: AuthContext) => Promise<ResourceListItem[]>;
230
+
231
+ subscription?: ResourceSubscription;
232
+ }
233
+ ```
234
+
235
+ **Example:**
236
+
237
+ ```typescript
238
+ {
239
+ uri: 'github://repo/{owner}/{repo}/issues',
240
+ name: 'GitHub Repository Issues',
241
+ description: 'List of all issues in a repository',
242
+ mimeType: 'application/json',
243
+
244
+ read: async (uri, context) => {
245
+ const { owner, repo } = parseUriTemplate(uri);
246
+ const octokit = new Octokit({ auth: context.githubToken });
247
+ const { data } = await octokit.issues.listForRepo({ owner, repo });
248
+ return { contents: data };
249
+ },
250
+
251
+ list: async (context) => {
252
+ const octokit = new Octokit({ auth: context.githubToken });
253
+ const { data } = await octokit.repos.listForAuthenticatedUser();
254
+ return data.map(repo => ({
255
+ uri: `github://repo/${repo.owner.login}/${repo.name}/issues`,
256
+ name: `${repo.full_name} Issues`
257
+ }));
258
+ }
259
+ }
260
+ ```
261
+
262
+ **HTTP Endpoints:**
263
+ - `POST /mcp/resources/list`
264
+ - `POST /mcp/resources/read`
265
+
266
+ **Reference:**
267
+ - [MCP Resource Specification](https://spec.modelcontextprotocol.io/specification/server/resources/)
268
+ - [Resource Examples](#)
269
+
270
+ ---
271
+
272
+ ## 7. Webhook-Based Subscriptions
273
+
274
+ This is the core differentiator of this library.
275
+
276
+ ### 7.1 Subscription Interface
277
+
278
+ ```typescript
279
+ interface ResourceSubscription {
280
+ /**
281
+ * Called when a client subscribes to a resource
282
+ *
283
+ * @param uri - Resource URI being subscribed to
284
+ * @param subscriptionId - Unique ID (generated by library)
285
+ * @param thirdPartyWebhookUrl - URL to give to third-party service
286
+ * @param context - Authentication context
287
+ * @returns Metadata to persist (webhook IDs, etc.)
288
+ */
289
+ onSubscribe: (
290
+ uri: string,
291
+ subscriptionId: string,
292
+ thirdPartyWebhookUrl: string,
293
+ context: AuthContext
294
+ ) => Promise<SubscriptionMetadata>;
295
+
296
+ /**
297
+ * Called when a client unsubscribes
298
+ *
299
+ * @param uri - Resource URI
300
+ * @param subscriptionId - Subscription ID
301
+ * @param storedData - Data from onSubscribe
302
+ * @param context - Authentication context
303
+ */
304
+ onUnsubscribe: (
305
+ uri: string,
306
+ subscriptionId: string,
307
+ storedData: SubscriptionMetadata,
308
+ context: AuthContext
309
+ ) => Promise<void>;
310
+
311
+ /**
312
+ * Called when third-party webhook is received
313
+ *
314
+ * @param subscriptionId - From webhook URL path
315
+ * @param payload - Webhook payload
316
+ * @param headers - HTTP headers (for signature verification)
317
+ * @returns Change information or null
318
+ */
319
+ onWebhook: (
320
+ subscriptionId: string,
321
+ payload: any,
322
+ headers: Record<string, string>
323
+ ) => Promise<WebhookChangeInfo | null>;
324
+ }
325
+
326
+ interface SubscriptionMetadata {
327
+ thirdPartyWebhookId: string;
328
+ metadata?: Record<string, any>;
329
+ }
330
+
331
+ interface WebhookChangeInfo {
332
+ resourceUri: string;
333
+ changeType: 'created' | 'updated' | 'deleted';
334
+ data?: any;
335
+ }
336
+ ```
337
+
338
+ ### 7.2 Subscription Lifecycle
339
+
340
+ **Step 1: Client Subscribes**
341
+
342
+ ```
343
+ POST /mcp/resources/subscribe
344
+ Authorization: Bearer <token>
345
+ Content-Type: application/json
346
+
347
+ {
348
+ "method": "resources/subscribe",
349
+ "params": {
350
+ "uri": "github://repo/octocat/hello-world/issues",
351
+ "callbackUrl": "https://client.example.com/webhooks/mcp",
352
+ "callbackSecret": "client-webhook-secret"
353
+ }
354
+ }
355
+
356
+ Response:
357
+ {
358
+ "subscriptionId": "sub_xyz789",
359
+ "status": "active"
360
+ }
361
+ ```
362
+
363
+ **Internal Flow:**
364
+ 1. Library generates `subscriptionId`
365
+ 2. Library creates `thirdPartyWebhookUrl = {publicUrl}/webhooks/incoming/{subscriptionId}`
366
+ 3. Library calls `onSubscribe(uri, subscriptionId, thirdPartyWebhookUrl, context)`
367
+ 4. Handler registers webhook with third-party (GitHub, etc.)
368
+ 5. Library stores subscription data in KV store
369
+ 6. Library returns `subscriptionId` to client
370
+
371
+ **Step 2: Third-Party Notifies MCP Server**
372
+
373
+ ```
374
+ POST /webhooks/incoming/sub_xyz789
375
+ X-GitHub-Event: issues
376
+ X-Hub-Signature-256: sha256=...
377
+ Content-Type: application/json
378
+
379
+ {
380
+ "action": "opened",
381
+ "issue": { ... },
382
+ "repository": { ... }
383
+ }
384
+ ```
385
+
386
+ **Internal Flow:**
387
+ 1. Library extracts `subscriptionId` from URL path
388
+ 2. Library loads subscription from KV store
389
+ 3. Library calls `onWebhook(subscriptionId, payload, headers)`
390
+ 4. Handler parses payload, returns `WebhookChangeInfo`
391
+ 5. Library calls client webhook with notification
392
+
393
+ **Step 3: MCP Server Notifies Client**
394
+
395
+ ```
396
+ POST https://client.example.com/webhooks/mcp
397
+ X-MCP-Signature: sha256=...
398
+ Content-Type: application/json
399
+
400
+ {
401
+ "subscriptionId": "sub_xyz789",
402
+ "resourceUri": "github://repo/octocat/hello-world/issues/42",
403
+ "changeType": "created",
404
+ "data": {
405
+ "number": 42,
406
+ "title": "New issue",
407
+ "state": "open"
408
+ },
409
+ "timestamp": 1698765432000
410
+ }
411
+ ```
412
+
413
+ **Step 4: Client Unsubscribes**
414
+
415
+ ```
416
+ POST /mcp/resources/unsubscribe
417
+ Authorization: Bearer <token>
418
+
419
+ {
420
+ "method": "resources/unsubscribe",
421
+ "params": {
422
+ "subscriptionId": "sub_xyz789"
423
+ }
424
+ }
425
+ ```
426
+
427
+ **Internal Flow:**
428
+ 1. Library loads subscription from KV store
429
+ 2. Library calls `onUnsubscribe(uri, subscriptionId, storedData, context)`
430
+ 3. Handler removes webhook from third-party service
431
+ 4. Library deletes subscription from KV store
432
+
433
+ **Reference:**
434
+ - [Subscription Example: GitHub](#)
435
+ - [Subscription Example: Google Drive](#)
436
+ - [Subscription Example: Slack](#)
437
+
438
+ ---
439
+
440
+ ## 8. Key-Value Store Interface
441
+
442
+ The library requires a persistent store for subscription data.
443
+
444
+ ### 8.1 Store Interface
445
+
446
+ ```typescript
447
+ interface KeyValueStore {
448
+ /**
449
+ * Get value by key
450
+ * @returns Value as string, or null if not found
451
+ */
452
+ get(key: string): Promise<string | null>;
453
+
454
+ /**
455
+ * Set value with optional TTL
456
+ * @param key - Key to set
457
+ * @param value - Value (will be JSON stringified)
458
+ * @param ttl - Time to live in seconds (optional)
459
+ */
460
+ set(key: string, value: string, ttl?: number): Promise<void>;
461
+
462
+ /**
463
+ * Delete key
464
+ */
465
+ delete(key: string): Promise<void>;
466
+
467
+ /**
468
+ * Scan keys by pattern (optional, for debugging)
469
+ * @param pattern - Glob pattern (e.g., "subscription:*")
470
+ */
471
+ scan?(pattern: string): Promise<string[]>;
472
+ }
473
+ ```
474
+
475
+ ### 8.2 Storage Schema
476
+
477
+ The library uses these key patterns:
478
+
479
+ ```typescript
480
+ // Primary subscription data
481
+ `subscription:{subscriptionId}` → {
482
+ uri: string,
483
+ resourceType: string,
484
+ clientCallbackUrl: string,
485
+ clientCallbackSecret?: string,
486
+ userId: string,
487
+ thirdPartyWebhookId: string,
488
+ metadata?: any,
489
+ createdAt: number
490
+ }
491
+
492
+ // User subscription index
493
+ `user:{userId}:subscriptions` → string[] // Array of subscriptionIds
494
+
495
+ // Resource subscription index (optional, for bulk operations)
496
+ `resource:{resourceUri}:subscriptions` → string[]
497
+ ```
498
+
499
+ ### 8.3 Redis Implementation
500
+
501
+ ```typescript
502
+ import Redis from 'ioredis';
503
+
504
+ const redis = new Redis(process.env.REDIS_URL);
505
+
506
+ const store: KeyValueStore = {
507
+ get: async (key) => await redis.get(key),
508
+
509
+ set: async (key, value, ttl) => {
510
+ if (ttl) {
511
+ await redis.setex(key, ttl, value);
512
+ } else {
513
+ await redis.set(key, value);
514
+ }
515
+ },
516
+
517
+ delete: async (key) => {
518
+ await redis.del(key);
519
+ },
520
+
521
+ scan: async (pattern) => {
522
+ const keys: string[] = [];
523
+ let cursor = '0';
524
+ do {
525
+ const [newCursor, matches] = await redis.scan(
526
+ cursor,
527
+ 'MATCH',
528
+ pattern,
529
+ 'COUNT',
530
+ 100
531
+ );
532
+ cursor = newCursor;
533
+ keys.push(...matches);
534
+ } while (cursor !== '0');
535
+ return keys;
536
+ }
537
+ };
538
+ ```
539
+
540
+ **Reference:**
541
+ - [Store Examples: Redis](#)
542
+ - [Store Examples: DynamoDB](#)
543
+ - [Store Examples: PostgreSQL](#)
544
+ - [Store Examples: In-Memory (Dev Only)](#)
545
+
546
+ ---
547
+
548
+ ## 9. Webhook Configuration
549
+
550
+ ### 9.1 Webhook Config Interface
551
+
552
+ ```typescript
553
+ interface WebhookConfig {
554
+ // Incoming webhooks from third-parties
555
+ incomingPath?: string; // Default: '/webhooks/incoming'
556
+ incomingSecret?: string; // Shared secret for verification
557
+ verifyIncomingSignature?: (
558
+ payload: any,
559
+ signature: string,
560
+ secret: string
561
+ ) => boolean;
562
+
563
+ // Outgoing webhooks to clients
564
+ outgoing?: {
565
+ timeout?: number; // Default: 5000ms
566
+ retries?: number; // Default: 3
567
+ retryDelay?: number; // Default: 1000ms (exponential backoff)
568
+
569
+ // Sign outgoing webhook payloads
570
+ signPayload?: (payload: any, secret: string) => string;
571
+ };
572
+ }
573
+ ```
574
+
575
+ ### 9.2 Signature Verification Examples
576
+
577
+ **GitHub Signature Verification:**
578
+
579
+ ```typescript
580
+ import crypto from 'crypto';
581
+
582
+ function verifyGitHubSignature(
583
+ payload: any,
584
+ signature: string,
585
+ secret: string
586
+ ): boolean {
587
+ const hmac = crypto.createHmac('sha256', secret);
588
+ hmac.update(JSON.stringify(payload));
589
+ const expected = `sha256=${hmac.digest('hex')}`;
590
+ return crypto.timingSafeEqual(
591
+ Buffer.from(signature),
592
+ Buffer.from(expected)
593
+ );
594
+ }
595
+ ```
596
+
597
+ **Slack Signature Verification:**
598
+
599
+ ```typescript
600
+ function verifySlackSignature(
601
+ payload: any,
602
+ signature: string,
603
+ timestamp: string,
604
+ secret: string
605
+ ): boolean {
606
+ const baseString = `v0:${timestamp}:${JSON.stringify(payload)}`;
607
+ const hmac = crypto.createHmac('sha256', secret);
608
+ hmac.update(baseString);
609
+ const expected = `v0=${hmac.digest('hex')}`;
610
+ return crypto.timingSafeEqual(
611
+ Buffer.from(signature),
612
+ Buffer.from(expected)
613
+ );
614
+ }
615
+ ```
616
+
617
+ **Reference:**
618
+ - [Webhook Security Guide](#)
619
+ - [Third-Party Webhook Formats](#)
620
+
621
+ ---
622
+
623
+ ## 10. Error Handling
624
+
625
+ ### 10.1 Error Types
626
+
627
+ ```typescript
628
+ class MCPError extends Error {
629
+ code: number;
630
+ data?: any;
631
+ }
632
+
633
+ class AuthenticationError extends MCPError { code = -32001 }
634
+ class ValidationError extends MCPError { code = -32002 }
635
+ class ResourceNotFoundError extends MCPError { code = -32003 }
636
+ class ToolExecutionError extends MCPError { code = -32004 }
637
+ class WebhookError extends MCPError { code = -32005 }
638
+ class StorageError extends MCPError { code = -32006 }
639
+ ```
640
+
641
+ ### 10.2 Retry Logic
642
+
643
+ The library implements exponential backoff for client webhook calls:
644
+
645
+ ```typescript
646
+ // Pseudocode
647
+ for (attempt = 0; attempt < maxRetries; attempt++) {
648
+ try {
649
+ response = await callClientWebhook(url, payload);
650
+ if (response.ok) return success;
651
+
652
+ // Don't retry 4xx client errors
653
+ if (response.status >= 400 && response.status < 500) {
654
+ return failure;
655
+ }
656
+ } catch (error) {
657
+ if (attempt < maxRetries - 1) {
658
+ await sleep(retryDelay * Math.pow(2, attempt));
659
+ }
660
+ }
661
+ }
662
+
663
+ // All retries failed - store in dead letter queue
664
+ await storeFailedWebhook(url, payload);
665
+ ```
666
+
667
+ **Reference:**
668
+ - [Error Handling Guide](#)
669
+ - [Dead Letter Queue Setup](#)
670
+
671
+ ---
672
+
673
+ ## 11. Authentication
674
+
675
+ ### 11.1 Authentication Handler
676
+
677
+ ```typescript
678
+ interface AuthContext {
679
+ userId: string;
680
+ [key: string]: any; // Additional context (tokens, permissions, etc.)
681
+ }
682
+
683
+ type AuthenticateFunction = (req: Request) => Promise<AuthContext>;
684
+ ```
685
+
686
+ ### 11.2 Example Implementations
687
+
688
+ **Bearer Token:**
689
+
690
+ ```typescript
691
+ authenticate: async (req) => {
692
+ const token = req.headers.authorization?.replace('Bearer ', '');
693
+ if (!token) throw new AuthenticationError('Missing token');
694
+
695
+ const payload = await verifyJWT(token);
696
+ return {
697
+ userId: payload.sub,
698
+ email: payload.email,
699
+ githubToken: payload.githubToken
700
+ };
701
+ }
702
+ ```
703
+
704
+ **API Key:**
705
+
706
+ ```typescript
707
+ authenticate: async (req) => {
708
+ const apiKey = req.headers['x-api-key'];
709
+ if (!apiKey) throw new AuthenticationError('Missing API key');
710
+
711
+ const user = await db.users.findByApiKey(apiKey);
712
+ if (!user) throw new AuthenticationError('Invalid API key');
713
+
714
+ return {
715
+ userId: user.id,
716
+ permissions: user.permissions
717
+ };
718
+ }
719
+ ```
720
+
721
+ **OAuth2:**
722
+
723
+ ```typescript
724
+ authenticate: async (req) => {
725
+ const token = req.headers.authorization?.replace('Bearer ', '');
726
+ const userInfo = await oauth2Client.verifyToken(token);
727
+
728
+ return {
729
+ userId: userInfo.sub,
730
+ email: userInfo.email,
731
+ scopes: userInfo.scope.split(' ')
732
+ };
733
+ }
734
+ ```
735
+
736
+ **Reference:**
737
+ - [Authentication Examples](#)
738
+ - [OAuth2 Integration Guide](#)
739
+
740
+ ---
741
+
742
+ ## 12. Complete Example: GitHub MCP Server
743
+
744
+ **File:** `github-mcp-server.ts`
745
+
746
+ ```typescript
747
+ import { createMCPServer } from 'mcp-http-webhook';
748
+ import { Octokit } from '@octokit/rest';
749
+ import Redis from 'ioredis';
750
+ import crypto from 'crypto';
751
+
752
+ const redis = new Redis(process.env.REDIS_URL);
753
+
754
+ const server = createMCPServer({
755
+ name: 'github-mcp',
756
+ version: '1.0.0',
757
+ port: 3000,
758
+ publicUrl: process.env.PUBLIC_URL || 'https://mcp.example.com',
759
+
760
+ store: {
761
+ get: async (key) => await redis.get(key),
762
+ set: async (key, value, ttl) => {
763
+ if (ttl) await redis.setex(key, ttl, value);
764
+ else await redis.set(key, value);
765
+ },
766
+ delete: async (key) => await redis.del(key),
767
+ scan: async (pattern) => {
768
+ const keys: string[] = [];
769
+ let cursor = '0';
770
+ do {
771
+ const [newCursor, matches] = await redis.scan(cursor, 'MATCH', pattern);
772
+ cursor = newCursor;
773
+ keys.push(...matches);
774
+ } while (cursor !== '0');
775
+ return keys;
776
+ }
777
+ },
778
+
779
+ authenticate: async (req) => {
780
+ const token = req.headers.authorization?.replace('Bearer ', '');
781
+ if (!token) throw new Error('Unauthorized');
782
+
783
+ const payload = await verifyJWT(token);
784
+ return {
785
+ userId: payload.sub,
786
+ githubToken: payload.githubToken
787
+ };
788
+ },
789
+
790
+ tools: [
791
+ {
792
+ name: 'create_issue',
793
+ description: 'Create a new GitHub issue',
794
+ inputSchema: {
795
+ type: 'object',
796
+ properties: {
797
+ owner: { type: 'string', description: 'Repository owner' },
798
+ repo: { type: 'string', description: 'Repository name' },
799
+ title: { type: 'string', description: 'Issue title' },
800
+ body: { type: 'string', description: 'Issue description' }
801
+ },
802
+ required: ['owner', 'repo', 'title']
803
+ },
804
+ handler: async (input, context) => {
805
+ const octokit = new Octokit({ auth: context.githubToken });
806
+ const { data } = await octokit.issues.create({
807
+ owner: input.owner,
808
+ repo: input.repo,
809
+ title: input.title,
810
+ body: input.body
811
+ });
812
+ return { issue: data };
813
+ }
814
+ },
815
+ {
816
+ name: 'list_repositories',
817
+ description: 'List all repositories for authenticated user',
818
+ inputSchema: {
819
+ type: 'object',
820
+ properties: {
821
+ type: {
822
+ type: 'string',
823
+ enum: ['all', 'owner', 'member'],
824
+ default: 'all'
825
+ }
826
+ }
827
+ },
828
+ handler: async (input, context) => {
829
+ const octokit = new Octokit({ auth: context.githubToken });
830
+ const { data } = await octokit.repos.listForAuthenticatedUser({
831
+ type: input.type || 'all'
832
+ });
833
+ return { repositories: data };
834
+ }
835
+ }
836
+ ],
837
+
838
+ resources: [
839
+ {
840
+ uri: 'github://repo/{owner}/{repo}/issues',
841
+ name: 'GitHub Repository Issues',
842
+ description: 'All issues in a GitHub repository',
843
+ mimeType: 'application/json',
844
+
845
+ read: async (uri, context) => {
846
+ const { owner, repo } = parseUriTemplate(uri);
847
+ const octokit = new Octokit({ auth: context.githubToken });
848
+ const { data } = await octokit.issues.listForRepo({
849
+ owner,
850
+ repo,
851
+ state: 'all'
852
+ });
853
+ return { contents: data };
854
+ },
855
+
856
+ list: async (context) => {
857
+ const octokit = new Octokit({ auth: context.githubToken });
858
+ const { data } = await octokit.repos.listForAuthenticatedUser();
859
+
860
+ return data.map(repo => ({
861
+ uri: `github://repo/${repo.owner.login}/${repo.name}/issues`,
862
+ name: `${repo.full_name} Issues`,
863
+ description: `Issue tracker for ${repo.full_name}`,
864
+ mimeType: 'application/json'
865
+ }));
866
+ },
867
+
868
+ subscription: {
869
+ onSubscribe: async (uri, subscriptionId, thirdPartyWebhookUrl, context) => {
870
+ const { owner, repo } = parseUriTemplate(uri);
871
+ const octokit = new Octokit({ auth: context.githubToken });
872
+
873
+ // Create webhook in GitHub pointing to our server
874
+ const { data: webhook } = await octokit.repos.createWebhook({
875
+ owner,
876
+ repo,
877
+ config: {
878
+ url: thirdPartyWebhookUrl,
879
+ content_type: 'json',
880
+ secret: process.env.GITHUB_WEBHOOK_SECRET
881
+ },
882
+ events: ['issues', 'issue_comment']
883
+ });
884
+
885
+ console.log(`Created GitHub webhook ${webhook.id} -> ${thirdPartyWebhookUrl}`);
886
+
887
+ return {
888
+ thirdPartyWebhookId: webhook.id.toString(),
889
+ metadata: { owner, repo }
890
+ };
891
+ },
892
+
893
+ onUnsubscribe: async (uri, subscriptionId, storedData, context) => {
894
+ const { owner, repo } = storedData.metadata;
895
+ const octokit = new Octokit({ auth: context.githubToken });
896
+
897
+ await octokit.repos.deleteWebhook({
898
+ owner,
899
+ repo,
900
+ hook_id: parseInt(storedData.thirdPartyWebhookId)
901
+ });
902
+
903
+ console.log(`Deleted GitHub webhook ${storedData.thirdPartyWebhookId}`);
904
+ },
905
+
906
+ onWebhook: async (subscriptionId, payload, headers) => {
907
+ // Verify GitHub signature
908
+ const signature = headers['x-hub-signature-256'];
909
+ const body = JSON.stringify(payload);
910
+ const hmac = crypto.createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET!);
911
+ hmac.update(body);
912
+ const expected = `sha256=${hmac.digest('hex')}`;
913
+
914
+ if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
915
+ throw new Error('Invalid webhook signature');
916
+ }
917
+
918
+ const event = headers['x-github-event'];
919
+
920
+ if (event === 'issues') {
921
+ const { action, issue, repository } = payload;
922
+
923
+ if (['opened', 'edited', 'closed', 'reopened'].includes(action)) {
924
+ return {
925
+ resourceUri: `github://repo/${repository.owner.login}/${repository.name}/issues`,
926
+ changeType: action === 'opened' ? 'created' :
927
+ action === 'closed' ? 'deleted' : 'updated',
928
+ data: {
929
+ issueNumber: issue.number,
930
+ title: issue.title,
931
+ state: issue.state,
932
+ action
933
+ }
934
+ };
935
+ }
936
+ }
937
+
938
+ if (event === 'issue_comment') {
939
+ const { action, issue, comment, repository } = payload;
940
+
941
+ if (action === 'created') {
942
+ return {
943
+ resourceUri: `github://repo/${repository.owner.login}/${repository.name}/issues`,
944
+ changeType: 'updated',
945
+ data: {
946
+ issueNumber: issue.number,
947
+ commentId: comment.id,
948
+ commentBody: comment.body,
949
+ commentAuthor: comment.user.login
950
+ }
951
+ };
952
+ }
953
+ }
954
+
955
+ return null;
956
+ }
957
+ }
958
+ }
959
+ ],
960
+
961
+ webhooks: {
962
+ incomingPath: '/webhooks/incoming',
963
+ incomingSecret: process.env.GITHUB_WEBHOOK_SECRET,
964
+ verifyIncomingSignature: (payload, signature, secret) => {
965
+ const hmac = crypto.createHmac('sha256', secret);
966
+ hmac.update(JSON.stringify(payload));
967
+ const expected = `sha256=${hmac.digest('hex')}`;
968
+ return crypto.timingSafeEqual(
969
+ Buffer.from(signature),
970
+ Buffer.from(expected)
971
+ );
972
+ },
973
+ outgoing: {
974
+ timeout: 5000,
975
+ retries: 3,
976
+ retryDelay: 1000,
977
+ signPayload: (payload, secret) => {
978
+ const hmac = crypto.createHmac('sha256', secret);
979
+ hmac.update(JSON.stringify(payload));
980
+ return `sha256=${hmac.digest('hex')}`;
981
+ }
982
+ }
983
+ },
984
+
985
+ logger: console,
986
+ logLevel: 'info'
987
+ });
988
+
989
+ await server.start();
990
+ console.log(`GitHub MCP server running on port 3000`);
991
+ console.log(`Webhook endpoint: ${process.env.PUBLIC_URL}/webhooks/incoming/{subscriptionId}`);
992
+ ```
993
+
994
+ **Reference:**
995
+ - [Complete Examples Repository](#)
996
+ - [GitHub Integration Guide](#)
997
+
998
+ ---
999
+
1000
+ ## 13. Deployment Guide
1001
+
1002
+ ### 13.1 Environment Variables
1003
+
1004
+ ```bash
1005
+ # Server Configuration
1006
+ PORT=3000
1007
+ PUBLIC_URL=https://mcp.example.com
1008
+ NODE_ENV=production
1009
+
1010
+ # Redis Configuration
1011
+ REDIS_URL=redis://localhost:6379
1012
+
1013
+ # GitHub Configuration
1014
+ GITHUB_WEBHOOK_SECRET=your-webhook-secret
1015
+
1016
+ # JWT Configuration
1017
+ JWT_SECRET=your-jwt-secret
1018
+ ```
1019
+
1020
+ ### 13.2 Docker Deployment
1021
+
1022
+ **Dockerfile:**
1023
+
1024
+ ```dockerfile
1025
+ FROM node:20-alpine
1026
+
1027
+ WORKDIR /app
1028
+
1029
+ COPY package*.json ./
1030
+ RUN npm ci --production
1031
+
1032
+ COPY . .
1033
+ RUN npm run build
1034
+
1035
+ EXPOSE 3000
1036
+
1037
+ CMD ["node", "dist/index.js"]
1038
+ ```
1039
+
1040
+ **docker-compose.yml:**
1041
+
1042
+ ```yaml
1043
+ version: '3.8'
1044
+
1045
+ services:
1046
+ mcp-server:
1047
+ build: .
1048
+ ports:
1049
+ - "3000:3000"
1050
+ environment:
1051
+ - PUBLIC_URL=https://mcp.example.com
1052
+ - REDIS_URL=redis://redis:6379
1053
+ - GITHUB_WEBHOOK_SECRET=${GITHUB_WEBHOOK_SECRET}
1054
+ depends_on:
1055
+ - redis
1056
+
1057
+ redis:
1058
+ image: redis:7-alpine
1059
+ volumes:
1060
+ - redis-data:/data
1061
+
1062
+ volumes:
1063
+ redis-data:
1064
+ ```
1065
+
1066
+ ### 13.3 Kubernetes Deployment
1067
+
1068
+ **deployment.yaml:**
1069
+
1070
+ ```yaml
1071
+ apiVersion: apps/v1
1072
+ kind: Deployment
1073
+ metadata:
1074
+ name: mcp-http-webhook
1075
+ labels:
1076
+ app: mcp-http-webhook
1077
+ spec:
1078
+ replicas: 3
1079
+ selector:
1080
+ matchLabels:
1081
+ app: mcp-http-webhook
1082
+ template:
1083
+ metadata:
1084
+ labels:
1085
+ app: mcp-http-webhook
1086
+ spec:
1087
+ containers:
1088
+ - name: mcp-server
1089
+ image: your-registry/mcp-http-webhook:latest
1090
+ ports:
1091
+ - containerPort: 3000
1092
+ env:
1093
+ - name: PORT
1094
+ value: "3000"
1095
+ - name: PUBLIC_URL
1096
+ value: "https://mcp.example.com"
1097
+ - name: REDIS_URL
1098
+ valueFrom:
1099
+ secretKeyRef:
1100
+ name: mcp-secrets
1101
+ key: redis-url
1102
+ - name: GITHUB_WEBHOOK_SECRET
1103
+ valueFrom:
1104
+ secretKeyRef:
1105
+ name: mcp-secrets
1106
+ key: github-webhook-secret
1107
+ - name: JWT_SECRET
1108
+ valueFrom:
1109
+ secretKeyRef:
1110
+ name: mcp-secrets
1111
+ key: jwt-secret
1112
+ resources:
1113
+ requests:
1114
+ memory: "256Mi"
1115
+ cpu: "250m"
1116
+ limits:
1117
+ memory: "512Mi"
1118
+ cpu: "500m"
1119
+ livenessProbe:
1120
+ httpGet:
1121
+ path: /health
1122
+ port: 3000
1123
+ initialDelaySeconds: 30
1124
+ periodSeconds: 10
1125
+ readinessProbe:
1126
+ httpGet:
1127
+ path: /ready
1128
+ port: 3000
1129
+ initialDelaySeconds: 5
1130
+ periodSeconds: 5
1131
+
1132
+ ---
1133
+ apiVersion: v1
1134
+ kind: Service
1135
+ metadata:
1136
+ name: mcp-http-webhook
1137
+ spec:
1138
+ selector:
1139
+ app: mcp-http-webhook
1140
+ ports:
1141
+ - protocol: TCP
1142
+ port: 80
1143
+ targetPort: 3000
1144
+ type: ClusterIP
1145
+
1146
+ ---
1147
+ apiVersion: networking.k8s.io/v1
1148
+ kind: Ingress
1149
+ metadata:
1150
+ name: mcp-http-webhook
1151
+ annotations:
1152
+ cert-manager.io/cluster-issuer: letsencrypt-prod
1153
+ nginx.ingress.kubernetes.io/ssl-redirect: "true"
1154
+ spec:
1155
+ ingressClassName: nginx
1156
+ tls:
1157
+ - hosts:
1158
+ - mcp.example.com
1159
+ secretName: mcp-tls
1160
+ rules:
1161
+ - host: mcp.example.com
1162
+ http:
1163
+ paths:
1164
+ - path: /
1165
+ pathType: Prefix
1166
+ backend:
1167
+ service:
1168
+ name: mcp-http-webhook
1169
+ port:
1170
+ number: 80
1171
+ ```
1172
+
1173
+ **secrets.yaml:**
1174
+
1175
+ ```yaml
1176
+ apiVersion: v1
1177
+ kind: Secret
1178
+ metadata:
1179
+ name: mcp-secrets
1180
+ type: Opaque
1181
+ stringData:
1182
+ redis-url: "redis://redis-service:6379"
1183
+ github-webhook-secret: "your-webhook-secret"
1184
+ jwt-secret: "your-jwt-secret"
1185
+ ```
1186
+
1187
+ ### 13.4 Scaling Considerations
1188
+
1189
+ **Horizontal Scaling:**
1190
+ - Multiple instances can run simultaneously
1191
+ - No connection state = trivial load balancing
1192
+ - All state stored in Redis/external KV store
1193
+ - Use sticky sessions NOT required
1194
+
1195
+ **Load Balancer Configuration:**
1196
+ ```nginx
1197
+ upstream mcp_backend {
1198
+ least_conn; # Or round_robin
1199
+ server mcp-1:3000;
1200
+ server mcp-2:3000;
1201
+ server mcp-3:3000;
1202
+ }
1203
+
1204
+ server {
1205
+ listen 443 ssl;
1206
+ server_name mcp.example.com;
1207
+
1208
+ location / {
1209
+ proxy_pass http://mcp_backend;
1210
+ proxy_set_header Host $host;
1211
+ proxy_set_header X-Real-IP $remote_addr;
1212
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
1213
+ proxy_set_header X-Forwarded-Proto $scheme;
1214
+ }
1215
+ }
1216
+ ```
1217
+
1218
+ **Reference:**
1219
+ - [Deployment Examples](#)
1220
+ - [Kubernetes Best Practices](#)
1221
+ - [High Availability Guide](#)
1222
+
1223
+ ---
1224
+
1225
+ ## 14. Monitoring & Observability
1226
+
1227
+ ### 14.1 Health Check Endpoints
1228
+
1229
+ The library automatically exposes:
1230
+
1231
+ ```
1232
+ GET /health # Basic health check
1233
+ GET /ready # Readiness check (includes store connectivity)
1234
+ GET /metrics # Prometheus metrics (optional)
1235
+ ```
1236
+
1237
+ ### 14.2 Logging
1238
+
1239
+ **Structured Logging Interface:**
1240
+
1241
+ ```typescript
1242
+ interface Logger {
1243
+ debug(message: string, meta?: any): void;
1244
+ info(message: string, meta?: any): void;
1245
+ warn(message: string, meta?: any): void;
1246
+ error(message: string, meta?: any): void;
1247
+ }
1248
+ ```
1249
+
1250
+ **Winston Example:**
1251
+
1252
+ ```typescript
1253
+ import winston from 'winston';
1254
+
1255
+ const logger = winston.createLogger({
1256
+ level: process.env.LOG_LEVEL || 'info',
1257
+ format: winston.format.json(),
1258
+ transports: [
1259
+ new winston.transports.Console({
1260
+ format: winston.format.combine(
1261
+ winston.format.timestamp(),
1262
+ winston.format.json()
1263
+ )
1264
+ })
1265
+ ]
1266
+ });
1267
+
1268
+ const server = createMCPServer({
1269
+ // ...
1270
+ logger,
1271
+ logLevel: 'info'
1272
+ });
1273
+ ```
1274
+
1275
+ ### 14.3 Metrics
1276
+
1277
+ **Key Metrics Tracked:**
1278
+
1279
+ - `mcp_requests_total` - Total HTTP requests (by method, status)
1280
+ - `mcp_request_duration_seconds` - Request latency histogram
1281
+ - `mcp_tool_calls_total` - Tool invocations (by tool name, status)
1282
+ - `mcp_resource_reads_total` - Resource read operations
1283
+ - `mcp_subscriptions_active` - Current active subscriptions
1284
+ - `mcp_webhook_incoming_total` - Third-party webhooks received
1285
+ - `mcp_webhook_outgoing_total` - Client notifications sent (by status)
1286
+ - `mcp_webhook_retry_total` - Webhook retry attempts
1287
+ - `mcp_store_operations_total` - KV store operations (by type)
1288
+
1289
+ **Prometheus Integration:**
1290
+
1291
+ ```typescript
1292
+ import promClient from 'prom-client';
1293
+
1294
+ const register = new promClient.Registry();
1295
+ promClient.collectDefaultMetrics({ register });
1296
+
1297
+ const server = createMCPServer({
1298
+ // ...
1299
+ metrics: {
1300
+ enabled: true,
1301
+ registry: register
1302
+ }
1303
+ });
1304
+
1305
+ // Metrics available at GET /metrics
1306
+ ```
1307
+
1308
+ ### 14.4 Distributed Tracing
1309
+
1310
+ **OpenTelemetry Integration:**
1311
+
1312
+ ```typescript
1313
+ import { trace } from '@opentelemetry/api';
1314
+ import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
1315
+ import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
1316
+
1317
+ const provider = new NodeTracerProvider();
1318
+ provider.addSpanProcessor(
1319
+ new SimpleSpanProcessor(new JaegerExporter())
1320
+ );
1321
+ provider.register();
1322
+
1323
+ const server = createMCPServer({
1324
+ // ...
1325
+ tracing: {
1326
+ enabled: true,
1327
+ serviceName: 'mcp-http-webhook'
1328
+ }
1329
+ });
1330
+ ```
1331
+
1332
+ **Reference:**
1333
+ - [Monitoring Setup Guide](#)
1334
+ - [Grafana Dashboard Templates](#)
1335
+ - [Alert Configuration Examples](#)
1336
+
1337
+ ---
1338
+
1339
+ ## 15. Testing
1340
+
1341
+ ### 15.1 Unit Testing
1342
+
1343
+ **Example using Jest:**
1344
+
1345
+ ```typescript
1346
+ import { describe, test, expect, beforeEach } from '@jest/globals';
1347
+ import { createMCPServer } from 'mcp-http-webhook';
1348
+
1349
+ describe('MCP Server', () => {
1350
+ let server: MCPServer;
1351
+ let mockStore: KeyValueStore;
1352
+
1353
+ beforeEach(() => {
1354
+ mockStore = {
1355
+ get: jest.fn(),
1356
+ set: jest.fn(),
1357
+ delete: jest.fn()
1358
+ };
1359
+
1360
+ server = createMCPServer({
1361
+ name: 'test-server',
1362
+ version: '1.0.0',
1363
+ publicUrl: 'http://localhost:3000',
1364
+ store: mockStore,
1365
+ tools: [],
1366
+ resources: []
1367
+ });
1368
+ });
1369
+
1370
+ test('should create server instance', () => {
1371
+ expect(server).toBeDefined();
1372
+ });
1373
+
1374
+ test('should handle tool calls', async () => {
1375
+ // Test implementation
1376
+ });
1377
+ });
1378
+ ```
1379
+
1380
+ ### 15.2 Integration Testing
1381
+
1382
+ **Testing Subscription Flow:**
1383
+
1384
+ ```typescript
1385
+ import request from 'supertest';
1386
+
1387
+ describe('Subscription Flow', () => {
1388
+ test('should create subscription and handle webhook', async () => {
1389
+ // 1. Subscribe to resource
1390
+ const subscribeRes = await request(app)
1391
+ .post('/mcp/resources/subscribe')
1392
+ .set('Authorization', 'Bearer test-token')
1393
+ .send({
1394
+ method: 'resources/subscribe',
1395
+ params: {
1396
+ uri: 'github://repo/test/test/issues',
1397
+ callbackUrl: 'http://client.test/webhook',
1398
+ callbackSecret: 'test-secret'
1399
+ }
1400
+ });
1401
+
1402
+ expect(subscribeRes.status).toBe(200);
1403
+ const { subscriptionId } = subscribeRes.body;
1404
+
1405
+ // 2. Simulate third-party webhook
1406
+ const webhookRes = await request(app)
1407
+ .post(`/webhooks/incoming/${subscriptionId}`)
1408
+ .set('X-GitHub-Event', 'issues')
1409
+ .send({
1410
+ action: 'opened',
1411
+ issue: { number: 1, title: 'Test' },
1412
+ repository: { owner: { login: 'test' }, name: 'test' }
1413
+ });
1414
+
1415
+ expect(webhookRes.status).toBe(200);
1416
+
1417
+ // 3. Verify client webhook was called
1418
+ // (Use nock or similar to mock HTTP calls)
1419
+ });
1420
+ });
1421
+ ```
1422
+
1423
+ ### 15.3 Load Testing
1424
+
1425
+ **Using k6:**
1426
+
1427
+ ```javascript
1428
+ import http from 'k6/http';
1429
+ import { check, sleep } from 'k6';
1430
+
1431
+ export let options = {
1432
+ stages: [
1433
+ { duration: '30s', target: 20 },
1434
+ { duration: '1m', target: 50 },
1435
+ { duration: '30s', target: 0 }
1436
+ ]
1437
+ };
1438
+
1439
+ export default function() {
1440
+ const payload = JSON.stringify({
1441
+ method: 'tools/call',
1442
+ params: {
1443
+ name: 'create_issue',
1444
+ arguments: {
1445
+ owner: 'test',
1446
+ repo: 'test',
1447
+ title: 'Load test issue'
1448
+ }
1449
+ }
1450
+ });
1451
+
1452
+ const params = {
1453
+ headers: {
1454
+ 'Content-Type': 'application/json',
1455
+ 'Authorization': 'Bearer test-token'
1456
+ }
1457
+ };
1458
+
1459
+ const res = http.post('http://localhost:3000/mcp/tools/call', payload, params);
1460
+
1461
+ check(res, {
1462
+ 'status is 200': (r) => r.status === 200,
1463
+ 'response time < 500ms': (r) => r.timings.duration < 500
1464
+ });
1465
+
1466
+ sleep(1);
1467
+ }
1468
+ ```
1469
+
1470
+ **Reference:**
1471
+ - [Testing Examples](#)
1472
+ - [Mock Store Implementation](#)
1473
+ - [Load Testing Guide](#)
1474
+
1475
+ ---
1476
+
1477
+ ## 16. Security Considerations
1478
+
1479
+ ### 16.1 Webhook Security
1480
+
1481
+ **Critical Security Practices:**
1482
+
1483
+ 1. **Always Verify Signatures**
1484
+ - Third-party webhooks: Verify using provider's signature
1485
+ - Client webhooks: Sign outgoing payloads
1486
+
1487
+ 2. **Use HTTPS Only**
1488
+ - `publicUrl` must be HTTPS in production
1489
+ - Reject non-HTTPS callback URLs
1490
+
1491
+ 3. **Rate Limiting**
1492
+ - Implement rate limits on webhook endpoints
1493
+ - Prevent DoS attacks
1494
+
1495
+ 4. **Timeout Configuration**
1496
+ - Set reasonable timeouts for outgoing webhooks
1497
+ - Prevent hanging connections
1498
+
1499
+ ### 16.2 Authentication Best Practices
1500
+
1501
+ ```typescript
1502
+ // Example: Multi-factor auth check
1503
+ authenticate: async (req) => {
1504
+ const token = req.headers.authorization?.replace('Bearer ', '');
1505
+ const payload = await verifyJWT(token);
1506
+
1507
+ // Verify IP whitelist for sensitive operations
1508
+ if (payload.requiresIpCheck) {
1509
+ const clientIp = req.ip;
1510
+ if (!isIpWhitelisted(clientIp, payload.userId)) {
1511
+ throw new AuthenticationError('IP not whitelisted');
1512
+ }
1513
+ }
1514
+
1515
+ // Check for revoked tokens
1516
+ const isRevoked = await redis.get(`revoked:${payload.jti}`);
1517
+ if (isRevoked) {
1518
+ throw new AuthenticationError('Token revoked');
1519
+ }
1520
+
1521
+ return {
1522
+ userId: payload.sub,
1523
+ permissions: payload.permissions
1524
+ };
1525
+ }
1526
+ ```
1527
+
1528
+ ### 16.3 Input Validation
1529
+
1530
+ The library automatically validates:
1531
+ - Tool inputs against `inputSchema`
1532
+ - Resource URIs against URI templates
1533
+ - Webhook payloads (basic structure)
1534
+
1535
+ **Additional Validation:**
1536
+
1537
+ ```typescript
1538
+ tools: [{
1539
+ name: 'create_issue',
1540
+ inputSchema: {
1541
+ type: 'object',
1542
+ properties: {
1543
+ title: {
1544
+ type: 'string',
1545
+ minLength: 1,
1546
+ maxLength: 256
1547
+ },
1548
+ body: {
1549
+ type: 'string',
1550
+ maxLength: 65536
1551
+ }
1552
+ },
1553
+ required: ['title']
1554
+ },
1555
+ handler: async (input, context) => {
1556
+ // Additional business logic validation
1557
+ if (containsSpam(input.title)) {
1558
+ throw new ValidationError('Content contains spam');
1559
+ }
1560
+
1561
+ // Proceed with tool execution
1562
+ }
1563
+ }]
1564
+ ```
1565
+
1566
+ ### 16.4 Secret Management
1567
+
1568
+ **DO NOT hardcode secrets:**
1569
+
1570
+ ```typescript
1571
+ // ❌ BAD
1572
+ const secret = 'my-secret-key';
1573
+
1574
+ // ✅ GOOD
1575
+ const secret = process.env.GITHUB_WEBHOOK_SECRET;
1576
+ if (!secret) {
1577
+ throw new Error('GITHUB_WEBHOOK_SECRET not configured');
1578
+ }
1579
+ ```
1580
+
1581
+ **Use Secret Management Services:**
1582
+ - AWS Secrets Manager
1583
+ - HashiCorp Vault
1584
+ - Kubernetes Secrets
1585
+ - Azure Key Vault
1586
+
1587
+ **Reference:**
1588
+ - [Security Checklist](#)
1589
+ - [Secret Management Guide](#)
1590
+ - [Penetration Testing Guide](#)
1591
+
1592
+ ---
1593
+
1594
+ ## 17. Advanced Features
1595
+
1596
+ ### 17.1 Batch Operations
1597
+
1598
+ ```typescript
1599
+ interface BatchConfig {
1600
+ maxBatchSize?: number;
1601
+ batchTimeout?: number;
1602
+ }
1603
+
1604
+ const server = createMCPServer({
1605
+ // ...
1606
+ batch: {
1607
+ maxBatchSize: 100,
1608
+ batchTimeout: 1000
1609
+ }
1610
+ });
1611
+
1612
+ // Clients can send multiple requests in one HTTP call
1613
+ POST /mcp/batch
1614
+ {
1615
+ "requests": [
1616
+ { "method": "tools/call", "params": {...} },
1617
+ { "method": "resources/read", "params": {...} }
1618
+ ]
1619
+ }
1620
+ ```
1621
+
1622
+ ### 17.2 Caching Layer
1623
+
1624
+ ```typescript
1625
+ interface CacheConfig {
1626
+ enabled: boolean;
1627
+ ttl?: number; // Default TTL in seconds
1628
+ keyPrefix?: string;
1629
+ }
1630
+
1631
+ const server = createMCPServer({
1632
+ // ...
1633
+ cache: {
1634
+ enabled: true,
1635
+ ttl: 300, // 5 minutes
1636
+ keyPrefix: 'mcp:cache:'
1637
+ }
1638
+ });
1639
+
1640
+ // Resources can specify custom cache behavior
1641
+ resources: [{
1642
+ uri: 'github://repo/{owner}/{repo}/issues',
1643
+ cache: {
1644
+ enabled: true,
1645
+ ttl: 600, // 10 minutes
1646
+ key: (uri) => `issues:${uri}`
1647
+ },
1648
+ read: async (uri, context) => {
1649
+ // Will be cached automatically
1650
+ }
1651
+ }]
1652
+ ```
1653
+
1654
+ ### 17.3 Webhook Dead Letter Queue
1655
+
1656
+ ```typescript
1657
+ interface DeadLetterQueueConfig {
1658
+ enabled: boolean;
1659
+ store: KeyValueStore;
1660
+ retention?: number; // Days to retain failed webhooks
1661
+ maxRetries?: number;
1662
+ }
1663
+
1664
+ const server = createMCPServer({
1665
+ // ...
1666
+ webhooks: {
1667
+ deadLetterQueue: {
1668
+ enabled: true,
1669
+ store: dlqStore,
1670
+ retention: 7,
1671
+ maxRetries: 5
1672
+ }
1673
+ }
1674
+ });
1675
+
1676
+ // Failed webhooks stored as:
1677
+ // dlq:{timestamp}:{subscriptionId} -> { url, payload, attempts, lastError }
1678
+ ```
1679
+
1680
+ ### 17.4 Resource Pagination
1681
+
1682
+ ```typescript
1683
+ resources: [{
1684
+ uri: 'github://repo/{owner}/{repo}/issues',
1685
+ read: async (uri, context, options) => {
1686
+ const { page = 1, limit = 50 } = options?.pagination || {};
1687
+
1688
+ const octokit = new Octokit({ auth: context.githubToken });
1689
+ const { data } = await octokit.issues.listForRepo({
1690
+ owner,
1691
+ repo,
1692
+ page,
1693
+ per_page: limit
1694
+ });
1695
+
1696
+ return {
1697
+ contents: data,
1698
+ pagination: {
1699
+ page,
1700
+ limit,
1701
+ hasMore: data.length === limit,
1702
+ nextPage: data.length === limit ? page + 1 : null
1703
+ }
1704
+ };
1705
+ }
1706
+ }]
1707
+
1708
+ // Client request:
1709
+ POST /mcp/resources/read
1710
+ {
1711
+ "method": "resources/read",
1712
+ "params": {
1713
+ "uri": "github://repo/test/test/issues",
1714
+ "pagination": {
1715
+ "page": 1,
1716
+ "limit": 50
1717
+ }
1718
+ }
1719
+ }
1720
+ ```
1721
+
1722
+ ### 17.5 Middleware Support
1723
+
1724
+ ```typescript
1725
+ interface Middleware {
1726
+ (req: Request, res: Response, next: NextFunction): void | Promise<void>;
1727
+ }
1728
+
1729
+ const server = createMCPServer({
1730
+ // ...
1731
+ middleware: [
1732
+ // Rate limiting
1733
+ rateLimit({
1734
+ windowMs: 15 * 60 * 1000,
1735
+ max: 100,
1736
+ message: 'Too many requests'
1737
+ }),
1738
+
1739
+ // Request logging
1740
+ (req, res, next) => {
1741
+ console.log(`${req.method} ${req.path}`);
1742
+ next();
1743
+ },
1744
+
1745
+ // Custom auth middleware
1746
+ async (req, res, next) => {
1747
+ if (req.path.startsWith('/admin')) {
1748
+ // Additional admin checks
1749
+ }
1750
+ next();
1751
+ }
1752
+ ]
1753
+ });
1754
+ ```
1755
+
1756
+ **Reference:**
1757
+ - [Advanced Features Guide](#)
1758
+ - [Caching Strategies](#)
1759
+ - [DLQ Management](#)
1760
+
1761
+ ---
1762
+
1763
+ ## 18. Migration Guide
1764
+
1765
+ ### 18.1 From Standard MCP SSE Transport
1766
+
1767
+ **Key Changes:**
1768
+
1769
+ | Standard MCP | HTTP Webhook MCP |
1770
+ |--------------|------------------|
1771
+ | Persistent SSE connection | Stateless HTTP requests |
1772
+ | Server pushes to client | Client provides callback URL |
1773
+ | `subscribe()` keeps connection | `subscribe()` returns subscriptionId |
1774
+ | Real-time push | Webhook callback |
1775
+ | Connection per client | Shared webhook endpoints |
1776
+
1777
+ **Migration Steps:**
1778
+
1779
+ 1. **Update Client Implementation**
1780
+ - Remove SSE connection logic
1781
+ - Implement webhook receiver endpoint
1782
+ - Provide callback URL in subscribe requests
1783
+ - Handle notifications via webhooks
1784
+
1785
+ 2. **Update Server Implementation**
1786
+ - Replace `StdioServerTransport` with `createMCPServer()`
1787
+ - Add KV store configuration
1788
+ - Implement subscription handlers
1789
+ - Configure webhook endpoints
1790
+
1791
+ 3. **Update Resource Subscriptions**
1792
+ ```typescript
1793
+ // Before (Standard MCP)
1794
+ server.setRequestHandler(SubscribeRequest, async (request) => {
1795
+ // Setup SSE push
1796
+ });
1797
+
1798
+ // After (HTTP Webhook)
1799
+ resources: [{
1800
+ subscription: {
1801
+ onSubscribe: async (uri, subscriptionId, webhookUrl, context) => {
1802
+ // Register with third-party
1803
+ // Return metadata
1804
+ }
1805
+ }
1806
+ }]
1807
+ ```
1808
+
1809
+ ### 18.2 From Custom Implementation
1810
+
1811
+ If migrating from a custom webhook implementation:
1812
+
1813
+ 1. **Adopt Standard MCP Protocol**
1814
+ - Use MCP JSON-RPC message format
1815
+ - Implement standard endpoints (`/tools/list`, `/resources/read`, etc.)
1816
+
1817
+ 2. **Use Library's Subscription Management**
1818
+ - Replace custom subscription tracking with library's KV store
1819
+ - Leverage automatic webhook routing
1820
+
1821
+ 3. **Standardize Error Handling**
1822
+ - Use MCP error codes
1823
+ - Implement standard error responses
1824
+
1825
+ **Reference:**
1826
+ - [Migration Examples](#)
1827
+ - [Comparison Guide](#)
1828
+
1829
+ ---
1830
+
1831
+ ## 19. Troubleshooting
1832
+
1833
+ ### 19.1 Common Issues
1834
+
1835
+ **Webhook Not Received:**
1836
+
1837
+ ```bash
1838
+ # Check webhook registration
1839
+ curl -H "Authorization: Bearer $TOKEN" \
1840
+ https://api.github.com/repos/owner/repo/hooks
1841
+
1842
+ # Test webhook delivery
1843
+ curl -X POST https://mcp.example.com/webhooks/incoming/sub_xyz \
1844
+ -H "Content-Type: application/json" \
1845
+ -d '{"test": true}'
1846
+
1847
+ # Check server logs
1848
+ docker logs mcp-server | grep "webhook"
1849
+
1850
+ # Verify subscription exists in store
1851
+ redis-cli GET "subscription:sub_xyz"
1852
+ ```
1853
+
1854
+ **Client Not Receiving Notifications:**
1855
+
1856
+ ```typescript
1857
+ // Enable debug logging
1858
+ const server = createMCPServer({
1859
+ // ...
1860
+ logLevel: 'debug',
1861
+ webhooks: {
1862
+ outgoing: {
1863
+ timeout: 5000,
1864
+ retries: 3,
1865
+
1866
+ // Add debugging
1867
+ onBeforeCall: (url, payload) => {
1868
+ console.log('Calling client webhook:', url, payload);
1869
+ },
1870
+ onAfterCall: (url, response, error) => {
1871
+ console.log('Webhook result:', { url, status: response?.status, error });
1872
+ }
1873
+ }
1874
+ }
1875
+ });
1876
+ ```
1877
+
1878
+ **Store Connection Issues:**
1879
+
1880
+ ```typescript
1881
+ // Test store connectivity
1882
+ async function testStore(store: KeyValueStore) {
1883
+ try {
1884
+ await store.set('test-key', 'test-value', 60);
1885
+ const value = await store.get('test-key');
1886
+ await store.delete('test-key');
1887
+
1888
+ if (value === 'test-value') {
1889
+ console.log('✓ Store connection OK');
1890
+ } else {
1891
+ console.error('✗ Store read/write mismatch');
1892
+ }
1893
+ } catch (error) {
1894
+ console.error('✗ Store connection failed:', error);
1895
+ }
1896
+ }
1897
+ ```
1898
+
1899
+ **Signature Verification Failures:**
1900
+
1901
+ ```typescript
1902
+ // Debug signature verification
1903
+ verifyIncomingSignature: (payload, signature, secret) => {
1904
+ console.log('Verifying signature:');
1905
+ console.log('- Received signature:', signature);
1906
+ console.log('- Payload:', JSON.stringify(payload));
1907
+ console.log('- Secret length:', secret?.length);
1908
+
1909
+ const hmac = crypto.createHmac('sha256', secret);
1910
+ hmac.update(JSON.stringify(payload));
1911
+ const expected = `sha256=${hmac.digest('hex')}`;
1912
+
1913
+ console.log('- Expected signature:', expected);
1914
+ console.log('- Match:', signature === expected);
1915
+
1916
+ return signature === expected;
1917
+ }
1918
+ ```
1919
+
1920
+ ### 19.2 Debugging Tools
1921
+
1922
+ **List All Subscriptions:**
1923
+
1924
+ ```typescript
1925
+ // GET /debug/subscriptions (development only)
1926
+ async function listAllSubscriptions(store: KeyValueStore) {
1927
+ const keys = await store.scan('subscription:*');
1928
+ const subscriptions = await Promise.all(
1929
+ keys.map(async (key) => {
1930
+ const data = await store.get(key);
1931
+ return { key, data: JSON.parse(data || '{}') };
1932
+ })
1933
+ );
1934
+ return subscriptions;
1935
+ }
1936
+ ```
1937
+
1938
+ **Test Webhook Delivery:**
1939
+
1940
+ ```bash
1941
+ # Simulate third-party webhook
1942
+ curl -X POST http://localhost:3000/webhooks/incoming/sub_xyz \
1943
+ -H "Content-Type: application/json" \
1944
+ -H "X-GitHub-Event: issues" \
1945
+ -H "X-Hub-Signature-256: sha256=..." \
1946
+ -d @webhook-payload.json
1947
+ ```
1948
+
1949
+ **Monitor Webhook Queue:**
1950
+
1951
+ ```typescript
1952
+ // Check dead letter queue
1953
+ async function checkDLQ(store: KeyValueStore) {
1954
+ const failedWebhooks = await store.scan('dlq:*');
1955
+ console.log(`Failed webhooks: ${failedWebhooks.length}`);
1956
+
1957
+ for (const key of failedWebhooks) {
1958
+ const data = await store.get(key);
1959
+ console.log(JSON.parse(data || '{}'));
1960
+ }
1961
+ }
1962
+ ```
1963
+
1964
+ **Reference:**
1965
+ - [Troubleshooting Guide](#)
1966
+ - [Debug Mode Documentation](#)
1967
+ - [FAQ](#)
1968
+
1969
+ ---
1970
+
1971
+ ## 20. API Reference
1972
+
1973
+ ### 20.1 Core Types
1974
+
1975
+ ```typescript
1976
+ // Server configuration
1977
+ interface MCPServerConfig { /* ... */ }
1978
+
1979
+ // Tool definition
1980
+ interface ToolDefinition<TInput, TOutput> { /* ... */ }
1981
+
1982
+ // Resource definition
1983
+ interface ResourceDefinition<TData> { /* ... */ }
1984
+
1985
+ // Subscription handlers
1986
+ interface ResourceSubscription { /* ... */ }
1987
+
1988
+ // Storage interface
1989
+ interface KeyValueStore { /* ... */ }
1990
+
1991
+ // Webhook configuration
1992
+ interface WebhookConfig { /* ... */ }
1993
+
1994
+ // Authentication context
1995
+ interface AuthContext { /* ... */ }
1996
+ ```
1997
+
1998
+ Full type definitions: [API Types Documentation](#)
1999
+
2000
+ ### 20.2 Utility Functions
2001
+
2002
+ **URI Template Parsing:**
2003
+
2004
+ ```typescript
2005
+ import { parseUriTemplate, matchUriTemplate } from 'mcp-http-webhook/utils';
2006
+
2007
+ const params = parseUriTemplate(
2008
+ 'github://repo/{owner}/{repo}/issues',
2009
+ 'github://repo/octocat/hello-world/issues'
2010
+ );
2011
+ // { owner: 'octocat', repo: 'hello-world' }
2012
+
2013
+ const matches = matchUriTemplate(
2014
+ 'github://repo/{owner}/{repo}/issues',
2015
+ 'github://repo/octocat/hello-world/issues'
2016
+ );
2017
+ // true
2018
+ ```
2019
+
2020
+ **ID Generation:**
2021
+
2022
+ ```typescript
2023
+ import { generateSubscriptionId, generateWebhookUrl } from 'mcp-http-webhook/utils';
2024
+
2025
+ const id = generateSubscriptionId();
2026
+ // 'sub_1a2b3c4d5e6f'
2027
+
2028
+ const url = generateWebhookUrl(publicUrl, subscriptionId);
2029
+ // 'https://mcp.example.com/webhooks/incoming/sub_1a2b3c4d5e6f'
2030
+ ```
2031
+
2032
+ **Signature Utilities:**
2033
+
2034
+ ```typescript
2035
+ import { createHmacSignature, verifyHmacSignature } from 'mcp-http-webhook/utils';
2036
+
2037
+ const signature = createHmacSignature(payload, secret, 'sha256');
2038
+ const isValid = verifyHmacSignature(payload, signature, secret, 'sha256');
2039
+ ```
2040
+
2041
+ **Reference:**
2042
+ - [Utility Functions Documentation](#)
2043
+ - [Helper Methods](#)
2044
+
2045
+ ---
2046
+
2047
+ ## 21. Examples Repository
2048
+
2049
+ ### 21.1 Complete Examples
2050
+
2051
+ - **GitHub Integration** - [github-mcp-server.ts](#)
2052
+ - Issue tracking, PR management, webhook subscriptions
2053
+
2054
+ - **Google Drive Integration** - [gdrive-mcp-server.ts](#)
2055
+ - File watching, change notifications via Drive API webhooks
2056
+
2057
+ - **Slack Integration** - [slack-mcp-server.ts](#)
2058
+ - Channel messages, event subscriptions, slash commands
2059
+
2060
+ - **PostgreSQL Changes** - [postgres-mcp-server.ts](#)
2061
+ - LISTEN/NOTIFY based resource subscriptions
2062
+
2063
+ - **Stripe Webhooks** - [stripe-mcp-server.ts](#)
2064
+ - Payment events, subscription changes
2065
+
2066
+ - **Shopify Integration** - [shopify-mcp-server.ts](#)
2067
+ - Product updates, order notifications
2068
+
2069
+ ### 21.2 Store Implementations
2070
+
2071
+ - **Redis** - [redis-store.ts](#)
2072
+ - **DynamoDB** - [dynamodb-store.ts](#)
2073
+ - **PostgreSQL** - [postgres-store.ts](#)
2074
+ - **MongoDB** - [mongodb-store.ts](#)
2075
+ - **In-Memory (Dev)** - [memory-store.ts](#)
2076
+
2077
+ ### 21.3 Authentication Examples
2078
+
2079
+ - **JWT Bearer Token** - [jwt-auth.ts](#)
2080
+ - **API Key** - [apikey-auth.ts](#)
2081
+ - **OAuth2** - [oauth2-auth.ts](#)
2082
+ - **Mutual TLS** - [mtls-auth.ts](#)
2083
+
2084
+ **Reference:**
2085
+ - [Examples Repository](https://github.com/your-org/mcp-http-webhook/tree/main/examples)
2086
+
2087
+ ---
2088
+
2089
+ ## 22. Performance Optimization
2090
+
2091
+ ### 22.1 Connection Pooling
2092
+
2093
+ **Redis Connection Pool:**
2094
+
2095
+ ```typescript
2096
+ import Redis from 'ioredis';
2097
+
2098
+ const redis = new Redis({
2099
+ host: process.env.REDIS_HOST,
2100
+ port: parseInt(process.env.REDIS_PORT || '6379'),
2101
+ maxRetriesPerRequest: 3,
2102
+ enableReadyCheck: true,
2103
+ lazyConnect: false,
2104
+
2105
+ // Connection pool settings
2106
+ connectionName: 'mcp-server',
2107
+ connectTimeout: 10000,
2108
+
2109
+ // Reconnection strategy
2110
+ retryStrategy: (times) => {
2111
+ const delay = Math.min(times * 50, 2000);
2112
+ return delay;
2113
+ }
2114
+ });
2115
+ ```
2116
+
2117
+ **HTTP Client Pool:**
2118
+
2119
+ ```typescript
2120
+ import { Agent } from 'https';
2121
+
2122
+ const httpsAgent = new Agent({
2123
+ keepAlive: true,
2124
+ maxSockets: 50,
2125
+ maxFreeSockets: 10,
2126
+ timeout: 60000,
2127
+ keepAliveMsecs: 30000
2128
+ });
2129
+
2130
+ // Use with fetch or axios
2131
+ fetch(url, { agent: httpsAgent });
2132
+ ```
2133
+
2134
+ ### 22.2 Caching Strategies
2135
+
2136
+ **Multi-Level Caching:**
2137
+
2138
+ ```typescript
2139
+ import NodeCache from 'node-cache';
2140
+
2141
+ const memoryCache = new NodeCache({ stdTTL: 60, checkperiod: 120 });
2142
+
2143
+ const cachedRead = async (uri: string, context: AuthContext) => {
2144
+ // L1: Memory cache (fastest)
2145
+ const memoryCached = memoryCache.get(uri);
2146
+ if (memoryCached) return memoryCached;
2147
+
2148
+ // L2: Redis cache
2149
+ const redisCached = await redis.get(`cache:${uri}`);
2150
+ if (redisCached) {
2151
+ const data = JSON.parse(redisCached);
2152
+ memoryCache.set(uri, data);
2153
+ return data;
2154
+ }
2155
+
2156
+ // L3: Fetch from source
2157
+ const data = await fetchFromSource(uri, context);
2158
+
2159
+ // Update caches
2160
+ memoryCache.set(uri, data);
2161
+ await redis.setex(`cache:${uri}`, 300, JSON.stringify(data));
2162
+
2163
+ return data;
2164
+ };
2165
+ ```
2166
+
2167
+ ### 22.3 Batch Processing
2168
+
2169
+ **Webhook Batching:**
2170
+
2171
+ ```typescript
2172
+ class WebhookBatcher {
2173
+ private queue: Map<string, any[]> = new Map();
2174
+ private timer: NodeJS.Timeout | null = null;
2175
+
2176
+ add(clientUrl: string, notification: any) {
2177
+ if (!this.queue.has(clientUrl)) {
2178
+ this.queue.set(clientUrl, []);
2179
+ }
2180
+ this.queue.get(clientUrl)!.push(notification);
2181
+
2182
+ if (!this.timer) {
2183
+ this.timer = setTimeout(() => this.flush(), 1000);
2184
+ }
2185
+ }
2186
+
2187
+ async flush() {
2188
+ const batches = Array.from(this.queue.entries());
2189
+ this.queue.clear();
2190
+ this.timer = null;
2191
+
2192
+ await Promise.all(
2193
+ batches.map(([url, notifications]) =>
2194
+ this.sendBatch(url, notifications)
2195
+ )
2196
+ );
2197
+ }
2198
+
2199
+ async sendBatch(url: string, notifications: any[]) {
2200
+ // Send all notifications in one request
2201
+ await fetch(url, {
2202
+ method: 'POST',
2203
+ headers: { 'Content-Type': 'application/json' },
2204
+ body: JSON.stringify({ batch: notifications })
2205
+ });
2206
+ }
2207
+ }
2208
+ ```
2209
+
2210
+ **Reference:**
2211
+ - [Performance Tuning Guide](#)
2212
+ - [Benchmarking Tools](#)
2213
+ - [Optimization Examples](#)
2214
+
2215
+ ---
2216
+
2217
+ ## 23. Contributing
2218
+
2219
+ ### 23.1 Development Setup
2220
+
2221
+ ```bash
2222
+ # Clone repository
2223
+ git clone https://github.com/your-org/mcp-http-webhook.git
2224
+ cd mcp-http-webhook
2225
+
2226
+ # Install dependencies
2227
+ pnpm install
2228
+
2229
+ # Run tests
2230
+ pnpm test
2231
+
2232
+ # Start development server
2233
+ pnpm dev
2234
+
2235
+ # Build
2236
+ pnpm build
2237
+ ```
2238
+
2239
+ ### 23.2 Project Structure
2240
+
2241
+ ```
2242
+ mcp-http-webhook/
2243
+ ├── src/
2244
+ │ ├── index.ts # Main export
2245
+ │ ├── server.ts # Server creation
2246
+ │ ├── protocol/ # MCP protocol implementation
2247
+ │ ├── webhooks/ # Webhook handling
2248
+ │ ├── subscriptions/ # Subscription management
2249
+ │ ├── utils/ # Utility functions
2250
+ │ └── types/ # TypeScript types
2251
+ ├── examples/ # Example implementations
2252
+ ├── tests/ # Test suites
2253
+ ├── docs/ # Documentation
2254
+ └── scripts/ # Build and utility scripts
2255
+ ```
2256
+
2257
+ ### 23.3 Coding Standards
2258
+
2259
+ **TypeScript Guidelines:**
2260
+
2261
+ ```typescript
2262
+ // Use explicit types for public APIs
2263
+ export interface ToolDefinition<TInput = any, TOutput = any> {
2264
+ name: string;
2265
+ description: string;
2266
+ inputSchema: JSONSchema;
2267
+ handler: ToolHandler<TInput, TOutput>;
2268
+ }
2269
+
2270
+ // Use async/await over promises
2271
+ async function fetchData(): Promise<Data> {
2272
+ return await api.get('/data');
2273
+ }
2274
+
2275
+ // Prefer const over let
2276
+ const config = getConfig();
2277
+
2278
+ // Use optional chaining and nullish coalescing
2279
+ const value = config?.webhooks?.timeout ?? 5000;
2280
+
2281
+ // Document public APIs with JSDoc
2282
+ /**
2283
+ * Creates a new MCP server instance
2284
+ * @param config - Server configuration
2285
+ * @returns Configured MCP server
2286
+ * @throws {ValidationError} If configuration is invalid
2287
+ */
2288
+ export function createMCPServer(config: MCPServerConfig): MCPServer {
2289
+ // ...
2290
+ }
2291
+ ```
2292
+
2293
+ **Code Style:**
2294
+
2295
+ ```typescript
2296
+ // Naming conventions
2297
+ class SubscriptionManager { } // PascalCase for classes
2298
+ function handleWebhook() { } // camelCase for functions
2299
+ const MAX_RETRIES = 3; // UPPER_CASE for constants
2300
+ interface WebhookConfig { } // PascalCase for interfaces
2301
+
2302
+ // Import ordering
2303
+ import { external } from 'external'; // External packages
2304
+ import { internal } from './internal'; // Internal modules
2305
+ import type { Type } from './types'; // Type imports last
2306
+
2307
+ // Error handling
2308
+ try {
2309
+ await riskyOperation();
2310
+ } catch (error) {
2311
+ logger.error('Operation failed', { error, context });
2312
+ throw new OperationError('Failed to complete', { cause: error });
2313
+ }
2314
+ ```
2315
+
2316
+ ### 23.4 Testing Requirements
2317
+
2318
+ **Test Coverage Goals:**
2319
+ - Unit tests: >80% coverage
2320
+ - Integration tests for all major flows
2321
+ - E2E tests for critical paths
2322
+
2323
+ **Test Structure:**
2324
+
2325
+ ```typescript
2326
+ describe('SubscriptionManager', () => {
2327
+ let manager: SubscriptionManager;
2328
+ let mockStore: jest.Mocked<KeyValueStore>;
2329
+
2330
+ beforeEach(() => {
2331
+ mockStore = {
2332
+ get: jest.fn(),
2333
+ set: jest.fn(),
2334
+ delete: jest.fn()
2335
+ };
2336
+ manager = new SubscriptionManager(mockStore);
2337
+ });
2338
+
2339
+ describe('createSubscription', () => {
2340
+ it('should create a new subscription', async () => {
2341
+ const result = await manager.createSubscription({
2342
+ uri: 'test://resource',
2343
+ clientCallbackUrl: 'http://client.test/webhook'
2344
+ });
2345
+
2346
+ expect(result.subscriptionId).toBeDefined();
2347
+ expect(mockStore.set).toHaveBeenCalled();
2348
+ });
2349
+
2350
+ it('should throw error for invalid URI', async () => {
2351
+ await expect(
2352
+ manager.createSubscription({
2353
+ uri: 'invalid',
2354
+ clientCallbackUrl: 'http://client.test/webhook'
2355
+ })
2356
+ ).rejects.toThrow(ValidationError);
2357
+ });
2358
+ });
2359
+ });
2360
+ ```
2361
+
2362
+ ### 23.5 Pull Request Process
2363
+
2364
+ 1. **Fork and Branch**
2365
+ ```bash
2366
+ git checkout -b feature/my-new-feature
2367
+ ```
2368
+
2369
+ 2. **Make Changes**
2370
+ - Write code following style guide
2371
+ - Add/update tests
2372
+ - Update documentation
2373
+
2374
+ 3. **Run Quality Checks**
2375
+ ```bash
2376
+ pnpm lint # ESLint
2377
+ pnpm format # Prettier
2378
+ pnpm test # Jest tests
2379
+ pnpm type-check # TypeScript
2380
+ ```
2381
+
2382
+ 4. **Commit with Conventional Commits**
2383
+ ```bash
2384
+ git commit -m "feat: add webhook retry backoff configuration"
2385
+ git commit -m "fix: resolve subscription cleanup race condition"
2386
+ git commit -m "docs: update subscription flow diagram"
2387
+ ```
2388
+
2389
+ 5. **Submit PR**
2390
+ - Clear description of changes
2391
+ - Link to related issues
2392
+ - Include screenshots/examples if applicable
2393
+
2394
+ **Reference:**
2395
+ - [Contributing Guide](https://github.com/your-org/mcp-http-webhook/blob/main/CONTRIBUTING.md)
2396
+ - [Code of Conduct](https://github.com/your-org/mcp-http-webhook/blob/main/CODE_OF_CONDUCT.md)
2397
+
2398
+ ---
2399
+
2400
+ ## 24. Changelog
2401
+
2402
+ ### Version 1.0.0 (2025-01-15)
2403
+
2404
+ **Initial Release**
2405
+
2406
+ **Features:**
2407
+ - ✨ HTTP-based MCP server implementation
2408
+ - ✨ Webhook-based resource subscriptions
2409
+ - ✨ Key-value store abstraction for persistence
2410
+ - ✨ Automatic webhook routing and retry logic
2411
+ - ✨ Signature verification for incoming/outgoing webhooks
2412
+ - ✨ Support for tools, resources, and prompts
2413
+ - ✨ Built-in authentication middleware
2414
+ - ✨ Prometheus metrics integration
2415
+ - ✨ OpenTelemetry tracing support
2416
+ - ✨ Dead letter queue for failed webhooks
2417
+
2418
+ **Storage Implementations:**
2419
+ - 📦 Redis adapter
2420
+ - 📦 DynamoDB adapter
2421
+ - 📦 PostgreSQL adapter
2422
+ - 📦 In-memory adapter (development)
2423
+
2424
+ **Examples:**
2425
+ - 📚 GitHub integration
2426
+ - 📚 Google Drive integration
2427
+ - 📚 Slack integration
2428
+ - 📚 PostgreSQL NOTIFY integration
2429
+ - 📚 Stripe webhooks
2430
+
2431
+ **Documentation:**
2432
+ - 📖 Complete API reference
2433
+ - 📖 Deployment guides (Docker, Kubernetes)
2434
+ - 📖 Security best practices
2435
+ - 📖 Performance optimization guide
2436
+ - 📖 Migration guide from standard MCP
2437
+
2438
+ ---
2439
+
2440
+ ## 25. Roadmap
2441
+
2442
+ ### Version 1.1.0 (Q2 2025)
2443
+
2444
+ **Planned Features:**
2445
+ - 🚀 GraphQL subscription support
2446
+ - 🚀 WebSocket fallback transport
2447
+ - 🚀 Built-in rate limiting per user
2448
+ - 🚀 Subscription priority levels
2449
+ - 🚀 Webhook payload transformation
2450
+ - 🚀 Multi-region deployment support
2451
+ - 🚀 Admin dashboard UI
2452
+
2453
+ ### Version 1.2.0 (Q3 2025)
2454
+
2455
+ **Planned Features:**
2456
+ - 🚀 gRPC transport option
2457
+ - 🚀 Event sourcing support
2458
+ - 🚀 Built-in circuit breaker
2459
+ - 🚀 A/B testing framework
2460
+ - 🚀 Subscription groups/topics
2461
+ - 🚀 Webhook replay functionality
2462
+
2463
+ ### Future Considerations
2464
+ - Cloud provider integrations (AWS EventBridge, Azure Event Grid)
2465
+ - Machine learning-based anomaly detection
2466
+ - Advanced analytics and insights
2467
+ - Multi-tenancy support
2468
+ - Federation across multiple MCP servers
2469
+
2470
+ **Feature Requests:**
2471
+ Submit feature requests at [GitHub Issues](https://github.com/your-org/mcp-http-webhook/issues)
2472
+
2473
+ ---
2474
+
2475
+ ## 26. FAQ
2476
+
2477
+ ### General Questions
2478
+
2479
+ **Q: How is this different from standard MCP with SSE transport?**
2480
+
2481
+ A: This library eliminates persistent connections entirely. Instead of maintaining an SSE connection for push notifications, clients provide a webhook URL and receive notifications via HTTP POST. This makes horizontal scaling trivial and works better with serverless architectures.
2482
+
2483
+ **Q: Can I use this with the standard MCP clients?**
2484
+
2485
+ A: No, standard MCP clients expect SSE transport. You'll need to implement a client that works with HTTP + webhooks. However, the protocol messages (tools/call, resources/read, etc.) remain the same.
2486
+
2487
+ **Q: Why do I need an external key-value store?**
2488
+
2489
+ A: Subscription state must be shared across multiple server instances. An external store (Redis, DynamoDB, etc.) enables horizontal scaling and prevents data loss during deployments.
2490
+
2491
+ ### Subscription Questions
2492
+
2493
+ **Q: What happens if a client's webhook endpoint is down?**
2494
+
2495
+ A: The library automatically retries with exponential backoff (configurable). After all retries fail, the notification is stored in the dead letter queue for manual intervention.
2496
+
2497
+ **Q: Can one resource have multiple subscriptions from the same client?**
2498
+
2499
+ A: Yes, each subscription gets a unique `subscriptionId`. A client can subscribe to the same resource multiple times with different callback URLs.
2500
+
2501
+ **Q: How do I test subscriptions locally without exposing a public URL?**
2502
+
2503
+ A: Use tools like [ngrok](https://ngrok.com/) or [localhost.run](https://localhost.run/) to create temporary public URLs that tunnel to your local machine.
2504
+
2505
+ ```bash
2506
+ ngrok http 3000
2507
+ # Use the generated URL as your publicUrl
2508
+ ```
2509
+
2510
+ ### Third-Party Integration Questions
2511
+
2512
+ **Q: What if the third-party service doesn't support webhooks?**
2513
+
2514
+ A: You can implement polling-based subscriptions:
2515
+
2516
+ ```typescript
2517
+ subscription: {
2518
+ onSubscribe: async (uri, subscriptionId, webhookUrl, context) => {
2519
+ // Start a polling job
2520
+ const jobId = await queue.add('poll-resource', {
2521
+ uri,
2522
+ subscriptionId,
2523
+ interval: 60000 // Poll every minute
2524
+ });
2525
+
2526
+ return { thirdPartyWebhookId: jobId };
2527
+ },
2528
+
2529
+ // In your polling worker:
2530
+ // - Fetch resource data
2531
+ // - Compare with previous state
2532
+ // - If changed, POST to webhookUrl
2533
+ }
2534
+ ```
2535
+
2536
+ **Q: How do I handle webhook signature verification for different third-party services?**
2537
+
2538
+ A: Each resource can implement custom verification logic:
2539
+
2540
+ ```typescript
2541
+ onWebhook: async (subscriptionId, payload, headers) => {
2542
+ // GitHub uses X-Hub-Signature-256
2543
+ if (headers['x-hub-signature-256']) {
2544
+ verifyGitHubSignature(payload, headers['x-hub-signature-256'], secret);
2545
+ }
2546
+
2547
+ // Slack uses X-Slack-Signature
2548
+ if (headers['x-slack-signature']) {
2549
+ verifySlackSignature(payload, headers['x-slack-signature'], headers['x-slack-request-timestamp'], secret);
2550
+ }
2551
+
2552
+ // Process webhook...
2553
+ }
2554
+ ```
2555
+
2556
+ ### Performance Questions
2557
+
2558
+ **Q: How many subscriptions can the server handle?**
2559
+
2560
+ A: This depends on your key-value store. Redis can easily handle millions of subscriptions. The bottleneck is typically outgoing webhook calls, which can be optimized with batching and connection pooling.
2561
+
2562
+ **Q: What's the latency for webhook notifications?**
2563
+
2564
+ A: Typically <500ms from third-party webhook receipt to client notification, depending on network conditions and your webhook processing logic.
2565
+
2566
+ **Q: Should I batch webhook notifications?**
2567
+
2568
+ A: Yes, if you expect high-frequency updates. The library supports batching multiple notifications into a single HTTP call to clients.
2569
+
2570
+ ### Security Questions
2571
+
2572
+ **Q: How do I prevent replay attacks on webhooks?**
2573
+
2574
+ A: Implement timestamp verification:
2575
+
2576
+ ```typescript
2577
+ verifyIncomingSignature: (payload, signature, secret) => {
2578
+ const timestamp = headers['x-webhook-timestamp'];
2579
+ const now = Date.now() / 1000;
2580
+
2581
+ // Reject webhooks older than 5 minutes
2582
+ if (Math.abs(now - parseInt(timestamp)) > 300) {
2583
+ return false;
2584
+ }
2585
+
2586
+ // Verify signature including timestamp
2587
+ return verifySignature(payload, signature, secret);
2588
+ }
2589
+ ```
2590
+
2591
+ **Q: Should I whitelist client callback URLs?**
2592
+
2593
+ A: Yes, for security:
2594
+
2595
+ ```typescript
2596
+ const ALLOWED_DOMAINS = ['client.example.com', 'app.client.com'];
2597
+
2598
+ authenticate: async (req) => {
2599
+ const callbackUrl = req.body.params?.callbackUrl;
2600
+ if (callbackUrl) {
2601
+ const domain = new URL(callbackUrl).hostname;
2602
+ if (!ALLOWED_DOMAINS.includes(domain)) {
2603
+ throw new ValidationError('Callback URL not whitelisted');
2604
+ }
2605
+ }
2606
+ // ... continue authentication
2607
+ }
2608
+ ```
2609
+
2610
+ ### Deployment Questions
2611
+
2612
+ **Q: Can I run this serverlessly (AWS Lambda, Cloud Functions)?**
2613
+
2614
+ A: Yes, but with caveats:
2615
+ - Tools and resources work perfectly (stateless HTTP)
2616
+ - Incoming webhooks work fine
2617
+ - Outgoing webhook calls need to handle cold starts
2618
+ - Consider using a separate always-on service for webhook processing
2619
+
2620
+ **Q: How do I handle zero-downtime deployments?**
2621
+
2622
+ A:
2623
+ 1. Use external store (Redis/DynamoDB) for state
2624
+ 2. Deploy new version alongside old
2625
+ 3. Update load balancer to point to new version
2626
+ 4. Old version continues processing in-flight webhooks
2627
+ 5. Gracefully shutdown old version after drain period
2628
+
2629
+ ```typescript
2630
+ process.on('SIGTERM', async () => {
2631
+ console.log('Received SIGTERM, starting graceful shutdown');
2632
+
2633
+ // Stop accepting new connections
2634
+ server.close();
2635
+
2636
+ // Wait for in-flight requests to complete
2637
+ await waitForInFlightRequests();
2638
+
2639
+ // Close store connections
2640
+ await redis.quit();
2641
+
2642
+ process.exit(0);
2643
+ });
2644
+ ```
2645
+
2646
+ ---
2647
+
2648
+ ## 27. Resources & Links
2649
+
2650
+ ### Official Documentation
2651
+ - [MCP Specification](https://spec.modelcontextprotocol.io/)
2652
+ - [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
2653
+ - [Library GitHub Repository](https://github.com/your-org/mcp-http-webhook)
2654
+ - [API Documentation](https://docs.mcp-http-webhook.dev/api)
2655
+
2656
+ ### Community
2657
+ - [Discord Server](https://discord.gg/mcp-webhook)
2658
+ - [GitHub Discussions](https://github.com/your-org/mcp-http-webhook/discussions)
2659
+ - [Stack Overflow Tag: mcp-http-webhook](https://stackoverflow.com/questions/tagged/mcp-http-webhook)
2660
+
2661
+ ### Examples & Tutorials
2662
+ - [Complete Examples Repository](https://github.com/your-org/mcp-http-webhook/tree/main/examples)
2663
+ - [Video Tutorials](https://youtube.com/playlist?list=xxx)
2664
+ - [Blog: Building Your First MCP Server](https://blog.example.com/first-mcp-server)
2665
+ - [Blog: Scaling MCP to 1M Subscriptions](https://blog.example.com/scaling-mcp)
2666
+
2667
+ ### Related Projects
2668
+ - [MCP Client Libraries](https://github.com/modelcontextprotocol)
2669
+ - [MCP Inspector](https://github.com/modelcontextprotocol/inspector)
2670
+ - [MCP CLI Tools](https://github.com/modelcontextprotocol/cli)
2671
+
2672
+ ### Third-Party Integrations
2673
+ - [GitHub Webhooks Documentation](https://docs.github.com/en/webhooks)
2674
+ - [Google Drive Push Notifications](https://developers.google.com/drive/api/guides/push)
2675
+ - [Slack Events API](https://api.slack.com/apis/connections/events-api)
2676
+ - [Stripe Webhooks](https://stripe.com/docs/webhooks)
2677
+ - [PostgreSQL NOTIFY](https://www.postgresql.org/docs/current/sql-notify.html)
2678
+
2679
+ ---
2680
+
2681
+ ## 28. License
2682
+
2683
+ **MIT License**
2684
+
2685
+ Copyright (c) 2025 Your Organization
2686
+
2687
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
2688
+
2689
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
2690
+
2691
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2692
+
2693
+ ---
2694
+
2695
+ ## 29. Support
2696
+
2697
+ ### Commercial Support
2698
+
2699
+ For enterprise support, custom integrations, and consulting services:
2700
+ - Email: support@mcp-http-webhook.dev
2701
+ - Enterprise Plans: [View Pricing](https://mcp-http-webhook.dev/pricing)
2702
+
2703
+ ### Community Support
2704
+
2705
+ - **GitHub Issues**: Bug reports and feature requests
2706
+ - **Discussions**: Questions and community help
2707
+ - **Discord**: Real-time chat with maintainers and community
2708
+ - **Stack Overflow**: Tag your questions with `mcp-http-webhook`
2709
+
2710
+ ### Bug Reports
2711
+
2712
+ When filing a bug report, please include:
2713
+
2714
+ ```markdown
2715
+ **Environment:**
2716
+ - Library version: 1.0.0
2717
+ - Node.js version: 20.x
2718
+ - Store implementation: Redis 7.x
2719
+ - Deployment: Kubernetes / Docker / Bare metal
2720
+
2721
+ **Expected Behavior:**
2722
+ [What you expected to happen]
2723
+
2724
+ **Actual Behavior:**
2725
+ [What actually happened]
2726
+
2727
+ **Reproduction Steps:**
2728
+ 1. Create server with config...
2729
+ 2. Subscribe to resource...
2730
+ 3. Send webhook...
2731
+
2732
+ **Logs:**
2733
+ ```
2734
+ [Relevant log output]
2735
+ ```
2736
+
2737
+ **Additional Context:**
2738
+ [Any other relevant information]
2739
+ ```
2740
+
2741
+ ---
2742
+
2743
+ ## 30. Acknowledgments
2744
+
2745
+ This library builds upon the excellent work of:
2746
+
2747
+ - **Anthropic** - For creating the Model Context Protocol specification
2748
+ - **MCP Community** - For feedback and contributions
2749
+ - **Third-Party Service Providers** - For comprehensive webhook documentation
2750
+ - **Open Source Contributors** - For example implementations and testing
2751
+
2752
+ Special thanks to all contributors who have helped make this library possible.
2753
+
2754
+ ---
2755
+
2756
+ ## Appendix A: Complete Type Definitions
2757
+
2758
+ See [Type Reference Documentation](https://docs.mcp-http-webhook.dev/types) for complete TypeScript type definitions.
2759
+
2760
+ ## Appendix B: HTTP API Specification
2761
+
2762
+ See [HTTP API Reference](https://docs.mcp-http-webhook.dev/http-api) for detailed endpoint specifications, request/response formats, and error codes.
2763
+
2764
+ ## Appendix C: Storage Migration Guide
2765
+
2766
+ See [Storage Migration Guide](https://docs.mcp-http-webhook.dev/storage-migration) for instructions on migrating between different key-value store implementations.
2767
+
2768
+ ## Appendix D: Performance Benchmarks
2769
+
2770
+ See [Benchmark Results](https://docs.mcp-http-webhook.dev/benchmarks) for performance comparisons across different configurations and deployment scenarios.
2771
+
2772
+ ---
2773
+
2774
+ **Document Version:** 1.0.0
2775
+ **Last Updated:** 2025-01-15
2776
+ **Maintained By:** MCP HTTP Webhook Team
2777
+
2778
+ For the latest version of this document, visit: [https://docs.mcp-http-webhook.dev/specification](https://docs.mcp-http-webhook.dev/specification)