@convertcom/mcp-server 0.0.1-beta.15 → 0.0.1-beta.16
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/build/api-config.js +21 -3
- package/build/api-config.js.map +1 -1
- package/build/auth-context.d.ts +3 -0
- package/build/auth-context.js.map +1 -1
- package/build/catalog-change.d.ts +6 -2
- package/build/catalog-change.js +5 -1
- package/build/catalog-change.js.map +1 -1
- package/build/executor.d.ts +6 -0
- package/build/executor.js +73 -22
- package/build/executor.js.map +1 -1
- package/build/generated-tool-validators.js +141 -158
- package/build/generated-tool-validators.js.map +1 -1
- package/build/index.js +19 -12
- package/build/index.js.map +1 -1
- package/build/kb-hybrid.d.ts +13 -5
- package/build/kb-hybrid.js +16 -10
- package/build/kb-hybrid.js.map +1 -1
- package/build/kb-search.d.ts +9 -3
- package/build/kb-search.js +95 -4
- package/build/kb-search.js.map +1 -1
- package/build/kb-semantic.d.ts +2 -2
- package/build/kb-semantic.js +11 -2
- package/build/kb-semantic.js.map +1 -1
- package/build/local-docs.d.ts +12 -0
- package/build/local-docs.js +9 -0
- package/build/local-docs.js.map +1 -0
- package/build/local-execution-context.d.ts +7 -0
- package/build/local-execution-context.js +18 -0
- package/build/local-execution-context.js.map +1 -0
- package/build/mcp-server-compat.d.ts +8 -0
- package/build/mcp-server-compat.js +28 -0
- package/build/mcp-server-compat.js.map +1 -0
- package/build/modern-runtime.d.ts +13 -0
- package/build/modern-runtime.js +148 -0
- package/build/modern-runtime.js.map +1 -0
- package/build/namespaced.js +2 -2
- package/build/namespaced.js.map +1 -1
- package/build/runtime.d.ts +221 -5
- package/build/runtime.js +169 -67
- package/build/runtime.js.map +1 -1
- package/package.json +6 -6
- package/server.json +2 -2
package/build/runtime.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import
|
|
2
|
+
import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
3
|
+
import { type CallToolRequest, type CallToolResult, type GetPromptRequest, type ReadResourceRequest, type ClientRequest, type Implementation, type ListResourcesResult, type ReadResourceResult, type Resource, type ServerNotification, type Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
3
4
|
import type { ToolExecutionContext } from './auth-context.js';
|
|
4
5
|
import { type CatalogChangeRequest } from './catalog-change.js';
|
|
5
6
|
import { type KbChunk, type KbDocument, type SearchResult } from './kb-search.js';
|
|
6
7
|
export declare const SERVER_NAME = "convert-api";
|
|
7
|
-
export declare const SERVER_VERSION_FALLBACK = "0.0.1-beta.
|
|
8
|
+
export declare const SERVER_VERSION_FALLBACK = "0.0.1-beta.16";
|
|
9
|
+
export declare const MAX_SEARCH_QUERY_CHARACTERS = 500;
|
|
8
10
|
export type ExecutionContextResolver = (extra: any, request: CallToolRequest) => ToolExecutionContext | undefined | Promise<ToolExecutionContext | undefined>;
|
|
9
11
|
export type ToolCatalogContextResolver = (extra: any) => ToolExecutionContext | undefined | Promise<ToolExecutionContext | undefined>;
|
|
10
12
|
export type DocsSearchProvider = {
|
|
11
|
-
search(query: string, limit?: number): SearchResult[] | Promise<SearchResult[]>;
|
|
12
|
-
getById(id: string): KbChunk | null | Promise<KbChunk | null>;
|
|
13
|
-
getDocumentById?(id: string): KbDocument | null | Promise<KbDocument | null>;
|
|
13
|
+
search(query: string, limit?: number, signal?: AbortSignal): SearchResult[] | Promise<SearchResult[]>;
|
|
14
|
+
getById(id: string, signal?: AbortSignal): KbChunk | null | Promise<KbChunk | null>;
|
|
15
|
+
getDocumentById?(id: string, signal?: AbortSignal): KbDocument | null | Promise<KbDocument | null>;
|
|
14
16
|
};
|
|
15
17
|
export type RuntimeLogger = {
|
|
16
18
|
log(event: string, payload?: unknown): void;
|
|
@@ -34,6 +36,8 @@ export type RuntimeResourceProvider = {
|
|
|
34
36
|
};
|
|
35
37
|
export type ConvertMcpRuntimeOptions = {
|
|
36
38
|
docs: DocsSearchProvider;
|
|
39
|
+
/** Disable only where the serving transport cannot scope list-change delivery safely. */
|
|
40
|
+
advertiseListChanged?: boolean;
|
|
37
41
|
executionContextResolver?: ExecutionContextResolver;
|
|
38
42
|
toolCatalogContextResolver?: ToolCatalogContextResolver;
|
|
39
43
|
logger?: RuntimeLogger;
|
|
@@ -47,12 +51,224 @@ export type ConvertMcpRuntimeOptions = {
|
|
|
47
51
|
toolExtensions?: RuntimeToolExtension[];
|
|
48
52
|
resourceProvider?: RuntimeResourceProvider;
|
|
49
53
|
toolDecorator?: (tool: Tool) => Tool;
|
|
54
|
+
/** Bounded authorization-shape cache supplied only by transports with stable configuration. */
|
|
55
|
+
toolCatalogCache?: Map<string, RuntimeToolCatalogSnapshot>;
|
|
56
|
+
/** Shares deterministic tool-catalog digests across stateless modern requests. */
|
|
57
|
+
toolCatalogRevisionCache?: Map<string, Promise<string>>;
|
|
58
|
+
/** Must cover every dynamic input used by toolDecorator; never include bearer or principal data. */
|
|
59
|
+
toolCatalogDecoratorCacheKey?: () => string;
|
|
50
60
|
serverInstructions?: string | ((toolSet: string) => string);
|
|
51
61
|
catalogChangeNotifier?: (request: CatalogChangeRequest) => Promise<void>;
|
|
52
62
|
};
|
|
63
|
+
export type RuntimeToolCatalogSnapshot = {
|
|
64
|
+
tools: Tool[];
|
|
65
|
+
toolsByName: ReadonlyMap<string, Tool>;
|
|
66
|
+
};
|
|
67
|
+
export type RuntimeRequestExtra = Pick<RequestHandlerExtra<ClientRequest, ServerNotification>, 'sendNotification' | 'signal' | 'requestId'> & {
|
|
68
|
+
clientInfo?: Implementation;
|
|
69
|
+
};
|
|
70
|
+
export type RuntimeToolResolutionObserver = (tool: Tool | undefined) => void;
|
|
53
71
|
export declare const SERVER_INSTRUCTIONS: string;
|
|
54
72
|
export declare function serverInstructionsForToolSet(toolSet: string, renderToolNames?: readonly string[]): string;
|
|
55
73
|
export declare const canonicalSearchTool: Tool;
|
|
56
74
|
export declare const canonicalFetchTool: Tool;
|
|
57
75
|
export declare function getToolsForClient(toolSet?: string, feedbackEnabled?: boolean, executionContext?: ToolExecutionContext): Tool[];
|
|
76
|
+
export type ConvertMcpServerIdentityOptions = Pick<ConvertMcpRuntimeOptions, 'serverVersion' | 'serverTitle' | 'iconBaseUrl' | 'websiteUrl' | 'toolsForClient' | 'serverInstructions'>;
|
|
77
|
+
export declare function convertMcpServerDefinition(options: ConvertMcpServerIdentityOptions): {
|
|
78
|
+
implementation: {
|
|
79
|
+
name: string;
|
|
80
|
+
version: string;
|
|
81
|
+
title: string;
|
|
82
|
+
description: string;
|
|
83
|
+
websiteUrl: string;
|
|
84
|
+
icons: {
|
|
85
|
+
src: string;
|
|
86
|
+
mimeType?: string | undefined;
|
|
87
|
+
sizes?: string[] | undefined;
|
|
88
|
+
theme?: "light" | "dark" | undefined;
|
|
89
|
+
}[];
|
|
90
|
+
};
|
|
91
|
+
options: {
|
|
92
|
+
capabilities: {
|
|
93
|
+
tools: {
|
|
94
|
+
listChanged: boolean;
|
|
95
|
+
};
|
|
96
|
+
prompts: {
|
|
97
|
+
listChanged: boolean;
|
|
98
|
+
};
|
|
99
|
+
resources: {
|
|
100
|
+
listChanged: boolean;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
instructions: string;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
58
106
|
export declare function createConvertMcpServer(options: ConvertMcpRuntimeOptions): Server;
|
|
107
|
+
export declare function createConvertMcpHandlers(options: ConvertMcpRuntimeOptions, clientInfoFor: (extra: RuntimeRequestExtra) => Implementation | undefined): {
|
|
108
|
+
listTools: (extra: RuntimeRequestExtra) => Promise<{
|
|
109
|
+
tools: {
|
|
110
|
+
inputSchema: {
|
|
111
|
+
[x: string]: unknown;
|
|
112
|
+
type: "object";
|
|
113
|
+
properties?: {
|
|
114
|
+
[x: string]: object;
|
|
115
|
+
} | undefined;
|
|
116
|
+
required?: string[] | undefined;
|
|
117
|
+
};
|
|
118
|
+
name: string;
|
|
119
|
+
description?: string | undefined;
|
|
120
|
+
outputSchema?: {
|
|
121
|
+
[x: string]: unknown;
|
|
122
|
+
type: "object";
|
|
123
|
+
properties?: {
|
|
124
|
+
[x: string]: object;
|
|
125
|
+
} | undefined;
|
|
126
|
+
required?: string[] | undefined;
|
|
127
|
+
} | undefined;
|
|
128
|
+
annotations?: {
|
|
129
|
+
title?: string | undefined;
|
|
130
|
+
readOnlyHint?: boolean | undefined;
|
|
131
|
+
destructiveHint?: boolean | undefined;
|
|
132
|
+
idempotentHint?: boolean | undefined;
|
|
133
|
+
openWorldHint?: boolean | undefined;
|
|
134
|
+
} | undefined;
|
|
135
|
+
execution?: {
|
|
136
|
+
taskSupport?: "required" | "optional" | "forbidden" | undefined;
|
|
137
|
+
} | undefined;
|
|
138
|
+
_meta?: {
|
|
139
|
+
[x: string]: unknown;
|
|
140
|
+
} | undefined;
|
|
141
|
+
icons?: {
|
|
142
|
+
src: string;
|
|
143
|
+
mimeType?: string | undefined;
|
|
144
|
+
sizes?: string[] | undefined;
|
|
145
|
+
theme?: "light" | "dark" | undefined;
|
|
146
|
+
}[] | undefined;
|
|
147
|
+
title?: string | undefined;
|
|
148
|
+
}[];
|
|
149
|
+
}>;
|
|
150
|
+
listResources: (extra: RuntimeRequestExtra) => Promise<ListResourcesResult>;
|
|
151
|
+
listResourceTemplates: (extra: RuntimeRequestExtra) => Promise<{
|
|
152
|
+
resourceTemplates: never[];
|
|
153
|
+
}>;
|
|
154
|
+
readResource: (request: ReadResourceRequest, extra: RuntimeRequestExtra) => Promise<ReadResourceResult>;
|
|
155
|
+
listPrompts: (extra: RuntimeRequestExtra) => Promise<{
|
|
156
|
+
prompts: {
|
|
157
|
+
name: string;
|
|
158
|
+
description?: string | undefined;
|
|
159
|
+
arguments?: {
|
|
160
|
+
name: string;
|
|
161
|
+
description?: string | undefined;
|
|
162
|
+
required?: boolean | undefined;
|
|
163
|
+
}[] | undefined;
|
|
164
|
+
_meta?: {
|
|
165
|
+
[x: string]: unknown;
|
|
166
|
+
} | undefined;
|
|
167
|
+
icons?: {
|
|
168
|
+
src: string;
|
|
169
|
+
mimeType?: string | undefined;
|
|
170
|
+
sizes?: string[] | undefined;
|
|
171
|
+
theme?: "light" | "dark" | undefined;
|
|
172
|
+
}[] | undefined;
|
|
173
|
+
title?: string | undefined;
|
|
174
|
+
}[];
|
|
175
|
+
}>;
|
|
176
|
+
getPrompt: (request: GetPromptRequest, extra: RuntimeRequestExtra) => Promise<{
|
|
177
|
+
[x: string]: unknown;
|
|
178
|
+
messages: {
|
|
179
|
+
role: "user" | "assistant";
|
|
180
|
+
content: {
|
|
181
|
+
type: "text";
|
|
182
|
+
text: string;
|
|
183
|
+
annotations?: {
|
|
184
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
185
|
+
priority?: number | undefined;
|
|
186
|
+
lastModified?: string | undefined;
|
|
187
|
+
} | undefined;
|
|
188
|
+
_meta?: {
|
|
189
|
+
[x: string]: unknown;
|
|
190
|
+
} | undefined;
|
|
191
|
+
} | {
|
|
192
|
+
type: "image";
|
|
193
|
+
data: string;
|
|
194
|
+
mimeType: string;
|
|
195
|
+
annotations?: {
|
|
196
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
197
|
+
priority?: number | undefined;
|
|
198
|
+
lastModified?: string | undefined;
|
|
199
|
+
} | undefined;
|
|
200
|
+
_meta?: {
|
|
201
|
+
[x: string]: unknown;
|
|
202
|
+
} | undefined;
|
|
203
|
+
} | {
|
|
204
|
+
type: "audio";
|
|
205
|
+
data: string;
|
|
206
|
+
mimeType: string;
|
|
207
|
+
annotations?: {
|
|
208
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
209
|
+
priority?: number | undefined;
|
|
210
|
+
lastModified?: string | undefined;
|
|
211
|
+
} | undefined;
|
|
212
|
+
_meta?: {
|
|
213
|
+
[x: string]: unknown;
|
|
214
|
+
} | undefined;
|
|
215
|
+
} | {
|
|
216
|
+
uri: string;
|
|
217
|
+
name: string;
|
|
218
|
+
type: "resource_link";
|
|
219
|
+
description?: string | undefined;
|
|
220
|
+
mimeType?: string | undefined;
|
|
221
|
+
size?: number | undefined;
|
|
222
|
+
annotations?: {
|
|
223
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
224
|
+
priority?: number | undefined;
|
|
225
|
+
lastModified?: string | undefined;
|
|
226
|
+
} | undefined;
|
|
227
|
+
_meta?: {
|
|
228
|
+
[x: string]: unknown;
|
|
229
|
+
} | undefined;
|
|
230
|
+
icons?: {
|
|
231
|
+
src: string;
|
|
232
|
+
mimeType?: string | undefined;
|
|
233
|
+
sizes?: string[] | undefined;
|
|
234
|
+
theme?: "light" | "dark" | undefined;
|
|
235
|
+
}[] | undefined;
|
|
236
|
+
title?: string | undefined;
|
|
237
|
+
} | {
|
|
238
|
+
type: "resource";
|
|
239
|
+
resource: {
|
|
240
|
+
uri: string;
|
|
241
|
+
text: string;
|
|
242
|
+
mimeType?: string | undefined;
|
|
243
|
+
_meta?: {
|
|
244
|
+
[x: string]: unknown;
|
|
245
|
+
} | undefined;
|
|
246
|
+
} | {
|
|
247
|
+
uri: string;
|
|
248
|
+
blob: string;
|
|
249
|
+
mimeType?: string | undefined;
|
|
250
|
+
_meta?: {
|
|
251
|
+
[x: string]: unknown;
|
|
252
|
+
} | undefined;
|
|
253
|
+
};
|
|
254
|
+
annotations?: {
|
|
255
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
256
|
+
priority?: number | undefined;
|
|
257
|
+
lastModified?: string | undefined;
|
|
258
|
+
} | undefined;
|
|
259
|
+
_meta?: {
|
|
260
|
+
[x: string]: unknown;
|
|
261
|
+
} | undefined;
|
|
262
|
+
};
|
|
263
|
+
}[];
|
|
264
|
+
_meta?: {
|
|
265
|
+
[x: string]: unknown;
|
|
266
|
+
progressToken?: string | number | undefined;
|
|
267
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
268
|
+
taskId: string;
|
|
269
|
+
} | undefined;
|
|
270
|
+
} | undefined;
|
|
271
|
+
description?: string | undefined;
|
|
272
|
+
}>;
|
|
273
|
+
callTool: (request: CallToolRequest, extra: RuntimeRequestExtra, onToolResolved?: RuntimeToolResolutionObserver) => Promise<CallToolResult>;
|
|
274
|
+
};
|
package/build/runtime.js
CHANGED
|
@@ -9,8 +9,11 @@ import { getPrompt, listPrompts } from './prompts.js';
|
|
|
9
9
|
import { composeResourceProviders, investigationPlaybookResourceProvider, } from './investigation-playbook.js';
|
|
10
10
|
import { JSON_SCHEMA_DRAFT_2020_12, tryParseJsonLike } from './shared.js';
|
|
11
11
|
import { displayKbTitle } from './kb-search.js';
|
|
12
|
+
import { applyFalsyRequestIdCancellationCompat } from './mcp-server-compat.js';
|
|
12
13
|
export const SERVER_NAME = 'convert-api';
|
|
13
|
-
export const SERVER_VERSION_FALLBACK = '0.0.1-beta.
|
|
14
|
+
export const SERVER_VERSION_FALLBACK = '0.0.1-beta.16';
|
|
15
|
+
export const MAX_SEARCH_QUERY_CHARACTERS = 500;
|
|
16
|
+
const MAX_RUNTIME_TOOL_CATALOG_VARIANTS = 16;
|
|
14
17
|
const noopLogger = { log: () => { } };
|
|
15
18
|
function failOpenLogger(logger) {
|
|
16
19
|
return {
|
|
@@ -175,6 +178,7 @@ export const canonicalSearchTool = {
|
|
|
175
178
|
properties: {
|
|
176
179
|
query: {
|
|
177
180
|
type: 'string',
|
|
181
|
+
maxLength: MAX_SEARCH_QUERY_CHARACTERS,
|
|
178
182
|
description: 'Search query for Convert.com documentation.',
|
|
179
183
|
},
|
|
180
184
|
},
|
|
@@ -250,15 +254,47 @@ export const canonicalFetchTool = {
|
|
|
250
254
|
export function getToolsForClient(toolSet = 'reporting', feedbackEnabled = true, executionContext) {
|
|
251
255
|
return [canonicalSearchTool, canonicalFetchTool, ...getNamespacedTools(toolSet, executionContext), ...getOperatorTools(toolSet, feedbackEnabled)];
|
|
252
256
|
}
|
|
253
|
-
function
|
|
257
|
+
function getRuntimeToolCatalog(options, toolSet, executionContext) {
|
|
258
|
+
const feedbackEnabled = resolveBooleanOption(options.feedbackEnabled);
|
|
259
|
+
const notificationsEnabled = resolveBooleanOption(options.notificationsEnabled);
|
|
260
|
+
const decoratorKey = options.toolCatalogDecoratorCacheKey?.();
|
|
261
|
+
const cacheKey = options.toolCatalogCache && (!options.toolDecorator || decoratorKey !== undefined)
|
|
262
|
+
? [
|
|
263
|
+
toolSet,
|
|
264
|
+
`feedback=${feedbackEnabled}`,
|
|
265
|
+
`notifications=${notificationsEnabled}`,
|
|
266
|
+
`account-wide=${executionContext?.authorizedAccounts?.some((account) => account.projects === null) === true}`,
|
|
267
|
+
`decorator=${decoratorKey ?? 'none'}`,
|
|
268
|
+
].join(':')
|
|
269
|
+
: undefined;
|
|
270
|
+
const cached = cacheKey ? options.toolCatalogCache?.get(cacheKey) : undefined;
|
|
271
|
+
if (cached)
|
|
272
|
+
return cached;
|
|
254
273
|
const tools = [
|
|
255
|
-
...getToolsForClient(toolSet,
|
|
274
|
+
...getToolsForClient(toolSet, feedbackEnabled, {
|
|
256
275
|
...executionContext,
|
|
257
|
-
notificationsEnabled
|
|
276
|
+
notificationsEnabled,
|
|
258
277
|
}),
|
|
259
278
|
...(options.toolExtensions ?? []).flatMap((extension) => extension.tools(toolSet)),
|
|
260
279
|
];
|
|
261
|
-
|
|
280
|
+
const decorated = options.toolDecorator ? tools.map(options.toolDecorator) : tools;
|
|
281
|
+
const snapshot = {
|
|
282
|
+
tools: decorated,
|
|
283
|
+
toolsByName: new Map(decorated.map((tool) => [tool.name, tool])),
|
|
284
|
+
};
|
|
285
|
+
if (cacheKey && options.toolCatalogCache) {
|
|
286
|
+
while (options.toolCatalogCache.size >= MAX_RUNTIME_TOOL_CATALOG_VARIANTS) {
|
|
287
|
+
const oldest = options.toolCatalogCache.keys().next().value;
|
|
288
|
+
if (typeof oldest !== 'string')
|
|
289
|
+
break;
|
|
290
|
+
options.toolCatalogCache.delete(oldest);
|
|
291
|
+
}
|
|
292
|
+
options.toolCatalogCache.set(cacheKey, snapshot);
|
|
293
|
+
}
|
|
294
|
+
return snapshot;
|
|
295
|
+
}
|
|
296
|
+
function getRuntimeTools(options, toolSet, executionContext) {
|
|
297
|
+
return getRuntimeToolCatalog(options, toolSet, executionContext).tools;
|
|
262
298
|
}
|
|
263
299
|
function canonicalKbId(id) {
|
|
264
300
|
return id.startsWith('convert-kb:') ? id : `convert-kb:${id}`;
|
|
@@ -300,39 +336,59 @@ function resolveServerTitle(serverTitle) {
|
|
|
300
336
|
const value = typeof serverTitle === 'function' ? serverTitle() : serverTitle;
|
|
301
337
|
return value?.trim() || 'Convert MCP';
|
|
302
338
|
}
|
|
303
|
-
export function
|
|
304
|
-
const logger = failOpenLogger(options.logger ?? noopLogger);
|
|
305
|
-
const resourceProvider = composeResourceProviders(investigationPlaybookResourceProvider, options.resourceProvider);
|
|
306
|
-
const hasResources = true;
|
|
339
|
+
export function convertMcpServerDefinition(options) {
|
|
307
340
|
const initialToolSet = resolveToolSet(options.toolsForClient);
|
|
308
341
|
const instructions = typeof options.serverInstructions === 'function'
|
|
309
342
|
? options.serverInstructions(initialToolSet)
|
|
310
343
|
: options.serverInstructions ?? serverInstructionsForToolSet(initialToolSet);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
344
|
+
return {
|
|
345
|
+
implementation: {
|
|
346
|
+
name: SERVER_NAME,
|
|
347
|
+
version: options.serverVersion ?? SERVER_VERSION_FALLBACK,
|
|
348
|
+
title: resolveServerTitle(options.serverTitle),
|
|
349
|
+
description: "Convert.com MCP tools for experimentation workflows, account data, and product documentation.",
|
|
350
|
+
websiteUrl: resolveWebsiteUrl(options.websiteUrl),
|
|
351
|
+
icons: convertBrandIcons(resolveIconBaseUrl(options.iconBaseUrl)),
|
|
352
|
+
},
|
|
353
|
+
options: {
|
|
354
|
+
capabilities: {
|
|
355
|
+
tools: { listChanged: true },
|
|
356
|
+
prompts: { listChanged: true },
|
|
357
|
+
resources: { listChanged: true },
|
|
358
|
+
},
|
|
359
|
+
instructions,
|
|
323
360
|
},
|
|
324
|
-
|
|
325
|
-
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
export function createConvertMcpServer(options) {
|
|
364
|
+
const definition = convertMcpServerDefinition(options);
|
|
365
|
+
const server = new Server(definition.implementation, definition.options);
|
|
366
|
+
applyFalsyRequestIdCancellationCompat(server);
|
|
367
|
+
const handlers = createConvertMcpHandlers(options, () => server.getClientVersion());
|
|
368
|
+
server.setRequestHandler(ListToolsRequestSchema, (_request, extra) => handlers.listTools(extra));
|
|
369
|
+
server.setRequestHandler(ListResourcesRequestSchema, (_request, extra) => handlers.listResources(extra));
|
|
370
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, (_request, extra) => handlers.listResourceTemplates(extra));
|
|
371
|
+
server.setRequestHandler(ReadResourceRequestSchema, (request, extra) => handlers.readResource(request, extra));
|
|
372
|
+
server.setRequestHandler(ListPromptsRequestSchema, (_request, extra) => handlers.listPrompts(extra));
|
|
373
|
+
server.setRequestHandler(GetPromptRequestSchema, (request, extra) => handlers.getPrompt(request, extra));
|
|
374
|
+
server.setRequestHandler(CallToolRequestSchema, (request, extra) => handlers.callTool(request, extra));
|
|
375
|
+
return server;
|
|
376
|
+
}
|
|
377
|
+
// Both protocol adapters register these same handlers. No transport may bypass
|
|
378
|
+
// the namespace dispatcher, grant checks, or target-bound confirmations.
|
|
379
|
+
export function createConvertMcpHandlers(options, clientInfoFor) {
|
|
380
|
+
const logger = failOpenLogger(options.logger ?? noopLogger);
|
|
381
|
+
const resourceProvider = composeResourceProviders(investigationPlaybookResourceProvider, options.resourceProvider);
|
|
326
382
|
let staticCatalogRevisionsPromise;
|
|
327
|
-
const toolCatalogRevisionPromises = new Map();
|
|
328
|
-
const staticCatalogRevisions = () => {
|
|
383
|
+
const toolCatalogRevisionPromises = options.toolCatalogRevisionCache ?? new Map();
|
|
384
|
+
const staticCatalogRevisions = (extra) => {
|
|
329
385
|
staticCatalogRevisionsPromise ??= Promise.all([
|
|
330
386
|
catalogRevision(listPrompts()),
|
|
331
|
-
Promise.resolve(resourceProvider.listResources(
|
|
387
|
+
Promise.resolve(resourceProvider.listResources(clientInfoFor(extra))).then(catalogRevision),
|
|
332
388
|
]).then(([prompts, resources]) => ({ prompts, resources }));
|
|
333
389
|
return staticCatalogRevisionsPromise;
|
|
334
390
|
};
|
|
335
|
-
const catalogRevisions = async (tools, cacheKey) => {
|
|
391
|
+
const catalogRevisions = async (tools, cacheKey, extra) => {
|
|
336
392
|
let toolRevisionPromise = toolCatalogRevisionPromises.get(cacheKey);
|
|
337
393
|
if (!toolRevisionPromise) {
|
|
338
394
|
toolRevisionPromise = catalogRevision(tools);
|
|
@@ -340,7 +396,7 @@ export function createConvertMcpServer(options) {
|
|
|
340
396
|
}
|
|
341
397
|
const [toolRevision, staticRevisions] = await Promise.all([
|
|
342
398
|
toolRevisionPromise,
|
|
343
|
-
staticCatalogRevisions(),
|
|
399
|
+
staticCatalogRevisions(extra),
|
|
344
400
|
]);
|
|
345
401
|
return { tools: toolRevision, ...staticRevisions };
|
|
346
402
|
};
|
|
@@ -366,6 +422,7 @@ export function createConvertMcpServer(options) {
|
|
|
366
422
|
`feedback=${resolveBooleanOption(options.feedbackEnabled)}`,
|
|
367
423
|
`notifications=${resolveBooleanOption(options.notificationsEnabled)}`,
|
|
368
424
|
`account-wide=${context?.authorizedAccounts?.some((account) => account.projects === null) === true}`,
|
|
425
|
+
`decorator=${options.toolCatalogDecoratorCacheKey?.() ?? 'none'}`,
|
|
369
426
|
].join(':');
|
|
370
427
|
const notifyCatalogChanges = async (requestedCatalog, extra, tools, cacheKey) => {
|
|
371
428
|
if (!options.catalogChangeNotifier || !cacheKey)
|
|
@@ -373,7 +430,7 @@ export function createConvertMcpServer(options) {
|
|
|
373
430
|
try {
|
|
374
431
|
await options.catalogChangeNotifier({
|
|
375
432
|
requestedCatalog,
|
|
376
|
-
revisions: await catalogRevisions(tools, cacheKey),
|
|
433
|
+
revisions: await catalogRevisions(tools, cacheKey, extra),
|
|
377
434
|
sendNotification: extra.sendNotification,
|
|
378
435
|
});
|
|
379
436
|
}
|
|
@@ -390,7 +447,7 @@ export function createConvertMcpServer(options) {
|
|
|
390
447
|
}
|
|
391
448
|
}
|
|
392
449
|
};
|
|
393
|
-
|
|
450
|
+
const listToolsHandler = async (extra) => {
|
|
394
451
|
const toolSet = resolveToolSet(options.toolsForClient);
|
|
395
452
|
const resolvedCatalog = await resolveCatalogContextSafely(extra);
|
|
396
453
|
const catalogContext = resolvedCatalog.context;
|
|
@@ -401,54 +458,62 @@ export function createConvertMcpServer(options) {
|
|
|
401
458
|
// request can emit list_changed when the full catalog recovers.
|
|
402
459
|
catalogCacheKey(toolSet, catalogContext));
|
|
403
460
|
return { tools };
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
461
|
+
};
|
|
462
|
+
const listResourcesHandler = async (extra) => {
|
|
463
|
+
const toolSet = resolveToolSet(options.toolsForClient);
|
|
464
|
+
if (options.catalogChangeNotifier) {
|
|
408
465
|
const resolvedCatalog = await resolveCatalogContextSafely(extra);
|
|
409
466
|
const catalogContext = resolvedCatalog.context;
|
|
410
467
|
const tools = getRuntimeTools(options, toolSet, catalogContext);
|
|
411
468
|
await notifyCatalogChanges('resources', extra, tools, resolvedCatalog.authoritative ? catalogCacheKey(toolSet, catalogContext) : undefined);
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
|
|
469
|
+
}
|
|
470
|
+
return { resources: await resourceProvider.listResources(clientInfoFor(extra)) };
|
|
471
|
+
};
|
|
472
|
+
const listResourceTemplatesHandler = async (extra) => {
|
|
473
|
+
if (options.catalogChangeNotifier) {
|
|
415
474
|
const toolSet = resolveToolSet(options.toolsForClient);
|
|
416
475
|
const resolvedCatalog = await resolveCatalogContextSafely(extra);
|
|
417
476
|
const catalogContext = resolvedCatalog.context;
|
|
418
477
|
const tools = getRuntimeTools(options, toolSet, catalogContext);
|
|
419
478
|
await notifyCatalogChanges(undefined, extra, tools, resolvedCatalog.authoritative ? catalogCacheKey(toolSet, catalogContext) : undefined);
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
|
|
479
|
+
}
|
|
480
|
+
return { resourceTemplates: [] };
|
|
481
|
+
};
|
|
482
|
+
const readResourceHandler = async (request, extra) => {
|
|
483
|
+
if (options.catalogChangeNotifier) {
|
|
423
484
|
const toolSet = resolveToolSet(options.toolsForClient);
|
|
424
485
|
const resolvedCatalog = await resolveCatalogContextSafely(extra);
|
|
425
486
|
const catalogContext = resolvedCatalog.context;
|
|
426
487
|
const tools = getRuntimeTools(options, toolSet, catalogContext);
|
|
427
488
|
await notifyCatalogChanges(undefined, extra, tools, resolvedCatalog.authoritative ? catalogCacheKey(toolSet, catalogContext) : undefined);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
489
|
+
}
|
|
490
|
+
const result = await resourceProvider.readResource(request.params.uri, clientInfoFor(extra));
|
|
491
|
+
if (!result) {
|
|
492
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown resource URI: ${request.params.uri}`);
|
|
493
|
+
}
|
|
494
|
+
return result;
|
|
495
|
+
};
|
|
496
|
+
const listPromptsHandler = async (extra) => {
|
|
497
|
+
if (options.catalogChangeNotifier) {
|
|
498
|
+
const toolSet = resolveToolSet(options.toolsForClient);
|
|
499
|
+
const resolvedCatalog = await resolveCatalogContextSafely(extra);
|
|
500
|
+
const catalogContext = resolvedCatalog.context;
|
|
501
|
+
const tools = getRuntimeTools(options, toolSet, catalogContext);
|
|
502
|
+
await notifyCatalogChanges('prompts', extra, tools, resolvedCatalog.authoritative ? catalogCacheKey(toolSet, catalogContext) : undefined);
|
|
503
|
+
}
|
|
441
504
|
return { prompts: listPrompts() };
|
|
442
|
-
}
|
|
443
|
-
|
|
505
|
+
};
|
|
506
|
+
const getPromptHandler = async (request, extra) => {
|
|
444
507
|
const toolSet = resolveToolSet(options.toolsForClient);
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
508
|
+
if (options.catalogChangeNotifier) {
|
|
509
|
+
const resolvedCatalog = await resolveCatalogContextSafely(extra);
|
|
510
|
+
const catalogContext = resolvedCatalog.context;
|
|
511
|
+
const tools = getRuntimeTools(options, toolSet, catalogContext);
|
|
512
|
+
await notifyCatalogChanges(undefined, extra, tools, resolvedCatalog.authoritative ? catalogCacheKey(toolSet, catalogContext) : undefined);
|
|
513
|
+
}
|
|
449
514
|
return getPrompt(request.params.name, request.params.arguments ?? {}, toolSet);
|
|
450
|
-
}
|
|
451
|
-
|
|
515
|
+
};
|
|
516
|
+
const callToolHandler = async (request, extra, onToolResolved) => {
|
|
452
517
|
const { name: toolName, arguments: toolArgs } = request.params;
|
|
453
518
|
const toolSet = resolveToolSet(options.toolsForClient);
|
|
454
519
|
const executionContext = await options.executionContextResolver?.(extra, request);
|
|
@@ -456,7 +521,27 @@ export function createConvertMcpServer(options) {
|
|
|
456
521
|
? { context: executionContext, authoritative: true }
|
|
457
522
|
: await resolveCatalogContextSafely(extra);
|
|
458
523
|
const catalogContext = resolvedCatalog.context;
|
|
459
|
-
|
|
524
|
+
const lightweightBuiltin = toolName === 'search'
|
|
525
|
+
? canonicalSearchTool
|
|
526
|
+
: toolName === 'fetch'
|
|
527
|
+
? canonicalFetchTool
|
|
528
|
+
: undefined;
|
|
529
|
+
// Hosted modern HTTP creates one protocol Server per request. Avoid
|
|
530
|
+
// materializing and decorating the entire hosted catalog immediately
|
|
531
|
+
// before KB search/fetch solely to recover one static output schema. The
|
|
532
|
+
// legacy notifier path still reconciles the complete catalog, and a tool
|
|
533
|
+
// decorator is applied to the selected built-in so projection remains
|
|
534
|
+
// identical to its advertised definition.
|
|
535
|
+
const runtimeCatalog = options.catalogChangeNotifier || (onToolResolved && !lightweightBuiltin)
|
|
536
|
+
? getRuntimeToolCatalog(options, toolSet, catalogContext)
|
|
537
|
+
: undefined;
|
|
538
|
+
const resolvedTool = lightweightBuiltin
|
|
539
|
+
? (options.toolDecorator ? options.toolDecorator(lightweightBuiltin) : lightweightBuiltin)
|
|
540
|
+
: runtimeCatalog?.toolsByName.get(toolName);
|
|
541
|
+
onToolResolved?.(resolvedTool);
|
|
542
|
+
if (runtimeCatalog) {
|
|
543
|
+
await notifyCatalogChanges(undefined, extra, runtimeCatalog.tools, resolvedCatalog.authoritative ? catalogCacheKey(toolSet, catalogContext) : undefined);
|
|
544
|
+
}
|
|
460
545
|
logger.log('callToolRequest', {
|
|
461
546
|
toolName,
|
|
462
547
|
hasAuthContext: Boolean(executionContext?.authContext || executionContext?.credentialsByAccount),
|
|
@@ -503,7 +588,16 @@ export function createConvertMcpServer(options) {
|
|
|
503
588
|
structuredContent: { error: "The 'query' argument is required for search." },
|
|
504
589
|
});
|
|
505
590
|
}
|
|
506
|
-
|
|
591
|
+
if (query.length > MAX_SEARCH_QUERY_CHARACTERS) {
|
|
592
|
+
logger.log('search.invalidArguments');
|
|
593
|
+
const message = `The 'query' argument must be ${MAX_SEARCH_QUERY_CHARACTERS} characters or fewer.`;
|
|
594
|
+
return omitStructuredContentOnError('search', {
|
|
595
|
+
isError: true,
|
|
596
|
+
content: [{ type: 'text', text: JSON.stringify({ error: message }) }],
|
|
597
|
+
structuredContent: { error: message },
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
const results = await options.docs.search(query, 8, extra.signal);
|
|
507
601
|
const payload = toSearchPayload(results);
|
|
508
602
|
logger.log('search.success', { matchCount: payload.results.length });
|
|
509
603
|
return {
|
|
@@ -534,9 +628,9 @@ export function createConvertMcpServer(options) {
|
|
|
534
628
|
}
|
|
535
629
|
const rawId = rawKbId(id);
|
|
536
630
|
const document = options.docs.getDocumentById
|
|
537
|
-
? await options.docs.getDocumentById(rawId)
|
|
631
|
+
? await options.docs.getDocumentById(rawId, extra.signal)
|
|
538
632
|
: null;
|
|
539
|
-
const chunk = document?.requested ?? await options.docs.getById(rawId);
|
|
633
|
+
const chunk = document?.requested ?? await options.docs.getById(rawId, extra.signal);
|
|
540
634
|
if (!chunk) {
|
|
541
635
|
const message = `Knowledge base document not found for id "${id}". Run search again and fetch one of the returned IDs.`;
|
|
542
636
|
logger.log('fetch.notFound');
|
|
@@ -656,8 +750,16 @@ export function createConvertMcpServer(options) {
|
|
|
656
750
|
content: [{ type: 'text', text: `Error: Unknown tool requested: ${toolName}` }],
|
|
657
751
|
structuredContent: { error: `Unknown tool: ${toolName}` },
|
|
658
752
|
};
|
|
659
|
-
}
|
|
660
|
-
return
|
|
753
|
+
};
|
|
754
|
+
return {
|
|
755
|
+
listTools: listToolsHandler,
|
|
756
|
+
listResources: listResourcesHandler,
|
|
757
|
+
listResourceTemplates: listResourceTemplatesHandler,
|
|
758
|
+
readResource: readResourceHandler,
|
|
759
|
+
listPrompts: listPromptsHandler,
|
|
760
|
+
getPrompt: getPromptHandler,
|
|
761
|
+
callTool: callToolHandler,
|
|
762
|
+
};
|
|
661
763
|
}
|
|
662
764
|
const MAX_FETCH_TEXT_BYTES = 384 * 1024;
|
|
663
765
|
function assembleKbDocument(document, sourceComplete) {
|