@elizaos/plugin-mcp 2.0.0-beta.1 → 2.0.11-beta.7

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.
@@ -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,MA4JvB,CAAC"}
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"}
@@ -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,MAWhB,CAAC;AAEF,eAAe,SAAS,CAAC;AAEzB,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
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"}
@@ -534,15 +534,15 @@ var resourceAnalysisTemplate = `{{{mcpProvider.text}}}
534
534
 
535
535
  # Prompt
536
536
 
537
- You are a helpful assistant responding to a user's request. You've just accessed the resource "{{{uri}}}" to help answer this request.
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
- You are an intelligent assistant helping select the right resource to address a user's request.
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
- 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.
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 (!server || server.status !== "connected") {
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: { ...state, values: { ...state.values, mcpProvider } },
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, state),
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(serverConfigs));
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(serverConfigs).map(async ([name, config]) => {
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 connection.transport.close();
2142
- await connection.client.close();
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
- const parsed = Number.parseInt(raw, 10);
2372
- if (!Number.isFinite(parsed)) {
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
- const query = url.searchParams.get("q") ?? "";
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
- if (!serverName.trim()) {
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(serverName);
2472
+ const details = await getMcpServerDetails(normalizedServerName);
2405
2473
  if (!details) {
2406
- error(res, `MCP server "${serverName}" not found`, 404);
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=BAE4A5B3EC26EDA264756E2164756E21
2649
+ //# debugId=52D6DFF1F6887D8364756E2164756E21