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.
- package/.eslintrc.json +16 -0
- package/.prettierrc.json +8 -0
- package/ARCHITECTURE.md +269 -0
- package/CONTRIBUTING.md +136 -0
- package/GETTING_STARTED.md +310 -0
- package/IMPLEMENTATION.md +294 -0
- package/LICENSE +21 -0
- package/MIGRATION_TO_SDK.md +263 -0
- package/README.md +496 -0
- package/SDK_INTEGRATION_COMPLETE.md +300 -0
- package/STANDARD_SUBSCRIPTIONS.md +268 -0
- package/STANDARD_SUBSCRIPTIONS_COMPLETE.md +309 -0
- package/SUMMARY.md +272 -0
- package/Spec.md +2778 -0
- package/dist/errors/index.d.ts +52 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +81 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/ProtocolHandler.d.ts +37 -0
- package/dist/protocol/ProtocolHandler.d.ts.map +1 -0
- package/dist/protocol/ProtocolHandler.js +172 -0
- package/dist/protocol/ProtocolHandler.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +502 -0
- package/dist/server.js.map +1 -0
- package/dist/stores/InMemoryStore.d.ts +27 -0
- package/dist/stores/InMemoryStore.d.ts.map +1 -0
- package/dist/stores/InMemoryStore.js +73 -0
- package/dist/stores/InMemoryStore.js.map +1 -0
- package/dist/stores/RedisStore.d.ts +18 -0
- package/dist/stores/RedisStore.d.ts.map +1 -0
- package/dist/stores/RedisStore.js +45 -0
- package/dist/stores/RedisStore.js.map +1 -0
- package/dist/stores/index.d.ts +3 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +9 -0
- package/dist/stores/index.js.map +1 -0
- package/dist/subscriptions/SubscriptionManager.d.ts +49 -0
- package/dist/subscriptions/SubscriptionManager.d.ts.map +1 -0
- package/dist/subscriptions/SubscriptionManager.js +181 -0
- package/dist/subscriptions/SubscriptionManager.js.map +1 -0
- package/dist/types/index.d.ts +271 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +51 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +154 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/webhooks/WebhookManager.d.ts +27 -0
- package/dist/webhooks/WebhookManager.d.ts.map +1 -0
- package/dist/webhooks/WebhookManager.js +174 -0
- package/dist/webhooks/WebhookManager.js.map +1 -0
- package/examples/GITHUB_LIVE_EXAMPLE.md +308 -0
- package/examples/GITHUB_LIVE_SETUP.md +253 -0
- package/examples/QUICKSTART.md +130 -0
- package/examples/basic-setup.ts +142 -0
- package/examples/github-server-live.ts +690 -0
- package/examples/github-server.ts +223 -0
- package/examples/google-drive-server-live.ts +773 -0
- package/examples/start-github-live.sh +53 -0
- package/jest.config.js +20 -0
- package/package.json +58 -0
- package/src/errors/index.ts +81 -0
- package/src/index.ts +19 -0
- package/src/server.ts +595 -0
- package/src/stores/InMemoryStore.ts +87 -0
- package/src/stores/RedisStore.ts +51 -0
- package/src/stores/index.ts +2 -0
- package/src/subscriptions/SubscriptionManager.ts +240 -0
- package/src/types/index.ts +341 -0
- package/src/utils/index.ts +156 -0
- package/src/webhooks/WebhookManager.ts +230 -0
- package/test-sdk-integration.sh +157 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { KeyValueStore } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Redis store implementation
|
|
5
|
+
* Requires ioredis package
|
|
6
|
+
*/
|
|
7
|
+
export class RedisStore implements KeyValueStore {
|
|
8
|
+
constructor(private redis: any) {} // ioredis instance
|
|
9
|
+
|
|
10
|
+
async get(key: string): Promise<string | null> {
|
|
11
|
+
return await this.redis.get(key);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async set(key: string, value: string, ttl?: number): Promise<void> {
|
|
15
|
+
if (ttl) {
|
|
16
|
+
await this.redis.setex(key, ttl, value);
|
|
17
|
+
} else {
|
|
18
|
+
await this.redis.set(key, value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async delete(key: string): Promise<void> {
|
|
23
|
+
await this.redis.del(key);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async scan(pattern: string): Promise<string[]> {
|
|
27
|
+
const keys: string[] = [];
|
|
28
|
+
let cursor = '0';
|
|
29
|
+
|
|
30
|
+
do {
|
|
31
|
+
const [newCursor, matches] = await this.redis.scan(
|
|
32
|
+
cursor,
|
|
33
|
+
'MATCH',
|
|
34
|
+
pattern,
|
|
35
|
+
'COUNT',
|
|
36
|
+
100
|
|
37
|
+
);
|
|
38
|
+
cursor = newCursor;
|
|
39
|
+
keys.push(...matches);
|
|
40
|
+
} while (cursor !== '0');
|
|
41
|
+
|
|
42
|
+
return keys;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create Redis store from connection URL
|
|
48
|
+
*/
|
|
49
|
+
export function createRedisStore(redis: any): KeyValueStore {
|
|
50
|
+
return new RedisStore(redis);
|
|
51
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { StorageError, ValidationError } from '../errors';
|
|
2
|
+
import {
|
|
3
|
+
AuthContext,
|
|
4
|
+
KeyValueStore,
|
|
5
|
+
Logger,
|
|
6
|
+
ResourceDefinition,
|
|
7
|
+
StoredSubscription
|
|
8
|
+
} from '../types';
|
|
9
|
+
import { generateSubscriptionId, generateWebhookUrl } from '../utils';
|
|
10
|
+
|
|
11
|
+
export class SubscriptionManager {
|
|
12
|
+
constructor(
|
|
13
|
+
private store: KeyValueStore,
|
|
14
|
+
private resources: ResourceDefinition[],
|
|
15
|
+
private publicUrl: string,
|
|
16
|
+
private logger?: Logger
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a new subscription
|
|
21
|
+
*/
|
|
22
|
+
async createSubscription(params: {
|
|
23
|
+
uri: string;
|
|
24
|
+
clientCallbackUrl: string;
|
|
25
|
+
clientCallbackSecret?: string;
|
|
26
|
+
context: AuthContext;
|
|
27
|
+
}): Promise<{ subscriptionId: string; status: string }> {
|
|
28
|
+
const { uri, clientCallbackUrl, clientCallbackSecret, context } = params;
|
|
29
|
+
|
|
30
|
+
// Find resource definition
|
|
31
|
+
const resource = this.findResourceForUri(uri);
|
|
32
|
+
if (!resource) {
|
|
33
|
+
throw new ValidationError(`No resource found for URI: ${uri}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!resource.subscription) {
|
|
37
|
+
throw new ValidationError(`Resource ${resource.name} does not support subscriptions`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Generate subscription ID
|
|
41
|
+
const subscriptionId = generateSubscriptionId();
|
|
42
|
+
const thirdPartyWebhookUrl = generateWebhookUrl(this.publicUrl, subscriptionId);
|
|
43
|
+
|
|
44
|
+
this.logger?.info('Creating subscription', {
|
|
45
|
+
subscriptionId,
|
|
46
|
+
uri,
|
|
47
|
+
clientCallbackUrl,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Call resource's onSubscribe handler
|
|
52
|
+
const metadata = await resource.subscription.onSubscribe(
|
|
53
|
+
uri,
|
|
54
|
+
subscriptionId,
|
|
55
|
+
thirdPartyWebhookUrl,
|
|
56
|
+
context
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Store subscription data
|
|
60
|
+
const subscriptionData: StoredSubscription = {
|
|
61
|
+
uri,
|
|
62
|
+
resourceType: resource.name,
|
|
63
|
+
clientCallbackUrl,
|
|
64
|
+
clientCallbackSecret,
|
|
65
|
+
userId: context.userId,
|
|
66
|
+
thirdPartyWebhookId: metadata.thirdPartyWebhookId,
|
|
67
|
+
metadata: metadata.metadata,
|
|
68
|
+
createdAt: Date.now(),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
await this.storeSubscription(subscriptionId, subscriptionData, context.userId);
|
|
72
|
+
|
|
73
|
+
this.logger?.info('Subscription created', { subscriptionId });
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
subscriptionId,
|
|
77
|
+
status: 'active',
|
|
78
|
+
};
|
|
79
|
+
} catch (error) {
|
|
80
|
+
this.logger?.error('Failed to create subscription', {
|
|
81
|
+
subscriptionId,
|
|
82
|
+
error: error instanceof Error ? error.message : String(error),
|
|
83
|
+
});
|
|
84
|
+
throw new StorageError('Failed to create subscription', { cause: error });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Delete a subscription
|
|
90
|
+
*/
|
|
91
|
+
async deleteSubscription(subscriptionId: string, context: AuthContext): Promise<void> {
|
|
92
|
+
this.logger?.info('Deleting subscription', { subscriptionId });
|
|
93
|
+
|
|
94
|
+
// Load subscription
|
|
95
|
+
const subscription = await this.loadSubscription(subscriptionId);
|
|
96
|
+
if (!subscription) {
|
|
97
|
+
throw new ValidationError(`Subscription ${subscriptionId} not found`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Verify ownership
|
|
101
|
+
if (subscription.userId !== context.userId) {
|
|
102
|
+
throw new ValidationError('Unauthorized to delete this subscription');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Find resource
|
|
106
|
+
const resource = this.findResourceForUri(subscription.uri);
|
|
107
|
+
if (!resource?.subscription) {
|
|
108
|
+
throw new StorageError('Resource subscription configuration not found');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Call resource's onUnsubscribe handler
|
|
113
|
+
await resource.subscription.onUnsubscribe(
|
|
114
|
+
subscription.uri,
|
|
115
|
+
subscriptionId,
|
|
116
|
+
{
|
|
117
|
+
thirdPartyWebhookId: subscription.thirdPartyWebhookId,
|
|
118
|
+
metadata: subscription.metadata,
|
|
119
|
+
},
|
|
120
|
+
context
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Delete from store
|
|
124
|
+
await this.removeSubscription(subscriptionId, context.userId);
|
|
125
|
+
|
|
126
|
+
this.logger?.info('Subscription deleted', { subscriptionId });
|
|
127
|
+
} catch (error) {
|
|
128
|
+
this.logger?.error('Failed to delete subscription', {
|
|
129
|
+
subscriptionId,
|
|
130
|
+
error: error instanceof Error ? error.message : String(error),
|
|
131
|
+
});
|
|
132
|
+
throw new StorageError('Failed to delete subscription', { cause: error });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get subscription by ID
|
|
138
|
+
*/
|
|
139
|
+
async getSubscription(subscriptionId: string): Promise<StoredSubscription | null> {
|
|
140
|
+
return this.loadSubscription(subscriptionId);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* List subscriptions for a user
|
|
145
|
+
*/
|
|
146
|
+
async listSubscriptions(userId: string): Promise<StoredSubscription[]> {
|
|
147
|
+
const indexKey = `user:${userId}:subscriptions`;
|
|
148
|
+
const indexData = await this.store.get(indexKey);
|
|
149
|
+
|
|
150
|
+
if (!indexData) {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const subscriptionIds: string[] = JSON.parse(indexData);
|
|
155
|
+
|
|
156
|
+
const subscriptions = await Promise.all(
|
|
157
|
+
subscriptionIds.map((id) => this.loadSubscription(id))
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return subscriptions.filter((s): s is StoredSubscription => s !== null);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Store subscription data
|
|
165
|
+
*/
|
|
166
|
+
private async storeSubscription(
|
|
167
|
+
subscriptionId: string,
|
|
168
|
+
data: StoredSubscription,
|
|
169
|
+
userId: string
|
|
170
|
+
): Promise<void> {
|
|
171
|
+
const key = `subscription:${subscriptionId}`;
|
|
172
|
+
await this.store.set(key, JSON.stringify(data));
|
|
173
|
+
|
|
174
|
+
// Update user index
|
|
175
|
+
const indexKey = `user:${userId}:subscriptions`;
|
|
176
|
+
const existing = await this.store.get(indexKey);
|
|
177
|
+
const subscriptionIds: string[] = existing ? JSON.parse(existing) : [];
|
|
178
|
+
|
|
179
|
+
if (!subscriptionIds.includes(subscriptionId)) {
|
|
180
|
+
subscriptionIds.push(subscriptionId);
|
|
181
|
+
await this.store.set(indexKey, JSON.stringify(subscriptionIds));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Load subscription data
|
|
187
|
+
*/
|
|
188
|
+
private async loadSubscription(subscriptionId: string): Promise<StoredSubscription | null> {
|
|
189
|
+
const key = `subscription:${subscriptionId}`;
|
|
190
|
+
const data = await this.store.get(key);
|
|
191
|
+
|
|
192
|
+
if (!data) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return JSON.parse(data);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Remove subscription data
|
|
201
|
+
*/
|
|
202
|
+
private async removeSubscription(subscriptionId: string, userId: string): Promise<void> {
|
|
203
|
+
const key = `subscription:${subscriptionId}`;
|
|
204
|
+
await this.store.delete(key);
|
|
205
|
+
|
|
206
|
+
// Update user index
|
|
207
|
+
const indexKey = `user:${userId}:subscriptions`;
|
|
208
|
+
const existing = await this.store.get(indexKey);
|
|
209
|
+
|
|
210
|
+
if (existing) {
|
|
211
|
+
const subscriptionIds: string[] = JSON.parse(existing);
|
|
212
|
+
const filtered = subscriptionIds.filter((id) => id !== subscriptionId);
|
|
213
|
+
await this.store.set(indexKey, JSON.stringify(filtered));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Find resource definition for URI
|
|
219
|
+
*/
|
|
220
|
+
private findResourceForUri(uri: string): ResourceDefinition | undefined {
|
|
221
|
+
return this.resources.find((resource) => {
|
|
222
|
+
// Convert URI template to regex pattern
|
|
223
|
+
// Step 1: Replace template variables with a placeholder
|
|
224
|
+
const withPlaceholders = resource.uri.replace(/\{[^}]+\}/g, '__PLACEHOLDER__');
|
|
225
|
+
|
|
226
|
+
// Step 2: Escape special regex characters (but not our placeholders)
|
|
227
|
+
const escapedPattern = withPlaceholders.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
228
|
+
|
|
229
|
+
// Step 3: Replace placeholders with capture groups
|
|
230
|
+
const patternString = escapedPattern.replace(/__PLACEHOLDER__/g, '([^/]+)');
|
|
231
|
+
|
|
232
|
+
// Step 4: Create regex with anchors
|
|
233
|
+
const pattern = new RegExp('^' + patternString + '$');
|
|
234
|
+
|
|
235
|
+
console.log('Pattern for resource', resource.name, ':', pattern);
|
|
236
|
+
console.log('Testing URI:', uri);
|
|
237
|
+
return pattern.test(uri);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* JSON Schema definition for input validation
|
|
5
|
+
*/
|
|
6
|
+
export interface JSONSchema {
|
|
7
|
+
type: string;
|
|
8
|
+
properties?: Record<string, any>;
|
|
9
|
+
required?: string[];
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Authentication context passed to handlers
|
|
15
|
+
*/
|
|
16
|
+
export interface AuthContext {
|
|
17
|
+
userId: string;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Authentication handler function
|
|
23
|
+
*/
|
|
24
|
+
export type AuthenticateFunction = (req: Request) => Promise<AuthContext>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Logger interface
|
|
28
|
+
*/
|
|
29
|
+
export interface Logger {
|
|
30
|
+
debug(message: string, meta?: any): void;
|
|
31
|
+
info(message: string, meta?: any): void;
|
|
32
|
+
warn(message: string, meta?: any): void;
|
|
33
|
+
error(message: string, meta?: any): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Key-value store interface for persistence
|
|
38
|
+
*/
|
|
39
|
+
export interface KeyValueStore {
|
|
40
|
+
/**
|
|
41
|
+
* Get value by key
|
|
42
|
+
* @returns Value as string, or null if not found
|
|
43
|
+
*/
|
|
44
|
+
get(key: string): Promise<string | null>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Set value with optional TTL
|
|
48
|
+
* @param key - Key to set
|
|
49
|
+
* @param value - Value (will be JSON stringified)
|
|
50
|
+
* @param ttl - Time to live in seconds (optional)
|
|
51
|
+
*/
|
|
52
|
+
set(key: string, value: string, ttl?: number): Promise<void>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Delete key
|
|
56
|
+
*/
|
|
57
|
+
delete(key: string): Promise<void>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Scan keys by pattern (optional, for debugging)
|
|
61
|
+
* @param pattern - Glob pattern (e.g., "subscription:*")
|
|
62
|
+
*/
|
|
63
|
+
scan?(pattern: string): Promise<string[]>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Tool handler function
|
|
68
|
+
*/
|
|
69
|
+
export type ToolHandler<TInput = any, TOutput = any> = (
|
|
70
|
+
input: TInput,
|
|
71
|
+
context: AuthContext
|
|
72
|
+
) => Promise<TOutput>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Tool definition
|
|
76
|
+
*/
|
|
77
|
+
export interface ToolDefinition<TInput = any, TOutput = any> {
|
|
78
|
+
name: string;
|
|
79
|
+
description: string;
|
|
80
|
+
inputSchema: JSONSchema;
|
|
81
|
+
handler: ToolHandler<TInput, TOutput>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resource list item
|
|
86
|
+
*/
|
|
87
|
+
export interface ResourceListItem {
|
|
88
|
+
uri: string;
|
|
89
|
+
name: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
mimeType?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resource read result
|
|
96
|
+
*/
|
|
97
|
+
export interface ResourceReadResult<TData = any> {
|
|
98
|
+
contents: TData;
|
|
99
|
+
metadata?: Record<string, any>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Resource read options
|
|
104
|
+
*/
|
|
105
|
+
export interface ResourceReadOptions {
|
|
106
|
+
pagination?: {
|
|
107
|
+
page?: number;
|
|
108
|
+
limit?: number;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Subscription metadata returned by onSubscribe
|
|
114
|
+
*/
|
|
115
|
+
export interface SubscriptionMetadata {
|
|
116
|
+
thirdPartyWebhookId: string;
|
|
117
|
+
metadata?: Record<string, any>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Webhook change information
|
|
122
|
+
*/
|
|
123
|
+
export interface WebhookChangeInfo {
|
|
124
|
+
resourceUri: string;
|
|
125
|
+
changeType: 'created' | 'updated' | 'deleted';
|
|
126
|
+
data?: any;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Resource subscription handlers
|
|
131
|
+
*/
|
|
132
|
+
export interface ResourceSubscription {
|
|
133
|
+
/**
|
|
134
|
+
* Called when a client subscribes to a resource
|
|
135
|
+
*/
|
|
136
|
+
onSubscribe: (
|
|
137
|
+
uri: string,
|
|
138
|
+
subscriptionId: string,
|
|
139
|
+
thirdPartyWebhookUrl: string,
|
|
140
|
+
context: AuthContext
|
|
141
|
+
) => Promise<SubscriptionMetadata>;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Called when a client unsubscribes
|
|
145
|
+
*/
|
|
146
|
+
onUnsubscribe: (
|
|
147
|
+
uri: string,
|
|
148
|
+
subscriptionId: string,
|
|
149
|
+
storedData: SubscriptionMetadata,
|
|
150
|
+
context: AuthContext
|
|
151
|
+
) => Promise<void>;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Called when third-party webhook is received
|
|
155
|
+
*/
|
|
156
|
+
onWebhook: (
|
|
157
|
+
subscriptionId: string,
|
|
158
|
+
payload: any,
|
|
159
|
+
headers: Record<string, string>
|
|
160
|
+
) => Promise<WebhookChangeInfo | null>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Resource definition
|
|
165
|
+
*/
|
|
166
|
+
export interface ResourceDefinition<TData = any> {
|
|
167
|
+
uri: string;
|
|
168
|
+
name: string;
|
|
169
|
+
description: string;
|
|
170
|
+
mimeType?: string;
|
|
171
|
+
|
|
172
|
+
read: (
|
|
173
|
+
uri: string,
|
|
174
|
+
context: AuthContext,
|
|
175
|
+
options?: ResourceReadOptions
|
|
176
|
+
) => Promise<ResourceReadResult<TData>>;
|
|
177
|
+
|
|
178
|
+
list?: (context: AuthContext) => Promise<ResourceListItem[]>;
|
|
179
|
+
|
|
180
|
+
subscription?: ResourceSubscription;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Prompt definition
|
|
185
|
+
*/
|
|
186
|
+
export interface PromptDefinition {
|
|
187
|
+
name: string;
|
|
188
|
+
description: string;
|
|
189
|
+
arguments?: Array<{
|
|
190
|
+
name: string;
|
|
191
|
+
description: string;
|
|
192
|
+
required?: boolean;
|
|
193
|
+
}>;
|
|
194
|
+
handler: (args: Record<string, any>, context: AuthContext) => Promise<{
|
|
195
|
+
messages: Array<{
|
|
196
|
+
role: 'user' | 'assistant';
|
|
197
|
+
content: string;
|
|
198
|
+
}>;
|
|
199
|
+
}>;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Webhook configuration
|
|
204
|
+
*/
|
|
205
|
+
export interface WebhookConfig {
|
|
206
|
+
incomingPath?: string;
|
|
207
|
+
incomingSecret?: string;
|
|
208
|
+
verifyIncomingSignature?: (payload: any, signature: string, secret: string) => boolean;
|
|
209
|
+
|
|
210
|
+
outgoing?: {
|
|
211
|
+
timeout?: number;
|
|
212
|
+
retries?: number;
|
|
213
|
+
retryDelay?: number;
|
|
214
|
+
signPayload?: (payload: any, secret: string) => string;
|
|
215
|
+
onBeforeCall?: (url: string, payload: any) => void;
|
|
216
|
+
onAfterCall?: (url: string, response: any, error?: any) => void;
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Metrics configuration
|
|
222
|
+
*/
|
|
223
|
+
export interface MetricsConfig {
|
|
224
|
+
enabled: boolean;
|
|
225
|
+
registry?: any; // prom-client Registry
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Tracing configuration
|
|
230
|
+
*/
|
|
231
|
+
export interface TracingConfig {
|
|
232
|
+
enabled: boolean;
|
|
233
|
+
serviceName?: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Batch configuration
|
|
238
|
+
*/
|
|
239
|
+
export interface BatchConfig {
|
|
240
|
+
maxBatchSize?: number;
|
|
241
|
+
batchTimeout?: number;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Cache configuration
|
|
246
|
+
*/
|
|
247
|
+
export interface CacheConfig {
|
|
248
|
+
enabled: boolean;
|
|
249
|
+
ttl?: number;
|
|
250
|
+
keyPrefix?: string;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Dead letter queue configuration
|
|
255
|
+
*/
|
|
256
|
+
export interface DeadLetterQueueConfig {
|
|
257
|
+
enabled: boolean;
|
|
258
|
+
store: KeyValueStore;
|
|
259
|
+
retention?: number;
|
|
260
|
+
maxRetries?: number;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Middleware function
|
|
265
|
+
*/
|
|
266
|
+
export type Middleware = (req: Request, res: Response, next: NextFunction) => void | Promise<void>;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* MCP Server configuration
|
|
270
|
+
*/
|
|
271
|
+
export interface MCPServerConfig {
|
|
272
|
+
// Server Identity
|
|
273
|
+
name: string;
|
|
274
|
+
version: string;
|
|
275
|
+
|
|
276
|
+
// HTTP Server Configuration
|
|
277
|
+
port?: number;
|
|
278
|
+
host?: string;
|
|
279
|
+
basePath?: string;
|
|
280
|
+
publicUrl: string;
|
|
281
|
+
|
|
282
|
+
// Authentication
|
|
283
|
+
authenticate?: AuthenticateFunction;
|
|
284
|
+
|
|
285
|
+
// Core MCP Components
|
|
286
|
+
tools: ToolDefinition[];
|
|
287
|
+
resources: ResourceDefinition[];
|
|
288
|
+
prompts?: PromptDefinition[];
|
|
289
|
+
|
|
290
|
+
// Storage
|
|
291
|
+
store: KeyValueStore;
|
|
292
|
+
|
|
293
|
+
// Webhook Configuration
|
|
294
|
+
webhooks?: WebhookConfig;
|
|
295
|
+
|
|
296
|
+
// Advanced Features
|
|
297
|
+
batch?: BatchConfig;
|
|
298
|
+
cache?: CacheConfig;
|
|
299
|
+
metrics?: MetricsConfig;
|
|
300
|
+
tracing?: TracingConfig;
|
|
301
|
+
middleware?: Middleware[];
|
|
302
|
+
|
|
303
|
+
// Logging
|
|
304
|
+
logger?: Logger;
|
|
305
|
+
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Stored subscription data
|
|
310
|
+
*/
|
|
311
|
+
export interface StoredSubscription {
|
|
312
|
+
uri: string;
|
|
313
|
+
resourceType: string;
|
|
314
|
+
clientCallbackUrl: string;
|
|
315
|
+
clientCallbackSecret?: string;
|
|
316
|
+
userId: string;
|
|
317
|
+
thirdPartyWebhookId: string;
|
|
318
|
+
metadata?: any;
|
|
319
|
+
createdAt: number;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* MCP Error codes
|
|
324
|
+
*/
|
|
325
|
+
export enum MCPErrorCode {
|
|
326
|
+
AuthenticationError = -32001,
|
|
327
|
+
ValidationError = -32002,
|
|
328
|
+
ResourceNotFoundError = -32003,
|
|
329
|
+
ToolExecutionError = -32004,
|
|
330
|
+
WebhookError = -32005,
|
|
331
|
+
StorageError = -32006,
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* MCP Server instance
|
|
336
|
+
*/
|
|
337
|
+
export interface MCPServer {
|
|
338
|
+
start(): Promise<void>;
|
|
339
|
+
stop(): Promise<void>;
|
|
340
|
+
getApp(): any; // Express app
|
|
341
|
+
}
|