@agentproto/adapter-mastra-agent 0.5.5 → 0.6.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.
@@ -17,6 +17,8 @@ import { ProviderHistoryCompat } from '@mastra/core/processors';
17
17
  import { AgentController } from '@mastra/core/agent-controller';
18
18
  import { Workspace } from '@mastra/core/workspace';
19
19
  import { createNotificationInboxTool } from '@mastra/core/notifications';
20
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
21
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
20
22
  import { SignalProvider } from '@mastra/core/signals';
21
23
  import { PROTOCOL_VERSION, ndJsonStream, AgentSideConnection } from '@agentclientprotocol/sdk';
22
24
  import { Writable, Readable } from 'stream';
@@ -787,6 +789,61 @@ function resolveMastraModel(ref, env = process.env) {
787
789
  }
788
790
  return modelId;
789
791
  }
792
+ async function connectDaemonMcpClient(opts = {}) {
793
+ const endpoint = await discoverDaemonEndpoint(opts);
794
+ if (!endpoint) throw new DaemonNotFoundError();
795
+ const env = opts.env ?? process.env;
796
+ const url = new URL(`${endpoint.url}/mcp`);
797
+ if (env.AGENTPROTO_SESSION_ID) url.searchParams.set("callerSessionId", env.AGENTPROTO_SESSION_ID);
798
+ const client = new Client({ name: "agentproto-mastra-agent", version: "0.1.0" }, { capabilities: {} });
799
+ const transport = new StreamableHTTPClientTransport(url, {
800
+ ...endpoint.token ? { requestInit: { headers: { authorization: `Bearer ${endpoint.token}` } } } : {}
801
+ });
802
+ await client.connect(transport);
803
+ return client;
804
+ }
805
+ async function listDaemonMcpTools(client) {
806
+ const { tools } = await client.listTools();
807
+ return tools.map((t) => ({
808
+ name: t.name,
809
+ ...t.description !== void 0 ? { description: t.description } : {},
810
+ inputSchema: t.inputSchema ?? { type: "object" }
811
+ }));
812
+ }
813
+ function injectAppId(toolName, args, appId) {
814
+ if (!appId) return args;
815
+ if (!toolName.startsWith("app_")) return args;
816
+ if (args.appId !== void 0) return args;
817
+ return { ...args, appId };
818
+ }
819
+ function relaxAppIdRequirement(inputSchema, toolName, appId) {
820
+ if (!appId || !toolName.startsWith("app_")) return inputSchema;
821
+ if (!Array.isArray(inputSchema.required) || !inputSchema.required.includes("appId")) return inputSchema;
822
+ return { ...inputSchema, required: inputSchema.required.filter((id) => id !== "appId") };
823
+ }
824
+ function extractText(content) {
825
+ if (!Array.isArray(content)) return "";
826
+ return content.filter(
827
+ (block) => !!block && typeof block === "object" && block.type === "text" && typeof block.text === "string"
828
+ ).map((block) => block.text).join("\n");
829
+ }
830
+ function makeDaemonMcpProxyTool(def, opts) {
831
+ const config = {
832
+ id: def.name,
833
+ description: def.description ?? `Daemon tool "${def.name}", proxied via the daemon's MCP gateway.`,
834
+ inputSchema: relaxAppIdRequirement(def.inputSchema, def.name, opts.appId),
835
+ execute: async (input) => {
836
+ const client = await opts.getClient();
837
+ const args = injectAppId(def.name, input ?? {}, opts.appId);
838
+ const result = await client.callTool({ name: def.name, arguments: args });
839
+ if (result.isError) {
840
+ throw new Error(`daemon tool "${def.name}" failed: ${extractText(result.content) || "(no error detail)"}`);
841
+ }
842
+ return result.structuredContent ?? extractText(result.content);
843
+ }
844
+ };
845
+ return createTool(config);
846
+ }
790
847
  var WATCHED_EVENT_KINDS = [
791
848
  "turn-end",
792
849
  "error",
@@ -1431,6 +1488,26 @@ function makeAgentFactory(opts = {}) {
1431
1488
  allowExec: opts.allowExec,
1432
1489
  extraTools: opts.extraTools
1433
1490
  });
1491
+ const modesEnabled = opts.modes !== false && !envFlag(process.env.AGENTPROTO_MASTRA_NO_MODES);
1492
+ const daemonClient = new DaemonClient({ cwd });
1493
+ const daemonSubAgentTools = modesEnabled ? makeDaemonTools({ client: daemonClient }) : {};
1494
+ let daemonMcpClient;
1495
+ let daemonMcpToolDefsPromise;
1496
+ const resolvedDaemonMcpTools = {};
1497
+ const appId = opts.appId ?? process.env.AGENTPROTO_APP_ID;
1498
+ async function getDaemonMcpToolDefs() {
1499
+ if (!daemonMcpToolDefsPromise) {
1500
+ daemonMcpToolDefsPromise = (async () => {
1501
+ try {
1502
+ daemonMcpClient = opts.daemonMcp?.client ?? await connectDaemonMcpClient({ cwd, ...opts.daemonMcp?.discoverOptions });
1503
+ return await listDaemonMcpTools(daemonMcpClient);
1504
+ } catch {
1505
+ return [];
1506
+ }
1507
+ })();
1508
+ }
1509
+ return daemonMcpToolDefsPromise;
1510
+ }
1434
1511
  const store = buildSqliteStore();
1435
1512
  let memory;
