@salesforce/sfdx-agent-harness-mastra 0.97.0 → 0.99.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/CHANGELOG.md +14 -0
- package/README.md +4 -4
- package/dist/mastra-harness.js +2 -2
- package/dist/mcp-config-mapper.d.ts +3 -1
- package/dist/mcp-config-mapper.js +59 -5
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable changes to `@salesforce/sfdx-agent-harness-mastra` are documented in this file.
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
+
## [0.99.0] - 2026-10-06
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
- **agent-sdk,harness-claude,harness-mastra,harness-openai**: add 'application-policy' kind to ToolDeclineReason @W-24368893@ ([#854](https://github.com/forcedotcom/agentic-dx/pull/854))
|
|
10
|
+
|
|
11
|
+
### Chores
|
|
12
|
+
- **deps-dev**: bump eslint-plugin-n from 18.3.0 to 18.4.0 in the eslint group across 1 directory ([#859](https://github.com/forcedotcom/agentic-dx/pull/859))
|
|
13
|
+
- **deps-dev**: bump the vitest group with 2 updates ([#860](https://github.com/forcedotcom/agentic-dx/pull/860))
|
|
14
|
+
|
|
15
|
+
## [0.98.0] - 2026-10-05
|
|
16
|
+
|
|
17
|
+
### Fixes
|
|
18
|
+
- **agent-sdk,harness-claude,harness-mastra,harness-openai**: rewrite MCP clientInfo.name to the running agent's name @W-24101379@ ([#843](https://github.com/forcedotcom/agentic-dx/pull/843))
|
|
19
|
+
|
|
6
20
|
## [0.97.0] - 2026-10-05
|
|
7
21
|
|
|
8
22
|
### Features
|
package/README.md
CHANGED
|
@@ -250,10 +250,10 @@ Consumer-executed tools (`AgentConfig.tools`) always bypass the gate — their e
|
|
|
250
250
|
via `submitToolResult`. Set `StreamOptions.batchApprovals: true` to surface parallel `require-approval` requests on one
|
|
251
251
|
stream for a batch-approval UI.
|
|
252
252
|
|
|
253
|
-
With `@mastra/core` 1.62.0,
|
|
254
|
-
emits one native `tool-output-denied` chunk; the adapter maps it to one
|
|
255
|
-
exact message the model sees. Automatic policy denies use
|
|
256
|
-
Mastra's `Tool call was not approved by the user` default.
|
|
253
|
+
With `@mastra/core` 1.62.0, a typed-reason decline (either `ToolDeclineReason` kind) is passed through
|
|
254
|
+
`agent.declineToolCall({ reason })`. Mastra emits one native `tool-output-denied` chunk; the adapter maps it to one
|
|
255
|
+
`tool-result(isError=true)` carrying the same exact message the model sees. Automatic policy denies use
|
|
256
|
+
`Denied by tool policy.`; reasonless consumer declines retain Mastra's `Tool call was not approved by the user` default.
|
|
257
257
|
|
|
258
258
|
### Tool approval timeout
|
|
259
259
|
|
package/dist/mastra-harness.js
CHANGED
|
@@ -623,7 +623,7 @@ export class MastraHarness {
|
|
|
623
623
|
// object and stores it here so we can attach each to its
|
|
624
624
|
// McpServerState below (W-23231658).
|
|
625
625
|
const httpErrorCaptures = new Map();
|
|
626
|
-
const enabledServers = mapToMastraMCPServers(config.mcpServers, config.orgJwt, this.innerFetch, httpErrorCaptures, options?.mcpAuthProviders);
|
|
626
|
+
const enabledServers = mapToMastraMCPServers(config.mcpServers, config.orgJwt, this.innerFetch, httpErrorCaptures, options?.mcpAuthProviders, config.name);
|
|
627
627
|
for (const [serverName, serverDef] of Object.entries(enabledServers)) {
|
|
628
628
|
const client = new MCPClient({
|
|
629
629
|
id: `${agentId}:${serverName}`,
|
|
@@ -1107,7 +1107,7 @@ export class MastraHarness {
|
|
|
1107
1107
|
]);
|
|
1108
1108
|
const nextHttpErrorCaptures = new Map();
|
|
1109
1109
|
const nextEnabledServers = hasEnabledServers(nextConfig)
|
|
1110
|
-
? mapToMastraMCPServers(nextConfig, config.orgJwt, this.innerFetch, nextHttpErrorCaptures, nextProviders)
|
|
1110
|
+
? mapToMastraMCPServers(nextConfig, config.orgJwt, this.innerFetch, nextHttpErrorCaptures, nextProviders, config.name)
|
|
1111
1111
|
: {};
|
|
1112
1112
|
for (const name of allNames) {
|
|
1113
1113
|
const prev = prevConfig[name];
|
|
@@ -57,4 +57,6 @@ innerFetch?: typeof fetch,
|
|
|
57
57
|
* (W-23231658). Stdio servers contribute no entry. Omitted in call sites
|
|
58
58
|
* that don't need recovery (e.g. unit tests of the mapping shape).
|
|
59
59
|
*/
|
|
60
|
-
captures?: Map<string, HttpErrorCapture>, providers?: McpAuthProviders
|
|
60
|
+
captures?: Map<string, HttpErrorCapture>, providers?: McpAuthProviders,
|
|
61
|
+
/** AgentConfig.name; see {@link resolveMcpClientName}. */
|
|
62
|
+
agentName?: string): Record<string, MastraMCPServerDefinition>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright 2026, Salesforce, Inc. All rights reserved.
|
|
3
3
|
* See LICENSE.txt for license terms.
|
|
4
4
|
*/
|
|
5
|
-
import { resolveMcpServerHeaders, } from '@salesforce/sfdx-agent-sdk';
|
|
5
|
+
import { resolveMcpClientName, resolveMcpServerHeaders, } from '@salesforce/sfdx-agent-sdk';
|
|
6
6
|
/**
|
|
7
7
|
* Returns `true` if `config` contains at least one server that is not explicitly disabled.
|
|
8
8
|
* A server is considered enabled when its `enabled` property is `true` or absent.
|
|
@@ -67,13 +67,17 @@ innerFetch = globalThis.fetch,
|
|
|
67
67
|
* (W-23231658). Stdio servers contribute no entry. Omitted in call sites
|
|
68
68
|
* that don't need recovery (e.g. unit tests of the mapping shape).
|
|
69
69
|
*/
|
|
70
|
-
captures, providers = {}
|
|
70
|
+
captures, providers = {},
|
|
71
|
+
/** AgentConfig.name; see {@link resolveMcpClientName}. */
|
|
72
|
+
agentName) {
|
|
71
73
|
const result = {};
|
|
72
74
|
for (const [name, serverConfig] of Object.entries(config)) {
|
|
73
75
|
if (serverConfig.enabled === false) {
|
|
74
76
|
continue;
|
|
75
77
|
}
|
|
76
78
|
if (serverConfig.type === 'stdio') {
|
|
79
|
+
// Stdio has no fetch layer for rewriting `clientInfo`, and `@mastra/mcp`
|
|
80
|
+
// exposes no constructor option for it. See https://github.com/mastra-ai/mastra/issues/25473.
|
|
77
81
|
const def = {
|
|
78
82
|
command: serverConfig.command,
|
|
79
83
|
...(serverConfig.args !== undefined && { args: serverConfig.args }),
|
|
@@ -92,9 +96,11 @@ captures, providers = {}) {
|
|
|
92
96
|
else if (serverConfig.type === 'remote') {
|
|
93
97
|
const url = serverConfig.url instanceof URL ? serverConfig.url : new URL(serverConfig.url);
|
|
94
98
|
const provider = providers[name];
|
|
99
|
+
// Wrap the innermost fetch so the client identity reaches every auth path.
|
|
100
|
+
const clientInfoFetch = wrapFetchWithClientInfoName(innerFetch, agentName, orgJwt);
|
|
95
101
|
const baseFetch = provider === undefined && orgJwt
|
|
96
|
-
? createAuthFetch(serverConfig.url, orgJwt,
|
|
97
|
-
:
|
|
102
|
+
? createAuthFetch(serverConfig.url, orgJwt, clientInfoFetch, serverConfig.headers)
|
|
103
|
+
: clientInfoFetch;
|
|
98
104
|
// Wrap with HTTP-error capture when the harness asked for it, so a
|
|
99
105
|
// non-2xx connect response's status + body survive the error
|
|
100
106
|
// `@mastra/mcp` discards (W-23231658). The mapper owns the capture
|
|
@@ -110,7 +116,7 @@ captures, providers = {}) {
|
|
|
110
116
|
transportFetch = wrapFetchWithErrorCapture(baseFetch, capture);
|
|
111
117
|
}
|
|
112
118
|
else {
|
|
113
|
-
transportFetch =
|
|
119
|
+
transportFetch = baseFetch;
|
|
114
120
|
}
|
|
115
121
|
const def = {
|
|
116
122
|
url,
|
|
@@ -154,6 +160,54 @@ function createAuthFetch(serverUrl, orgJwt, innerFetch, existingHeaders) {
|
|
|
154
160
|
return innerFetch(url, { ...init, headers: merged });
|
|
155
161
|
};
|
|
156
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Wraps a transport fetch to rewrite `clientInfo.name` on MCP `initialize`
|
|
165
|
+
* requests. Other requests pass through unchanged, and extra arguments are forwarded.
|
|
166
|
+
*
|
|
167
|
+
* Only the `initialize` handshake is rewritten: a server configured with
|
|
168
|
+
* `protocolVersion: '2026-07-28'` sends no `initialize`, so the rewrite does nothing there.
|
|
169
|
+
*/
|
|
170
|
+
function wrapFetchWithClientInfoName(baseFetch, agentName, orgJwt) {
|
|
171
|
+
const forward = baseFetch;
|
|
172
|
+
const wrapped = async (input, init, ...rest) => {
|
|
173
|
+
const rewrittenBody = rewriteInitializeClientInfoName(init?.body, () => resolveMcpClientName(agentName, orgJwt));
|
|
174
|
+
return rewrittenBody === undefined
|
|
175
|
+
? forward(input, init, ...rest)
|
|
176
|
+
: forward(input, { ...init, body: rewrittenBody }, ...rest);
|
|
177
|
+
};
|
|
178
|
+
return wrapped;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* If `body` is a JSON-RPC `initialize` request, return a new body string with
|
|
182
|
+
* `params.clientInfo.name` set to `resolveName()` (creating `clientInfo` when
|
|
183
|
+
* absent, preserving `version` and any other fields). Returns `undefined` — a
|
|
184
|
+
* pass-through signal — for any non-`initialize` request or an unparseable /
|
|
185
|
+
* non-string body. Best-effort: it never throws into the transport.
|
|
186
|
+
*/
|
|
187
|
+
function rewriteInitializeClientInfoName(body, resolveName) {
|
|
188
|
+
if (typeof body !== 'string')
|
|
189
|
+
return undefined;
|
|
190
|
+
let message;
|
|
191
|
+
try {
|
|
192
|
+
message = JSON.parse(body);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
if (message === null || typeof message !== 'object' || Array.isArray(message))
|
|
198
|
+
return undefined;
|
|
199
|
+
const msg = message;
|
|
200
|
+
if (msg.method !== 'initialize')
|
|
201
|
+
return undefined;
|
|
202
|
+
const params = typeof msg.params === 'object' && msg.params !== null && !Array.isArray(msg.params)
|
|
203
|
+
? msg.params
|
|
204
|
+
: {};
|
|
205
|
+
const nextParams = {
|
|
206
|
+
...params,
|
|
207
|
+
clientInfo: { ...(params.clientInfo ?? {}), name: resolveName() },
|
|
208
|
+
};
|
|
209
|
+
return JSON.stringify({ ...msg, params: nextParams });
|
|
210
|
+
}
|
|
157
211
|
/**
|
|
158
212
|
* Wrap a transport fetch so every non-2xx response's status + body is recorded
|
|
159
213
|
* into `capture.latest` before the response is handed back to `@mastra/mcp`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/sfdx-agent-harness-mastra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.99.0",
|
|
4
4
|
"description": "Mastra-backed AgentHarness implementation for @salesforce/sfdx-agent-sdk",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,20 +42,20 @@
|
|
|
42
42
|
"openai": "^7.23.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@salesforce/sfdx-agent-sdk": "0.
|
|
45
|
+
"@salesforce/sfdx-agent-sdk": "0.100.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@eslint/js": "^10.0.1",
|
|
49
49
|
"@salesforce/harness-conformance": "0.0.1",
|
|
50
|
-
"@salesforce/sfdx-agent-sdk": "0.
|
|
50
|
+
"@salesforce/sfdx-agent-sdk": "0.100.0",
|
|
51
51
|
"@types/node": "^22.20.1",
|
|
52
|
-
"@vitest/coverage-istanbul": "^5.0.
|
|
52
|
+
"@vitest/coverage-istanbul": "^5.0.2",
|
|
53
53
|
"@vitest/eslint-plugin": "^1.6.27",
|
|
54
54
|
"eslint": "^10.11.0",
|
|
55
55
|
"eslint-config-prettier": "^10.1.8",
|
|
56
56
|
"eslint-import-resolver-typescript": "^4.4.5",
|
|
57
57
|
"eslint-plugin-import": "^2.32.0",
|
|
58
|
-
"eslint-plugin-n": "^18.
|
|
58
|
+
"eslint-plugin-n": "^18.4.0",
|
|
59
59
|
"globals": "^17.12.0",
|
|
60
60
|
"lint-staged": "^17.5.1",
|
|
61
61
|
"prettier": "^3.9.7",
|