@elizaos/plugin-mcp 2.0.0-beta.1 → 2.0.3-beta.3
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/README.md +55 -226
- package/dist/cjs/index.cjs +106 -28
- package/dist/cjs/index.js.map +11 -11
- package/dist/node/actions/mcp.d.ts.map +1 -1
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +106 -28
- package/dist/node/index.js.map +11 -11
- package/dist/node/prompts.d.ts +8 -8
- package/dist/node/prompts.d.ts.map +1 -1
- package/dist/node/routes-mcp.d.ts.map +1 -1
- package/dist/node/service.d.ts +1 -0
- package/dist/node/service.d.ts.map +1 -1
- package/dist/node/types.d.ts +0 -4
- package/dist/node/types.d.ts.map +1 -1
- package/dist/node/utils/schemas.d.ts +0 -4
- package/dist/node/utils/schemas.d.ts.map +1 -1
- package/dist/node/utils/selection.d.ts.map +1 -1
- package/package.json +19 -5
- package/registry-entry.json +44 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../../src/actions/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAIX,KAAK,cAAc,EAEnB,KAAK,MAAM,EAGZ,MAAM,eAAe,CAAC;AAqBvB,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC,MAAM,MAAM,KAAK,GAAG,WAAW,GAAG,eAAe,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;AAmU1F,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjD,KAAK,GAAG,IAAI,CAId;AAED,eAAO,MAAM,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../../src/actions/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAIX,KAAK,cAAc,EAEnB,KAAK,MAAM,EAGZ,MAAM,eAAe,CAAC;AAqBvB,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC,MAAM,MAAM,KAAK,GAAG,WAAW,GAAG,eAAe,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;AAmU1F,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjD,KAAK,GAAG,IAAI,CAId;AAED,eAAO,MAAM,SAAS,EAAE,MA6JvB,CAAC"}
|
package/dist/node/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,MAAM,EAEZ,MAAM,eAAe,CAAC;AAoBvB,QAAA,MAAM,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,MAAM,EAEZ,MAAM,eAAe,CAAC;AAoBvB,QAAA,MAAM,SAAS,EAAE,MAgBhB,CAAC;AAEF,eAAe,SAAS,CAAC;AAEzB,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/node/index.js
CHANGED
|
@@ -534,15 +534,15 @@ var resourceAnalysisTemplate = `{{{mcpProvider.text}}}
|
|
|
534
534
|
|
|
535
535
|
# Prompt
|
|
536
536
|
|
|
537
|
-
|
|
537
|
+
Respond to the user's request using the resource "{{{uri}}}".
|
|
538
538
|
|
|
539
539
|
Original user request: "{{{userMessage}}}"
|
|
540
540
|
|
|
541
541
|
Resource metadata:
|
|
542
|
-
{{{resourceMeta}}
|
|
542
|
+
{{{resourceMeta}}}
|
|
543
543
|
|
|
544
544
|
Resource content:
|
|
545
|
-
{{{resourceContent}}
|
|
545
|
+
{{{resourceContent}}}
|
|
546
546
|
|
|
547
547
|
Instructions:
|
|
548
548
|
1. Analyze how well the resource's content addresses the user's specific question or need
|
|
@@ -559,7 +559,7 @@ var resourceSelectionTemplate = `{{{mcpProvider.text}}}
|
|
|
559
559
|
|
|
560
560
|
# Prompt
|
|
561
561
|
|
|
562
|
-
|
|
562
|
+
Select the right resource to address the user's request.
|
|
563
563
|
|
|
564
564
|
CRITICAL INSTRUCTIONS:
|
|
565
565
|
1. You MUST specify both a valid serverName AND uri from the list above
|
|
@@ -602,7 +602,7 @@ var toolReasoningTemplate = `{{{mcpProvider.text}}}
|
|
|
602
602
|
|
|
603
603
|
# Prompt
|
|
604
604
|
|
|
605
|
-
|
|
605
|
+
Synthesize the result from the "{{{toolName}}}" tool into a response to the user's request.
|
|
606
606
|
|
|
607
607
|
Original user request: "{{{userMessage}}}"
|
|
608
608
|
|
|
@@ -749,13 +749,11 @@ var ResourceSelectionSchema = {
|
|
|
749
749
|
properties: {
|
|
750
750
|
serverName: {
|
|
751
751
|
type: "string",
|
|
752
|
-
minLength: 1
|
|
753
|
-
errorMessage: "serverName must not be empty"
|
|
752
|
+
minLength: 1
|
|
754
753
|
},
|
|
755
754
|
uri: {
|
|
756
755
|
type: "string",
|
|
757
|
-
minLength: 1
|
|
758
|
-
errorMessage: "uri must not be empty"
|
|
756
|
+
minLength: 1
|
|
759
757
|
},
|
|
760
758
|
reasoning: {
|
|
761
759
|
type: "string"
|
|
@@ -1159,13 +1157,11 @@ var toolSelectionNameSchema = {
|
|
|
1159
1157
|
properties: {
|
|
1160
1158
|
serverName: {
|
|
1161
1159
|
type: "string",
|
|
1162
|
-
minLength: 1
|
|
1163
|
-
errorMessage: "serverName must not be empty"
|
|
1160
|
+
minLength: 1
|
|
1164
1161
|
},
|
|
1165
1162
|
toolName: {
|
|
1166
1163
|
type: "string",
|
|
1167
|
-
minLength: 1
|
|
1168
|
-
errorMessage: "toolName must not be empty"
|
|
1164
|
+
minLength: 1
|
|
1169
1165
|
},
|
|
1170
1166
|
reasoning: {
|
|
1171
1167
|
type: "string"
|
|
@@ -1211,7 +1207,7 @@ function validateToolSelectionName(parsed, state) {
|
|
|
1211
1207
|
const data = basicResult.data;
|
|
1212
1208
|
const mcpData = state.values.mcp ?? {};
|
|
1213
1209
|
const server = mcpData[data.serverName];
|
|
1214
|
-
if (
|
|
1210
|
+
if (server?.status !== "connected") {
|
|
1215
1211
|
return {
|
|
1216
1212
|
success: false,
|
|
1217
1213
|
error: `Server "${data.serverName}" not found or not connected`
|
|
@@ -1384,8 +1380,16 @@ async function createToolSelectionName({
|
|
|
1384
1380
|
callback,
|
|
1385
1381
|
mcpProvider
|
|
1386
1382
|
}) {
|
|
1383
|
+
const stateWithMcp = {
|
|
1384
|
+
...state,
|
|
1385
|
+
values: {
|
|
1386
|
+
...state.values,
|
|
1387
|
+
mcp: state.values.mcp ?? mcpProvider.data.mcp,
|
|
1388
|
+
mcpProvider
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1387
1391
|
const toolSelectionPrompt = composePromptFromState3({
|
|
1388
|
-
state:
|
|
1392
|
+
state: stateWithMcp,
|
|
1389
1393
|
template: toolSelectionNameTemplate
|
|
1390
1394
|
});
|
|
1391
1395
|
const toolSelectionName = await runtime.useModel(ModelType4.TEXT_LARGE, {
|
|
@@ -1394,10 +1398,10 @@ async function createToolSelectionName({
|
|
|
1394
1398
|
return await withModelRetry({
|
|
1395
1399
|
runtime,
|
|
1396
1400
|
message,
|
|
1397
|
-
state,
|
|
1401
|
+
state: stateWithMcp,
|
|
1398
1402
|
callback,
|
|
1399
1403
|
input: toolSelectionName,
|
|
1400
|
-
validationFn: (parsed) => validateToolSelectionName(parsed,
|
|
1404
|
+
validationFn: (parsed) => validateToolSelectionName(parsed, stateWithMcp),
|
|
1401
1405
|
createFeedbackPromptFn: (originalResponse, errorMessage, composedState, userMessage) => createToolSelectionFeedbackPrompt(typeof originalResponse === "string" ? originalResponse : JSON.stringify(originalResponse), errorMessage, composedState, userMessage),
|
|
1402
1406
|
failureMsg: "I'm having trouble figuring out the best way to help with your request."
|
|
1403
1407
|
});
|
|
@@ -1884,6 +1888,7 @@ var provider = {
|
|
|
1884
1888
|
|
|
1885
1889
|
// src/service.ts
|
|
1886
1890
|
import { logger as logger2, Service } from "@elizaos/core";
|
|
1891
|
+
import { validateMcpServerConfig } from "@elizaos/security/mcp-server-config";
|
|
1887
1892
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1888
1893
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
1889
1894
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
@@ -1984,15 +1989,28 @@ class McpService extends Service {
|
|
|
1984
1989
|
}
|
|
1985
1990
|
return;
|
|
1986
1991
|
}
|
|
1992
|
+
async filterValidatedServerConfigs(serverConfigs) {
|
|
1993
|
+
const validated = {};
|
|
1994
|
+
for (const [name, config] of Object.entries(serverConfigs)) {
|
|
1995
|
+
const rejection = await validateMcpServerConfig(config);
|
|
1996
|
+
if (rejection) {
|
|
1997
|
+
logger2.error({ server: name, rejection }, "Skipping MCP server with invalid or unsafe config");
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
validated[name] = config;
|
|
2001
|
+
}
|
|
2002
|
+
return validated;
|
|
2003
|
+
}
|
|
1987
2004
|
async updateServerConnections(serverConfigs) {
|
|
2005
|
+
const safeConfigs = await this.filterValidatedServerConfigs(serverConfigs);
|
|
1988
2006
|
const currentNames = new Set(this.connections.keys());
|
|
1989
|
-
const newNames = new Set(Object.keys(
|
|
2007
|
+
const newNames = new Set(Object.keys(safeConfigs));
|
|
1990
2008
|
for (const name of currentNames) {
|
|
1991
2009
|
if (!newNames.has(name)) {
|
|
1992
2010
|
await this.deleteConnection(name);
|
|
1993
2011
|
}
|
|
1994
2012
|
}
|
|
1995
|
-
const connectionPromises = Object.entries(
|
|
2013
|
+
const connectionPromises = Object.entries(safeConfigs).map(async ([name, config]) => {
|
|
1996
2014
|
const currentConnection = this.connections.get(name);
|
|
1997
2015
|
if (!currentConnection) {
|
|
1998
2016
|
await this.initializeConnection(name, config);
|
|
@@ -2138,9 +2156,16 @@ class McpService extends Service {
|
|
|
2138
2156
|
async deleteConnection(name) {
|
|
2139
2157
|
const connection = this.connections.get(name);
|
|
2140
2158
|
if (connection) {
|
|
2141
|
-
await
|
|
2142
|
-
|
|
2159
|
+
const closeResults = await Promise.allSettled([
|
|
2160
|
+
connection.transport.close(),
|
|
2161
|
+
connection.client.close()
|
|
2162
|
+
]);
|
|
2143
2163
|
this.connections.delete(name);
|
|
2164
|
+
for (const result of closeResults) {
|
|
2165
|
+
if (result.status === "rejected") {
|
|
2166
|
+
logger2.warn({ error: result.reason, serverName: name }, `Failed to close MCP connection resource for "${name}"`);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2144
2169
|
}
|
|
2145
2170
|
const state = this.connectionStates.get(name);
|
|
2146
2171
|
if (state) {
|
|
@@ -2158,6 +2183,17 @@ class McpService extends Service {
|
|
|
2158
2183
|
if (!config.command) {
|
|
2159
2184
|
throw new Error(`Missing command for stdio MCP server ${name}`);
|
|
2160
2185
|
}
|
|
2186
|
+
const rejection = await validateMcpServerConfig({
|
|
2187
|
+
type: "stdio",
|
|
2188
|
+
command: config.command,
|
|
2189
|
+
args: config.args,
|
|
2190
|
+
env: config.env,
|
|
2191
|
+
cwd: config.cwd,
|
|
2192
|
+
timeoutInMillis: config.timeoutInMillis
|
|
2193
|
+
});
|
|
2194
|
+
if (rejection) {
|
|
2195
|
+
throw new Error(`MCP stdio server "${name}" rejected at spawn: ${rejection}`);
|
|
2196
|
+
}
|
|
2161
2197
|
return new StdioClientTransport({
|
|
2162
2198
|
command: config.command,
|
|
2163
2199
|
args: config.args ? [...config.args] : undefined,
|
|
@@ -2173,6 +2209,13 @@ class McpService extends Service {
|
|
|
2173
2209
|
if (!config.url) {
|
|
2174
2210
|
throw new Error(`Missing URL for HTTP MCP server ${name}`);
|
|
2175
2211
|
}
|
|
2212
|
+
const rejection = await validateMcpServerConfig({
|
|
2213
|
+
type: config.type,
|
|
2214
|
+
url: config.url
|
|
2215
|
+
});
|
|
2216
|
+
if (rejection) {
|
|
2217
|
+
throw new Error(`MCP remote server "${name}" rejected at connect: ${rejection}`);
|
|
2218
|
+
}
|
|
2176
2219
|
return new SSEClientTransport(new URL(config.url));
|
|
2177
2220
|
}
|
|
2178
2221
|
appendErrorMessage(connection, error) {
|
|
@@ -2364,12 +2407,17 @@ async function getMcpServerDetails(name) {
|
|
|
2364
2407
|
}
|
|
2365
2408
|
|
|
2366
2409
|
// src/routes-mcp.ts
|
|
2410
|
+
var MCP_MARKETPLACE_QUERY_MAX_LENGTH = 200;
|
|
2411
|
+
var MCP_MARKETPLACE_SERVER_NAME_MAX_LENGTH = 200;
|
|
2367
2412
|
function parseClampedInteger(value, options = {}) {
|
|
2368
2413
|
const raw = value == null ? "" : value.trim();
|
|
2369
2414
|
if (!raw)
|
|
2370
2415
|
return Number.isFinite(options.fallback) ? options.fallback : undefined;
|
|
2371
|
-
|
|
2372
|
-
|
|
2416
|
+
if (!/^[+-]?\d+$/.test(raw)) {
|
|
2417
|
+
return Number.isFinite(options.fallback) ? options.fallback : undefined;
|
|
2418
|
+
}
|
|
2419
|
+
const parsed = Number(raw);
|
|
2420
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
2373
2421
|
return Number.isFinite(options.fallback) ? options.fallback : undefined;
|
|
2374
2422
|
}
|
|
2375
2423
|
if (options.min !== undefined && parsed < options.min)
|
|
@@ -2378,10 +2426,23 @@ function parseClampedInteger(value, options = {}) {
|
|
|
2378
2426
|
return options.max;
|
|
2379
2427
|
return parsed;
|
|
2380
2428
|
}
|
|
2429
|
+
function normalizeBoundedString(value, maxLength, label) {
|
|
2430
|
+
const normalized = value.trim();
|
|
2431
|
+
if (normalized.length > maxLength) {
|
|
2432
|
+
throw new RangeError(`${label} must be ${maxLength} characters or fewer`);
|
|
2433
|
+
}
|
|
2434
|
+
return normalized;
|
|
2435
|
+
}
|
|
2381
2436
|
async function handleMcpRoutes(ctx) {
|
|
2382
2437
|
const { req, res, method, pathname, url, state, json, error, readJsonBody } = ctx;
|
|
2383
2438
|
if (method === "GET" && pathname === "/api/mcp/marketplace/search") {
|
|
2384
|
-
|
|
2439
|
+
let query;
|
|
2440
|
+
try {
|
|
2441
|
+
query = normalizeBoundedString(url.searchParams.get("q") ?? "", MCP_MARKETPLACE_QUERY_MAX_LENGTH, "Marketplace search query");
|
|
2442
|
+
} catch (err) {
|
|
2443
|
+
error(res, err instanceof Error ? err.message : String(err), 400);
|
|
2444
|
+
return true;
|
|
2445
|
+
}
|
|
2385
2446
|
const limitStr = url.searchParams.get("limit");
|
|
2386
2447
|
const limit = limitStr ? parseClampedInteger(limitStr, { min: 1, max: 50, fallback: 30 }) : 30;
|
|
2387
2448
|
try {
|
|
@@ -2396,14 +2457,21 @@ async function handleMcpRoutes(ctx) {
|
|
|
2396
2457
|
const serverName = ctx.decodePathComponent(pathname.slice("/api/mcp/marketplace/details/".length), res, "server name");
|
|
2397
2458
|
if (serverName === null)
|
|
2398
2459
|
return true;
|
|
2399
|
-
|
|
2460
|
+
let normalizedServerName;
|
|
2461
|
+
try {
|
|
2462
|
+
normalizedServerName = normalizeBoundedString(serverName, MCP_MARKETPLACE_SERVER_NAME_MAX_LENGTH, "Server name");
|
|
2463
|
+
} catch (err) {
|
|
2464
|
+
error(res, err instanceof Error ? err.message : String(err), 400);
|
|
2465
|
+
return true;
|
|
2466
|
+
}
|
|
2467
|
+
if (!normalizedServerName) {
|
|
2400
2468
|
error(res, "Server name is required", 400);
|
|
2401
2469
|
return true;
|
|
2402
2470
|
}
|
|
2403
2471
|
try {
|
|
2404
|
-
const details = await getMcpServerDetails(
|
|
2472
|
+
const details = await getMcpServerDetails(normalizedServerName);
|
|
2405
2473
|
if (!details) {
|
|
2406
|
-
error(res, `MCP server "${
|
|
2474
|
+
error(res, `MCP server "${normalizedServerName}" not found`, 404);
|
|
2407
2475
|
return true;
|
|
2408
2476
|
}
|
|
2409
2477
|
json(res, { ok: true, server: details });
|
|
@@ -2491,6 +2559,12 @@ async function handleMcpRoutes(ctx) {
|
|
|
2491
2559
|
error(res, "servers must be a JSON object", 400);
|
|
2492
2560
|
return true;
|
|
2493
2561
|
}
|
|
2562
|
+
for (const serverName of Object.keys(body.servers)) {
|
|
2563
|
+
if (ctx.isBlockedObjectKey(serverName)) {
|
|
2564
|
+
error(res, 'Invalid server name: "__proto__", "constructor", and "prototype" are reserved', 400);
|
|
2565
|
+
return true;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2494
2568
|
const mcpRejection = await ctx.resolveMcpServersRejection(body.servers);
|
|
2495
2569
|
if (mcpRejection) {
|
|
2496
2570
|
error(res, mcpRejection, 400);
|
|
@@ -2558,6 +2632,10 @@ var mcpPlugin = {
|
|
|
2558
2632
|
init: async (_config, _runtime) => {
|
|
2559
2633
|
logger4.info("Initializing MCP plugin...");
|
|
2560
2634
|
},
|
|
2635
|
+
async dispose(runtime) {
|
|
2636
|
+
const svc = runtime.getService(McpService.serviceType);
|
|
2637
|
+
await svc?.stop();
|
|
2638
|
+
},
|
|
2561
2639
|
services: [McpService],
|
|
2562
2640
|
actions: [...promoteSubactionsToActions(withMcpContext(mcpAction))],
|
|
2563
2641
|
providers: [provider]
|
|
@@ -2568,4 +2646,4 @@ export {
|
|
|
2568
2646
|
src_default as default
|
|
2569
2647
|
};
|
|
2570
2648
|
|
|
2571
|
-
//# debugId=
|
|
2649
|
+
//# debugId=52D6DFF1F6887D8364756E2164756E21
|