@salesforce/b2c-dx-mcp 3.0.1 → 3.1.1

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.
Files changed (69) hide show
  1. package/content/guidance/b2c-cli/b2c-config/SKILL.md +18 -1
  2. package/content/guidance/b2c-cli/b2c-docs/SKILL.md +8 -3
  3. package/content/guidance/b2c-cli/b2c-ecdn/SKILL.md +4 -4
  4. package/content/guidance/b2c-cli/b2c-ecdn/references/ADVANCED.md +21 -6
  5. package/content/guidance/b2c-cli/b2c-ecdn/references/SECURITY.md +4 -4
  6. package/content/guidance/b2c-cli/b2c-job/SKILL.md +2 -0
  7. package/content/guidance/b2c-cli/b2c-job/references/EXPORT.md +9 -3
  8. package/content/guidance/b2c-cli/b2c-job/references/IMPORT.md +12 -0
  9. package/content/guidance/b2c-cli/b2c-mrt/SKILL.md +5 -4
  10. package/content/guidance/b2c-cli/b2c-mrt/references/BUNDLE-COMMANDS.md +21 -3
  11. package/content/guidance/b2c-cli/b2c-mrt/references/ENVIRONMENT-COMMANDS.md +23 -0
  12. package/content/guidance/b2c-cli/b2c-webdav/SKILL.md +17 -0
  13. package/content/guidance/index.json +22 -22
  14. package/content/guidance/mcp/scapi/SKILL.md +46 -14
  15. package/dist/commands/mcp.js +1 -1
  16. package/dist/guidance.js +0 -2
  17. package/dist/registry.js +1 -1
  18. package/dist/server-context.d.ts +2 -0
  19. package/dist/server-context.js +4 -0
  20. package/dist/server.d.ts +2 -4
  21. package/dist/server.js +16 -3
  22. package/dist/services.d.ts +7 -5
  23. package/dist/services.js +19 -8
  24. package/dist/tools/adapter.js +1 -1
  25. package/dist/tools/scapi/execution-registry.d.ts +62 -0
  26. package/dist/tools/scapi/execution-registry.js +303 -0
  27. package/dist/tools/scapi/index.d.ts +2 -1
  28. package/dist/tools/scapi/index.js +2 -2
  29. package/dist/tools/scapi/scapi-code.d.ts +2 -1
  30. package/dist/tools/scapi/scapi-code.js +71 -34
  31. package/dist/utils/types.d.ts +10 -3
  32. package/node_modules/@modelcontextprotocol/server/dist/createMcpHandler-CLhGwQTn.d.mts +2 -1
  33. package/node_modules/@modelcontextprotocol/server/dist/createMcpHandler-dBHMsxwf.d.cts +2 -1
  34. package/node_modules/@modelcontextprotocol/server/dist/mcp-D7GmuPnv.cjs +1 -1
  35. package/node_modules/@modelcontextprotocol/server/dist/mcp-DXXb3Vv3.mjs +1 -1
  36. package/node_modules/@modelcontextprotocol/server/dist/src-CX2iR2pK.mjs +2 -0
  37. package/node_modules/@modelcontextprotocol/server/dist/src-STyD_Vvf.cjs +2 -0
  38. package/node_modules/@salesforce/b2c-agent-plugins/CHANGELOG.md +30 -0
  39. package/node_modules/@salesforce/b2c-agent-plugins/README.md +4 -2
  40. package/node_modules/@salesforce/b2c-agent-plugins/b2c/.codex-plugin/plugin.json +1 -1
  41. package/node_modules/@salesforce/b2c-agent-plugins/b2c/plugin.json +1 -1
  42. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/.codex-plugin/plugin.json +1 -1
  43. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/plugin.json +1 -1
  44. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-config/SKILL.md +18 -1
  45. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-docs/SKILL.md +8 -3
  46. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-ecdn/SKILL.md +4 -4
  47. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-ecdn/references/ADVANCED.md +21 -6
  48. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-ecdn/references/SECURITY.md +4 -4
  49. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-job/SKILL.md +2 -0
  50. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-job/references/EXPORT.md +9 -3
  51. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-job/references/IMPORT.md +12 -0
  52. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-mrt/SKILL.md +5 -4
  53. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-mrt/references/BUNDLE-COMMANDS.md +21 -3
  54. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-mrt/references/ENVIRONMENT-COMMANDS.md +23 -0
  55. package/node_modules/@salesforce/b2c-agent-plugins/b2c-cli/skills/b2c-webdav/SKILL.md +17 -0
  56. package/node_modules/@salesforce/b2c-agent-plugins/b2c-ops/.codex-plugin/plugin.json +1 -1
  57. package/node_modules/@salesforce/b2c-agent-plugins/b2c-ops/plugin.json +1 -1
  58. package/node_modules/@salesforce/b2c-agent-plugins/b2c-python-sdk/.codex-plugin/plugin.json +41 -0
  59. package/node_modules/@salesforce/b2c-agent-plugins/b2c-python-sdk/README.md +35 -0
  60. package/node_modules/@salesforce/b2c-agent-plugins/b2c-python-sdk/assets/logo.svg +31 -0
  61. package/node_modules/@salesforce/b2c-agent-plugins/b2c-python-sdk/plugin.json +45 -0
  62. package/node_modules/@salesforce/b2c-agent-plugins/b2c-python-sdk/skills/using-b2c-tooling-sdk/SKILL.md +226 -0
  63. package/node_modules/@salesforce/b2c-agent-plugins/b2c-python-sdk/skills/using-b2c-tooling-sdk/references/api-catalog.md +154 -0
  64. package/node_modules/@salesforce/b2c-agent-plugins/package.json +1 -1
  65. package/node_modules/@salesforce/b2c-agent-plugins/plugins.json +3 -0
  66. package/node_modules/@salesforce/b2c-agent-plugins/storefront-next/.codex-plugin/plugin.json +1 -1
  67. package/node_modules/@salesforce/b2c-agent-plugins/storefront-next/plugin.json +1 -1
  68. package/oclif.manifest.json +2 -2
  69. package/package.json +3 -3
