@elizaos/plugin-mcp 1.7.0 → 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 +1359 -1954
- package/dist/cjs/index.js.map +24 -25
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1401 -2012
- package/dist/index.js.map +24 -25
- package/dist/provider.d.ts.map +1 -1
- package/dist/service.d.ts +26 -28
- 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 +52 -85
- 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/mcp.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,1607 +1,491 @@
|
|
|
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);
|
|
12
|
-
|
|
13
|
-
// src/tool-compatibility/providers/openai.ts
|
|
14
|
-
var exports_openai = {};
|
|
15
|
-
__export(exports_openai, {
|
|
16
|
-
OpenAIReasoningMcpCompatibility: () => OpenAIReasoningMcpCompatibility,
|
|
17
|
-
OpenAIMcpCompatibility: () => OpenAIMcpCompatibility2
|
|
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}
|
|
77
|
-
|
|
78
|
-
IMPORTANT: ${constraintText}`;
|
|
79
|
-
}
|
|
80
|
-
return `IMPORTANT: ${constraintText}`;
|
|
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
|
-
});
|
|
121
|
-
|
|
122
|
-
// src/tool-compatibility/providers/anthropic.ts
|
|
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
|
-
});
|
|
172
|
-
|
|
173
|
-
// src/tool-compatibility/providers/google.ts
|
|
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}
|
|
203
|
-
|
|
204
|
-
Constraints: ${constraintText}`;
|
|
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
|
-
});
|
|
273
|
-
|
|
274
|
-
// src/tool-compatibility/index.ts
|
|
275
|
-
class McpToolCompatibility {
|
|
276
|
-
modelInfo;
|
|
277
|
-
constructor(modelInfo2) {
|
|
278
|
-
this.modelInfo = modelInfo2;
|
|
279
|
-
}
|
|
280
|
-
transformToolSchema(toolSchema) {
|
|
281
|
-
if (!this.shouldApply()) {
|
|
282
|
-
return toolSchema;
|
|
283
|
-
}
|
|
284
|
-
return this.processSchema(toolSchema);
|
|
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
|
-
}
|
|
513
|
-
|
|
514
|
-
// src/index.ts
|
|
515
|
-
import { logger as logger8 } from "@elizaos/core";
|
|
516
|
-
|
|
517
|
-
// src/actions/callToolAction.ts
|
|
518
|
-
import {
|
|
519
|
-
logger as logger5
|
|
520
|
-
} from "@elizaos/core";
|
|
521
|
-
|
|
522
|
-
// src/types.ts
|
|
523
|
-
var MCP_SERVICE_NAME = "mcp";
|
|
524
|
-
var DEFAULT_MCP_TIMEOUT_SECONDS = 60000;
|
|
525
|
-
var MIN_MCP_TIMEOUT_SECONDS = 1;
|
|
526
|
-
var DEFAULT_MAX_RETRIES = 2;
|
|
527
|
-
var ToolSelectionSchema = {
|
|
528
|
-
type: "object",
|
|
529
|
-
required: ["serverName", "toolName", "arguments"],
|
|
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
|
-
};
|
|
574
|
-
var DEFAULT_PING_CONFIG = {
|
|
575
|
-
enabled: true,
|
|
576
|
-
intervalMs: 1e4,
|
|
577
|
-
timeoutMs: 5000,
|
|
578
|
-
failuresBeforeDisconnect: 3
|
|
579
|
-
};
|
|
580
|
-
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
581
|
-
var BACKOFF_MULTIPLIER = 2;
|
|
582
|
-
var INITIAL_RETRY_DELAY = 2000;
|
|
583
|
-
|
|
584
|
-
// src/utils/error.ts
|
|
585
|
-
import {
|
|
586
|
-
ModelType,
|
|
587
|
-
composePromptFromState,
|
|
588
|
-
logger
|
|
589
|
-
} from "@elizaos/core";
|
|
590
|
-
|
|
591
|
-
// src/templates/errorAnalysisPrompt.ts
|
|
592
|
-
var errorAnalysisPrompt = `
|
|
593
|
-
{{{mcpProvider.text}}}
|
|
594
|
-
|
|
595
|
-
{{{recentMessages}}}
|
|
596
|
-
|
|
597
|
-
# Prompt
|
|
598
|
-
|
|
599
|
-
You're an assistant helping a user, but there was an error accessing the resource you tried to use.
|
|
600
|
-
|
|
601
|
-
User request: "{{{userMessage}}}"
|
|
602
|
-
Error message: {{{error}}}
|
|
603
|
-
|
|
604
|
-
Create a helpful response that:
|
|
605
|
-
1. Acknowledges the issue in user-friendly terms
|
|
606
|
-
2. Offers alternative approaches to help if possible
|
|
607
|
-
3. Doesn't expose technical error details unless they're truly helpful
|
|
608
|
-
4. Maintains a helpful, conversational tone
|
|
609
|
-
|
|
610
|
-
Your response:
|
|
611
|
-
`;
|
|
612
|
-
|
|
613
|
-
// src/utils/error.ts
|
|
614
|
-
async function handleMcpError(state, mcpProvider, error, runtime2, message, type, callback) {
|
|
615
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
616
|
-
logger.error({ error, mcpType: type }, `Error executing MCP ${type}: ${errorMessage}`);
|
|
617
|
-
let responseText = `I'm sorry, I wasn't able to get the information you requested. There seems to be an issue with the ${type} right now. Is there something else I can help you with?`;
|
|
618
|
-
let thoughtText = `Error calling MCP ${type} and failed to generate a custom response. Providing a generic fallback response.`;
|
|
619
|
-
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
|
-
try {
|
|
634
|
-
const errorResponse = await runtime2.useModel(ModelType.TEXT_SMALL, {
|
|
635
|
-
prompt
|
|
636
|
-
});
|
|
637
|
-
responseText = errorResponse;
|
|
638
|
-
thoughtText = `Error calling MCP ${type}: ${errorMessage}. Providing a helpful response to the user.`;
|
|
639
|
-
await callback({
|
|
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"]
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
return {
|
|
654
|
-
text: `Failed to execute MCP ${type}`,
|
|
655
|
-
values: {
|
|
656
|
-
success: false,
|
|
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
|
-
},
|
|
665
|
-
success: false,
|
|
666
|
-
error: error instanceof Error ? error : new Error(errorMessage)
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// src/utils/processing.ts
|
|
671
|
-
import {
|
|
672
|
-
ContentType,
|
|
673
|
-
ModelType as ModelType2,
|
|
674
|
-
createUniqueUuid,
|
|
675
|
-
logger as logger2
|
|
676
|
-
} from "@elizaos/core";
|
|
677
|
-
import { composePromptFromState as composePromptFromState2 } from "@elizaos/core";
|
|
678
|
-
|
|
679
|
-
// src/templates/resourceAnalysisTemplate.ts
|
|
680
|
-
var resourceAnalysisTemplate = `
|
|
681
|
-
{{{mcpProvider.text}}}
|
|
682
|
-
|
|
683
|
-
{{{recentMessages}}}
|
|
684
|
-
|
|
685
|
-
# Prompt
|
|
686
|
-
|
|
687
|
-
You are a helpful assistant responding to a user's request. You've just accessed the resource "{{{uri}}}" to help answer this request.
|
|
688
|
-
|
|
689
|
-
Original user request: "{{{userMessage}}}"
|
|
690
|
-
|
|
691
|
-
Resource metadata:
|
|
692
|
-
{{{resourceMeta}}
|
|
693
|
-
|
|
694
|
-
Resource content:
|
|
695
|
-
{{{resourceContent}}
|
|
696
|
-
|
|
697
|
-
Instructions:
|
|
698
|
-
1. Analyze how well the resource's content addresses the user's specific question or need
|
|
699
|
-
2. Identify the most relevant information from the resource
|
|
700
|
-
3. Create a natural, conversational response that incorporates this information
|
|
701
|
-
4. If the resource content is insufficient, acknowledge its limitations and explain what you can determine
|
|
702
|
-
5. Do not start with phrases like "According to the resource" or "Here's what I found" - instead, integrate the information naturally
|
|
703
|
-
6. Maintain your helpful, intelligent assistant personality while presenting the information
|
|
704
|
-
|
|
705
|
-
Your response (written as if directly to the user):
|
|
706
|
-
`;
|
|
707
|
-
|
|
708
|
-
// src/templates/toolReasoningTemplate.ts
|
|
709
|
-
var toolReasoningTemplate = `
|
|
710
|
-
{{{mcpProvider.text}}}
|
|
711
|
-
|
|
712
|
-
{{{recentMessages}}}
|
|
713
|
-
|
|
714
|
-
# Prompt
|
|
715
|
-
|
|
716
|
-
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.
|
|
717
|
-
|
|
718
|
-
Original user request: "{{{userMessage}}}"
|
|
719
|
-
|
|
720
|
-
Tool response:
|
|
721
|
-
{{{toolOutput}}}
|
|
722
|
-
|
|
723
|
-
{{#if hasAttachments}}
|
|
724
|
-
The tool also returned images or other media that will be shared with the user.
|
|
725
|
-
{{/if}}
|
|
726
|
-
|
|
727
|
-
Instructions:
|
|
728
|
-
1. Analyze how well the tool's response addresses the user's specific question or need
|
|
729
|
-
2. Identify the most relevant information from the tool's output
|
|
730
|
-
3. Create a natural, conversational response that incorporates this information
|
|
731
|
-
4. If the tool's response is insufficient, acknowledge its limitations and explain what you can determine
|
|
732
|
-
5. Do not start with phrases like "I used the X tool" or "Here's what I found" - instead, integrate the information naturally
|
|
733
|
-
6. Maintain your helpful, intelligent assistant personality while presenting the information
|
|
734
|
-
|
|
735
|
-
Your response (written as if directly to the user):
|
|
736
|
-
`;
|
|
737
|
-
|
|
738
|
-
// src/utils/mcp.ts
|
|
739
|
-
async function createMcpMemory(runtime2, message, type, serverName, content, metadata) {
|
|
740
|
-
const memory = await runtime2.addEmbeddingToMemory({
|
|
741
|
-
entityId: message.entityId,
|
|
742
|
-
agentId: runtime2.agentId,
|
|
743
|
-
roomId: message.roomId,
|
|
744
|
-
content: {
|
|
745
|
-
text: `Used the "${type}" from "${serverName}" server.
|
|
746
|
-
Content: ${content}`,
|
|
747
|
-
metadata: {
|
|
748
|
-
...metadata,
|
|
749
|
-
serverName
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
});
|
|
753
|
-
await runtime2.createMemory(memory, type === "resource" ? "resources" : "tools", true);
|
|
754
|
-
}
|
|
755
|
-
function buildMcpProviderData(servers) {
|
|
756
|
-
const mcpData = {};
|
|
757
|
-
let textContent = "";
|
|
758
|
-
if (servers.length === 0) {
|
|
759
|
-
return {
|
|
760
|
-
values: { mcp: {} },
|
|
761
|
-
data: { mcp: {} },
|
|
762
|
-
text: "No MCP servers are currently connected."
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
for (const server of servers) {
|
|
766
|
-
mcpData[server.name] = {
|
|
767
|
-
status: server.status,
|
|
768
|
-
tools: {},
|
|
769
|
-
resources: {}
|
|
770
|
-
};
|
|
771
|
-
textContent += `## Server: ${server.name} (${server.status})
|
|
772
|
-
|
|
773
|
-
`;
|
|
774
|
-
if (server.tools && server.tools.length > 0) {
|
|
775
|
-
textContent += `### Tools:
|
|
776
|
-
|
|
777
|
-
`;
|
|
778
|
-
for (const tool of server.tools) {
|
|
779
|
-
mcpData[server.name].tools[tool.name] = {
|
|
780
|
-
description: tool.description || "No description available",
|
|
781
|
-
inputSchema: tool.inputSchema || {}
|
|
782
|
-
};
|
|
783
|
-
textContent += `- **${tool.name}**: ${tool.description || "No description available"}
|
|
784
|
-
`;
|
|
785
|
-
}
|
|
786
|
-
textContent += `
|
|
787
|
-
`;
|
|
788
|
-
}
|
|
789
|
-
if (server.resources && server.resources.length > 0) {
|
|
790
|
-
textContent += `### Resources:
|
|
791
|
-
|
|
792
|
-
`;
|
|
793
|
-
for (const resource of server.resources) {
|
|
794
|
-
mcpData[server.name].resources[resource.uri] = {
|
|
795
|
-
name: resource.name,
|
|
796
|
-
description: resource.description || "No description available",
|
|
797
|
-
mimeType: resource.mimeType
|
|
798
|
-
};
|
|
799
|
-
textContent += `- **${resource.name}** (${resource.uri}): ${resource.description || "No description available"}
|
|
800
|
-
`;
|
|
801
|
-
}
|
|
802
|
-
textContent += `
|
|
803
|
-
`;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
return {
|
|
807
|
-
values: { mcp: mcpData, mcpText: `# MCP Configuration
|
|
808
|
-
|
|
809
|
-
${textContent}` },
|
|
810
|
-
data: { mcp: mcpData },
|
|
811
|
-
text: `# MCP Configuration
|
|
812
|
-
|
|
813
|
-
${textContent}`
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// src/utils/processing.ts
|
|
818
|
-
function getMimeTypeToContentType(mimeType) {
|
|
819
|
-
if (!mimeType)
|
|
820
|
-
return;
|
|
821
|
-
if (mimeType.startsWith("image/"))
|
|
822
|
-
return ContentType.IMAGE;
|
|
823
|
-
if (mimeType.startsWith("video/"))
|
|
824
|
-
return ContentType.VIDEO;
|
|
825
|
-
if (mimeType.startsWith("audio/"))
|
|
826
|
-
return ContentType.AUDIO;
|
|
827
|
-
if (mimeType.includes("pdf") || mimeType.includes("document"))
|
|
828
|
-
return ContentType.DOCUMENT;
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
function processResourceResult(result, uri) {
|
|
832
|
-
let resourceContent = "";
|
|
833
|
-
let resourceMeta = "";
|
|
834
|
-
for (const content of result.contents) {
|
|
835
|
-
if (content.text) {
|
|
836
|
-
resourceContent += content.text;
|
|
837
|
-
} else if (content.blob) {
|
|
838
|
-
resourceContent += `[Binary data - ${content.mimeType || "unknown type"}]`;
|
|
839
|
-
}
|
|
840
|
-
resourceMeta += `Resource: ${content.uri || uri}
|
|
841
|
-
`;
|
|
842
|
-
if (content.mimeType) {
|
|
843
|
-
resourceMeta += `Type: ${content.mimeType}
|
|
844
|
-
`;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
return { resourceContent, resourceMeta };
|
|
848
|
-
}
|
|
849
|
-
function processToolResult(result, serverName, toolName, runtime2, messageEntityId) {
|
|
850
|
-
let toolOutput = "";
|
|
851
|
-
let hasAttachments = false;
|
|
852
|
-
const attachments = [];
|
|
853
|
-
for (const content of result.content) {
|
|
854
|
-
if (content.type === "text") {
|
|
855
|
-
toolOutput += content.text;
|
|
856
|
-
} else if (content.type === "image") {
|
|
857
|
-
hasAttachments = true;
|
|
858
|
-
attachments.push({
|
|
859
|
-
contentType: getMimeTypeToContentType(content.mimeType),
|
|
860
|
-
url: `data:${content.mimeType};base64,${content.data}`,
|
|
861
|
-
id: createUniqueUuid(runtime2, messageEntityId),
|
|
862
|
-
title: "Generated image",
|
|
863
|
-
source: `${serverName}/${toolName}`,
|
|
864
|
-
description: "Tool-generated image",
|
|
865
|
-
text: "Generated image"
|
|
866
|
-
});
|
|
867
|
-
} else if (content.type === "resource") {
|
|
868
|
-
const resource = content.resource;
|
|
869
|
-
if (resource && "text" in resource) {
|
|
870
|
-
toolOutput += `
|
|
871
|
-
|
|
872
|
-
Resource (${resource.uri}):
|
|
873
|
-
${resource.text}`;
|
|
874
|
-
} else if (resource && "blob" in resource) {
|
|
875
|
-
toolOutput += `
|
|
876
|
-
|
|
877
|
-
Resource (${resource.uri}): [Binary data]`;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
return { toolOutput, hasAttachments, attachments };
|
|
882
|
-
}
|
|
883
|
-
async function handleResourceAnalysis(runtime2, message, uri, serverName, resourceContent, resourceMeta, callback) {
|
|
884
|
-
await createMcpMemory(runtime2, message, "resource", serverName, resourceContent, {
|
|
885
|
-
uri,
|
|
886
|
-
isResourceAccess: true
|
|
887
|
-
});
|
|
888
|
-
const analysisPrompt = createAnalysisPrompt(uri, message.content.text || "", resourceContent, resourceMeta);
|
|
889
|
-
const analyzedResponse = await runtime2.useModel(ModelType2.TEXT_SMALL, {
|
|
890
|
-
prompt: analysisPrompt
|
|
891
|
-
});
|
|
892
|
-
if (callback) {
|
|
893
|
-
await callback({
|
|
894
|
-
text: analyzedResponse,
|
|
895
|
-
thought: `I analyzed the content from the ${uri} resource on ${serverName} and crafted a thoughtful response that addresses the user's request while maintaining my conversational style.`,
|
|
896
|
-
actions: ["READ_MCP_RESOURCE"]
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
async function handleToolResponse(runtime2, message, serverName, toolName, toolArgs, toolOutput, hasAttachments, attachments, state, mcpProvider, callback) {
|
|
901
|
-
await createMcpMemory(runtime2, message, "tool", serverName, toolOutput, {
|
|
902
|
-
toolName,
|
|
903
|
-
arguments: toolArgs,
|
|
904
|
-
isToolCall: true
|
|
905
|
-
});
|
|
906
|
-
const reasoningPrompt = createReasoningPrompt(state, mcpProvider, toolName, serverName, message.content.text || "", toolOutput, hasAttachments);
|
|
907
|
-
logger2.info({ reasoningPrompt }, "reasoning prompt");
|
|
908
|
-
const reasonedResponse = await runtime2.useModel(ModelType2.TEXT_SMALL, {
|
|
909
|
-
prompt: reasoningPrompt
|
|
910
|
-
});
|
|
911
|
-
const agentId = message.agentId || runtime2.agentId;
|
|
912
|
-
const replyMemory = {
|
|
913
|
-
entityId: agentId,
|
|
914
|
-
roomId: message.roomId,
|
|
915
|
-
worldId: message.worldId,
|
|
916
|
-
content: {
|
|
917
|
-
text: reasonedResponse,
|
|
918
|
-
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.`,
|
|
919
|
-
actions: ["CALL_MCP_TOOL"],
|
|
920
|
-
attachments: hasAttachments && attachments.length > 0 ? attachments : undefined
|
|
921
|
-
}
|
|
922
|
-
};
|
|
923
|
-
await runtime2.createMemory(replyMemory, "messages");
|
|
924
|
-
if (callback) {
|
|
925
|
-
await callback({
|
|
926
|
-
text: reasonedResponse,
|
|
927
|
-
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.`,
|
|
928
|
-
actions: ["CALL_MCP_TOOL"],
|
|
929
|
-
attachments: hasAttachments && attachments.length > 0 ? attachments : undefined
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
|
-
return replyMemory;
|
|
933
|
-
}
|
|
934
|
-
async function sendInitialResponse(callback) {
|
|
935
|
-
if (callback) {
|
|
936
|
-
const responseContent = {
|
|
937
|
-
thought: "The user is asking for information that can be found in an MCP resource. I will retrieve and analyze the appropriate resource.",
|
|
938
|
-
text: "I'll retrieve that information for you. Let me access the resource...",
|
|
939
|
-
actions: ["READ_MCP_RESOURCE"]
|
|
940
|
-
};
|
|
941
|
-
await callback(responseContent);
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
function createAnalysisPrompt(uri, userMessage, resourceContent, resourceMeta) {
|
|
945
|
-
const enhancedState = {
|
|
946
|
-
data: {},
|
|
947
|
-
text: "",
|
|
948
|
-
values: {
|
|
949
|
-
uri,
|
|
950
|
-
userMessage,
|
|
951
|
-
resourceContent,
|
|
952
|
-
resourceMeta
|
|
953
|
-
}
|
|
954
|
-
};
|
|
955
|
-
return composePromptFromState2({
|
|
956
|
-
state: enhancedState,
|
|
957
|
-
template: resourceAnalysisTemplate
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
function createReasoningPrompt(state, mcpProvider, toolName, serverName, userMessage, toolOutput, hasAttachments) {
|
|
961
|
-
const enhancedState = {
|
|
962
|
-
...state,
|
|
963
|
-
values: {
|
|
964
|
-
...state.values,
|
|
965
|
-
mcpProvider,
|
|
966
|
-
toolName,
|
|
967
|
-
serverName,
|
|
968
|
-
userMessage,
|
|
969
|
-
toolOutput,
|
|
970
|
-
hasAttachments
|
|
971
|
-
}
|
|
972
|
-
};
|
|
973
|
-
return composePromptFromState2({
|
|
974
|
-
state: enhancedState,
|
|
975
|
-
template: toolReasoningTemplate
|
|
976
|
-
});
|
|
977
|
-
}
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { logger as logger7 } from "@elizaos/core";
|
|
978
3
|
|
|
979
|
-
// src/
|
|
4
|
+
// src/actions/readResourceAction.ts
|
|
980
5
|
import {
|
|
981
6
|
ModelType as ModelType4,
|
|
982
7
|
composePromptFromState as composePromptFromState3,
|
|
983
|
-
logger as
|
|
8
|
+
logger as logger3
|
|
984
9
|
} from "@elizaos/core";
|
|
985
10
|
|
|
986
|
-
// src/
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
function parseJSON(input) {
|
|
990
|
-
let cleanedInput = input.replace(/^```(?:json)?\s*|\s*```$/g, "").trim();
|
|
991
|
-
const firstBrace = cleanedInput.indexOf("{");
|
|
992
|
-
const lastBrace = cleanedInput.lastIndexOf("}");
|
|
993
|
-
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
|
|
994
|
-
cleanedInput = cleanedInput.substring(firstBrace, lastBrace + 1);
|
|
995
|
-
}
|
|
996
|
-
return JSON5.parse(cleanedInput);
|
|
997
|
-
}
|
|
998
|
-
var ajv = new Ajv({
|
|
999
|
-
allErrors: true,
|
|
1000
|
-
strict: false
|
|
1001
|
-
});
|
|
1002
|
-
function validateJsonSchema(data, schema) {
|
|
1003
|
-
try {
|
|
1004
|
-
const validate = ajv.compile(schema);
|
|
1005
|
-
const valid = validate(data);
|
|
1006
|
-
if (!valid) {
|
|
1007
|
-
const errors = (validate.errors || []).map((err) => {
|
|
1008
|
-
const path = err.instancePath ? `${err.instancePath.replace(/^\//, "")}` : "value";
|
|
1009
|
-
return `${path}: ${err.message}`;
|
|
1010
|
-
});
|
|
1011
|
-
return { success: false, error: errors.join(", ") };
|
|
1012
|
-
}
|
|
1013
|
-
return { success: true, data };
|
|
1014
|
-
} catch (error) {
|
|
1015
|
-
return {
|
|
1016
|
-
success: false,
|
|
1017
|
-
error: `Schema validation error: ${error instanceof Error ? error.message : String(error)}`
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
11
|
+
// src/templates/resourceSelectionTemplate.ts
|
|
12
|
+
var resourceSelectionTemplate = `
|
|
13
|
+
{{{mcpProvider.text}}}
|
|
1021
14
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
async function withModelRetry({
|
|
1028
|
-
runtime: runtime2,
|
|
1029
|
-
message,
|
|
1030
|
-
state,
|
|
1031
|
-
callback,
|
|
1032
|
-
input,
|
|
1033
|
-
validationFn,
|
|
1034
|
-
createFeedbackPromptFn,
|
|
1035
|
-
failureMsg,
|
|
1036
|
-
retryCount = 0
|
|
1037
|
-
}) {
|
|
1038
|
-
const maxRetries = getMaxRetries(runtime2);
|
|
1039
|
-
try {
|
|
1040
|
-
logger3.info(`[WITH-MODEL-RETRY] Raw selection input:
|
|
1041
|
-
${input}`);
|
|
1042
|
-
const parsedJson = typeof input === "string" ? parseJSON(input) : input;
|
|
1043
|
-
logger3.debug(`[WITH-MODEL-RETRY] Parsed selection input:
|
|
1044
|
-
${JSON.stringify(parsedJson, null, 2)}`);
|
|
1045
|
-
const validationResult = validationFn(parsedJson);
|
|
1046
|
-
if (validationResult.success === false) {
|
|
1047
|
-
throw new Error(validationResult.error);
|
|
1048
|
-
}
|
|
1049
|
-
return validationResult.data;
|
|
1050
|
-
} catch (parseError) {
|
|
1051
|
-
const errorMessage = parseError instanceof Error ? parseError.message : "Unknown parsing error";
|
|
1052
|
-
logger3.error({ errorMessage }, `[WITH-MODEL-RETRY] Failed to parse response: ${errorMessage}`);
|
|
1053
|
-
if (retryCount < maxRetries) {
|
|
1054
|
-
logger3.debug(`[WITH-MODEL-RETRY] Retrying (attempt ${retryCount + 1}/${maxRetries})`);
|
|
1055
|
-
const feedbackPrompt = createFeedbackPromptFn(input, errorMessage, state, message.content.text || "");
|
|
1056
|
-
const retrySelection = await runtime2.useModel(ModelType3.OBJECT_LARGE, {
|
|
1057
|
-
prompt: feedbackPrompt
|
|
1058
|
-
});
|
|
1059
|
-
return withModelRetry({
|
|
1060
|
-
runtime: runtime2,
|
|
1061
|
-
input: retrySelection,
|
|
1062
|
-
validationFn,
|
|
1063
|
-
message,
|
|
1064
|
-
state,
|
|
1065
|
-
createFeedbackPromptFn,
|
|
1066
|
-
callback,
|
|
1067
|
-
failureMsg,
|
|
1068
|
-
retryCount: retryCount + 1
|
|
1069
|
-
});
|
|
1070
|
-
}
|
|
1071
|
-
if (callback && failureMsg) {
|
|
1072
|
-
await callback({
|
|
1073
|
-
text: failureMsg,
|
|
1074
|
-
thought: "Failed to parse response after multiple retries. Requesting clarification from user.",
|
|
1075
|
-
actions: ["REPLY"]
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
return null;
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
function getMaxRetries(runtime2) {
|
|
1082
|
-
try {
|
|
1083
|
-
const rawSettings = runtime2.getSetting("mcp");
|
|
1084
|
-
const settings = rawSettings;
|
|
1085
|
-
if (settings && typeof settings.maxRetries === "number") {
|
|
1086
|
-
const configValue = settings.maxRetries;
|
|
1087
|
-
if (!Number.isNaN(configValue) && configValue >= 0) {
|
|
1088
|
-
logger3.debug(`[WITH-MODEL-RETRY] Using configured selection retries: ${configValue}`);
|
|
1089
|
-
return configValue;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
} catch (error) {
|
|
1093
|
-
logger3.debug({ error: error instanceof Error ? error.message : String(error) }, "[WITH-MODEL-RETRY] Error reading selection retries config");
|
|
1094
|
-
}
|
|
1095
|
-
return DEFAULT_MAX_RETRIES;
|
|
1096
|
-
}
|
|
15
|
+
{{{recentMessages}}}
|
|
16
|
+
|
|
17
|
+
# Prompt
|
|
18
|
+
|
|
19
|
+
You are an intelligent assistant helping select the right resource to address a user's request.
|
|
1097
20
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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}
|
|
1101
32
|
|
|
1102
|
-
|
|
33
|
+
!!! YOUR RESPONSE MUST BE A VALID JSON OBJECT ONLY !!!
|
|
1103
34
|
|
|
1104
|
-
|
|
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
|
|
1105
42
|
|
|
1106
|
-
|
|
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
|
+
}
|
|
1107
49
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
2. Each name must match EXACTLY as shown in the list:
|
|
1111
|
-
- Example (correct): "serverName": "github"
|
|
1112
|
-
- Example (incorrect): "serverName": "GitHub", "Github", or variations
|
|
1113
|
-
3. Extract ACTUAL parameter values from the conversation context.
|
|
1114
|
-
- Do not invent or use placeholders like "octocat" or "Hello-World" unless the user said so.
|
|
1115
|
-
4. Include a "reasoning" field explaining why the selected tool fits the request.
|
|
1116
|
-
5. If no tool is appropriate, respond with:
|
|
1117
|
-
{
|
|
1118
|
-
"noToolAvailable": true
|
|
1119
|
-
}
|
|
50
|
+
REMEMBER: Your response will be parsed directly as JSON. If it fails to parse, the operation will fail completely!
|
|
51
|
+
`;
|
|
1120
52
|
|
|
1121
|
-
|
|
53
|
+
// src/types.ts
|
|
54
|
+
var MCP_SERVICE_NAME = "mcp";
|
|
55
|
+
var DEFAULT_MCP_TIMEOUT_SECONDS = 60000;
|
|
56
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
57
|
+
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
58
|
+
var BACKOFF_MULTIPLIER = 2;
|
|
59
|
+
var INITIAL_RETRY_DELAY = 2000;
|
|
60
|
+
var DEFAULT_PING_CONFIG = {
|
|
61
|
+
enabled: true,
|
|
62
|
+
intervalMs: 1e4,
|
|
63
|
+
timeoutMs: 5000,
|
|
64
|
+
failuresBeforeDisconnect: 3
|
|
65
|
+
};
|
|
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
|
+
};
|
|
1122
76
|
|
|
1123
|
-
|
|
77
|
+
// src/utils/error.ts
|
|
78
|
+
import {
|
|
79
|
+
ModelType,
|
|
80
|
+
composePromptFromState,
|
|
81
|
+
logger
|
|
82
|
+
} from "@elizaos/core";
|
|
1124
83
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
- DO NOT include comments (// or /* */) anywhere.
|
|
1129
|
-
- DO NOT use placeholders (e.g., "replace with...", "example", "your...", etc.)
|
|
1130
|
-
- ALL strings must use double quotes.
|
|
84
|
+
// src/templates/errorAnalysisPrompt.ts
|
|
85
|
+
var errorAnalysisPrompt = `
|
|
86
|
+
{{{mcpProvider.text}}}
|
|
1131
87
|
|
|
1132
|
-
|
|
1133
|
-
- All values must be fully grounded in user input or inferred contextually.
|
|
1134
|
-
- No missing fields unless they are explicitly optional in the schema.
|
|
1135
|
-
- All types must match the schema (strings, numbers, booleans).
|
|
88
|
+
{{{recentMessages}}}
|
|
1136
89
|
|
|
1137
|
-
|
|
1138
|
-
Your response MUST contain ONLY these top-level keys:
|
|
1139
|
-
1. "serverName" — The name of the server (e.g., "github", "notion")
|
|
1140
|
-
2. "toolName" — The name of the tool (e.g., "get_file_contents", "search")
|
|
1141
|
-
3. "reasoning" — A string explaining how the values were inferred from the conversation.
|
|
1142
|
-
4. "noToolAvailable" — A boolean indicating if no tool is available (true/false)
|
|
90
|
+
# Prompt
|
|
1143
91
|
|
|
1144
|
-
|
|
1145
|
-
{
|
|
1146
|
-
"serverName": "github",
|
|
1147
|
-
"toolName": "get_file_contents",
|
|
1148
|
-
"reasoning": "The user wants to retrieve the README from the facebook/react repository.",
|
|
1149
|
-
"noToolAvailable": false
|
|
1150
|
-
}
|
|
92
|
+
You're an assistant helping a user, but there was an error accessing the resource you tried to use.
|
|
1151
93
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
94
|
+
User request: "{{{userMessage}}}"
|
|
95
|
+
Error message: {{{error}}}
|
|
96
|
+
|
|
97
|
+
Create a helpful response that:
|
|
98
|
+
1. Acknowledges the issue in user-friendly terms
|
|
99
|
+
2. Offers alternative approaches to help if possible
|
|
100
|
+
3. Doesn't expose technical error details unless they're truly helpful
|
|
101
|
+
4. Maintains a helpful, conversational tone
|
|
1156
102
|
|
|
1157
|
-
|
|
103
|
+
Your response:
|
|
1158
104
|
`;
|
|
1159
|
-
var toolSelectionArgumentTemplate = `
|
|
1160
|
-
{{recentMessages}}
|
|
1161
105
|
|
|
1162
|
-
|
|
106
|
+
// src/utils/error.ts
|
|
107
|
+
async function handleMcpError(state, mcpProvider, error, runtime, message, type, callback) {
|
|
108
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
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;
|
|
112
|
+
if (callback) {
|
|
113
|
+
try {
|
|
114
|
+
const prompt = composePromptFromState({
|
|
115
|
+
state: {
|
|
116
|
+
...state,
|
|
117
|
+
values: { ...state.values, mcpProvider, userMessage: message.content.text || "", error: errorMessage }
|
|
118
|
+
},
|
|
119
|
+
template: errorAnalysisPrompt
|
|
120
|
+
});
|
|
121
|
+
responseText = await runtime.useModel(ModelType.TEXT_SMALL, { prompt });
|
|
122
|
+
} catch {}
|
|
123
|
+
await callback({ thought: `MCP ${type} error: ${errorMessage}`, text: responseText, actions: ["REPLY"] });
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
text: `Failed to execute MCP ${type}`,
|
|
127
|
+
values: { success: false, error: errorMessage, errorType: type },
|
|
128
|
+
data: { actionName: type === "tool" ? "CALL_MCP_TOOL" : "READ_MCP_RESOURCE", error: errorMessage },
|
|
129
|
+
success: false,
|
|
130
|
+
error: error instanceof Error ? error : new Error(errorMessage)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/utils/processing.ts
|
|
135
|
+
import {
|
|
136
|
+
ContentType,
|
|
137
|
+
ModelType as ModelType2,
|
|
138
|
+
createUniqueUuid
|
|
139
|
+
} from "@elizaos/core";
|
|
140
|
+
import { composePromptFromState as composePromptFromState2 } from "@elizaos/core";
|
|
141
|
+
|
|
142
|
+
// src/templates/resourceAnalysisTemplate.ts
|
|
143
|
+
var resourceAnalysisTemplate = `
|
|
144
|
+
{{{mcpProvider.text}}}
|
|
1163
145
|
|
|
1164
|
-
|
|
1165
|
-
The reasoning behind this selection is: "{{toolSelectionName.reasoning}}"
|
|
146
|
+
{{{recentMessages}}}
|
|
1166
147
|
|
|
1167
|
-
|
|
1168
|
-
1. Ensure the "toolArguments" object strictly adheres to the structure and requirements defined in the schema.
|
|
1169
|
-
2. All parameter values must be extracted from the conversation context and must be concrete, usable values.
|
|
1170
|
-
3. Avoid placeholders or generic terms unless explicitly provided by the user.
|
|
148
|
+
# Prompt
|
|
1171
149
|
|
|
1172
|
-
|
|
150
|
+
You are a helpful assistant responding to a user's request. You've just accessed the resource "{{{uri}}}" to help answer this request.
|
|
1173
151
|
|
|
1174
|
-
|
|
1175
|
-
- The response MUST be a single valid JSON object.
|
|
1176
|
-
- DO NOT wrap the JSON in triple backticks (\`\`\`), code blocks, or include any explanatory text.
|
|
1177
|
-
- DO NOT include comments (// or /* */) anywhere.
|
|
1178
|
-
- DO NOT use placeholders (e.g., "replace with...", "example", "your...", etc.)
|
|
1179
|
-
- ALL strings must use double quotes
|
|
152
|
+
Original user request: "{{{userMessage}}}"
|
|
1180
153
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
- No missing fields unless they are explicitly optional in the schema.
|
|
1184
|
-
- All types must match the schema (strings, numbers, booleans).
|
|
154
|
+
Resource metadata:
|
|
155
|
+
{{{resourceMeta}}
|
|
1185
156
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
1. "toolArguments" — An object matching the input schema: {{toolInputSchema}}
|
|
1189
|
-
2. "reasoning" — A string explaining how the values were inferred from the conversation.
|
|
157
|
+
Resource content:
|
|
158
|
+
{{{resourceContent}}
|
|
1190
159
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
},
|
|
1199
|
-
"reasoning": "The user wants to see the README from the facebook/react repository based on our conversation."
|
|
1200
|
-
}
|
|
160
|
+
Instructions:
|
|
161
|
+
1. Analyze how well the resource's content addresses the user's specific question or need
|
|
162
|
+
2. Identify the most relevant information from the resource
|
|
163
|
+
3. Create a natural, conversational response that incorporates this information
|
|
164
|
+
4. If the resource content is insufficient, acknowledge its limitations and explain what you can determine
|
|
165
|
+
5. Do not start with phrases like "According to the resource" or "Here's what I found" - instead, integrate the information naturally
|
|
166
|
+
6. Maintain your helpful, intelligent assistant personality while presenting the information
|
|
1201
167
|
|
|
1202
|
-
|
|
168
|
+
Your response (written as if directly to the user):
|
|
1203
169
|
`;
|
|
1204
170
|
|
|
1205
|
-
// src/utils/
|
|
1206
|
-
var
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
toolName: {
|
|
1216
|
-
type: "string",
|
|
1217
|
-
minLength: 1,
|
|
1218
|
-
errorMessage: "toolName must not be empty"
|
|
1219
|
-
},
|
|
1220
|
-
reasoning: {
|
|
1221
|
-
type: "string"
|
|
1222
|
-
},
|
|
1223
|
-
noToolAvailable: {
|
|
1224
|
-
type: "boolean"
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
};
|
|
1228
|
-
var toolSelectionArgumentSchema = {
|
|
1229
|
-
type: "object",
|
|
1230
|
-
required: ["toolArguments"],
|
|
1231
|
-
properties: {
|
|
1232
|
-
toolArguments: {
|
|
1233
|
-
type: "object"
|
|
171
|
+
// src/utils/mcp.ts
|
|
172
|
+
var NO_DESC = "No description";
|
|
173
|
+
async function createMcpMemory(runtime, message, type, serverName, content, metadata) {
|
|
174
|
+
const memory = await runtime.addEmbeddingToMemory({
|
|
175
|
+
entityId: message.entityId,
|
|
176
|
+
agentId: runtime.agentId,
|
|
177
|
+
roomId: message.roomId,
|
|
178
|
+
content: {
|
|
179
|
+
text: `Used "${type}" from "${serverName}". Content: ${content}`,
|
|
180
|
+
metadata: { ...metadata, serverName }
|
|
1234
181
|
}
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
// src/utils/validation.ts
|
|
1239
|
-
function validateToolSelectionName(parsed, state) {
|
|
1240
|
-
const basicResult = validateJsonSchema(parsed, toolSelectionNameSchema);
|
|
1241
|
-
if (basicResult.success === false) {
|
|
1242
|
-
return { success: false, error: basicResult.error };
|
|
1243
|
-
}
|
|
1244
|
-
const data = basicResult.data;
|
|
1245
|
-
const mcpData = state.values.mcp || {};
|
|
1246
|
-
const server = mcpData[data.serverName];
|
|
1247
|
-
if (!server || server.status !== "connected") {
|
|
1248
|
-
return {
|
|
1249
|
-
success: false,
|
|
1250
|
-
error: `Server "${data.serverName}" not found or not connected`
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
const toolInfo = server.tools?.[data.toolName];
|
|
1254
|
-
if (!toolInfo) {
|
|
1255
|
-
return {
|
|
1256
|
-
success: false,
|
|
1257
|
-
error: `Tool "${data.toolName}" not found on server "${data.serverName}"`
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
return { success: true, data };
|
|
182
|
+
});
|
|
183
|
+
await runtime.createMemory(memory, type === "resource" ? "resources" : "tools", true);
|
|
1261
184
|
}
|
|
1262
|
-
function
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
return { success: false, error: basicResult.error };
|
|
1266
|
-
}
|
|
1267
|
-
const data = basicResult.data;
|
|
1268
|
-
const validationResult = validateJsonSchema(data.toolArguments, toolInputSchema);
|
|
1269
|
-
if (validationResult.success === false) {
|
|
1270
|
-
return {
|
|
1271
|
-
success: false,
|
|
1272
|
-
error: `Invalid arguments: ${validationResult.error}`
|
|
1273
|
-
};
|
|
185
|
+
function buildMcpProviderData(servers) {
|
|
186
|
+
if (servers.length === 0) {
|
|
187
|
+
return { values: { mcp: {} }, data: { mcp: {} }, text: "No MCP servers connected." };
|
|
1274
188
|
}
|
|
1275
|
-
|
|
189
|
+
const mcpData = {};
|
|
190
|
+
const lines = [`# MCP Configuration
|
|
191
|
+
`];
|
|
192
|
+
for (const server of servers) {
|
|
193
|
+
const tools = {};
|
|
194
|
+
const resources = {};
|
|
195
|
+
lines.push(`## ${server.name} (${server.status})
|
|
196
|
+
`);
|
|
197
|
+
if (server.tools?.length) {
|
|
198
|
+
lines.push(`### Tools
|
|
199
|
+
`);
|
|
200
|
+
for (const t of server.tools) {
|
|
201
|
+
tools[t.name] = { description: t.description || NO_DESC, inputSchema: t.inputSchema || {} };
|
|
202
|
+
lines.push(`- **${t.name}**: ${t.description || NO_DESC}`);
|
|
203
|
+
}
|
|
204
|
+
lines.push("");
|
|
205
|
+
}
|
|
206
|
+
if (server.resources?.length) {
|
|
207
|
+
lines.push(`### Resources
|
|
208
|
+
`);
|
|
209
|
+
for (const r of server.resources) {
|
|
210
|
+
resources[r.uri] = { name: r.name, description: r.description || NO_DESC, mimeType: r.mimeType };
|
|
211
|
+
lines.push(`- **${r.name}** (${r.uri}): ${r.description || NO_DESC}`);
|
|
212
|
+
}
|
|
213
|
+
lines.push("");
|
|
214
|
+
}
|
|
215
|
+
mcpData[server.name] = { status: server.status, tools, resources };
|
|
216
|
+
}
|
|
217
|
+
const text = lines.join(`
|
|
218
|
+
`);
|
|
219
|
+
return { values: { mcp: mcpData, mcpText: text }, data: { mcp: mcpData }, text };
|
|
1276
220
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
221
|
+
|
|
222
|
+
// src/utils/processing.ts
|
|
223
|
+
function getMimeTypeToContentType(mimeType) {
|
|
224
|
+
if (!mimeType)
|
|
225
|
+
return;
|
|
226
|
+
if (mimeType.startsWith("image/"))
|
|
227
|
+
return ContentType.IMAGE;
|
|
228
|
+
if (mimeType.startsWith("video/"))
|
|
229
|
+
return ContentType.VIDEO;
|
|
230
|
+
if (mimeType.startsWith("audio/"))
|
|
231
|
+
return ContentType.AUDIO;
|
|
232
|
+
if (mimeType.includes("pdf") || mimeType.includes("document"))
|
|
233
|
+
return ContentType.DOCUMENT;
|
|
234
|
+
return;
|
|
1279
235
|
}
|
|
1280
|
-
function
|
|
1281
|
-
let
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
resourcesDescription += `Resource: ${uri} (Server: ${serverName})
|
|
1287
|
-
`;
|
|
1288
|
-
resourcesDescription += `Name: ${resource.name || "No name available"}
|
|
236
|
+
function processResourceResult(result, uri) {
|
|
237
|
+
let resourceContent = "";
|
|
238
|
+
let resourceMeta = "";
|
|
239
|
+
for (const content of result.contents) {
|
|
240
|
+
resourceContent += content.text || (content.blob ? `[Binary: ${content.mimeType || "unknown"}]` : "");
|
|
241
|
+
resourceMeta += `Resource: ${content.uri || uri}
|
|
1289
242
|
`;
|
|
1290
|
-
|
|
1291
|
-
|
|
243
|
+
if (content.mimeType)
|
|
244
|
+
resourceMeta += `Type: ${content.mimeType}
|
|
1292
245
|
`;
|
|
1293
|
-
}
|
|
1294
246
|
}
|
|
1295
|
-
return
|
|
247
|
+
return { resourceContent, resourceMeta };
|
|
1296
248
|
}
|
|
1297
|
-
function
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
249
|
+
function processToolResult(result, serverName, toolName, runtime, messageEntityId) {
|
|
250
|
+
let toolOutput = "";
|
|
251
|
+
let hasAttachments = false;
|
|
252
|
+
const attachments = [];
|
|
253
|
+
let attachmentIndex = 0;
|
|
254
|
+
for (const content of result.content) {
|
|
255
|
+
if (content.type === "text") {
|
|
256
|
+
toolOutput += content.text;
|
|
257
|
+
} else if (content.type === "image") {
|
|
258
|
+
hasAttachments = true;
|
|
259
|
+
attachments.push({
|
|
260
|
+
contentType: getMimeTypeToContentType(content.mimeType),
|
|
261
|
+
url: `data:${content.mimeType};base64,${content.data}`,
|
|
262
|
+
id: createUniqueUuid(runtime, `${messageEntityId}-attachment-${attachmentIndex++}`),
|
|
263
|
+
title: "Generated image",
|
|
264
|
+
source: `${serverName}/${toolName}`,
|
|
265
|
+
description: "Tool-generated image",
|
|
266
|
+
text: "Generated image"
|
|
267
|
+
});
|
|
268
|
+
} else if (content.type === "resource" && content.resource) {
|
|
269
|
+
const r = content.resource;
|
|
270
|
+
toolOutput += r.text ? `
|
|
1302
271
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
${itemsDescription}
|
|
272
|
+
Resource (${r.uri}):
|
|
273
|
+
${r.text}` : `
|
|
1306
274
|
|
|
1307
|
-
|
|
275
|
+
Resource (${r.uri}): [Binary]`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { toolOutput, hasAttachments, attachments };
|
|
1308
279
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
state,
|
|
1314
|
-
message,
|
|
1315
|
-
callback,
|
|
1316
|
-
mcpProvider
|
|
1317
|
-
}) {
|
|
1318
|
-
const toolSelectionPrompt = composePromptFromState3({
|
|
1319
|
-
state: { ...state, values: { ...state.values, mcpProvider } },
|
|
1320
|
-
template: toolSelectionNameTemplate
|
|
1321
|
-
});
|
|
1322
|
-
logger4.debug(`[SELECTION] Tool Selection Name Prompt:
|
|
1323
|
-
${toolSelectionPrompt}`);
|
|
1324
|
-
const toolSelectionName = await runtime2.useModel(ModelType4.TEXT_LARGE, {
|
|
1325
|
-
prompt: toolSelectionPrompt
|
|
1326
|
-
});
|
|
1327
|
-
logger4.debug(`[SELECTION] Tool Selection Name Response:
|
|
1328
|
-
${toolSelectionName}`);
|
|
1329
|
-
return await withModelRetry({
|
|
1330
|
-
runtime: runtime2,
|
|
1331
|
-
message,
|
|
1332
|
-
state,
|
|
1333
|
-
callback,
|
|
1334
|
-
input: toolSelectionName,
|
|
1335
|
-
validationFn: (parsed) => validateToolSelectionName(parsed, state),
|
|
1336
|
-
createFeedbackPromptFn: (originalResponse, errorMessage, state2, userMessage) => createToolSelectionFeedbackPrompt(originalResponse, errorMessage, state2, userMessage),
|
|
1337
|
-
failureMsg: "I'm having trouble figuring out the best way to help with your request."
|
|
280
|
+
async function handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback) {
|
|
281
|
+
await createMcpMemory(runtime, message, "resource", serverName, resourceContent, {
|
|
282
|
+
uri,
|
|
283
|
+
isResourceAccess: true
|
|
1338
284
|
});
|
|
1339
|
-
|
|
1340
|
-
async function createToolSelectionArgument({
|
|
1341
|
-
runtime: runtime2,
|
|
1342
|
-
state,
|
|
1343
|
-
message,
|
|
1344
|
-
callback,
|
|
1345
|
-
mcpProvider,
|
|
1346
|
-
toolSelectionName
|
|
1347
|
-
}) {
|
|
1348
|
-
if (!toolSelectionName) {
|
|
1349
|
-
logger4.warn("[SELECTION] Tool selection name is not provided. Cannot create tool selection argument.");
|
|
1350
|
-
return null;
|
|
1351
|
-
}
|
|
1352
|
-
const { serverName, toolName } = toolSelectionName;
|
|
1353
|
-
const toolInputSchema = mcpProvider.data.mcp[serverName].tools[toolName].inputSchema;
|
|
1354
|
-
logger4.trace(`[SELECTION] Tool Input Schema:
|
|
1355
|
-
${JSON.stringify({ toolInputSchema }, null, 2)}`);
|
|
1356
|
-
const toolSelectionArgumentPrompt = composePromptFromState3({
|
|
285
|
+
const prompt = composePromptFromState2({
|
|
1357
286
|
state: {
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
toolSelectionName,
|
|
1362
|
-
toolInputSchema: JSON.stringify(toolInputSchema)
|
|
1363
|
-
}
|
|
287
|
+
data: {},
|
|
288
|
+
text: "",
|
|
289
|
+
values: { uri, userMessage: message.content.text || "", resourceContent, resourceMeta }
|
|
1364
290
|
},
|
|
1365
|
-
template:
|
|
1366
|
-
});
|
|
1367
|
-
logger4.debug(`[SELECTION] Tool Selection Prompt:
|
|
1368
|
-
${toolSelectionArgumentPrompt}`);
|
|
1369
|
-
const toolSelectionArgument = await runtime2.useModel(ModelType4.TEXT_LARGE, {
|
|
1370
|
-
prompt: toolSelectionArgumentPrompt
|
|
1371
|
-
});
|
|
1372
|
-
logger4.debug(`[SELECTION] Tool Selection Argument Response:
|
|
1373
|
-
${toolSelectionArgument}`);
|
|
1374
|
-
return await withModelRetry({
|
|
1375
|
-
runtime: runtime2,
|
|
1376
|
-
message,
|
|
1377
|
-
state,
|
|
1378
|
-
callback,
|
|
1379
|
-
input: toolSelectionArgument,
|
|
1380
|
-
validationFn: (parsed) => validateToolSelectionArgument(parsed, state),
|
|
1381
|
-
createFeedbackPromptFn: (originalResponse, errorMessage, state2, userMessage) => createToolSelectionFeedbackPrompt(originalResponse, errorMessage, state2, userMessage),
|
|
1382
|
-
failureMsg: "I'm having trouble figuring out the best way to help with your request."
|
|
291
|
+
template: resourceAnalysisTemplate
|
|
1383
292
|
});
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
toolsDescription += `Tool: ${toolName} (Server: ${serverName})
|
|
1392
|
-
`;
|
|
1393
|
-
toolsDescription += `Description: ${tool.description || "No description available"}
|
|
1394
|
-
|
|
1395
|
-
`;
|
|
1396
|
-
}
|
|
293
|
+
const response = await runtime.useModel(ModelType2.TEXT_SMALL, { prompt });
|
|
294
|
+
if (callback) {
|
|
295
|
+
await callback({
|
|
296
|
+
text: response,
|
|
297
|
+
thought: `Analyzed resource ${uri} from ${serverName}`,
|
|
298
|
+
actions: ["READ_MCP_RESOURCE"]
|
|
299
|
+
});
|
|
1397
300
|
}
|
|
1398
|
-
const feedbackPrompt = createFeedbackPrompt2(originalResponse, errorMessage, "tool", toolsDescription, userMessage);
|
|
1399
|
-
logger4.debug(`[SELECTION] Tool Selection Feedback Prompt:
|
|
1400
|
-
${feedbackPrompt}`);
|
|
1401
|
-
return feedbackPrompt;
|
|
1402
|
-
}
|
|
1403
|
-
function createFeedbackPrompt2(originalResponse, errorMessage, itemType, itemsDescription, userMessage) {
|
|
1404
|
-
return `Error parsing JSON: ${errorMessage}
|
|
1405
|
-
|
|
1406
|
-
Your original response:
|
|
1407
|
-
${originalResponse}
|
|
1408
|
-
|
|
1409
|
-
Please try again with valid JSON for ${itemType} selection.
|
|
1410
|
-
Available ${itemType}s:
|
|
1411
|
-
${itemsDescription}
|
|
1412
|
-
|
|
1413
|
-
User request: ${userMessage}`;
|
|
1414
301
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
async function handleNoToolAvailable(callback, toolSelection) {
|
|
1418
|
-
const responseText = "I don't have a specific tool that can help with that request. Let me try to assist you directly instead.";
|
|
1419
|
-
const thoughtText = "No appropriate MCP tool available for this request. Falling back to direct assistance.";
|
|
1420
|
-
if (callback && toolSelection?.noToolAvailable) {
|
|
302
|
+
async function sendInitialResponse(callback) {
|
|
303
|
+
if (callback) {
|
|
1421
304
|
await callback({
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
actions: ["
|
|
305
|
+
thought: "Retrieving MCP resource...",
|
|
306
|
+
text: "I'll retrieve that information for you. Let me access the resource...",
|
|
307
|
+
actions: ["READ_MCP_RESOURCE"]
|
|
1425
308
|
});
|
|
1426
309
|
}
|
|
1427
|
-
return {
|
|
1428
|
-
text: responseText,
|
|
1429
|
-
values: {
|
|
1430
|
-
success: true,
|
|
1431
|
-
noToolAvailable: true,
|
|
1432
|
-
fallbackToDirectAssistance: true
|
|
1433
|
-
},
|
|
1434
|
-
data: {
|
|
1435
|
-
actionName: "CALL_MCP_TOOL",
|
|
1436
|
-
noToolAvailable: true,
|
|
1437
|
-
reason: toolSelection?.reasoning || "No appropriate tool available"
|
|
1438
|
-
},
|
|
1439
|
-
success: true
|
|
1440
|
-
};
|
|
1441
310
|
}
|
|
1442
311
|
|
|
1443
|
-
// src/
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
"INVOKE_TOOL",
|
|
1456
|
-
"INVOKE_MCP_TOOL"
|
|
1457
|
-
],
|
|
1458
|
-
description: "Calls a tool from an MCP server to perform a specific task",
|
|
1459
|
-
validate: async (runtime2, _message, _state) => {
|
|
1460
|
-
const mcpService = runtime2.getService(MCP_SERVICE_NAME);
|
|
1461
|
-
if (!mcpService)
|
|
1462
|
-
return false;
|
|
1463
|
-
const servers = mcpService.getServers();
|
|
1464
|
-
return servers.length > 0 && servers.some((server) => server.status === "connected" && server.tools && server.tools.length > 0);
|
|
1465
|
-
},
|
|
1466
|
-
handler: async (runtime2, message, _state, _options, callback) => {
|
|
1467
|
-
const composedState = await runtime2.composeState(message, ["RECENT_MESSAGES", "MCP"]);
|
|
1468
|
-
const mcpService = runtime2.getService(MCP_SERVICE_NAME);
|
|
1469
|
-
if (!mcpService) {
|
|
1470
|
-
throw new Error("MCP service not available");
|
|
312
|
+
// src/utils/json.ts
|
|
313
|
+
import Ajv from "ajv";
|
|
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;
|
|
1471
324
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
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
|
+
}
|
|
345
|
+
function parseJSON(input) {
|
|
346
|
+
let cleanedInput = input.replace(/^```(?:json)?\s*|\s*```$/g, "").trim();
|
|
347
|
+
const firstBrace = cleanedInput.indexOf("{");
|
|
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);
|
|
371
|
+
}
|
|
372
|
+
return JSON5.parse(cleanedInput);
|
|
373
|
+
}
|
|
374
|
+
var ajv = new Ajv({
|
|
375
|
+
allErrors: true,
|
|
376
|
+
strict: false
|
|
377
|
+
});
|
|
378
|
+
function validateJsonSchema(data, schema) {
|
|
379
|
+
try {
|
|
380
|
+
const validate = ajv.compile(schema);
|
|
381
|
+
const valid = validate(data);
|
|
382
|
+
if (!valid) {
|
|
383
|
+
const errors = (validate.errors || []).map((err) => {
|
|
384
|
+
const path = err.instancePath ? `${err.instancePath.replace(/^\//, "")}` : "value";
|
|
385
|
+
return `${path}: ${err.message}`;
|
|
1494
386
|
});
|
|
1495
|
-
|
|
1496
|
-
logger5.warn("[NO_TOOL_SELECTION_ARGUMENT] No appropriate tool selection argument available");
|
|
1497
|
-
return await handleNoToolAvailable(callback, toolSelectionName);
|
|
1498
|
-
}
|
|
1499
|
-
logger5.info(`[SELECTED] Tool Selection result:
|
|
1500
|
-
${JSON.stringify(toolSelectionArgument, null, 2)}`);
|
|
1501
|
-
const result = await mcpService.callTool(serverName, toolName, toolSelectionArgument.toolArguments);
|
|
1502
|
-
const { toolOutput, hasAttachments, attachments } = processToolResult(result, serverName, toolName, runtime2, message.entityId);
|
|
1503
|
-
const replyMemory = await handleToolResponse(runtime2, message, serverName, toolName, toolSelectionArgument.toolArguments, toolOutput, hasAttachments, attachments, composedState, mcpProvider, callback);
|
|
1504
|
-
return {
|
|
1505
|
-
text: `Successfully called tool: ${serverName}/${toolName}. Reasoned response: ${replyMemory.content.text}`,
|
|
1506
|
-
values: {
|
|
1507
|
-
success: true,
|
|
1508
|
-
toolExecuted: true,
|
|
1509
|
-
serverName,
|
|
1510
|
-
toolName,
|
|
1511
|
-
hasAttachments,
|
|
1512
|
-
output: toolOutput
|
|
1513
|
-
},
|
|
1514
|
-
data: {
|
|
1515
|
-
actionName: "CALL_MCP_TOOL",
|
|
1516
|
-
serverName,
|
|
1517
|
-
toolName,
|
|
1518
|
-
toolArguments: toolSelectionArgument.toolArguments,
|
|
1519
|
-
reasoning: toolSelectionName.reasoning,
|
|
1520
|
-
output: toolOutput,
|
|
1521
|
-
attachments: attachments || []
|
|
1522
|
-
},
|
|
1523
|
-
success: true
|
|
1524
|
-
};
|
|
1525
|
-
} catch (error) {
|
|
1526
|
-
return await handleMcpError(composedState, mcpProvider, error, runtime2, message, "tool", callback);
|
|
387
|
+
return { success: false, error: errors.join(", ") };
|
|
1527
388
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
},
|
|
1537
|
-
{
|
|
1538
|
-
name: "{{assistant}}",
|
|
1539
|
-
content: {
|
|
1540
|
-
text: "I'll help you with that request. Let me access the right tool...",
|
|
1541
|
-
actions: ["CALL_MCP_TOOL"]
|
|
1542
|
-
}
|
|
1543
|
-
},
|
|
1544
|
-
{
|
|
1545
|
-
name: "{{assistant}}",
|
|
1546
|
-
content: {
|
|
1547
|
-
text: `I found the following information about climate change:
|
|
1548
|
-
|
|
1549
|
-
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.`,
|
|
1550
|
-
actions: ["CALL_MCP_TOOL"]
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
]
|
|
1554
|
-
]
|
|
1555
|
-
};
|
|
1556
|
-
|
|
1557
|
-
// src/actions/readResourceAction.ts
|
|
1558
|
-
import {
|
|
1559
|
-
ModelType as ModelType5,
|
|
1560
|
-
composePromptFromState as composePromptFromState4,
|
|
1561
|
-
logger as logger6
|
|
1562
|
-
} from "@elizaos/core";
|
|
1563
|
-
|
|
1564
|
-
// src/templates/resourceSelectionTemplate.ts
|
|
1565
|
-
var resourceSelectionTemplate = `
|
|
1566
|
-
{{{mcpProvider.text}}}
|
|
1567
|
-
|
|
1568
|
-
{{{recentMessages}}}
|
|
1569
|
-
|
|
1570
|
-
# Prompt
|
|
389
|
+
return { success: true, data };
|
|
390
|
+
} catch (error) {
|
|
391
|
+
return {
|
|
392
|
+
success: false,
|
|
393
|
+
error: `Schema validation error: ${error instanceof Error ? error.message : String(error)}`
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
1571
397
|
|
|
1572
|
-
|
|
398
|
+
// src/utils/validation.ts
|
|
399
|
+
function validateResourceSelection(selection) {
|
|
400
|
+
return validateJsonSchema(selection, ResourceSelectionSchema);
|
|
401
|
+
}
|
|
402
|
+
function createResourceSelectionFeedbackPrompt(originalResponse, errorMessage, composedState, userMessage) {
|
|
403
|
+
let description = "";
|
|
404
|
+
for (const [serverName, server] of Object.entries(composedState.values.mcp || {})) {
|
|
405
|
+
if (server.status !== "connected")
|
|
406
|
+
continue;
|
|
407
|
+
for (const [uri, resource] of Object.entries(server.resources || {})) {
|
|
408
|
+
description += `Resource: ${uri} (Server: ${serverName})
|
|
409
|
+
`;
|
|
410
|
+
description += `Name: ${resource.name || "No name"}
|
|
411
|
+
`;
|
|
412
|
+
description += `Description: ${resource.description || "No description"}
|
|
1573
413
|
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
WRONG: "serverName": "GitHub" or "Github" or any other variation
|
|
1579
|
-
3. The uri value should match EXACTLY the resource uri listed
|
|
1580
|
-
CORRECT: "uri": "weather://San Francisco/current" (if that's the exact uri)
|
|
1581
|
-
WRONG: "uri": "weather://sanfrancisco/current" or any variation
|
|
1582
|
-
4. Identify the user's information need from the conversation context
|
|
1583
|
-
5. Select the most appropriate resource based on its description and the request
|
|
1584
|
-
6. If no resource seems appropriate, output {"noResourceAvailable": true}
|
|
414
|
+
`;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return `Error parsing JSON: ${errorMessage}
|
|
1585
418
|
|
|
1586
|
-
|
|
419
|
+
Your original response:
|
|
420
|
+
${originalResponse}
|
|
1587
421
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
- NO placeholders like "replace with...", "example", "your...", "actual", etc.
|
|
1592
|
-
- Every parameter value must be a concrete, usable value (not instructions to replace)
|
|
1593
|
-
- Use proper JSON syntax with double quotes for strings
|
|
1594
|
-
- NO explanatory text before or after the JSON object
|
|
422
|
+
Please try again with valid JSON for resource selection.
|
|
423
|
+
Available resources:
|
|
424
|
+
${description}
|
|
1595
425
|
|
|
1596
|
-
|
|
1597
|
-
{
|
|
1598
|
-
"serverName": "weather-server",
|
|
1599
|
-
"uri": "weather://San Francisco/current",
|
|
1600
|
-
"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."
|
|
426
|
+
User request: ${userMessage}`;
|
|
1601
427
|
}
|
|
1602
428
|
|
|
1603
|
-
|
|
1604
|
-
|
|
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,
|
|
462
|
+
message,
|
|
463
|
+
state,
|
|
464
|
+
createFeedbackPromptFn,
|
|
465
|
+
callback,
|
|
466
|
+
failureMsg,
|
|
467
|
+
retryCount: retryCount + 1
|
|
468
|
+
});
|
|
469
|
+
}
|
|
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;
|
|
488
|
+
}
|
|
1605
489
|
|
|
1606
490
|
// src/actions/readResourceAction.ts
|
|
1607
491
|
function createResourceSelectionPrompt(composedState, userMessage) {
|
|
@@ -1634,7 +518,7 @@ function createResourceSelectionPrompt(composedState, userMessage) {
|
|
|
1634
518
|
userMessage
|
|
1635
519
|
}
|
|
1636
520
|
};
|
|
1637
|
-
return
|
|
521
|
+
return composePromptFromState3({
|
|
1638
522
|
state: enhancedState,
|
|
1639
523
|
template: resourceSelectionTemplate
|
|
1640
524
|
});
|
|
@@ -1652,16 +536,16 @@ var readResourceAction = {
|
|
|
1652
536
|
"ACCESS_MCP_RESOURCE"
|
|
1653
537
|
],
|
|
1654
538
|
description: "Reads a resource from an MCP server",
|
|
1655
|
-
validate: async (
|
|
1656
|
-
const mcpService =
|
|
539
|
+
validate: async (runtime, _message, _state) => {
|
|
540
|
+
const mcpService = runtime.getService(MCP_SERVICE_NAME);
|
|
1657
541
|
if (!mcpService)
|
|
1658
542
|
return false;
|
|
1659
543
|
const servers = mcpService.getServers();
|
|
1660
544
|
return servers.length > 0 && servers.some((server) => server.status === "connected" && server.resources && server.resources.length > 0);
|
|
1661
545
|
},
|
|
1662
|
-
handler: async (
|
|
1663
|
-
const composedState = await
|
|
1664
|
-
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);
|
|
1665
549
|
if (!mcpService) {
|
|
1666
550
|
throw new Error("MCP service not available");
|
|
1667
551
|
}
|
|
@@ -1669,11 +553,11 @@ var readResourceAction = {
|
|
|
1669
553
|
try {
|
|
1670
554
|
await sendInitialResponse(callback);
|
|
1671
555
|
const resourceSelectionPrompt = createResourceSelectionPrompt(composedState, message.content.text || "");
|
|
1672
|
-
const resourceSelection = await
|
|
556
|
+
const resourceSelection = await runtime.useModel(ModelType4.TEXT_SMALL, {
|
|
1673
557
|
prompt: resourceSelectionPrompt
|
|
1674
558
|
});
|
|
1675
559
|
const parsedSelection = await withModelRetry({
|
|
1676
|
-
runtime
|
|
560
|
+
runtime,
|
|
1677
561
|
state: composedState,
|
|
1678
562
|
message,
|
|
1679
563
|
callback,
|
|
@@ -1708,347 +592,915 @@ var readResourceAction = {
|
|
|
1708
592
|
success: true
|
|
1709
593
|
};
|
|
1710
594
|
}
|
|
1711
|
-
const { serverName, uri, reasoning } = parsedSelection;
|
|
1712
|
-
|
|
1713
|
-
const result = await mcpService.readResource(serverName, uri);
|
|
1714
|
-
|
|
1715
|
-
const { resourceContent, resourceMeta } = processResourceResult(result, uri);
|
|
1716
|
-
await handleResourceAnalysis(
|
|
1717
|
-
return {
|
|
1718
|
-
text: `Successfully read resource: ${uri}`,
|
|
1719
|
-
values: {
|
|
1720
|
-
success: true,
|
|
1721
|
-
resourceRead: true,
|
|
1722
|
-
serverName,
|
|
1723
|
-
uri
|
|
1724
|
-
},
|
|
1725
|
-
data: {
|
|
1726
|
-
actionName: "READ_MCP_RESOURCE",
|
|
1727
|
-
serverName,
|
|
1728
|
-
uri,
|
|
1729
|
-
reasoning,
|
|
1730
|
-
resourceMeta,
|
|
1731
|
-
contentLength: resourceContent?.length || 0
|
|
1732
|
-
},
|
|
1733
|
-
success: true
|
|
1734
|
-
};
|
|
1735
|
-
} catch (error) {
|
|
1736
|
-
return await handleMcpError(composedState, mcpProvider, error,
|
|
595
|
+
const { serverName, uri, reasoning } = parsedSelection;
|
|
596
|
+
logger3.debug(`Selected resource "${uri}" on server "${serverName}" because: ${reasoning}`);
|
|
597
|
+
const result = await mcpService.readResource(serverName, uri);
|
|
598
|
+
logger3.debug(`Read resource ${uri} from server ${serverName}`);
|
|
599
|
+
const { resourceContent, resourceMeta } = processResourceResult(result, uri);
|
|
600
|
+
await handleResourceAnalysis(runtime, message, uri, serverName, resourceContent, resourceMeta, callback);
|
|
601
|
+
return {
|
|
602
|
+
text: `Successfully read resource: ${uri}`,
|
|
603
|
+
values: {
|
|
604
|
+
success: true,
|
|
605
|
+
resourceRead: true,
|
|
606
|
+
serverName,
|
|
607
|
+
uri
|
|
608
|
+
},
|
|
609
|
+
data: {
|
|
610
|
+
actionName: "READ_MCP_RESOURCE",
|
|
611
|
+
serverName,
|
|
612
|
+
uri,
|
|
613
|
+
reasoning,
|
|
614
|
+
resourceMeta,
|
|
615
|
+
contentLength: resourceContent?.length || 0
|
|
616
|
+
},
|
|
617
|
+
success: true
|
|
618
|
+
};
|
|
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);
|
|
1047
|
+
}
|
|
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;
|
|
1092
|
+
}
|
|
1737
1093
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
}
|
|
1746
|
-
},
|
|
1747
|
-
{
|
|
1748
|
-
name: "{{assistant}}",
|
|
1749
|
-
content: {
|
|
1750
|
-
text: `I'll retrieve that information for you. Let me access the resource...`,
|
|
1751
|
-
actions: ["READ_MCP_RESOURCE"]
|
|
1752
|
-
}
|
|
1753
|
-
},
|
|
1754
|
-
{
|
|
1755
|
-
name: "{{assistant}}",
|
|
1756
|
-
content: {
|
|
1757
|
-
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.`,
|
|
1758
|
-
actions: ["READ_MCP_RESOURCE"]
|
|
1759
|
-
}
|
|
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);
|
|
1760
1101
|
}
|
|
1761
|
-
]
|
|
1762
|
-
]
|
|
1763
|
-
};
|
|
1764
|
-
|
|
1765
|
-
// src/provider.ts
|
|
1766
|
-
var provider = {
|
|
1767
|
-
name: "MCP",
|
|
1768
|
-
description: "Information about connected MCP servers, tools, and resources",
|
|
1769
|
-
get: async (runtime2, _message, _state) => {
|
|
1770
|
-
const mcpService = runtime2.getService(MCP_SERVICE_NAME);
|
|
1771
|
-
if (!mcpService) {
|
|
1772
|
-
return {
|
|
1773
|
-
values: { mcp: {} },
|
|
1774
|
-
data: { mcp: {} },
|
|
1775
|
-
text: "No MCP servers are available."
|
|
1776
|
-
};
|
|
1777
1102
|
}
|
|
1778
|
-
return
|
|
1103
|
+
return processed;
|
|
1779
1104
|
}
|
|
1780
|
-
|
|
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
|
+
}
|
|
1132
|
+
|
|
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;
|
|
1178
|
+
}
|
|
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;
|
|
1781
1301
|
|
|
1782
1302
|
// src/service.ts
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
1786
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1303
|
+
var err = (e) => e instanceof Error ? e.message : String(e);
|
|
1304
|
+
|
|
1787
1305
|
class McpService extends Service {
|
|
1788
1306
|
static serviceType = MCP_SERVICE_NAME;
|
|
1789
|
-
capabilityDescription = "Enables the agent to interact with MCP
|
|
1307
|
+
capabilityDescription = "Enables the agent to interact with MCP servers";
|
|
1790
1308
|
connections = new Map;
|
|
1791
1309
|
connectionStates = new Map;
|
|
1792
|
-
mcpProvider = {
|
|
1793
|
-
values: { mcp: {}, mcpText: "" },
|
|
1794
|
-
data: { mcp: {} },
|
|
1795
|
-
text: ""
|
|
1796
|
-
};
|
|
1310
|
+
mcpProvider = { values: { mcp: {}, mcpText: "" }, data: { mcp: {} }, text: "" };
|
|
1797
1311
|
pingConfig = DEFAULT_PING_CONFIG;
|
|
1798
1312
|
toolCompatibility = null;
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
return service;
|
|
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;
|
|
1812
1325
|
}
|
|
1813
1326
|
async waitForInitialization() {
|
|
1814
|
-
|
|
1815
|
-
await this.initializationPromise;
|
|
1816
|
-
}
|
|
1327
|
+
await this.initPromise;
|
|
1817
1328
|
}
|
|
1818
1329
|
async stop() {
|
|
1819
|
-
for (const
|
|
1820
|
-
await this.
|
|
1821
|
-
}
|
|
1822
|
-
this.connections.clear();
|
|
1823
|
-
for (const state of this.connectionStates.values()) {
|
|
1824
|
-
if (state.pingInterval)
|
|
1825
|
-
clearInterval(state.pingInterval);
|
|
1826
|
-
if (state.reconnectTimeout)
|
|
1827
|
-
clearTimeout(state.reconnectTimeout);
|
|
1330
|
+
for (const name of this.connections.keys()) {
|
|
1331
|
+
await this.disconnect(name);
|
|
1828
1332
|
}
|
|
1829
|
-
this.connectionStates.clear();
|
|
1830
1333
|
}
|
|
1831
|
-
async
|
|
1832
|
-
logger7.info("[McpService] Starting MCP server initialization...");
|
|
1334
|
+
async init() {
|
|
1833
1335
|
try {
|
|
1834
|
-
const
|
|
1835
|
-
|
|
1836
|
-
const serverNames = mcpSettings?.servers ? Object.keys(mcpSettings.servers) : [];
|
|
1837
|
-
logger7.info(`[McpService] Getting MCP settings... hasSettings=${!!mcpSettings} hasServers=${!!mcpSettings?.servers} serverCount=${serverCount} servers=${JSON.stringify(serverNames)}`);
|
|
1838
|
-
if (!mcpSettings || !mcpSettings.servers) {
|
|
1839
|
-
logger7.info("[McpService] No MCP servers configured.");
|
|
1840
|
-
this.mcpProvider = buildMcpProviderData([]);
|
|
1841
|
-
return;
|
|
1842
|
-
}
|
|
1843
|
-
if (Object.keys(mcpSettings.servers).length === 0) {
|
|
1844
|
-
logger7.info("[McpService] MCP settings exist but no servers configured.");
|
|
1336
|
+
const settings = this.getSettings();
|
|
1337
|
+
if (!settings?.servers || Object.keys(settings.servers).length === 0) {
|
|
1845
1338
|
this.mcpProvider = buildMcpProviderData([]);
|
|
1846
1339
|
return;
|
|
1847
1340
|
}
|
|
1848
|
-
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
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");
|
|
1871
1372
|
this.mcpProvider = buildMcpProviderData([]);
|
|
1872
1373
|
}
|
|
1873
1374
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
if (!
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
logger7.info("[McpService] Found MCP settings in character.settings.mcp (fallback)");
|
|
1882
|
-
settings = characterSettings.mcp;
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
if (!settings || !settings.servers) {
|
|
1886
|
-
const runtimeSettings = this.runtime.settings;
|
|
1887
|
-
if (runtimeSettings?.mcp) {
|
|
1888
|
-
logger7.info("[McpService] Found MCP settings in runtime.settings.mcp (fallback)");
|
|
1889
|
-
settings = runtimeSettings.mcp;
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
if (settings && typeof settings === "object" && settings.servers) {
|
|
1893
|
-
logger7.info(`[McpService] MCP settings found with ${Object.keys(settings.servers).length} server(s)`);
|
|
1894
|
-
return settings;
|
|
1895
|
-
}
|
|
1896
|
-
logger7.info("[McpService] No valid MCP settings found");
|
|
1897
|
-
return;
|
|
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;
|
|
1898
1382
|
}
|
|
1899
|
-
async
|
|
1900
|
-
|
|
1901
|
-
const
|
|
1902
|
-
for (const name of currentNames) {
|
|
1903
|
-
if (!newNames.has(name)) {
|
|
1904
|
-
await this.deleteConnection(name);
|
|
1905
|
-
logger7.info(`Deleted MCP server: ${name}`);
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
const connectionPromises = Object.entries(serverConfigs).map(async ([name, config]) => {
|
|
1909
|
-
const currentConnection = this.connections.get(name);
|
|
1910
|
-
if (!currentConnection) {
|
|
1911
|
-
try {
|
|
1912
|
-
await this.initializeConnection(name, config);
|
|
1913
|
-
logger7.info(`✓ Connected to MCP server: ${name}`);
|
|
1914
|
-
} catch (error) {
|
|
1915
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName: name }, `✗ Failed to connect to new MCP server ${name}`);
|
|
1916
|
-
}
|
|
1917
|
-
} else if (JSON.stringify(config) !== currentConnection.server.config) {
|
|
1918
|
-
try {
|
|
1919
|
-
await this.deleteConnection(name);
|
|
1920
|
-
await this.initializeConnection(name, config);
|
|
1921
|
-
logger7.info(`✓ Reconnected MCP server with updated config: ${name}`);
|
|
1922
|
-
} catch (error) {
|
|
1923
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName: name }, `✗ Failed to reconnect MCP server ${name}`);
|
|
1924
|
-
}
|
|
1925
|
-
}
|
|
1926
|
-
});
|
|
1927
|
-
await Promise.allSettled(connectionPromises);
|
|
1928
|
-
logger7.info(`[McpService] All server connection attempts completed`);
|
|
1929
|
-
}
|
|
1930
|
-
async initializeConnection(name, config) {
|
|
1931
|
-
await this.deleteConnection(name);
|
|
1932
|
-
const state = {
|
|
1933
|
-
status: "connecting",
|
|
1934
|
-
reconnectAttempts: 0,
|
|
1935
|
-
consecutivePingFailures: 0
|
|
1936
|
-
};
|
|
1383
|
+
async connect(name, config) {
|
|
1384
|
+
await this.disconnect(name);
|
|
1385
|
+
const state = { status: "connecting", reconnectAttempts: 0, consecutivePingFailures: 0 };
|
|
1937
1386
|
this.connectionStates.set(name, state);
|
|
1938
1387
|
try {
|
|
1939
1388
|
const client = new Client({ name: "ElizaOS", version: "1.0.0" }, { capabilities: {} });
|
|
1940
|
-
const transport = config.type === "stdio" ?
|
|
1941
|
-
const
|
|
1942
|
-
server: {
|
|
1943
|
-
name,
|
|
1944
|
-
config: JSON.stringify(config),
|
|
1945
|
-
status: "connecting"
|
|
1946
|
-
},
|
|
1389
|
+
const transport = config.type === "stdio" ? this.createStdioTransport(name, config) : this.createHttpTransport(name, config);
|
|
1390
|
+
const conn = {
|
|
1391
|
+
server: { name, config: JSON.stringify(config), status: "connecting" },
|
|
1947
1392
|
client,
|
|
1948
1393
|
transport
|
|
1949
1394
|
};
|
|
1950
|
-
this.connections.set(name,
|
|
1951
|
-
this.setupTransportHandlers(name,
|
|
1952
|
-
await
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
const
|
|
1957
|
-
const
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
config: JSON.stringify(config),
|
|
1962
|
-
error: "",
|
|
1963
|
-
tools,
|
|
1964
|
-
resources,
|
|
1965
|
-
resourceTemplates
|
|
1966
|
-
};
|
|
1395
|
+
this.connections.set(name, conn);
|
|
1396
|
+
this.setupTransportHandlers(name, conn, state, config.type === "stdio");
|
|
1397
|
+
await Promise.race([
|
|
1398
|
+
client.connect(transport),
|
|
1399
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error("Connection timeout")), 60000))
|
|
1400
|
+
]);
|
|
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 };
|
|
1967
1406
|
state.status = "connected";
|
|
1968
1407
|
state.lastConnected = new Date;
|
|
1969
1408
|
state.reconnectAttempts = 0;
|
|
1970
1409
|
state.consecutivePingFailures = 0;
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
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) {
|
|
1974
1415
|
state.status = "disconnected";
|
|
1975
|
-
state.lastError =
|
|
1976
|
-
this.
|
|
1977
|
-
throw
|
|
1416
|
+
state.lastError = e instanceof Error ? e : new Error(String(e));
|
|
1417
|
+
this.handleDisconnect(name, e);
|
|
1418
|
+
throw e;
|
|
1978
1419
|
}
|
|
1979
1420
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
const
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
logger7.error({ error, serverName: name }, `Transport error for "${name}"`);
|
|
1990
|
-
connection.server.status = "disconnected";
|
|
1991
|
-
this.appendErrorMessage(connection, error.message);
|
|
1992
|
-
}
|
|
1993
|
-
if (!isHttpTransport) {
|
|
1994
|
-
this.handleDisconnection(name, error);
|
|
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)`);
|
|
1995
1430
|
}
|
|
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);
|
|
1996
1471
|
};
|
|
1997
|
-
|
|
1998
|
-
if (
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
logger7.warn({ serverName: name }, `Transport closed for "${name}"`);
|
|
2002
|
-
connection.server.status = "disconnected";
|
|
2003
|
-
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"));
|
|
2004
1476
|
}
|
|
2005
1477
|
};
|
|
2006
1478
|
}
|
|
2007
|
-
|
|
2008
|
-
const connection = this.connections.get(name);
|
|
2009
|
-
if (!connection)
|
|
2010
|
-
return;
|
|
2011
|
-
const config = JSON.parse(connection.server.config);
|
|
2012
|
-
const isHttpTransport = config.type !== "stdio";
|
|
2013
|
-
if (isHttpTransport) {
|
|
2014
|
-
logger7.debug(`[McpService] Skipping ping monitoring for HTTP server: ${name}`);
|
|
2015
|
-
return;
|
|
2016
|
-
}
|
|
1479
|
+
startPingMonitor(name) {
|
|
2017
1480
|
const state = this.connectionStates.get(name);
|
|
2018
1481
|
if (!state || !this.pingConfig.enabled)
|
|
2019
1482
|
return;
|
|
2020
1483
|
if (state.pingInterval)
|
|
2021
1484
|
clearInterval(state.pingInterval);
|
|
2022
|
-
state.pingInterval = setInterval(() => {
|
|
2023
|
-
this.
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
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
|
+
}
|
|
2027
1501
|
}, this.pingConfig.intervalMs);
|
|
2028
1502
|
}
|
|
2029
|
-
|
|
2030
|
-
const connection = this.connections.get(name);
|
|
2031
|
-
if (!connection)
|
|
2032
|
-
throw new Error(`No connection for ping: ${name}`);
|
|
2033
|
-
await Promise.race([
|
|
2034
|
-
connection.client.listTools(),
|
|
2035
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("Ping timeout")), this.pingConfig.timeoutMs))
|
|
2036
|
-
]);
|
|
2037
|
-
const state = this.connectionStates.get(name);
|
|
2038
|
-
if (state)
|
|
2039
|
-
state.consecutivePingFailures = 0;
|
|
2040
|
-
}
|
|
2041
|
-
handlePingFailure(name, error) {
|
|
2042
|
-
const state = this.connectionStates.get(name);
|
|
2043
|
-
if (!state)
|
|
2044
|
-
return;
|
|
2045
|
-
state.consecutivePingFailures++;
|
|
2046
|
-
if (state.consecutivePingFailures >= this.pingConfig.failuresBeforeDisconnect) {
|
|
2047
|
-
logger7.warn(`Ping failures exceeded for ${name}, disconnecting and attempting reconnect.`);
|
|
2048
|
-
this.handleDisconnection(name, error);
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
handleDisconnection(name, error) {
|
|
1503
|
+
handleDisconnect(name, error) {
|
|
2052
1504
|
const state = this.connectionStates.get(name);
|
|
2053
1505
|
if (!state)
|
|
2054
1506
|
return;
|
|
@@ -2059,212 +1511,145 @@ class McpService extends Service {
|
|
|
2059
1511
|
if (state.reconnectTimeout)
|
|
2060
1512
|
clearTimeout(state.reconnectTimeout);
|
|
2061
1513
|
if (state.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
2062
|
-
|
|
1514
|
+
logger6.error(`[MCP] Max reconnect attempts for ${name}`);
|
|
2063
1515
|
return;
|
|
2064
1516
|
}
|
|
2065
1517
|
const delay = INITIAL_RETRY_DELAY * Math.pow(BACKOFF_MULTIPLIER, state.reconnectAttempts);
|
|
2066
1518
|
state.reconnectTimeout = setTimeout(async () => {
|
|
2067
1519
|
state.reconnectAttempts++;
|
|
2068
|
-
logger7.info(`Attempting to reconnect to ${name} (attempt ${state.reconnectAttempts})...`);
|
|
2069
1520
|
const config = this.connections.get(name)?.server.config;
|
|
2070
1521
|
if (config) {
|
|
2071
1522
|
try {
|
|
2072
|
-
await this.
|
|
2073
|
-
} catch (
|
|
2074
|
-
|
|
2075
|
-
this.handleDisconnection(name, err);
|
|
1523
|
+
await this.connect(name, JSON.parse(config));
|
|
1524
|
+
} catch (e) {
|
|
1525
|
+
this.handleDisconnect(name, e);
|
|
2076
1526
|
}
|
|
2077
1527
|
}
|
|
2078
1528
|
}, delay);
|
|
2079
1529
|
}
|
|
2080
|
-
async
|
|
2081
|
-
const
|
|
2082
|
-
if (
|
|
2083
|
-
|
|
2084
|
-
await connection.transport.close();
|
|
2085
|
-
await connection.client.close();
|
|
2086
|
-
} catch (error) {
|
|
2087
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName: name }, `Failed to close transport for ${name}`);
|
|
2088
|
-
}
|
|
2089
|
-
this.connections.delete(name);
|
|
2090
|
-
}
|
|
2091
|
-
const state = this.connectionStates.get(name);
|
|
2092
|
-
if (state) {
|
|
2093
|
-
if (state.pingInterval)
|
|
2094
|
-
clearInterval(state.pingInterval);
|
|
2095
|
-
if (state.reconnectTimeout)
|
|
2096
|
-
clearTimeout(state.reconnectTimeout);
|
|
2097
|
-
this.connectionStates.delete(name);
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
getServerConnection(serverName) {
|
|
2101
|
-
return this.connections.get(serverName);
|
|
2102
|
-
}
|
|
2103
|
-
async buildStdioClientTransport(name, config) {
|
|
2104
|
-
if (!config.command) {
|
|
2105
|
-
throw new Error(`Missing command for stdio MCP server ${name}`);
|
|
2106
|
-
}
|
|
2107
|
-
return new StdioClientTransport({
|
|
2108
|
-
command: config.command,
|
|
2109
|
-
args: config.args,
|
|
2110
|
-
env: {
|
|
2111
|
-
...config.env,
|
|
2112
|
-
...process.env.PATH ? { PATH: process.env.PATH } : {}
|
|
2113
|
-
},
|
|
2114
|
-
stderr: "pipe",
|
|
2115
|
-
cwd: config.cwd
|
|
2116
|
-
});
|
|
2117
|
-
}
|
|
2118
|
-
async buildHttpClientTransport(name, config) {
|
|
2119
|
-
if (!config.url) {
|
|
2120
|
-
throw new Error(`Missing URL for HTTP MCP server ${name}`);
|
|
2121
|
-
}
|
|
2122
|
-
if (config.type === "sse") {
|
|
2123
|
-
logger7.warn(`Server "${name}": "sse" transport type is deprecated. Use "streamable-http" or "http" instead for the modern Streamable HTTP transport.`);
|
|
2124
|
-
}
|
|
2125
|
-
return new SSEClientTransport(new URL(config.url));
|
|
2126
|
-
}
|
|
2127
|
-
appendErrorMessage(connection, error) {
|
|
2128
|
-
const newError = connection.server.error ? `${connection.server.error}
|
|
2129
|
-
${error}` : error;
|
|
2130
|
-
connection.server.error = newError;
|
|
2131
|
-
}
|
|
2132
|
-
async fetchToolsList(serverName) {
|
|
1530
|
+
async fetchTools(name) {
|
|
1531
|
+
const conn = this.connections.get(name);
|
|
1532
|
+
if (!conn)
|
|
1533
|
+
return [];
|
|
2133
1534
|
try {
|
|
2134
|
-
const
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
}
|
|
2146
|
-
processedTool.inputSchema = this.applyToolCompatibility(tool.inputSchema);
|
|
2147
|
-
logger7.debug(`Applied tool compatibility for: ${tool.name} on server: ${serverName}`);
|
|
2148
|
-
} catch (error) {
|
|
2149
|
-
logger7.warn({ error, toolName: tool.name, serverName }, `Tool compatibility failed for ${tool.name} on ${serverName}`);
|
|
2150
|
-
}
|
|
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);
|
|
1541
|
+
try {
|
|
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;
|
|
2151
1546
|
}
|
|
2152
|
-
return processedTool;
|
|
2153
1547
|
});
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
logger7.info(`[${serverName}] ${tool.name}: ${tool.description}`);
|
|
2157
|
-
}
|
|
2158
|
-
return tools;
|
|
2159
|
-
} catch (error) {
|
|
2160
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName }, `Failed to fetch tools for ${serverName}`);
|
|
1548
|
+
} catch (e) {
|
|
1549
|
+
logger6.warn({ error: err(e), server: name }, `[MCP] Failed to fetch tools`);
|
|
2161
1550
|
return [];
|
|
2162
1551
|
}
|
|
2163
1552
|
}
|
|
2164
|
-
async
|
|
1553
|
+
async fetchResources(name) {
|
|
2165
1554
|
try {
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
}
|
|
2170
|
-
const response = await connection.client.listResources();
|
|
2171
|
-
return response?.resources || [];
|
|
2172
|
-
} catch (error) {
|
|
2173
|
-
logger7.warn({ error: error instanceof Error ? error.message : String(error), serverName }, `No resources found for ${serverName}`);
|
|
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`);
|
|
2174
1558
|
return [];
|
|
2175
1559
|
}
|
|
2176
1560
|
}
|
|
2177
|
-
async
|
|
1561
|
+
async fetchResourceTemplates(name) {
|
|
2178
1562
|
try {
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
}
|
|
2183
|
-
const response = await connection.client.listResourceTemplates();
|
|
2184
|
-
return response?.resourceTemplates || [];
|
|
2185
|
-
} catch (error) {
|
|
2186
|
-
logger7.warn({ error: error instanceof Error ? error.message : String(error), serverName }, `No resource templates found for ${serverName}`);
|
|
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`);
|
|
2187
1566
|
return [];
|
|
2188
1567
|
}
|
|
2189
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
|
+
}
|
|
2190
1595
|
getServers() {
|
|
2191
|
-
return Array.from(this.connections.values()).filter((
|
|
1596
|
+
return Array.from(this.connections.values()).filter((c) => !c.server.disabled).map((c) => c.server);
|
|
2192
1597
|
}
|
|
2193
1598
|
getProviderData() {
|
|
2194
1599
|
return this.mcpProvider;
|
|
2195
1600
|
}
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
const
|
|
2212
|
-
if (
|
|
2213
|
-
|
|
2214
|
-
}
|
|
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
|
+
}
|
|
1623
|
+
async callTool(serverName, toolName, args) {
|
|
1624
|
+
await this.ensureConnected(serverName);
|
|
1625
|
+
const conn = this.connections.get(serverName);
|
|
1626
|
+
if (!conn)
|
|
1627
|
+
throw new Error(`No connection: ${serverName}`);
|
|
1628
|
+
if (conn.server.disabled)
|
|
1629
|
+
throw new Error(`Server disabled: ${serverName}`);
|
|
1630
|
+
const config = JSON.parse(conn.server.config);
|
|
1631
|
+
const timeout = config.timeoutInMillis || DEFAULT_MCP_TIMEOUT_SECONDS;
|
|
1632
|
+
const result = await conn.client.callTool({ name: toolName, arguments: args }, undefined, { timeout });
|
|
1633
|
+
if (!result.content)
|
|
1634
|
+
throw new Error("Invalid tool result");
|
|
2215
1635
|
return result;
|
|
2216
1636
|
}
|
|
2217
1637
|
async readResource(serverName, uri) {
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
if (
|
|
2223
|
-
throw new Error(`Server
|
|
2224
|
-
}
|
|
2225
|
-
return await connection.client.readResource({ uri });
|
|
1638
|
+
await this.ensureConnected(serverName);
|
|
1639
|
+
const conn = this.connections.get(serverName);
|
|
1640
|
+
if (!conn)
|
|
1641
|
+
throw new Error(`No connection: ${serverName}`);
|
|
1642
|
+
if (conn.server.disabled)
|
|
1643
|
+
throw new Error(`Server disabled: ${serverName}`);
|
|
1644
|
+
return conn.client.readResource({ uri });
|
|
2226
1645
|
}
|
|
2227
1646
|
async restartConnection(serverName) {
|
|
2228
|
-
const
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
try {
|
|
2235
|
-
await this.deleteConnection(serverName);
|
|
2236
|
-
await this.initializeConnection(serverName, JSON.parse(config));
|
|
2237
|
-
logger7.info(`${serverName} MCP server connected`);
|
|
2238
|
-
} catch (error) {
|
|
2239
|
-
logger7.error({ error: error instanceof Error ? error.message : String(error), serverName }, `Failed to restart connection for ${serverName}`);
|
|
2240
|
-
throw new Error(`Failed to connect to ${serverName} MCP server`);
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
initializeToolCompatibility() {
|
|
2245
|
-
if (this.compatibilityInitialized)
|
|
2246
|
-
return;
|
|
2247
|
-
this.toolCompatibility = createMcpToolCompatibilitySync(this.runtime);
|
|
2248
|
-
this.compatibilityInitialized = true;
|
|
2249
|
-
if (this.toolCompatibility) {
|
|
2250
|
-
logger7.info(`Tool compatibility enabled`);
|
|
2251
|
-
} else {
|
|
2252
|
-
logger7.info(`No tool compatibility needed`);
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
applyToolCompatibility(toolSchema) {
|
|
2256
|
-
if (!this.compatibilityInitialized) {
|
|
2257
|
-
this.initializeToolCompatibility();
|
|
2258
|
-
}
|
|
2259
|
-
if (!this.toolCompatibility || !toolSchema) {
|
|
2260
|
-
return toolSchema;
|
|
2261
|
-
}
|
|
2262
|
-
try {
|
|
2263
|
-
return this.toolCompatibility.transformToolSchema(toolSchema);
|
|
2264
|
-
} catch (error) {
|
|
2265
|
-
logger7.warn({ error }, `Tool compatibility transformation failed`);
|
|
2266
|
-
return toolSchema;
|
|
2267
|
-
}
|
|
1647
|
+
const conn = this.connections.get(serverName);
|
|
1648
|
+
if (!conn)
|
|
1649
|
+
throw new Error(`No connection: ${serverName}`);
|
|
1650
|
+
const config = conn.server.config;
|
|
1651
|
+
await this.disconnect(serverName);
|
|
1652
|
+
await this.connect(serverName, JSON.parse(config));
|
|
2268
1653
|
}
|
|
2269
1654
|
}
|
|
2270
1655
|
|
|
@@ -2273,23 +1658,27 @@ var mcpPlugin = {
|
|
|
2273
1658
|
name: "mcp",
|
|
2274
1659
|
description: "Plugin for connecting to MCP (Model Context Protocol) servers",
|
|
2275
1660
|
init: async (_config, _runtime) => {
|
|
2276
|
-
|
|
1661
|
+
logger7.info("Initializing MCP plugin...");
|
|
2277
1662
|
},
|
|
2278
1663
|
services: [McpService],
|
|
2279
|
-
actions: [
|
|
1664
|
+
actions: [readResourceAction],
|
|
2280
1665
|
providers: [provider]
|
|
2281
1666
|
};
|
|
2282
1667
|
var src_default = mcpPlugin;
|
|
2283
1668
|
export {
|
|
1669
|
+
isMcpToolAction,
|
|
1670
|
+
getSchemaCache,
|
|
1671
|
+
getMcpToolActionsForServer,
|
|
2284
1672
|
detectModelProvider,
|
|
2285
1673
|
src_default as default,
|
|
2286
1674
|
createMcpToolCompatibilitySync,
|
|
2287
1675
|
createMcpToolCompatibility,
|
|
2288
|
-
|
|
1676
|
+
createMcpToolActions,
|
|
1677
|
+
createMcpToolAction,
|
|
2289
1678
|
ResourceSelectionSchema,
|
|
2290
1679
|
McpToolCompatibility,
|
|
2291
1680
|
McpService,
|
|
2292
|
-
|
|
1681
|
+
McpSchemaCache,
|
|
2293
1682
|
MCP_SERVICE_NAME,
|
|
2294
1683
|
MAX_RECONNECT_ATTEMPTS,
|
|
2295
1684
|
INITIAL_RETRY_DELAY,
|
|
@@ -2299,4 +1688,4 @@ export {
|
|
|
2299
1688
|
BACKOFF_MULTIPLIER
|
|
2300
1689
|
};
|
|
2301
1690
|
|
|
2302
|
-
//# debugId=
|
|
1691
|
+
//# debugId=C0ED6395DC1FA15F64756E2164756E21
|