@juspay/neurolink 9.26.2 → 9.28.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/CHANGELOG.md +12 -0
- package/README.md +59 -9
- package/dist/cli/commands/config.d.ts +4 -4
- package/dist/cli/commands/mcp.d.ts +87 -0
- package/dist/cli/commands/mcp.js +1524 -0
- package/dist/cli/loop/optionsSchema.js +4 -0
- package/dist/core/modules/ToolsManager.js +29 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +27 -1
- package/dist/lib/core/modules/ToolsManager.js +29 -2
- package/dist/lib/index.d.ts +2 -1
- package/dist/lib/index.js +27 -1
- package/dist/lib/mcp/agentExposure.d.ts +228 -0
- package/dist/lib/mcp/agentExposure.js +357 -0
- package/dist/lib/mcp/batching/index.d.ts +11 -0
- package/dist/lib/mcp/batching/index.js +11 -0
- package/dist/lib/mcp/batching/requestBatcher.d.ts +202 -0
- package/dist/lib/mcp/batching/requestBatcher.js +442 -0
- package/dist/lib/mcp/caching/index.d.ts +11 -0
- package/dist/lib/mcp/caching/index.js +11 -0
- package/dist/lib/mcp/caching/toolCache.d.ts +221 -0
- package/dist/lib/mcp/caching/toolCache.js +434 -0
- package/dist/lib/mcp/elicitation/elicitationManager.d.ts +169 -0
- package/dist/lib/mcp/elicitation/elicitationManager.js +377 -0
- package/dist/lib/mcp/elicitation/index.d.ts +11 -0
- package/dist/lib/mcp/elicitation/index.js +12 -0
- package/dist/lib/mcp/elicitation/types.d.ts +278 -0
- package/dist/lib/mcp/elicitation/types.js +11 -0
- package/dist/lib/mcp/elicitationProtocol.d.ts +228 -0
- package/dist/lib/mcp/elicitationProtocol.js +376 -0
- package/dist/lib/mcp/enhancedToolDiscovery.d.ts +205 -0
- package/dist/lib/mcp/enhancedToolDiscovery.js +482 -0
- package/dist/lib/mcp/index.d.ts +38 -1
- package/dist/lib/mcp/index.js +36 -3
- package/dist/lib/mcp/mcpRegistryClient.d.ts +332 -0
- package/dist/lib/mcp/mcpRegistryClient.js +489 -0
- package/dist/lib/mcp/mcpServerBase.d.ts +227 -0
- package/dist/lib/mcp/mcpServerBase.js +374 -0
- package/dist/lib/mcp/multiServerManager.d.ts +310 -0
- package/dist/lib/mcp/multiServerManager.js +580 -0
- package/dist/lib/mcp/routing/index.d.ts +11 -0
- package/dist/lib/mcp/routing/index.js +11 -0
- package/dist/lib/mcp/routing/toolRouter.d.ts +219 -0
- package/dist/lib/mcp/routing/toolRouter.js +417 -0
- package/dist/lib/mcp/serverCapabilities.d.ts +341 -0
- package/dist/lib/mcp/serverCapabilities.js +503 -0
- package/dist/lib/mcp/toolAnnotations.d.ts +154 -0
- package/dist/lib/mcp/toolAnnotations.js +240 -0
- package/dist/lib/mcp/toolConverter.d.ts +178 -0
- package/dist/lib/mcp/toolConverter.js +259 -0
- package/dist/lib/mcp/toolIntegration.d.ts +136 -0
- package/dist/lib/mcp/toolIntegration.js +335 -0
- package/dist/lib/memory/hippocampusInitializer.d.ts +2 -2
- package/dist/lib/memory/hippocampusInitializer.js +1 -1
- package/dist/lib/neurolink.d.ts +275 -2
- package/dist/lib/neurolink.js +596 -56
- package/dist/lib/providers/litellm.d.ts +10 -0
- package/dist/lib/providers/litellm.js +104 -2
- package/dist/lib/types/configTypes.d.ts +56 -0
- package/dist/lib/types/conversation.d.ts +2 -2
- package/dist/lib/types/generateTypes.d.ts +4 -0
- package/dist/lib/types/index.d.ts +2 -1
- package/dist/lib/types/modelTypes.d.ts +6 -6
- package/dist/lib/types/streamTypes.d.ts +2 -0
- package/dist/lib/types/tools.d.ts +2 -0
- package/dist/lib/utils/pricing.js +177 -17
- package/dist/lib/utils/schemaConversion.d.ts +6 -1
- package/dist/lib/utils/schemaConversion.js +50 -28
- package/dist/lib/workflow/config.d.ts +16 -16
- package/dist/mcp/agentExposure.d.ts +228 -0
- package/dist/mcp/agentExposure.js +356 -0
- package/dist/mcp/batching/index.d.ts +11 -0
- package/dist/mcp/batching/index.js +10 -0
- package/dist/mcp/batching/requestBatcher.d.ts +202 -0
- package/dist/mcp/batching/requestBatcher.js +441 -0
- package/dist/mcp/caching/index.d.ts +11 -0
- package/dist/mcp/caching/index.js +10 -0
- package/dist/mcp/caching/toolCache.d.ts +221 -0
- package/dist/mcp/caching/toolCache.js +433 -0
- package/dist/mcp/elicitation/elicitationManager.d.ts +169 -0
- package/dist/mcp/elicitation/elicitationManager.js +376 -0
- package/dist/mcp/elicitation/index.d.ts +11 -0
- package/dist/mcp/elicitation/index.js +11 -0
- package/dist/mcp/elicitation/types.d.ts +278 -0
- package/dist/mcp/elicitation/types.js +10 -0
- package/dist/mcp/elicitationProtocol.d.ts +228 -0
- package/dist/mcp/elicitationProtocol.js +375 -0
- package/dist/mcp/enhancedToolDiscovery.d.ts +205 -0
- package/dist/mcp/enhancedToolDiscovery.js +481 -0
- package/dist/mcp/index.d.ts +38 -1
- package/dist/mcp/index.js +36 -3
- package/dist/mcp/mcpRegistryClient.d.ts +332 -0
- package/dist/mcp/mcpRegistryClient.js +488 -0
- package/dist/mcp/mcpServerBase.d.ts +227 -0
- package/dist/mcp/mcpServerBase.js +373 -0
- package/dist/mcp/multiServerManager.d.ts +310 -0
- package/dist/mcp/multiServerManager.js +579 -0
- package/dist/mcp/routing/index.d.ts +11 -0
- package/dist/mcp/routing/index.js +10 -0
- package/dist/mcp/routing/toolRouter.d.ts +219 -0
- package/dist/mcp/routing/toolRouter.js +416 -0
- package/dist/mcp/serverCapabilities.d.ts +341 -0
- package/dist/mcp/serverCapabilities.js +502 -0
- package/dist/mcp/toolAnnotations.d.ts +154 -0
- package/dist/mcp/toolAnnotations.js +239 -0
- package/dist/mcp/toolConverter.d.ts +178 -0
- package/dist/mcp/toolConverter.js +258 -0
- package/dist/mcp/toolIntegration.d.ts +136 -0
- package/dist/mcp/toolIntegration.js +334 -0
- package/dist/memory/hippocampusInitializer.d.ts +2 -2
- package/dist/memory/hippocampusInitializer.js +1 -1
- package/dist/neurolink.d.ts +275 -2
- package/dist/neurolink.js +596 -56
- package/dist/providers/litellm.d.ts +10 -0
- package/dist/providers/litellm.js +104 -2
- package/dist/types/configTypes.d.ts +56 -0
- package/dist/types/conversation.d.ts +2 -2
- package/dist/types/generateTypes.d.ts +4 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/streamTypes.d.ts +2 -0
- package/dist/types/tools.d.ts +2 -0
- package/dist/utils/pricing.js +177 -17
- package/dist/utils/schemaConversion.d.ts +6 -1
- package/dist/utils/schemaConversion.js +50 -28
- package/package.json +2 -2
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Capabilities - Resources and Prompts
|
|
3
|
+
*
|
|
4
|
+
* Extends MCP server functionality with resource and prompt handling
|
|
5
|
+
* according to MCP specification. This module provides:
|
|
6
|
+
* - Resource registration and management
|
|
7
|
+
* - Prompt template registration and execution
|
|
8
|
+
* - Resource subscription support
|
|
9
|
+
*
|
|
10
|
+
* @module mcp/serverCapabilities
|
|
11
|
+
* @since 8.39.0
|
|
12
|
+
*/
|
|
13
|
+
import { EventEmitter } from "events";
|
|
14
|
+
import { logger } from "../utils/logger.js";
|
|
15
|
+
import { ErrorFactory } from "../utils/errorHandling.js";
|
|
16
|
+
import { withTimeout } from "../utils/async/withTimeout.js";
|
|
17
|
+
/**
|
|
18
|
+
* Server Capabilities Manager
|
|
19
|
+
*
|
|
20
|
+
* Manages resources and prompts for MCP servers.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const capabilities = new ServerCapabilitiesManager({
|
|
25
|
+
* resources: true,
|
|
26
|
+
* prompts: true,
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // Register a resource
|
|
30
|
+
* capabilities.registerResource({
|
|
31
|
+
* uri: "file:///data/config.json",
|
|
32
|
+
* name: "Configuration",
|
|
33
|
+
* mimeType: "application/json",
|
|
34
|
+
* reader: async (uri) => ({
|
|
35
|
+
* uri,
|
|
36
|
+
* mimeType: "application/json",
|
|
37
|
+
* text: JSON.stringify({ key: "value" }),
|
|
38
|
+
* }),
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* // Register a prompt
|
|
42
|
+
* capabilities.registerPrompt({
|
|
43
|
+
* name: "summarize",
|
|
44
|
+
* description: "Summarize text content",
|
|
45
|
+
* arguments: [{ name: "text", required: true }],
|
|
46
|
+
* generator: async (args) => ({
|
|
47
|
+
* messages: [
|
|
48
|
+
* { role: "user", content: { type: "text", text: `Summarize: ${args.text}` } },
|
|
49
|
+
* ],
|
|
50
|
+
* }),
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export class ServerCapabilitiesManager extends EventEmitter {
|
|
55
|
+
config;
|
|
56
|
+
resources = new Map();
|
|
57
|
+
prompts = new Map();
|
|
58
|
+
subscriptions = new Map();
|
|
59
|
+
resourceTemplates = new Map();
|
|
60
|
+
constructor(config = {}) {
|
|
61
|
+
super();
|
|
62
|
+
this.config = {
|
|
63
|
+
resources: config.resources ?? true,
|
|
64
|
+
prompts: config.prompts ?? true,
|
|
65
|
+
resourceSubscriptions: config.resourceSubscriptions ?? true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// ========================================
|
|
69
|
+
// RESOURCE MANAGEMENT
|
|
70
|
+
// ========================================
|
|
71
|
+
/**
|
|
72
|
+
* Register a resource
|
|
73
|
+
*/
|
|
74
|
+
registerResource(resource) {
|
|
75
|
+
if (!this.config.resources) {
|
|
76
|
+
throw ErrorFactory.invalidConfiguration("resources", "Resource support is not enabled");
|
|
77
|
+
}
|
|
78
|
+
this.validateResourceUri(resource.uri);
|
|
79
|
+
this.resources.set(resource.uri, resource);
|
|
80
|
+
this.emit("resourceRegistered", {
|
|
81
|
+
uri: resource.uri,
|
|
82
|
+
name: resource.name,
|
|
83
|
+
timestamp: new Date(),
|
|
84
|
+
});
|
|
85
|
+
logger.debug(`[ServerCapabilities] Registered resource: ${resource.uri}`);
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Register a resource template (with URI pattern)
|
|
90
|
+
*/
|
|
91
|
+
registerResourceTemplate(pattern, template) {
|
|
92
|
+
if (!this.config.resources) {
|
|
93
|
+
throw ErrorFactory.invalidConfiguration("resources", "Resource support is not enabled");
|
|
94
|
+
}
|
|
95
|
+
this.resourceTemplates.set(pattern, {
|
|
96
|
+
...template,
|
|
97
|
+
uri: template.uriPattern,
|
|
98
|
+
});
|
|
99
|
+
this.emit("resourceTemplateRegistered", {
|
|
100
|
+
pattern,
|
|
101
|
+
timestamp: new Date(),
|
|
102
|
+
});
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Unregister a resource
|
|
107
|
+
*/
|
|
108
|
+
unregisterResource(uri) {
|
|
109
|
+
const removed = this.resources.delete(uri);
|
|
110
|
+
if (removed) {
|
|
111
|
+
// Clear subscriptions
|
|
112
|
+
this.subscriptions.delete(uri);
|
|
113
|
+
this.emit("resourceUnregistered", {
|
|
114
|
+
uri,
|
|
115
|
+
timestamp: new Date(),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return removed;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* List all resources
|
|
122
|
+
*/
|
|
123
|
+
listResources() {
|
|
124
|
+
return Array.from(this.resources.values()).map((r) => ({
|
|
125
|
+
uri: r.uri,
|
|
126
|
+
name: r.name,
|
|
127
|
+
description: r.description,
|
|
128
|
+
mimeType: r.mimeType,
|
|
129
|
+
size: r.size,
|
|
130
|
+
dynamic: r.dynamic,
|
|
131
|
+
annotations: r.annotations,
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Read a resource
|
|
136
|
+
*/
|
|
137
|
+
async readResource(uri, context) {
|
|
138
|
+
// Check direct resources
|
|
139
|
+
let resource = this.resources.get(uri);
|
|
140
|
+
// Check templates if not found
|
|
141
|
+
if (!resource) {
|
|
142
|
+
resource = this.findResourceTemplate(uri);
|
|
143
|
+
}
|
|
144
|
+
if (!resource) {
|
|
145
|
+
throw ErrorFactory.invalidConfiguration("resource", `Resource not found: ${uri}`);
|
|
146
|
+
}
|
|
147
|
+
const startTime = Date.now();
|
|
148
|
+
const resourceTimeoutMs = 30_000;
|
|
149
|
+
try {
|
|
150
|
+
const content = await withTimeout(resource.reader(uri, context), resourceTimeoutMs, `Resource read timed out after ${resourceTimeoutMs}ms for URI: ${uri}`);
|
|
151
|
+
const duration = Date.now() - startTime;
|
|
152
|
+
this.emit("resourceRead", {
|
|
153
|
+
uri,
|
|
154
|
+
duration,
|
|
155
|
+
success: true,
|
|
156
|
+
timestamp: new Date(),
|
|
157
|
+
});
|
|
158
|
+
return content;
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
const duration = Date.now() - startTime;
|
|
162
|
+
this.emit("resourceRead", {
|
|
163
|
+
uri,
|
|
164
|
+
duration,
|
|
165
|
+
success: false,
|
|
166
|
+
error: error instanceof Error ? error.message : String(error),
|
|
167
|
+
timestamp: new Date(),
|
|
168
|
+
});
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Subscribe to resource changes
|
|
174
|
+
*/
|
|
175
|
+
subscribeToResource(uri, callback) {
|
|
176
|
+
if (!this.config.resourceSubscriptions) {
|
|
177
|
+
throw ErrorFactory.invalidConfiguration("resourceSubscriptions", "Resource subscriptions are not enabled");
|
|
178
|
+
}
|
|
179
|
+
if (!this.subscriptions.has(uri)) {
|
|
180
|
+
this.subscriptions.set(uri, new Set());
|
|
181
|
+
}
|
|
182
|
+
const subs = this.subscriptions.get(uri);
|
|
183
|
+
if (subs) {
|
|
184
|
+
subs.add(callback);
|
|
185
|
+
}
|
|
186
|
+
this.emit("resourceSubscribed", {
|
|
187
|
+
uri,
|
|
188
|
+
timestamp: new Date(),
|
|
189
|
+
});
|
|
190
|
+
// Return unsubscribe function
|
|
191
|
+
return () => {
|
|
192
|
+
const subs = this.subscriptions.get(uri);
|
|
193
|
+
if (subs) {
|
|
194
|
+
subs.delete(callback);
|
|
195
|
+
if (subs.size === 0) {
|
|
196
|
+
this.subscriptions.delete(uri);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
this.emit("resourceUnsubscribed", {
|
|
200
|
+
uri,
|
|
201
|
+
timestamp: new Date(),
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Notify subscribers of resource change
|
|
207
|
+
*/
|
|
208
|
+
async notifyResourceChanged(uri) {
|
|
209
|
+
const subscribers = this.subscriptions.get(uri);
|
|
210
|
+
if (!subscribers || subscribers.size === 0) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const content = await this.readResource(uri);
|
|
215
|
+
for (const callback of subscribers) {
|
|
216
|
+
try {
|
|
217
|
+
const result = callback(uri, content);
|
|
218
|
+
// Handle async callbacks that return promises
|
|
219
|
+
if (result && typeof result.catch === "function") {
|
|
220
|
+
result.catch((error) => {
|
|
221
|
+
logger.error(`[ServerCapabilities] Async error notifying subscriber for ${uri}:`, error);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
logger.error(`[ServerCapabilities] Error notifying subscriber for ${uri}:`, error);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
this.emit("resourceChanged", {
|
|
230
|
+
uri,
|
|
231
|
+
subscriberCount: subscribers.size,
|
|
232
|
+
timestamp: new Date(),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
logger.error(`[ServerCapabilities] Error reading resource for notification: ${uri}`, error);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get resource by URI
|
|
241
|
+
*/
|
|
242
|
+
getResource(uri) {
|
|
243
|
+
return this.resources.get(uri) ?? this.findResourceTemplate(uri);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Validate resource URI
|
|
247
|
+
*/
|
|
248
|
+
validateResourceUri(uri) {
|
|
249
|
+
try {
|
|
250
|
+
new URL(uri);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Allow non-URL URIs but warn
|
|
254
|
+
logger.warn(`[ServerCapabilities] Resource URI is not a valid URL: ${uri}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Find matching resource template
|
|
259
|
+
*/
|
|
260
|
+
findResourceTemplate(uri) {
|
|
261
|
+
for (const [pattern, template] of this.resourceTemplates) {
|
|
262
|
+
if (this.matchesPattern(uri, pattern)) {
|
|
263
|
+
return {
|
|
264
|
+
...template,
|
|
265
|
+
uri,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Check if URI matches a pattern
|
|
273
|
+
*/
|
|
274
|
+
matchesPattern(uri, pattern) {
|
|
275
|
+
// Guard against excessively long patterns that could cause ReDoS
|
|
276
|
+
if (pattern.length > 200) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
// Escape regex metacharacters, then replace glob wildcards
|
|
280
|
+
const regexPattern = pattern
|
|
281
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
282
|
+
.replace(/\*/g, ".*")
|
|
283
|
+
.replace(/\?/g, ".")
|
|
284
|
+
// Restore URI template placeholders: \{...\} -> [^/]+
|
|
285
|
+
.replace(/\\\{[^}]*\\\}/g, "[^/]+");
|
|
286
|
+
try {
|
|
287
|
+
return new RegExp(`^${regexPattern}$`).test(uri);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Invalid regex pattern — treat as non-match
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// ========================================
|
|
295
|
+
// PROMPT MANAGEMENT
|
|
296
|
+
// ========================================
|
|
297
|
+
/**
|
|
298
|
+
* Register a prompt
|
|
299
|
+
*/
|
|
300
|
+
registerPrompt(prompt) {
|
|
301
|
+
if (!this.config.prompts) {
|
|
302
|
+
throw ErrorFactory.invalidConfiguration("prompts", "Prompt support is not enabled");
|
|
303
|
+
}
|
|
304
|
+
this.validatePromptName(prompt.name);
|
|
305
|
+
this.prompts.set(prompt.name, prompt);
|
|
306
|
+
this.emit("promptRegistered", {
|
|
307
|
+
name: prompt.name,
|
|
308
|
+
timestamp: new Date(),
|
|
309
|
+
});
|
|
310
|
+
logger.debug(`[ServerCapabilities] Registered prompt: ${prompt.name}`);
|
|
311
|
+
return this;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Unregister a prompt
|
|
315
|
+
*/
|
|
316
|
+
unregisterPrompt(name) {
|
|
317
|
+
const removed = this.prompts.delete(name);
|
|
318
|
+
if (removed) {
|
|
319
|
+
this.emit("promptUnregistered", {
|
|
320
|
+
name,
|
|
321
|
+
timestamp: new Date(),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return removed;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* List all prompts
|
|
328
|
+
*/
|
|
329
|
+
listPrompts() {
|
|
330
|
+
return Array.from(this.prompts.values()).map((p) => ({
|
|
331
|
+
name: p.name,
|
|
332
|
+
description: p.description,
|
|
333
|
+
arguments: p.arguments,
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Get a prompt
|
|
338
|
+
*/
|
|
339
|
+
async getPrompt(name, args = {}, context) {
|
|
340
|
+
const prompt = this.prompts.get(name);
|
|
341
|
+
if (!prompt) {
|
|
342
|
+
throw ErrorFactory.invalidConfiguration("prompt", `Prompt not found: ${name}`);
|
|
343
|
+
}
|
|
344
|
+
// Validate required arguments
|
|
345
|
+
for (const arg of prompt.arguments ?? []) {
|
|
346
|
+
if (arg.required && args[arg.name] === undefined) {
|
|
347
|
+
throw ErrorFactory.invalidConfiguration("promptArgument", `Missing required argument: ${arg.name}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const startTime = Date.now();
|
|
351
|
+
const promptTimeoutMs = 30_000;
|
|
352
|
+
try {
|
|
353
|
+
const result = await withTimeout(prompt.generator(args, context), promptTimeoutMs, `Prompt generation timed out after ${promptTimeoutMs}ms for prompt: ${name}`);
|
|
354
|
+
const duration = Date.now() - startTime;
|
|
355
|
+
this.emit("promptGenerated", {
|
|
356
|
+
name,
|
|
357
|
+
duration,
|
|
358
|
+
success: true,
|
|
359
|
+
messageCount: result.messages.length,
|
|
360
|
+
timestamp: new Date(),
|
|
361
|
+
});
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
const duration = Date.now() - startTime;
|
|
366
|
+
this.emit("promptGenerated", {
|
|
367
|
+
name,
|
|
368
|
+
duration,
|
|
369
|
+
success: false,
|
|
370
|
+
error: error instanceof Error ? error.message : String(error),
|
|
371
|
+
timestamp: new Date(),
|
|
372
|
+
});
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Get prompt by name
|
|
378
|
+
*/
|
|
379
|
+
getPromptDefinition(name) {
|
|
380
|
+
return this.prompts.get(name);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Validate prompt name
|
|
384
|
+
*/
|
|
385
|
+
validatePromptName(name) {
|
|
386
|
+
if (!name || typeof name !== "string") {
|
|
387
|
+
throw ErrorFactory.invalidConfiguration("promptName", "Prompt name is required and must be a string");
|
|
388
|
+
}
|
|
389
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(name)) {
|
|
390
|
+
throw ErrorFactory.invalidConfiguration("promptName", "Prompt name must start with a letter or underscore and contain only alphanumeric characters, underscores, and hyphens");
|
|
391
|
+
}
|
|
392
|
+
if (this.prompts.has(name)) {
|
|
393
|
+
throw ErrorFactory.invalidConfiguration("promptName", `Prompt '${name}' is already registered`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// ========================================
|
|
397
|
+
// UTILITY METHODS
|
|
398
|
+
// ========================================
|
|
399
|
+
/**
|
|
400
|
+
* Get capabilities object for MCP protocol
|
|
401
|
+
*/
|
|
402
|
+
getCapabilities() {
|
|
403
|
+
const capabilities = {};
|
|
404
|
+
if (this.config.resources) {
|
|
405
|
+
capabilities.resources = {
|
|
406
|
+
subscribe: this.config.resourceSubscriptions,
|
|
407
|
+
listChanged: true,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
if (this.config.prompts) {
|
|
411
|
+
capabilities.prompts = {
|
|
412
|
+
listChanged: true,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return capabilities;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get statistics
|
|
419
|
+
*/
|
|
420
|
+
getStatistics() {
|
|
421
|
+
let subscriptionCount = 0;
|
|
422
|
+
for (const subs of this.subscriptions.values()) {
|
|
423
|
+
subscriptionCount += subs.size;
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
resourceCount: this.resources.size,
|
|
427
|
+
templateCount: this.resourceTemplates.size,
|
|
428
|
+
promptCount: this.prompts.size,
|
|
429
|
+
subscriptionCount,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Clear all resources and prompts
|
|
434
|
+
*/
|
|
435
|
+
clear() {
|
|
436
|
+
this.resources.clear();
|
|
437
|
+
this.resourceTemplates.clear();
|
|
438
|
+
this.prompts.clear();
|
|
439
|
+
this.subscriptions.clear();
|
|
440
|
+
this.emit("cleared", { timestamp: new Date() });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Create a simple text resource
|
|
445
|
+
*/
|
|
446
|
+
export function createTextResource(uri, name, content, options) {
|
|
447
|
+
return {
|
|
448
|
+
uri,
|
|
449
|
+
name,
|
|
450
|
+
description: options?.description,
|
|
451
|
+
mimeType: "text/plain",
|
|
452
|
+
dynamic: options?.dynamic ?? typeof content === "function",
|
|
453
|
+
reader: async (requestUri) => ({
|
|
454
|
+
uri: requestUri,
|
|
455
|
+
mimeType: "text/plain",
|
|
456
|
+
text: typeof content === "function" ? await content() : content,
|
|
457
|
+
}),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Create a JSON resource
|
|
462
|
+
*/
|
|
463
|
+
export function createJsonResource(uri, name, content, options) {
|
|
464
|
+
return {
|
|
465
|
+
uri,
|
|
466
|
+
name,
|
|
467
|
+
description: options?.description,
|
|
468
|
+
mimeType: "application/json",
|
|
469
|
+
dynamic: options?.dynamic ?? typeof content === "function",
|
|
470
|
+
reader: async (requestUri) => ({
|
|
471
|
+
uri: requestUri,
|
|
472
|
+
mimeType: "application/json",
|
|
473
|
+
text: JSON.stringify(typeof content === "function" ? await content() : content, null, 2),
|
|
474
|
+
}),
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Create a simple prompt template
|
|
479
|
+
*/
|
|
480
|
+
export function createPrompt(name, template, options) {
|
|
481
|
+
return {
|
|
482
|
+
name,
|
|
483
|
+
description: options?.description,
|
|
484
|
+
arguments: options?.arguments,
|
|
485
|
+
generator: async (args) => {
|
|
486
|
+
// Simple template substitution
|
|
487
|
+
let text = template;
|
|
488
|
+
for (const [key, value] of Object.entries(args)) {
|
|
489
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
490
|
+
text = text.replace(new RegExp(`\\{${escapedKey}\\}`, "g"), () => String(value));
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
messages: [
|
|
494
|
+
{
|
|
495
|
+
role: "user",
|
|
496
|
+
content: { type: "text", text },
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
};
|
|
500
|
+
},
|
|
501
|
+
};
|
|
502
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Annotations System
|
|
3
|
+
*
|
|
4
|
+
* Enhanced tool annotations for MCP tools providing hints to AI models
|
|
5
|
+
* about tool behavior, safety, and execution characteristics.
|
|
6
|
+
*
|
|
7
|
+
* Implements MCP 2024-11-05 specification tool annotations including:
|
|
8
|
+
* - readOnlyHint: Tool only reads data
|
|
9
|
+
* - destructiveHint: Tool performs destructive operations
|
|
10
|
+
* - idempotentHint: Tool can be safely retried
|
|
11
|
+
* - requiresConfirmation: Tool needs user confirmation
|
|
12
|
+
*
|
|
13
|
+
* @module mcp/toolAnnotations
|
|
14
|
+
* @since 8.39.0
|
|
15
|
+
*/
|
|
16
|
+
import type { JsonObject } from "../types/common.js";
|
|
17
|
+
import type { NeuroLinkExecutionContext, ToolResult } from "../types/mcpTypes.js";
|
|
18
|
+
/**
|
|
19
|
+
* Tool annotation metadata for MCP tools
|
|
20
|
+
* Provides hints to AI models about tool behavior and safety
|
|
21
|
+
*/
|
|
22
|
+
export type MCPToolAnnotations = {
|
|
23
|
+
/**
|
|
24
|
+
* Human-readable title for the tool
|
|
25
|
+
*/
|
|
26
|
+
title?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the tool only reads data without side effects
|
|
29
|
+
* When true, AI models may call more freely for information gathering
|
|
30
|
+
*/
|
|
31
|
+
readOnlyHint?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Whether the tool performs destructive operations
|
|
34
|
+
* When true, requires additional confirmation before execution
|
|
35
|
+
*/
|
|
36
|
+
destructiveHint?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the tool can be safely retried without side effects
|
|
39
|
+
* When true, automatic retry is safe on failure
|
|
40
|
+
*/
|
|
41
|
+
idempotentHint?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the tool requires user confirmation before execution
|
|
44
|
+
* When true, triggers HITL confirmation flow
|
|
45
|
+
*/
|
|
46
|
+
requiresConfirmation?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the tool operates on an open world of resources
|
|
49
|
+
* From the MCP protocol specification
|
|
50
|
+
*/
|
|
51
|
+
openWorldHint?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Custom tags for categorization and filtering
|
|
54
|
+
*/
|
|
55
|
+
tags?: string[];
|
|
56
|
+
/**
|
|
57
|
+
* Estimated execution time in milliseconds
|
|
58
|
+
*/
|
|
59
|
+
estimatedDuration?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Rate limit hint (calls per minute)
|
|
62
|
+
*/
|
|
63
|
+
rateLimitHint?: number;
|
|
64
|
+
/**
|
|
65
|
+
* Cost hint (arbitrary units for comparison)
|
|
66
|
+
*/
|
|
67
|
+
costHint?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Complexity level for UI display
|
|
70
|
+
*/
|
|
71
|
+
complexity?: "simple" | "medium" | "complex";
|
|
72
|
+
/**
|
|
73
|
+
* Whether tool execution should be audited/logged
|
|
74
|
+
*/
|
|
75
|
+
auditRequired?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Security classification for the tool
|
|
78
|
+
*/
|
|
79
|
+
securityLevel?: "public" | "internal" | "restricted";
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Enhanced tool definition with annotations
|
|
83
|
+
*/
|
|
84
|
+
export type MCPServerTool = {
|
|
85
|
+
/**
|
|
86
|
+
* Tool name (must be unique within server)
|
|
87
|
+
*/
|
|
88
|
+
name: string;
|
|
89
|
+
/**
|
|
90
|
+
* Tool description for AI models
|
|
91
|
+
*/
|
|
92
|
+
description: string;
|
|
93
|
+
/**
|
|
94
|
+
* JSON Schema for tool input parameters
|
|
95
|
+
*/
|
|
96
|
+
inputSchema?: JsonObject;
|
|
97
|
+
/**
|
|
98
|
+
* Output schema for result validation
|
|
99
|
+
*/
|
|
100
|
+
outputSchema?: JsonObject;
|
|
101
|
+
/**
|
|
102
|
+
* Tool behavior annotations
|
|
103
|
+
*/
|
|
104
|
+
annotations?: MCPToolAnnotations;
|
|
105
|
+
/**
|
|
106
|
+
* Tool execution function
|
|
107
|
+
*/
|
|
108
|
+
execute: (params: unknown, context?: NeuroLinkExecutionContext) => Promise<ToolResult | unknown>;
|
|
109
|
+
/**
|
|
110
|
+
* Additional metadata
|
|
111
|
+
*/
|
|
112
|
+
metadata?: Record<string, unknown>;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Infer annotations from tool definition
|
|
116
|
+
* Uses heuristics based on tool description and name
|
|
117
|
+
*/
|
|
118
|
+
export declare function inferAnnotations(tool: Pick<MCPServerTool, "name" | "description">): MCPToolAnnotations;
|
|
119
|
+
/**
|
|
120
|
+
* Merge multiple annotation objects with precedence
|
|
121
|
+
* Later annotations override earlier ones
|
|
122
|
+
*/
|
|
123
|
+
export declare function mergeAnnotations(...annotationSets: (MCPToolAnnotations | undefined)[]): MCPToolAnnotations;
|
|
124
|
+
/**
|
|
125
|
+
* Validate tool annotations
|
|
126
|
+
* Returns list of validation errors (empty if valid)
|
|
127
|
+
*/
|
|
128
|
+
export declare function validateAnnotations(annotations: MCPToolAnnotations): string[];
|
|
129
|
+
/**
|
|
130
|
+
* Create a tool with default annotations inferred
|
|
131
|
+
*/
|
|
132
|
+
export declare function createAnnotatedTool(tool: Omit<MCPServerTool, "annotations"> & {
|
|
133
|
+
annotations?: MCPToolAnnotations;
|
|
134
|
+
}): MCPServerTool;
|
|
135
|
+
/**
|
|
136
|
+
* Check if a tool requires confirmation based on annotations
|
|
137
|
+
*/
|
|
138
|
+
export declare function requiresConfirmation(tool: MCPServerTool): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Check if a tool is safe for automatic retry
|
|
141
|
+
*/
|
|
142
|
+
export declare function isSafeToRetry(tool: MCPServerTool): boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Get tool safety level based on annotations
|
|
145
|
+
*/
|
|
146
|
+
export declare function getToolSafetyLevel(tool: MCPServerTool): "safe" | "moderate" | "dangerous";
|
|
147
|
+
/**
|
|
148
|
+
* Filter tools by annotation predicates
|
|
149
|
+
*/
|
|
150
|
+
export declare function filterToolsByAnnotations(tools: MCPServerTool[], predicate: (annotations: MCPToolAnnotations) => boolean): MCPServerTool[];
|
|
151
|
+
/**
|
|
152
|
+
* Get human-readable summary of tool annotations
|
|
153
|
+
*/
|
|
154
|
+
export declare function getAnnotationSummary(annotations: MCPToolAnnotations): string;
|