@salesforce/sfdx-agent-sdk 0.50.0 → 0.52.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 +11 -0
- package/README.md +44 -4
- package/dist/agent-manager.d.ts +11 -4
- package/dist/agent-manager.js +11 -9
- package/dist/agent.d.ts +17 -7
- package/dist/agent.js +15 -10
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +1 -0
- package/dist/harness/agent-harness.d.ts +22 -5
- package/dist/harness/harness-config.d.ts +20 -1
- package/dist/harness/public.d.ts +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/mcp-auth.d.ts +20 -0
- package/dist/mcp-auth.js +37 -0
- package/dist/mcp-config.d.ts +80 -2
- package/dist/mcp-config.js +6 -0
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to `@salesforce/sfdx-agent-sdk` are documented in this file.
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
+
## [0.52.0] - 2026-08-24
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
- **agent-sdk,harness-claude,harness-mastra,harness-openai**: support OAuth authProvider on remote MCP servers @W-23939243@ ([#757](https://github.com/forcedotcom/agentic-dx/pull/757))
|
|
10
|
+
|
|
11
|
+
## [0.51.0] - 2026-08-18
|
|
12
|
+
|
|
13
|
+
### Chores
|
|
14
|
+
- **deps-dev**: bump the eslint group across 1 directory with 2 updates ([#754](https://github.com/forcedotcom/agentic-dx/pull/754))
|
|
15
|
+
- **deps-dev**: bump the dev-dependencies group with 2 updates ([#753](https://github.com/forcedotcom/agentic-dx/pull/753))
|
|
16
|
+
|
|
6
17
|
## [0.50.0] - 2026-08-17
|
|
7
18
|
|
|
8
19
|
### Features
|
package/README.md
CHANGED
|
@@ -386,6 +386,45 @@ type MCPRemoteServerConfig = {
|
|
|
386
386
|
};
|
|
387
387
|
```
|
|
388
388
|
|
|
389
|
+
#### OAuth-protected remote MCP servers
|
|
390
|
+
|
|
391
|
+
To connect an OAuth-protected third-party remote MCP server (Figma, Linear, Notion, Sentry, Neon, Hugging Face, …),
|
|
392
|
+
supply an `McpOAuthClientProvider` through the manager's **`mcpAuthProviderResolver`**, not on the server config. The
|
|
393
|
+
provider is a credential-bearing runtime object, so it is deliberately kept **off** the persisted `AgentConfig` — it is
|
|
394
|
+
resolved per agent at install / restore / update and threaded to the harness at runtime, never serialized to disk.
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
const manager = await createAgentManager(storageRoot, harnessFactory, {
|
|
398
|
+
// (agentId, config) => Record<remoteServerName, McpOAuthClientProvider> | undefined
|
|
399
|
+
mcpAuthProviderResolver: (_agentId, _config) => ({ figma: myFigmaProvider }),
|
|
400
|
+
});
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The harness forwards the provider into the underlying HTTP transport (`@modelcontextprotocol/sdk`'s
|
|
404
|
+
`StreamableHTTPClientTransport` on Claude / OpenAI, `@mastra/mcp`'s `HttpServerDefinition` on Mastra). The transport
|
|
405
|
+
attaches existing tokens and silently refreshes them when possible. If user interaction is required, it invokes the
|
|
406
|
+
provider's `redirectToAuthorization` callback and surfaces `McpServerErrorDetail.category === 'oauth-required'`;
|
|
407
|
+
ADX does not open a browser or accept the authorization callback code. The static `headers` seam cannot provide this
|
|
408
|
+
recovery because headers are snapshotted at publish time.
|
|
409
|
+
|
|
410
|
+
- **`McpOAuthClientProvider`** (exported) is a harness-neutral interface that structurally mirrors the required core of
|
|
411
|
+
the MCP SDK's `OAuthClientProvider`, so a real provider assigns to it with no cast and the SDK stays free of a direct
|
|
412
|
+
`@modelcontextprotocol/sdk` dependency. **`McpAuthProviders`** (exported) is the `Readonly<Record<serverName,
|
|
413
|
+
McpOAuthClientProvider>>` the resolver returns.
|
|
414
|
+
- **One provider instance per server**, and return the **same** instance across calls — the upstream contract forbids
|
|
415
|
+
tokens / codes / verifiers crossing sessions, and a harness cycles a server when its provider reference changes (so
|
|
416
|
+
returning a fresh instance each update reconnects that server every time).
|
|
417
|
+
- **One auth owner per server (enforced centrally).** The SDK normalizes the resolver's output before handing it to a
|
|
418
|
+
harness: a provider is **dropped** for a Salesforce Platform URL (the rotating org JWT owns auth there), a stdio /
|
|
419
|
+
disabled / unknown server; and a provider **paired with a static `Authorization` header** is **rejected**
|
|
420
|
+
(`AgentSDKErrorType.INVALID_MCP_AUTH_CONFIG`) because the MCP SDK transport would let the static header silently win
|
|
421
|
+
over the provider token. For a provider-owned non-Salesforce server the harness attaches the proxy-aware inner `fetch`
|
|
422
|
+
(no Salesforce JWT injected, `HTTPS_PROXY` preserved) while the provider drives auth.
|
|
423
|
+
|
|
424
|
+
> **Interactive authorization stays with the consumer.** AFV's provider owns the Sign in UI, browser, VS Code callback,
|
|
425
|
+
> and code exchange. After it saves the new tokens, call `Agent.reconnectMcpServer` to connect. ADX owns runtime provider
|
|
426
|
+
> forwarding and silent refresh only; the transport's `finishAuth(code)` seam is not part of the public API.
|
|
427
|
+
|
|
389
428
|
**Tool-exposure policy** (which tools bypass the active runtime's tool-search deferral) is configured per-agent on the
|
|
390
429
|
harness extension surface, not per-server here. See `MastraAgentConfig.toolSearch.alwaysActive` and
|
|
391
430
|
`ClaudeAgentConfig.toolSearch.alwaysActive` for the entry shape that covers "all tools from server X", "tool Y on server
|
|
@@ -411,16 +450,17 @@ only `MCPRemoteServerConfig` carries it.
|
|
|
411
450
|
|
|
412
451
|
#### `McpServerErrorDetail`
|
|
413
452
|
|
|
414
|
-
Structured projection of an MCP server failure.
|
|
415
|
-
|
|
453
|
+
Structured projection of an MCP server failure. Route interactive authorization on `category === 'oauth-required'`;
|
|
454
|
+
an ordinary missing or invalid bearer remains `http-401`. The same detail is attached to `mcp-server-discovery-failed`
|
|
455
|
+
telemetry so subscribers can route on it without pattern-matching `error.message`.
|
|
416
456
|
|
|
417
457
|
| Field | Type | Description |
|
|
418
458
|
| ----------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
419
459
|
| `category` | `McpServerErrorCategory` | Stable category for routing logic. Set is additive across minor versions — values are added but never renamed or removed. |
|
|
420
460
|
| `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). |
|
|
421
|
-
| `retriable` | `boolean` | Whether the SDK considers the failure transient (worth `Agent.reconnectMcpServer`
|
|
461
|
+
| `retriable` | `boolean` | Whether the SDK considers the failure transient (worth `Agent.reconnectMcpServer`) versus fatal. |
|
|
422
462
|
|
|
423
|
-
`McpServerErrorCategory` values: `'connect-timeout'`, `'http-401'`, `'http-403'`, `'http-4xx'`, `'http-5xx'`,
|
|
463
|
+
`McpServerErrorCategory` values: `'connect-timeout'`, `'oauth-required'`, `'http-401'`, `'http-403'`, `'http-4xx'`, `'http-5xx'`,
|
|
424
464
|
`'transport-eof'`, `'protocol-error'`, `'config-error'`, `'aborted'`, `'unknown'`.
|
|
425
465
|
|
|
426
466
|
#### `McpToolInfo`
|
package/dist/agent-manager.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type Clock, LogBus, type LogRecord, type Unsubscribe, type UniqueIDGenerator } from '@salesforce/agentic-common';
|
|
2
2
|
import { type AgentHarness, type ConfigOf } from './harness/agent-harness.js';
|
|
3
3
|
import type { HarnessFactory } from './harness/harness-factory.js';
|
|
4
|
-
import { type AgentConfig } from './harness/harness-config.js';
|
|
5
|
-
import { type Agent } from './agent.js';
|
|
4
|
+
import { type AgentConfig, type McpAuthProviderResolver } from './harness/harness-config.js';
|
|
5
|
+
import { type Agent, type AgentRuntimeResolvers } from './agent.js';
|
|
6
6
|
import type { HooksForAgent } from './types/redaction.js';
|
|
7
7
|
import { type TelemetryEventCallback } from './types/telemetry-events.js';
|
|
8
8
|
import type { WireCommunicationEventCallback } from './types/wire-communication-event.js';
|
|
@@ -138,7 +138,7 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
|
|
|
138
138
|
private readonly harnessSupportedProviderHints;
|
|
139
139
|
private readonly agentIdGenerator;
|
|
140
140
|
private readonly agentConnectivityResolver;
|
|
141
|
-
private readonly
|
|
141
|
+
private readonly resolvers;
|
|
142
142
|
private readonly clock;
|
|
143
143
|
private readonly identityStore;
|
|
144
144
|
private readonly agents;
|
|
@@ -161,7 +161,7 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
|
|
|
161
161
|
* is private, so this is the only way to obtain an instance, but
|
|
162
162
|
* consumers should always go through {@link createAgentManager}.
|
|
163
163
|
*/
|
|
164
|
-
static __build<H extends AgentHarness>(harness: H, harnessSupportedProviderHints: readonly ProviderHint[], agentConnectivityResolver: AgentConnectivityResolver,
|
|
164
|
+
static __build<H extends AgentHarness>(harness: H, harnessSupportedProviderHints: readonly ProviderHint[], agentConnectivityResolver: AgentConnectivityResolver, resolvers: AgentRuntimeResolvers, storageRootFolder: string, agentIdGenerator: UniqueIDGenerator, clock: Clock, logBus: LogBus): Promise<DefaultAgentManager<H>>;
|
|
165
165
|
private init;
|
|
166
166
|
shutdown(): Promise<void>;
|
|
167
167
|
createAgent(projectRoot: string, config?: ConfigOf<H> & {
|
|
@@ -228,4 +228,11 @@ export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness>
|
|
|
228
228
|
export declare function createAgentManager<H extends AgentHarness = AgentHarness>(storageRootFolder: string, harnessFactory: HarnessFactory<H>, options?: {
|
|
229
229
|
connectivityResolver?: AgentConnectivityResolver;
|
|
230
230
|
hooksForAgent?: HooksForAgent;
|
|
231
|
+
/**
|
|
232
|
+
* Resolves the per-agent OAuth provider map for remote MCP servers.
|
|
233
|
+
* See {@link McpAuthProviderResolver}. Providers are supplied here at
|
|
234
|
+
* runtime and never persisted; required to connect OAuth-protected
|
|
235
|
+
* third-party remote MCP servers.
|
|
236
|
+
*/
|
|
237
|
+
mcpAuthProviderResolver?: McpAuthProviderResolver;
|
|
231
238
|
}): Promise<AgentManager<H>>;
|
package/dist/agent-manager.js
CHANGED
|
@@ -7,6 +7,7 @@ import { resolve } from 'node:path';
|
|
|
7
7
|
import { stat } from 'node:fs/promises';
|
|
8
8
|
import { SUPPORTED_PROTOCOL_VERSIONS } from './harness/agent-harness.js';
|
|
9
9
|
import { toHarnessConfig } from './harness/harness-config.js';
|
|
10
|
+
import { normalizeMcpAuthProviders } from './mcp-auth.js';
|
|
10
11
|
import { DefaultAgent } from './agent.js';
|
|
11
12
|
import { AgentSDKError, AgentSDKErrorType } from './errors.js';
|
|
12
13
|
import { TelemetryRouter } from './internal/telemetry-router.js';
|
|
@@ -28,7 +29,7 @@ export class DefaultAgentManager {
|
|
|
28
29
|
harnessSupportedProviderHints;
|
|
29
30
|
agentIdGenerator;
|
|
30
31
|
agentConnectivityResolver;
|
|
31
|
-
|
|
32
|
+
resolvers;
|
|
32
33
|
clock;
|
|
33
34
|
identityStore;
|
|
34
35
|
agents = new Map();
|
|
@@ -43,11 +44,11 @@ export class DefaultAgentManager {
|
|
|
43
44
|
wireRouter;
|
|
44
45
|
unroutedUnsubs;
|
|
45
46
|
disposed = false;
|
|
46
|
-
constructor(harness, harnessSupportedProviderHints, agentConnectivityResolver,
|
|
47
|
+
constructor(harness, harnessSupportedProviderHints, agentConnectivityResolver, resolvers, identityStore, agentIdGenerator, clock, logBus) {
|
|
47
48
|
this.harness = harness;
|
|
48
49
|
this.harnessSupportedProviderHints = harnessSupportedProviderHints;
|
|
49
50
|
this.agentConnectivityResolver = agentConnectivityResolver;
|
|
50
|
-
this.
|
|
51
|
+
this.resolvers = resolvers;
|
|
51
52
|
this.identityStore = identityStore;
|
|
52
53
|
this.agentIdGenerator = agentIdGenerator;
|
|
53
54
|
this.clock = clock;
|
|
@@ -75,9 +76,9 @@ export class DefaultAgentManager {
|
|
|
75
76
|
* is private, so this is the only way to obtain an instance, but
|
|
76
77
|
* consumers should always go through {@link createAgentManager}.
|
|
77
78
|
*/
|
|
78
|
-
static async __build(harness, harnessSupportedProviderHints, agentConnectivityResolver,
|
|
79
|
+
static async __build(harness, harnessSupportedProviderHints, agentConnectivityResolver, resolvers, storageRootFolder, agentIdGenerator, clock, logBus) {
|
|
79
80
|
const identityStore = new AgentIdentityStore(storageRootFolder, harness.harnessId, logBus);
|
|
80
|
-
const manager = new DefaultAgentManager(harness, harnessSupportedProviderHints, agentConnectivityResolver,
|
|
81
|
+
const manager = new DefaultAgentManager(harness, harnessSupportedProviderHints, agentConnectivityResolver, resolvers, identityStore, agentIdGenerator, clock, logBus);
|
|
81
82
|
await manager.init();
|
|
82
83
|
return manager;
|
|
83
84
|
}
|
|
@@ -183,8 +184,9 @@ export class DefaultAgentManager {
|
|
|
183
184
|
if (!this.harnessSupportedProviderHints.includes(providerHint)) {
|
|
184
185
|
throw new AgentSDKError(`Harness "${this.harness.harnessId}" does not support providerHint "${providerHint}". Supported: ${this.harnessSupportedProviderHints.join(', ')}.`, AgentSDKErrorType.MODEL_NOT_SUPPORTED_BY_HARNESS);
|
|
185
186
|
}
|
|
186
|
-
const hooks = this.hooksForAgent?.(agentId, config) ?? {};
|
|
187
|
-
|
|
187
|
+
const hooks = this.resolvers.hooksForAgent?.(agentId, config) ?? {};
|
|
188
|
+
const mcpAuthProviders = normalizeMcpAuthProviders(config.mcpServers, this.resolvers.mcpAuthProviderResolver?.(agentId, config));
|
|
189
|
+
await this.harness.createAgent(agentId, projectRoot, runtime.modelConnectivityInfo, toHarnessConfig(config, runtime.orgJwt), { ...(options.abortSignal !== undefined ? { abortSignal: options.abortSignal } : {}), hooks, mcpAuthProviders });
|
|
188
190
|
const agentSlice = this.router.registerAgent(agentId);
|
|
189
191
|
// Forward-compat: register the agent against the wire-communication router
|
|
190
192
|
// too. Today every WireCommunicationEvent lands on the unrouted slice (no
|
|
@@ -192,7 +194,7 @@ export class DefaultAgentManager {
|
|
|
192
194
|
// symmetrically with the telemetry router so the wiring is in place if/when
|
|
193
195
|
// the event shape grows an agentId field.
|
|
194
196
|
this.wireRouter.registerAgent(agentId);
|
|
195
|
-
const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, runtime.modelConnectivityInfo, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.harnessSupportedProviderHints, this.
|
|
197
|
+
const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, runtime.modelConnectivityInfo, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.harnessSupportedProviderHints, this.resolvers, this.identityStore, this.router, agentSlice, { telemetry: this.telemetryBus, log: this.logBus }, this.clock, this.agentIdGenerator);
|
|
196
198
|
this.agents.set(agentId, agent);
|
|
197
199
|
const agentCreatedAt = this.clock.now();
|
|
198
200
|
const modelName = runtime.modelConnectivityInfo.model.name;
|
|
@@ -359,7 +361,7 @@ export async function createAgentManager(storageRootFolder, harnessFactory, opti
|
|
|
359
361
|
}
|
|
360
362
|
const agentConnectivityResolver = options?.connectivityResolver ?? new DefaultAgentConnectivityResolver();
|
|
361
363
|
const clock = new RealClock();
|
|
362
|
-
return DefaultAgentManager.__build(harness, harnessFactory.supportedProviderHints, agentConnectivityResolver, options?.hooksForAgent, storageRootFolder, new UUIDGenerator(), clock,
|
|
364
|
+
return DefaultAgentManager.__build(harness, harnessFactory.supportedProviderHints, agentConnectivityResolver, { hooksForAgent: options?.hooksForAgent, mcpAuthProviderResolver: options?.mcpAuthProviderResolver }, storageRootFolder, new UUIDGenerator(), clock,
|
|
363
365
|
// The manager's root log bus shares the manager clock and self-reads the process environment context.
|
|
364
366
|
new LogBus(clock));
|
|
365
367
|
}
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Clock, type JSONWebToken, LogBus, type LogRecord, type OrgConnection, type UniqueIDGenerator, type Unsubscribe } from '@salesforce/agentic-common';
|
|
2
2
|
import type { AgentHarness } from './harness/agent-harness.js';
|
|
3
|
-
import { type AgentConfig } from './harness/harness-config.js';
|
|
3
|
+
import { type AgentConfig, type McpAuthProviderResolver } from './harness/harness-config.js';
|
|
4
4
|
import { type ChatSession } from './chat-session.js';
|
|
5
5
|
import type { McpServerInfo } from './mcp-config.js';
|
|
6
6
|
import type { AgentConnectivityResolver } from './agent-connectivity-resolver.js';
|
|
@@ -144,6 +144,15 @@ export interface Agent {
|
|
|
144
144
|
/** Subscribe to structured log records scoped to this agent (and its sessions). Returns an unsubscribe function. */
|
|
145
145
|
onLog(callback: (record: LogRecord) => void): Unsubscribe;
|
|
146
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* The per-agent runtime resolvers the SDK threads from `createAgentManager` into each agent — callbacks the consumer
|
|
149
|
+
* supplies once and the SDK re-invokes per install / restore / update. Bundled so a new runtime binding is one field
|
|
150
|
+
* here, not another parameter through every manager → agent seam. Each member is optional (unset by the consumer).
|
|
151
|
+
*/
|
|
152
|
+
export type AgentRuntimeResolvers = {
|
|
153
|
+
hooksForAgent?: HooksForAgent;
|
|
154
|
+
mcpAuthProviderResolver?: McpAuthProviderResolver;
|
|
155
|
+
};
|
|
147
156
|
/**
|
|
148
157
|
* Default implementation of {@link Agent} that delegates
|
|
149
158
|
* agent and thread operations to an {@link AgentHarness}.
|
|
@@ -158,7 +167,7 @@ export declare class DefaultAgent implements Agent {
|
|
|
158
167
|
private orgJwt;
|
|
159
168
|
private readonly agentConnectivityResolver;
|
|
160
169
|
private readonly harnessSupportedProviderHints;
|
|
161
|
-
private readonly
|
|
170
|
+
private readonly resolvers;
|
|
162
171
|
private readonly identityStore;
|
|
163
172
|
private readonly sessions;
|
|
164
173
|
private readonly sessionSliceUnregisters;
|
|
@@ -181,17 +190,18 @@ export declare class DefaultAgent implements Agent {
|
|
|
181
190
|
* @param orgConnection - Authenticated org connection carrying identity and env inference.
|
|
182
191
|
* @param orgJwt - Self-refreshing JWT for the resolved org (used for MCP auth injection).
|
|
183
192
|
* @param agentConnectivityResolver - Used to re-resolve org connectivity when the org or model changes.
|
|
184
|
-
* @param
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* the
|
|
193
|
+
* @param resolvers - Per-agent runtime resolvers ({@link AgentRuntimeResolvers}: `hooksForAgent`,
|
|
194
|
+
* `mcpAuthProviderResolver`) supplied by the SDK consumer at `createAgentManager` time. The agent re-invokes
|
|
195
|
+
* them on every `updateAgentConfig` (with `nextConfig`, and again with `previousConfig` on the rollback path)
|
|
196
|
+
* so what the harness sees always reflects the current persisted config. Each member is `undefined` when the
|
|
197
|
+
* consumer didn't pass it.
|
|
188
198
|
* @param identityStore - SDK-owned persistence for the `{ agentId, projectRoot, AgentConfig }` triple. The agent
|
|
189
199
|
* calls `write()` on a successful `updateAgentConfig` so disk state and in-memory state stay in lockstep.
|
|
190
200
|
* @param router - Telemetry router used to obtain session slices when sessions are created.
|
|
191
201
|
* @param inbound - Router slice delivering harness events routed to this agent (non-session-scoped).
|
|
192
202
|
* @param parent - Manager's bus pair; this agent forwards its events upward into them.
|
|
193
203
|
*/
|
|
194
|
-
constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, modelConnectivityInfo: ModelConnectivityInfo, orgConnection: OrgConnection | undefined, orgJwt: JSONWebToken | undefined, agentConnectivityResolver: AgentConnectivityResolver, harnessSupportedProviderHints: readonly ProviderHint[],
|
|
204
|
+
constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, modelConnectivityInfo: ModelConnectivityInfo, orgConnection: OrgConnection | undefined, orgJwt: JSONWebToken | undefined, agentConnectivityResolver: AgentConnectivityResolver, harnessSupportedProviderHints: readonly ProviderHint[], resolvers: AgentRuntimeResolvers, identityStore: AgentIdentityStore, router: TelemetryRouter, inbound: TelemetrySlice, parent: AgentParentBuses, clock?: Clock, idGenerator?: UniqueIDGenerator);
|
|
195
205
|
/**
|
|
196
206
|
* @requirements
|
|
197
207
|
* - MUST return the agent's ID.
|
package/dist/agent.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { LogBus, RealClock, UUIDGenerator, } from '@salesforce/agentic-common';
|
|
6
6
|
import { toHarnessConfig } from './harness/harness-config.js';
|
|
7
|
+
import { normalizeMcpAuthProviders } from './mcp-auth.js';
|
|
7
8
|
import { DefaultChatSession } from './chat-session.js';
|
|
8
9
|
import { AgentSDKError, AgentSDKErrorType } from './errors.js';
|
|
9
10
|
import { createTelemetryBus } from './types/telemetry-events.js';
|
|
@@ -21,7 +22,7 @@ export class DefaultAgent {
|
|
|
21
22
|
orgJwt;
|
|
22
23
|
agentConnectivityResolver;
|
|
23
24
|
harnessSupportedProviderHints;
|
|
24
|
-
|
|
25
|
+
resolvers;
|
|
25
26
|
identityStore;
|
|
26
27
|
sessions = new Map();
|
|
27
28
|
sessionSliceUnregisters = new Map();
|
|
@@ -46,17 +47,18 @@ export class DefaultAgent {
|
|
|
46
47
|
* @param orgConnection - Authenticated org connection carrying identity and env inference.
|
|
47
48
|
* @param orgJwt - Self-refreshing JWT for the resolved org (used for MCP auth injection).
|
|
48
49
|
* @param agentConnectivityResolver - Used to re-resolve org connectivity when the org or model changes.
|
|
49
|
-
* @param
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* the
|
|
50
|
+
* @param resolvers - Per-agent runtime resolvers ({@link AgentRuntimeResolvers}: `hooksForAgent`,
|
|
51
|
+
* `mcpAuthProviderResolver`) supplied by the SDK consumer at `createAgentManager` time. The agent re-invokes
|
|
52
|
+
* them on every `updateAgentConfig` (with `nextConfig`, and again with `previousConfig` on the rollback path)
|
|
53
|
+
* so what the harness sees always reflects the current persisted config. Each member is `undefined` when the
|
|
54
|
+
* consumer didn't pass it.
|
|
53
55
|
* @param identityStore - SDK-owned persistence for the `{ agentId, projectRoot, AgentConfig }` triple. The agent
|
|
54
56
|
* calls `write()` on a successful `updateAgentConfig` so disk state and in-memory state stay in lockstep.
|
|
55
57
|
* @param router - Telemetry router used to obtain session slices when sessions are created.
|
|
56
58
|
* @param inbound - Router slice delivering harness events routed to this agent (non-session-scoped).
|
|
57
59
|
* @param parent - Manager's bus pair; this agent forwards its events upward into them.
|
|
58
60
|
*/
|
|
59
|
-
constructor(harness, agentId, projectRoot, config, modelConnectivityInfo, orgConnection, orgJwt, agentConnectivityResolver, harnessSupportedProviderHints,
|
|
61
|
+
constructor(harness, agentId, projectRoot, config, modelConnectivityInfo, orgConnection, orgJwt, agentConnectivityResolver, harnessSupportedProviderHints, resolvers, identityStore, router, inbound, parent, clock = new RealClock(), idGenerator = new UUIDGenerator()) {
|
|
60
62
|
this.harness = harness;
|
|
61
63
|
this.agentId = agentId;
|
|
62
64
|
this.projectRoot = projectRoot;
|
|
@@ -66,7 +68,7 @@ export class DefaultAgent {
|
|
|
66
68
|
this.orgJwt = orgJwt;
|
|
67
69
|
this.agentConnectivityResolver = agentConnectivityResolver;
|
|
68
70
|
this.harnessSupportedProviderHints = harnessSupportedProviderHints;
|
|
69
|
-
this.
|
|
71
|
+
this.resolvers = resolvers;
|
|
70
72
|
this.identityStore = identityStore;
|
|
71
73
|
this.router = router;
|
|
72
74
|
this.clock = clock;
|
|
@@ -157,10 +159,12 @@ export class DefaultAgent {
|
|
|
157
159
|
nextOrgJwt = runtime.orgJwt;
|
|
158
160
|
}
|
|
159
161
|
try {
|
|
160
|
-
const nextHooks = this.hooksForAgent?.(this.agentId, nextConfig) ?? {};
|
|
162
|
+
const nextHooks = this.resolvers.hooksForAgent?.(this.agentId, nextConfig) ?? {};
|
|
163
|
+
const nextMcpAuthProviders = normalizeMcpAuthProviders(nextConfig.mcpServers, this.resolvers.mcpAuthProviderResolver?.(this.agentId, nextConfig));
|
|
161
164
|
await this.harness.updateAgent(this.agentId, nextModelConnectivityInfo, toHarnessConfig(nextConfig, nextOrgJwt), {
|
|
162
165
|
...(options?.abortSignal !== undefined ? { abortSignal: options.abortSignal } : {}),
|
|
163
166
|
hooks: nextHooks,
|
|
167
|
+
mcpAuthProviders: nextMcpAuthProviders,
|
|
164
168
|
});
|
|
165
169
|
// Persist before the in-memory swaps so a write failure flows through the same
|
|
166
170
|
// catch block as an updateAgent failure: the rollback re-runs updateAgent against
|
|
@@ -177,8 +181,9 @@ export class DefaultAgent {
|
|
|
177
181
|
// against its current state — if updateAgent partially applied (e.g. some MCP
|
|
178
182
|
// servers were already cycled), reverting via updateAgent restores them too.
|
|
179
183
|
try {
|
|
180
|
-
const previousHooks = this.hooksForAgent?.(this.agentId, previousConfig) ?? {};
|
|
181
|
-
|
|
184
|
+
const previousHooks = this.resolvers.hooksForAgent?.(this.agentId, previousConfig) ?? {};
|
|
185
|
+
const previousMcpAuthProviders = normalizeMcpAuthProviders(previousConfig.mcpServers, this.resolvers.mcpAuthProviderResolver?.(this.agentId, previousConfig));
|
|
186
|
+
await this.harness.updateAgent(this.agentId, previousModelConnectivityInfo, toHarnessConfig(previousConfig, previousOrgJwt), { hooks: previousHooks, mcpAuthProviders: previousMcpAuthProviders });
|
|
182
187
|
}
|
|
183
188
|
catch {
|
|
184
189
|
// Ignore restoration errors; rethrow the original failure.
|
package/dist/errors.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const AgentSDKErrorType: {
|
|
|
4
4
|
readonly COMPACTION_FAILED: 'COMPACTION_FAILED';
|
|
5
5
|
readonly DISPOSED: 'DISPOSED';
|
|
6
6
|
readonly INCOMPATIBLE_HARNESS: 'INCOMPATIBLE_HARNESS';
|
|
7
|
+
readonly INVALID_MCP_AUTH_CONFIG: 'INVALID_MCP_AUTH_CONFIG';
|
|
7
8
|
readonly INVALID_MESSAGE_CONTENT: 'INVALID_MESSAGE_CONTENT';
|
|
8
9
|
readonly MCP_SERVER_DISABLED: 'MCP_SERVER_DISABLED';
|
|
9
10
|
readonly MCP_SERVER_NOT_FOUND: 'MCP_SERVER_NOT_FOUND';
|
package/dist/errors.js
CHANGED
|
@@ -8,6 +8,7 @@ export const AgentSDKErrorType = {
|
|
|
8
8
|
COMPACTION_FAILED: 'COMPACTION_FAILED',
|
|
9
9
|
DISPOSED: 'DISPOSED',
|
|
10
10
|
INCOMPATIBLE_HARNESS: 'INCOMPATIBLE_HARNESS',
|
|
11
|
+
INVALID_MCP_AUTH_CONFIG: 'INVALID_MCP_AUTH_CONFIG',
|
|
11
12
|
INVALID_MESSAGE_CONTENT: 'INVALID_MESSAGE_CONTENT',
|
|
12
13
|
MCP_SERVER_DISABLED: 'MCP_SERVER_DISABLED',
|
|
13
14
|
MCP_SERVER_NOT_FOUND: 'MCP_SERVER_NOT_FOUND',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LogRecord, Unsubscribe } from '@salesforce/agentic-common';
|
|
2
|
-
import type { McpServerInfo } from '../mcp-config.js';
|
|
2
|
+
import type { McpServerInfo, McpAuthProviders } from '../mcp-config.js';
|
|
3
3
|
import type { ChatStreamResult } from '../types/events.js';
|
|
4
4
|
import type { Message, MessagePart } from '../types/messages.js';
|
|
5
5
|
import type { TelemetryEventCallback } from '../types/telemetry-events.js';
|
|
@@ -142,11 +142,20 @@ export interface AgentHarness {
|
|
|
142
142
|
* native error path so the original tool output never leaks to the
|
|
143
143
|
* model. The SDK does not own fail-closed semantics; consumers
|
|
144
144
|
* wrap their hook bodies in `try`/`catch` themselves when they
|
|
145
|
-
*
|
|
145
|
+
* want a richer fail-closed substitute.
|
|
146
|
+
* - `mcpAuthProviders` — per-agent OAuth provider map (remote MCP server
|
|
147
|
+
* name → {@link McpOAuthClientProvider}), resolved by the SDK from
|
|
148
|
+
* `createAgentManager`'s `mcpAuthProviderResolver` and already normalized
|
|
149
|
+
* (Salesforce URLs / stdio / disabled / missing servers dropped, static-
|
|
150
|
+
* Authorization conflicts rejected — see `normalizeMcpAuthProviders`).
|
|
151
|
+
* Providers are runtime bindings, NEVER part of `HarnessAgentConfig`;
|
|
152
|
+
* harnesses store the map on per-agent state and forward `providers[name]`
|
|
153
|
+
* into the remote transport for that server.
|
|
146
154
|
*/
|
|
147
155
|
createAgent(agentId: string, projectRoot: string, modelConnectivityInfo: ModelConnectivityInfo, config?: HarnessAgentConfig, options?: {
|
|
148
156
|
abortSignal?: AbortSignal;
|
|
149
157
|
hooks?: AgentHooks;
|
|
158
|
+
mcpAuthProviders?: McpAuthProviders;
|
|
150
159
|
}): Promise<void>;
|
|
151
160
|
/**
|
|
152
161
|
* Destroy an agent and release its resources (MCP connections, workspace, memory).
|
|
@@ -179,9 +188,12 @@ export interface AgentHarness {
|
|
|
179
188
|
* matching the rest of the cross-harness contract.
|
|
180
189
|
* - preserve the in-memory MCP client (and its discovered tool catalog)
|
|
181
190
|
* for any server name whose config is `mcpServerConfigEqual` to the
|
|
182
|
-
* currently-applied one
|
|
183
|
-
*
|
|
184
|
-
* -
|
|
191
|
+
* currently-applied one AND whose applied OAuth provider reference (from
|
|
192
|
+
* `options.mcpAuthProviders`) is unchanged. No transport teardown, no
|
|
193
|
+
* `tools/list` re-run, no per-server discovery telemetry.
|
|
194
|
+
* - cycle (disconnect-then-reconnect) any server whose config differs OR
|
|
195
|
+
* whose applied provider reference changed (a swapped provider instance
|
|
196
|
+
* must cycle the server so the new one owns auth).
|
|
185
197
|
* - disconnect any server present in the currently-applied config but
|
|
186
198
|
* absent from the next config, removing it from
|
|
187
199
|
* `getMcpServerInfo()`'s output.
|
|
@@ -233,10 +245,15 @@ export interface AgentHarness {
|
|
|
233
245
|
* re-resolved on every `updateAgent` so consumers can vary hooks by
|
|
234
246
|
* config (and so a rollback `updateAgent(previousConfig)` restores
|
|
235
247
|
* the prior hook bag too).
|
|
248
|
+
* - `mcpAuthProviders` — per-agent normalized OAuth provider map, re-resolved
|
|
249
|
+
* on every `updateAgent` (so a provider swap cycles its server, and a
|
|
250
|
+
* rollback `updateAgent(previousConfig)` restores the prior providers).
|
|
251
|
+
* Same runtime-only semantics as `createAgent.options.mcpAuthProviders`.
|
|
236
252
|
*/
|
|
237
253
|
updateAgent(agentId: string, modelConnectivityInfo: ModelConnectivityInfo, config?: HarnessAgentConfig, options?: {
|
|
238
254
|
abortSignal?: AbortSignal;
|
|
239
255
|
hooks?: AgentHooks;
|
|
256
|
+
mcpAuthProviders?: McpAuthProviders;
|
|
240
257
|
}): Promise<void>;
|
|
241
258
|
/**
|
|
242
259
|
* List the IDs of all currently registered agents.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Decision, ToolDefinition, ToolPolicyRule } from '../types/tools.js';
|
|
2
|
-
import type { MCPConfiguration } from '../mcp-config.js';
|
|
2
|
+
import type { MCPConfiguration, McpAuthProviders } from '../mcp-config.js';
|
|
3
3
|
import type { JSONWebToken } from '@salesforce/agentic-common';
|
|
4
4
|
import type { Model, ModelName } from '../models/index.js';
|
|
5
5
|
/**
|
|
@@ -88,6 +88,25 @@ export type AgentConfig = {
|
|
|
88
88
|
*/
|
|
89
89
|
defaultToolDecision?: Decision;
|
|
90
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* Resolves the per-agent OAuth {@link McpAuthProviders} map (remote MCP server
|
|
93
|
+
* name → provider) from the agent's id and the config the SDK has on file.
|
|
94
|
+
* Invoked by `AgentManager` once per agent install (`createAgent`, boot-time
|
|
95
|
+
* restore) and by `Agent.updateAgentConfig`; the resolved map is normalized by
|
|
96
|
+
* the SDK (see `normalizeMcpAuthProviders`) and threaded to the harness via
|
|
97
|
+
* `AgentHarness.createAgent` / `updateAgent`'s `options.mcpAuthProviders`.
|
|
98
|
+
*
|
|
99
|
+
* This is the seam that keeps credential-bearing provider objects OFF the
|
|
100
|
+
* persisted {@link AgentConfig}: providers are supplied here at runtime, never
|
|
101
|
+
* serialized. Mirrors `HooksForAgent`.
|
|
102
|
+
*
|
|
103
|
+
* The callback is sync — the SDK does not await. Return **stable, pre-constructed**
|
|
104
|
+
* provider instances: returning a NEW provider object for a server on each call
|
|
105
|
+
* intentionally cycles that server on every update (reference identity is how a
|
|
106
|
+
* provider swap is detected). Consumers needing async provider setup pre-construct
|
|
107
|
+
* before returning.
|
|
108
|
+
*/
|
|
109
|
+
export type McpAuthProviderResolver = (agentId: string, config: AgentConfig) => McpAuthProviders | undefined;
|
|
91
110
|
/**
|
|
92
111
|
* Harness-facing configuration for creating/updating an agent.
|
|
93
112
|
*
|
package/dist/harness/public.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ export type { AgentHarness, HarnessFactory, WithAgentConfig, ConfigOf } from './
|
|
|
45
45
|
export type { AgentHooks } from '../types/redaction.js';
|
|
46
46
|
export { SUPPORTED_PROTOCOL_VERSIONS } from './agent-harness.js';
|
|
47
47
|
export { mcpServerConfigEqual } from '../mcp-config.js';
|
|
48
|
+
export type { McpAuthProviders, McpOAuthClientProvider } from '../mcp-config.js';
|
|
48
49
|
export { HarnessBusOwner } from './harness-bus-owner.js';
|
|
49
50
|
export { lowerStreamInput, type InputMessagePart } from './stream-input.js';
|
|
50
51
|
export { GenSink } from './gen-sink.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -6,9 +6,9 @@ export { BUILT_IN_TOOL_POLICIES, SKILL_BRIDGE_SERVER_ID, definePolicy, matcherMa
|
|
|
6
6
|
export type { ResolverResult, ResolverTiers, ToolInvocation } from './policy-resolver.js';
|
|
7
7
|
export type { ContextUsage, FinishReason, UsageMetadata } from './types/usage.js';
|
|
8
8
|
export type { AgentHooks, HooksForAgent, ToolResultRedactor, ToolResultRedactionInput, ToolResultRedactionResult, } from './types/redaction.js';
|
|
9
|
-
export type { AgentConfig, HarnessAgentConfig, StreamOptions } from './harness/harness-config.js';
|
|
9
|
+
export type { AgentConfig, HarnessAgentConfig, McpAuthProviderResolver, StreamOptions, } from './harness/harness-config.js';
|
|
10
10
|
export { DEFAULT_MAX_STEPS } from './harness/harness-config.js';
|
|
11
|
-
export type { MCPConfiguration, MCPServerConfig, MCPStdioServerConfig, MCPRemoteServerConfig, McpServerInfo, McpServerErrorCategory, McpServerErrorDetail, McpToolInfo, McpToolAnnotations, } from './mcp-config.js';
|
|
11
|
+
export type { MCPConfiguration, MCPServerConfig, MCPStdioServerConfig, MCPRemoteServerConfig, McpOAuthClientProvider, McpAuthProviders, McpServerInfo, McpServerErrorCategory, McpServerErrorDetail, McpToolInfo, McpToolAnnotations, } from './mcp-config.js';
|
|
12
12
|
export { McpServerStatus, mcpServerConfigEqual } from './mcp-config.js';
|
|
13
13
|
export { Model, ModelName, createClaudeModel, Models, validateMultimodalFiles, ACCEPTED_MODEL_WIRE_IDS, } from './models/index.js';
|
|
14
14
|
export type { ClaudeModelOverrides, MultimodalFile, SupportedFileFormat, ThinkingMode } from './models/index.js';
|
|
@@ -28,7 +28,7 @@ export type { AgentCreatedEvent, AgentDestroyedEvent, ChatStreamCompletedEvent,
|
|
|
28
28
|
export type { LogLevel, LogRecord, Unsubscribe } from '@salesforce/agentic-common';
|
|
29
29
|
export type { EnvironmentFields } from '@salesforce/agentic-common';
|
|
30
30
|
export { readEnvironmentContext, resolveFeatureId } from '@salesforce/agentic-common';
|
|
31
|
-
export { resolveMcpServerHeaders } from './mcp-auth.js';
|
|
31
|
+
export { resolveMcpServerHeaders, isSalesforcePlatformMcpUrl } from './mcp-auth.js';
|
|
32
32
|
export type { OrgConnection, OrgConnectionFactory } from '@salesforce/agentic-common';
|
|
33
33
|
export type { JSONWebToken, JWTOptions, RequiredJWTHeaders, RequiredJWTPayload } from '@salesforce/agentic-common';
|
|
34
34
|
export { Workspace } from './workspace.js';
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,6 @@ export { WireCommunicationFileWriter, } from './wire-communication-file-writer.j
|
|
|
23
23
|
export { AgentSDKError, AgentSDKErrorType } from './errors.js';
|
|
24
24
|
export { readEnvironmentContext, resolveFeatureId } from '@salesforce/agentic-common';
|
|
25
25
|
// ── MCP Auth ────────────────────────────────────────────────────────
|
|
26
|
-
export { resolveMcpServerHeaders } from './mcp-auth.js';
|
|
26
|
+
export { resolveMcpServerHeaders, isSalesforcePlatformMcpUrl } from './mcp-auth.js';
|
|
27
27
|
export { Workspace } from './workspace.js';
|
|
28
28
|
//# sourceMappingURL=index.js.map
|
package/dist/mcp-auth.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JSONWebToken } from '@salesforce/agentic-common';
|
|
2
|
+
import type { MCPConfiguration, McpAuthProviders } from './mcp-config.js';
|
|
2
3
|
/**
|
|
3
4
|
* Returns true if the URL matches a Salesforce Hosted MCP Server endpoint.
|
|
4
5
|
* Covers all environments (prod, dev, test, perf, stage) and all API versions
|
|
@@ -18,3 +19,22 @@ export declare function isSalesforcePlatformMcpUrl(url: string | URL): boolean;
|
|
|
18
19
|
* @returns The resolved headers — original headers plus any injected auth.
|
|
19
20
|
*/
|
|
20
21
|
export declare function resolveMcpServerHeaders(url: string | URL, orgJwt: JSONWebToken | undefined, headers?: Record<string, string>): Promise<Record<string, string>>;
|
|
22
|
+
/**
|
|
23
|
+
* Normalizes the raw provider map returned by a consumer's
|
|
24
|
+
* `mcpAuthProviderResolver` into the map the harness may act on, enforcing the
|
|
25
|
+
* "one Authorization owner per server" rule centrally (so no harness reimplements
|
|
26
|
+
* this policy). For each provider entry, keyed by server name:
|
|
27
|
+
*
|
|
28
|
+
* - **Dropped** (provider ignored) when the named server is missing, is a stdio
|
|
29
|
+
* server, is disabled, or is a Salesforce Platform URL. Salesforce URLs are
|
|
30
|
+
* authenticated by the rotating org JWT, which owns auth there; forwarding a
|
|
31
|
+
* provider alongside it would arm two competing Authorization owners.
|
|
32
|
+
* - **Rejected** (throws) when the server carries a static `Authorization`
|
|
33
|
+
* header AND a provider — the MCP SDK transport spreads static `requestInit`
|
|
34
|
+
* headers OVER the provider token, so the static header would silently win and
|
|
35
|
+
* strand the provider in a 401 loop. Failing loud at resolve time beats a
|
|
36
|
+
* silent auth failure at runtime.
|
|
37
|
+
*
|
|
38
|
+
* Returns a fresh, reference-only map (the same provider instances, never mutated).
|
|
39
|
+
*/
|
|
40
|
+
export declare function normalizeMcpAuthProviders(mcpServers: MCPConfiguration | undefined, resolved: McpAuthProviders | undefined): McpAuthProviders;
|
package/dist/mcp-auth.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Copyright 2026, Salesforce, Inc. All rights reserved.
|
|
3
3
|
* See LICENSE.txt for license terms.
|
|
4
4
|
*/
|
|
5
|
+
import { AgentSDKError, AgentSDKErrorType } from './errors.js';
|
|
5
6
|
const SALESFORCE_PLATFORM_MCP_URL_REGEX = /^https:\/\/((dev|test|perf|stage)\.)?api\.salesforce\.com\/platform\/mcp\//;
|
|
6
7
|
/**
|
|
7
8
|
* Returns true if the URL matches a Salesforce Hosted MCP Server endpoint.
|
|
@@ -37,4 +38,40 @@ export async function resolveMcpServerHeaders(url, orgJwt, headers = {}) {
|
|
|
37
38
|
function hasAuthorizationHeader(headers) {
|
|
38
39
|
return Object.keys(headers).some((key) => key.toLowerCase() === 'authorization');
|
|
39
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Normalizes the raw provider map returned by a consumer's
|
|
43
|
+
* `mcpAuthProviderResolver` into the map the harness may act on, enforcing the
|
|
44
|
+
* "one Authorization owner per server" rule centrally (so no harness reimplements
|
|
45
|
+
* this policy). For each provider entry, keyed by server name:
|
|
46
|
+
*
|
|
47
|
+
* - **Dropped** (provider ignored) when the named server is missing, is a stdio
|
|
48
|
+
* server, is disabled, or is a Salesforce Platform URL. Salesforce URLs are
|
|
49
|
+
* authenticated by the rotating org JWT, which owns auth there; forwarding a
|
|
50
|
+
* provider alongside it would arm two competing Authorization owners.
|
|
51
|
+
* - **Rejected** (throws) when the server carries a static `Authorization`
|
|
52
|
+
* header AND a provider — the MCP SDK transport spreads static `requestInit`
|
|
53
|
+
* headers OVER the provider token, so the static header would silently win and
|
|
54
|
+
* strand the provider in a 401 loop. Failing loud at resolve time beats a
|
|
55
|
+
* silent auth failure at runtime.
|
|
56
|
+
*
|
|
57
|
+
* Returns a fresh, reference-only map (the same provider instances, never mutated).
|
|
58
|
+
*/
|
|
59
|
+
export function normalizeMcpAuthProviders(mcpServers, resolved) {
|
|
60
|
+
if (!resolved || !mcpServers)
|
|
61
|
+
return {};
|
|
62
|
+
const normalized = {};
|
|
63
|
+
for (const [name, provider] of Object.entries(resolved)) {
|
|
64
|
+
const server = mcpServers[name];
|
|
65
|
+
if (!server || server.type !== 'remote' || server.enabled === false)
|
|
66
|
+
continue;
|
|
67
|
+
if (isSalesforcePlatformMcpUrl(server.url))
|
|
68
|
+
continue;
|
|
69
|
+
if (server.headers && hasAuthorizationHeader(server.headers)) {
|
|
70
|
+
throw new AgentSDKError(`MCP server "${name}" has both a static Authorization header and an OAuth authProvider. ` +
|
|
71
|
+
`Remove the static Authorization header — the provider owns auth for this server.`, AgentSDKErrorType.INVALID_MCP_AUTH_CONFIG);
|
|
72
|
+
}
|
|
73
|
+
normalized[name] = provider;
|
|
74
|
+
}
|
|
75
|
+
return normalized;
|
|
76
|
+
}
|
|
40
77
|
//# sourceMappingURL=mcp-auth.js.map
|
package/dist/mcp-config.d.ts
CHANGED
|
@@ -35,12 +35,82 @@ export type MCPStdioServerConfig = {
|
|
|
35
35
|
/** Timeout in milliseconds for individual requests to the server. */
|
|
36
36
|
timeout?: number;
|
|
37
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* Harness-neutral contract for an MCP OAuth client provider, letting an
|
|
40
|
+
* OAuth-protected remote MCP server recover the RFC 9728 `401` challenge its
|
|
41
|
+
* transport receives (the static {@link MCPRemoteServerConfig.headers} seam
|
|
42
|
+
* cannot — headers are snapshotted at publish time).
|
|
43
|
+
*
|
|
44
|
+
* This package is harness-runtime-free (a lint rule bans importing
|
|
45
|
+
* `@modelcontextprotocol/*`), so this structurally mirrors the *required* core
|
|
46
|
+
* of the MCP SDK's `OAuthClientProvider` — same pattern as {@link McpToolAnnotations}.
|
|
47
|
+
* It is bidirectionally compatible: a real provider assigns to this field, and
|
|
48
|
+
* this field assigns into the transport option with no cast. Optional upstream
|
|
49
|
+
* members are omitted (a real instance still carries them at runtime); the
|
|
50
|
+
* harness-side compile-time drift-guard pins the compatibility.
|
|
51
|
+
*/
|
|
52
|
+
export interface McpOAuthClientProvider {
|
|
53
|
+
/** URL the user agent is redirected to after authorization (or `undefined` for non-interactive flows). */
|
|
54
|
+
readonly redirectUrl: string | URL | undefined;
|
|
55
|
+
/** Metadata about this OAuth client. */
|
|
56
|
+
readonly clientMetadata: {
|
|
57
|
+
redirect_uris: string[];
|
|
58
|
+
};
|
|
59
|
+
/** Loads this client's registration, or `undefined` if not yet registered. */
|
|
60
|
+
clientInformation(): {
|
|
61
|
+
client_id: string;
|
|
62
|
+
} | undefined | Promise<{
|
|
63
|
+
client_id: string;
|
|
64
|
+
} | undefined>;
|
|
65
|
+
/** Persists a dynamic client registration for later {@link clientInformation} reads. */
|
|
66
|
+
saveClientInformation?(clientInformation: {
|
|
67
|
+
client_id: string;
|
|
68
|
+
}): void | Promise<void>;
|
|
69
|
+
/** Loads any existing OAuth tokens for the current session, or `undefined`. */
|
|
70
|
+
tokens(): {
|
|
71
|
+
access_token: string;
|
|
72
|
+
token_type: string;
|
|
73
|
+
} | undefined | Promise<{
|
|
74
|
+
access_token: string;
|
|
75
|
+
token_type: string;
|
|
76
|
+
} | undefined>;
|
|
77
|
+
/** Stores new OAuth tokens after a successful authorization. */
|
|
78
|
+
saveTokens(tokens: {
|
|
79
|
+
access_token: string;
|
|
80
|
+
token_type: string;
|
|
81
|
+
}): void | Promise<void>;
|
|
82
|
+
/** Redirects the user agent to the given URL to begin the authorization flow. */
|
|
83
|
+
redirectToAuthorization(authorizationUrl: URL): void | Promise<void>;
|
|
84
|
+
/** Saves a PKCE code verifier before redirecting to the authorization flow. */
|
|
85
|
+
saveCodeVerifier(codeVerifier: string): void | Promise<void>;
|
|
86
|
+
/** Loads the PKCE code verifier needed to validate the authorization result. */
|
|
87
|
+
codeVerifier(): string | Promise<string>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A per-agent map of remote MCP server name → its OAuth {@link McpOAuthClientProvider}.
|
|
91
|
+
*
|
|
92
|
+
* Providers are behavioral, credential-bearing runtime objects, so they are
|
|
93
|
+
* NOT part of the persisted {@link MCPConfiguration}. They are supplied at
|
|
94
|
+
* runtime through the manager's `mcpAuthProviderResolver` and threaded to the
|
|
95
|
+
* harness via the `createAgent` / `updateAgent` options bag — never serialized
|
|
96
|
+
* to disk. A harness cycles a server when its provider reference changes.
|
|
97
|
+
*/
|
|
98
|
+
export type McpAuthProviders = Readonly<Record<string, McpOAuthClientProvider>>;
|
|
38
99
|
/** MCP server accessible over HTTP/SSE at a remote URL. */
|
|
39
100
|
export type MCPRemoteServerConfig = {
|
|
40
101
|
type: 'remote';
|
|
41
102
|
/** URL of the remote MCP server endpoint. */
|
|
42
103
|
url: string | URL;
|
|
43
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* HTTP headers sent with requests to the server.
|
|
106
|
+
*
|
|
107
|
+
* When an OAuth `authProvider` is supplied for this server (via the
|
|
108
|
+
* manager's `mcpAuthProviderResolver`), this must NOT carry an
|
|
109
|
+
* `Authorization` header — the MCP SDK transport spreads static
|
|
110
|
+
* `requestInit` headers OVER the provider token, so a static
|
|
111
|
+
* `Authorization` would silently win and strand the provider. The SDK
|
|
112
|
+
* rejects that combination at resolve time (see `normalizeMcpAuthProviders`).
|
|
113
|
+
*/
|
|
44
114
|
headers?: Record<string, string>;
|
|
45
115
|
/** Whether this server is enabled. Defaults to `true`. */
|
|
46
116
|
enabled?: boolean;
|
|
@@ -187,6 +257,8 @@ export type McpToolInfo = {
|
|
|
187
257
|
export type McpServerErrorCategory =
|
|
188
258
|
/** 3s default or configured timeout exceeded. */
|
|
189
259
|
'connect-timeout'
|
|
260
|
+
/** OAuth needs user interaction; the host must authorize before reconnecting. */
|
|
261
|
+
| 'oauth-required'
|
|
190
262
|
/** Unauthorized — JWT missing/expired. */
|
|
191
263
|
| 'http-401'
|
|
192
264
|
/** Forbidden — JWT valid but the principal lacks permission. */
|
|
@@ -230,7 +302,7 @@ export type McpServerErrorDetail = {
|
|
|
230
302
|
code?: number;
|
|
231
303
|
/**
|
|
232
304
|
* Whether the failure is transient (worth a retry via
|
|
233
|
-
* `Agent.reconnectMcpServer`
|
|
305
|
+
* `Agent.reconnectMcpServer`) vs fatal (consumer
|
|
234
306
|
* must fix config). Harness implementations populate this with their best
|
|
235
307
|
* heuristic; consumers may override per-category.
|
|
236
308
|
*/
|
|
@@ -259,6 +331,12 @@ export type McpServerErrorDetail = {
|
|
|
259
331
|
* strings round-trip; `headers` (key-order-insensitive); `timeout`,
|
|
260
332
|
* `reconnectionOptions` (field-wise).
|
|
261
333
|
*
|
|
334
|
+
* This predicate compares only the serializable {@link MCPServerConfig}. A
|
|
335
|
+
* server's OAuth `authProvider` is NOT part of the config (it is a runtime
|
|
336
|
+
* binding supplied per-agent via `mcpAuthProviderResolver`); a harness cycles
|
|
337
|
+
* a server when this predicate fails OR its applied provider reference
|
|
338
|
+
* changed. See each harness's `updateAgent` diff.
|
|
339
|
+
*
|
|
262
340
|
* Two configs that pass this predicate but produce different runtime tools
|
|
263
341
|
* (e.g. an upstream stdio server whose binary was overwritten on disk) are
|
|
264
342
|
* NOT detected here — the predicate compares declared config, not runtime
|
package/dist/mcp-config.js
CHANGED
|
@@ -40,6 +40,12 @@ export var McpServerStatus;
|
|
|
40
40
|
* strings round-trip; `headers` (key-order-insensitive); `timeout`,
|
|
41
41
|
* `reconnectionOptions` (field-wise).
|
|
42
42
|
*
|
|
43
|
+
* This predicate compares only the serializable {@link MCPServerConfig}. A
|
|
44
|
+
* server's OAuth `authProvider` is NOT part of the config (it is a runtime
|
|
45
|
+
* binding supplied per-agent via `mcpAuthProviderResolver`); a harness cycles
|
|
46
|
+
* a server when this predicate fails OR its applied provider reference
|
|
47
|
+
* changed. See each harness's `updateAgent` diff.
|
|
48
|
+
*
|
|
43
49
|
* Two configs that pass this predicate but produce different runtime tools
|
|
44
50
|
* (e.g. an upstream stdio server whose binary was overwritten on disk) are
|
|
45
51
|
* NOT detected here — the predicate compares declared config, not runtime
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/sfdx-agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -47,24 +47,24 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@eslint/js": "^10.0.1",
|
|
50
|
-
"@salesforce/sfdx-agent-harness-claude": "0.
|
|
51
|
-
"@salesforce/sfdx-agent-harness-mastra": "0.
|
|
52
|
-
"@salesforce/sfdx-agent-harness-openai": "0.
|
|
50
|
+
"@salesforce/sfdx-agent-harness-claude": "0.48.0",
|
|
51
|
+
"@salesforce/sfdx-agent-harness-mastra": "0.51.0",
|
|
52
|
+
"@salesforce/sfdx-agent-harness-openai": "0.17.0",
|
|
53
53
|
"@types/node": "^22.20.1",
|
|
54
54
|
"@vitest/coverage-istanbul": "^4.1.10",
|
|
55
55
|
"@vitest/eslint-plugin": "^1.6.26",
|
|
56
|
-
"eslint": "^10.8.
|
|
56
|
+
"eslint": "^10.8.1",
|
|
57
57
|
"eslint-config-prettier": "^10.1.8",
|
|
58
58
|
"eslint-import-resolver-typescript": "^4.4.5",
|
|
59
59
|
"eslint-plugin-import": "^2.32.0",
|
|
60
|
-
"eslint-plugin-n": "^18.
|
|
60
|
+
"eslint-plugin-n": "^18.3.0",
|
|
61
61
|
"globals": "^17.9.0",
|
|
62
62
|
"lint-staged": "^17.3.0",
|
|
63
63
|
"prettier": "^3.9.6",
|
|
64
64
|
"rimraf": "^6.1.3",
|
|
65
|
-
"tsx": "^4.23.
|
|
65
|
+
"tsx": "^4.23.12",
|
|
66
66
|
"typescript": "^7.0.2",
|
|
67
|
-
"typescript-eslint": "^8.
|
|
67
|
+
"typescript-eslint": "^8.67.0",
|
|
68
68
|
"vitest": "^4.1.8"
|
|
69
69
|
},
|
|
70
70
|
"engines": {
|