@salesforce/sfdx-agent-harness-mastra 0.6.0 → 0.7.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/README.md +24 -0
- package/dist/mastra-harness.js +65 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -109,6 +109,30 @@ Mastra-internal pool / index / loaded-tools state is intentionally not on the ha
|
|
|
109
109
|
are `getStateStats()`, `clearState(threadId?)`, `clearAllState()`, and `cleanupNow()` on `ToolSearchHandle`, plus `id`
|
|
110
110
|
on both handles.
|
|
111
111
|
|
|
112
|
+
### MCP tool-event enrichment
|
|
113
|
+
|
|
114
|
+
The harness enriches every `tool-call`, `tool-result`, and `tool-approval-request` event with the originating MCP server
|
|
115
|
+
name and any annotations the server declared, so consumers can branch on tool hints or group by server without parsing
|
|
116
|
+
the namespaced tool name themselves. `serverName` is `undefined` for non-MCP tools (workspace tools, consumer-declared
|
|
117
|
+
tools); `annotations` is `undefined` when the server omitted the field (per the MCP spec).
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
for await (const event of result.eventStream) {
|
|
121
|
+
if (event.type === 'tool-approval-request') {
|
|
122
|
+
if (event.annotations?.readOnlyHint) {
|
|
123
|
+
// Auto-approve safe reads.
|
|
124
|
+
await session.approveToolCall(event.toolCall.toolCallId);
|
|
125
|
+
} else {
|
|
126
|
+
// Group/label by server when present, e.g. "sfdx wants to: deploy_metadata".
|
|
127
|
+
const approved = await promptUser(event.serverName, event.toolCall);
|
|
128
|
+
approved
|
|
129
|
+
? await session.approveToolCall(event.toolCall.toolCallId)
|
|
130
|
+
: await session.declineToolCall(event.toolCall.toolCallId);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
112
136
|
### Using LLM Gateway Express
|
|
113
137
|
|
|
114
138
|
```ts
|
package/dist/mastra-harness.js
CHANGED
|
@@ -168,6 +168,50 @@ async function listMarkdownFilesInDirectory(dir) {
|
|
|
168
168
|
}
|
|
169
169
|
return names.sort().map((name) => path.join(dir, name));
|
|
170
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Resolve the MCP server name and (optionally) annotations for a Mastra
|
|
173
|
+
* namespaced tool name (`${serverName}_${toolName}`). Returns `undefined` for
|
|
174
|
+
* tools that are not in any MCP server's catalog (consumer-declared client
|
|
175
|
+
* tools, workspace tools).
|
|
176
|
+
*
|
|
177
|
+
* Iterates `agentState.mcpServers` and prefix-matches rather than splitting on
|
|
178
|
+
* `_`, because server names themselves may contain underscores
|
|
179
|
+
* (`data_pipeline_query_records` → server `data_pipeline`, tool
|
|
180
|
+
* `query_records`). Mirrors the catalog access pattern used by
|
|
181
|
+
* {@link mapMastraToolsToMcpToolInfo}.
|
|
182
|
+
*
|
|
183
|
+
* Design debt: this walk runs per `tool-call` / `tool-result` /
|
|
184
|
+
* `tool-approval-request` event and reconstructs the tool→server linkage from
|
|
185
|
+
* the namespaced string. The cost is O(servers) on a typically-small map, but
|
|
186
|
+
* three call sites now share the same lookup, so the carry-on-the-record fix —
|
|
187
|
+
* attaching an `McpToolInfo` reference (with `serverName`) on the in-memory
|
|
188
|
+
* tool-call record from the moment Mastra namespaces the name — becomes
|
|
189
|
+
* proportionally more valuable. That fix touches `MastraEventAdapter`,
|
|
190
|
+
* `ToolCallInfo`, and the per-stream pipeline; out of scope for W-22693491.
|
|
191
|
+
* If a fourth per-event MCP-catalog lookup gets added, do the
|
|
192
|
+
* carry-on-the-record refactor first; if this walk shows up in profiles
|
|
193
|
+
* before that, build a `Map<namespacedToolName, { serverName, McpToolInfo }>`
|
|
194
|
+
* index keyed off discovery completion.
|
|
195
|
+
*/
|
|
196
|
+
/**
|
|
197
|
+
* Tool ChatEvents that carry the optional `serverName` + `annotations`
|
|
198
|
+
* enrichment fields. Centralizing the predicate lets `createEventStream`
|
|
199
|
+
* narrow once and apply the lookup uniformly across the three variants.
|
|
200
|
+
*/
|
|
201
|
+
function isEnrichableToolEvent(event) {
|
|
202
|
+
return event.type === 'tool-call' || event.type === 'tool-result' || event.type === 'tool-approval-request';
|
|
203
|
+
}
|
|
204
|
+
function lookupMcpToolMetadata(agentState, namespacedToolName) {
|
|
205
|
+
for (const [serverName, serverState] of agentState.mcpServers) {
|
|
206
|
+
if (!namespacedToolName.startsWith(`${serverName}_`))
|
|
207
|
+
continue;
|
|
208
|
+
const original = namespacedToolName.slice(serverName.length + 1);
|
|
209
|
+
const annotations = serverState.tools[original]
|
|
210
|
+
?.mcp?.annotations;
|
|
211
|
+
return { serverName, ...(annotations ? { annotations } : {}) };
|
|
212
|
+
}
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
171
215
|
/**
|
|
172
216
|
* Projects the namespaced Mastra `ToolAction` map for one MCP server into the
|
|
173
217
|
* SDK's harness-agnostic `McpToolInfo[]` shape.
|
|
@@ -452,6 +496,13 @@ export class MastraHarness {
|
|
|
452
496
|
const localFs = new LocalFilesystem({ basePath: projectRoot, allowedPaths });
|
|
453
497
|
const sandbox = new LocalSandbox({
|
|
454
498
|
workingDirectory: projectRoot,
|
|
499
|
+
// Forward the parent process env so spawned commands see HOME,
|
|
500
|
+
// locale, etc. (LocalSandbox's default forwards only PATH, which
|
|
501
|
+
// breaks tools like `sf org list` that resolve config from HOME).
|
|
502
|
+
// TODO: filter sensitive vars (e.g. ANTHROPIC_*, AWS creds) here
|
|
503
|
+
// if the harness ever runs alongside secrets it shouldn't leak
|
|
504
|
+
// into spawned tool subprocesses.
|
|
505
|
+
env: process.env,
|
|
455
506
|
});
|
|
456
507
|
mastraWorkspace = new Workspace({
|
|
457
508
|
filesystem: localFs,
|
|
@@ -978,7 +1029,7 @@ export class MastraHarness {
|
|
|
978
1029
|
agentState.activeRuns.set(threadId, { runId: output.runId, fullStream: output.fullStream });
|
|
979
1030
|
const adapter = new MastraEventAdapter(this.buses.getLogBus());
|
|
980
1031
|
return {
|
|
981
|
-
eventStream: this.createEventStream(output.fullStream, adapter),
|
|
1032
|
+
eventStream: this.createEventStream(output.fullStream, adapter, agentState),
|
|
982
1033
|
textStream: this.readableStreamToAsyncGenerator(output.textStream),
|
|
983
1034
|
};
|
|
984
1035
|
}
|
|
@@ -993,11 +1044,23 @@ export class MastraHarness {
|
|
|
993
1044
|
agentResourceId(agentId) {
|
|
994
1045
|
return `AGENT_INSTANCE:${agentId}`;
|
|
995
1046
|
}
|
|
996
|
-
async *createEventStream(fullStream, adapter) {
|
|
1047
|
+
async *createEventStream(fullStream, adapter, agentState) {
|
|
997
1048
|
for await (const chunk of this.readableStreamToAsyncGenerator(fullStream)) {
|
|
998
1049
|
const event = adapter.toChatEvent(chunk);
|
|
999
1050
|
if (!event)
|
|
1000
1051
|
continue;
|
|
1052
|
+
if (isEnrichableToolEvent(event)) {
|
|
1053
|
+
const toolName = event.type === 'tool-approval-request' ? event.toolCall.toolName : event.toolName;
|
|
1054
|
+
const metadata = lookupMcpToolMetadata(agentState, toolName);
|
|
1055
|
+
if (metadata) {
|
|
1056
|
+
yield {
|
|
1057
|
+
...event,
|
|
1058
|
+
serverName: metadata.serverName,
|
|
1059
|
+
...(metadata.annotations ? { annotations: metadata.annotations } : {}),
|
|
1060
|
+
};
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1001
1064
|
yield event;
|
|
1002
1065
|
}
|
|
1003
1066
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/sfdx-agent-harness-mastra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Mastra-backed AgentHarness implementation for @salesforce/sfdx-agent-sdk",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"@mastra/memory": "^1.19.0",
|
|
35
35
|
"@mastra/schema-compat": "^1.2.10",
|
|
36
36
|
"@salesforce/agentic-common": "0.4.0",
|
|
37
|
-
"@salesforce/llm-gateway-sdk": "0.
|
|
37
|
+
"@salesforce/llm-gateway-sdk": "0.5.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@salesforce/sfdx-agent-sdk": "0.
|
|
40
|
+
"@salesforce/sfdx-agent-sdk": "0.8.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@eslint/js": "^10.0.1",
|
|
44
|
-
"@salesforce/sfdx-agent-sdk": "0.
|
|
44
|
+
"@salesforce/sfdx-agent-sdk": "0.8.0",
|
|
45
45
|
"@types/node": "^22.19.17",
|
|
46
46
|
"@vitest/coverage-istanbul": "^4.1.7",
|
|
47
47
|
"@vitest/eslint-plugin": "^1.6.17",
|