@salesforce/sfdx-agent-harness-mastra 0.100.0 → 0.102.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 +10 -0
- package/README.md +5 -11
- package/dist/index.d.ts +5 -5
- package/dist/mastra-harness-factory.d.ts +3 -3
- package/dist/mastra-harness.d.ts +14 -16
- package/dist/mastra-harness.js +73 -61
- package/dist/mcp-config-mapper.d.ts +8 -31
- package/dist/mcp-config-mapper.js +21 -144
- package/dist/mcp-error-classifier.d.ts +22 -20
- package/dist/mcp-error-classifier.js +211 -62
- package/dist/pass-through-anthropic.d.ts +15 -15
- package/dist/pass-through-anthropic.js +21 -23
- package/dist/pass-through-openai-chat-completions.d.ts +12 -12
- package/dist/pass-through-openai-chat-completions.js +15 -17
- package/dist/pass-through-openai.d.ts +9 -9
- package/dist/pass-through-openai.js +14 -16
- package/dist/retry-pre-first-token.d.ts +2 -2
- package/dist/stream-errors.d.ts +1 -1
- package/dist/stream-errors.js +1 -1
- package/dist/{v3-stream-mapping.d.ts → stream-mapping.d.ts} +62 -47
- package/dist/{v3-stream-mapping.js → stream-mapping.js} +51 -32
- package/dist/tool-args-marker.d.ts +1 -1
- package/dist/tool-args-marker.js +1 -1
- package/package.json +23 -23
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
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.102.0] - 2026-10-13
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
- **deps**: upgrade dependencies and move the Mastra harness to @mastra/mcp 2.x @W-24334288@ ([#877](https://github.com/forcedotcom/agentic-dx/pull/877))
|
|
10
|
+
|
|
11
|
+
## [0.101.0] - 2026-10-13
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
- **harness-mastra**: migrate pass-throughs to LanguageModelV4 and remove chat-generations @W-24224624@ ([#876](https://github.com/forcedotcom/agentic-dx/pull/876))
|
|
15
|
+
|
|
6
16
|
## [0.100.0] - 2026-10-09
|
|
7
17
|
|
|
8
18
|
### Chores
|
package/README.md
CHANGED
|
@@ -59,8 +59,8 @@ Linux and macOS release the handle eagerly; the retry options are no-ops on thos
|
|
|
59
59
|
| ----------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
60
|
| `MastraHarnessFactory` | class | `HarnessFactory` that produces a Mastra-backed `AgentHarness` bound to a storage root. |
|
|
61
61
|
| `MastraHarnessFactoryConfig` | interface | Configuration for `MastraHarnessFactory` (`toolApprovalTimeoutMs?`, `languageModelBuilders?`). |
|
|
62
|
-
| `MastraLanguageModelBuilderContext` | type | Context bag the harness threads into each `
|
|
63
|
-
| `MastraLanguageModelBuilder` | type | Factory function shape for a consumer-injected builder: `(ctx: MastraLanguageModelBuilderContext) =>
|
|
62
|
+
| `MastraLanguageModelBuilderContext` | type | Context bag the harness threads into each `LanguageModelV4` builder (`getInfo`, `logBus`, `emitWireCommunication`, `innerFetch`). |
|
|
63
|
+
| `MastraLanguageModelBuilder` | type | Factory function shape for a consumer-injected builder: `(ctx: MastraLanguageModelBuilderContext) => LanguageModelV4`. |
|
|
64
64
|
| `MASTRA_BUILT_IN_TOOL_POLICIES` | const | Frozen `ReadonlyArray<ToolPolicyRule>` the harness feeds the SDK's `resolveToolApprovalPolicy` as the `tiers.harness` slice — `allow` rules for Mastra's built-in `updateWorkingMemory` / `skill` / `skill_search` / `skill_read` tools. |
|
|
65
65
|
| `MASTRA_BUILT_IN_TOOL_NAMES` | const | The four built-in tool leaf names `MASTRA_BUILT_IN_TOOL_POLICIES` covers. |
|
|
66
66
|
|
|
@@ -81,20 +81,14 @@ no `globalThis` mutation. When no proxy env is set, `fetch` runs unwrapped (zero
|
|
|
81
81
|
|
|
82
82
|
#### Injecting custom wire shapes
|
|
83
83
|
|
|
84
|
-
A consumer can register a `
|
|
84
|
+
A consumer can register a `LanguageModelV4` builder for a custom `providerHint` via
|
|
85
85
|
`MastraHarnessFactoryConfig.languageModelBuilders` without editing the harness's internal switch. Each injected key
|
|
86
|
-
becomes a supported provider hint and a dispatch arm. Used to inject wire shapes the harness does not ship natively
|
|
87
|
-
(e.g., a temporary GovCloud `chat/generations` fallback).
|
|
86
|
+
becomes a supported provider hint and a dispatch arm. Used to inject wire shapes the harness does not ship natively.
|
|
88
87
|
|
|
89
88
|
```ts
|
|
90
|
-
import {
|
|
91
|
-
CHAT_GENERATIONS_PROVIDER_HINT,
|
|
92
|
-
buildChatGenerationsLanguageModel,
|
|
93
|
-
} from '@salesforce/sfdx-agent-chat-generations';
|
|
94
|
-
|
|
95
89
|
const factory = new MastraHarnessFactory({
|
|
96
90
|
languageModelBuilders: {
|
|
97
|
-
|
|
91
|
+
'my-custom-wire-shape': (ctx) => buildMyLanguageModel(ctx),
|
|
98
92
|
},
|
|
99
93
|
});
|
|
100
94
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { LogBus } from '@salesforce/agentic-common';
|
|
2
2
|
import type { ModelConnectivityInfo, WireCommunicationEvent } from '@salesforce/sfdx-agent-sdk';
|
|
3
|
-
import type {
|
|
3
|
+
import type { LanguageModelV4 } from '@ai-sdk/provider';
|
|
4
4
|
export { MastraHarnessFactory, type MastraHarnessFactoryConfig } from './mastra-harness-factory.js';
|
|
5
5
|
export type { MastraAgentHarness } from './mastra-harness.js';
|
|
6
6
|
export { MASTRA_BUILT_IN_TOOL_NAMES, MASTRA_BUILT_IN_TOOL_POLICIES } from './mastra-built-in-policies.js';
|
|
7
7
|
export { type MastraAgentConfig, type MastraSkillSearchConfig, type MastraToolSearchConfig, type SkillSearchHandle, type ToolSearchHandle, } from './tool-search.js';
|
|
8
8
|
/**
|
|
9
|
-
* Context the harness threads into each `
|
|
9
|
+
* Context the harness threads into each `LanguageModelV4` builder — both the
|
|
10
10
|
* built-in builders and any consumer-injected builders registered via
|
|
11
11
|
* `MastraHarnessFactoryConfig.languageModelBuilders`. The shape mirrors what
|
|
12
12
|
* `MastraHarness.buildLanguageModel` currently hands its internal builders, so an
|
|
@@ -33,10 +33,10 @@ export type MastraLanguageModelBuilderContext = {
|
|
|
33
33
|
innerFetch: typeof fetch;
|
|
34
34
|
};
|
|
35
35
|
/**
|
|
36
|
-
* Factory function shape for a consumer-injected `
|
|
36
|
+
* Factory function shape for a consumer-injected `LanguageModelV4` builder.
|
|
37
37
|
* Registered via `MastraHarnessFactoryConfig.languageModelBuilders` keyed by a custom
|
|
38
38
|
* `providerHint`, used to inject wire shapes the harness does not ship natively
|
|
39
|
-
* (e.g. a
|
|
39
|
+
* (e.g. a bespoke gateway wire format). The builder receives the
|
|
40
40
|
* same context bag the harness's built-in pass-through builders get, so an injected
|
|
41
41
|
* builder can emit `WireCommunicationEvent`s and honor `HTTPS_PROXY` routing without
|
|
42
42
|
* duplicating the harness's own fetch wrapper / wire-monitoring plumbing. The
|
|
@@ -55,4 +55,4 @@ export type MastraLanguageModelBuilderContext = {
|
|
|
55
55
|
* });
|
|
56
56
|
* ```
|
|
57
57
|
*/
|
|
58
|
-
export type MastraLanguageModelBuilder = (ctx: MastraLanguageModelBuilderContext) =>
|
|
58
|
+
export type MastraLanguageModelBuilder = (ctx: MastraLanguageModelBuilderContext) => LanguageModelV4;
|
|
@@ -12,10 +12,10 @@ export interface MastraHarnessFactoryConfig {
|
|
|
12
12
|
*/
|
|
13
13
|
toolApprovalTimeoutMs?: number;
|
|
14
14
|
/**
|
|
15
|
-
* Consumer-registered `
|
|
15
|
+
* Consumer-registered `LanguageModelV4` builders keyed by `providerHint`. Each key
|
|
16
16
|
* becomes a supported provider hint and a dispatch arm in `buildLanguageModel`. Used
|
|
17
|
-
* to inject wire shapes the harness does not ship natively (e.g. a
|
|
18
|
-
*
|
|
17
|
+
* to inject wire shapes the harness does not ship natively (e.g. a bespoke
|
|
18
|
+
* gateway wire format). Keys MUST NOT collide with the
|
|
19
19
|
* harness's built-in hints (`anthropic`, `bedrock-anthropic`, `openai`,
|
|
20
20
|
* `openai-responses`, `openai-compatible`) — constructing with a colliding key
|
|
21
21
|
* throws at factory construction time rather than silently shadowing the built-in.
|
package/dist/mastra-harness.d.ts
CHANGED
|
@@ -5,10 +5,9 @@ import { McpServerStatus, type AgentHarness, type WithAgentConfig, type HarnessS
|
|
|
5
5
|
import { type AgentHooks } from '@salesforce/sfdx-agent-sdk/harness';
|
|
6
6
|
import type { MastraLanguageModelBuilderContext } from './index.js';
|
|
7
7
|
import { MastraApprovalCoordinator } from './mastra-approval-coordinator.js';
|
|
8
|
-
import { type HttpErrorCapture } from './mcp-config-mapper.js';
|
|
9
8
|
import { type MastraAgentConfig, type SkillSearchHandle, type ToolSearchHandle } from './tool-search.js';
|
|
10
9
|
import { type MultimodalFile } from '@salesforce/sfdx-agent-sdk';
|
|
11
|
-
import type {
|
|
10
|
+
import type { LanguageModelV4 } from '@ai-sdk/provider';
|
|
12
11
|
import { Clock, type LogRecord, type BackoffRetryerDeps, type UniqueIDGenerator, type Unsubscribe } from '@salesforce/agentic-common';
|
|
13
12
|
import { type ToolsInput } from './utils.js';
|
|
14
13
|
import { Workspace } from '@mastra/core/workspace';
|
|
@@ -67,15 +66,6 @@ interface McpServerState {
|
|
|
67
66
|
* on (tool merging, alwaysActive expansion, MCP-tool metadata lookup).
|
|
68
67
|
*/
|
|
69
68
|
tools: ToolsInput;
|
|
70
|
-
/**
|
|
71
|
-
* HTTP-error capture slot for remote servers (W-23231658). The remote
|
|
72
|
-
* transport fetch (built by `mapToMastraMCPServers`) writes the latest
|
|
73
|
-
* non-2xx `{ status, body }` here; the discovery / reconnect catch reads
|
|
74
|
-
* `latest` to recover the HTTP status `@mastra/mcp` discards when a
|
|
75
|
-
* fallback-eligible connect fails. `undefined` for stdio servers and for
|
|
76
|
-
* remote servers when no capture map was threaded in.
|
|
77
|
-
*/
|
|
78
|
-
httpErrorCapture?: HttpErrorCapture;
|
|
79
69
|
}
|
|
80
70
|
/**
|
|
81
71
|
* Internal state for a single agent. Holds Mastra-specific instances so the
|
|
@@ -247,13 +237,13 @@ export interface MastraHarnessOptions {
|
|
|
247
237
|
*/
|
|
248
238
|
innerFetch?: typeof fetch;
|
|
249
239
|
/**
|
|
250
|
-
* Consumer-registered `
|
|
240
|
+
* Consumer-registered `LanguageModelV4` builders keyed by `providerHint`.
|
|
251
241
|
* Forwarded from `MastraHarnessFactoryConfig.languageModelBuilders`. The
|
|
252
242
|
* harness consults this map in `buildLanguageModel`'s `default` arm before
|
|
253
243
|
* throwing `MODEL_NOT_SUPPORTED_BY_HARNESS`, so an injected builder can
|
|
254
244
|
* handle a custom hint without editing the harness's internal switch.
|
|
255
245
|
*/
|
|
256
|
-
languageModelBuilders?: Readonly<Record<string, (ctx: MastraLanguageModelBuilderContext) =>
|
|
246
|
+
languageModelBuilders?: Readonly<Record<string, (ctx: MastraLanguageModelBuilderContext) => LanguageModelV4>>;
|
|
257
247
|
/**
|
|
258
248
|
* Clock / sleep / RNG for the pre-first-token LLM retry loop (W-24236393), whose policy is
|
|
259
249
|
* `PRE_FIRST_TOKEN_RETRY_OPTIONS`. Tests inject a no-sleep `sleep`.
|
|
@@ -514,7 +504,7 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
514
504
|
* non-empty paths.
|
|
515
505
|
*/
|
|
516
506
|
/**
|
|
517
|
-
* Dispatches on the bag's `providerHint` and returns a `
|
|
507
|
+
* Dispatches on the bag's `providerHint` and returns a `LanguageModelV4` that
|
|
518
508
|
* speaks the provider-native pass-through endpoint. The builder closes over
|
|
519
509
|
* `getInfo` so within-shape `modelId` swaps, JWT rotations, and
|
|
520
510
|
* premium-rate-limit gate flips land on the next outbound call without
|
|
@@ -588,8 +578,8 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
588
578
|
* `agent.stream()` accepts.
|
|
589
579
|
*
|
|
590
580
|
* `text` parts collapse into text content blocks; `image` / `file` parts become v5 `file`
|
|
591
|
-
* content blocks (`mediaType` + base64 `data` + optional `filename`) that Mastra
|
|
592
|
-
*
|
|
581
|
+
* content blocks (`mediaType` + base64 `data` + optional `filename`) that Mastra normalizes to V4
|
|
582
|
+
* `file` parts for the provider pass-through translators. Pre-stream validation and the
|
|
593
583
|
* input-part guard live in the SDK's {@link lowerStreamInput}, so file caps and the
|
|
594
584
|
* `MULTIMODAL_NOT_SUPPORTED` / `INVALID_MESSAGE_CONTENT` semantics are identical to the Claude
|
|
595
585
|
* harness; this method only supplies the Mastra-specific lowering of each part.
|
|
@@ -737,6 +727,14 @@ export declare class MastraHarness implements MastraAgentHarness {
|
|
|
737
727
|
* after `shutdown()` has disposed the buses.
|
|
738
728
|
*/
|
|
739
729
|
private prepareMcpStatusChange;
|
|
730
|
+
/**
|
|
731
|
+
* Surface the one MCP option this harness cannot honor. `@mastra/mcp` 2.x speaks the
|
|
732
|
+
* stateless 2026-07-28 protocol, has no SSE stream to resume, and dropped
|
|
733
|
+
* `reconnectionOptions`, so the mapper does not forward it; the Claude harness still does.
|
|
734
|
+
* Without this a consumer's tuning would silently stop applying on Mastra, so warn once per
|
|
735
|
+
* server definition that is (re)built.
|
|
736
|
+
*/
|
|
737
|
+
private warnIfReconnectionOptionsIgnored;
|
|
740
738
|
/**
|
|
741
739
|
* Issue #591: install a `progress.onUpdate(serverName, ...)` listener
|
|
742
740
|
* on the MCP client. Incoming `notifications/progress` are dispatched
|
package/dist/mastra-harness.js
CHANGED
|
@@ -21,13 +21,13 @@ import { isStandardSchemaWithJSON, standardSchemaToJSONSchema } from '@mastra/sc
|
|
|
21
21
|
import { SessionContextStore, sortJsonValue } from './session-context-store.js';
|
|
22
22
|
import { mapToolDefinitionsToMastraTools } from './mastra-tool-mapper.js';
|
|
23
23
|
import { hasEnabledServers, mapToMastraMCPServers } from './mcp-config-mapper.js';
|
|
24
|
-
import { buildMcpErrorDetail,
|
|
24
|
+
import { buildMcpErrorDetail, errorFromDiscoveryDetails, sanitizeMcpErrorMessage } from './mcp-error-classifier.js';
|
|
25
25
|
import { partitionPool, resolveAlwaysActive, } from './tool-search.js';
|
|
26
26
|
import { validateMultimodalFiles } from '@salesforce/sfdx-agent-sdk';
|
|
27
27
|
import { buildAnthropicDirectLanguageModel, buildAnthropicLanguageModel } from './pass-through-anthropic.js';
|
|
28
28
|
import { buildOpenAIChatCompletionsLanguageModel } from './pass-through-openai-chat-completions.js';
|
|
29
29
|
import { buildOpenAILanguageModel } from './pass-through-openai.js';
|
|
30
|
-
import { readToolArgsRetryScope, TOOL_ARGS_RETRY_SCOPE_AGENT_ID, TOOL_ARGS_RETRY_SCOPE_PROVIDER, TOOL_ARGS_RETRY_SCOPE_THREAD_ID, } from './
|
|
30
|
+
import { readToolArgsRetryScope, TOOL_ARGS_RETRY_SCOPE_AGENT_ID, TOOL_ARGS_RETRY_SCOPE_PROVIDER, TOOL_ARGS_RETRY_SCOPE_THREAD_ID, } from './stream-mapping.js';
|
|
31
31
|
import { ToolResultRedactionProcessor } from './mastra-tool-result-redaction-processor.js';
|
|
32
32
|
import { backfillCreatedAt, buildSummaryPrompt, Clock, getErrorMessage, RealClock, splitFrontmatterAndBody, UUIDGenerator, } from '@salesforce/agentic-common';
|
|
33
33
|
import { withPreFirstTokenRetry } from './retry-pre-first-token.js';
|
|
@@ -192,6 +192,33 @@ const DEFAULT_MCP_TIMEOUT_MS = 600_000;
|
|
|
192
192
|
* factory/consumer knob — same posture as `resumeRpcTimeoutMs`, 6c).
|
|
193
193
|
*/
|
|
194
194
|
const DEFAULT_IDLE_READ_TIMEOUT_MS = 120_000;
|
|
195
|
+
// Issue #529 step 2c: the pre-#529 `ActiveRun` interface has been collapsed
|
|
196
|
+
// into `MastraApprovalCoordinator`. The coordinator owns the per-turn pump
|
|
197
|
+
// loop, the multi-source `fullStream` rotation, the per-tool-callId approval
|
|
198
|
+
// timeouts, and the consumer-tool parker — all of which previously lived on
|
|
199
|
+
// `ActiveRun` + `MastraStreamBroker` as two parallel state machines for the
|
|
200
|
+
// same `(agentId, threadId)`. See `mastra-approval-coordinator.ts`.
|
|
201
|
+
/**
|
|
202
|
+
* Resolve a failed MCP connect (the discovery catch and the `reconnectServer` catch share this) into what the
|
|
203
|
+
* harness records and emits: the error to report, its sanitized one-line message, and the structured
|
|
204
|
+
* {@link McpServerErrorDetail}. A server `@mastra/mcp` reports as `needs-auth` is surfaced as an OAuth
|
|
205
|
+
* requirement (`oauth-required`, non-retriable) regardless of the transport error it produced.
|
|
206
|
+
*
|
|
207
|
+
* The telemetry / log / status-change emits around the call differ between the two sites and stay at the
|
|
208
|
+
* call sites on purpose.
|
|
209
|
+
*/
|
|
210
|
+
function resolveMcpFailure(client, serverName, err) {
|
|
211
|
+
const oauthRequired = client.getServerAuthState(serverName) === 'needs-auth';
|
|
212
|
+
const effectiveError = oauthRequired
|
|
213
|
+
? new (class UnauthorizedError extends Error {
|
|
214
|
+
})(sanitizeMcpErrorMessage(err))
|
|
215
|
+
: err;
|
|
216
|
+
return {
|
|
217
|
+
effectiveError,
|
|
218
|
+
message: sanitizeMcpErrorMessage(effectiveError),
|
|
219
|
+
errorDetail: buildMcpErrorDetail(effectiveError),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
195
222
|
/**
|
|
196
223
|
* Build the `Map<namespacedName, ToolSource>` index `resolveAlwaysActive`
|
|
197
224
|
* needs to match `{ serverName }` / `{ serverName, toolName }` / `{ toolName }`
|
|
@@ -618,12 +645,9 @@ export class MastraHarness {
|
|
|
618
645
|
const mcpAuthProviders = new Map(Object.entries(options?.mcpAuthProviders ?? {}).filter(([name]) => config.mcpServers?.[name]?.enabled !== false));
|
|
619
646
|
if (hasEnabledServers(config.mcpServers)) {
|
|
620
647
|
options?.abortSignal?.throwIfAborted();
|
|
621
|
-
|
|
622
|
-
// object and stores it here so we can attach each to its
|
|
623
|
-
// McpServerState below (W-23231658).
|
|
624
|
-
const httpErrorCaptures = new Map();
|
|
625
|
-
const enabledServers = mapToMastraMCPServers(config.mcpServers, config.orgJwt, this.innerFetch, httpErrorCaptures, options?.mcpAuthProviders, config.name);
|
|
648
|
+
const enabledServers = mapToMastraMCPServers(config.mcpServers, config.orgJwt, this.innerFetch, options?.mcpAuthProviders, config.name);
|
|
626
649
|
for (const [serverName, serverDef] of Object.entries(enabledServers)) {
|
|
650
|
+
this.warnIfReconnectionOptionsIgnored(agentId, serverName, config.mcpServers[serverName]);
|
|
627
651
|
const client = new MCPClient({
|
|
628
652
|
id: `${agentId}:${serverName}`,
|
|
629
653
|
servers: { [serverName]: serverDef },
|
|
@@ -640,7 +664,6 @@ export class MastraHarness {
|
|
|
640
664
|
ready: Promise.resolve(),
|
|
641
665
|
settled: false,
|
|
642
666
|
tools: {},
|
|
643
|
-
httpErrorCapture: httpErrorCaptures.get(serverName),
|
|
644
667
|
};
|
|
645
668
|
mcpServerStates.set(serverName, entry);
|
|
646
669
|
entry.ready = this.runMcpDiscovery(agentId, serverName, client, mcpServerStates, 0, options?.abortSignal);
|
|
@@ -955,11 +978,6 @@ export class MastraHarness {
|
|
|
955
978
|
agentId,
|
|
956
979
|
serverName,
|
|
957
980
|
});
|
|
958
|
-
// W-23231658: clear any stale HTTP-error capture so a prior failure's
|
|
959
|
-
// status can't be mistaken for this reconnect's. The wrapper repopulates
|
|
960
|
-
// `latest` only if this reconnect's connect sees a non-2xx response.
|
|
961
|
-
if (serverState.httpErrorCapture)
|
|
962
|
-
serverState.httpErrorCapture.latest = undefined;
|
|
963
981
|
// Transport-level reconnect can throw (stdio spawn failure, remote handshake
|
|
964
982
|
// failure). Record on serverState and emit only `failed` (not started+failed
|
|
965
983
|
// — `started` already fired above). This keeps the public contract
|
|
@@ -971,19 +989,12 @@ export class MastraHarness {
|
|
|
971
989
|
// Drop telemetry + state writes if a newer epoch has superseded us.
|
|
972
990
|
if (state.mcpServers.get(serverName)?.epoch !== epoch)
|
|
973
991
|
return;
|
|
974
|
-
//
|
|
975
|
-
//
|
|
976
|
-
//
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
const effectiveError =
|
|
980
|
-
? new (class UnauthorizedError extends Error {
|
|
981
|
-
})(sanitizeMcpErrorMessage(err))
|
|
982
|
-
: captured
|
|
983
|
-
? synthesizeHttpTransportError(captured.status, captured.body, err)
|
|
984
|
-
: err;
|
|
985
|
-
const message = sanitizeMcpErrorMessage(effectiveError);
|
|
986
|
-
const errorDetail = buildMcpErrorDetail(effectiveError);
|
|
992
|
+
// `reconnectServer` rethrows the live error: the raw `SdkHttpError` /
|
|
993
|
+
// `SdkError` for a connected client, or a `MastraError` wrapping it once a
|
|
994
|
+
// prior connect failed. Either way the HTTP status / error code is on the
|
|
995
|
+
// error or its `cause` chain (W-23231658), which `buildMcpErrorDetail`
|
|
996
|
+
// reads directly.
|
|
997
|
+
const { effectiveError, message, errorDetail } = resolveMcpFailure(serverState.client, serverName, err);
|
|
987
998
|
serverState.error = message;
|
|
988
999
|
serverState.errorDetail = errorDetail;
|
|
989
1000
|
serverState.settled = true;
|
|
@@ -1104,9 +1115,8 @@ export class MastraHarness {
|
|
|
1104
1115
|
...state.mcpAuthProviders.keys(),
|
|
1105
1116
|
...Object.keys(nextProviders),
|
|
1106
1117
|
]);
|
|
1107
|
-
const nextHttpErrorCaptures = new Map();
|
|
1108
1118
|
const nextEnabledServers = hasEnabledServers(nextConfig)
|
|
1109
|
-
? mapToMastraMCPServers(nextConfig, config.orgJwt, this.innerFetch,
|
|
1119
|
+
? mapToMastraMCPServers(nextConfig, config.orgJwt, this.innerFetch, nextProviders, config.name)
|
|
1110
1120
|
: {};
|
|
1111
1121
|
for (const name of allNames) {
|
|
1112
1122
|
const prev = prevConfig[name];
|
|
@@ -1128,6 +1138,7 @@ export class MastraHarness {
|
|
|
1128
1138
|
if (next && next.enabled !== false) {
|
|
1129
1139
|
const serverDef = nextEnabledServers[name];
|
|
1130
1140
|
if (serverDef) {
|
|
1141
|
+
this.warnIfReconnectionOptionsIgnored(agentId, name, next);
|
|
1131
1142
|
const client = new MCPClient({
|
|
1132
1143
|
id: `${agentId}:${name}`,
|
|
1133
1144
|
servers: { [name]: serverDef },
|
|
@@ -1140,7 +1151,6 @@ export class MastraHarness {
|
|
|
1140
1151
|
ready: Promise.resolve(),
|
|
1141
1152
|
settled: false,
|
|
1142
1153
|
tools: {},
|
|
1143
|
-
httpErrorCapture: nextHttpErrorCaptures.get(name),
|
|
1144
1154
|
};
|
|
1145
1155
|
state.mcpServers.set(name, entry);
|
|
1146
1156
|
if (nextProvider !== undefined) {
|
|
@@ -1550,7 +1560,7 @@ export class MastraHarness {
|
|
|
1550
1560
|
* non-empty paths.
|
|
1551
1561
|
*/
|
|
1552
1562
|
/**
|
|
1553
|
-
* Dispatches on the bag's `providerHint` and returns a `
|
|
1563
|
+
* Dispatches on the bag's `providerHint` and returns a `LanguageModelV4` that
|
|
1554
1564
|
* speaks the provider-native pass-through endpoint. The builder closes over
|
|
1555
1565
|
* `getInfo` so within-shape `modelId` swaps, JWT rotations, and
|
|
1556
1566
|
* premium-rate-limit gate flips land on the next outbound call without
|
|
@@ -1671,9 +1681,9 @@ export class MastraHarness {
|
|
|
1671
1681
|
}));
|
|
1672
1682
|
default: {
|
|
1673
1683
|
// Consult the injected builders map before throwing. This is the
|
|
1674
|
-
// extension point for consumer-registered wire shapes (e.g. a
|
|
1675
|
-
//
|
|
1676
|
-
//
|
|
1684
|
+
// extension point for consumer-registered wire shapes (e.g. a bespoke
|
|
1685
|
+
// gateway wire format). The builder receives the same context bag the
|
|
1686
|
+
// built-in builders get.
|
|
1677
1687
|
const custom = this.languageModelBuilders?.[initial.providerHint];
|
|
1678
1688
|
if (custom) {
|
|
1679
1689
|
return this.wrapLanguageModel(custom({ getInfo, logBus, emitWireCommunication, innerFetch }));
|
|
@@ -1773,8 +1783,8 @@ export class MastraHarness {
|
|
|
1773
1783
|
* `agent.stream()` accepts.
|
|
1774
1784
|
*
|
|
1775
1785
|
* `text` parts collapse into text content blocks; `image` / `file` parts become v5 `file`
|
|
1776
|
-
* content blocks (`mediaType` + base64 `data` + optional `filename`) that Mastra
|
|
1777
|
-
*
|
|
1786
|
+
* content blocks (`mediaType` + base64 `data` + optional `filename`) that Mastra normalizes to V4
|
|
1787
|
+
* `file` parts for the provider pass-through translators. Pre-stream validation and the
|
|
1778
1788
|
* input-part guard live in the SDK's {@link lowerStreamInput}, so file caps and the
|
|
1779
1789
|
* `MULTIMODAL_NOT_SUPPORTED` / `INVALID_MESSAGE_CONTENT` semantics are identical to the Claude
|
|
1780
1790
|
* harness; this method only supplies the Mastra-specific lowering of each part.
|
|
@@ -2295,6 +2305,28 @@ export class MastraHarness {
|
|
|
2295
2305
|
}
|
|
2296
2306
|
};
|
|
2297
2307
|
}
|
|
2308
|
+
/**
|
|
2309
|
+
* Surface the one MCP option this harness cannot honor. `@mastra/mcp` 2.x speaks the
|
|
2310
|
+
* stateless 2026-07-28 protocol, has no SSE stream to resume, and dropped
|
|
2311
|
+
* `reconnectionOptions`, so the mapper does not forward it; the Claude harness still does.
|
|
2312
|
+
* Without this a consumer's tuning would silently stop applying on Mastra, so warn once per
|
|
2313
|
+
* server definition that is (re)built.
|
|
2314
|
+
*/
|
|
2315
|
+
warnIfReconnectionOptionsIgnored(agentId, serverName, serverConfig) {
|
|
2316
|
+
if (serverConfig?.type !== 'remote' || serverConfig.reconnectionOptions === undefined)
|
|
2317
|
+
return;
|
|
2318
|
+
this.buses.emitLog({
|
|
2319
|
+
level: 'warn',
|
|
2320
|
+
message: `MCP server "${serverName}": reconnectionOptions is ignored by the Mastra harness`,
|
|
2321
|
+
timestamp: this.clock.now(),
|
|
2322
|
+
context: {
|
|
2323
|
+
event_type: 'mcp-reconnection-options-ignored',
|
|
2324
|
+
agentId,
|
|
2325
|
+
serverName,
|
|
2326
|
+
reason: '@mastra/mcp 2.x is stateless and has no reconnection options; only the Claude harness honors them',
|
|
2327
|
+
},
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2298
2330
|
/**
|
|
2299
2331
|
* Issue #591: install a `progress.onUpdate(serverName, ...)` listener
|
|
2300
2332
|
* on the MCP client. Incoming `notifications/progress` are dispatched
|
|
@@ -2384,18 +2416,14 @@ export class MastraHarness {
|
|
|
2384
2416
|
const s = serverStates.get(serverName);
|
|
2385
2417
|
return s && s.epoch === epoch ? s : undefined;
|
|
2386
2418
|
};
|
|
2387
|
-
// W-23231658: clear any HTTP-error capture left from a prior discovery
|
|
2388
|
-
// run on this (reused) client/capture so a stale status can't bleed into
|
|
2389
|
-
// this run's failure classification. The wrapper repopulates `latest`
|
|
2390
|
-
// only if a non-2xx response actually occurs during this connect.
|
|
2391
|
-
const captureSlot = serverStates.get(serverName)?.httpErrorCapture;
|
|
2392
|
-
if (captureSlot)
|
|
2393
|
-
captureSlot.latest = undefined;
|
|
2394
2419
|
return makeAbortable(client.listToolsetsWithErrors(), abortSignal)
|
|
2395
|
-
.then(({ toolsets, errors }) => {
|
|
2420
|
+
.then(({ toolsets, errors, errorDetails }) => {
|
|
2396
2421
|
const serverError = errors[serverName];
|
|
2397
2422
|
if (serverError) {
|
|
2398
|
-
|
|
2423
|
+
// `errorDetails` keeps the HTTP status and deepest error code that the
|
|
2424
|
+
// legacy per-server string drops (W-23231658); stubs without it fall
|
|
2425
|
+
// back to the bare message.
|
|
2426
|
+
throw errorFromDiscoveryDetails(errorDetails?.[serverName] ?? { message: serverError });
|
|
2399
2427
|
}
|
|
2400
2428
|
const tools = toolsets[serverName] ?? {};
|
|
2401
2429
|
const namespacedTools = {};
|
|
@@ -2461,23 +2489,7 @@ export class MastraHarness {
|
|
|
2461
2489
|
// terminal telemetry — see the matching comment in the
|
|
2462
2490
|
// success path above (issue #373).
|
|
2463
2491
|
//
|
|
2464
|
-
|
|
2465
|
-
// HTTP response, prefer it over the bodyless generic error
|
|
2466
|
-
// `@mastra/mcp` rethrows. The capture is only ever populated
|
|
2467
|
-
// when a real HTTP error occurred on this server's transport,
|
|
2468
|
-
// so by construction it IS the (or a) cause of this failure;
|
|
2469
|
-
// the original `err` rides along as `cause` so its stack is
|
|
2470
|
-
// preserved on the failed event.
|
|
2471
|
-
const captured = s.httpErrorCapture?.latest;
|
|
2472
|
-
const oauthRequired = s.client.getServerAuthState(serverName) === 'needs-auth';
|
|
2473
|
-
const effectiveError = oauthRequired
|
|
2474
|
-
? new (class UnauthorizedError extends Error {
|
|
2475
|
-
})(sanitizeMcpErrorMessage(err))
|
|
2476
|
-
: captured
|
|
2477
|
-
? synthesizeHttpTransportError(captured.status, captured.body, err)
|
|
2478
|
-
: err;
|
|
2479
|
-
const errorMessage = sanitizeMcpErrorMessage(effectiveError);
|
|
2480
|
-
const errorDetail = buildMcpErrorDetail(effectiveError);
|
|
2492
|
+
const { effectiveError, message: errorMessage, errorDetail, } = resolveMcpFailure(s.client, serverName, err);
|
|
2481
2493
|
s.error = errorMessage;
|
|
2482
2494
|
s.errorDetail = errorDetail;
|
|
2483
2495
|
s.settled = true;
|
|
@@ -6,23 +6,6 @@ import { type McpAuthProviders, type MCPConfiguration } from '@salesforce/sfdx-a
|
|
|
6
6
|
* A server is considered enabled when its `enabled` property is `true` or absent.
|
|
7
7
|
*/
|
|
8
8
|
export declare function hasEnabledServers(config: MCPConfiguration | undefined): config is MCPConfiguration;
|
|
9
|
-
/** A captured non-2xx HTTP response from a remote MCP transport request. */
|
|
10
|
-
export type CapturedHttpError = {
|
|
11
|
-
status: number;
|
|
12
|
-
body: string;
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* Per-server mutable slot the remote-MCP fetch wrapper writes the latest
|
|
16
|
-
* non-2xx response into. The harness reads `latest` in its discovery /
|
|
17
|
-
* reconnect catch blocks to recover the HTTP status + body that
|
|
18
|
-
* `@mastra/mcp`'s `connectHttp` discards when a fallback-eligible status
|
|
19
|
-
* (`400` / `404` / `405`) triggers the SSE fallback and both transports fail —
|
|
20
|
-
* the thrown `Error("Could not connect to server with any available HTTP
|
|
21
|
-
* transport")` carries no status, no body, and no `cause` (W-23231658, #617).
|
|
22
|
-
*/
|
|
23
|
-
export type HttpErrorCapture = {
|
|
24
|
-
latest?: CapturedHttpError;
|
|
25
|
-
};
|
|
26
9
|
/**
|
|
27
10
|
* Converts our {@link MCPConfiguration} into the `servers` map expected by Mastra's `MCPClient`.
|
|
28
11
|
*
|
|
@@ -34,10 +17,13 @@ export type HttpErrorCapture = {
|
|
|
34
17
|
* Mastra's `timeout` (post-connect request lifetime) and `connectTimeout` (initial transport
|
|
35
18
|
* handshake) so the configured value covers both phases — Mastra's hardcoded 3s
|
|
36
19
|
* `connectTimeout` default would otherwise abort cold-start handshakes before `timeout` applied.
|
|
37
|
-
* -
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
20
|
+
* - Every definition carries `clientInfo.name` from {@link resolveMcpClientName} (agent name, else
|
|
21
|
+
* JWT feature id, else the SDK default — never the server-map key `@mastra/mcp` would default
|
|
22
|
+
* to). Mastra sends it on `server/discover`, on every modern-era request, and on a legacy
|
|
23
|
+
* `initialize`, for stdio and remote servers alike.
|
|
24
|
+
* - `MCPRemoteServerConfig.reconnectionOptions` is deliberately NOT forwarded: `@mastra/mcp` 2.x
|
|
25
|
+
* speaks the stateless 2026-07-28 protocol, has no SSE stream to resume, and dropped the option.
|
|
26
|
+
* The Claude harness still honors it. See the compile-time guard below.
|
|
41
27
|
* - A supplied runtime OAuth provider is forwarded to the remote transport with the
|
|
42
28
|
* proxy-aware `innerFetch`; without one, `orgJwt` provides the existing JWT fetch.
|
|
43
29
|
*/
|
|
@@ -48,15 +34,6 @@ export declare function mapToMastraMCPServers(config: MCPConfiguration, orgJwt?:
|
|
|
48
34
|
* here so MCP remote transports honor `HTTPS_PROXY` without `globalThis`
|
|
49
35
|
* mutation. Stdio transports ignore it (they're child processes).
|
|
50
36
|
*/
|
|
51
|
-
innerFetch?: typeof fetch,
|
|
52
|
-
/**
|
|
53
|
-
* Optional per-server HTTP-error capture map. When supplied, every remote
|
|
54
|
-
* server's transport fetch is wrapped so the latest non-2xx `{ status, body }`
|
|
55
|
-
* is recorded into `captures.get(serverName)`. The harness reads this in its
|
|
56
|
-
* discovery / reconnect catch to recover the status `@mastra/mcp` discards
|
|
57
|
-
* (W-23231658). Stdio servers contribute no entry. Omitted in call sites
|
|
58
|
-
* that don't need recovery (e.g. unit tests of the mapping shape).
|
|
59
|
-
*/
|
|
60
|
-
captures?: Map<string, HttpErrorCapture>, providers?: McpAuthProviders,
|
|
37
|
+
innerFetch?: typeof fetch, providers?: McpAuthProviders,
|
|
61
38
|
/** AgentConfig.name; see {@link resolveMcpClientName}. */
|
|
62
39
|
agentName?: string): Record<string, MastraMCPServerDefinition>;
|