mcp-http-webhook 1.0.6 → 1.0.7
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/COMPLETION_IMPLEMENTATION.md +280 -0
- package/PAGINATION_IMPLEMENTATION.md +221 -0
- package/README.md +194 -6
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +81 -9
- package/dist/server.js.map +1 -1
- package/dist/types/index.d.ts +57 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/examples/completion-example.ts +339 -0
- package/examples/pagination-example.ts +261 -0
- package/package.json +5 -4
- package/src/__tests__/pagination.test.ts +323 -0
- package/src/server.ts +97 -14
- package/src/types/index.ts +74 -1
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
|
|
2
|
+
import request from 'supertest';
|
|
3
|
+
import { createMCPServer, InMemoryStore, ResourceDefinition, AuthContext, ResourceReadOptions } from '../index';
|
|
4
|
+
|
|
5
|
+
describe('Pagination Support', () => {
|
|
6
|
+
let server: any;
|
|
7
|
+
let app: any;
|
|
8
|
+
|
|
9
|
+
// Mock data
|
|
10
|
+
const mockItems = Array.from({ length: 50 }, (_, i) => ({
|
|
11
|
+
id: `item-${i + 1}`,
|
|
12
|
+
name: `Item ${i + 1}`,
|
|
13
|
+
data: `Data for item ${i + 1}`,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
const paginatedResource: ResourceDefinition = {
|
|
18
|
+
uri: 'test://items',
|
|
19
|
+
name: 'test-items',
|
|
20
|
+
description: 'Test resource with pagination',
|
|
21
|
+
mimeType: 'application/json',
|
|
22
|
+
|
|
23
|
+
list: async (context: AuthContext, options?: ResourceReadOptions) => {
|
|
24
|
+
const page = options?.pagination?.page || 1;
|
|
25
|
+
const limit = options?.pagination?.limit || 10;
|
|
26
|
+
const offset = (page - 1) * limit;
|
|
27
|
+
|
|
28
|
+
const paginatedItems = mockItems.slice(offset, offset + limit);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
resources: paginatedItems.map(item => ({
|
|
32
|
+
uri: `test://items/${item.id}`,
|
|
33
|
+
name: item.name,
|
|
34
|
+
})),
|
|
35
|
+
pagination: {
|
|
36
|
+
page,
|
|
37
|
+
limit,
|
|
38
|
+
total: mockItems.length,
|
|
39
|
+
hasMore: offset + limit < mockItems.length,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
read: async (uri: string, context: AuthContext, options?: ResourceReadOptions) => {
|
|
45
|
+
const itemId = uri.split('/').pop();
|
|
46
|
+
const item = mockItems.find(i => i.id === itemId);
|
|
47
|
+
|
|
48
|
+
if (!item) {
|
|
49
|
+
throw new Error('Item not found');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Support pagination for large content
|
|
53
|
+
const page = options?.pagination?.page || 1;
|
|
54
|
+
const limit = options?.pagination?.limit || 100;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
contents: item,
|
|
58
|
+
pagination: options?.pagination
|
|
59
|
+
? {
|
|
60
|
+
page,
|
|
61
|
+
limit,
|
|
62
|
+
total: 1,
|
|
63
|
+
hasMore: false,
|
|
64
|
+
}
|
|
65
|
+
: undefined,
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const cursorResource: ResourceDefinition = {
|
|
71
|
+
uri: 'test://cursor',
|
|
72
|
+
name: 'test-cursor',
|
|
73
|
+
description: 'Test resource with cursor pagination',
|
|
74
|
+
mimeType: 'application/json',
|
|
75
|
+
|
|
76
|
+
list: async (_context: AuthContext) => {
|
|
77
|
+
return {
|
|
78
|
+
resources: [{ uri: 'test://cursor', name: 'Cursor Resource' }],
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
read: async (uri: string, context: AuthContext, options?: ResourceReadOptions) => {
|
|
83
|
+
const cursor = options?.pagination?.cursor;
|
|
84
|
+
const limit = options?.pagination?.limit || 10;
|
|
85
|
+
const cursorIndex = cursor ? parseInt(cursor, 10) : 0;
|
|
86
|
+
|
|
87
|
+
const paginatedItems = mockItems.slice(cursorIndex, cursorIndex + limit);
|
|
88
|
+
const hasMore = cursorIndex + limit < mockItems.length;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
contents: paginatedItems,
|
|
92
|
+
pagination: {
|
|
93
|
+
page: 1,
|
|
94
|
+
limit,
|
|
95
|
+
total: mockItems.length,
|
|
96
|
+
hasMore,
|
|
97
|
+
nextCursor: hasMore ? String(cursorIndex + limit) : undefined,
|
|
98
|
+
prevCursor: cursorIndex > 0 ? String(Math.max(0, cursorIndex - limit)) : undefined,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
server = createMCPServer({
|
|
105
|
+
name: 'pagination-test',
|
|
106
|
+
version: '1.0.0',
|
|
107
|
+
publicUrl: 'http://localhost:3001',
|
|
108
|
+
port: 3001,
|
|
109
|
+
store: new InMemoryStore(),
|
|
110
|
+
tools: [],
|
|
111
|
+
resources: [paginatedResource, cursorResource],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await server.start();
|
|
115
|
+
app = server.getApp();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
afterAll(async () => {
|
|
119
|
+
await server.stop();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('List with Pagination', () => {
|
|
123
|
+
it('should return first page by default', async () => {
|
|
124
|
+
const response = await request(app)
|
|
125
|
+
.post('/mcp')
|
|
126
|
+
.send({
|
|
127
|
+
jsonrpc: '2.0',
|
|
128
|
+
id: 1,
|
|
129
|
+
method: 'resources/list',
|
|
130
|
+
params: {},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(response.status).toBe(200);
|
|
134
|
+
expect(response.body.result).toBeDefined();
|
|
135
|
+
expect(response.body.result.resources).toHaveLength(10); // Default limit
|
|
136
|
+
expect(response.body.result._meta?.pagination).toMatchObject({
|
|
137
|
+
page: 1,
|
|
138
|
+
limit: 10,
|
|
139
|
+
total: 50,
|
|
140
|
+
hasMore: true,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should return specific page with custom limit', async () => {
|
|
145
|
+
const response = await request(app)
|
|
146
|
+
.post('/mcp')
|
|
147
|
+
.send({
|
|
148
|
+
jsonrpc: '2.0',
|
|
149
|
+
id: 1,
|
|
150
|
+
method: 'resources/list',
|
|
151
|
+
params: {},
|
|
152
|
+
_meta: {
|
|
153
|
+
page: 2,
|
|
154
|
+
limit: 5,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(response.status).toBe(200);
|
|
159
|
+
expect(response.body.result.resources).toHaveLength(5);
|
|
160
|
+
expect(response.body.result._meta?.pagination).toMatchObject({
|
|
161
|
+
page: 2,
|
|
162
|
+
limit: 5,
|
|
163
|
+
total: 50,
|
|
164
|
+
hasMore: true,
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should indicate no more pages on last page', async () => {
|
|
169
|
+
const response = await request(app)
|
|
170
|
+
.post('/mcp')
|
|
171
|
+
.send({
|
|
172
|
+
jsonrpc: '2.0',
|
|
173
|
+
id: 1,
|
|
174
|
+
method: 'resources/list',
|
|
175
|
+
params: {},
|
|
176
|
+
_meta: {
|
|
177
|
+
page: 5,
|
|
178
|
+
limit: 10,
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(response.status).toBe(200);
|
|
183
|
+
expect(response.body.result.resources).toHaveLength(10);
|
|
184
|
+
expect(response.body.result._meta?.pagination.hasMore).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('Read with Pagination', () => {
|
|
189
|
+
it('should read without pagination by default', async () => {
|
|
190
|
+
const response = await request(app)
|
|
191
|
+
.post('/mcp')
|
|
192
|
+
.send({
|
|
193
|
+
jsonrpc: '2.0',
|
|
194
|
+
id: 1,
|
|
195
|
+
method: 'resources/read',
|
|
196
|
+
params: {
|
|
197
|
+
uri: 'test://items/item-1',
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(response.status).toBe(200);
|
|
202
|
+
expect(response.body.result.contents).toBeDefined();
|
|
203
|
+
expect(response.body.result._meta?.pagination).toBeUndefined();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should support pagination when requested', async () => {
|
|
207
|
+
const response = await request(app)
|
|
208
|
+
.post('/mcp')
|
|
209
|
+
.send({
|
|
210
|
+
jsonrpc: '2.0',
|
|
211
|
+
id: 1,
|
|
212
|
+
method: 'resources/read',
|
|
213
|
+
params: {
|
|
214
|
+
uri: 'test://items/item-1',
|
|
215
|
+
},
|
|
216
|
+
_meta: {
|
|
217
|
+
page: 1,
|
|
218
|
+
limit: 50,
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(response.status).toBe(200);
|
|
223
|
+
expect(response.body.result.contents).toBeDefined();
|
|
224
|
+
expect(response.body.result._meta?.pagination).toMatchObject({
|
|
225
|
+
page: 1,
|
|
226
|
+
limit: 50,
|
|
227
|
+
total: 1,
|
|
228
|
+
hasMore: false,
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('Cursor-based Pagination', () => {
|
|
234
|
+
it('should support cursor pagination', async () => {
|
|
235
|
+
const response = await request(app)
|
|
236
|
+
.post('/mcp')
|
|
237
|
+
.send({
|
|
238
|
+
jsonrpc: '2.0',
|
|
239
|
+
id: 1,
|
|
240
|
+
method: 'resources/read',
|
|
241
|
+
params: {
|
|
242
|
+
uri: 'test://cursor',
|
|
243
|
+
},
|
|
244
|
+
_meta: {
|
|
245
|
+
limit: 10,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect(response.status).toBe(200);
|
|
250
|
+
expect(response.body.result.contents).toHaveLength(10);
|
|
251
|
+
expect(response.body.result._meta?.pagination).toMatchObject({
|
|
252
|
+
page: 1,
|
|
253
|
+
limit: 10,
|
|
254
|
+
total: 50,
|
|
255
|
+
hasMore: true,
|
|
256
|
+
});
|
|
257
|
+
expect(response.body.result._meta?.pagination.nextCursor).toBe('10');
|
|
258
|
+
expect(response.body.result._meta?.pagination.prevCursor).toBeUndefined();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should navigate using cursor', async () => {
|
|
262
|
+
const response = await request(app)
|
|
263
|
+
.post('/mcp')
|
|
264
|
+
.send({
|
|
265
|
+
jsonrpc: '2.0',
|
|
266
|
+
id: 1,
|
|
267
|
+
method: 'resources/read',
|
|
268
|
+
params: {
|
|
269
|
+
uri: 'test://cursor',
|
|
270
|
+
},
|
|
271
|
+
_meta: {
|
|
272
|
+
cursor: '20',
|
|
273
|
+
limit: 10,
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
expect(response.status).toBe(200);
|
|
278
|
+
expect(response.body.result.contents).toHaveLength(10);
|
|
279
|
+
expect(response.body.result._meta?.pagination.nextCursor).toBe('30');
|
|
280
|
+
expect(response.body.result._meta?.pagination.prevCursor).toBe('10');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should handle last page with cursor', async () => {
|
|
284
|
+
const response = await request(app)
|
|
285
|
+
.post('/mcp')
|
|
286
|
+
.send({
|
|
287
|
+
jsonrpc: '2.0',
|
|
288
|
+
id: 1,
|
|
289
|
+
method: 'resources/read',
|
|
290
|
+
params: {
|
|
291
|
+
uri: 'test://cursor',
|
|
292
|
+
},
|
|
293
|
+
_meta: {
|
|
294
|
+
cursor: '45',
|
|
295
|
+
limit: 10,
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
expect(response.status).toBe(200);
|
|
300
|
+
expect(response.body.result.contents).toHaveLength(5);
|
|
301
|
+
expect(response.body.result._meta?.pagination.hasMore).toBe(false);
|
|
302
|
+
expect(response.body.result._meta?.pagination.nextCursor).toBeUndefined();
|
|
303
|
+
expect(response.body.result._meta?.pagination.prevCursor).toBe('35');
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('Backward Compatibility', () => {
|
|
308
|
+
it('should handle resources without pagination support', async () => {
|
|
309
|
+
// This tests that old resources still work
|
|
310
|
+
const response = await request(app)
|
|
311
|
+
.post('/mcp')
|
|
312
|
+
.send({
|
|
313
|
+
jsonrpc: '2.0',
|
|
314
|
+
id: 1,
|
|
315
|
+
method: 'resources/list',
|
|
316
|
+
params: {},
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
expect(response.status).toBe(200);
|
|
320
|
+
expect(response.body.result.resources).toBeDefined();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
});
|
package/src/server.ts
CHANGED
|
@@ -2,6 +2,7 @@ import express, { Express, Request, Response, NextFunction } from 'express';
|
|
|
2
2
|
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
+
import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
|
|
5
6
|
import { MCPServerConfig, MCPServer, Logger, JSONSchema, CredentialFieldDefinition } from './types';
|
|
6
7
|
import { SubscriptionManager } from './subscriptions/SubscriptionManager';
|
|
7
8
|
import { WebhookManager } from './webhooks/WebhookManager';
|
|
@@ -205,6 +206,7 @@ export function createMCPServer(config: MCPServerConfig): MCPServer {
|
|
|
205
206
|
experimental: {
|
|
206
207
|
isConnector: true,
|
|
207
208
|
},
|
|
209
|
+
"completions": {}
|
|
208
210
|
},
|
|
209
211
|
}
|
|
210
212
|
);
|
|
@@ -255,12 +257,28 @@ export function createMCPServer(config: MCPServerConfig): MCPServer {
|
|
|
255
257
|
if (isTemplate && resourceDef.list) {
|
|
256
258
|
// Use ResourceTemplate for templated URIs
|
|
257
259
|
const template = new ResourceTemplate(resourceDef.uri, {
|
|
258
|
-
list: async () => {
|
|
260
|
+
list: async (extra?: any) => {
|
|
259
261
|
const context = (sdkServer as any)._currentContext || { userId: 'anonymous' };
|
|
260
|
-
|
|
262
|
+
|
|
263
|
+
// Extract pagination options from extra metadata
|
|
264
|
+
const options: any = {};
|
|
265
|
+
const meta = extra?._meta as any;
|
|
266
|
+
if (meta?.page || meta?.limit || meta?.cursor) {
|
|
267
|
+
options.pagination = {
|
|
268
|
+
page: meta.page ? parseInt(String(meta.page), 10) : undefined,
|
|
269
|
+
limit: meta.limit ? parseInt(String(meta.limit), 10) : undefined,
|
|
270
|
+
cursor: meta.cursor,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const result = await resourceDef.list!(context, options);
|
|
275
|
+
|
|
276
|
+
// Handle both array and paginated responses
|
|
277
|
+
const resources = Array.isArray(result) ? result : result.resources;
|
|
278
|
+
const pagination = Array.isArray(result) ? undefined : result.pagination;
|
|
261
279
|
|
|
262
280
|
// Map to MCP SDK Resource format with index signature
|
|
263
|
-
|
|
281
|
+
const response: any = {
|
|
264
282
|
resources: resources.map((r: any) => ({
|
|
265
283
|
uri: r.uri,
|
|
266
284
|
name: r.name,
|
|
@@ -270,6 +288,13 @@ export function createMCPServer(config: MCPServerConfig): MCPServer {
|
|
|
270
288
|
...r, // Spread to add index signature
|
|
271
289
|
})),
|
|
272
290
|
};
|
|
291
|
+
|
|
292
|
+
// Add pagination metadata if present
|
|
293
|
+
if (pagination) {
|
|
294
|
+
response._meta = { pagination };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return response;
|
|
273
298
|
},
|
|
274
299
|
});
|
|
275
300
|
sdkServer.registerResource(
|
|
@@ -279,9 +304,21 @@ export function createMCPServer(config: MCPServerConfig): MCPServer {
|
|
|
279
304
|
description: resourceDef.description,
|
|
280
305
|
mimeType: resourceDef.mimeType || 'application/json',
|
|
281
306
|
},
|
|
282
|
-
async (uri
|
|
307
|
+
async (uri, _variables, _extra) => {
|
|
283
308
|
const context = (sdkServer as any)._currentContext || { userId: 'anonymous' };
|
|
284
|
-
|
|
309
|
+
|
|
310
|
+
// Extract pagination options from _extra metadata
|
|
311
|
+
const options: any = {};
|
|
312
|
+
const meta = _extra?._meta as any;
|
|
313
|
+
if (meta?.page || meta?.limit || meta?.cursor) {
|
|
314
|
+
options.pagination = {
|
|
315
|
+
page: meta.page ? parseInt(String(meta.page), 10) : undefined,
|
|
316
|
+
limit: meta.limit ? parseInt(String(meta.limit), 10) : undefined,
|
|
317
|
+
cursor: meta.cursor,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const result = await resourceDef.read(uri.href, { ...context, ...(_variables || {}) }, options);
|
|
285
322
|
|
|
286
323
|
const metadata =
|
|
287
324
|
result.metadata ||
|
|
@@ -311,6 +348,11 @@ export function createMCPServer(config: MCPServerConfig): MCPServer {
|
|
|
311
348
|
if (metadata) {
|
|
312
349
|
response.metadata = metadata;
|
|
313
350
|
}
|
|
351
|
+
|
|
352
|
+
// Add pagination metadata if present
|
|
353
|
+
if (result.pagination) {
|
|
354
|
+
response._meta = { pagination: result.pagination };
|
|
355
|
+
}
|
|
314
356
|
|
|
315
357
|
return response;
|
|
316
358
|
}
|
|
@@ -326,7 +368,19 @@ export function createMCPServer(config: MCPServerConfig): MCPServer {
|
|
|
326
368
|
},
|
|
327
369
|
async (uri: any, _extra: any) => {
|
|
328
370
|
const context = (sdkServer as any)._currentContext || { userId: 'anonymous' };
|
|
329
|
-
|
|
371
|
+
|
|
372
|
+
// Extract pagination options from _extra metadata
|
|
373
|
+
const options: any = {};
|
|
374
|
+
const meta = _extra?._meta as any;
|
|
375
|
+
if (meta?.page || meta?.limit || meta?.cursor) {
|
|
376
|
+
options.pagination = {
|
|
377
|
+
page: meta.page ? parseInt(String(meta.page), 10) : undefined,
|
|
378
|
+
limit: meta.limit ? parseInt(String(meta.limit), 10) : undefined,
|
|
379
|
+
cursor: meta.cursor,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const result = await resourceDef.read(uri.href, context, options);
|
|
330
384
|
|
|
331
385
|
const metadata =
|
|
332
386
|
result.metadata ||
|
|
@@ -356,6 +410,11 @@ export function createMCPServer(config: MCPServerConfig): MCPServer {
|
|
|
356
410
|
if (metadata) {
|
|
357
411
|
response.metadata = metadata;
|
|
358
412
|
}
|
|
413
|
+
|
|
414
|
+
// Add pagination metadata if present
|
|
415
|
+
if (result.pagination) {
|
|
416
|
+
response._meta = { pagination: result.pagination };
|
|
417
|
+
}
|
|
359
418
|
|
|
360
419
|
return response;
|
|
361
420
|
}
|
|
@@ -366,19 +425,43 @@ export function createMCPServer(config: MCPServerConfig): MCPServer {
|
|
|
366
425
|
// Register prompts with MCP SDK
|
|
367
426
|
if (config.prompts) {
|
|
368
427
|
config.prompts.forEach((promptDef) => {
|
|
428
|
+
const argsSchema: Record<string, any> = {};
|
|
429
|
+
|
|
430
|
+
// Build args schema with completion support
|
|
431
|
+
if (promptDef.arguments) {
|
|
432
|
+
promptDef.arguments.forEach((arg) => {
|
|
433
|
+
let argSchema = z.string();
|
|
434
|
+
|
|
435
|
+
// Add completion support if handler is provided
|
|
436
|
+
if (promptDef.completion) {
|
|
437
|
+
argSchema = completable(z.string(), async (value, context) => {
|
|
438
|
+
const ctx = (sdkServer as any)._currentContext || { userId: 'anonymous' };
|
|
439
|
+
const ref = {
|
|
440
|
+
type: 'ref/prompt' as const,
|
|
441
|
+
name: promptDef.name,
|
|
442
|
+
arguments: context?.arguments || {},
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
const completions = await promptDef.completion!(ref, arg.name, ctx);
|
|
447
|
+
return completions.map(c => c.value);
|
|
448
|
+
} catch (error) {
|
|
449
|
+
logger.error('Completion error', { error });
|
|
450
|
+
return [];
|
|
451
|
+
}
|
|
452
|
+
}) as any;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
argsSchema[arg.name] = argSchema;
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
369
459
|
sdkServer.registerPrompt(
|
|
370
460
|
promptDef.name,
|
|
371
461
|
{
|
|
372
462
|
title: promptDef.name,
|
|
373
463
|
description: promptDef.description,
|
|
374
|
-
argsSchema
|
|
375
|
-
promptDef.arguments?.reduce(
|
|
376
|
-
(schema, arg) => {
|
|
377
|
-
schema[arg.name] = z.string();
|
|
378
|
-
return schema;
|
|
379
|
-
},
|
|
380
|
-
{} as Record<string, any>
|
|
381
|
-
) || {},
|
|
464
|
+
argsSchema,
|
|
382
465
|
},
|
|
383
466
|
async (args: any) => {
|
|
384
467
|
const context = (sdkServer as any)._currentContext || { userId: 'anonymous' };
|
package/src/types/index.ts
CHANGED
|
@@ -197,9 +197,33 @@ export interface ResourceListItem {
|
|
|
197
197
|
/**
|
|
198
198
|
* Resource read result
|
|
199
199
|
*/
|
|
200
|
+
/**
|
|
201
|
+
* Pagination metadata for responses
|
|
202
|
+
*/
|
|
203
|
+
export interface PaginationMetadata {
|
|
204
|
+
page: number;
|
|
205
|
+
limit: number;
|
|
206
|
+
total?: number;
|
|
207
|
+
hasMore?: boolean;
|
|
208
|
+
nextCursor?: string;
|
|
209
|
+
prevCursor?: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Resource read result with optional pagination
|
|
214
|
+
*/
|
|
200
215
|
export interface ResourceReadResult<TData = any> {
|
|
201
216
|
contents: TData;
|
|
202
217
|
metadata?: ResourceMetadata;
|
|
218
|
+
pagination?: PaginationMetadata;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Resource list result with optional pagination
|
|
223
|
+
*/
|
|
224
|
+
export interface ResourceListResult {
|
|
225
|
+
resources: ResourceListItem[];
|
|
226
|
+
pagination?: PaginationMetadata;
|
|
203
227
|
}
|
|
204
228
|
|
|
205
229
|
/**
|
|
@@ -209,6 +233,7 @@ export interface ResourceReadOptions {
|
|
|
209
233
|
pagination?: {
|
|
210
234
|
page?: number;
|
|
211
235
|
limit?: number;
|
|
236
|
+
cursor?: string;
|
|
212
237
|
};
|
|
213
238
|
}
|
|
214
239
|
|
|
@@ -280,11 +305,52 @@ export interface ResourceDefinition<TData = any> {
|
|
|
280
305
|
options?: ResourceReadOptions
|
|
281
306
|
) => Promise<ResourceReadResult<TData>>;
|
|
282
307
|
|
|
283
|
-
list?: (
|
|
308
|
+
list?: (
|
|
309
|
+
context: AuthContext,
|
|
310
|
+
options?: ResourceReadOptions
|
|
311
|
+
) => Promise<ResourceListResult | ResourceListItem[]>;
|
|
284
312
|
|
|
285
313
|
subscription?: ResourceSubscription;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Optional completion handler for resource URI parameters
|
|
317
|
+
*/
|
|
318
|
+
completion?: CompletionHandler;
|
|
286
319
|
}
|
|
287
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Completion item returned by completion handlers
|
|
323
|
+
*/
|
|
324
|
+
export interface CompletionItem {
|
|
325
|
+
value: string;
|
|
326
|
+
label?: string;
|
|
327
|
+
description?: string;
|
|
328
|
+
type?: 'value' | 'function' | 'variable' | 'constant' | 'other';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Completion reference - identifies what to complete
|
|
333
|
+
*/
|
|
334
|
+
export type CompletionRef =
|
|
335
|
+
| {
|
|
336
|
+
type: 'ref/prompt';
|
|
337
|
+
name: string;
|
|
338
|
+
arguments?: Record<string, string>;
|
|
339
|
+
}
|
|
340
|
+
| {
|
|
341
|
+
type: 'ref/resource';
|
|
342
|
+
uri: string;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Completion handler function
|
|
347
|
+
*/
|
|
348
|
+
export type CompletionHandler = (
|
|
349
|
+
ref: CompletionRef,
|
|
350
|
+
argument: string,
|
|
351
|
+
context: AuthContext
|
|
352
|
+
) => Promise<CompletionItem[]>;
|
|
353
|
+
|
|
288
354
|
/**
|
|
289
355
|
* Prompt definition
|
|
290
356
|
*/
|
|
@@ -302,6 +368,10 @@ export interface PromptDefinition {
|
|
|
302
368
|
content: string;
|
|
303
369
|
}>;
|
|
304
370
|
}>;
|
|
371
|
+
/**
|
|
372
|
+
* Optional completion handler for prompt arguments
|
|
373
|
+
*/
|
|
374
|
+
completion?: CompletionHandler;
|
|
305
375
|
}
|
|
306
376
|
|
|
307
377
|
/**
|
|
@@ -391,6 +461,9 @@ export interface MCPServerConfig {
|
|
|
391
461
|
tools: ToolDefinition[];
|
|
392
462
|
resources: ResourceDefinition[];
|
|
393
463
|
prompts?: PromptDefinition[];
|
|
464
|
+
|
|
465
|
+
// Completions
|
|
466
|
+
completions?: CompletionHandler;
|
|
394
467
|
|
|
395
468
|
// Storage
|
|
396
469
|
store: KeyValueStore;
|