@@ -33,18 +33,18 @@ runtime support, not configured access; `op.security` gives scopes.
33
33
  Use these API IDs to narrow discovery; inspect the operation's inputs before calling.
34
34
  Snippet names below have the `builtin/` prefix. Describe only the relevant snippet.
35
35
 
36
- | Task | API / starting point |
37
- | --- | --- |
38
- | Review runs, including successes | `operation/jobs/v1`: `searchJobExecutions`; `job-execution-review` snippet |
39
- | Inspect steps and exact log path | `operation/jobs/v1`: `getJobExecution`; `job-execution-inspect` snippet |
40
- | Investigate failures with detail reads | `failed-job-triage` snippet; [jobs](references/jobs.md) |
41
- | Start a job / stop an execution | `operation/jobs/v1`: `createJobExecution` / `deleteJobExecution`; confirm intent and active runs first |
42
- | Active/rollback versions, activation metadata | `dx/scripts/v1`: `getCodeVersions`; `code-version-inspect` snippet |
43
- | Activate/create/delete a code version | `dx/scripts/v1`: `updateCodeVersion` / `createCodeVersion` / `deleteCodeVersion` |
44
- | Site status, catalog, ordered cartridge path | `site/sites/v1`: `getSiteById`; `site-cartridge-inspect` snippet |
45
- | Change a site's custom cartridge path | `site/sites/v1`: `replaceSiteCustomCartridges`; preserve order and unrelated entries |
46
- | Basic product + optional category assignment | `create-product` snippet; [products](references/products.md) |
47
- | Campaign assignments and promotion details | `campaign-promotions` snippet; [promotions](references/promotions.md) |
36
+ | Task | API / starting point |
37
+ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
38
+ | Review runs, including successes | `operation/jobs/v1`: `searchJobExecutions`; `job-execution-review` snippet |
39
+ | Inspect steps and exact log path | `operation/jobs/v1`: `getJobExecution`; `job-execution-inspect` snippet |
40
+ | Investigate failures with detail reads | `failed-job-triage` snippet; [jobs](references/jobs.md) |
41
+ | Start a job / stop an execution | `operation/jobs/v1`: `createJobExecution` / `deleteJobExecution`; confirm intent and active runs first |
42
+ | Active/rollback versions, activation metadata | `dx/scripts/v1`: `getCodeVersions`; `code-version-inspect` snippet |
43
+ | Activate/create/delete a code version | `dx/scripts/v1`: `updateCodeVersion` / `createCodeVersion` / `deleteCodeVersion` |
44
+ | Site status, catalog, ordered cartridge path | `site/sites/v1`: `getSiteById`; `site-cartridge-inspect` snippet |
45
+ | Change a site's custom cartridge path | `site/sites/v1`: `replaceSiteCustomCartridges`; preserve order and unrelated entries |
46
+ | Basic product + optional category assignment | `create-product` snippet; [products](references/products.md) |
47
+ | Campaign assignments and promotion details | `campaign-promotions` snippet; [promotions](references/promotions.md) |
48
48
 