1436
1513
  const historyCompat = new ProviderHistoryCompat({
@@ -1439,21 +1516,38 @@ function makeAgentFactory(opts = {}) {
1439
1516
  const { agent } = await buildMastraAgent(handle, {
1440
1517
  inputProcessors: [historyCompat],
1441
1518
  resolveModel: (ref) => resolveMastraModel(ref),
1442
- // Match each declared tool ref against the workspace toolset by id. A
1443
- // ref with no matching executor still resolves to a stub that fails
1444
- // fast and clearly on call rather than being dropped: a dropped ref
1445
- // leaves the model unable to see it at all, and (if the model still
1446
- // tries the name from AGENT.md prose) surfaces as an opaque provider
1447
- // NoSuchToolError this adapter's ACP layer silently swallows (see
1448
- // tool-call-map.ts), which is how a declared-but-unwired tool used to
1449
- // hang a turn with zero recorded tool calls instead of failing fast.
1450
- resolveTool: (ref) => {
1519
+ // Match each declared tool ref, in order: the workspace toolset, the
1520
+ // curated daemon sub-agent tools, then (modes-on only) the generic
1521
+ // daemon MCP proxy ONLY for a ref an AGENT.md actually declares,
1522
+ // which is what keeps the proxy an allowlist rather than a blanket
1523
+ // grant of the daemon's whole toolset. A ref matching none of those
1524
+ // still resolves to a stub that fails fast and clearly on call —
1525
+ // rather than being dropped: a dropped ref leaves the model unable to
1526
+ // see it at all, and (if the model still tries the name from AGENT.md
1527
+ // prose) surfaces as an opaque provider NoSuchToolError this adapter's
1528
+ // ACP layer silently swallows (see tool-call-map.ts), which is how a
1529
+ // declared-but-unwired tool used to hang a turn with zero recorded
1530
+ // tool calls instead of failing fast.
1531
+ resolveTool: async (ref) => {
1451
1532
  const id = toolRefId(ref);
1452
1533
  if (!id) return void 0;
1453
- const tool = workspaceTools[id];
1454
- if (tool) return { name: id, tool };
1534
+ const workspaceTool = workspaceTools[id];
1535
+ if (workspaceTool) return { name: id, tool: workspaceTool };
1536
+ const daemonSubAgentTool = daemonSubAgentTools[id];
1537
+ if (daemonSubAgentTool) return { name: id, tool: daemonSubAgentTool };
1538
+ if (modesEnabled) {
1539
+ const def = (await getDaemonMcpToolDefs()).find((d) => d.name === id);
1540
+ if (def) {
1541
+ const tool = makeDaemonMcpProxyTool(def, {
1542
+ getClient: async () => daemonMcpClient,
1543
+ ...appId !== void 0 ? { appId } : {}
1544
+ });
1545
+ resolvedDaemonMcpTools[id] = tool;
1546
+ return { name: id, tool };
1547
+ }
1548
+ }
1455
1549
  console.warn(
1456
- `[@agentproto/adapter-mastra-agent] agent '${handle.id}' declares tool '${id}' but no executor is wired for it in this adapter's workspace toolset \u2014 calls to it will fail immediately instead of hanging. Wire it in workspace-tools.ts or pass it via extraTools.`
1550
+ `[@agentproto/adapter-mastra-agent] agent '${handle.id}' declares tool '${id}' but no executor is wired for it (not in the workspace toolset, the daemon sub-agent tools, or the daemon's own MCP tool list) \u2014 calls to it will fail immediately instead of hanging. Wire it in workspace-tools.ts, pass it via extraTools, or expose it from the daemon.`
1457
1551
  );
1458
1552
  return { name: id, tool: makeUnwiredToolStub(id) };
1459
1553
  },
@@ -1461,12 +1555,10 @@ function makeAgentFactory(opts = {}) {
1461
1555
  // The markdown body is the agent's primary system prompt (AIP-42).
1462
1556
  body
1463
1557
  });
1464
- const modesEnabled = opts.modes !== false && !envFlag(process.env.AGENTPROTO_MASTRA_NO_MODES);
1465
1558
  const { modes, defaultModeId } = modesEnabled ? resolveModes(body) : { modes: [{ id: "main", metadata: { default: true } }], defaultModeId: "main" };
1466
1559
  let tools = workspaceTools;
1467
1560
  if (modesEnabled) {
1468
- const daemonClient = new DaemonClient({ cwd });
1469
- tools = { ...workspaceTools, ...makeDaemonTools({ client: daemonClient }) };
1561
+ tools = { ...workspaceTools, ...daemonSubAgentTools, ...resolvedDaemonMcpTools };
1470
1562
  const signalProvider = new AgentprotoSignalProvider({
1471
1563
  client: daemonClient,
1472
1564
  stateEmitter: new DaemonStateEmitter({ client: daemonClient, cwd })
@@ -2091,5 +2183,5 @@ function runAcpOverStdio(buildController) {
2091
2183
  }
2092
2184
 
2093
2185
  export { DEFAULT_MODEL, DEFAULT_MODEL_CATALOG, DEFAULT_TOOL_IDS, DISABLED_BUILTIN_TOOL_IDS, DaemonClient, DaemonHttpError, DaemonNotFoundError, MastraAcpAgent, buildSqliteMemory, buildSqliteStore, createEventMapper, defaultAgentManifest, discoverDaemonEndpoint, makeAgentFactory, makeDaemonTools, makeWorkspaceTools, messageText, modelRefToString, promptContent, promptText, providerOf, resolveInCwd, resolveMastraModel, resolveMemoryDbPath, runAcpOverStdio, toolCallTitle, toolKindFor };
2094
- //# sourceMappingURL=chunk-6ILIPWNM.mjs.map
2095
- //# sourceMappingURL=chunk-6ILIPWNM.mjs.map
2186
+ //# sourceMappingURL=chunk-BOEMDDBC.mjs.map
2187
+ //# sourceMappingURL=chunk-BOEMDDBC.mjs.map