@elizaos/plugin-mcp 1.7.1 → 1.8.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/dist/actions/dynamic-tool-actions.d.ts +16 -0
- package/dist/actions/dynamic-tool-actions.d.ts.map +1 -0
- package/dist/cache/index.d.ts +5 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/schema-cache.d.ts +34 -0
- package/dist/cache/schema-cache.d.ts.map +1 -0
- package/dist/cjs/index.cjs +1161 -1646
- package/dist/cjs/index.js.map +23 -24
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1166 -1667
- package/dist/index.js.map +23 -24
- package/dist/provider.d.ts.map +1 -1
- package/dist/service.d.ts +23 -21
- package/dist/service.d.ts.map +1 -1
- package/dist/tool-compatibility/base.d.ts +25 -0
- package/dist/tool-compatibility/base.d.ts.map +1 -0
- package/dist/tool-compatibility/index.d.ts +7 -52
- package/dist/tool-compatibility/index.d.ts.map +1 -1
- package/dist/tool-compatibility/providers/anthropic.d.ts +2 -3
- package/dist/tool-compatibility/providers/anthropic.d.ts.map +1 -1
- package/dist/tool-compatibility/providers/google.d.ts +2 -3
- package/dist/tool-compatibility/providers/google.d.ts.map +1 -1
- package/dist/tool-compatibility/providers/openai.d.ts +2 -3
- package/dist/tool-compatibility/providers/openai.d.ts.map +1 -1
- package/dist/types.d.ts +53 -88
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/action-naming.d.ts +9 -0
- package/dist/utils/action-naming.d.ts.map +1 -0
- package/dist/utils/error.d.ts +1 -11
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/utils/json.d.ts.map +1 -1
- package/dist/utils/processing.d.ts +5 -11
- package/dist/utils/processing.d.ts.map +1 -1
- package/dist/utils/schema-converter.d.ts +9 -0
- package/dist/utils/schema-converter.d.ts.map +1 -0
- package/dist/utils/validation.d.ts +1 -24
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/wrapper.d.ts +6 -15
- package/dist/utils/wrapper.d.ts.map +1 -1
- package/package.json +3 -1
- package/dist/actions/callToolAction.d.ts +0 -3
- package/dist/actions/callToolAction.d.ts.map +0 -1
- package/dist/templates/feedbackTemplate.d.ts +0 -2
- package/dist/templates/feedbackTemplate.d.ts.map +0 -1
- package/dist/templates/toolReasoningTemplate.d.ts +0 -2
- package/dist/templates/toolReasoningTemplate.d.ts.map +0 -1
- package/dist/templates/toolSelectionTemplate.d.ts +0 -3
- package/dist/templates/toolSelectionTemplate.d.ts.map +0 -1
- package/dist/utils/handler.d.ts +0 -3
- package/dist/utils/handler.d.ts.map +0 -1
- package/dist/utils/schemas.d.ts +0 -69
- package/dist/utils/schemas.d.ts.map +0 -1
- package/dist/utils/selection.d.ts +0 -38
- package/dist/utils/selection.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,585 +1,78 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
for (var name in all)
|
|
4
|
-
__defProp(target, name, {
|
|
5
|
-
get: all[name],
|
|
6
|
-
enumerable: true,
|
|
7
|
-
configurable: true,
|
|
8
|
-
set: (newValue) => all[name] = () => newValue
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { logger as logger7 } from "@elizaos/core";
|
|
12
3
|
|
|
13
|
-
// src/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
var OpenAIMcpCompatibility2, OpenAIReasoningMcpCompatibility;
|
|
20
|
-
var init_openai = __esm(() => {
|
|
21
|
-
OpenAIMcpCompatibility2 = class OpenAIMcpCompatibility2 extends McpToolCompatibility {
|
|
22
|
-
constructor(modelInfo2) {
|
|
23
|
-
super(modelInfo2);
|
|
24
|
-
}
|
|
25
|
-
shouldApply() {
|
|
26
|
-
return this.modelInfo.provider === "openai" && (!this.modelInfo.supportsStructuredOutputs || this.modelInfo.isReasoningModel === true);
|
|
27
|
-
}
|
|
28
|
-
getUnsupportedStringProperties() {
|
|
29
|
-
const baseUnsupported = ["format"];
|
|
30
|
-
if (this.modelInfo.isReasoningModel === true) {
|
|
31
|
-
return [...baseUnsupported, "pattern"];
|
|
32
|
-
}
|
|
33
|
-
if (this.modelInfo.modelId.includes("gpt-3.5") || this.modelInfo.modelId.includes("davinci")) {
|
|
34
|
-
return [...baseUnsupported, "pattern"];
|
|
35
|
-
}
|
|
36
|
-
return baseUnsupported;
|
|
37
|
-
}
|
|
38
|
-
getUnsupportedNumberProperties() {
|
|
39
|
-
if (this.modelInfo.isReasoningModel === true) {
|
|
40
|
-
return ["exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
41
|
-
}
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
44
|
-
getUnsupportedArrayProperties() {
|
|
45
|
-
if (this.modelInfo.isReasoningModel === true) {
|
|
46
|
-
return ["uniqueItems"];
|
|
47
|
-
}
|
|
48
|
-
return [];
|
|
49
|
-
}
|
|
50
|
-
getUnsupportedObjectProperties() {
|
|
51
|
-
return ["minProperties", "maxProperties"];
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
OpenAIReasoningMcpCompatibility = class OpenAIReasoningMcpCompatibility extends McpToolCompatibility {
|
|
55
|
-
constructor(modelInfo2) {
|
|
56
|
-
super(modelInfo2);
|
|
57
|
-
}
|
|
58
|
-
shouldApply() {
|
|
59
|
-
return this.modelInfo.provider === "openai" && this.modelInfo.isReasoningModel === true;
|
|
60
|
-
}
|
|
61
|
-
getUnsupportedStringProperties() {
|
|
62
|
-
return ["format", "pattern", "minLength", "maxLength"];
|
|
63
|
-
}
|
|
64
|
-
getUnsupportedNumberProperties() {
|
|
65
|
-
return ["exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
66
|
-
}
|
|
67
|
-
getUnsupportedArrayProperties() {
|
|
68
|
-
return ["uniqueItems", "minItems", "maxItems"];
|
|
69
|
-
}
|
|
70
|
-
getUnsupportedObjectProperties() {
|
|
71
|
-
return ["minProperties", "maxProperties", "additionalProperties"];
|
|
72
|
-
}
|
|
73
|
-
mergeDescription(originalDescription, constraints) {
|
|
74
|
-
const constraintText = this.formatConstraintsForReasoningModel(constraints);
|
|
75
|
-
if (originalDescription) {
|
|
76
|
-
return `${originalDescription}
|
|
4
|
+
// src/actions/readResourceAction.ts
|
|
5
|
+
import {
|
|
6
|
+
ModelType as ModelType4,
|
|
7
|
+
composePromptFromState as composePromptFromState3,
|
|
8
|
+
logger as logger3
|
|
9
|
+
} from "@elizaos/core";
|
|
77
10
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
formatConstraintsForReasoningModel(constraints) {
|
|
83
|
-
const rules = [];
|
|
84
|
-
if (constraints.minLength) {
|
|
85
|
-
rules.push(`minimum ${constraints.minLength} characters`);
|
|
86
|
-
}
|
|
87
|
-
if (constraints.maxLength) {
|
|
88
|
-
rules.push(`maximum ${constraints.maxLength} characters`);
|
|
89
|
-
}
|
|
90
|
-
if (constraints.minimum !== undefined) {
|
|
91
|
-
rules.push(`must be >= ${constraints.minimum}`);
|
|
92
|
-
}
|
|
93
|
-
if (constraints.maximum !== undefined) {
|
|
94
|
-
rules.push(`must be <= ${constraints.maximum}`);
|
|
95
|
-
}
|
|
96
|
-
if (constraints.format === "email") {
|
|
97
|
-
rules.push(`must be a valid email address`);
|
|
98
|
-
}
|
|
99
|
-
if (constraints.format === "uri" || constraints.format === "url") {
|
|
100
|
-
rules.push(`must be a valid URL`);
|
|
101
|
-
}
|
|
102
|
-
if (constraints.format === "uuid") {
|
|
103
|
-
rules.push(`must be a valid UUID`);
|
|
104
|
-
}
|
|
105
|
-
if (constraints.pattern) {
|
|
106
|
-
rules.push(`must match pattern: ${constraints.pattern}`);
|
|
107
|
-
}
|
|
108
|
-
if (constraints.enum) {
|
|
109
|
-
rules.push(`must be one of: ${constraints.enum.join(", ")}`);
|
|
110
|
-
}
|
|
111
|
-
if (constraints.minItems) {
|
|
112
|
-
rules.push(`array must have at least ${constraints.minItems} items`);
|
|
113
|
-
}
|
|
114
|
-
if (constraints.maxItems) {
|
|
115
|
-
rules.push(`array must have at most ${constraints.maxItems} items`);
|
|
116
|
-
}
|
|
117
|
-
return rules.length > 0 ? rules.join(", ") : JSON.stringify(constraints);
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
});
|
|
11
|
+
// src/templates/resourceSelectionTemplate.ts
|
|
12
|
+
var resourceSelectionTemplate = `
|
|
13
|
+
{{{mcpProvider.text}}}
|
|
121
14
|
|
|
122
|
-
|
|
123
|
-
var exports_anthropic = {};
|
|
124
|
-
__export(exports_anthropic, {
|
|
125
|
-
AnthropicMcpCompatibility: () => AnthropicMcpCompatibility2
|
|
126
|
-
});
|
|
127
|
-
var AnthropicMcpCompatibility2;
|
|
128
|
-
var init_anthropic = __esm(() => {
|
|
129
|
-
AnthropicMcpCompatibility2 = class AnthropicMcpCompatibility2 extends McpToolCompatibility {
|
|
130
|
-
constructor(modelInfo2) {
|
|
131
|
-
super(modelInfo2);
|
|
132
|
-
}
|
|
133
|
-
shouldApply() {
|
|
134
|
-
return this.modelInfo.provider === "anthropic";
|
|
135
|
-
}
|
|
136
|
-
getUnsupportedStringProperties() {
|
|
137
|
-
return [];
|
|
138
|
-
}
|
|
139
|
-
getUnsupportedNumberProperties() {
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
getUnsupportedArrayProperties() {
|
|
143
|
-
return [];
|
|
144
|
-
}
|
|
145
|
-
getUnsupportedObjectProperties() {
|
|
146
|
-
return ["additionalProperties"];
|
|
147
|
-
}
|
|
148
|
-
mergeDescription(originalDescription, constraints) {
|
|
149
|
-
const constraintHints = this.formatConstraintsForAnthropic(constraints);
|
|
150
|
-
if (originalDescription && constraintHints) {
|
|
151
|
-
return `${originalDescription}. ${constraintHints}`;
|
|
152
|
-
} else if (constraintHints) {
|
|
153
|
-
return constraintHints;
|
|
154
|
-
}
|
|
155
|
-
return originalDescription || "";
|
|
156
|
-
}
|
|
157
|
-
formatConstraintsForAnthropic(constraints) {
|
|
158
|
-
const hints = [];
|
|
159
|
-
if (constraints.additionalProperties === false) {
|
|
160
|
-
hints.push("Only use the specified properties");
|
|
161
|
-
}
|
|
162
|
-
if (constraints.format === "date-time") {
|
|
163
|
-
hints.push("Use ISO 8601 date-time format");
|
|
164
|
-
}
|
|
165
|
-
if (constraints.pattern) {
|
|
166
|
-
hints.push(`Must match the pattern: ${constraints.pattern}`);
|
|
167
|
-
}
|
|
168
|
-
return hints.join(". ");
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
});
|
|
15
|
+
{{{recentMessages}}}
|
|
172
16
|
|
|
173
|
-
|
|
174
|
-
var exports_google = {};
|
|
175
|
-
__export(exports_google, {
|
|
176
|
-
GoogleMcpCompatibility: () => GoogleMcpCompatibility2
|
|
177
|
-
});
|
|
178
|
-
var GoogleMcpCompatibility2;
|
|
179
|
-
var init_google = __esm(() => {
|
|
180
|
-
GoogleMcpCompatibility2 = class GoogleMcpCompatibility2 extends McpToolCompatibility {
|
|
181
|
-
constructor(modelInfo2) {
|
|
182
|
-
super(modelInfo2);
|
|
183
|
-
}
|
|
184
|
-
shouldApply() {
|
|
185
|
-
return this.modelInfo.provider === "google";
|
|
186
|
-
}
|
|
187
|
-
getUnsupportedStringProperties() {
|
|
188
|
-
return ["minLength", "maxLength", "pattern", "format"];
|
|
189
|
-
}
|
|
190
|
-
getUnsupportedNumberProperties() {
|
|
191
|
-
return ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
192
|
-
}
|
|
193
|
-
getUnsupportedArrayProperties() {
|
|
194
|
-
return ["minItems", "maxItems", "uniqueItems"];
|
|
195
|
-
}
|
|
196
|
-
getUnsupportedObjectProperties() {
|
|
197
|
-
return ["minProperties", "maxProperties", "additionalProperties"];
|
|
198
|
-
}
|
|
199
|
-
mergeDescription(originalDescription, constraints) {
|
|
200
|
-
const constraintText = this.formatConstraintsForGoogle(constraints);
|
|
201
|
-
if (originalDescription && constraintText) {
|
|
202
|
-
return `${originalDescription}
|
|
17
|
+
# Prompt
|
|
203
18
|
|
|
204
|
-
|
|
205
|
-
} else if (constraintText) {
|
|
206
|
-
return `Constraints: ${constraintText}`;
|
|
207
|
-
}
|
|
208
|
-
return originalDescription || "";
|
|
209
|
-
}
|
|
210
|
-
formatConstraintsForGoogle(constraints) {
|
|
211
|
-
const rules = [];
|
|
212
|
-
if (constraints.minLength) {
|
|
213
|
-
rules.push(`text must be at least ${constraints.minLength} characters long`);
|
|
214
|
-
}
|
|
215
|
-
if (constraints.maxLength) {
|
|
216
|
-
rules.push(`text must be no more than ${constraints.maxLength} characters long`);
|
|
217
|
-
}
|
|
218
|
-
if (constraints.minimum !== undefined) {
|
|
219
|
-
rules.push(`number must be at least ${constraints.minimum}`);
|
|
220
|
-
}
|
|
221
|
-
if (constraints.maximum !== undefined) {
|
|
222
|
-
rules.push(`number must be no more than ${constraints.maximum}`);
|
|
223
|
-
}
|
|
224
|
-
if (constraints.exclusiveMinimum !== undefined) {
|
|
225
|
-
rules.push(`number must be greater than ${constraints.exclusiveMinimum}`);
|
|
226
|
-
}
|
|
227
|
-
if (constraints.exclusiveMaximum !== undefined) {
|
|
228
|
-
rules.push(`number must be less than ${constraints.exclusiveMaximum}`);
|
|
229
|
-
}
|
|
230
|
-
if (constraints.multipleOf) {
|
|
231
|
-
rules.push(`number must be a multiple of ${constraints.multipleOf}`);
|
|
232
|
-
}
|
|
233
|
-
if (constraints.format === "email") {
|
|
234
|
-
rules.push(`must be a valid email address`);
|
|
235
|
-
}
|
|
236
|
-
if (constraints.format === "uri" || constraints.format === "url") {
|
|
237
|
-
rules.push(`must be a valid URL starting with http:// or https://`);
|
|
238
|
-
}
|
|
239
|
-
if (constraints.format === "uuid") {
|
|
240
|
-
rules.push(`must be a valid UUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`);
|
|
241
|
-
}
|
|
242
|
-
if (constraints.format === "date-time") {
|
|
243
|
-
rules.push(`must be a valid ISO 8601 date-time (e.g., 2023-12-25T10:30:00Z)`);
|
|
244
|
-
}
|
|
245
|
-
if (constraints.pattern) {
|
|
246
|
-
rules.push(`must match the regular expression pattern: ${constraints.pattern}`);
|
|
247
|
-
}
|
|
248
|
-
if (constraints.enum && Array.isArray(constraints.enum)) {
|
|
249
|
-
rules.push(`must be exactly one of these values: ${constraints.enum.join(", ")}`);
|
|
250
|
-
}
|
|
251
|
-
if (constraints.minItems) {
|
|
252
|
-
rules.push(`array must contain at least ${constraints.minItems} items`);
|
|
253
|
-
}
|
|
254
|
-
if (constraints.maxItems) {
|
|
255
|
-
rules.push(`array must contain no more than ${constraints.maxItems} items`);
|
|
256
|
-
}
|
|
257
|
-
if (constraints.uniqueItems === true) {
|
|
258
|
-
rules.push(`array items must all be unique (no duplicates)`);
|
|
259
|
-
}
|
|
260
|
-
if (constraints.minProperties) {
|
|
261
|
-
rules.push(`object must have at least ${constraints.minProperties} properties`);
|
|
262
|
-
}
|
|
263
|
-
if (constraints.maxProperties) {
|
|
264
|
-
rules.push(`object must have no more than ${constraints.maxProperties} properties`);
|
|
265
|
-
}
|
|
266
|
-
if (constraints.additionalProperties === false) {
|
|
267
|
-
rules.push(`object must only contain the specified properties, no additional properties allowed`);
|
|
268
|
-
}
|
|
269
|
-
return rules.join("; ");
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
});
|
|
19
|
+
You are an intelligent assistant helping select the right resource to address a user's request.
|
|
273
20
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
processSchema(schema) {
|
|
287
|
-
const processed = { ...schema };
|
|
288
|
-
switch (processed.type) {
|
|
289
|
-
case "string":
|
|
290
|
-
return this.processStringSchema(processed);
|
|
291
|
-
case "number":
|
|
292
|
-
case "integer":
|
|
293
|
-
return this.processNumberSchema(processed);
|
|
294
|
-
case "array":
|
|
295
|
-
return this.processArraySchema(processed);
|
|
296
|
-
case "object":
|
|
297
|
-
return this.processObjectSchema(processed);
|
|
298
|
-
default:
|
|
299
|
-
return this.processGenericSchema(processed);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
processStringSchema(schema) {
|
|
303
|
-
const constraints = {};
|
|
304
|
-
const processed = { ...schema };
|
|
305
|
-
if (typeof schema.minLength === "number") {
|
|
306
|
-
constraints.minLength = schema.minLength;
|
|
307
|
-
}
|
|
308
|
-
if (typeof schema.maxLength === "number") {
|
|
309
|
-
constraints.maxLength = schema.maxLength;
|
|
310
|
-
}
|
|
311
|
-
if (typeof schema.pattern === "string") {
|
|
312
|
-
constraints.pattern = schema.pattern;
|
|
313
|
-
}
|
|
314
|
-
if (typeof schema.format === "string") {
|
|
315
|
-
constraints.format = schema.format;
|
|
316
|
-
}
|
|
317
|
-
if (Array.isArray(schema.enum)) {
|
|
318
|
-
constraints.enum = schema.enum;
|
|
319
|
-
}
|
|
320
|
-
const unsupportedProps = this.getUnsupportedStringProperties();
|
|
321
|
-
for (const prop of unsupportedProps) {
|
|
322
|
-
if (prop in processed) {
|
|
323
|
-
delete processed[prop];
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (Object.keys(constraints).length > 0) {
|
|
327
|
-
processed.description = this.mergeDescription(schema.description, constraints);
|
|
328
|
-
}
|
|
329
|
-
return processed;
|
|
330
|
-
}
|
|
331
|
-
processNumberSchema(schema) {
|
|
332
|
-
const constraints = {};
|
|
333
|
-
const processed = { ...schema };
|
|
334
|
-
if (typeof schema.minimum === "number") {
|
|
335
|
-
constraints.minimum = schema.minimum;
|
|
336
|
-
}
|
|
337
|
-
if (typeof schema.maximum === "number") {
|
|
338
|
-
constraints.maximum = schema.maximum;
|
|
339
|
-
}
|
|
340
|
-
if (typeof schema.exclusiveMinimum === "number") {
|
|
341
|
-
constraints.exclusiveMinimum = schema.exclusiveMinimum;
|
|
342
|
-
}
|
|
343
|
-
if (typeof schema.exclusiveMaximum === "number") {
|
|
344
|
-
constraints.exclusiveMaximum = schema.exclusiveMaximum;
|
|
345
|
-
}
|
|
346
|
-
if (typeof schema.multipleOf === "number") {
|
|
347
|
-
constraints.multipleOf = schema.multipleOf;
|
|
348
|
-
}
|
|
349
|
-
const unsupportedProps = this.getUnsupportedNumberProperties();
|
|
350
|
-
for (const prop of unsupportedProps) {
|
|
351
|
-
if (prop in processed) {
|
|
352
|
-
delete processed[prop];
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
if (Object.keys(constraints).length > 0) {
|
|
356
|
-
processed.description = this.mergeDescription(schema.description, constraints);
|
|
357
|
-
}
|
|
358
|
-
return processed;
|
|
359
|
-
}
|
|
360
|
-
processArraySchema(schema) {
|
|
361
|
-
const constraints = {};
|
|
362
|
-
const processed = { ...schema };
|
|
363
|
-
if (typeof schema.minItems === "number") {
|
|
364
|
-
constraints.minItems = schema.minItems;
|
|
365
|
-
}
|
|
366
|
-
if (typeof schema.maxItems === "number") {
|
|
367
|
-
constraints.maxItems = schema.maxItems;
|
|
368
|
-
}
|
|
369
|
-
if (typeof schema.uniqueItems === "boolean") {
|
|
370
|
-
constraints.uniqueItems = schema.uniqueItems;
|
|
371
|
-
}
|
|
372
|
-
if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
|
|
373
|
-
processed.items = this.processSchema(schema.items);
|
|
374
|
-
}
|
|
375
|
-
const unsupportedProps = this.getUnsupportedArrayProperties();
|
|
376
|
-
for (const prop of unsupportedProps) {
|
|
377
|
-
if (prop in processed) {
|
|
378
|
-
delete processed[prop];
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
if (Object.keys(constraints).length > 0) {
|
|
382
|
-
processed.description = this.mergeDescription(schema.description, constraints);
|
|
383
|
-
}
|
|
384
|
-
return processed;
|
|
385
|
-
}
|
|
386
|
-
processObjectSchema(schema) {
|
|
387
|
-
const constraints = {};
|
|
388
|
-
const processed = { ...schema };
|
|
389
|
-
if (typeof schema.minProperties === "number") {
|
|
390
|
-
constraints.minProperties = schema.minProperties;
|
|
391
|
-
}
|
|
392
|
-
if (typeof schema.maxProperties === "number") {
|
|
393
|
-
constraints.maxProperties = schema.maxProperties;
|
|
394
|
-
}
|
|
395
|
-
if (typeof schema.additionalProperties === "boolean") {
|
|
396
|
-
constraints.additionalProperties = schema.additionalProperties;
|
|
397
|
-
}
|
|
398
|
-
if (schema.properties && typeof schema.properties === "object") {
|
|
399
|
-
processed.properties = {};
|
|
400
|
-
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
401
|
-
if (typeof prop === "object" && !Array.isArray(prop)) {
|
|
402
|
-
processed.properties[key] = this.processSchema(prop);
|
|
403
|
-
} else {
|
|
404
|
-
processed.properties[key] = prop;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
const unsupportedProps = this.getUnsupportedObjectProperties();
|
|
409
|
-
for (const prop of unsupportedProps) {
|
|
410
|
-
if (prop in processed) {
|
|
411
|
-
delete processed[prop];
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
if (Object.keys(constraints).length > 0) {
|
|
415
|
-
processed.description = this.mergeDescription(schema.description, constraints);
|
|
416
|
-
}
|
|
417
|
-
return processed;
|
|
418
|
-
}
|
|
419
|
-
processGenericSchema(schema) {
|
|
420
|
-
const processed = { ...schema };
|
|
421
|
-
if (Array.isArray(schema.oneOf)) {
|
|
422
|
-
processed.oneOf = schema.oneOf.map((s) => typeof s === "object" ? this.processSchema(s) : s);
|
|
423
|
-
}
|
|
424
|
-
if (Array.isArray(schema.anyOf)) {
|
|
425
|
-
processed.anyOf = schema.anyOf.map((s) => typeof s === "object" ? this.processSchema(s) : s);
|
|
426
|
-
}
|
|
427
|
-
if (Array.isArray(schema.allOf)) {
|
|
428
|
-
processed.allOf = schema.allOf.map((s) => typeof s === "object" ? this.processSchema(s) : s);
|
|
429
|
-
}
|
|
430
|
-
return processed;
|
|
431
|
-
}
|
|
432
|
-
mergeDescription(originalDescription, constraints) {
|
|
433
|
-
const constraintJson = JSON.stringify(constraints);
|
|
434
|
-
if (originalDescription) {
|
|
435
|
-
return `${originalDescription}
|
|
436
|
-
${constraintJson}`;
|
|
437
|
-
}
|
|
438
|
-
return constraintJson;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
function detectModelProvider(runtime2) {
|
|
442
|
-
const modelString = runtime2?.modelProvider || runtime2?.model || "";
|
|
443
|
-
const modelId = String(modelString).toLowerCase();
|
|
444
|
-
let provider2 = "unknown";
|
|
445
|
-
let supportsStructuredOutputs = false;
|
|
446
|
-
let isReasoningModel = false;
|
|
447
|
-
if (modelId.includes("openai") || modelId.includes("gpt-") || modelId.includes("o1-") || modelId.includes("o3-")) {
|
|
448
|
-
provider2 = "openai";
|
|
449
|
-
supportsStructuredOutputs = modelId.includes("gpt-4") || modelId.includes("o1") || modelId.includes("o3");
|
|
450
|
-
isReasoningModel = modelId.includes("o1") || modelId.includes("o3");
|
|
451
|
-
} else if (modelId.includes("anthropic") || modelId.includes("claude")) {
|
|
452
|
-
provider2 = "anthropic";
|
|
453
|
-
supportsStructuredOutputs = true;
|
|
454
|
-
} else if (modelId.includes("google") || modelId.includes("gemini")) {
|
|
455
|
-
provider2 = "google";
|
|
456
|
-
supportsStructuredOutputs = true;
|
|
457
|
-
} else if (modelId.includes("openrouter")) {
|
|
458
|
-
provider2 = "openrouter";
|
|
459
|
-
supportsStructuredOutputs = false;
|
|
460
|
-
}
|
|
461
|
-
return {
|
|
462
|
-
provider: provider2,
|
|
463
|
-
modelId,
|
|
464
|
-
supportsStructuredOutputs,
|
|
465
|
-
isReasoningModel
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
async function createMcpToolCompatibility(runtime2) {
|
|
469
|
-
const modelInfo2 = detectModelProvider(runtime2);
|
|
470
|
-
try {
|
|
471
|
-
switch (modelInfo2.provider) {
|
|
472
|
-
case "openai":
|
|
473
|
-
const { OpenAIMcpCompatibility: OpenAIMcpCompatibility3 } = await Promise.resolve().then(() => (init_openai(), exports_openai));
|
|
474
|
-
return new OpenAIMcpCompatibility3(modelInfo2);
|
|
475
|
-
case "anthropic":
|
|
476
|
-
const { AnthropicMcpCompatibility: AnthropicMcpCompatibility3 } = await Promise.resolve().then(() => (init_anthropic(), exports_anthropic));
|
|
477
|
-
return new AnthropicMcpCompatibility3(modelInfo2);
|
|
478
|
-
case "google":
|
|
479
|
-
const { GoogleMcpCompatibility: GoogleMcpCompatibility3 } = await Promise.resolve().then(() => (init_google(), exports_google));
|
|
480
|
-
return new GoogleMcpCompatibility3(modelInfo2);
|
|
481
|
-
default:
|
|
482
|
-
return null;
|
|
483
|
-
}
|
|
484
|
-
} catch (error) {
|
|
485
|
-
console.warn("Failed to load compatibility provider:", error);
|
|
486
|
-
return null;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
function createMcpToolCompatibilitySync(runtime) {
|
|
490
|
-
const modelInfo = detectModelProvider(runtime);
|
|
491
|
-
try {
|
|
492
|
-
switch (modelInfo.provider) {
|
|
493
|
-
case "openai":
|
|
494
|
-
const OpenAIModule = eval("require")("./providers/openai");
|
|
495
|
-
const { OpenAIMcpCompatibility } = OpenAIModule;
|
|
496
|
-
return new OpenAIMcpCompatibility(modelInfo);
|
|
497
|
-
case "anthropic":
|
|
498
|
-
const AnthropicModule = eval("require")("./providers/anthropic");
|
|
499
|
-
const { AnthropicMcpCompatibility } = AnthropicModule;
|
|
500
|
-
return new AnthropicMcpCompatibility(modelInfo);
|
|
501
|
-
case "google":
|
|
502
|
-
const GoogleModule = eval("require")("./providers/google");
|
|
503
|
-
const { GoogleMcpCompatibility } = GoogleModule;
|
|
504
|
-
return new GoogleMcpCompatibility(modelInfo);
|
|
505
|
-
default:
|
|
506
|
-
return null;
|
|
507
|
-
}
|
|
508
|
-
} catch (error) {
|
|
509
|
-
console.warn("Failed to load compatibility provider:", error);
|
|
510
|
-
return null;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
21
|
+
CRITICAL INSTRUCTIONS:
|
|
22
|
+
1. You MUST specify both a valid serverName AND uri from the list above
|
|
23
|
+
2. The serverName value should match EXACTLY the server name shown in parentheses (Server: X)
|
|
24
|
+
CORRECT: "serverName": "github" (if the server is called "github")
|
|
25
|
+
WRONG: "serverName": "GitHub" or "Github" or any other variation
|
|
26
|
+
3. The uri value should match EXACTLY the resource uri listed
|
|
27
|
+
CORRECT: "uri": "weather://San Francisco/current" (if that's the exact uri)
|
|
28
|
+
WRONG: "uri": "weather://sanfrancisco/current" or any variation
|
|
29
|
+
4. Identify the user's information need from the conversation context
|
|
30
|
+
5. Select the most appropriate resource based on its description and the request
|
|
31
|
+
6. If no resource seems appropriate, output {"noResourceAvailable": true}
|
|
513
32
|
|
|
514
|
-
|
|
515
|
-
import { logger as logger8 } from "@elizaos/core";
|
|
33
|
+
!!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
|
|
516
34
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
35
|
+
STRICT FORMAT REQUIREMENTS:
|
|
36
|
+
- NO code block formatting (NO backticks or \`\`\`)
|
|
37
|
+
- NO comments (NO // or /* */)
|
|
38
|
+
- NO placeholders like "replace with...", "example", "your...", "actual", etc.
|
|
39
|
+
- Every parameter value must be a concrete, usable value (not instructions to replace)
|
|
40
|
+
- Use proper JSON syntax with double quotes for strings
|
|
41
|
+
- NO explanatory text before or after the JSON object
|
|
42
|
+
|
|
43
|
+
EXAMPLE RESPONSE:
|
|
44
|
+
{
|
|
45
|
+
"serverName": "weather-server",
|
|
46
|
+
"uri": "weather://San Francisco/current",
|
|
47
|
+
"reasoning": "Based on the conversation, the user is asking about current weather in San Francisco. This resource provides up-to-date weather information for that city."
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
REMEMBER: Your response will be parsed directly as JSON. If it fails to parse, the operation will fail completely!
|
|
51
|
+
`;
|
|
521
52
|
|
|
522
53
|
// src/types.ts
|
|
523
54
|
var MCP_SERVICE_NAME = "mcp";
|
|
524
55
|
var DEFAULT_MCP_TIMEOUT_SECONDS = 60000;
|
|
525
|
-
var MIN_MCP_TIMEOUT_SECONDS = 1;
|
|
526
56
|
var DEFAULT_MAX_RETRIES = 2;
|
|
527
|
-
var
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
properties: {
|
|
531
|
-
serverName: {
|
|
532
|
-
type: "string",
|
|
533
|
-
minLength: 1,
|
|
534
|
-
errorMessage: "serverName must not be empty"
|
|
535
|
-
},
|
|
536
|
-
toolName: {
|
|
537
|
-
type: "string",
|
|
538
|
-
minLength: 1,
|
|
539
|
-
errorMessage: "toolName must not be empty"
|
|
540
|
-
},
|
|
541
|
-
arguments: {
|
|
542
|
-
type: "object"
|
|
543
|
-
},
|
|
544
|
-
reasoning: {
|
|
545
|
-
type: "string"
|
|
546
|
-
},
|
|
547
|
-
noToolAvailable: {
|
|
548
|
-
type: "boolean"
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
};
|
|
552
|
-
var ResourceSelectionSchema = {
|
|
553
|
-
type: "object",
|
|
554
|
-
required: ["serverName", "uri"],
|
|
555
|
-
properties: {
|
|
556
|
-
serverName: {
|
|
557
|
-
type: "string",
|
|
558
|
-
minLength: 1,
|
|
559
|
-
errorMessage: "serverName must not be empty"
|
|
560
|
-
},
|
|
561
|
-
uri: {
|
|
562
|
-
type: "string",
|
|
563
|
-
minLength: 1,
|
|
564
|
-
errorMessage: "uri must not be empty"
|
|
565
|
-
},
|
|
566
|
-
reasoning: {
|
|
567
|
-
type: "string"
|
|
568
|
-
},
|
|
569
|
-
noResourceAvailable: {
|
|
570
|
-
type: "boolean"
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
};
|
|
57
|
+
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
58
|
+
var BACKOFF_MULTIPLIER = 2;
|
|
59
|
+
var INITIAL_RETRY_DELAY = 2000;
|
|
574
60
|
var DEFAULT_PING_CONFIG = {
|
|
575
61
|
enabled: true,
|
|
576
62
|
intervalMs: 1e4,
|
|
577
63
|
timeoutMs: 5000,
|
|
578
64
|
failuresBeforeDisconnect: 3
|
|
579
65
|
};
|
|
580
|
-
var
|
|
581
|
-
|
|
582
|
-
|
|
66
|
+
var ResourceSelectionSchema = {
|
|
67
|
+
type: "object",
|
|
68
|
+
required: ["serverName", "uri"],
|
|
69
|
+
properties: {
|
|
70
|
+
serverName: { type: "string", minLength: 1 },
|
|
71
|
+
uri: { type: "string", minLength: 1 },
|
|
72
|
+
reasoning: { type: "string" },
|
|
73
|
+
noResourceAvailable: { type: "boolean" }
|
|
74
|
+
}
|
|
75
|
+
};
|
|
583
76
|
|
|
584
77
|
// src/utils/error.ts
|
|
585
78
|
import {
|
|
@@ -611,95 +104,38 @@ Your response:
|
|
|
611
104
|
`;
|
|
612
105
|
|
|
613
106
|
// src/utils/error.ts
|
|
614
|
-
async function handleMcpError(state, mcpProvider, error,
|
|
107
|
+
async function handleMcpError(state, mcpProvider, error, runtime, message, type, callback) {
|
|
615
108
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
616
|
-
logger.error({ error, mcpType: type }, `
|
|
617
|
-
|
|
618
|
-
let
|
|
109
|
+
logger.error({ error, mcpType: type }, `MCP ${type} error: ${errorMessage}`);
|
|
110
|
+
const fallbackText = `I wasn't able to complete that request. There's an issue with the ${type}. Can I help with something else?`;
|
|
111
|
+
let responseText = fallbackText;
|
|
619
112
|
if (callback) {
|
|
620
|
-
const enhancedState = {
|
|
621
|
-
...state,
|
|
622
|
-
values: {
|
|
623
|
-
...state.values,
|
|
624
|
-
mcpProvider,
|
|
625
|
-
userMessage: message.content.text || "",
|
|
626
|
-
error: errorMessage
|
|
627
|
-
}
|
|
628
|
-
};
|
|
629
|
-
const prompt = composePromptFromState({
|
|
630
|
-
state: enhancedState,
|
|
631
|
-
template: errorAnalysisPrompt
|
|
632
|
-
});
|
|
633
113
|
try {
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
thought: thoughtText,
|
|
641
|
-
text: responseText,
|
|
642
|
-
actions: ["REPLY"]
|
|
643
|
-
});
|
|
644
|
-
} catch (modelError) {
|
|
645
|
-
logger.error({ error: modelError instanceof Error ? modelError.message : String(modelError) }, "Failed to generate error response");
|
|
646
|
-
await callback({
|
|
647
|
-
thought: thoughtText,
|
|
648
|
-
text: responseText,
|
|
649
|
-
actions: ["REPLY"]
|
|
114
|
+
const prompt = composePromptFromState({
|
|
115
|
+
state: {
|
|
116
|
+
...state,
|
|
117
|
+
values: { ...state.values, mcpProvider, userMessage: message.content.text || "", error: errorMessage }
|
|
118
|
+
},
|
|
119
|
+
template: errorAnalysisPrompt
|
|
650
120
|
});
|
|
651
|
-
|
|
121
|
+
responseText = await runtime.useModel(ModelType.TEXT_SMALL, { prompt });
|
|
122
|
+
} catch {}
|
|
123
|
+
await callback({ thought: `MCP ${type} error: ${errorMessage}`, text: responseText, actions: ["REPLY"] });
|
|
652
124
|
}
|
|
653
125
|
return {
|
|
654
126
|
text: `Failed to execute MCP ${type}`,
|
|
655
|
-
values: {
|
|
656
|
-
|
|
657
|
-
error: errorMessage,
|
|
658
|
-
errorType: type
|
|
659
|
-
},
|
|
660
|
-
data: {
|
|
661
|
-
actionName: type === "tool" ? "CALL_MCP_TOOL" : "READ_MCP_RESOURCE",
|
|
662
|
-
error: errorMessage,
|
|
663
|
-
mcpType: type
|
|
664
|
-
},
|
|
127
|
+
values: { success: false, error: errorMessage, errorType: type },
|
|
128
|
+
data: { actionName: type === "tool" ? "CALL_MCP_TOOL" : "READ_MCP_RESOURCE", error: errorMessage },
|
|
665
129
|
success: false,
|
|
666
130
|
error: error instanceof Error ? error : new Error(errorMessage)
|
|
667
131
|
};
|
|
668
132
|
}
|
|
669
133
|
|
|
670
|
-
// src/utils/handler.ts
|
|
671
|
-
async function handleNoToolAvailable(callback, toolSelection) {
|
|
672
|
-
const responseText = "I don't have a specific tool that can help with that request. Let me try to assist you directly instead.";
|
|
673
|
-
const thoughtText = "No appropriate MCP tool available for this request. Falling back to direct assistance.";
|
|
674
|
-
if (callback && toolSelection?.noToolAvailable) {
|
|
675
|
-
await callback({
|
|
676
|
-
text: responseText,
|
|
677
|
-
thought: thoughtText,
|
|
678
|
-
actions: ["REPLY"]
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
return {
|
|
682
|
-
text: responseText,
|
|
683
|
-
values: {
|
|
684
|
-
success: true,
|
|
685
|
-
noToolAvailable: true,
|
|
686
|
-
fallbackToDirectAssistance: true
|
|
687
|
-
},
|
|
688
|
-
data: {
|
|
689
|
-
actionName: "CALL_MCP_TOOL",
|
|
690
|
-
noToolAvailable: true,
|
|
691
|
-
reason: toolSelection?.reasoning || "No appropriate tool available"
|
|
692
|
-
},
|
|
693
|
-
success: true
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
|
|
697
134
|
// src/utils/processing.ts
|
|
698
135
|
import {
|
|
699
136
|
ContentType,
|
|
700
137
|
ModelType as ModelType2,
|
|
701
|
-
createUniqueUuid
|
|
702
|
-
logger as logger2
|
|
138
|
+
createUniqueUuid
|
|
703
139
|
} from "@elizaos/core";
|
|
704
140
|
import { composePromptFromState as composePromptFromState2 } from "@elizaos/core";
|
|
705
141
|
|
|
@@ -732,49 +168,19 @@ Instructions:
|
|
|
732
168
|
Your response (written as if directly to the user):
|
|
733
169
|
`;
|
|
734
170
|
|
|
735
|
-
// src/templates/toolReasoningTemplate.ts
|
|
736
|
-
var toolReasoningTemplate = `
|
|
737
|
-
{{{mcpProvider.text}}}
|
|
738
|
-
|
|
739
|
-
{{{recentMessages}}}
|
|
740
|
-
|
|
741
|
-
# Prompt
|
|
742
|
-
|
|
743
|
-
You are a helpful assistant responding to a user's request. You've just used the "{{{toolName}}}" tool from the "{{{serverName}}}" server to help answer this request.
|
|
744
|
-
|
|
745
|
-
Original user request: "{{{userMessage}}}"
|
|
746
|
-
|
|
747
|
-
Tool response:
|
|
748
|
-
{{{toolOutput}}}
|
|
749
|
-
|
|
750
|
-
{{#if hasAttachments}}
|
|
751
|
-
The tool also returned images or other media that will be shared with the user.
|
|
752
|
-
{{/if}}
|
|
753
|
-
|
|
754
|
-
Instructions:
|
|
755
|
-
1. Analyze how well the tool's response addresses the user's specific question or need
|
|
756
|
-
2. Identify the most relevant information from the tool's output
|
|
757
|
-
3. Create a natural, conversational response that incorporates this information
|
|
758
|
-
4. If the tool's response is insufficient, acknowledge its limitations and explain what you can determine
|
|
759
|
-
5. Do not start with phrases like "I used the X tool" or "Here's what I found" - instead, integrate the information naturally
|
|
760
|
-
6. Maintain your helpful, intelligent assistant personality while presenting the information
|
|
761
|
-
|
|
762
|
-
Your response (written as if directly to the user):
|
|
763
|
-
`;
|
|
764
|
-
|
|
765
171
|
// src/utils/mcp.ts
|
|
766
172
|
var NO_DESC = "No description";
|
|
767
|
-
async function createMcpMemory(
|
|
768
|
-
const memory = await
|
|
173
|
+
async function createMcpMemory(runtime, message, type, serverName, content, metadata) {
|
|
174
|
+
const memory = await runtime.addEmbeddingToMemory({
|
|
769
175
|
entityId: message.entityId,
|
|
770
|
-
agentId:
|
|
176
|
+
agentId: runtime.agentId,
|
|
771
177
|
roomId: message.roomId,
|
|
772
178
|
content: {
|
|
773
179
|
text: `Used "${type}" from "${serverName}". Content: ${content}`,
|
|
774
180
|
metadata: { ...metadata, serverName }
|
|
775
181
|
}
|
|
776
182
|
});
|
|
777
|
-
await
|
|
183
|
+
await runtime.createMemory(memory, type === "resource" ? "resources" : "tools", true);
|
|
778
184
|
}
|
|
779
185
|
function buildMcpProviderData(servers) {
|
|
780
186
|
if (servers.length === 0) {
|
|
@@ -831,24 +237,20 @@ function processResourceResult(result, uri) {
|
|
|
831
237
|
let resourceContent = "";
|
|
832
238
|
let resourceMeta = "";
|
|
833
239
|
for (const content of result.contents) {
|
|
834
|
-
|
|
835
|
-
resourceContent += content.text;
|
|
836
|
-
} else if (content.blob) {
|
|
837
|
-
resourceContent += `[Binary data - ${content.mimeType || "unknown type"}]`;
|
|
838
|
-
}
|
|
240
|
+
resourceContent += content.text || (content.blob ? `[Binary: ${content.mimeType || "unknown"}]` : "");
|
|
839
241
|
resourceMeta += `Resource: ${content.uri || uri}
|
|
840
242
|
`;
|
|
841
|
-
if (content.mimeType)
|
|
243
|
+
if (content.mimeType)
|
|
842
244
|
resourceMeta += `Type: ${content.mimeType}
|
|
843
245
|
`;
|
|
844
|
-
}
|
|
845
246
|
}
|
|
846
247
|
return { resourceContent, resourceMeta };
|
|
847
248
|
}
|
|
848
|
-
function processToolResult(result, serverName, toolName,
|
|
249
|
+
function processToolResult(result, serverName, toolName, runtime, messageEntityId) {
|
|
849
250
|
let toolOutput = "";
|
|
850
251
|
let hasAttachments = false;
|
|
851
252
|
const attachments = [];
|
|
253
|
+
let attachmentIndex = 0;
|
|
852
254
|
for (const content of result.content) {
|
|
853
255
|
if (content.type === "text") {
|
|
854
256
|
toolOutput += content.text;
|
|
@@ -857,140 +259,115 @@ function processToolResult(result, serverName, toolName, runtime2, messageEntity
|
|
|
857
259
|
attachments.push({
|
|
858
260
|
contentType: getMimeTypeToContentType(content.mimeType),
|
|
859
261
|
url: `data:${content.mimeType};base64,${content.data}`,
|
|
860
|
-
id: createUniqueUuid(
|
|
262
|
+
id: createUniqueUuid(runtime, `${messageEntityId}-attachment-${attachmentIndex++}`),
|
|
861
263
|
title: "Generated image",
|
|
862
264
|
source: `${serverName}/${toolName}`,
|
|
863
265
|
description: "Tool-generated image",
|
|
864
266
|
text: "Generated image"
|
|
865
267
|
});
|
|
866
|
-
} else if (content.type === "resource") {
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
toolOutput += `
|
|
268
|
+
} else if (content.type === "resource" && content.resource) {
|
|
269
|
+
const r = content.resource;
|
|
270
|
+
toolOutput += r.text ? `
|
|
870
271
|
|
|
871
|
-
Resource (${
|
|
872
|
-
${
|
|
873
|
-
} else if (resource && "blob" in resource) {
|
|
874
|
-
toolOutput += `
|
|
272
|
+
Resource (${r.uri}):
|
|
273
|
+
${r.text}` : `
|
|
875
274
|
|
|
876
|
-
Resource (${
|
|
877
|
-
}
|
|
275
|
+
Resource (${r.uri}): [Binary]`;
|
|
878
276
|
}
|
|
879
277
|
}
|
|
880
278
|
return { toolOutput, hasAttachments, attachments };
|
|
881
279
|
}
|
|
882
|
-
async function handleResourceAnalysis(
|
|
883
|
-
await createMcpMemory(
|
|
280
|
+
async function handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback) {
|
|
281
|
+
await createMcpMemory(runtime, message, "resource", serverName, resourceContent, {
|
|
884
282
|
uri,
|
|
885
283
|
isResourceAccess: true
|
|
886
284
|
});
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
285
|
+
const prompt = composePromptFromState2({
|
|
286
|
+
state: {
|
|
287
|
+
data: {},
|
|
288
|
+
text: "",
|
|
289
|
+
values: { uri, userMessage: message.content.text || "", resourceContent, resourceMeta }
|
|
290
|
+
},
|
|
291
|
+
template: resourceAnalysisTemplate
|
|
890
292
|
});
|
|
293
|
+
const response = await runtime.useModel(ModelType2.TEXT_SMALL, { prompt });
|
|
891
294
|
if (callback) {
|
|
892
295
|
await callback({
|
|
893
|
-
text:
|
|
894
|
-
thought: `
|
|
296
|
+
text: response,
|
|
297
|
+
thought: `Analyzed resource ${uri} from ${serverName}`,
|
|
895
298
|
actions: ["READ_MCP_RESOURCE"]
|
|
896
299
|
});
|
|
897
300
|
}
|
|
898
301
|
}
|
|
899
|
-
async function handleToolResponse(runtime2, message, serverName, toolName, toolArgs, toolOutput, hasAttachments, attachments, state, mcpProvider, callback) {
|
|
900
|
-
await createMcpMemory(runtime2, message, "tool", serverName, toolOutput, {
|
|
901
|
-
toolName,
|
|
902
|
-
arguments: toolArgs,
|
|
903
|
-
isToolCall: true
|
|
904
|
-
});
|
|
905
|
-
const reasoningPrompt = createReasoningPrompt(state, mcpProvider, toolName, serverName, message.content.text || "", toolOutput, hasAttachments);
|
|
906
|
-
logger2.info({ reasoningPrompt }, "reasoning prompt");
|
|
907
|
-
const reasonedResponse = await runtime2.useModel(ModelType2.TEXT_SMALL, {
|
|
908
|
-
prompt: reasoningPrompt
|
|
909
|
-
});
|
|
910
|
-
const agentId = message.agentId || runtime2.agentId;
|
|
911
|
-
const replyMemory = {
|
|
912
|
-
entityId: agentId,
|
|
913
|
-
roomId: message.roomId,
|
|
914
|
-
worldId: message.worldId,
|
|
915
|
-
content: {
|
|
916
|
-
text: reasonedResponse,
|
|
917
|
-
thought: `I analyzed the output from the ${toolName} tool on ${serverName} and crafted a thoughtful response that addresses the user's request while maintaining my conversational style.`,
|
|
918
|
-
actions: ["CALL_MCP_TOOL"],
|
|
919
|
-
attachments: hasAttachments && attachments.length > 0 ? attachments : undefined
|
|
920
|
-
}
|
|
921
|
-
};
|
|
922
|
-
await runtime2.createMemory(replyMemory, "messages");
|
|
923
|
-
if (callback) {
|
|
924
|
-
await callback({
|
|
925
|
-
text: reasonedResponse,
|
|
926
|
-
thought: `I analyzed the output from the ${toolName} tool on ${serverName} and crafted a thoughtful response that addresses the user's request while maintaining my conversational style.`,
|
|
927
|
-
actions: ["CALL_MCP_TOOL"],
|
|
928
|
-
attachments: hasAttachments && attachments.length > 0 ? attachments : undefined
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
return replyMemory;
|
|
932
|
-
}
|
|
933
302
|
async function sendInitialResponse(callback) {
|
|
934
303
|
if (callback) {
|
|
935
|
-
|
|
936
|
-
thought: "
|
|
304
|
+
await callback({
|
|
305
|
+
thought: "Retrieving MCP resource...",
|
|
937
306
|
text: "I'll retrieve that information for you. Let me access the resource...",
|
|
938
307
|
actions: ["READ_MCP_RESOURCE"]
|
|
939
|
-
};
|
|
940
|
-
await callback(responseContent);
|
|
308
|
+
});
|
|
941
309
|
}
|
|
942
310
|
}
|
|
943
|
-
function createAnalysisPrompt(uri, userMessage, resourceContent, resourceMeta) {
|
|
944
|
-
const enhancedState = {
|
|
945
|
-
data: {},
|
|
946
|
-
text: "",
|
|
947
|
-
values: {
|
|
948
|
-
uri,
|
|
949
|
-
userMessage,
|
|
950
|
-
resourceContent,
|
|
951
|
-
resourceMeta
|
|
952
|
-
}
|
|
953
|
-
};
|
|
954
|
-
return composePromptFromState2({
|
|
955
|
-
state: enhancedState,
|
|
956
|
-
template: resourceAnalysisTemplate
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
function createReasoningPrompt(state, mcpProvider, toolName, serverName, userMessage, toolOutput, hasAttachments) {
|
|
960
|
-
const enhancedState = {
|
|
961
|
-
...state,
|
|
962
|
-
values: {
|
|
963
|
-
...state.values,
|
|
964
|
-
mcpProvider,
|
|
965
|
-
toolName,
|
|
966
|
-
serverName,
|
|
967
|
-
userMessage,
|
|
968
|
-
toolOutput,
|
|
969
|
-
hasAttachments
|
|
970
|
-
}
|
|
971
|
-
};
|
|
972
|
-
return composePromptFromState2({
|
|
973
|
-
state: enhancedState,
|
|
974
|
-
template: toolReasoningTemplate
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
// src/utils/selection.ts
|
|
979
|
-
import {
|
|
980
|
-
ModelType as ModelType4,
|
|
981
|
-
composePromptFromState as composePromptFromState3,
|
|
982
|
-
logger as logger4
|
|
983
|
-
} from "@elizaos/core";
|
|
984
311
|
|
|
985
312
|
// src/utils/json.ts
|
|
986
313
|
import Ajv from "ajv";
|
|
987
314
|
import JSON5 from "json5";
|
|
315
|
+
function findMatchingClose(str, start, open, close) {
|
|
316
|
+
let depth = 0;
|
|
317
|
+
let inString = false;
|
|
318
|
+
let escape = false;
|
|
319
|
+
for (let i = start;i < str.length; i++) {
|
|
320
|
+
const ch = str[i];
|
|
321
|
+
if (escape) {
|
|
322
|
+
escape = false;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (ch === "\\" && inString) {
|
|
326
|
+
escape = true;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (ch === '"') {
|
|
330
|
+
inString = !inString;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (inString)
|
|
334
|
+
continue;
|
|
335
|
+
if (ch === open)
|
|
336
|
+
depth++;
|
|
337
|
+
else if (ch === close) {
|
|
338
|
+
depth--;
|
|
339
|
+
if (depth === 0)
|
|
340
|
+
return i;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return -1;
|
|
344
|
+
}
|
|
988
345
|
function parseJSON(input) {
|
|
989
346
|
let cleanedInput = input.replace(/^```(?:json)?\s*|\s*```$/g, "").trim();
|
|
990
347
|
const firstBrace = cleanedInput.indexOf("{");
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
348
|
+
const firstBracket = cleanedInput.indexOf("[");
|
|
349
|
+
let start = -1;
|
|
350
|
+
let isArray = false;
|
|
351
|
+
if (firstBrace === -1 && firstBracket === -1) {
|
|
352
|
+
return JSON5.parse(cleanedInput);
|
|
353
|
+
} else if (firstBrace === -1) {
|
|
354
|
+
start = firstBracket;
|
|
355
|
+
isArray = true;
|
|
356
|
+
} else if (firstBracket === -1) {
|
|
357
|
+
start = firstBrace;
|
|
358
|
+
isArray = false;
|
|
359
|
+
} else {
|
|
360
|
+
if (firstBracket < firstBrace) {
|
|
361
|
+
start = firstBracket;
|
|
362
|
+
isArray = true;
|
|
363
|
+
} else {
|
|
364
|
+
start = firstBrace;
|
|
365
|
+
isArray = false;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const end = isArray ? findMatchingClose(cleanedInput, start, "[", "]") : findMatchingClose(cleanedInput, start, "{", "}");
|
|
369
|
+
if (end !== -1) {
|
|
370
|
+
cleanedInput = cleanedInput.substring(start, end + 1);
|
|
994
371
|
}
|
|
995
372
|
return JSON5.parse(cleanedInput);
|
|
996
373
|
}
|
|
@@ -1014,575 +391,101 @@ function validateJsonSchema(data, schema) {
|
|
|
1014
391
|
return {
|
|
1015
392
|
success: false,
|
|
1016
393
|
error: `Schema validation error: ${error instanceof Error ? error.message : String(error)}`
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// src/utils/
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
ModelType as ModelType3
|
|
1025
|
-
} from "@elizaos/core";
|
|
1026
|
-
async function withModelRetry({
|
|
1027
|
-
runtime: runtime2,
|
|
1028
|
-
message,
|
|
1029
|
-
state,
|
|
1030
|
-
callback,
|
|
1031
|
-
input,
|
|
1032
|
-
validationFn,
|
|
1033
|
-
createFeedbackPromptFn,
|
|
1034
|
-
failureMsg,
|
|
1035
|
-
retryCount = 0
|
|
1036
|
-
}) {
|
|
1037
|
-
const maxRetries = getMaxRetries(runtime2);
|
|
1038
|
-
try {
|
|
1039
|
-
logger3.info(`[WITH-MODEL-RETRY] Raw selection input:
|
|
1040
|
-
${input}`);
|
|
1041
|
-
const parsedJson = typeof input === "string" ? parseJSON(input) : input;
|
|
1042
|
-
logger3.debug(`[WITH-MODEL-RETRY] Parsed selection input:
|
|
1043
|
-
${JSON.stringify(parsedJson, null, 2)}`);
|
|
1044
|
-
const validationResult = validationFn(parsedJson);
|
|
1045
|
-
if (validationResult.success === false) {
|
|
1046
|
-
throw new Error(validationResult.error);
|
|
1047
|
-
}
|
|
1048
|
-
return validationResult.data;
|
|
1049
|
-
} catch (parseError) {
|
|
1050
|
-
const errorMessage = parseError instanceof Error ? parseError.message : "Unknown parsing error";
|
|
1051
|
-
logger3.error({ errorMessage }, `[WITH-MODEL-RETRY] Failed to parse response: ${errorMessage}`);
|
|
1052
|
-
if (retryCount < maxRetries) {
|
|
1053
|
-
logger3.debug(`[WITH-MODEL-RETRY] Retrying (attempt ${retryCount + 1}/${maxRetries})`);
|
|
1054
|
-
const feedbackPrompt = createFeedbackPromptFn(input, errorMessage, state, message.content.text || "");
|
|
1055
|
-
const retrySelection = await runtime2.useModel(ModelType3.OBJECT_LARGE, {
|
|
1056
|
-
prompt: feedbackPrompt
|
|
1057
|
-
});
|
|
1058
|
-
return withModelRetry({
|
|
1059
|
-
runtime: runtime2,
|
|
1060
|
-
input: retrySelection,
|
|
1061
|
-
validationFn,
|
|
1062
|
-
message,
|
|
1063
|
-
state,
|
|
1064
|
-
createFeedbackPromptFn,
|
|
1065
|
-
callback,
|
|
1066
|
-
failureMsg,
|
|
1067
|
-
retryCount: retryCount + 1
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
1070
|
-
if (callback && failureMsg) {
|
|
1071
|
-
await callback({
|
|
1072
|
-
text: failureMsg,
|
|
1073
|
-
thought: "Failed to parse response after multiple retries. Requesting clarification from user.",
|
|
1074
|
-
actions: ["REPLY"]
|
|
1075
|
-
});
|
|
1076
|
-
}
|
|
1077
|
-
return null;
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
function getMaxRetries(runtime2) {
|
|
1081
|
-
try {
|
|
1082
|
-
const rawSettings = runtime2.getSetting("mcp");
|
|
1083
|
-
const settings = rawSettings;
|
|
1084
|
-
if (settings && typeof settings.maxRetries === "number") {
|
|
1085
|
-
const configValue = settings.maxRetries;
|
|
1086
|
-
if (!Number.isNaN(configValue) && configValue >= 0) {
|
|
1087
|
-
logger3.debug(`[WITH-MODEL-RETRY] Using configured selection retries: ${configValue}`);
|
|
1088
|
-
return configValue;
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
} catch (error) {
|
|
1092
|
-
logger3.debug({ error: error instanceof Error ? error.message : String(error) }, "[WITH-MODEL-RETRY] Error reading selection retries config");
|
|
1093
|
-
}
|
|
1094
|
-
return DEFAULT_MAX_RETRIES;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
// src/templates/toolSelectionTemplate.ts
|
|
1098
|
-
var toolSelectionNameTemplate = `
|
|
1099
|
-
{{mcpProvider.text}}
|
|
1100
|
-
|
|
1101
|
-
{{recentMessages}}
|
|
1102
|
-
|
|
1103
|
-
# TASK: Select the Most Appropriate Tool and Server
|
|
1104
|
-
|
|
1105
|
-
You must select the most appropriate tool from the list above to fulfill the user's request. Your response must be a valid JSON object with the required properties.
|
|
1106
|
-
|
|
1107
|
-
## CRITICAL INSTRUCTIONS
|
|
1108
|
-
1. Provide both "serverName" and "toolName" from the options listed above.
|
|
1109
|
-
2. Each name must match EXACTLY as shown in the list:
|
|
1110
|
-
- Example (correct): "serverName": "github"
|
|
1111
|
-
- Example (incorrect): "serverName": "GitHub", "Github", or variations
|
|
1112
|
-
3. Extract ACTUAL parameter values from the conversation context.
|
|
1113
|
-
- Do not invent or use placeholders like "octocat" or "Hello-World" unless the user said so.
|
|
1114
|
-
4. Include a "reasoning" field explaining why the selected tool fits the request.
|
|
1115
|
-
5. If no tool is appropriate, respond with:
|
|
1116
|
-
{
|
|
1117
|
-
"noToolAvailable": true
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
!!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
|
|
1121
|
-
|
|
1122
|
-
CRITICAL: Your response must START with { and END with }. DO NOT include ANY text before or after the JSON.
|
|
1123
|
-
|
|
1124
|
-
## STRICT FORMAT REQUIREMENTS
|
|
1125
|
-
- The response MUST be a single valid JSON object.
|
|
1126
|
-
- DO NOT wrap the JSON in triple backticks (\`\`\`), code blocks, or include any explanatory text.
|
|
1127
|
-
- DO NOT include comments (// or /* */) anywhere.
|
|
1128
|
-
- DO NOT use placeholders (e.g., "replace with...", "example", "your...", etc.)
|
|
1129
|
-
- ALL strings must use double quotes.
|
|
1130
|
-
|
|
1131
|
-
## CRITICAL NOTES
|
|
1132
|
-
- All values must be fully grounded in user input or inferred contextually.
|
|
1133
|
-
- No missing fields unless they are explicitly optional in the schema.
|
|
1134
|
-
- All types must match the schema (strings, numbers, booleans).
|
|
1135
|
-
|
|
1136
|
-
## JSON OBJECT STRUCTURE
|
|
1137
|
-
Your response MUST contain ONLY these top-level keys:
|
|
1138
|
-
1. "serverName" — The name of the server (e.g., "github", "notion")
|
|
1139
|
-
2. "toolName" — The name of the tool (e.g., "get_file_contents", "search")
|
|
1140
|
-
3. "reasoning" — A string explaining how the values were inferred from the conversation.
|
|
1141
|
-
4. "noToolAvailable" — A boolean indicating if no tool is available (true/false)
|
|
1142
|
-
|
|
1143
|
-
## EXAMPLE RESPONSE
|
|
1144
|
-
{
|
|
1145
|
-
"serverName": "github",
|
|
1146
|
-
"toolName": "get_file_contents",
|
|
1147
|
-
"reasoning": "The user wants to retrieve the README from the facebook/react repository.",
|
|
1148
|
-
"noToolAvailable": false
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
## REMINDERS
|
|
1152
|
-
- Use "github" as serverName for GitHub tools.
|
|
1153
|
-
- Use "notion" as serverName for Notion tools.
|
|
1154
|
-
- For search and knowledge-based tasks, MCP tools are often appropriate.
|
|
1155
|
-
|
|
1156
|
-
REMEMBER: This output will be parsed directly as JSON. If the format is incorrect, the operation will fail.
|
|
1157
|
-
`;
|
|
1158
|
-
var toolSelectionArgumentTemplate = `
|
|
1159
|
-
{{recentMessages}}
|
|
1160
|
-
|
|
1161
|
-
# TASK: Generate a Strictly Valid JSON Object for Tool Execution
|
|
1162
|
-
|
|
1163
|
-
You have chosen the "{{toolSelectionName.toolName}}" tool from the "{{toolSelectionName.serverName}}" server to address the user's request.
|
|
1164
|
-
The reasoning behind this selection is: "{{toolSelectionName.reasoning}}"
|
|
1165
|
-
|
|
1166
|
-
## CRITICAL INSTRUCTIONS
|
|
1167
|
-
1. Ensure the "toolArguments" object strictly adheres to the structure and requirements defined in the schema.
|
|
1168
|
-
2. All parameter values must be extracted from the conversation context and must be concrete, usable values.
|
|
1169
|
-
3. Avoid placeholders or generic terms unless explicitly provided by the user.
|
|
1170
|
-
|
|
1171
|
-
!!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
|
|
1172
|
-
|
|
1173
|
-
## STRICT FORMAT REQUIREMENTS
|
|
1174
|
-
- The response MUST be a single valid JSON object.
|
|
1175
|
-
- DO NOT wrap the JSON in triple backticks (\`\`\`), code blocks, or include any explanatory text.
|
|
1176
|
-
- DO NOT include comments (// or /* */) anywhere.
|
|
1177
|
-
- DO NOT use placeholders (e.g., "replace with...", "example", "your...", etc.)
|
|
1178
|
-
- ALL strings must use double quotes
|
|
1179
|
-
|
|
1180
|
-
## CRITICAL NOTES
|
|
1181
|
-
- All values must be fully grounded in user input or inferred contextually.
|
|
1182
|
-
- No missing fields unless they are explicitly optional in the schema.
|
|
1183
|
-
- All types must match the schema (strings, numbers, booleans).
|
|
1184
|
-
|
|
1185
|
-
## JSON OBJECT STRUCTURE
|
|
1186
|
-
Your response MUST contain ONLY these two top-level keys:
|
|
1187
|
-
1. "toolArguments" — An object matching the input schema: {{toolInputSchema}}
|
|
1188
|
-
2. "reasoning" — A string explaining how the values were inferred from the conversation.
|
|
1189
|
-
|
|
1190
|
-
## EXAMPLE RESPONSE
|
|
1191
|
-
{
|
|
1192
|
-
"toolArguments": {
|
|
1193
|
-
"owner": "facebook",
|
|
1194
|
-
"repo": "react",
|
|
1195
|
-
"path": "README.md",
|
|
1196
|
-
"branch": "main"
|
|
1197
|
-
},
|
|
1198
|
-
"reasoning": "The user wants to see the README from the facebook/react repository based on our conversation."
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
REMEMBER: Your response will be parsed directly as JSON. If it fails to parse, the operation will fail completely.
|
|
1202
|
-
`;
|
|
1203
|
-
|
|
1204
|
-
// src/utils/schemas.ts
|
|
1205
|
-
var toolSelectionNameSchema = {
|
|
1206
|
-
type: "object",
|
|
1207
|
-
required: ["serverName", "toolName"],
|
|
1208
|
-
properties: {
|
|
1209
|
-
serverName: {
|
|
1210
|
-
type: "string",
|
|
1211
|
-
minLength: 1,
|
|
1212
|
-
errorMessage: "serverName must not be empty"
|
|
1213
|
-
},
|
|
1214
|
-
toolName: {
|
|
1215
|
-
type: "string",
|
|
1216
|
-
minLength: 1,
|
|
1217
|
-
errorMessage: "toolName must not be empty"
|
|
1218
|
-
},
|
|
1219
|
-
reasoning: {
|
|
1220
|
-
type: "string"
|
|
1221
|
-
},
|
|
1222
|
-
noToolAvailable: {
|
|
1223
|
-
type: "boolean"
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
};
|
|
1227
|
-
var toolSelectionArgumentSchema = {
|
|
1228
|
-
type: "object",
|
|
1229
|
-
required: ["toolArguments"],
|
|
1230
|
-
properties: {
|
|
1231
|
-
toolArguments: {
|
|
1232
|
-
type: "object"
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
};
|
|
1236
|
-
|
|
1237
|
-
// src/utils/validation.ts
|
|
1238
|
-
function validateToolSelectionName(parsed, state) {
|
|
1239
|
-
const basicResult = validateJsonSchema(parsed, toolSelectionNameSchema);
|
|
1240
|
-
if (basicResult.success === false) {
|
|
1241
|
-
return { success: false, error: basicResult.error };
|
|
1242
|
-
}
|
|
1243
|
-
const data = basicResult.data;
|
|
1244
|
-
const mcpData = state.values.mcp || {};
|
|
1245
|
-
const server = mcpData[data.serverName];
|
|
1246
|
-
if (!server || server.status !== "connected") {
|
|
1247
|
-
return {
|
|
1248
|
-
success: false,
|
|
1249
|
-
error: `Server "${data.serverName}" not found or not connected`
|
|
1250
|
-
};
|
|
1251
|
-
}
|
|
1252
|
-
const toolInfo = server.tools?.[data.toolName];
|
|
1253
|
-
if (!toolInfo) {
|
|
1254
|
-
return {
|
|
1255
|
-
success: false,
|
|
1256
|
-
error: `Tool "${data.toolName}" not found on server "${data.serverName}"`
|
|
1257
|
-
};
|
|
1258
|
-
}
|
|
1259
|
-
return { success: true, data };
|
|
1260
|
-
}
|
|
1261
|
-
function validateToolSelectionArgument(parsed, toolInputSchema) {
|
|
1262
|
-
const basicResult = validateJsonSchema(parsed, toolSelectionArgumentSchema);
|
|
1263
|
-
if (basicResult.success === false) {
|
|
1264
|
-
return { success: false, error: basicResult.error };
|
|
1265
|
-
}
|
|
1266
|
-
const data = basicResult.data;
|
|
1267
|
-
const validationResult = validateJsonSchema(data.toolArguments, toolInputSchema);
|
|
1268
|
-
if (validationResult.success === false) {
|
|
1269
|
-
return {
|
|
1270
|
-
success: false,
|
|
1271
|
-
error: `Invalid arguments: ${validationResult.error}`
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1274
|
-
return { success: true, data };
|
|
1275
|
-
}
|
|
1276
|
-
function validateResourceSelection(selection) {
|
|
1277
|
-
return validateJsonSchema(selection, ResourceSelectionSchema);
|
|
1278
|
-
}
|
|
1279
|
-
function createResourceSelectionFeedbackPrompt(originalResponse, errorMessage, composedState, userMessage) {
|
|
1280
|
-
let resourcesDescription = "";
|
|
1281
|
-
for (const [serverName, server] of Object.entries(composedState.values.mcp || {})) {
|
|
1282
|
-
if (server.status !== "connected")
|
|
1283
|
-
continue;
|
|
1284
|
-
for (const [uri, resource] of Object.entries(server.resources || {})) {
|
|
1285
|
-
resourcesDescription += `Resource: ${uri} (Server: ${serverName})
|
|
1286
|
-
`;
|
|
1287
|
-
resourcesDescription += `Name: ${resource.name || "No name available"}
|
|
1288
|
-
`;
|
|
1289
|
-
resourcesDescription += `Description: ${resource.description || "No description available"}
|
|
1290
|
-
|
|
1291
|
-
`;
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
return createFeedbackPrompt(originalResponse, errorMessage, "resource", resourcesDescription, userMessage);
|
|
1295
|
-
}
|
|
1296
|
-
function createFeedbackPrompt(originalResponse, errorMessage, itemType, itemsDescription, userMessage) {
|
|
1297
|
-
return `Error parsing JSON: ${errorMessage}
|
|
1298
|
-
|
|
1299
|
-
Your original response:
|
|
1300
|
-
${originalResponse}
|
|
1301
|
-
|
|
1302
|
-
Please try again with valid JSON for ${itemType} selection.
|
|
1303
|
-
Available ${itemType}s:
|
|
1304
|
-
${itemsDescription}
|
|
1305
|
-
|
|
1306
|
-
User request: ${userMessage}`;
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
// src/utils/selection.ts
|
|
1310
|
-
async function createToolSelectionName({
|
|
1311
|
-
runtime: runtime2,
|
|
1312
|
-
state,
|
|
1313
|
-
message,
|
|
1314
|
-
callback,
|
|
1315
|
-
mcpProvider
|
|
1316
|
-
}) {
|
|
1317
|
-
const toolSelectionPrompt = composePromptFromState3({
|
|
1318
|
-
state: { ...state, values: { ...state.values, mcpProvider } },
|
|
1319
|
-
template: toolSelectionNameTemplate
|
|
1320
|
-
});
|
|
1321
|
-
logger4.debug(`[SELECTION] Tool Selection Name Prompt:
|
|
1322
|
-
${toolSelectionPrompt}`);
|
|
1323
|
-
const toolSelectionName = await runtime2.useModel(ModelType4.TEXT_LARGE, {
|
|
1324
|
-
prompt: toolSelectionPrompt
|
|
1325
|
-
});
|
|
1326
|
-
logger4.debug(`[SELECTION] Tool Selection Name Response:
|
|
1327
|
-
${toolSelectionName}`);
|
|
1328
|
-
return await withModelRetry({
|
|
1329
|
-
runtime: runtime2,
|
|
1330
|
-
message,
|
|
1331
|
-
state,
|
|
1332
|
-
callback,
|
|
1333
|
-
input: toolSelectionName,
|
|
1334
|
-
validationFn: (parsed) => validateToolSelectionName(parsed, state),
|
|
1335
|
-
createFeedbackPromptFn: (originalResponse, errorMessage, state2, userMessage) => createToolSelectionFeedbackPrompt(originalResponse, errorMessage, state2, userMessage),
|
|
1336
|
-
failureMsg: "I'm having trouble figuring out the best way to help with your request."
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
1339
|
-
async function createToolSelectionArgument({
|
|
1340
|
-
runtime: runtime2,
|
|
1341
|
-
state,
|
|
1342
|
-
message,
|
|
1343
|
-
callback,
|
|
1344
|
-
mcpProvider,
|
|
1345
|
-
toolSelectionName
|
|
1346
|
-
}) {
|
|
1347
|
-
if (!toolSelectionName) {
|
|
1348
|
-
logger4.warn("[SELECTION] Tool selection name is not provided. Cannot create tool selection argument.");
|
|
1349
|
-
return null;
|
|
1350
|
-
}
|
|
1351
|
-
const { serverName, toolName } = toolSelectionName;
|
|
1352
|
-
const toolInputSchema = mcpProvider.data.mcp[serverName].tools[toolName].inputSchema;
|
|
1353
|
-
logger4.trace(`[SELECTION] Tool Input Schema:
|
|
1354
|
-
${JSON.stringify({ toolInputSchema }, null, 2)}`);
|
|
1355
|
-
const toolSelectionArgumentPrompt = composePromptFromState3({
|
|
1356
|
-
state: {
|
|
1357
|
-
...state,
|
|
1358
|
-
values: {
|
|
1359
|
-
...state.values,
|
|
1360
|
-
toolSelectionName,
|
|
1361
|
-
toolInputSchema: JSON.stringify(toolInputSchema)
|
|
1362
|
-
}
|
|
1363
|
-
},
|
|
1364
|
-
template: toolSelectionArgumentTemplate
|
|
1365
|
-
});
|
|
1366
|
-
logger4.debug(`[SELECTION] Tool Selection Prompt:
|
|
1367
|
-
${toolSelectionArgumentPrompt}`);
|
|
1368
|
-
const toolSelectionArgument = await runtime2.useModel(ModelType4.TEXT_LARGE, {
|
|
1369
|
-
prompt: toolSelectionArgumentPrompt
|
|
1370
|
-
});
|
|
1371
|
-
logger4.debug(`[SELECTION] Tool Selection Argument Response:
|
|
1372
|
-
${toolSelectionArgument}`);
|
|
1373
|
-
return await withModelRetry({
|
|
1374
|
-
runtime: runtime2,
|
|
1375
|
-
message,
|
|
1376
|
-
state,
|
|
1377
|
-
callback,
|
|
1378
|
-
input: toolSelectionArgument,
|
|
1379
|
-
validationFn: (parsed) => validateToolSelectionArgument(parsed, state),
|
|
1380
|
-
createFeedbackPromptFn: (originalResponse, errorMessage, state2, userMessage) => createToolSelectionFeedbackPrompt(originalResponse, errorMessage, state2, userMessage),
|
|
1381
|
-
failureMsg: "I'm having trouble figuring out the best way to help with your request."
|
|
1382
|
-
});
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/utils/validation.ts
|
|
399
|
+
function validateResourceSelection(selection) {
|
|
400
|
+
return validateJsonSchema(selection, ResourceSelectionSchema);
|
|
1383
401
|
}
|
|
1384
|
-
function
|
|
1385
|
-
let
|
|
1386
|
-
for (const [serverName, server] of Object.entries(
|
|
402
|
+
function createResourceSelectionFeedbackPrompt(originalResponse, errorMessage, composedState, userMessage) {
|
|
403
|
+
let description = "";
|
|
404
|
+
for (const [serverName, server] of Object.entries(composedState.values.mcp || {})) {
|
|
1387
405
|
if (server.status !== "connected")
|
|
1388
406
|
continue;
|
|
1389
|
-
for (const [
|
|
1390
|
-
|
|
407
|
+
for (const [uri, resource] of Object.entries(server.resources || {})) {
|
|
408
|
+
description += `Resource: ${uri} (Server: ${serverName})
|
|
1391
409
|
`;
|
|
1392
|
-
|
|
410
|
+
description += `Name: ${resource.name || "No name"}
|
|
411
|
+
`;
|
|
412
|
+
description += `Description: ${resource.description || "No description"}
|
|
1393
413
|
|
|
1394
414
|
`;
|
|
1395
415
|
}
|
|
1396
416
|
}
|
|
1397
|
-
const feedbackPrompt = createFeedbackPrompt2(originalResponse, errorMessage, "tool", toolsDescription, userMessage);
|
|
1398
|
-
logger4.debug(`[SELECTION] Tool Selection Feedback Prompt:
|
|
1399
|
-
${feedbackPrompt}`);
|
|
1400
|
-
return feedbackPrompt;
|
|
1401
|
-
}
|
|
1402
|
-
function createFeedbackPrompt2(originalResponse, errorMessage, itemType, itemsDescription, userMessage) {
|
|
1403
417
|
return `Error parsing JSON: ${errorMessage}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
418
|
+
|
|
419
|
+
Your original response:
|
|
420
|
+
${originalResponse}
|
|
421
|
+
|
|
422
|
+
Please try again with valid JSON for resource selection.
|
|
423
|
+
Available resources:
|
|
424
|
+
${description}
|
|
425
|
+
|
|
426
|
+
User request: ${userMessage}`;
|
|
1413
427
|
}
|
|
1414
428
|
|
|
1415
|
-
// src/
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
if (
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
state: composedState,
|
|
1449
|
-
message,
|
|
1450
|
-
callback,
|
|
1451
|
-
mcpProvider
|
|
1452
|
-
});
|
|
1453
|
-
if (!toolSelectionName || toolSelectionName.noToolAvailable) {
|
|
1454
|
-
logger5.warn("[NO_TOOL_AVAILABLE] No appropriate tool available for the request");
|
|
1455
|
-
return await handleNoToolAvailable(callback, toolSelectionName);
|
|
1456
|
-
}
|
|
1457
|
-
const { serverName, toolName, reasoning } = toolSelectionName;
|
|
1458
|
-
logger5.info(`[CALLING] Calling tool "${serverName}/${toolName}" on server with reasoning: "${reasoning}"`);
|
|
1459
|
-
const toolSelectionArgument = await createToolSelectionArgument({
|
|
1460
|
-
runtime: runtime2,
|
|
1461
|
-
state: composedState,
|
|
429
|
+
// src/utils/wrapper.ts
|
|
430
|
+
import {
|
|
431
|
+
logger as logger2,
|
|
432
|
+
ModelType as ModelType3
|
|
433
|
+
} from "@elizaos/core";
|
|
434
|
+
async function withModelRetry({
|
|
435
|
+
runtime,
|
|
436
|
+
message,
|
|
437
|
+
state,
|
|
438
|
+
callback,
|
|
439
|
+
input,
|
|
440
|
+
validationFn,
|
|
441
|
+
createFeedbackPromptFn,
|
|
442
|
+
failureMsg,
|
|
443
|
+
retryCount = 0
|
|
444
|
+
}) {
|
|
445
|
+
const maxRetries = getMaxRetries(runtime);
|
|
446
|
+
try {
|
|
447
|
+
const parsed = typeof input === "string" ? parseJSON(input) : input;
|
|
448
|
+
const result = validationFn(parsed);
|
|
449
|
+
if (!result.success)
|
|
450
|
+
throw new Error(result.error);
|
|
451
|
+
return result.data;
|
|
452
|
+
} catch (e) {
|
|
453
|
+
const error = e instanceof Error ? e.message : "Parse error";
|
|
454
|
+
logger2.error({ error }, "[Retry] Parse failed");
|
|
455
|
+
if (retryCount < maxRetries) {
|
|
456
|
+
const feedback = createFeedbackPromptFn(input, error, state, message.content.text || "");
|
|
457
|
+
const retry = await runtime.useModel(ModelType3.OBJECT_LARGE, { prompt: feedback });
|
|
458
|
+
return withModelRetry({
|
|
459
|
+
runtime,
|
|
460
|
+
input: retry,
|
|
461
|
+
validationFn,
|
|
1462
462
|
message,
|
|
463
|
+
state,
|
|
464
|
+
createFeedbackPromptFn,
|
|
1463
465
|
callback,
|
|
1464
|
-
|
|
1465
|
-
|
|
466
|
+
failureMsg,
|
|
467
|
+
retryCount: retryCount + 1
|
|
1466
468
|
});
|
|
1467
|
-
if (!toolSelectionArgument) {
|
|
1468
|
-
logger5.warn("[NO_TOOL_SELECTION_ARGUMENT] No appropriate tool selection argument available");
|
|
1469
|
-
return await handleNoToolAvailable(callback, toolSelectionName);
|
|
1470
|
-
}
|
|
1471
|
-
logger5.info(`[SELECTED] Tool Selection result:
|
|
1472
|
-
${JSON.stringify(toolSelectionArgument, null, 2)}`);
|
|
1473
|
-
const result = await mcpService.callTool(serverName, toolName, toolSelectionArgument.toolArguments);
|
|
1474
|
-
const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, toolName, runtime2, message.entityId);
|
|
1475
|
-
const replyMemory = await handleToolResponse(runtime2, message, serverName, toolName, toolSelectionArgument.toolArguments, toolOutput, hasAttachments, attachments, composedState, mcpProvider, callback);
|
|
1476
|
-
const actionResult = {
|
|
1477
|
-
text: `Successfully called tool: ${serverName}/${toolName}. Reasoned response: ${replyMemory.content.text}`,
|
|
1478
|
-
values: {
|
|
1479
|
-
success: true,
|
|
1480
|
-
toolExecuted: true,
|
|
1481
|
-
serverName,
|
|
1482
|
-
toolName,
|
|
1483
|
-
hasAttachments,
|
|
1484
|
-
output: toolOutput
|
|
1485
|
-
},
|
|
1486
|
-
data: {
|
|
1487
|
-
actionName: "CALL_MCP_TOOL",
|
|
1488
|
-
serverName,
|
|
1489
|
-
toolName,
|
|
1490
|
-
toolArguments: toolSelectionArgument.toolArguments,
|
|
1491
|
-
reasoning: toolSelectionName.reasoning,
|
|
1492
|
-
output: toolOutput,
|
|
1493
|
-
attachments: attachments || []
|
|
1494
|
-
},
|
|
1495
|
-
success: true
|
|
1496
|
-
};
|
|
1497
|
-
logger5.info({
|
|
1498
|
-
serverName,
|
|
1499
|
-
toolName,
|
|
1500
|
-
hasOutput: !!toolOutput,
|
|
1501
|
-
outputLength: toolOutput?.length || 0,
|
|
1502
|
-
hasAttachments,
|
|
1503
|
-
reasoning: toolSelectionName.reasoning
|
|
1504
|
-
}, `[CALL_MCP_TOOL] Action result`);
|
|
1505
|
-
return actionResult;
|
|
1506
|
-
} catch (error) {
|
|
1507
|
-
return await handleMcpError(composedState, mcpProvider, error, runtime2, message, "tool", callback);
|
|
1508
469
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
content: {
|
|
1528
|
-
text: `I found the following information about climate change:
|
|
1529
|
-
|
|
1530
|
-
Climate change refers to long-term shifts in temperatures and weather patterns. These shifts may be natural, but since the 1800s, human activities have been the main driver of climate change, primarily due to the burning of fossil fuels like coal, oil, and gas, which produces heat-trapping gases.`,
|
|
1531
|
-
actions: ["CALL_MCP_TOOL"]
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
]
|
|
1535
|
-
]
|
|
1536
|
-
};
|
|
1537
|
-
|
|
1538
|
-
// src/actions/readResourceAction.ts
|
|
1539
|
-
import {
|
|
1540
|
-
ModelType as ModelType5,
|
|
1541
|
-
composePromptFromState as composePromptFromState4,
|
|
1542
|
-
logger as logger6
|
|
1543
|
-
} from "@elizaos/core";
|
|
1544
|
-
|
|
1545
|
-
// src/templates/resourceSelectionTemplate.ts
|
|
1546
|
-
var resourceSelectionTemplate = `
|
|
1547
|
-
{{{mcpProvider.text}}}
|
|
1548
|
-
|
|
1549
|
-
{{{recentMessages}}}
|
|
1550
|
-
|
|
1551
|
-
# Prompt
|
|
1552
|
-
|
|
1553
|
-
You are an intelligent assistant helping select the right resource to address a user's request.
|
|
1554
|
-
|
|
1555
|
-
CRITICAL INSTRUCTIONS:
|
|
1556
|
-
1. You MUST specify both a valid serverName AND uri from the list above
|
|
1557
|
-
2. The serverName value should match EXACTLY the server name shown in parentheses (Server: X)
|
|
1558
|
-
CORRECT: "serverName": "github" (if the server is called "github")
|
|
1559
|
-
WRONG: "serverName": "GitHub" or "Github" or any other variation
|
|
1560
|
-
3. The uri value should match EXACTLY the resource uri listed
|
|
1561
|
-
CORRECT: "uri": "weather://San Francisco/current" (if that's the exact uri)
|
|
1562
|
-
WRONG: "uri": "weather://sanfrancisco/current" or any variation
|
|
1563
|
-
4. Identify the user's information need from the conversation context
|
|
1564
|
-
5. Select the most appropriate resource based on its description and the request
|
|
1565
|
-
6. If no resource seems appropriate, output {"noResourceAvailable": true}
|
|
1566
|
-
|
|
1567
|
-
!!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
|
|
1568
|
-
|
|
1569
|
-
STRICT FORMAT REQUIREMENTS:
|
|
1570
|
-
- NO code block formatting (NO backticks or \`\`\`)
|
|
1571
|
-
- NO comments (NO // or /* */)
|
|
1572
|
-
- NO placeholders like "replace with...", "example", "your...", "actual", etc.
|
|
1573
|
-
- Every parameter value must be a concrete, usable value (not instructions to replace)
|
|
1574
|
-
- Use proper JSON syntax with double quotes for strings
|
|
1575
|
-
- NO explanatory text before or after the JSON object
|
|
1576
|
-
|
|
1577
|
-
EXAMPLE RESPONSE:
|
|
1578
|
-
{
|
|
1579
|
-
"serverName": "weather-server",
|
|
1580
|
-
"uri": "weather://San Francisco/current",
|
|
1581
|
-
"reasoning": "Based on the conversation, the user is asking about current weather in San Francisco. This resource provides up-to-date weather information for that city."
|
|
470
|
+
if (callback && failureMsg) {
|
|
471
|
+
await callback({ text: failureMsg, thought: "Parse failed after retries", actions: ["REPLY"] });
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function getMaxRetries(runtime) {
|
|
477
|
+
try {
|
|
478
|
+
const mcp = runtime.getSetting("mcp");
|
|
479
|
+
if (mcp?.maxRetries !== undefined) {
|
|
480
|
+
const val = Number(mcp.maxRetries);
|
|
481
|
+
if (!isNaN(val) && val >= 0)
|
|
482
|
+
return val;
|
|
483
|
+
}
|
|
484
|
+
} catch (e) {
|
|
485
|
+
logger2.debug({ error: e instanceof Error ? e.message : e }, "[Retry] Failed to get maxRetries setting");
|
|
486
|
+
}
|
|
487
|
+
return DEFAULT_MAX_RETRIES;
|
|
1582
488
|
}
|
|
1583
|
-
|
|
1584
|
-
REMEMBER: Your response will be parsed directly as JSON. If it fails to parse, the operation will fail completely!
|
|
1585
|
-
`;
|
|
1586
489
|
|
|
1587
490
|
// src/actions/readResourceAction.ts
|
|
1588
491
|
function createResourceSelectionPrompt(composedState, userMessage) {
|
|
@@ -1615,7 +518,7 @@ function createResourceSelectionPrompt(composedState, userMessage) {
|
|
|
1615
518
|
userMessage
|
|
1616
519
|
}
|
|
1617
520
|
};
|
|
1618
|
-
return
|
|
521
|
+
return composePromptFromState3({
|
|
1619
522
|
state: enhancedState,
|
|
1620
523
|
template: resourceSelectionTemplate
|
|
1621
524
|
});
|
|
@@ -1633,16 +536,16 @@ var readResourceAction = {
|
|
|
1633
536
|
"ACCESS_MCP_RESOURCE"
|
|
1634
537
|
],
|
|
1635
538
|
description: "Reads a resource from an MCP server",
|
|
1636
|
-
validate: async (
|
|
1637
|
-
const mcpService =
|
|
539
|
+
validate: async (runtime, _message, _state) => {
|
|
540
|
+
const mcpService = runtime.getService(MCP_SERVICE_NAME);
|
|
1638
541
|
if (!mcpService)
|
|
1639
542
|
return false;
|
|
1640
543
|
const servers = mcpService.getServers();
|
|
1641
544
|
return servers.length > 0 && servers.some((server) => server.status === "connected" && server.resources && server.resources.length > 0);
|
|
1642
545
|
},
|
|
1643
|
-
handler: async (
|
|
1644
|
-
const composedState = await
|
|
1645
|
-
const mcpService =
|
|
546
|
+
handler: async (runtime, message, _state, _options, callback) => {
|
|
547
|
+
const composedState = await runtime.composeState(message, ["RECENT_MESSAGES", "MCP"]);
|
|
548
|
+
const mcpService = runtime.getService(MCP_SERVICE_NAME);
|
|
1646
549
|
if (!mcpService) {
|
|
1647
550
|
throw new Error("MCP service not available");
|
|
1648
551
|
}
|
|
@@ -1650,11 +553,11 @@ var readResourceAction = {
|
|
|
1650
553
|
try {
|
|
1651
554
|
await sendInitialResponse(callback);
|
|
1652
555
|
const resourceSelectionPrompt = createResourceSelectionPrompt(composedState, message.content.text || "");
|
|
1653
|
-
const resourceSelection = await
|
|
556
|
+
const resourceSelection = await runtime.useModel(ModelType4.TEXT_SMALL, {
|
|
1654
557
|
prompt: resourceSelectionPrompt
|
|
1655
558
|
});
|
|
1656
559
|
const parsedSelection = await withModelRetry({
|
|
1657
|
-
runtime
|
|
560
|
+
runtime,
|
|
1658
561
|
state: composedState,
|
|
1659
562
|
message,
|
|
1660
563
|
callback,
|
|
@@ -1690,11 +593,11 @@ var readResourceAction = {
|
|
|
1690
593
|
};
|
|
1691
594
|
}
|
|
1692
595
|
const { serverName, uri, reasoning } = parsedSelection;
|
|
1693
|
-
|
|
596
|
+
logger3.debug(`Selected resource "${uri}" on server "${serverName}" because: ${reasoning}`);
|
|
1694
597
|
const result = await mcpService.readResource(serverName, uri);
|
|
1695
|
-
|
|
598
|
+
logger3.debug(`Read resource ${uri} from server ${serverName}`);
|
|
1696
599
|
const { resourceContent, resourceMeta } = processResourceResult(result, uri);
|
|
1697
|
-
await handleResourceAnalysis(
|
|
600
|
+
await handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback);
|
|
1698
601
|
return {
|
|
1699
602
|
text: `Successfully read resource: ${uri}`,
|
|
1700
603
|
values: {
|
|
@@ -1713,271 +616,891 @@ var readResourceAction = {
|
|
|
1713
616
|
},
|
|
1714
617
|
success: true
|
|
1715
618
|
};
|
|
1716
|
-
} catch (error) {
|
|
1717
|
-
return await handleMcpError(composedState, mcpProvider, error,
|
|
619
|
+
} catch (error) {
|
|
620
|
+
return await handleMcpError(composedState, mcpProvider, error, runtime, message, "resource", callback);
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
examples: [
|
|
624
|
+
[
|
|
625
|
+
{
|
|
626
|
+
name: "{{user}}",
|
|
627
|
+
content: {
|
|
628
|
+
text: "Can you get the documentation about installing ElizaOS?"
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: "{{assistant}}",
|
|
633
|
+
content: {
|
|
634
|
+
text: `I'll retrieve that information for you. Let me access the resource...`,
|
|
635
|
+
actions: ["READ_MCP_RESOURCE"]
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
name: "{{assistant}}",
|
|
640
|
+
content: {
|
|
641
|
+
text: `ElizaOS installation is straightforward. You'll need Node.js 23+ and Git installed. For Windows users, WSL 2 is required. The quickest way to get started is by cloning the ElizaOS starter repository with \`git clone https://github.com/elizaos/eliza-starter.git\`, then run \`cd eliza-starter && cp .env.example .env && bun i && bun run build && bun start\`. This will set up a development environment with the core features enabled. After starting, you can access the web interface at http://localhost:3000 to interact with your agent.`,
|
|
642
|
+
actions: ["READ_MCP_RESOURCE"]
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
]
|
|
646
|
+
]
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// src/provider.ts
|
|
650
|
+
var EMPTY_PROVIDER = { values: { mcp: {} }, data: { mcp: {} }, text: "No MCP servers available." };
|
|
651
|
+
var provider = {
|
|
652
|
+
name: "MCP",
|
|
653
|
+
description: "Connected MCP servers, tools, and resources",
|
|
654
|
+
get: async (runtime, _message, _state) => {
|
|
655
|
+
const svc = runtime.getService(MCP_SERVICE_NAME);
|
|
656
|
+
if (!svc)
|
|
657
|
+
return EMPTY_PROVIDER;
|
|
658
|
+
await svc.waitForInitialization();
|
|
659
|
+
return svc.getProviderData();
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
// src/service.ts
|
|
664
|
+
import { Service, logger as logger6 } from "@elizaos/core";
|
|
665
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
666
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
667
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
668
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
669
|
+
|
|
670
|
+
// src/actions/dynamic-tool-actions.ts
|
|
671
|
+
import {
|
|
672
|
+
logger as logger4
|
|
673
|
+
} from "@elizaos/core";
|
|
674
|
+
|
|
675
|
+
// src/utils/schema-converter.ts
|
|
676
|
+
function mapJsonSchemaType(jsonType) {
|
|
677
|
+
if (Array.isArray(jsonType)) {
|
|
678
|
+
return mapJsonSchemaType(jsonType.find((t) => t !== "null"));
|
|
679
|
+
}
|
|
680
|
+
switch (jsonType) {
|
|
681
|
+
case "string":
|
|
682
|
+
return "string";
|
|
683
|
+
case "number":
|
|
684
|
+
case "integer":
|
|
685
|
+
return "number";
|
|
686
|
+
case "boolean":
|
|
687
|
+
return "boolean";
|
|
688
|
+
case "array":
|
|
689
|
+
return "array";
|
|
690
|
+
default:
|
|
691
|
+
return "object";
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function buildDescription(name, prop) {
|
|
695
|
+
const parts = [prop.description || `Parameter: ${name}`];
|
|
696
|
+
if (prop.enum?.length)
|
|
697
|
+
parts.push(`Allowed: ${prop.enum.map((v) => JSON.stringify(v)).join(", ")}`);
|
|
698
|
+
if (prop.format)
|
|
699
|
+
parts.push(`Format: ${prop.format}`);
|
|
700
|
+
if (prop.minimum !== undefined || prop.maximum !== undefined) {
|
|
701
|
+
parts.push(`Range: ${[prop.minimum !== undefined ? `min: ${prop.minimum}` : "", prop.maximum !== undefined ? `max: ${prop.maximum}` : ""].filter(Boolean).join(", ")}`);
|
|
702
|
+
}
|
|
703
|
+
if (prop.minLength !== undefined || prop.maxLength !== undefined) {
|
|
704
|
+
parts.push(`Length: ${[prop.minLength !== undefined ? `min: ${prop.minLength}` : "", prop.maxLength !== undefined ? `max: ${prop.maxLength}` : ""].filter(Boolean).join(", ")}`);
|
|
705
|
+
}
|
|
706
|
+
if (prop.pattern)
|
|
707
|
+
parts.push(`Pattern: ${prop.pattern}`);
|
|
708
|
+
if (prop.default !== undefined)
|
|
709
|
+
parts.push(`Default: ${JSON.stringify(prop.default)}`);
|
|
710
|
+
if (prop.type === "array" && prop.items)
|
|
711
|
+
parts.push(`Array of ${prop.items.type || "any"}`);
|
|
712
|
+
if (prop.type === "object" && prop.properties) {
|
|
713
|
+
const keys = Object.keys(prop.properties);
|
|
714
|
+
parts.push(`Object with: ${keys.join(", ")}`);
|
|
715
|
+
}
|
|
716
|
+
return parts.join(". ");
|
|
717
|
+
}
|
|
718
|
+
function convertJsonSchemaToActionParams(schema) {
|
|
719
|
+
const properties = schema?.properties;
|
|
720
|
+
if (!properties || Object.keys(properties).length === 0)
|
|
721
|
+
return;
|
|
722
|
+
const required = new Set(schema?.required || []);
|
|
723
|
+
const params = {};
|
|
724
|
+
for (const [name, prop] of Object.entries(properties)) {
|
|
725
|
+
params[name] = {
|
|
726
|
+
type: mapJsonSchemaType(prop.type),
|
|
727
|
+
description: buildDescription(name, prop),
|
|
728
|
+
required: required.has(name)
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
return Object.keys(params).length > 0 ? params : undefined;
|
|
732
|
+
}
|
|
733
|
+
function validateParamsAgainstSchema(params, schema) {
|
|
734
|
+
if (!schema)
|
|
735
|
+
return [];
|
|
736
|
+
const errors = [];
|
|
737
|
+
const properties = schema.properties;
|
|
738
|
+
const required = new Set(schema.required || []);
|
|
739
|
+
for (const field of required) {
|
|
740
|
+
if (params[field] === undefined || params[field] === null) {
|
|
741
|
+
errors.push(`Missing required parameter: ${field}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (properties) {
|
|
745
|
+
for (const [name, value] of Object.entries(params)) {
|
|
746
|
+
const prop = properties[name];
|
|
747
|
+
if (!prop)
|
|
748
|
+
continue;
|
|
749
|
+
const expected = mapJsonSchemaType(prop.type);
|
|
750
|
+
const actual = getValueType(value);
|
|
751
|
+
if (actual !== expected && value !== null && value !== undefined) {
|
|
752
|
+
errors.push(`Parameter '${name}' expected ${expected}, got ${actual}`);
|
|
753
|
+
}
|
|
754
|
+
if (prop.enum && !prop.enum.includes(value)) {
|
|
755
|
+
errors.push(`Parameter '${name}' must be one of: ${prop.enum.map((v) => JSON.stringify(v)).join(", ")}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return errors;
|
|
760
|
+
}
|
|
761
|
+
function getValueType(value) {
|
|
762
|
+
if (Array.isArray(value))
|
|
763
|
+
return "array";
|
|
764
|
+
if (value === null)
|
|
765
|
+
return "object";
|
|
766
|
+
switch (typeof value) {
|
|
767
|
+
case "string":
|
|
768
|
+
return "string";
|
|
769
|
+
case "number":
|
|
770
|
+
return "number";
|
|
771
|
+
case "boolean":
|
|
772
|
+
return "boolean";
|
|
773
|
+
default:
|
|
774
|
+
return "object";
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// src/utils/action-naming.ts
|
|
779
|
+
function normalize(str) {
|
|
780
|
+
return str.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
781
|
+
}
|
|
782
|
+
function toActionName(serverName, toolName) {
|
|
783
|
+
const server = normalize(serverName);
|
|
784
|
+
const tool = normalize(toolName);
|
|
785
|
+
if (!server)
|
|
786
|
+
return tool || "_";
|
|
787
|
+
if (!tool)
|
|
788
|
+
return server + "_";
|
|
789
|
+
if (tool.startsWith(server + "_"))
|
|
790
|
+
return tool;
|
|
791
|
+
return `${server}_${tool}`;
|
|
792
|
+
}
|
|
793
|
+
function generateSimiles(serverName, toolName) {
|
|
794
|
+
const tool = normalize(toolName);
|
|
795
|
+
const fullName = toActionName(serverName, toolName);
|
|
796
|
+
const similes = [
|
|
797
|
+
tool,
|
|
798
|
+
`${serverName}/${toolName}`,
|
|
799
|
+
`${serverName.toLowerCase()}/${toolName.toLowerCase()}`,
|
|
800
|
+
`MCP_${fullName}`
|
|
801
|
+
];
|
|
802
|
+
const parts = toolName.split("_");
|
|
803
|
+
if (parts.length === 2) {
|
|
804
|
+
const reversed = `${parts[1]}_${parts[0]}`.toUpperCase();
|
|
805
|
+
if (reversed !== tool)
|
|
806
|
+
similes.push(reversed);
|
|
807
|
+
}
|
|
808
|
+
return [...new Set(similes)].filter((s) => s !== fullName);
|
|
809
|
+
}
|
|
810
|
+
function makeUniqueActionName(serverName, toolName, existing) {
|
|
811
|
+
let name = toActionName(serverName, toolName);
|
|
812
|
+
if (!existing.has(name))
|
|
813
|
+
return name;
|
|
814
|
+
let suffix = 2;
|
|
815
|
+
while (existing.has(`${name}_${suffix}`))
|
|
816
|
+
suffix++;
|
|
817
|
+
return `${name}_${suffix}`;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/actions/dynamic-tool-actions.ts
|
|
821
|
+
function extractParams(message, state) {
|
|
822
|
+
const content = message.content;
|
|
823
|
+
return content.actionParams || content.actionInput || state?.data?.actionParams || {};
|
|
824
|
+
}
|
|
825
|
+
function createMcpToolAction(serverName, tool, existingNames) {
|
|
826
|
+
const actionName = makeUniqueActionName(serverName, tool.name, existingNames);
|
|
827
|
+
const description = `${tool.description || `Execute ${tool.name}`} (MCP: ${serverName}/${tool.name})`;
|
|
828
|
+
return {
|
|
829
|
+
name: actionName,
|
|
830
|
+
description,
|
|
831
|
+
similes: generateSimiles(serverName, tool.name),
|
|
832
|
+
parameters: convertJsonSchemaToActionParams(tool.inputSchema),
|
|
833
|
+
validate: async (runtime) => {
|
|
834
|
+
const svc = runtime.getService(MCP_SERVICE_NAME);
|
|
835
|
+
if (!svc)
|
|
836
|
+
return false;
|
|
837
|
+
if (svc.isLazyConnection(serverName))
|
|
838
|
+
return true;
|
|
839
|
+
const server = svc.getServers().find((s) => s.name === serverName);
|
|
840
|
+
return server?.status === "connected" && !!server.tools?.some((t) => t.name === tool.name);
|
|
841
|
+
},
|
|
842
|
+
handler: async (runtime, message, state, _options, callback) => {
|
|
843
|
+
const svc = runtime.getService(MCP_SERVICE_NAME);
|
|
844
|
+
if (!svc) {
|
|
845
|
+
return { success: false, error: "MCP service not available", data: { actionName, serverName, toolName: tool.name } };
|
|
846
|
+
}
|
|
847
|
+
const params = extractParams(message, state);
|
|
848
|
+
logger4.info({ serverName, toolName: tool.name, params }, `[MCP] Executing ${actionName}`);
|
|
849
|
+
const errors = validateParamsAgainstSchema(params, tool.inputSchema);
|
|
850
|
+
const missing = errors.filter((e) => e.startsWith("Missing required"));
|
|
851
|
+
if (missing.length > 0) {
|
|
852
|
+
logger4.error({ missing, params }, `[MCP] Missing required params for ${actionName}`);
|
|
853
|
+
return { success: false, error: missing.join(", "), data: { actionName, serverName, toolName: tool.name } };
|
|
854
|
+
}
|
|
855
|
+
const warnings = errors.filter((e) => !e.startsWith("Missing required"));
|
|
856
|
+
if (warnings.length > 0) {
|
|
857
|
+
logger4.warn({ warnings, params }, `[MCP] Type warnings for ${actionName}`);
|
|
858
|
+
}
|
|
859
|
+
const result = await svc.callTool(serverName, tool.name, params);
|
|
860
|
+
const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, tool.name, runtime, message.entityId);
|
|
861
|
+
if (result.isError) {
|
|
862
|
+
logger4.error({ serverName, toolName: tool.name, output: toolOutput }, `[MCP] Tool error`);
|
|
863
|
+
return {
|
|
864
|
+
success: false,
|
|
865
|
+
error: toolOutput || "Tool execution failed",
|
|
866
|
+
text: toolOutput,
|
|
867
|
+
data: { actionName, serverName, toolName: tool.name, toolArguments: params, isError: true }
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
if (callback && hasAttachments && attachments.length > 0) {
|
|
871
|
+
await callback({ text: `Executed ${serverName}/${tool.name}`, attachments });
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
success: true,
|
|
875
|
+
text: toolOutput,
|
|
876
|
+
values: { success: true, serverName, toolName: tool.name, hasAttachments, output: toolOutput },
|
|
877
|
+
data: { actionName, serverName, toolName: tool.name, toolArguments: params, output: toolOutput, attachments: attachments.length > 0 ? attachments : undefined }
|
|
878
|
+
};
|
|
879
|
+
},
|
|
880
|
+
examples: [[
|
|
881
|
+
{ name: "{{user}}", content: { text: `Can you use ${tool.name}?` } },
|
|
882
|
+
{ name: "{{assistant}}", content: { text: `I'll execute ${tool.name} for you.`, actions: [actionName] } }
|
|
883
|
+
]],
|
|
884
|
+
_mcpMeta: { serverName, toolName: tool.name, originalSchema: tool.inputSchema }
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
function createMcpToolActions(serverName, tools, existingNames) {
|
|
888
|
+
const actions = tools.map((tool) => {
|
|
889
|
+
const action = createMcpToolAction(serverName, tool, existingNames);
|
|
890
|
+
existingNames.add(action.name);
|
|
891
|
+
logger4.debug({ actionName: action.name, serverName, toolName: tool.name }, `[MCP] Created action`);
|
|
892
|
+
return action;
|
|
893
|
+
});
|
|
894
|
+
logger4.info({ serverName, toolCount: actions.length }, `[MCP] Created ${actions.length} actions for ${serverName}`);
|
|
895
|
+
return actions;
|
|
896
|
+
}
|
|
897
|
+
function isMcpToolAction(action) {
|
|
898
|
+
return "_mcpMeta" in action && typeof action._mcpMeta === "object";
|
|
899
|
+
}
|
|
900
|
+
function getMcpToolActionsForServer(actions, serverName) {
|
|
901
|
+
return actions.filter((a) => isMcpToolAction(a) && a._mcpMeta.serverName === serverName);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// src/cache/schema-cache.ts
|
|
905
|
+
import { createHash } from "crypto";
|
|
906
|
+
import { logger as logger5 } from "@elizaos/core";
|
|
907
|
+
var CACHE_KEY_PREFIX = "mcp:schema:v1";
|
|
908
|
+
var DEFAULT_TTL = 3600;
|
|
909
|
+
|
|
910
|
+
class UpstashClient {
|
|
911
|
+
url;
|
|
912
|
+
token;
|
|
913
|
+
constructor(url, token) {
|
|
914
|
+
this.url = url;
|
|
915
|
+
this.token = token;
|
|
916
|
+
if (url.startsWith("redis://") || url.startsWith("rediss://")) {
|
|
917
|
+
throw new Error("MCP_CACHE_REDIS_URL must be Upstash REST URL (https://...), not Redis protocol URL");
|
|
918
|
+
}
|
|
919
|
+
this.url = url.replace(/\/$/, "");
|
|
920
|
+
}
|
|
921
|
+
async exec(cmd) {
|
|
922
|
+
try {
|
|
923
|
+
const res = await fetch(this.url, {
|
|
924
|
+
method: "POST",
|
|
925
|
+
headers: { Authorization: `Bearer ${this.token}`, "Content-Type": "application/json" },
|
|
926
|
+
body: JSON.stringify(cmd)
|
|
927
|
+
});
|
|
928
|
+
if (!res.ok)
|
|
929
|
+
return null;
|
|
930
|
+
const data = await res.json();
|
|
931
|
+
return data.result;
|
|
932
|
+
} catch {
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
get = (key) => this.exec(["GET", key]);
|
|
937
|
+
setex = (key, ttl, val) => this.exec(["SETEX", key, String(ttl), val]);
|
|
938
|
+
del = (key) => this.exec(["DEL", key]);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
class McpSchemaCache {
|
|
942
|
+
client = null;
|
|
943
|
+
ttl;
|
|
944
|
+
constructor() {
|
|
945
|
+
const enabled = process.env.MCP_SCHEMA_CACHE_ENABLED === "true";
|
|
946
|
+
const url = process.env.MCP_CACHE_REDIS_URL;
|
|
947
|
+
const token = process.env.MCP_CACHE_REDIS_TOKEN;
|
|
948
|
+
this.ttl = parseInt(process.env.MCP_SCHEMA_CACHE_TTL || String(DEFAULT_TTL), 10);
|
|
949
|
+
if (!enabled) {
|
|
950
|
+
logger5.debug("[McpSchemaCache] Disabled");
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (!url || !token) {
|
|
954
|
+
logger5.warn("[McpSchemaCache] Enabled but missing MCP_CACHE_REDIS_URL or MCP_CACHE_REDIS_TOKEN");
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
try {
|
|
958
|
+
this.client = new UpstashClient(url, token);
|
|
959
|
+
logger5.info(`[McpSchemaCache] Enabled (TTL: ${this.ttl}s)`);
|
|
960
|
+
} catch (e) {
|
|
961
|
+
logger5.error(`[McpSchemaCache] Init failed: ${e instanceof Error ? e.message : e}`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
get isEnabled() {
|
|
965
|
+
return this.client !== null;
|
|
966
|
+
}
|
|
967
|
+
hashConfig(config) {
|
|
968
|
+
const sortedJson = JSON.stringify(config, (_, value) => value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value).sort().reduce((sorted, key) => {
|
|
969
|
+
sorted[key] = value[key];
|
|
970
|
+
return sorted;
|
|
971
|
+
}, {}) : value);
|
|
972
|
+
return createHash("sha256").update(sortedJson).digest("hex").slice(0, 16);
|
|
973
|
+
}
|
|
974
|
+
key(agentId, serverName) {
|
|
975
|
+
return `${CACHE_KEY_PREFIX}:${agentId}:${serverName}`;
|
|
976
|
+
}
|
|
977
|
+
async getSchemas(agentId, serverName, configHash) {
|
|
978
|
+
if (!this.client)
|
|
979
|
+
return null;
|
|
980
|
+
try {
|
|
981
|
+
const raw = await this.client.get(this.key(agentId, serverName));
|
|
982
|
+
if (!raw)
|
|
983
|
+
return null;
|
|
984
|
+
const cached = JSON.parse(raw);
|
|
985
|
+
if (cached.configHash !== configHash) {
|
|
986
|
+
await this.client.del(this.key(agentId, serverName));
|
|
987
|
+
logger5.debug(`[McpSchemaCache] Config changed for ${serverName}, invalidated`);
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
logger5.info(`[McpSchemaCache] Hit: ${serverName} (${cached.tools.length} tools)`);
|
|
991
|
+
return cached;
|
|
992
|
+
} catch {
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
async setSchemas(agentId, serverName, configHash, tools) {
|
|
997
|
+
if (!this.client)
|
|
998
|
+
return;
|
|
999
|
+
try {
|
|
1000
|
+
const cached = {
|
|
1001
|
+
serverName,
|
|
1002
|
+
tools: tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })),
|
|
1003
|
+
cachedAt: Date.now(),
|
|
1004
|
+
configHash
|
|
1005
|
+
};
|
|
1006
|
+
await this.client.setex(this.key(agentId, serverName), this.ttl, JSON.stringify(cached));
|
|
1007
|
+
logger5.info(`[McpSchemaCache] Cached: ${serverName} (${tools.length} tools)`);
|
|
1008
|
+
} catch {}
|
|
1009
|
+
}
|
|
1010
|
+
static toTools(cached) {
|
|
1011
|
+
return cached.tools.map((t) => ({
|
|
1012
|
+
name: t.name,
|
|
1013
|
+
description: t.description,
|
|
1014
|
+
inputSchema: t.inputSchema || { type: "object", properties: {} }
|
|
1015
|
+
}));
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
var instance = null;
|
|
1019
|
+
function getSchemaCache() {
|
|
1020
|
+
if (!instance)
|
|
1021
|
+
instance = new McpSchemaCache;
|
|
1022
|
+
return instance;
|
|
1023
|
+
}
|
|
1024
|
+
// src/tool-compatibility/base.ts
|
|
1025
|
+
class McpToolCompatibility {
|
|
1026
|
+
modelInfo;
|
|
1027
|
+
constructor(modelInfo) {
|
|
1028
|
+
this.modelInfo = modelInfo;
|
|
1029
|
+
}
|
|
1030
|
+
transformToolSchema(toolSchema) {
|
|
1031
|
+
return this.shouldApply() ? this.processSchema(toolSchema) : toolSchema;
|
|
1032
|
+
}
|
|
1033
|
+
processSchema(schema) {
|
|
1034
|
+
const processed = { ...schema };
|
|
1035
|
+
switch (processed.type) {
|
|
1036
|
+
case "string":
|
|
1037
|
+
return this.processTypeSchema(processed, this.getUnsupportedStringProperties());
|
|
1038
|
+
case "number":
|
|
1039
|
+
case "integer":
|
|
1040
|
+
return this.processTypeSchema(processed, this.getUnsupportedNumberProperties());
|
|
1041
|
+
case "array":
|
|
1042
|
+
return this.processArraySchema(processed);
|
|
1043
|
+
case "object":
|
|
1044
|
+
return this.processObjectSchema(processed);
|
|
1045
|
+
default:
|
|
1046
|
+
return this.processGenericSchema(processed);
|
|
1718
1047
|
}
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1048
|
+
}
|
|
1049
|
+
processTypeSchema(schema, unsupported) {
|
|
1050
|
+
const processed = { ...schema };
|
|
1051
|
+
const constraints = {};
|
|
1052
|
+
for (const prop of [
|
|
1053
|
+
"minLength",
|
|
1054
|
+
"maxLength",
|
|
1055
|
+
"pattern",
|
|
1056
|
+
"format",
|
|
1057
|
+
"enum",
|
|
1058
|
+
"minimum",
|
|
1059
|
+
"maximum",
|
|
1060
|
+
"exclusiveMinimum",
|
|
1061
|
+
"exclusiveMaximum",
|
|
1062
|
+
"multipleOf",
|
|
1063
|
+
"minItems",
|
|
1064
|
+
"maxItems",
|
|
1065
|
+
"uniqueItems"
|
|
1066
|
+
]) {
|
|
1067
|
+
if (schema[prop] !== undefined) {
|
|
1068
|
+
constraints[prop] = schema[prop];
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
for (const prop of unsupported) {
|
|
1072
|
+
delete processed[prop];
|
|
1073
|
+
}
|
|
1074
|
+
if (Object.keys(constraints).length > 0) {
|
|
1075
|
+
processed.description = this.mergeDescription(schema.description, constraints);
|
|
1076
|
+
}
|
|
1077
|
+
return processed;
|
|
1078
|
+
}
|
|
1079
|
+
processArraySchema(schema) {
|
|
1080
|
+
const processed = this.processTypeSchema(schema, this.getUnsupportedArrayProperties());
|
|
1081
|
+
if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
|
|
1082
|
+
processed.items = this.processSchema(schema.items);
|
|
1083
|
+
}
|
|
1084
|
+
return processed;
|
|
1085
|
+
}
|
|
1086
|
+
processObjectSchema(schema) {
|
|
1087
|
+
const processed = this.processTypeSchema(schema, this.getUnsupportedObjectProperties());
|
|
1088
|
+
if (schema.properties) {
|
|
1089
|
+
processed.properties = {};
|
|
1090
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1091
|
+
processed.properties[key] = typeof prop === "object" && !Array.isArray(prop) ? this.processSchema(prop) : prop;
|
|
1741
1092
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
}
|
|
1093
|
+
}
|
|
1094
|
+
return processed;
|
|
1095
|
+
}
|
|
1096
|
+
processGenericSchema(schema) {
|
|
1097
|
+
const processed = { ...schema };
|
|
1098
|
+
for (const key of ["oneOf", "anyOf", "allOf"]) {
|
|
1099
|
+
if (Array.isArray(schema[key])) {
|
|
1100
|
+
processed[key] = schema[key].map((s) => typeof s === "object" ? this.processSchema(s) : s);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
return processed;
|
|
1104
|
+
}
|
|
1105
|
+
mergeDescription(original, constraints) {
|
|
1106
|
+
const json = JSON.stringify(constraints);
|
|
1107
|
+
return original ? `${original}
|
|
1108
|
+
${json}` : json;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
// src/tool-compatibility/providers/openai.ts
|
|
1112
|
+
class OpenAIMcpCompatibility extends McpToolCompatibility {
|
|
1113
|
+
constructor(modelInfo) {
|
|
1114
|
+
super(modelInfo);
|
|
1115
|
+
}
|
|
1116
|
+
shouldApply() {
|
|
1117
|
+
return this.modelInfo.provider === "openai" && (!this.modelInfo.supportsStructuredOutputs || this.modelInfo.isReasoningModel === true);
|
|
1118
|
+
}
|
|
1119
|
+
getUnsupportedStringProperties() {
|
|
1120
|
+
return this.modelInfo.isReasoningModel || this.modelInfo.modelId.includes("gpt-3.5") ? ["format", "pattern"] : ["format"];
|
|
1121
|
+
}
|
|
1122
|
+
getUnsupportedNumberProperties() {
|
|
1123
|
+
return this.modelInfo.isReasoningModel ? ["exclusiveMinimum", "exclusiveMaximum", "multipleOf"] : [];
|
|
1124
|
+
}
|
|
1125
|
+
getUnsupportedArrayProperties() {
|
|
1126
|
+
return this.modelInfo.isReasoningModel ? ["uniqueItems"] : [];
|
|
1127
|
+
}
|
|
1128
|
+
getUnsupportedObjectProperties() {
|
|
1129
|
+
return ["minProperties", "maxProperties"];
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1745
1132
|
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
return
|
|
1133
|
+
class OpenAIReasoningMcpCompatibility extends McpToolCompatibility {
|
|
1134
|
+
constructor(modelInfo) {
|
|
1135
|
+
super(modelInfo);
|
|
1136
|
+
}
|
|
1137
|
+
shouldApply() {
|
|
1138
|
+
return this.modelInfo.provider === "openai" && this.modelInfo.isReasoningModel === true;
|
|
1139
|
+
}
|
|
1140
|
+
getUnsupportedStringProperties() {
|
|
1141
|
+
return ["format", "pattern", "minLength", "maxLength"];
|
|
1142
|
+
}
|
|
1143
|
+
getUnsupportedNumberProperties() {
|
|
1144
|
+
return ["exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
1145
|
+
}
|
|
1146
|
+
getUnsupportedArrayProperties() {
|
|
1147
|
+
return ["uniqueItems", "minItems", "maxItems"];
|
|
1148
|
+
}
|
|
1149
|
+
getUnsupportedObjectProperties() {
|
|
1150
|
+
return ["minProperties", "maxProperties", "additionalProperties"];
|
|
1151
|
+
}
|
|
1152
|
+
mergeDescription(original, constraints) {
|
|
1153
|
+
const rules = [];
|
|
1154
|
+
if (constraints.minLength)
|
|
1155
|
+
rules.push(`minimum ${constraints.minLength} characters`);
|
|
1156
|
+
if (constraints.maxLength)
|
|
1157
|
+
rules.push(`maximum ${constraints.maxLength} characters`);
|
|
1158
|
+
if (constraints.minimum !== undefined)
|
|
1159
|
+
rules.push(`must be >= ${constraints.minimum}`);
|
|
1160
|
+
if (constraints.maximum !== undefined)
|
|
1161
|
+
rules.push(`must be <= ${constraints.maximum}`);
|
|
1162
|
+
if (constraints.format === "email")
|
|
1163
|
+
rules.push(`must be a valid email`);
|
|
1164
|
+
if (constraints.format === "uri" || constraints.format === "url")
|
|
1165
|
+
rules.push(`must be a valid URL`);
|
|
1166
|
+
if (constraints.pattern)
|
|
1167
|
+
rules.push(`must match: ${constraints.pattern}`);
|
|
1168
|
+
if (constraints.enum)
|
|
1169
|
+
rules.push(`must be one of: ${constraints.enum.join(", ")}`);
|
|
1170
|
+
if (constraints.minItems)
|
|
1171
|
+
rules.push(`at least ${constraints.minItems} items`);
|
|
1172
|
+
if (constraints.maxItems)
|
|
1173
|
+
rules.push(`at most ${constraints.maxItems} items`);
|
|
1174
|
+
const text = rules.length > 0 ? `IMPORTANT: ${rules.join(", ")}` : JSON.stringify(constraints);
|
|
1175
|
+
return original ? `${original}
|
|
1176
|
+
|
|
1177
|
+
${text}` : text;
|
|
1761
1178
|
}
|
|
1762
|
-
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// src/tool-compatibility/providers/anthropic.ts
|
|
1182
|
+
class AnthropicMcpCompatibility extends McpToolCompatibility {
|
|
1183
|
+
constructor(modelInfo) {
|
|
1184
|
+
super(modelInfo);
|
|
1185
|
+
}
|
|
1186
|
+
shouldApply() {
|
|
1187
|
+
return this.modelInfo.provider === "anthropic";
|
|
1188
|
+
}
|
|
1189
|
+
getUnsupportedStringProperties() {
|
|
1190
|
+
return [];
|
|
1191
|
+
}
|
|
1192
|
+
getUnsupportedNumberProperties() {
|
|
1193
|
+
return [];
|
|
1194
|
+
}
|
|
1195
|
+
getUnsupportedArrayProperties() {
|
|
1196
|
+
return [];
|
|
1197
|
+
}
|
|
1198
|
+
getUnsupportedObjectProperties() {
|
|
1199
|
+
return ["additionalProperties"];
|
|
1200
|
+
}
|
|
1201
|
+
mergeDescription(original, constraints) {
|
|
1202
|
+
const hints = [];
|
|
1203
|
+
if (constraints.additionalProperties === false)
|
|
1204
|
+
hints.push("Only use the specified properties");
|
|
1205
|
+
if (constraints.format === "date-time")
|
|
1206
|
+
hints.push("Use ISO 8601 format");
|
|
1207
|
+
if (constraints.pattern)
|
|
1208
|
+
hints.push(`Must match: ${constraints.pattern}`);
|
|
1209
|
+
const text = hints.join(". ");
|
|
1210
|
+
return original && text ? `${original}. ${text}` : original || text || "";
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// src/tool-compatibility/providers/google.ts
|
|
1215
|
+
class GoogleMcpCompatibility extends McpToolCompatibility {
|
|
1216
|
+
constructor(modelInfo) {
|
|
1217
|
+
super(modelInfo);
|
|
1218
|
+
}
|
|
1219
|
+
shouldApply() {
|
|
1220
|
+
return this.modelInfo.provider === "google";
|
|
1221
|
+
}
|
|
1222
|
+
getUnsupportedStringProperties() {
|
|
1223
|
+
return ["minLength", "maxLength", "pattern", "format"];
|
|
1224
|
+
}
|
|
1225
|
+
getUnsupportedNumberProperties() {
|
|
1226
|
+
return ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
1227
|
+
}
|
|
1228
|
+
getUnsupportedArrayProperties() {
|
|
1229
|
+
return ["minItems", "maxItems", "uniqueItems"];
|
|
1230
|
+
}
|
|
1231
|
+
getUnsupportedObjectProperties() {
|
|
1232
|
+
return ["minProperties", "maxProperties", "additionalProperties"];
|
|
1233
|
+
}
|
|
1234
|
+
mergeDescription(original, constraints) {
|
|
1235
|
+
const rules = [];
|
|
1236
|
+
if (constraints.minLength)
|
|
1237
|
+
rules.push(`at least ${constraints.minLength} chars`);
|
|
1238
|
+
if (constraints.maxLength)
|
|
1239
|
+
rules.push(`at most ${constraints.maxLength} chars`);
|
|
1240
|
+
if (constraints.minimum !== undefined)
|
|
1241
|
+
rules.push(`>= ${constraints.minimum}`);
|
|
1242
|
+
if (constraints.maximum !== undefined)
|
|
1243
|
+
rules.push(`<= ${constraints.maximum}`);
|
|
1244
|
+
if (constraints.format === "email")
|
|
1245
|
+
rules.push(`valid email`);
|
|
1246
|
+
if (constraints.format === "uri" || constraints.format === "url")
|
|
1247
|
+
rules.push(`valid URL`);
|
|
1248
|
+
if (constraints.pattern)
|
|
1249
|
+
rules.push(`matches ${constraints.pattern}`);
|
|
1250
|
+
if (constraints.enum)
|
|
1251
|
+
rules.push(`one of: ${constraints.enum.join(", ")}`);
|
|
1252
|
+
if (constraints.minItems)
|
|
1253
|
+
rules.push(`>= ${constraints.minItems} items`);
|
|
1254
|
+
if (constraints.maxItems)
|
|
1255
|
+
rules.push(`<= ${constraints.maxItems} items`);
|
|
1256
|
+
if (constraints.uniqueItems)
|
|
1257
|
+
rules.push(`unique items`);
|
|
1258
|
+
const text = rules.length > 0 ? `Constraints: ${rules.join("; ")}` : "";
|
|
1259
|
+
return original && text ? `${original}
|
|
1260
|
+
|
|
1261
|
+
${text}` : original || text;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// src/tool-compatibility/index.ts
|
|
1266
|
+
function detectModelProvider(runtime) {
|
|
1267
|
+
const providerString = String(runtime?.modelProvider || "").toLowerCase();
|
|
1268
|
+
const modelString = String(runtime?.model || "").toLowerCase();
|
|
1269
|
+
let provider2 = "unknown";
|
|
1270
|
+
let supportsStructuredOutputs = false;
|
|
1271
|
+
let isReasoningModel = false;
|
|
1272
|
+
if (providerString.includes("openai") || modelString.includes("gpt-") || modelString.includes("o1") || modelString.includes("o3")) {
|
|
1273
|
+
provider2 = "openai";
|
|
1274
|
+
supportsStructuredOutputs = modelString.includes("gpt-4") || modelString.includes("o1") || modelString.includes("o3");
|
|
1275
|
+
isReasoningModel = modelString.includes("o1") || modelString.includes("o3");
|
|
1276
|
+
} else if (providerString.includes("anthropic") || modelString.includes("claude")) {
|
|
1277
|
+
provider2 = "anthropic";
|
|
1278
|
+
supportsStructuredOutputs = true;
|
|
1279
|
+
} else if (providerString.includes("google") || modelString.includes("gemini")) {
|
|
1280
|
+
provider2 = "google";
|
|
1281
|
+
supportsStructuredOutputs = true;
|
|
1282
|
+
} else if (providerString.includes("openrouter") || modelString.includes("openrouter")) {
|
|
1283
|
+
provider2 = "openrouter";
|
|
1284
|
+
}
|
|
1285
|
+
return { provider: provider2, modelId: modelString || providerString || "unknown", supportsStructuredOutputs, isReasoningModel };
|
|
1286
|
+
}
|
|
1287
|
+
function createMcpToolCompatibilitySync(runtime) {
|
|
1288
|
+
const info = detectModelProvider(runtime);
|
|
1289
|
+
switch (info.provider) {
|
|
1290
|
+
case "openai":
|
|
1291
|
+
return info.isReasoningModel ? new OpenAIReasoningMcpCompatibility(info) : new OpenAIMcpCompatibility(info);
|
|
1292
|
+
case "anthropic":
|
|
1293
|
+
return new AnthropicMcpCompatibility(info);
|
|
1294
|
+
case "google":
|
|
1295
|
+
return new GoogleMcpCompatibility(info);
|
|
1296
|
+
default:
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
var createMcpToolCompatibility = createMcpToolCompatibilitySync;
|
|
1763
1301
|
|
|
1764
1302
|
// src/service.ts
|
|
1765
|
-
|
|
1766
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1767
|
-
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
1768
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1769
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1770
|
-
var errMsg = (e) => e instanceof Error ? e.message : String(e);
|
|
1303
|
+
var err = (e) => e instanceof Error ? e.message : String(e);
|
|
1771
1304
|
|
|
1772
1305
|
class McpService extends Service {
|
|
1773
1306
|
static serviceType = MCP_SERVICE_NAME;
|
|
1774
|
-
capabilityDescription = "Enables the agent to interact with MCP
|
|
1307
|
+
capabilityDescription = "Enables the agent to interact with MCP servers";
|
|
1775
1308
|
connections = new Map;
|
|
1776
1309
|
connectionStates = new Map;
|
|
1777
|
-
mcpProvider = {
|
|
1778
|
-
values: { mcp: {}, mcpText: "" },
|
|
1779
|
-
data: { mcp: {} },
|
|
1780
|
-
text: ""
|
|
1781
|
-
};
|
|
1310
|
+
mcpProvider = { values: { mcp: {}, mcpText: "" }, data: { mcp: {} }, text: "" };
|
|
1782
1311
|
pingConfig = DEFAULT_PING_CONFIG;
|
|
1783
1312
|
toolCompatibility = null;
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
return
|
|
1313
|
+
initPromise = null;
|
|
1314
|
+
registeredActions = new Map;
|
|
1315
|
+
schemaCache = getSchemaCache();
|
|
1316
|
+
lazyConnections = new Map;
|
|
1317
|
+
constructor(runtime) {
|
|
1318
|
+
super(runtime);
|
|
1319
|
+
this.initPromise = this.init();
|
|
1320
|
+
}
|
|
1321
|
+
static async start(runtime) {
|
|
1322
|
+
const svc = new McpService(runtime);
|
|
1323
|
+
await svc.initPromise;
|
|
1324
|
+
return svc;
|
|
1796
1325
|
}
|
|
1797
1326
|
async waitForInitialization() {
|
|
1798
|
-
|
|
1799
|
-
await this.initializationPromise;
|
|
1800
|
-
}
|
|
1327
|
+
await this.initPromise;
|
|
1801
1328
|
}
|
|
1802
1329
|
async stop() {
|
|
1803
1330
|
for (const name of this.connections.keys()) {
|
|
1804
|
-
await this.
|
|
1805
|
-
}
|
|
1806
|
-
for (const [name, state] of this.connectionStates.entries()) {
|
|
1807
|
-
if (state.pingInterval)
|
|
1808
|
-
clearInterval(state.pingInterval);
|
|
1809
|
-
if (state.reconnectTimeout)
|
|
1810
|
-
clearTimeout(state.reconnectTimeout);
|
|
1811
|
-
this.connectionStates.delete(name);
|
|
1331
|
+
await this.disconnect(name);
|
|
1812
1332
|
}
|
|
1813
1333
|
}
|
|
1814
|
-
async
|
|
1334
|
+
async init() {
|
|
1815
1335
|
try {
|
|
1816
|
-
const
|
|
1817
|
-
if (!
|
|
1336
|
+
const settings = this.getSettings();
|
|
1337
|
+
if (!settings?.servers || Object.keys(settings.servers).length === 0) {
|
|
1818
1338
|
this.mcpProvider = buildMcpProviderData([]);
|
|
1819
1339
|
return;
|
|
1820
1340
|
}
|
|
1821
|
-
const
|
|
1822
|
-
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1341
|
+
const start = Date.now();
|
|
1342
|
+
const entries = Object.entries(settings.servers);
|
|
1343
|
+
const results = { cached: [], connected: [], failed: [] };
|
|
1344
|
+
await Promise.allSettled(entries.map(async ([name, config]) => {
|
|
1345
|
+
try {
|
|
1346
|
+
const hash = this.schemaCache.hashConfig(config);
|
|
1347
|
+
if (this.schemaCache.isEnabled) {
|
|
1348
|
+
const cached = await this.schemaCache.getSchemas(this.runtime.agentId, name, hash);
|
|
1349
|
+
if (cached) {
|
|
1350
|
+
this.registerToolsAsActions(name, McpSchemaCache.toTools(cached));
|
|
1351
|
+
this.lazyConnections.set(name, config);
|
|
1352
|
+
results.cached.push(`${name}:${cached.tools.length}`);
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
await this.connect(name, config);
|
|
1357
|
+
const server = this.connections.get(name)?.server;
|
|
1358
|
+
if (this.schemaCache.isEnabled && server?.tools?.length) {
|
|
1359
|
+
await this.schemaCache.setSchemas(this.runtime.agentId, name, hash, server.tools);
|
|
1360
|
+
}
|
|
1361
|
+
results.connected.push(`${name}:${server?.tools?.length || 0}`);
|
|
1362
|
+
} catch (e) {
|
|
1363
|
+
logger6.error({ error: err(e), server: name }, `[MCP] Failed: ${name}`);
|
|
1364
|
+
results.failed.push(name);
|
|
1365
|
+
}
|
|
1366
|
+
}));
|
|
1367
|
+
const total = results.cached.length + results.connected.length;
|
|
1368
|
+
logger6.info(`[MCP] Ready ${total}/${entries.length} in ${Date.now() - start}ms ` + `(${results.cached.length} cached, ${results.connected.length} connected, ${results.failed.length} failed)`);
|
|
1369
|
+
this.mcpProvider = buildMcpProviderData(this.getServers());
|
|
1370
|
+
} catch (e) {
|
|
1371
|
+
logger6.error({ error: err(e) }, "[MCP] Init failed");
|
|
1839
1372
|
this.mcpProvider = buildMcpProviderData([]);
|
|
1840
1373
|
}
|
|
1841
1374
|
}
|
|
1842
|
-
|
|
1843
|
-
let
|
|
1844
|
-
if (!
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
}
|
|
1850
|
-
return settings && typeof settings === "object" && settings.servers ? settings : undefined;
|
|
1851
|
-
}
|
|
1852
|
-
async updateServerConnections(serverConfigs) {
|
|
1853
|
-
const newNames = new Set(Object.keys(serverConfigs));
|
|
1854
|
-
for (const name of this.connections.keys()) {
|
|
1855
|
-
if (!newNames.has(name))
|
|
1856
|
-
await this.deleteConnection(name);
|
|
1857
|
-
}
|
|
1858
|
-
await Promise.allSettled(Object.entries(serverConfigs).map(async ([name, config]) => {
|
|
1859
|
-
const current = this.connections.get(name);
|
|
1860
|
-
if (!current) {
|
|
1861
|
-
await this.initializeConnection(name, config).catch((e) => logger7.error({ error: errMsg(e), serverName: name }, `[MCP] Failed: ${name}`));
|
|
1862
|
-
} else if (JSON.stringify(config) !== current.server.config) {
|
|
1863
|
-
await this.deleteConnection(name);
|
|
1864
|
-
await this.initializeConnection(name, config).catch((e) => logger7.error({ error: errMsg(e), serverName: name }, `[MCP] Failed: ${name}`));
|
|
1865
|
-
}
|
|
1866
|
-
}));
|
|
1375
|
+
getSettings() {
|
|
1376
|
+
let s = this.runtime.getSetting("mcp");
|
|
1377
|
+
if (!s?.servers)
|
|
1378
|
+
s = this.runtime.character?.settings?.mcp;
|
|
1379
|
+
if (!s?.servers)
|
|
1380
|
+
s = this.runtime.settings?.mcp;
|
|
1381
|
+
return s?.servers ? s : undefined;
|
|
1867
1382
|
}
|
|
1868
|
-
async
|
|
1869
|
-
await this.
|
|
1870
|
-
const state = {
|
|
1871
|
-
status: "connecting",
|
|
1872
|
-
reconnectAttempts: 0,
|
|
1873
|
-
consecutivePingFailures: 0
|
|
1874
|
-
};
|
|
1383
|
+
async connect(name, config) {
|
|
1384
|
+
await this.disconnect(name);
|
|
1385
|
+
const state = { status: "connecting", reconnectAttempts: 0, consecutivePingFailures: 0 };
|
|
1875
1386
|
this.connectionStates.set(name, state);
|
|
1876
1387
|
try {
|
|
1877
1388
|
const client = new Client({ name: "ElizaOS", version: "1.0.0" }, { capabilities: {} });
|
|
1878
|
-
const transport = config.type === "stdio" ?
|
|
1879
|
-
const
|
|
1389
|
+
const transport = config.type === "stdio" ? this.createStdioTransport(name, config) : this.createHttpTransport(name, config);
|
|
1390
|
+
const conn = {
|
|
1880
1391
|
server: { name, config: JSON.stringify(config), status: "connecting" },
|
|
1881
1392
|
client,
|
|
1882
1393
|
transport
|
|
1883
1394
|
};
|
|
1884
|
-
this.connections.set(name,
|
|
1885
|
-
this.setupTransportHandlers(name,
|
|
1886
|
-
const connectPromise = client.connect(transport);
|
|
1887
|
-
connectPromise.catch(() => {});
|
|
1395
|
+
this.connections.set(name, conn);
|
|
1396
|
+
this.setupTransportHandlers(name, conn, state, config.type === "stdio");
|
|
1888
1397
|
await Promise.race([
|
|
1889
|
-
|
|
1890
|
-
new Promise((_,
|
|
1398
|
+
client.connect(transport),
|
|
1399
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error("Connection timeout")), 60000))
|
|
1891
1400
|
]);
|
|
1892
|
-
const
|
|
1893
|
-
const tools = await this.
|
|
1894
|
-
const resources =
|
|
1895
|
-
const resourceTemplates =
|
|
1896
|
-
|
|
1897
|
-
status: "connected",
|
|
1898
|
-
name,
|
|
1899
|
-
config: JSON.stringify(config),
|
|
1900
|
-
error: "",
|
|
1901
|
-
tools,
|
|
1902
|
-
resources,
|
|
1903
|
-
resourceTemplates
|
|
1904
|
-
};
|
|
1401
|
+
const caps = client.getServerCapabilities();
|
|
1402
|
+
const tools = await this.fetchTools(name);
|
|
1403
|
+
const resources = caps?.resources ? await this.fetchResources(name) : [];
|
|
1404
|
+
const resourceTemplates = caps?.resources ? await this.fetchResourceTemplates(name) : [];
|
|
1405
|
+
conn.server = { status: "connected", name, config: JSON.stringify(config), error: "", tools, resources, resourceTemplates };
|
|
1905
1406
|
state.status = "connected";
|
|
1906
1407
|
state.lastConnected = new Date;
|
|
1907
1408
|
state.reconnectAttempts = 0;
|
|
1908
1409
|
state.consecutivePingFailures = 0;
|
|
1909
|
-
|
|
1910
|
-
|
|
1410
|
+
if (config.type === "stdio")
|
|
1411
|
+
this.startPingMonitor(name);
|
|
1412
|
+
this.registerToolsAsActions(name, tools);
|
|
1413
|
+
logger6.info(`[MCP] Connected: ${name} (${tools?.length || 0} tools)`);
|
|
1414
|
+
} catch (e) {
|
|
1911
1415
|
state.status = "disconnected";
|
|
1912
|
-
state.lastError =
|
|
1913
|
-
this.
|
|
1914
|
-
throw
|
|
1416
|
+
state.lastError = e instanceof Error ? e : new Error(String(e));
|
|
1417
|
+
this.handleDisconnect(name, e);
|
|
1418
|
+
throw e;
|
|
1915
1419
|
}
|
|
1916
1420
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
this.appendErrorMessage(connection, msg);
|
|
1421
|
+
async disconnect(name) {
|
|
1422
|
+
this.unregisterToolsAsActions(name);
|
|
1423
|
+
const conn = this.connections.get(name);
|
|
1424
|
+
if (conn) {
|
|
1425
|
+
try {
|
|
1426
|
+
await conn.transport.close();
|
|
1427
|
+
await conn.client.close();
|
|
1428
|
+
} catch (e) {
|
|
1429
|
+
logger6.debug({ error: err(e), server: name }, `[MCP] Error during disconnect (expected during shutdown)`);
|
|
1927
1430
|
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1431
|
+
this.connections.delete(name);
|
|
1432
|
+
}
|
|
1433
|
+
const state = this.connectionStates.get(name);
|
|
1434
|
+
if (state) {
|
|
1435
|
+
if (state.pingInterval)
|
|
1436
|
+
clearInterval(state.pingInterval);
|
|
1437
|
+
if (state.reconnectTimeout)
|
|
1438
|
+
clearTimeout(state.reconnectTimeout);
|
|
1439
|
+
this.connectionStates.delete(name);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
createStdioTransport(name, config) {
|
|
1443
|
+
if (!config.command)
|
|
1444
|
+
throw new Error(`Missing command for stdio server ${name}`);
|
|
1445
|
+
return new StdioClientTransport({
|
|
1446
|
+
command: config.command,
|
|
1447
|
+
args: config.args,
|
|
1448
|
+
env: { ...config.env, ...process.env.PATH ? { PATH: process.env.PATH } : {} },
|
|
1449
|
+
stderr: "pipe",
|
|
1450
|
+
cwd: config.cwd
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
createHttpTransport(name, config) {
|
|
1454
|
+
if (!config.url)
|
|
1455
|
+
throw new Error(`Missing URL for server ${name}`);
|
|
1456
|
+
const url = new URL(config.url);
|
|
1457
|
+
const opts = config.headers ? { requestInit: { headers: config.headers } } : undefined;
|
|
1458
|
+
return config.type === "sse" ? new SSEClientTransport(url, opts) : new StreamableHTTPClientTransport(url, opts);
|
|
1459
|
+
}
|
|
1460
|
+
setupTransportHandlers(name, conn, state, isStdio) {
|
|
1461
|
+
conn.transport.onerror = async (e) => {
|
|
1462
|
+
const msg = e?.message || "";
|
|
1463
|
+
if (isStdio || !msg.includes("SSE") && !msg.includes("timeout") && msg !== "undefined") {
|
|
1464
|
+
logger6.error({ error: e, server: name }, `[MCP] Transport error: ${name}`);
|
|
1465
|
+
conn.server.status = "disconnected";
|
|
1466
|
+
conn.server.error = (conn.server.error || "") + `
|
|
1467
|
+
` + msg;
|
|
1468
|
+
}
|
|
1469
|
+
if (isStdio)
|
|
1470
|
+
this.handleDisconnect(name, e);
|
|
1930
1471
|
};
|
|
1931
|
-
|
|
1932
|
-
if (
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
this.handleDisconnection(name, new Error("Transport closed"));
|
|
1472
|
+
conn.transport.onclose = async () => {
|
|
1473
|
+
if (isStdio) {
|
|
1474
|
+
conn.server.status = "disconnected";
|
|
1475
|
+
this.handleDisconnect(name, new Error("Transport closed"));
|
|
1936
1476
|
}
|
|
1937
1477
|
};
|
|
1938
1478
|
}
|
|
1939
|
-
|
|
1940
|
-
const connection = this.connections.get(name);
|
|
1941
|
-
if (!connection)
|
|
1942
|
-
return;
|
|
1943
|
-
const config = JSON.parse(connection.server.config);
|
|
1944
|
-
if (config.type !== "stdio")
|
|
1945
|
-
return;
|
|
1479
|
+
startPingMonitor(name) {
|
|
1946
1480
|
const state = this.connectionStates.get(name);
|
|
1947
1481
|
if (!state || !this.pingConfig.enabled)
|
|
1948
1482
|
return;
|
|
1949
1483
|
if (state.pingInterval)
|
|
1950
1484
|
clearInterval(state.pingInterval);
|
|
1951
|
-
state.pingInterval = setInterval(() => {
|
|
1952
|
-
this.
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1485
|
+
state.pingInterval = setInterval(async () => {
|
|
1486
|
+
const conn = this.connections.get(name);
|
|
1487
|
+
if (!conn)
|
|
1488
|
+
return;
|
|
1489
|
+
try {
|
|
1490
|
+
await Promise.race([
|
|
1491
|
+
conn.client.listTools(),
|
|
1492
|
+
new Promise((_, r) => setTimeout(() => r(new Error("Ping timeout")), this.pingConfig.timeoutMs))
|
|
1493
|
+
]);
|
|
1494
|
+
state.consecutivePingFailures = 0;
|
|
1495
|
+
} catch (e) {
|
|
1496
|
+
state.consecutivePingFailures++;
|
|
1497
|
+
if (state.consecutivePingFailures >= this.pingConfig.failuresBeforeDisconnect) {
|
|
1498
|
+
this.handleDisconnect(name, e);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1956
1501
|
}, this.pingConfig.intervalMs);
|
|
1957
1502
|
}
|
|
1958
|
-
|
|
1959
|
-
const connection = this.connections.get(name);
|
|
1960
|
-
if (!connection)
|
|
1961
|
-
throw new Error(`No connection: ${name}`);
|
|
1962
|
-
await Promise.race([
|
|
1963
|
-
connection.client.listTools(),
|
|
1964
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("Ping timeout")), this.pingConfig.timeoutMs))
|
|
1965
|
-
]);
|
|
1966
|
-
const state = this.connectionStates.get(name);
|
|
1967
|
-
if (state)
|
|
1968
|
-
state.consecutivePingFailures = 0;
|
|
1969
|
-
}
|
|
1970
|
-
handlePingFailure(name, error) {
|
|
1971
|
-
const state = this.connectionStates.get(name);
|
|
1972
|
-
if (!state)
|
|
1973
|
-
return;
|
|
1974
|
-
state.consecutivePingFailures++;
|
|
1975
|
-
if (state.consecutivePingFailures >= this.pingConfig.failuresBeforeDisconnect) {
|
|
1976
|
-
logger7.warn(`Ping failures exceeded for ${name}, disconnecting and attempting reconnect.`);
|
|
1977
|
-
this.handleDisconnection(name, error);
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
handleDisconnection(name, error) {
|
|
1503
|
+
handleDisconnect(name, error) {
|
|
1981
1504
|
const state = this.connectionStates.get(name);
|
|
1982
1505
|
if (!state)
|
|
1983
1506
|
return;
|
|
@@ -1988,140 +1511,131 @@ class McpService extends Service {
|
|
|
1988
1511
|
if (state.reconnectTimeout)
|
|
1989
1512
|
clearTimeout(state.reconnectTimeout);
|
|
1990
1513
|
if (state.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
1991
|
-
|
|
1514
|
+
logger6.error(`[MCP] Max reconnect attempts for ${name}`);
|
|
1992
1515
|
return;
|
|
1993
1516
|
}
|
|
1994
1517
|
const delay = INITIAL_RETRY_DELAY * Math.pow(BACKOFF_MULTIPLIER, state.reconnectAttempts);
|
|
1995
1518
|
state.reconnectTimeout = setTimeout(async () => {
|
|
1996
1519
|
state.reconnectAttempts++;
|
|
1997
|
-
logger7.info(`Attempting to reconnect to ${name} (attempt ${state.reconnectAttempts})...`);
|
|
1998
1520
|
const config = this.connections.get(name)?.server.config;
|
|
1999
1521
|
if (config) {
|
|
2000
1522
|
try {
|
|
2001
|
-
await this.
|
|
2002
|
-
} catch (
|
|
2003
|
-
|
|
2004
|
-
this.handleDisconnection(name, err);
|
|
1523
|
+
await this.connect(name, JSON.parse(config));
|
|
1524
|
+
} catch (e) {
|
|
1525
|
+
this.handleDisconnect(name, e);
|
|
2005
1526
|
}
|
|
2006
1527
|
}
|
|
2007
1528
|
}, delay);
|
|
2008
1529
|
}
|
|
2009
|
-
async
|
|
2010
|
-
const
|
|
2011
|
-
if (
|
|
2012
|
-
try {
|
|
2013
|
-
await connection.transport.close();
|
|
2014
|
-
await connection.client.close();
|
|
2015
|
-
} catch (error) {
|
|
2016
|
-
logger7.error({ error: errMsg(error), serverName: name }, `Failed to close transport for ${name}`);
|
|
2017
|
-
}
|
|
2018
|
-
this.connections.delete(name);
|
|
2019
|
-
}
|
|
2020
|
-
const state = this.connectionStates.get(name);
|
|
2021
|
-
if (state) {
|
|
2022
|
-
if (state.pingInterval)
|
|
2023
|
-
clearInterval(state.pingInterval);
|
|
2024
|
-
if (state.reconnectTimeout)
|
|
2025
|
-
clearTimeout(state.reconnectTimeout);
|
|
2026
|
-
this.connectionStates.delete(name);
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
async buildStdioClientTransport(name, config) {
|
|
2030
|
-
if (!config.command) {
|
|
2031
|
-
throw new Error(`Missing command for stdio MCP server ${name}`);
|
|
2032
|
-
}
|
|
2033
|
-
return new StdioClientTransport({
|
|
2034
|
-
command: config.command,
|
|
2035
|
-
args: config.args,
|
|
2036
|
-
env: {
|
|
2037
|
-
...config.env,
|
|
2038
|
-
...process.env.PATH ? { PATH: process.env.PATH } : {}
|
|
2039
|
-
},
|
|
2040
|
-
stderr: "pipe",
|
|
2041
|
-
cwd: config.cwd
|
|
2042
|
-
});
|
|
2043
|
-
}
|
|
2044
|
-
async buildHttpClientTransport(name, config) {
|
|
2045
|
-
if (!config.url)
|
|
2046
|
-
throw new Error(`Missing URL for MCP server ${name}`);
|
|
2047
|
-
const url = new URL(config.url);
|
|
2048
|
-
const opts = config.headers ? { requestInit: { headers: config.headers } } : undefined;
|
|
2049
|
-
if (config.type === "sse") {
|
|
2050
|
-
logger7.warn(`[MCP] "${name}": SSE requires Redis. Use "streamable-http" instead.`);
|
|
2051
|
-
return new SSEClientTransport(url, opts);
|
|
2052
|
-
}
|
|
2053
|
-
return new StreamableHTTPClientTransport(url, opts);
|
|
2054
|
-
}
|
|
2055
|
-
appendErrorMessage(connection, error) {
|
|
2056
|
-
connection.server.error = connection.server.error ? `${connection.server.error}
|
|
2057
|
-
${error}` : error;
|
|
2058
|
-
}
|
|
2059
|
-
async fetchToolsList(serverName) {
|
|
2060
|
-
const connection = this.connections.get(serverName);
|
|
2061
|
-
if (!connection)
|
|
1530
|
+
async fetchTools(name) {
|
|
1531
|
+
const conn = this.connections.get(name);
|
|
1532
|
+
if (!conn)
|
|
2062
1533
|
return [];
|
|
2063
1534
|
try {
|
|
2064
|
-
const
|
|
2065
|
-
return (
|
|
2066
|
-
if (!
|
|
2067
|
-
return
|
|
2068
|
-
if (!this.
|
|
2069
|
-
this.
|
|
1535
|
+
const res = await conn.client.listTools();
|
|
1536
|
+
return (res?.tools || []).map((t) => {
|
|
1537
|
+
if (!t.inputSchema)
|
|
1538
|
+
return t;
|
|
1539
|
+
if (!this.toolCompatibility)
|
|
1540
|
+
this.toolCompatibility = createMcpToolCompatibilitySync(this.runtime);
|
|
2070
1541
|
try {
|
|
2071
|
-
return { ...
|
|
2072
|
-
} catch {
|
|
2073
|
-
|
|
1542
|
+
return { ...t, inputSchema: this.toolCompatibility.transformToolSchema(t.inputSchema) };
|
|
1543
|
+
} catch (e) {
|
|
1544
|
+
logger6.debug({ error: err(e), tool: t.name }, `[MCP] Schema transform failed, using original`);
|
|
1545
|
+
return t;
|
|
2074
1546
|
}
|
|
2075
1547
|
});
|
|
2076
|
-
} catch (
|
|
2077
|
-
|
|
1548
|
+
} catch (e) {
|
|
1549
|
+
logger6.warn({ error: err(e), server: name }, `[MCP] Failed to fetch tools`);
|
|
2078
1550
|
return [];
|
|
2079
1551
|
}
|
|
2080
1552
|
}
|
|
2081
|
-
async
|
|
2082
|
-
const conn = this.connections.get(name);
|
|
2083
|
-
if (!conn)
|
|
2084
|
-
return [];
|
|
1553
|
+
async fetchResources(name) {
|
|
2085
1554
|
try {
|
|
2086
|
-
return (await
|
|
2087
|
-
} catch (
|
|
2088
|
-
|
|
1555
|
+
return (await this.connections.get(name)?.client.listResources())?.resources || [];
|
|
1556
|
+
} catch (e) {
|
|
1557
|
+
logger6.debug({ error: err(e), server: name }, `[MCP] Failed to fetch resources`);
|
|
2089
1558
|
return [];
|
|
2090
1559
|
}
|
|
2091
1560
|
}
|
|
2092
|
-
async
|
|
2093
|
-
const conn = this.connections.get(name);
|
|
2094
|
-
if (!conn)
|
|
2095
|
-
return [];
|
|
1561
|
+
async fetchResourceTemplates(name) {
|
|
2096
1562
|
try {
|
|
2097
|
-
return (await
|
|
2098
|
-
} catch (
|
|
2099
|
-
|
|
1563
|
+
return (await this.connections.get(name)?.client.listResourceTemplates())?.resourceTemplates || [];
|
|
1564
|
+
} catch (e) {
|
|
1565
|
+
logger6.debug({ error: err(e), server: name }, `[MCP] Failed to fetch resource templates`);
|
|
2100
1566
|
return [];
|
|
2101
1567
|
}
|
|
2102
1568
|
}
|
|
1569
|
+
registerToolsAsActions(serverName, tools) {
|
|
1570
|
+
if (!tools?.length)
|
|
1571
|
+
return;
|
|
1572
|
+
const existing = new Set([...this.runtime.actions.map((a) => a.name), ...this.registeredActions.keys()]);
|
|
1573
|
+
const actions = createMcpToolActions(serverName, tools, existing);
|
|
1574
|
+
for (const action of actions) {
|
|
1575
|
+
if (!this.registeredActions.has(action.name)) {
|
|
1576
|
+
this.runtime.registerAction(action);
|
|
1577
|
+
this.registeredActions.set(action.name, action);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
logger6.info(`[MCP] Registered ${actions.length} actions for ${serverName}`);
|
|
1581
|
+
}
|
|
1582
|
+
unregisterToolsAsActions(serverName) {
|
|
1583
|
+
const toRemove = [];
|
|
1584
|
+
for (const [name, action] of this.registeredActions) {
|
|
1585
|
+
if (action._mcpMeta.serverName === serverName)
|
|
1586
|
+
toRemove.push(name);
|
|
1587
|
+
}
|
|
1588
|
+
for (const name of toRemove) {
|
|
1589
|
+
const idx = this.runtime.actions.findIndex((a) => a.name === name);
|
|
1590
|
+
if (idx !== -1)
|
|
1591
|
+
this.runtime.actions.splice(idx, 1);
|
|
1592
|
+
this.registeredActions.delete(name);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
2103
1595
|
getServers() {
|
|
2104
|
-
return Array.from(this.connections.values()).filter((
|
|
1596
|
+
return Array.from(this.connections.values()).filter((c) => !c.server.disabled).map((c) => c.server);
|
|
2105
1597
|
}
|
|
2106
1598
|
getProviderData() {
|
|
2107
1599
|
return this.mcpProvider;
|
|
2108
1600
|
}
|
|
1601
|
+
getRegisteredActions() {
|
|
1602
|
+
return Array.from(this.registeredActions.values());
|
|
1603
|
+
}
|
|
1604
|
+
isLazyConnection(serverName) {
|
|
1605
|
+
return this.lazyConnections.has(serverName);
|
|
1606
|
+
}
|
|
1607
|
+
async ensureConnected(serverName) {
|
|
1608
|
+
if (this.connections.has(serverName))
|
|
1609
|
+
return;
|
|
1610
|
+
const config = this.lazyConnections.get(serverName);
|
|
1611
|
+
if (!config)
|
|
1612
|
+
throw new Error(`Unknown server: ${serverName}`);
|
|
1613
|
+
const start = Date.now();
|
|
1614
|
+
await this.connect(serverName, config);
|
|
1615
|
+
this.lazyConnections.delete(serverName);
|
|
1616
|
+
const server = this.connections.get(serverName)?.server;
|
|
1617
|
+
if (this.schemaCache.isEnabled && server?.tools?.length) {
|
|
1618
|
+
await this.schemaCache.setSchemas(this.runtime.agentId, serverName, this.schemaCache.hashConfig(config), server.tools);
|
|
1619
|
+
}
|
|
1620
|
+
logger6.info(`[MCP] Lazy connected: ${serverName} in ${Date.now() - start}ms`);
|
|
1621
|
+
this.mcpProvider = buildMcpProviderData(this.getServers());
|
|
1622
|
+
}
|
|
2109
1623
|
async callTool(serverName, toolName, args) {
|
|
1624
|
+
await this.ensureConnected(serverName);
|
|
2110
1625
|
const conn = this.connections.get(serverName);
|
|
2111
1626
|
if (!conn)
|
|
2112
1627
|
throw new Error(`No connection: ${serverName}`);
|
|
2113
1628
|
if (conn.server.disabled)
|
|
2114
1629
|
throw new Error(`Server disabled: ${serverName}`);
|
|
2115
1630
|
const config = JSON.parse(conn.server.config);
|
|
2116
|
-
const timeout =
|
|
2117
|
-
const result = await conn.client.callTool({ name: toolName, arguments: args }, undefined, {
|
|
2118
|
-
timeout
|
|
2119
|
-
});
|
|
1631
|
+
const timeout = config.timeoutInMillis || DEFAULT_MCP_TIMEOUT_SECONDS;
|
|
1632
|
+
const result = await conn.client.callTool({ name: toolName, arguments: args }, undefined, { timeout });
|
|
2120
1633
|
if (!result.content)
|
|
2121
|
-
throw new Error("Invalid tool result
|
|
1634
|
+
throw new Error("Invalid tool result");
|
|
2122
1635
|
return result;
|
|
2123
1636
|
}
|
|
2124
1637
|
async readResource(serverName, uri) {
|
|
1638
|
+
await this.ensureConnected(serverName);
|
|
2125
1639
|
const conn = this.connections.get(serverName);
|
|
2126
1640
|
if (!conn)
|
|
2127
1641
|
throw new Error(`No connection: ${serverName}`);
|
|
@@ -2134,27 +1648,8 @@ ${error}` : error;
|
|
|
2134
1648
|
if (!conn)
|
|
2135
1649
|
throw new Error(`No connection: ${serverName}`);
|
|
2136
1650
|
const config = conn.server.config;
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
await this.deleteConnection(serverName);
|
|
2140
|
-
await this.initializeConnection(serverName, JSON.parse(config));
|
|
2141
|
-
}
|
|
2142
|
-
initializeToolCompatibility() {
|
|
2143
|
-
if (this.compatibilityInitialized)
|
|
2144
|
-
return;
|
|
2145
|
-
this.toolCompatibility = createMcpToolCompatibilitySync(this.runtime);
|
|
2146
|
-
this.compatibilityInitialized = true;
|
|
2147
|
-
}
|
|
2148
|
-
applyToolCompatibility(toolSchema) {
|
|
2149
|
-
if (!this.compatibilityInitialized)
|
|
2150
|
-
this.initializeToolCompatibility();
|
|
2151
|
-
if (!this.toolCompatibility || !toolSchema)
|
|
2152
|
-
return toolSchema;
|
|
2153
|
-
try {
|
|
2154
|
-
return this.toolCompatibility.transformToolSchema(toolSchema);
|
|
2155
|
-
} catch {
|
|
2156
|
-
return toolSchema;
|
|
2157
|
-
}
|
|
1651
|
+
await this.disconnect(serverName);
|
|
1652
|
+
await this.connect(serverName, JSON.parse(config));
|
|
2158
1653
|
}
|
|
2159
1654
|
}
|
|
2160
1655
|
|
|
@@ -2163,23 +1658,27 @@ var mcpPlugin = {
|
|
|
2163
1658
|
name: "mcp",
|
|
2164
1659
|
description: "Plugin for connecting to MCP (Model Context Protocol) servers",
|
|
2165
1660
|
init: async (_config, _runtime) => {
|
|
2166
|
-
|
|
1661
|
+
logger7.info("Initializing MCP plugin...");
|
|
2167
1662
|
},
|
|
2168
1663
|
services: [McpService],
|
|
2169
|
-
actions: [
|
|
1664
|
+
actions: [readResourceAction],
|
|
2170
1665
|
providers: [provider]
|
|
2171
1666
|
};
|
|
2172
1667
|
var src_default = mcpPlugin;
|
|
2173
1668
|
export {
|
|
1669
|
+
isMcpToolAction,
|
|
1670
|
+
getSchemaCache,
|
|
1671
|
+
getMcpToolActionsForServer,
|
|
2174
1672
|
detectModelProvider,
|
|
2175
1673
|
src_default as default,
|
|
2176
1674
|
createMcpToolCompatibilitySync,
|
|
2177
1675
|
createMcpToolCompatibility,
|
|
2178
|
-
|
|
1676
|
+
createMcpToolActions,
|
|
1677
|
+
createMcpToolAction,
|
|
2179
1678
|
ResourceSelectionSchema,
|
|
2180
1679
|
McpToolCompatibility,
|
|
2181
1680
|
McpService,
|
|
2182
|
-
|
|
1681
|
+
McpSchemaCache,
|
|
2183
1682
|
MCP_SERVICE_NAME,
|
|
2184
1683
|
MAX_RECONNECT_ATTEMPTS,
|
|
2185
1684
|
INITIAL_RETRY_DELAY,
|
|
@@ -2189,4 +1688,4 @@ export {
|
|
|
2189
1688
|
BACKOFF_MULTIPLIER
|
|
2190
1689
|
};
|
|
2191
1690
|
|
|
2192
|
-
//# debugId=
|
|
1691
|
+
//# debugId=C0ED6395DC1FA15F64756E2164756E21
|