49
49
  File content is WebDAV: `webdav_list` / `webdav_get` / `webdav_put`.
50
50
  Deploy local cartridge files with `cartridge_deploy`. Job schedules/definitions
@@ -115,16 +115,48 @@ These are conditional setup references, not additional prerequisite reads.
115
115
  no program replay or automatic cleanup deletion.
116
116
  - Filter/page at the API, then project/aggregate in code. Include IDs, verification
117
117
  fields, errors, totals, and continuation inputs. Do not crop away missing data.
118
- - Limits: 20 calls, four concurrent, 30 seconds, 24 KB returned. Narrow oversized
118
+ - Limits: 20 calls, four outstanding, 30 seconds of active execution, 24 KB returned.
119
+ Managed API/token calls are serialized within an execution. Narrow oversized
119
120
  discovery; reduce live pages/projections.
120
121
  - SDK safety applies per request, including POST searches. Use only authorized
121
- targeted exceptions. Confirmation-required requests stop. Code mode does not
122
+ targeted exceptions. Confirmation uses client elicitation; see below. Code mode does not
122
123
  transfer binaries; use WebDAV tools for instance files, an external client for
123
124
  binary SCAPI endpoints.
124
125
  - `fetch` and `WebSocket` are disabled. Use `scapi.request()` inside programs;
125
126
  direct HTTP belongs in an external client, outside MCP Safety Mode. Do not use
126
127
  imports or other Node networking APIs to bypass this boundary.
127
128
 
