@salesforce/sfdx-agent-sdk 0.9.0 → 0.11.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 CHANGED
@@ -245,12 +245,27 @@ type MCPRemoteServerConfig = {
245
245
 
246
246
  #### `McpServerInfo`
247
247
 
248
- | Field | Type | Description |
249
- | -------- | ------------------------------------------------------ | --------------------------------------- |
250
- | `name` | `string` | Server identifier. |
251
- | `status` | `'connected' \| 'connecting' \| 'disabled' \| 'error'` | Connection state. |
252
- | `tools` | [`McpToolInfo[]`](#mcptoolinfo) | Discovered tools (name + metadata). |
253
- | `error?` | `string` | Error message when status is `'error'`. |
248
+ | Field | Type | Description |
249
+ | -------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
250
+ | `name` | `string` | Server identifier. |
251
+ | `status` | `'connected' \| 'connecting' \| 'disabled' \| 'error' \| 'reconnecting'` | Connection state. `'reconnecting'` is reported during an `Agent.reconnectMcpServer(name)` call against a previously-`'connected'` server. |
252
+ | `tools` | [`McpToolInfo[]`](#mcptoolinfo) | Discovered tools (name + metadata). |
253
+ | `error?` | `string` | Sanitized human-readable error message when status is `'error'`. Stack frames and file paths are stripped at the harness boundary. |
254
+ | `errorDetail?` | [`McpServerErrorDetail`](#mcpservererrordetail) | Structured failure projection for programmatic routing (category / code / retriable). Populated when status is `'error'`. |
255
+
256
+ #### `McpServerErrorDetail`
257
+
258
+ Structured projection of an MCP server failure. Mirror is also attached to the `mcp-server-discovery-failed` telemetry
259
+ event so subscribers can route on it without pattern-matching `error.message`.
260
+
261
+ | Field | Type | Description |
262
+ | ----------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
263
+ | `category` | `McpServerErrorCategory` | Stable category for routing logic. Set is additive across minor versions — values are added but never renamed or removed. |
264
+ | `code?` | `number` | JSON-RPC error code from the underlying `McpError`, when the failure originated as a JSON-RPC error. Undefined for transport-level failures and for harnesses whose underlying SDK does not surface the code (e.g. Claude). |
265
+ | `retriable` | `boolean` | Whether the SDK considers the failure transient (worth `Agent.reconnectMcpServer` / `Agent.refreshMcpAuth`) versus fatal. |
266
+
267
+ `McpServerErrorCategory` values: `'connect-timeout'`, `'http-401'`, `'http-403'`, `'http-4xx'`, `'http-5xx'`,
268
+ `'transport-eof'`, `'protocol-error'`, `'config-error'`, `'aborted'`, `'unknown'`.
254
269
 
255
270
  #### `McpToolInfo`
256
271
 
@@ -510,6 +525,12 @@ Returns `true` if the URL matches a Salesforce Hosted MCP Server endpoint (prod,
510
525
 
511
526
  - `ModelName` — enum of supported model identifiers
512
527
  - `SfApiEnv` — Salesforce API environment enum (`dev`, `perf`, `prod`, `stage`, `test`)
528
+ - `inferSfApiEnv(instanceUrl, options?)` — maps an instance URL to a `SfApiEnv`. Re-exported from
529
+ `@salesforce/agentic-common` for consumers that need the mapping without an `OrgConnection` (e.g. building a
530
+ Salesforce platform MCP URL). Defaults the unrecognized-`.pc-rnd.` fallback to `SfApiEnv.Test` (`.pc-rnd.` is an
531
+ internal-only OrgFarm domain — not Prod by definition); pass `{ pcRndFallback: SfApiEnv.Prod }` to override. See the
532
+ [`@salesforce/agentic-common` README](../agentic-common/README.md#infersfapienvinstanceurl-options-sfapienv) for the
533
+ full resolution order.
513
534
 
514
535
  ### Telemetry & structured logs
515
536
 
@@ -700,11 +721,14 @@ All variants share `{ type: <discriminant>, timestamp: Date }` plus the fields b
700
721
  | `tool-approval-resolved` | `agentId`, `threadId`, `toolCallId`, `approved` |
701
722
  | `mcp-server-discovery-started` | `agentId`, `serverName` |
702
723
  | `mcp-server-discovery-completed` | `agentId`, `serverName`, `toolCount`, `durationMs` |
703
- | `mcp-server-discovery-failed` | `agentId`, `serverName`, `durationMs`, `error` |
704
- | `mcp-discovery-snapshot` | `agentId`, `servers` (`McpServerInfo[]`) |
705
-
706
- `mcp-discovery-snapshot` is emitted by harnesses whose runtime exposes aggregate MCP server status (currently only the
707
- Claude harness, once per `init` arrival). Its `servers` payload mirrors `agent.getMcpServerInfo()`.
724
+ | `mcp-server-discovery-failed` | `agentId`, `serverName`, `durationMs`, `error`, `errorDetail?` ([`McpServerErrorDetail`](#mcpservererrordetail)) |
725
+ | `mcp-server-status-changed` | `agentId`, `serverName`, `previousStatus`, `nextStatus`, `error?` |
726
+
727
+ `mcp-server-status-changed` fires on every per-server status transition emitted in addition to the discovery trio so
728
+ consumers can route on the `(previousStatus, nextStatus)` pair without polling `getMcpServerInfo()`. A reconnect on a
729
+ previously-`connected` server emits, in order: `connected → reconnecting`, then on success `reconnecting → connected`,
730
+ or on failure `reconnecting → error`. Emitted by the Mastra harness today; the Claude harness emits the discovery trio
731
+ on the same lifecycle moments and consumers can converge on the same `getMcpServerInfo()` snapshot from either side.
708
732
 
709
733
  Every chat entry point (`chat`, `submitToolResult`, `approveToolCall`, `declineToolCall`) emits exactly one
710
734
  `chat-stream-started` followed by exactly one terminal event — either `chat-stream-completed` (natural completion) or
@@ -55,15 +55,28 @@ export interface ChatSession {
55
55
  */
56
56
  chat(message: string, options?: ChatOptions): Promise<ChatStreamResult>;
57
57
  /**
58
- * Feed the result of a client-side tool execution back into the conversation
59
- * and resume stream generation.
58
+ * Feed the result of a **consumer-executed (client-side) tool** back into the
59
+ * conversation and resume stream generation.
60
60
  *
61
- * Resumes the suspended agentic loop from the most recent `chat()` call.
61
+ * "Client-side tool" means a tool you declared in {@link AgentConfig.tools}
62
+ * without an `execute` function — the SDK registers its name + schema with the
63
+ * model but does not run it. When the model calls one, the chat eventStream
64
+ * emits a `tool-call` event and ends with `finishReason: 'tool-calls'`. Your
65
+ * application runs the tool however it likes (HTTP call, DB query, UI prompt,
66
+ * etc.) and calls this method with the result; the agent loop resumes and
67
+ * produces its next turn on the returned `ChatStreamResult.eventStream`.
68
+ *
69
+ * Use this method ONLY for client-side tools. Tools provided via
70
+ * {@link AgentConfig.mcpServers} are executed by the harness — their results
71
+ * flow naturally as `tool-result` events without consumer involvement.
72
+ * Human-in-the-loop approval of harness-executed tools uses
73
+ * {@link approveToolCall} / {@link declineToolCall}, not this method.
62
74
  *
63
75
  * On pre-stream failure, subscribers are notified with `ErrorEvent` + `FinishEvent` before
64
76
  * the returned promise rejects. See the interface-level "Failure handling" notes for details.
65
77
  *
66
- * @param toolResult - The completed tool execution result.
78
+ * @param toolResult - The completed tool execution result. `toolCallId` and
79
+ * `toolName` MUST match the values from the originating `tool-call` event.
67
80
  */
68
81
  submitToolResult(toolResult: ToolResultInfo): Promise<ChatStreamResult>;
69
82
  /**
@@ -151,6 +164,17 @@ export declare class DefaultChatSession implements ChatSession {
151
164
  private readonly parentUnsubs;
152
165
  private readonly clock;
153
166
  private readonly idGenerator;
167
+ /**
168
+ * Tracks the start timestamp of every in-flight `tool-call` keyed by `toolCallId`,
169
+ * used by `deriveToolTelemetry` to compute `tool-execution-completed.durationMs`.
170
+ *
171
+ * Lifetime is per-session (not per-stream) so a `tool-call` on the initial chat stream
172
+ * pairs with a `tool-result` on the approval-continuation / submit-tool-result
173
+ * continuation stream — those are continuations of the same logical chat turn. Cleared
174
+ * on every terminal `finish` ChatEvent (the turn closed cleanly; any unmatched entries
175
+ * are stale and should not bleed into the next turn).
176
+ */
177
+ private readonly toolStartMs;
154
178
  private disposed;
155
179
  /**
156
180
  * @param harness - The agent harness managing thread and message lifecycle.
@@ -195,8 +219,12 @@ export declare class DefaultChatSession implements ChatSession {
195
219
  * - MUST derive `tool-execution-started`, `tool-execution-completed`, and
196
220
  * `tool-approval-requested` telemetry from the corresponding `ChatEvent` types so harness
197
221
  * implementations don't reimplement this. `tool-execution-completed.durationMs` is measured
198
- * between the matching `tool-call` and `tool-result`; an unmatched `tool-result` (e.g. from
199
- * a resume flow that crosses a stream boundary) is skipped.
222
+ * between the matching `tool-call` and `tool-result`, including across approval / resume
223
+ * continuations within the same chat turn those are continuations of one logical turn
224
+ * and the tool start timestamp lives on the session, not the stream. The tracking map is
225
+ * cleared on every terminal `finish` ChatEvent so stale entries from one turn never bleed
226
+ * into the next. An unmatched `tool-result` (no recorded `tool-call` in the session) is
227
+ * still skipped.
200
228
  *
201
229
  * `chat-stream-started` is emitted by the entry-point method (chat / submitToolResult /
202
230
  * approveToolCall / declineToolCall) before the harness call so that pre-stream rejections
@@ -269,9 +297,13 @@ export declare class DefaultChatSession implements ChatSession {
269
297
  * implementation free of telemetry plumbing — they just yield the right `ChatEvent` shapes.
270
298
  *
271
299
  * `tool-execution-completed.durationMs` is measured between the matching `tool-call` and
272
- * `tool-result` using `this.clock`. An unmatched `tool-result` (e.g. a resume flow that
273
- * crosses a stream boundary so the original `tool-call` was on a previous stream) is skipped
274
- * the consumer correlates by `toolCallId` against the earlier `tool-execution-started`.
300
+ * `tool-result` using `this.clock`. The tracking map (`this.toolStartMs`) lives on the
301
+ * session, so a `tool-call` on the initial stream pairs with a `tool-result` on the
302
+ * approval-continuation / submit-tool-result continuation stream those are continuations
303
+ * of the same logical chat turn. The map is cleared on every terminal `finish` ChatEvent
304
+ * (handled by `wrapEventStream`). An unmatched `tool-result` (no recorded `tool-call`) is
305
+ * skipped — the consumer correlates by `toolCallId` against the earlier
306
+ * `tool-execution-started`.
275
307
  */
276
308
  private deriveToolTelemetry;
277
309
  /**
@@ -20,6 +20,17 @@ export class DefaultChatSession {
20
20
  parentUnsubs;
21
21
  clock;
22
22
  idGenerator;
23
+ /**
24
+ * Tracks the start timestamp of every in-flight `tool-call` keyed by `toolCallId`,
25
+ * used by `deriveToolTelemetry` to compute `tool-execution-completed.durationMs`.
26
+ *
27
+ * Lifetime is per-session (not per-stream) so a `tool-call` on the initial chat stream
28
+ * pairs with a `tool-result` on the approval-continuation / submit-tool-result
29
+ * continuation stream — those are continuations of the same logical chat turn. Cleared
30
+ * on every terminal `finish` ChatEvent (the turn closed cleanly; any unmatched entries
31
+ * are stale and should not bleed into the next turn).
32
+ */
33
+ toolStartMs = new Map();
23
34
  disposed = false;
24
35
  /**
25
36
  * @param harness - The agent harness managing thread and message lifecycle.
@@ -103,8 +114,12 @@ export class DefaultChatSession {
103
114
  * - MUST derive `tool-execution-started`, `tool-execution-completed`, and
104
115
  * `tool-approval-requested` telemetry from the corresponding `ChatEvent` types so harness
105
116
  * implementations don't reimplement this. `tool-execution-completed.durationMs` is measured
106
- * between the matching `tool-call` and `tool-result`; an unmatched `tool-result` (e.g. from
107
- * a resume flow that crosses a stream boundary) is skipped.
117
+ * between the matching `tool-call` and `tool-result`, including across approval / resume
118
+ * continuations within the same chat turn those are continuations of one logical turn
119
+ * and the tool start timestamp lives on the session, not the stream. The tracking map is
120
+ * cleared on every terminal `finish` ChatEvent so stale entries from one turn never bleed
121
+ * into the next. An unmatched `tool-result` (no recorded `tool-call` in the session) is
122
+ * still skipped.
108
123
  *
109
124
  * `chat-stream-started` is emitted by the entry-point method (chat / submitToolResult /
110
125
  * approveToolCall / declineToolCall) before the harness call so that pre-stream rejections
@@ -112,18 +127,21 @@ export class DefaultChatSession {
112
127
  * `durationMs` measures real elapsed time on both terminal events.
113
128
  */
114
129
  async *wrapEventStream(stream, startedAt) {
115
- const toolStartMs = new Map();
116
130
  let sawFinish = false;
117
131
  let lastError;
118
132
  let finishUsage;
119
133
  try {
120
134
  for await (const event of stream) {
121
135
  this.chatEventBus.emit(event);
122
- this.deriveToolTelemetry(event, toolStartMs);
136
+ this.deriveToolTelemetry(event);
123
137
  yield event;
124
138
  if (event.type === 'finish') {
125
139
  sawFinish = true;
126
140
  finishUsage = event.usage;
141
+ // Turn boundary — clear any unmatched in-flight tool starts so a stale
142
+ // entry from one turn cannot pair with an unrelated tool-result on the
143
+ // next turn. Matched pairs already removed their entry in `deriveToolTelemetry`.
144
+ this.toolStartMs.clear();
127
145
  }
128
146
  if (event.type === 'error')
129
147
  lastError = event.error;
@@ -140,6 +158,10 @@ export class DefaultChatSession {
140
158
  const finishEvent = { type: 'finish', finishReason: 'error' };
141
159
  this.chatEventBus.emit(finishEvent);
142
160
  yield finishEvent;
161
+ // Match the natural-finish branch: every terminal `finish` clears the
162
+ // tool-start tracking map so a stale entry can't pair with an unrelated
163
+ // tool-result on the next turn.
164
+ this.toolStartMs.clear();
143
165
  }
144
166
  const finishedAt = this.clock.now();
145
167
  const durationMs = finishedAt.getTime() - startedAt.getTime();
@@ -312,13 +334,17 @@ export class DefaultChatSession {
312
334
  * implementation free of telemetry plumbing — they just yield the right `ChatEvent` shapes.
313
335
  *
314
336
  * `tool-execution-completed.durationMs` is measured between the matching `tool-call` and
315
- * `tool-result` using `this.clock`. An unmatched `tool-result` (e.g. a resume flow that
316
- * crosses a stream boundary so the original `tool-call` was on a previous stream) is skipped
317
- * the consumer correlates by `toolCallId` against the earlier `tool-execution-started`.
337
+ * `tool-result` using `this.clock`. The tracking map (`this.toolStartMs`) lives on the
338
+ * session, so a `tool-call` on the initial stream pairs with a `tool-result` on the
339
+ * approval-continuation / submit-tool-result continuation stream those are continuations
340
+ * of the same logical chat turn. The map is cleared on every terminal `finish` ChatEvent
341
+ * (handled by `wrapEventStream`). An unmatched `tool-result` (no recorded `tool-call`) is
342
+ * skipped — the consumer correlates by `toolCallId` against the earlier
343
+ * `tool-execution-started`.
318
344
  */
319
- deriveToolTelemetry(event, toolStartMs) {
345
+ deriveToolTelemetry(event) {
320
346
  if (event.type === 'tool-call') {
321
- toolStartMs.set(event.toolCallId, this.clock.now().getTime());
347
+ this.toolStartMs.set(event.toolCallId, this.clock.now().getTime());
322
348
  this.telemetryBus.emit({
323
349
  type: 'tool-execution-started',
324
350
  timestamp: this.clock.now(),
@@ -331,10 +357,10 @@ export class DefaultChatSession {
331
357
  });
332
358
  }
333
359
  else if (event.type === 'tool-result') {
334
- const start = toolStartMs.get(event.toolCallId);
360
+ const start = this.toolStartMs.get(event.toolCallId);
335
361
  if (start === undefined)
336
362
  return;
337
- toolStartMs.delete(event.toolCallId);
363
+ this.toolStartMs.delete(event.toolCallId);
338
364
  this.telemetryBus.emit({
339
365
  type: 'tool-execution-completed',
340
366
  timestamp: this.clock.now(),
@@ -208,16 +208,22 @@ export interface AgentHarness {
208
208
  */
209
209
  stream(agentId: string, threadId: string, message: string, options?: StreamOptions): Promise<ChatStreamResult>;
210
210
  /**
211
- * Feed the result of a client-side tool execution back into the conversation
212
- * and resume stream generation.
211
+ * Feed the result of a **consumer-executed (client-side) tool** back into the
212
+ * conversation and resume stream generation. Implements the consumer-facing
213
+ * {@link ChatSession.submitToolResult} contract: see that JSDoc for the
214
+ * full consumer-level semantics.
213
215
  *
214
- * Resumes the suspended agentic loop from the most recent `stream()` call on
215
- * the given thread. The harness feeds `toolResult` into the loop and returns
216
- * a continuation stream.
216
+ * Called only for tools declared in {@link HarnessAgentConfig.tools} (no
217
+ * `execute` function). Tools provided via `mcpServers` are executed by the
218
+ * harness directly and never reach this method. The harness resumes the
219
+ * suspended agentic loop from the most recent `stream()` call on the given
220
+ * thread, feeds `toolResult` into the loop, and returns a continuation
221
+ * stream the consumer iterates for the model's next turn.
217
222
  *
218
223
  * @param agentId - ID of the agent.
219
224
  * @param threadId - ID of the conversation thread.
220
- * @param toolResult - The completed tool execution result.
225
+ * @param toolResult - The completed tool execution result; `toolCallId`
226
+ * matches the id from the originating `tool-call` event.
221
227
  */
222
228
  submitToolResult(agentId: string, threadId: string, toolResult: ToolResultInfo): Promise<ChatStreamResult>;
223
229
  /**
package/dist/index.d.ts CHANGED
@@ -4,10 +4,10 @@ export type { ToolDefinition, ToolCallInfo, ToolResultInfo } from './types/tools
4
4
  export type { FinishReason, UsageMetadata } from './types/usage.js';
5
5
  export type { AgentConfig, HarnessAgentConfig, StreamOptions } from './harness/harness-config.js';
6
6
  export { DEFAULT_MAX_STEPS } from './harness/harness-config.js';
7
- export type { MCPConfiguration, MCPServerConfig, MCPStdioServerConfig, MCPRemoteServerConfig, McpServerInfo, McpToolInfo, McpToolAnnotations, } from './mcp-config.js';
7
+ export type { MCPConfiguration, MCPServerConfig, MCPStdioServerConfig, MCPRemoteServerConfig, McpServerInfo, McpServerErrorCategory, McpServerErrorDetail, McpToolInfo, McpToolAnnotations, } from './mcp-config.js';
8
8
  export { McpServerStatus } from './mcp-config.js';
9
9
  export { ModelName } from '@salesforce/llm-gateway-sdk';
10
- export { SfApiEnv } from '@salesforce/agentic-common';
10
+ export { inferSfApiEnv, SfApiEnv } from '@salesforce/agentic-common';
11
11
  export { type AgentManager, type RestoreFailure, createAgentManager } from './agent-manager.js';
12
12
  export { type Agent } from './agent.js';
13
13
  export { type ChatSession, type ChatOptions } from './chat-session.js';
@@ -16,7 +16,7 @@ export type { AgentHarness, HarnessFactory, WithAgentConfig, ConfigOf } from './
16
16
  export { SUPPORTED_PROTOCOL_VERSIONS } from './harness/agent-harness.js';
17
17
  export { HarnessBusOwner } from './harness/harness-bus-owner.js';
18
18
  export { AgentSDKError, AgentSDKErrorType } from './errors.js';
19
- export type { AgentCreatedEvent, AgentDestroyedEvent, ChatStreamCompletedEvent, ChatStreamErrorEvent, ChatStreamStartedEvent, ChatStreamTrigger, McpDiscoverySnapshotEvent, McpServerDiscoveryCompletedEvent, McpServerDiscoveryFailedEvent, McpServerDiscoveryStartedEvent, SessionCreatedEvent, SessionDestroyedEvent, TelemetryEvent, TelemetryEventCallback, ToolApprovalRequestedEvent, ToolApprovalResolvedEvent, ToolExecutionCompletedEvent, ToolExecutionStartedEvent, } from './types/telemetry-events.js';
19
+ export type { AgentCreatedEvent, AgentDestroyedEvent, ChatStreamCompletedEvent, ChatStreamErrorEvent, ChatStreamStartedEvent, ChatStreamTrigger, McpServerDiscoveryCompletedEvent, McpServerDiscoveryFailedEvent, McpServerDiscoveryStartedEvent, McpServerStatusChangedEvent, SessionCreatedEvent, SessionDestroyedEvent, TelemetryEvent, TelemetryEventCallback, ToolApprovalRequestedEvent, ToolApprovalResolvedEvent, ToolExecutionCompletedEvent, ToolExecutionStartedEvent, } from './types/telemetry-events.js';
20
20
  export type { LogLevel, LogRecord, Unsubscribe } from '@salesforce/agentic-common';
21
21
  export { resolveMcpServerHeaders } from './mcp-auth.js';
22
22
  export type { OrgConnection, OrgConnectionFactory } from '@salesforce/agentic-common';
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  export { DEFAULT_MAX_STEPS } from './harness/harness-config.js';
6
6
  export { McpServerStatus } from './mcp-config.js';
7
7
  export { ModelName } from '@salesforce/llm-gateway-sdk';
8
- export { SfApiEnv } from '@salesforce/agentic-common';
8
+ export { inferSfApiEnv, SfApiEnv } from '@salesforce/agentic-common';
9
9
  // ── Agent Layer ─────────────────────────────────────────────────────
10
10
  export { createAgentManager } from './agent-manager.js';
11
11
  export {} from './agent.js';
@@ -17,7 +17,18 @@ export type MCPStdioServerConfig = {
17
17
  command: string;
18
18
  /** Arguments passed to the command. */
19
19
  args?: string[];
20
- /** Environment variables set when running the server process. */
20
+ /**
21
+ * Environment variables set when running the server process.
22
+ *
23
+ * **JWT rotation caveat.** These env vars are forwarded verbatim to the
24
+ * child process at spawn time. The MCP SDK's stdio transport has no
25
+ * per-request hook, so a JWT-bearing env var (e.g. `SF_ACCESS_TOKEN`)
26
+ * is frozen at spawn. After a JWT rotation, an stdio MCP server that
27
+ * depends on the env value will see a stale value until the child is
28
+ * respawned — call `Agent.reconnectMcpServer(name)` to refresh.
29
+ * Remote (HTTP) servers do not have this limitation; their auth headers
30
+ * are re-resolved per request through a host-side fetch hook.
31
+ */
21
32
  env?: Record<string, string>;
22
33
  /** Whether this server is enabled. Defaults to `true`. */
23
34
  enabled?: boolean;
@@ -41,7 +52,14 @@ export declare enum McpServerStatus {
41
52
  Connected = "connected",
42
53
  Connecting = "connecting",
43
54
  Disabled = "disabled",
44
- Error = "error"
55
+ Error = "error",
56
+ /**
57
+ * Transport is being cycled after the server was previously `Connected` —
58
+ * e.g. an explicit `Agent.reconnectMcpServer(name)` call. The server stays
59
+ * in this state until the new transport + discovery resolves to either
60
+ * `Connected` (success) or `Error` (failure).
61
+ */
62
+ Reconnecting = "reconnecting"
45
63
  }
46
64
  /**
47
65
  * Behavioral / UI-presentation hints for an MCP-discovered tool.
@@ -106,10 +124,79 @@ export type McpToolInfo = {
106
124
  /** Behavioral / UI-presentation hints declared by the MCP server. */
107
125
  annotations?: McpToolAnnotations;
108
126
  };
127
+ /**
128
+ * Stable category for routing logic on an MCP server failure. Consumers writing
129
+ * reconnect / retry / UX flows match on this instead of regexing the
130
+ * free-form `error` string. The set is additive across minor versions — values
131
+ * may be added but never removed or renamed.
132
+ */
133
+ export type McpServerErrorCategory =
134
+ /** 3s default or configured timeout exceeded. */
135
+ 'connect-timeout'
136
+ /** Unauthorized — JWT missing/expired. */
137
+ | 'http-401'
138
+ /** Forbidden — JWT valid but the principal lacks permission. */
139
+ | 'http-403'
140
+ /** Other 4xx — likely a config issue. */
141
+ | 'http-4xx'
142
+ /** Server-side error — transient or fatal. */
143
+ | 'http-5xx'
144
+ /** Connection closed unexpectedly. */
145
+ | 'transport-eof'
146
+ /** MCP spec violation in response (JSON-RPC framing or schema). */
147
+ | 'protocol-error'
148
+ /** Bad URL, bad headers, bad config. */
149
+ | 'config-error'
150
+ /** Consumer-driven `AbortSignal`. */
151
+ | 'aborted'
152
+ /** Could not be classified. */
153
+ | 'unknown';
154
+ /**
155
+ * Structured projection of an MCP server failure attached to an
156
+ * {@link McpServerInfo} (post-discovery polling surface) or an
157
+ * {@link McpServerDiscoveryFailedEvent} (per-event telemetry).
158
+ *
159
+ * Replaces consumer pattern-matching against the free-form `error` string for
160
+ * routing decisions. The free-form string is still surfaced (sanitized — no V8
161
+ * stack frames or file paths) for human-readable display and logs.
162
+ */
163
+ export type McpServerErrorDetail = {
164
+ /** Stable category for routing logic. */
165
+ category: McpServerErrorCategory;
166
+ /**
167
+ * JSON-RPC error code from the underlying `McpError`, when the failure
168
+ * originated as a JSON-RPC error. `undefined` for transport-level failures
169
+ * (DNS, TCP, TLS) and for harnesses whose underlying SDK does not surface
170
+ * the JSON-RPC code (e.g. the Claude harness).
171
+ *
172
+ * See [@modelcontextprotocol/sdk types](https://www.npmjs.com/package/@modelcontextprotocol/sdk)
173
+ * (`McpError`) and the JSON-RPC 2.0
174
+ * [error object](https://www.jsonrpc.org/specification#error_object).
175
+ */
176
+ code?: number;
177
+ /**
178
+ * Whether the failure is transient (worth a retry via
179
+ * `Agent.reconnectMcpServer` / `Agent.refreshMcpAuth`) vs fatal (consumer
180
+ * must fix config). Harness implementations populate this with their best
181
+ * heuristic; consumers may override per-category.
182
+ */
183
+ retriable: boolean;
184
+ };
109
185
  /** Runtime status of a configured MCP server, including its discovered tools. */
110
186
  export type McpServerInfo = {
111
187
  name: string;
112
188
  status: McpServerStatus;
113
189
  tools: McpToolInfo[];
190
+ /**
191
+ * Human-readable error string. Sanitized at the harness boundary — no V8
192
+ * stack frames, no file paths, no internal class names. Safe for UI
193
+ * display and telemetry tags. For programmatic routing, prefer
194
+ * {@link errorDetail}.
195
+ */
114
196
  error?: string;
197
+ /**
198
+ * Structured failure projection. Populated by the harness when `status` is
199
+ * `Error`; absent otherwise. See {@link McpServerErrorDetail}.
200
+ */
201
+ errorDetail?: McpServerErrorDetail;
115
202
  };
@@ -9,5 +9,12 @@ export var McpServerStatus;
9
9
  McpServerStatus["Connecting"] = "connecting";
10
10
  McpServerStatus["Disabled"] = "disabled";
11
11
  McpServerStatus["Error"] = "error";
12
+ /**
13
+ * Transport is being cycled after the server was previously `Connected` —
14
+ * e.g. an explicit `Agent.reconnectMcpServer(name)` call. The server stays
15
+ * in this state until the new transport + discovery resolves to either
16
+ * `Connected` (success) or `Error` (failure).
17
+ */
18
+ McpServerStatus["Reconnecting"] = "reconnecting";
12
19
  })(McpServerStatus || (McpServerStatus = {}));
13
20
  //# sourceMappingURL=mcp-config.js.map
@@ -1,5 +1,5 @@
1
1
  import type { EventBus } from '@salesforce/agentic-common';
2
- import type { McpServerInfo, McpToolAnnotations } from '../mcp-config.js';
2
+ import type { McpServerErrorDetail, McpServerStatus, McpToolAnnotations } from '../mcp-config.js';
3
3
  import type { UsageMetadata } from './usage.js';
4
4
  /**
5
5
  * Telemetry events emitted by the Agent SDK.
@@ -99,22 +99,41 @@ export type McpServerDiscoveryFailedEvent = Base<'mcp-server-discovery-failed'>
99
99
  serverName: string;
100
100
  durationMs: number;
101
101
  error: Error;
102
+ /**
103
+ * Structured failure projection. Mirrors `McpServerInfo.errorDetail` so
104
+ * subscribers can route on category / code / retriable without
105
+ * pattern-matching `error.message`. Populated by harnesses that classify
106
+ * MCP failures.
107
+ */
108
+ errorDetail?: McpServerErrorDetail;
102
109
  };
103
110
  /**
104
- * Whole-snapshot MCP discovery event emitted by harnesses whose underlying SDK
105
- * surfaces MCP status in a single aggregate message rather than as per-server
106
- * lifecycle events. Currently emitted by the Claude harness on each
107
- * `SDKSystemMessage init` arrival.
111
+ * Per-transition MCP transport-state event emitted whenever a server's
112
+ * `McpServerStatus` changes. Consumers that need to react to specific
113
+ * transitions filter on the `(previousStatus, nextStatus)` pair.
108
114
  *
109
- * The `servers` payload mirrors the result of `harness.getMcpServerInfo(agentId)`
110
- * at the moment of the snapshot, so consumers can read status + tools without
111
- * a follow-up call.
115
+ * Emitted in addition to `mcp-server-discovery-{started,completed,failed}`
116
+ * the discovery trio reports work boundaries (start/end of a discovery run)
117
+ * while this event reports the status the server is now in. A reconnect on a
118
+ * `Connected` server emits, in order:
119
+ * `Connected → Reconnecting` (transport is being cycled),
120
+ * then on success `Reconnecting → Connected`, or on failure
121
+ * `Reconnecting → Error`.
122
+ *
123
+ * Emitted by the Mastra harness today. The Claude harness emits it through
124
+ * the per-server discovery telemetry triple (started → completed/failed) but
125
+ * not as a standalone transition event — both harnesses converge on the same
126
+ * `McpServerInfo[]` shape via `getMcpServerInfo()`.
112
127
  */
113
- export type McpDiscoverySnapshotEvent = Base<'mcp-discovery-snapshot'> & {
128
+ export type McpServerStatusChangedEvent = Base<'mcp-server-status-changed'> & {
114
129
  agentId: string;
115
- servers: McpServerInfo[];
130
+ serverName: string;
131
+ previousStatus: McpServerStatus;
132
+ nextStatus: McpServerStatus;
133
+ /** Populated when `nextStatus === 'error'`. */
134
+ error?: string;
116
135
  };
117
- export type TelemetryEvent = AgentCreatedEvent | AgentDestroyedEvent | SessionCreatedEvent | SessionDestroyedEvent | ChatStreamStartedEvent | ChatStreamCompletedEvent | ChatStreamErrorEvent | ToolExecutionStartedEvent | ToolExecutionCompletedEvent | ToolApprovalRequestedEvent | ToolApprovalResolvedEvent | McpServerDiscoveryStartedEvent | McpServerDiscoveryCompletedEvent | McpServerDiscoveryFailedEvent | McpDiscoverySnapshotEvent;
136
+ export type TelemetryEvent = AgentCreatedEvent | AgentDestroyedEvent | SessionCreatedEvent | SessionDestroyedEvent | ChatStreamStartedEvent | ChatStreamCompletedEvent | ChatStreamErrorEvent | ToolExecutionStartedEvent | ToolExecutionCompletedEvent | ToolApprovalRequestedEvent | ToolApprovalResolvedEvent | McpServerDiscoveryStartedEvent | McpServerDiscoveryCompletedEvent | McpServerDiscoveryFailedEvent | McpServerStatusChangedEvent;
118
137
  export type TelemetryEventCallback = (event: TelemetryEvent) => void;
119
138
  export type TelemetryBus = EventBus<TelemetryEvent>;
120
139
  export {};
@@ -1,17 +1,28 @@
1
1
  import type { McpToolAnnotations } from '../mcp-config.js';
2
2
  /**
3
- * Declares a consumer-executed tool that an agent can invoke.
3
+ * Declares a **consumer-executed (client-side) tool** that an agent can invoke.
4
4
  *
5
5
  * Tools declared here are schema-only: the harness registers the name,
6
6
  * description, and input schema with the model so it can generate tool-call
7
7
  * chunks, but the harness never executes these tools itself. When the model
8
8
  * calls one, the stream ends with `finishReason: 'tool-calls'` and the
9
9
  * consumer is responsible for running the tool locally and feeding the result
10
- * back via `ChatSession.submitToolResult()`.
10
+ * back via {@link ChatSession.submitToolResult}.
11
+ *
12
+ * Typical flow:
13
+ * 1. Pass `{ tools: [{ name, description, inputSchema }] }` in `AgentConfig`.
14
+ * 2. Iterate `eventStream` from `session.chat(...)`; capture the `tool-call`
15
+ * event for one of your declared tools.
16
+ * 3. Run the tool locally (HTTP call, DB query, UI prompt — anything).
17
+ * 4. Call `session.submitToolResult({ toolCallId, toolName, result, isError })`
18
+ * with the same `toolCallId` from the event. Iterate the returned
19
+ * `ChatStreamResult.eventStream` for the model's next turn.
11
20
  *
12
21
  * For harness-executed (native) tools such as MCP tools, use
13
- * `AgentConfig.mcpServers`. To gate native tool calls with human approval,
14
- * use `StreamOptions.requireToolApproval`.
22
+ * `AgentConfig.mcpServers` those run in the harness and never need
23
+ * `submitToolResult`. To gate harness-executed tool calls with human approval,
24
+ * use `StreamOptions.requireToolApproval` plus `approveToolCall` /
25
+ * `declineToolCall`.
15
26
  *
16
27
  * Structurally compatible with AI SDK `LanguageModelV4FunctionTool`.
17
28
  */
@@ -53,15 +64,26 @@ export type ToolCallInfo = {
53
64
  /**
54
65
  * Base shape for a tool result.
55
66
  *
56
- * Extended by {@link ToolResultPart} (message content) and {@link ToolResultEvent} (stream event).
67
+ * Used by {@link ChatSession.submitToolResult} to feed a consumer-executed tool's
68
+ * output back into the agent loop. Extended by {@link ToolResultPart} (message
69
+ * content) and {@link ToolResultEvent} (stream event).
70
+ *
71
+ * `toolCallId` and `toolName` MUST match the values from the originating
72
+ * `tool-call` event the consumer observed on the chat eventStream — that's how
73
+ * the harness pairs the result with the suspended call.
57
74
  */
58
75
  export type ToolResultInfo = {
59
76
  /** Identifier linking this result to the originating tool call. */
60
77
  toolCallId: string;
61
78
  /** Name of the tool that produced this result. */
62
79
  toolName: string;
63
- /** The tool's output value. */
80
+ /** The tool's output value. Pass through whatever your tool produced. */
64
81
  result: unknown;
65
- /** Whether the tool execution resulted in an error. */
82
+ /**
83
+ * Set to `true` to signal that the tool execution failed. The model
84
+ * receives a structured error and can recover (apologize, retry with
85
+ * different args, ask the user, etc.) rather than seeing the failure as
86
+ * the SDK or harness crashing.
87
+ */
66
88
  isError?: boolean;
67
89
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-sdk",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -35,13 +35,13 @@
35
35
  "LICENSE.txt"
36
36
  ],
37
37
  "dependencies": {
38
- "@salesforce/agentic-common": "0.5.0",
39
- "@salesforce/llm-gateway-sdk": "0.6.0"
38
+ "@salesforce/agentic-common": "0.6.0",
39
+ "@salesforce/llm-gateway-sdk": "0.8.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@eslint/js": "^10.0.1",
43
- "@salesforce/sfdx-agent-harness-claude": "0.5.0",
44
- "@salesforce/sfdx-agent-harness-mastra": "0.8.0",
43
+ "@salesforce/sfdx-agent-harness-claude": "0.7.0",
44
+ "@salesforce/sfdx-agent-harness-mastra": "0.10.0",
45
45
  "@types/node": "^22.19.17",
46
46
  "@vitest/coverage-istanbul": "^4.1.7",
47
47
  "@vitest/eslint-plugin": "^1.6.17",