129
+ ## Confirmation and cancellation
130
+
131
+ - A request requiring approval retains the running program and returns MCP
132
+ `input_required`. Let the client handle elicitation and the protocol retry;
133
+ never manufacture approval responses or modify opaque `requestState`.
134
+ - Retries validate the original code, input, and target arguments, then resume
135
+ the same execution. Repeated code is **not evaluated again**. Each request
136
+ needs its own approval; a whole program is not a transaction.
137
+ - Later managed calls wait for the pending decision. Earlier writes remain
138
+ applied. Prompts identify the organization, operation, method/path, and execution
139
+ ID. JSON previews are redacted and capped at five lines/400 characters; marked
140
+ truncation does not limit approval, which covers the full request.
141
+ Results include operation outcomes; `unknown` requires
142
+ checking the affected records before a fresh attempt.
143
+ - Decline/cancel terminates the entire execution, even if code catches errors.
144
+ Explicitly stop work with `scapi_execute({action: "cancel", executionId, skillRead: true})`;
145
+ omit code, input, project overrides, and protocol continuation state.
146
+ - Approval has no server deadline; waiting does not consume the active runtime
147
+ budget. At most four executions may be active per server, including pending
148
+ approvals. Cancel unwanted work to free a slot. Server shutdown or disconnect
149
+ releases retained workers; state does not survive a restart. Clients may impose
150
+ their own timeout. Duplicate responses never replay code or send a request twice.
151
+ - To change code/input/target, cancel unwanted retained work and start a fresh
152
+ execution without continuation state. Cancellation does not roll back writes.
153
+ Clients without form elicitation stop at confirmation-required requests.
154
+
155
+ For safety levels, rule matching, configuration precedence, and confirmation
156
+ semantics, read `docs_read({query: "guide-safety"})` when needed. If docs tools
157
+ are unavailable, use the [Safety Mode guide](https://salesforcecommercecloud.github.io/b2c-developer-tooling/guide/safety.md).
158
+ This is an optional policy reference, not another prerequisite read.
159
+
128
160
  ## Reusable workflows
129
161
 
130
162
  Find workflows with `codemode.search(query)`; `codemode.describe(name)`
@@ -200,7 +200,7 @@ export default class McpServerCommand extends BaseCommand {
200
200
  'docs-topics': Flags.string({
201
201
  description: 'Limit the documentation exposed by the docs tools to these categories (comma-separated allowlist). ' +
202
202
  'Options: script-api, job-step, commerce-api, pwa-kit-managed-runtime, sfnext, sfra, b2c-commerce, tooling, ' +
203
- 'help-admin, help-merchant. ' +
203
+ 'help-admin, help-merchant, help-kb. ' +
204
204
  'Bounds the whole docs corpus; per-call category/storefront narrow within it. Unknown names are ignored.',
205
205
  env: 'SFCC_DOCS_TOPICS',
206
206
  }),
package/dist/guidance.js CHANGED
@@ -36,8 +36,6 @@ const inputSchema = {
36
36
  const collectionSchema = z.object({
37
37
  id: z.string(),
38
38
  title: z.string(),
39
- isGA: z.boolean(),
40
- workspaces: z.array(z.string()).optional(),
41
39
  });
42
40
  const pageFields = {
43
41
  collections: z.array(collectionSchema),
package/dist/registry.js CHANGED
@@ -40,7 +40,7 @@ export function createToolRegistry(loadServices, serverContext, detectedWorkspac
40
40
  ...createDiagnosticsTools(loadServices, serverContext),
41
41
  ...createDocsTools(loadServices, { detectedWorkspaces, enabledCategories: enabledDocCategories }),
42
42
  ...createMrtTools(loadServices),
43
- ...createScapiTools(loadServices),
43
+ ...createScapiTools(loadServices, serverContext?.scapiExecutions),
44
44
  createGuidanceTool(),
45
45
  ];
46
46
  // Organize tools by their declared toolsets (supports multi-toolset)
@@ -1,6 +1,7 @@
1
1
  import { LogWatchRegistry } from './tools/diagnostics/log-watch-registry.js';
2
2
  import { MrtLogWatchRegistry } from './tools/diagnostics/mrt-log-watch-registry.js';
3
3
  import { DebugSessionRegistry } from './tools/diagnostics/session-registry.js';
4
+ import { ScapiExecutionRegistry } from './tools/scapi/execution-registry.js';
4
5
  /**
5
6
  * Server-scoped persistent state that lives for the lifetime of the MCP
6
7
  * server process. Holds registries for stateful resources (debug sessions,
@@ -27,6 +28,7 @@ export declare class ServerContext {
27
28
  readonly debugSessions: DebugSessionRegistry;
28
29
  readonly logWatches: LogWatchRegistry;
29
30
  readonly mrtLogWatches: MrtLogWatchRegistry;
31
+ readonly scapiExecutions: ScapiExecutionRegistry;
30
32
  constructor();
31
33
  destroyAll(): Promise<void>;
32
34
  }
@@ -6,6 +6,7 @@
6
6
  import { LogWatchRegistry } from './tools/diagnostics/log-watch-registry.js';
7
7
  import { MrtLogWatchRegistry } from './tools/diagnostics/mrt-log-watch-registry.js';
8
8
  import { DebugSessionRegistry } from './tools/diagnostics/session-registry.js';
9
+ import { ScapiExecutionRegistry } from './tools/scapi/execution-registry.js';
9
10
  /**
10
11
  * Server-scoped persistent state that lives for the lifetime of the MCP
11
12
  * server process. Holds registries for stateful resources (debug sessions,
@@ -32,16 +33,19 @@ export class ServerContext {
32
33
  debugSessions;
33
34
  logWatches;
34
35
  mrtLogWatches;
36
+ scapiExecutions;
35
37
  constructor() {
36
38
  this.debugSessions = new DebugSessionRegistry();
37
39
  this.logWatches = new LogWatchRegistry();
38
40
  this.mrtLogWatches = new MrtLogWatchRegistry();
41
+ this.scapiExecutions = new ScapiExecutionRegistry();
39
42
  }
40
43
  async destroyAll() {
41
44
  await Promise.allSettled([
42
45
  this.debugSessions.destroyAll(),
43
46
  this.logWatches.destroyAll(),
44
47
  this.mrtLogWatches.destroyAll(),
48
+ this.scapiExecutions.destroyAll(),
45
49
  ]);
46
50
  }
47
51
  }
package/dist/server.d.ts CHANGED
@@ -2,7 +2,7 @@ import { McpServer } from '@modelcontextprotocol/server';
2
2
  import type { CallToolResult, Implementation, ReadResourceResult, ToolAnnotations, ServerOptions, Transport } from '@modelcontextprotocol/server';
3
3
  import { type ZodRawShape } from 'zod';
4
4
  import type { Telemetry } from '@salesforce/b2c-tooling-sdk/telemetry';
5
- import type { McpToolConfig } from './utils/types.js';
5
+ import type { McpToolConfig, ToolContext } from './utils/types.js';
6
6
  /**
7
7
  * Extended server options.
8
8
  */
@@ -45,9 +45,7 @@ export declare class B2CDxMcpServer extends McpServer {
45
45
  * @param inputSchema - Zod schema for input validation
46
46
  * @param handler - Function to handle tool invocations
47
47
  */
48
- addTool(name: string, description: string, inputSchema: ZodRawShape, handler: (args: Record<string, unknown>, context?: {
49
- signal?: AbortSignal;
50
- }) => Promise<CallToolResult>, metadata?: Pick<McpToolConfig, 'outputSchema' | 'title'> & {
48
+ addTool(name: string, description: string, inputSchema: ZodRawShape, handler: (args: Record<string, unknown>, context?: ToolContext) => Promise<CallToolResult>, metadata?: Pick<McpToolConfig, 'outputSchema' | 'title'> & {
51
49
  annotations?: ToolAnnotations;
52
50
  }): void;
53
51
  close(): Promise<void>;
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * SPDX-License-Identifier: Apache-2
4
4
  * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5
5
  */
6
- import { McpServer, ProtocolError, ProtocolErrorCode, CLIENT_INFO_META_KEY, PROTOCOL_VERSION_META_KEY, } from '@modelcontextprotocol/server';
6
+ import { McpServer, ProtocolError, ProtocolErrorCode, CLIENT_INFO_META_KEY, PROTOCOL_VERSION_META_KEY, CLIENT_CAPABILITIES_META_KEY, } from '@modelcontextprotocol/server';
7
7
  import { z } from 'zod';
8
8
  import { getLogger } from '@salesforce/b2c-tooling-sdk/logging';
9
9
  /**
@@ -25,7 +25,11 @@ export class B2CDxMcpServer extends McpServer {
25
25
  */
26
26
  constructor(serverInfo, options) {
27
27
  const { cleanup, telemetry, ...serverOptions } = options ?? {};
28
- super(serverInfo, serverOptions);
28
+ // The bundled SDK patch supports zero for human-paced approval with no server deadline.
29
+ super(serverInfo, {
30
+ ...serverOptions,
31
+ inputRequired: { maxRounds: 20, roundTimeoutMs: 0, ...serverOptions.inputRequired },
32
+ });
29
33
  this.cleanup = cleanup;
30
34
  this.telemetry = telemetry;
31
35
  // Set up oninitialized handler
@@ -70,7 +74,16 @@ export class B2CDxMcpServer extends McpServer {
70
74
  this.telemetry?.addAttributes({ clientName: clientInfo.name, clientVersion: clientInfo.version });
71
75
  const startTime = Date.now();
72
76
  try {
73
- const result = await handler(args, { signal: context.mcpReq?.signal });
77
+ const envelope = context.mcpReq?.envelope;
78
+ const capabilities = envelope?.[CLIENT_CAPABILITIES_META_KEY] ?? this.server.getClientCapabilities();
79
+ const elicitation = capabilities?.elicitation;
80
+ const result = await handler(args, {
81
+ signal: context.mcpReq?.signal,
82
+ requestState: context.mcpReq?.requestState?.(),
83
+ inputResponses: context.mcpReq?.inputResponses,
84
+ supportsElicitation: elicitation !== undefined && (elicitation.form !== undefined || Object.keys(elicitation).length === 0),
85
+ keepCancellation: !envelope?.[PROTOCOL_VERSION_META_KEY],
86
+ });
74
87
  const runTimeMs = Date.now() - startTime;
75
88
  // Extract error message from CallToolResult content when isError is true
76
89
  const errorMessage = result.isError ? result.content?.find((c) => c.type === 'text')?.text : undefined;
@@ -19,8 +19,8 @@
19
19
  *
20
20
  * ## Resolution Pattern
21
21
  *
22
- * Both B2CInstance and MRT auth are resolved once at server startup (not on each tool call).
23
- * This provides fail-fast behavior and consistent performance.
22
+ * B2C instance and MRT auth are resolved for each tool call, so edits to project
23
+ * configuration are picked up without restarting the server.
24
24
  *
25
25
  * **B2C Instance** (for WebDAV/OCAPI tools):
26
26
  * - Flags (highest priority) merged with dw.json (auto-discovered or via --config)
@@ -39,7 +39,7 @@
39
39
  import fs from 'node:fs';
40
40
  import type { B2CInstance } from '@salesforce/b2c-tooling-sdk';
41
41
  import type { AuthStrategy } from '@salesforce/b2c-tooling-sdk/auth';
42
- import type { ResolvedB2CConfig } from '@salesforce/b2c-tooling-sdk/config';
42
+ import { type ResolvedB2CConfig } from '@salesforce/b2c-tooling-sdk/config';
43
43
  import type { ConfigurationResolutionSource, ProjectDirectoryInfo, ToolResolution } from './tools/project-context.js';
44
44
  import { WebDavClient, type CustomApisClient, type MetricsClient, type ScapiSchemasClient } from '@salesforce/b2c-tooling-sdk/clients';
45
45
  /**
@@ -101,13 +101,13 @@ export interface ServicesResolutionInputs {
101
101
  export declare class Services {
102
102
  /**
103
103
  * Pre-resolved B2C instance for WebDAV/OCAPI operations.
104
- * Resolved once at server startup from InstanceCommand flags and dw.json.
104
+ * Resolved for this tool call from server flags and project configuration.
105
105
  * Undefined if no B2C instance configuration was available.
106
106
  */
107
107
  readonly b2cInstance?: B2CInstance;
108
108
  /**
109
109
  * Pre-resolved MRT configuration (auth, project, environment, origin).
110
- * Resolved once at server startup from MrtCommand flags and ~/.mobify.
110
+ * Resolved for this tool call from server flags and MRT configuration.
111
111
  */
112
112
  readonly mrtConfig: MrtConfig;
113
113
  /**
@@ -310,6 +310,8 @@ export declare class Services {
310
310
  * @returns Resolved absolute path
311
311
  */
312
312
  resolveWithProjectDirectory(pathArg?: string, projectDirectoryOverride?: string): string;
313
+ /** Run a tool with its resolved safety policy, without changing other calls. */
314
+ runWithSafety<T>(callback: () => T): T;
313
315
  /**
314
316
  * Get file or directory stats.
315
317
  *
package/dist/services.js CHANGED
@@ -24,8 +24,8 @@
24
24
  *
25
25
  * ## Resolution Pattern
26
26
  *
27
- * Both B2CInstance and MRT auth are resolved once at server startup (not on each tool call).
28
- * This provides fail-fast behavior and consistent performance.
27
+ * B2C instance and MRT auth are resolved for each tool call, so edits to project
28
+ * configuration are picked up without restarting the server.
29
29
  *
30
30
  * **B2C Instance** (for WebDAV/OCAPI tools):
31
31
  * - Flags (highest priority) merged with dw.json (auto-discovered or via --config)
@@ -44,7 +44,9 @@
44
44
  import fs from 'node:fs';
45
45
  import path from 'node:path';
46
46
  import os from 'node:os';
47
- import { createCustomApisClient, createMetricsClient, createScapiSchemasClient, toOrganizationId, WebDavClient, } from '@salesforce/b2c-tooling-sdk/clients';
47
+ import { getB2CConfigDirectory } from '@salesforce/b2c-tooling-sdk/config';
48
+ import { SafetyGuard, resolveEffectiveSafetyConfig, loadGlobalSafetyConfig } from '@salesforce/b2c-tooling-sdk/safety';
49
+ import { createCustomApisClient, createMetricsClient, createScapiSchemasClient, toOrganizationId, WebDavClient, globalMiddlewareRegistry, createSafetyMiddleware, } from '@salesforce/b2c-tooling-sdk/clients';
48
50
  function createToolResolution(config, inputs) {
49
51
  const configuredProjectDirectory = config.values.projectDirectory;
50
52
  const projectDirectory = inputs?.projectDirectory ??
@@ -87,13 +89,13 @@ function createToolResolution(config, inputs) {
87
89
  export class Services {
88
90
  /**
89
91
  * Pre-resolved B2C instance for WebDAV/OCAPI operations.
90
- * Resolved once at server startup from InstanceCommand flags and dw.json.
92
+ * Resolved for this tool call from server flags and project configuration.
91
93
  * Undefined if no B2C instance configuration was available.
92
94
  */
93
95
  b2cInstance;
94
96
  /**
95
97
  * Pre-resolved MRT configuration (auth, project, environment, origin).
96
- * Resolved once at server startup from MrtCommand flags and ~/.mobify.
98
+ * Resolved for this tool call from server flags and MRT configuration.
97
99
  */
98
100
  mrtConfig;
99
101
  /**
@@ -339,9 +341,6 @@ export class Services {
339
341
  listDirectory(dirPath) {
340
342
  return fs.readdirSync(dirPath, { withFileTypes: true });
341
343
  }
342
- // ============================================
343
- // SCAPI Helper Methods
344
- // ============================================
345
344
  /**
346
345
  * Read a file from the filesystem.
347
346
  *
@@ -352,6 +351,9 @@ export class Services {
352
351
  readFile(filePath, encoding = 'utf8') {
353
352
  return fs.readFileSync(filePath, { encoding });
354
353
  }
354
+ // ============================================
355
+ // SCAPI Helper Methods
356
+ // ============================================
355
357
  /**
356
358
  * Resolve a path relative to the current working directory.
357
359
  *
@@ -414,6 +416,15 @@ export class Services {
414
416
  }
415
417
  return path.resolve(projectDir, pathArg);
416
418
  }
419
+ /** Run a tool with its resolved safety policy, without changing other calls. */
420
+ runWithSafety(callback) {
421
+ const environment = Object.fromEntries(['SFCC_SAFETY_LEVEL', 'SFCC_SAFETY_CONFIRM', 'SFCC_SAFETY_CONFIG'].map((name) => [
422
+ name,
423
+ this.getEnvironmentVariable(name),
424
+ ]));
425
+ const guard = new SafetyGuard(resolveEffectiveSafetyConfig(this.resolvedConfig.values.safety, loadGlobalSafetyConfig(getB2CConfigDirectory(), environment, this.resolution.projectDirectory.path), environment));
426
+ return globalMiddlewareRegistry.runWithOverrides([{ name: 'cli-safety-guard', getMiddleware: () => createSafetyMiddleware(guard) }], callback);
427
+ }
417
428
  /**
418
429
  * Get file or directory stats.
419
430
  *
@@ -271,7 +271,7 @@ export function createToolAdapter(options, loadServices, serverContext) {
271
271
  executionResolution.directories[name] = value;
272
272
  },
273
273
  };
274
- const output = await execute(args, context);
274
+ const output = await services.runWithSafety(() => execute(args, context));
275
275
  // 6. Format output
276
276
  const result = formatOutput(output);
277
277
  return resolution ? attachResolution(result, executionResolution) : result;
@@ -0,0 +1,62 @@
1
+ import type { ScapiConfirmation, ScapiRuntimeControl } from '@salesforce/b2c-tooling-sdk/scapi';
2
+ import type { ToolContext, ToolResult } from '../../utils/types.js';
3
+ declare function deferred<T>(): {
4
+ promise: Promise<T>;
5
+ resolve: (value: T) => void;
6
+ reject: (reason: Error) => void;
7
+ };
8
+ interface OperationRecord {
9
+ kind: 'auth' | 'request';
10
+ method?: string;
11
+ path?: string;
12
+ status: 'awaiting_confirmation' | 'completed' | 'failed' | 'in_flight' | 'not_sent' | 'unknown';
13
+ httpStatus?: number;
14
+ }
15
+ interface ApprovalRound {
16
+ state: string;
17
+ key: string;
18
+ prompt: ToolResult;
19
+ decision: ReturnType<typeof deferred<void>>;
20
+ responded?: boolean;
21
+ }
22
+ /** One worker survives protocol round trips; none of its code is replayed. */
23
+ export declare class ScapiExecution {
24
+ readonly digest: string;
25
+ private readonly supportsElicitation;
26
+ private readonly mintState;
27
+ control?: ScapiRuntimeControl;
28
+ readonly controller: AbortController;
29
+ done: Promise<void>;
30
+ readonly id: `${string}-${string}-${string}-${string}-${string}`;
31
+ readonly operations: OperationRecord[];
32
+ result?: ToolResult;
33
+ readonly rounds: Map<string, ApprovalRound>;
34
+ private cancellation?;
35
+ private cancellationListeners;
36
+ private dispatched;
37
+ private event;
38
+ private pending?;
39
+ private queue;
40
+ constructor(digest: string, supportsElicitation: boolean, mintState: (round: number) => string);
41
+ cancel(reason?: string): void;
42
+ confirm(request: ScapiConfirmation): Promise<void>;
43
+ finish(result: ToolResult): void;
44
+ markDispatched(): void;
45
+ retry(state: string, context: ToolContext): Promise<ToolResult>;
46
+ runCall<T>(kind: OperationRecord['kind'], input: unknown, operation: () => Promise<T>): Promise<T>;
47
+ wait(context?: ToolContext): Promise<ToolResult>;
48
+ }
49
+ /** Stdio-server-owned continuations. Terminal records are bounded; tokens never start new work. */
50
+ export declare class ScapiExecutionRegistry {
51
+ private readonly options;
52
+ private readonly executions;
53
+ private readonly secret;
54
+ constructor(options?: {
55
+ maxActive?: number;
56
+ });
57
+ cancel(id: string): Promise<ToolResult>;
58
+ destroyAll(): Promise<void>;
59
+ retry(args: unknown, context: ToolContext): Promise<ToolResult>;
60
+ start(args: unknown, context: ToolContext | undefined, run: (execution: ScapiExecution) => Promise<ToolResult>): Promise<ToolResult>;
61
+ }
62
+ export {};