@salesforce/b2c-dx-mcp 1.10.1 → 2.0.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.
Files changed (35) hide show
  1. package/dist/commands/mcp.d.ts +13 -7
  2. package/dist/commands/mcp.js +52 -11
  3. package/dist/registry.d.ts +3 -2
  4. package/dist/server-context.d.ts +1 -1
  5. package/dist/server-context.js +1 -1
  6. package/dist/services.d.ts +9 -1
  7. package/dist/services.js +11 -1
  8. package/dist/tools/adapter.d.ts +8 -1
  9. package/dist/tools/adapter.js +10 -4
  10. package/dist/tools/cartridges/index.js +4 -1
  11. package/dist/tools/diagnostics/config-inspect.js +3 -2
  12. package/dist/tools/diagnostics/debug-list-sessions.js +0 -1
  13. package/dist/tools/diagnostics/debug-start-session.js +10 -8
  14. package/dist/tools/diagnostics/session-registry.js +2 -2
  15. package/dist/tools/mrt/index.js +9 -3
  16. package/dist/tools/project-context.d.ts +22 -0
  17. package/dist/tools/project-context.js +22 -0
  18. package/dist/tools/scapi/metrics-get.js +1 -0
  19. package/dist/tools/scapi/scapi-custom-api-generate-scaffold.d.ts +4 -1
  20. package/dist/tools/scapi/scapi-custom-api-generate-scaffold.js +16 -2
  21. package/dist/tools/scapi/scapi-custom-apis-get-status.js +1 -0
  22. package/dist/tools/storefrontnext/figma/figma-to-component/index.d.ts +3 -0
  23. package/dist/tools/storefrontnext/figma/figma-to-component/index.js +9 -3
  24. package/dist/tools/storefrontnext/figma/generate-component/index.d.ts +3 -0
  25. package/dist/tools/storefrontnext/figma/generate-component/index.js +4 -1
  26. package/dist/tools/storefrontnext/figma/map-tokens/index.d.ts +3 -0
  27. package/dist/tools/storefrontnext/figma/map-tokens/index.js +9 -2
  28. package/dist/tools/storefrontnext/page-designer-decorator/index.d.ts +8 -1
  29. package/dist/tools/storefrontnext/page-designer-decorator/index.js +7 -2
  30. package/dist/tools/storefrontnext/site-theming/index.js +6 -1
  31. package/dist/tools/storefrontnext/site-theming/theming-store.d.ts +2 -0
  32. package/dist/tools/storefrontnext/site-theming/theming-store.js +1 -1
  33. package/dist/tools/storefrontnext/site-theming/types.d.ts +1 -0
  34. package/oclif.manifest.json +1 -1
  35. package/package.json +2 -2
@@ -1,6 +1,7 @@
1
1
  import { BaseCommand } from '@salesforce/b2c-tooling-sdk/cli';
2
2
  import type { ResolvedB2CConfig } from '@salesforce/b2c-tooling-sdk/config';
3
3
  import { Services } from '../services.js';
4
+ import type { ProjectContextInput } from '../tools/project-context.js';
4
5
  /**
5
6
  * oclif Command that starts the B2C DX MCP server.
6
7
  *
@@ -81,13 +82,16 @@ export default class McpServerCommand extends BaseCommand<typeof McpServerComman
81
82
  * - extractInstanceFlags() - B2C instance flags (--server, --username, etc.)
82
83
  * - extractMrtFlags() - MRT flags (--api-key, --project, etc.) and loading options
83
84
  *
84
- * Priority (highest to lowest):
85
- * 1. CLI flags (--server, --username, --api-key, etc.)
86
- * 2. Environment variables (SFCC_SERVER, SFCC_USERNAME, MRT_API_KEY, etc.)
87
- * 3. dw.json file (via --config flag or auto-discovered from --project-directory)
88
- * 4. ~/.mobify file (for MRT API key)
85
+ * Configuration file selection (highest to lowest):
86
+ * 1. Per-call configPath
87
+ * 2. Startup --config / SFCC_CONFIG
88
+ * 3. SFCC_CONFIG from the selected project's .env
89
+ * 4. dw.json in the selected project directory
90
+ *
91
+ * Values are then merged through the normal CLI resolver, including
92
+ * environment, plugin, dw.json, ~/.mobify, and package.json sources.
89
93
  */
90
- protected loadConfiguration(): Promise<ResolvedB2CConfig>;
94
+ protected loadConfiguration(projectContext?: ProjectContextInput): Promise<ResolvedB2CConfig>;
91
95
  /**
92
96
  * Loads configuration and creates a new Services instance.
93
97
  *
@@ -97,7 +101,7 @@ export default class McpServerCommand extends BaseCommand<typeof McpServerComman
97
101
  *
98
102
  * @returns A new Services instance with loaded configuration
99
103
  */
100
- protected loadServices(): Promise<Services>;
104
+ protected loadServices(projectContext?: ProjectContextInput): Promise<Services>;
101
105
  /**
102
106
  * Main entry point - starts the MCP server.
103
107
  *
@@ -125,4 +129,6 @@ export default class McpServerCommand extends BaseCommand<typeof McpServerComman
125
129
  * These can be exposed to Services if needed for features like telemetry or caching.
126
130
  */
127
131
  run(): Promise<void>;
132
+ /** Parse a project's .env without mutating the long-lived MCP process environment. */
133
+ private loadProjectEnvironment;
128
134
  }
@@ -132,8 +132,10 @@
132
132
  * { "args": ["--toolsets", "all", "--allow-non-ga-tools", "--debug"] }
133
133
  * ```
134
134
  */
135
+ import path from 'node:path';
135
136
  import { Flags } from '@oclif/core';
136
137
  import { BaseCommand, MrtCommand, InstanceCommand, loadConfig, extractInstanceFlags, extractMrtFlags, } from '@salesforce/b2c-tooling-sdk/cli';
138
+ import { EnvSource, readProjectEnvironment } from '@salesforce/b2c-tooling-sdk/config';
137
139
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
138
140
  import { B2CDxMcpServer } from '../server.js';
139
141
  import { Services } from '../services.js';
@@ -240,24 +242,47 @@ export default class McpServerCommand extends BaseCommand {
240
242
  * - extractInstanceFlags() - B2C instance flags (--server, --username, etc.)
241
243
  * - extractMrtFlags() - MRT flags (--api-key, --project, etc.) and loading options
242
244
  *
243
- * Priority (highest to lowest):
244
- * 1. CLI flags (--server, --username, --api-key, etc.)
245
- * 2. Environment variables (SFCC_SERVER, SFCC_USERNAME, MRT_API_KEY, etc.)
246
- * 3. dw.json file (via --config flag or auto-discovered from --project-directory)
247
- * 4. ~/.mobify file (for MRT API key)
245
+ * Configuration file selection (highest to lowest):
246
+ * 1. Per-call configPath
247
+ * 2. Startup --config / SFCC_CONFIG
248
+ * 3. SFCC_CONFIG from the selected project's .env
249
+ * 4. dw.json in the selected project directory
250
+ *
251
+ * Values are then merged through the normal CLI resolver, including
252
+ * environment, plugin, dw.json, ~/.mobify, and package.json sources.
248
253
  */
249
- async loadConfiguration() {
254
+ async loadConfiguration(projectContext) {
250
255
  const mrt = extractMrtFlags(this.flags);
256
+ const baseOptions = this.getBaseConfigOptions();
257
+ const effectiveProjectDirectory = projectContext?.projectDirectory ?? baseOptions.projectDirectory;
258
+ const projectEnvironment = this.loadProjectEnvironment(effectiveProjectDirectory);
259
+ const projectConfigPath = projectEnvironment?.SFCC_CONFIG;
260
+ const configPath = (projectContext?.configPath
261
+ ? path.resolve(effectiveProjectDirectory ?? process.cwd(), projectContext.configPath)
262
+ : undefined) ??
263
+ baseOptions.configPath ??
264
+ (projectConfigPath && effectiveProjectDirectory
265
+ ? path.isAbsolute(projectConfigPath)
266
+ ? projectConfigPath
267
+ : path.resolve(effectiveProjectDirectory, projectConfigPath)
268
+ : undefined);
251
269
  const options = {
252
- ...this.getBaseConfigOptions(),
270
+ ...baseOptions,
253
271
  ...mrt.options,
272
+ configPath,
273
+ ...(projectContext?.projectDirectory && {
274
+ projectDirectory: projectContext.projectDirectory,
275
+ workingDirectory: projectContext.projectDirectory,
276
+ }),
254
277
  };
255
278
  // Combine B2C instance flags and MRT config flags
256
279
  const flagConfig = {
257
280
  ...extractInstanceFlags(this.flags),
258
281
  ...mrt.config,
259
282
  };
260
- return loadConfig(flagConfig, options);
283
+ return loadConfig(flagConfig, options, {
284
+ before: projectEnvironment ? [new EnvSource(projectEnvironment)] : undefined,
285
+ });
261
286
  }
262
287
  /**
263
288
  * Loads configuration and creates a new Services instance.
@@ -268,9 +293,11 @@ export default class McpServerCommand extends BaseCommand {
268
293
  *
269
294
  * @returns A new Services instance with loaded configuration
270
295
  */
271
- async loadServices() {
272
- const config = await this.loadConfiguration();
273
- return Services.fromResolvedConfig(config);
296
+ async loadServices(projectContext) {
297
+ const config = await this.loadConfiguration(projectContext);
298
+ const effectiveProjectDirectory = projectContext?.projectDirectory ?? this.flags?.['project-directory'];
299
+ const projectEnvironment = this.loadProjectEnvironment(effectiveProjectDirectory);
300
+ return Services.fromResolvedConfig(config, projectEnvironment);
274
301
  }
275
302
  /**
276
303
  * Main entry point - starts the MCP server.
@@ -362,5 +389,19 @@ export default class McpServerCommand extends BaseCommand {
362
389
  });
363
390
  this.logger.info({ version: this.config.version }, 'MCP Server running on stdio');
364
391
  }
392
+ /** Parse a project's .env without mutating the long-lived MCP process environment. */
393
+ loadProjectEnvironment(projectDirectory) {
394
+ if (!projectDirectory)
395
+ return undefined;
396
+ const envPath = path.join(projectDirectory, '.env');
397
+ try {
398
+ return readProjectEnvironment(projectDirectory);
399
+ }
400
+ catch (error) {
401
+ const message = error instanceof Error ? error.message : String(error);
402
+ this.logger?.warn({ envPath, error: message }, '[Config] Failed to load project .env file');
403
+ }
404
+ return undefined;
405
+ }
365
406
  }
366
407
  //# sourceMappingURL=mcp.js.map
@@ -4,6 +4,7 @@ import type { McpTool, Toolset, StartupFlags } from './utils/index.js';
4
4
  import type { B2CDxMcpServer } from './server.js';
5
5
  import type { Services } from './services.js';
6
6
  import type { ServerContext } from './server-context.js';
7
+ import type { ProjectContextInput } from './tools/project-context.js';
7
8
  /**
8
9
  * Registry of tools organized by toolset.
9
10
  * Tools can belong to multiple toolsets via their `toolsets` array.
@@ -17,5 +18,5 @@ export type ToolRegistry = Record<Toolset, McpTool[]>;
17
18
  * @param loadServices - Function that loads configuration and returns Services instance
18
19
  * @returns Complete tool registry
19
20
  */
20
- export declare function createToolRegistry(loadServices: () => Promise<Services> | Services, serverContext?: ServerContext, detectedWorkspaces?: readonly ProjectType[], enabledDocCategories?: readonly DocCategory[]): ToolRegistry;
21
- export declare function registerToolsets(flags: StartupFlags, server: B2CDxMcpServer, loadServices: () => Promise<Services> | Services, serverContext?: ServerContext): Promise<void>;
21
+ export declare function createToolRegistry(loadServices: (projectContext?: ProjectContextInput) => Promise<Services> | Services, serverContext?: ServerContext, detectedWorkspaces?: readonly ProjectType[], enabledDocCategories?: readonly DocCategory[]): ToolRegistry;
22
+ export declare function registerToolsets(flags: StartupFlags, server: B2CDxMcpServer, loadServices: (projectContext?: ProjectContextInput) => Promise<Services> | Services, serverContext?: ServerContext): Promise<void>;
@@ -18,7 +18,7 @@ import { DebugSessionRegistry } from './tools/diagnostics/session-registry.js';
18
18
  * client would also share the same context. Tools that mutate registries
19
19
  * (like the debug tools) should not assume single-tenant access.
20
20
  *
21
- * The registries dedup on a stable key (host:client_id for debug, hostname for
21
+ * The registries dedup on an internal stable key (host plus MCP-owned debugger client ID, hostname for
22
22
  * SFCC log watches, project/environment/origin for MRT log watches), so
23
23
  * multi-agent use is functional but agents may see each other's
24
24
  * sessions/watches via list tools.
@@ -23,7 +23,7 @@ import { DebugSessionRegistry } from './tools/diagnostics/session-registry.js';
23
23
  * client would also share the same context. Tools that mutate registries
24
24
  * (like the debug tools) should not assume single-tenant access.
25
25
  *
26
- * The registries dedup on a stable key (host:client_id for debug, hostname for
26
+ * The registries dedup on an internal stable key (host plus MCP-owned debugger client ID, hostname for
27
27
  * SFCC log watches, project/environment/origin for MRT log watches), so
28
28
  * multi-agent use is functional but agents may see each other's
29
29
  * sessions/watches via list tools.
@@ -65,6 +65,8 @@ export interface ServicesOptions {
65
65
  mrtConfig?: MrtConfig;
66
66
  /** Resolved configuration for access to SCAPI settings */
67
67
  resolvedConfig: ResolvedB2CConfig;
68
+ /** Project-scoped environment parsed from the effective project's .env file. */
69
+ projectEnvironment?: Readonly<Record<string, string | undefined>>;
68
70
  }
69
71
  /**
70
72
  * Services class that provides utilities for MCP tools.
@@ -100,6 +102,7 @@ export declare class Services {
100
102
  * Provides access to shortCode, tenantId, and OAuth credentials.
101
103
  * @private
102
104
  */
105
+ private readonly projectEnvironment;
103
106
  private readonly resolvedConfig;
104
107
  constructor(opts: ServicesOptions);
105
108
  /**
@@ -114,7 +117,7 @@ export declare class Services {
114
117
  * const services = Services.fromResolvedConfig(this.resolvedConfig);
115
118
  * ```
116
119
  */
117
- static fromResolvedConfig(config: ResolvedB2CConfig): Services;
120
+ static fromResolvedConfig(config: ResolvedB2CConfig, projectEnvironment?: Readonly<Record<string, string | undefined>>): Services;
118
121
  /**
119
122
  * Check if a file or directory exists.
120
123
  *
@@ -143,6 +146,11 @@ export declare class Services {
143
146
  * Get the current working directory.
144
147
  */
145
148
  getCwd(): string;
149
+ /**
150
+ * Read an environment variable with the ambient process environment taking
151
+ * precedence over the project-scoped .env value.
152
+ */
153
+ getEnvironmentVariable(name: string): string | undefined;
146
154
  /**
147
155
  * Get the user's home directory.
148
156
  */
package/dist/services.js CHANGED
@@ -79,11 +79,13 @@ export class Services {
79
79
  * Provides access to shortCode, tenantId, and OAuth credentials.
80
80
  * @private
81
81
  */
82
+ projectEnvironment;
82
83
  resolvedConfig;
83
84
  constructor(opts) {
84
85
  this.b2cInstance = opts.b2cInstance;
85
86
  this.mrtConfig = opts.mrtConfig ?? {};
86
87
  this.resolvedConfig = opts.resolvedConfig;
88
+ this.projectEnvironment = opts.projectEnvironment ?? {};
87
89
  }
88
90
  /**
89
91
  * Creates a Services instance from an already-resolved configuration.
@@ -97,7 +99,7 @@ export class Services {
97
99
  * const services = Services.fromResolvedConfig(this.resolvedConfig);
98
100
  * ```
99
101
  */
100
- static fromResolvedConfig(config) {
102
+ static fromResolvedConfig(config, projectEnvironment) {
101
103
  // Build MRT config using factory methods
102
104
  const mrtConfig = {
103
105
  auth: config.hasMrtConfig() ? config.createMrtAuth() : undefined,
@@ -111,6 +113,7 @@ export class Services {
111
113
  b2cInstance,
112
114
  mrtConfig,
113
115
  resolvedConfig: config,
116
+ projectEnvironment,
114
117
  });
115
118
  }
116
119
  // ============================================
@@ -161,6 +164,13 @@ export class Services {
161
164
  getCwd() {
162
165
  return process.cwd();
163
166
  }
167
+ /**
168
+ * Read an environment variable with the ambient process environment taking
169
+ * precedence over the project-scoped .env value.
170
+ */
171
+ getEnvironmentVariable(name) {
172
+ return process.env[name] ?? this.projectEnvironment[name];
173
+ }
164
174
  /**
165
175
  * Get the user's home directory.
166
176
  */
@@ -69,6 +69,7 @@ import type { B2CInstance } from '@salesforce/b2c-tooling-sdk';
69
69
  import type { McpTool, ToolResult, Toolset } from '../utils/index.js';
70
70
  import type { Services, MrtConfig } from '../services.js';
71
71
  import type { ServerContext } from '../server-context.js';
72
+ import { type ProjectContextInput } from './project-context.js';
72
73
  /**
73
74
  * Context provided to tool execute functions.
74
75
  * Contains the B2CInstance and/or MRT config based on tool requirements.
@@ -125,6 +126,12 @@ export interface ToolAdapterOptions<TInput, TOutput> {
125
126
  * Defaults to false.
126
127
  */
127
128
  requiresMrtAuth?: boolean;
129
+ /**
130
+ * Whether this tool resolves configuration or files relative to a project.
131
+ * Project-aware tools automatically expose a per-call `projectDirectory`
132
+ * input and use it while loading Services.
133
+ */
134
+ usesProjectContext?: boolean;
128
135
  /**
129
136
  * Execute function that performs the tool's operation.
130
137
  * Receives validated input and a context with B2CInstance and/or auth based on requirements.
@@ -212,4 +219,4 @@ export declare function jsonResult(data: unknown, indent?: number): ToolResult;
212
219
  * }, loadServices);
213
220
  * ```
214
221
  */
215
- export declare function createToolAdapter<TInput, TOutput>(options: ToolAdapterOptions<TInput, TOutput>, loadServices: () => Promise<Services> | Services, serverContext?: ServerContext): McpTool;
222
+ export declare function createToolAdapter<TInput, TOutput>(options: ToolAdapterOptions<TInput, TOutput>, loadServices: (projectContext?: ProjectContextInput) => Promise<Services> | Services, serverContext?: ServerContext): McpTool;
@@ -70,6 +70,7 @@
70
70
  * ```
71
71
  */
72
72
  import { z } from 'zod';
73
+ import { projectContextInputSchema } from './project-context.js';
73
74
  /**
74
75
  * Creates a text-only success result.
75
76
  *
@@ -169,13 +170,17 @@ function formatZodErrors(error) {
169
170
  * ```
170
171
  */
171
172
  export function createToolAdapter(options, loadServices, serverContext) {
172
- const { name, description, inputSchema, toolsets, isGA = true, requiresInstance = false, requiresMrtAuth = false, execute, formatOutput, } = options;
173
+ const { name, description, inputSchema, toolsets, isGA = true, requiresInstance = false, requiresMrtAuth = false, usesProjectContext = false, execute, formatOutput, } = options;
174
+ const effectiveUsesProjectContext = usesProjectContext || requiresInstance || requiresMrtAuth;
175
+ const effectiveInputSchema = effectiveUsesProjectContext
176
+ ? { ...projectContextInputSchema, ...inputSchema }
177
+ : inputSchema;
173
178
  // Create Zod schema from inputSchema definition
174
- const zodSchema = z.object(inputSchema);
179
+ const zodSchema = z.object(effectiveInputSchema);
175
180
  return {
176
181
  name,
177
182
  description,
178
- inputSchema,
183
+ inputSchema: effectiveInputSchema,
179
184
  toolsets,
180
185
  isGA,
181
186
  async handler(rawArgs) {
@@ -187,7 +192,8 @@ export function createToolAdapter(options, loadServices, serverContext) {
187
192
  const args = parseResult.data;
188
193
  try {
189
194
  // 2. Load Services to get fresh configuration (re-reads config files)
190
- const services = await loadServices();
195
+ const projectContext = effectiveUsesProjectContext ? args : undefined;
196
+ const services = await loadServices(projectContext);
191
197
  // 3. Get B2CInstance if required (loaded on each call)
192
198
  let b2cInstance;
193
199
  if (requiresInstance) {
@@ -44,6 +44,7 @@ function createCartridgeDeployTool(loadServices, injections) {
44
44
  toolsets: ['CARTRIDGES'],
45
45
  isGA: true,
46
46
  requiresInstance: true,
47
+ usesProjectContext: true,
47
48
  inputSchema: {
48
49
  directory: z
49
50
  .string()
@@ -87,7 +88,8 @@ function createCartridgeDeployTool(loadServices, injections) {
87
88
  instance.config.codeVersion = codeVersion;
88
89
  }
89
90
  // Resolve directory path: relative paths are resolved relative to project directory, absolute paths are used as-is
90
- const directory = context.services.resolveWithProjectDirectory(args.directory);
91
+ const projectDirectory = context.services.resolveProjectDirectory(args.projectDirectory);
92
+ const directory = context.services.resolveWithProjectDirectory(args.directory, args.projectDirectory);
91
93
  // Parse options
92
94
  const options = {
93
95
  include: args.cartridges,
@@ -106,6 +108,7 @@ function createCartridgeDeployTool(loadServices, injections) {
106
108
  const result = await findAndDeployCartridgesFn(instance, directory, options);
107
109
  return {
108
110
  ...result,
111
+ projectDirectory,
109
112
  resolvedDirectory: directory,
110
113
  postInstructions: CARTRIDGE_PATH_REMINDER,
111
114
  };
@@ -20,11 +20,12 @@ export function createConfigInspectTool(loadServices) {
20
20
  name: 'config_inspect',
21
21
  description: 'Inspect the resolved B2C Commerce configuration the MCP server is using — instance hostname, auth, SCAPI, MRT, and other settings — along with which source (dw.json, environment variables, flags) provided each value. ' +
22
22
  'Secrets (passwords, client secrets, API keys) are redacted by default. ' +
23
- 'The output includes the effective projectDirectory and how it was resolved (explicit --project-directory / SFCC_PROJECT_DIRECTORY vs. the process working directory), which is useful for diagnosing why the server targets the wrong instance or cannot find a project. ' +
23
+ 'Pass projectDirectory and/or configPath to inspect the same project and dw.json-format file a CLI command would use. The output includes the effective projectDirectory and source provenance, which is useful for diagnosing why the server targets the wrong instance or cannot find a project. ' +
24
24
  'Use this first when configuration seems wrong, auth is failing, or the server appears to be operating in the wrong directory.',
25
25
  toolsets: ['DIAGNOSTICS'],
26
26
  isGA: true,
27
27
  requiresInstance: false,
28
+ usesProjectContext: true,
28
29
  inputSchema: {
29
30
  unmask: z
30
31
  .boolean()
@@ -33,7 +34,7 @@ export function createConfigInspectTool(loadServices) {
33
34
  },
34
35
  async execute(args, { services }) {
35
36
  const resolved = services.getResolvedConfig();
36
- const projectDirectory = services.resolveProjectDirectory();
37
+ const projectDirectory = services.resolveProjectDirectory(args.projectDirectory);
37
38
  return {
38
39
  config: redactConfigValues(resolved.values, { unmask: args.unmask ?? false }),
39
40
  projectDirectory,
@@ -22,7 +22,6 @@ export function createDebugListSessionsTool(loadServices, serverContext) {
22
22
  return {
23
23
  session_id: entry.sessionId,
24
24
  hostname: entry.hostname,
25
- client_id: entry.clientId,
26
25
  halted_threads: entry.manager
27
26
  .getKnownThreads()
28
27
  .filter((t) => t.status === 'halted')
@@ -3,6 +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 { randomUUID } from 'node:crypto';
6
7
  import { z } from 'zod';
7
8
  import { createToolAdapter, jsonResult } from '../adapter.js';
8
9
  import { DebugSessionManager, createSourceMapper, } from '@salesforce/b2c-tooling-sdk/operations/debug';
@@ -12,19 +13,17 @@ export function createDebugStartSessionTool(loadServices, serverContext) {
12
13
  return createToolAdapter({
13
14
  name: 'debug_start_session',
14
15
  description: 'Start a script debugger session on a B2C Commerce instance to debug SFRA controllers, custom API scripts, hooks, jobs, or any server-side script. ' +
16
+ 'Uses projectDirectory to load project configuration and discover cartridges. cartridgeDirectory may override only the cartridge discovery/source-mapping root. ' +
15
17
  'Returns a session_id for use with other debug tools, plus discovered cartridge mappings. ' +
16
18
  'WARNING: Debug sessions halt remote request threads on the instance. Always call debug_end_session when finished.',
17
19
  toolsets: ['CARTRIDGES', 'DIAGNOSTICS', 'SCAPI'],
18
20
  inputSchema: {
19
- cartridge_directory: z
21
+ cartridgeDirectory: z
20
22
  .string()
21
23
  .optional()
22
- .describe('Path to directory containing cartridges. Defaults to project directory.'),
23
- client_id: z
24
- .string()
25
- .optional()
26
- .describe('Client ID for the debugger API. Defaults to "b2c-cli". Use a different ID to run concurrent sessions on the same host.'),
24
+ .describe('Optional cartridge discovery and debugger source-mapping root. Relative paths resolve from projectDirectory. Defaults to projectDirectory.'),
27
25
  },
26
+ usesProjectContext: true,
28
27
  async execute(args, context) {
29
28
  const registry = getRegistry(context);
30
29
  const credentials = context.services.getBasicAuthCredentials();
@@ -33,8 +32,9 @@ export function createDebugStartSessionTool(loadServices, serverContext) {
33
32
  'Set via SFCC_SERVER/SFCC_USERNAME/SFCC_PASSWORD env vars, or dw.json.');
34
33
  }
35
34
  const { hostname, username, password } = credentials;
36
- const clientId = args.client_id ?? 'b2c-cli';
37
- const cartridgeDir = context.services.resolveWithProjectDirectory(args.cartridge_directory);
35
+ const clientId = `b2c-dx-mcp-${randomUUID()}`;
36
+ const projectDirectory = context.services.resolveProjectDirectory(args.projectDirectory);
37
+ const cartridgeDir = context.services.resolveWithProjectDirectory(args.cartridgeDirectory, args.projectDirectory);
38
38
  const cartridges = findCartridges(cartridgeDir);
39
39
  const warnings = [];
40
40
  if (cartridges.length === 0) {
@@ -70,6 +70,8 @@ export function createDebugStartSessionTool(loadServices, serverContext) {
70
70
  cartridge_mappings: cartridgeMappings,
71
71
  session_cookie: dwsid ? { name: 'dwsid', value: dwsid } : null,
72
72
  warnings,
73
+ projectDirectory,
74
+ cartridgeDirectory: cartridgeDir,
73
75
  };
74
76
  },
75
77
  formatOutput: (output) => jsonResult(output),
@@ -67,9 +67,9 @@ export class DebugSessionRegistry {
67
67
  const { hostname, clientId, manager, sourceMapper, cartridges } = opts;
68
68
  const existing = this.findByHostAndClientId(hostname, clientId);
69
69
  if (existing) {
70
- throw new Error(`A debug session already exists for ${hostname} with client ID "${clientId}" ` +
70
+ throw new Error(`A debug session already exists for ${hostname} ` +
71
71
  `(session_id: "${existing.sessionId}"). ` +
72
- `End it with debug_end_session first, or use a different client_id.`);
72
+ `End it with debug_end_session first.`);
73
73
  }
74
74
  const sessionId = randomUUID();
75
75
  const now = Date.now();
@@ -127,6 +127,7 @@ function createMrtBundlePushTool(loadServices, injections) {
127
127
  isGA: true,
128
128
  // MRT operations use ApiKeyStrategy from MRT_API_KEY or ~/.mobify
129
129
  requiresMrtAuth: true,
130
+ usesProjectContext: true,
130
131
  inputSchema: {
131
132
  buildDirectory: z
132
133
  .string()
@@ -167,12 +168,13 @@ function createMrtBundlePushTool(loadServices, injections) {
167
168
  // Get origin from --cloud-origin flag or mrtOrigin config (optional)
168
169
  const origin = context.mrtConfig?.origin;
169
170
  // Detect project type and get project-type-aware defaults
170
- const projectDir = context.services.resolveWithProjectDirectory();
171
+ const projectDirectory = context.services.resolveProjectDirectory(args.projectDirectory);
172
+ const projectDir = projectDirectory.path;
171
173
  const { projectTypes } = await detectWorkspaceTypeFn(projectDir);
172
174
  const defaults = getDefaultsForProjectTypes(projectTypes);
173
175
  const ssrOnly = args.ssrOnly ? parseGlobPatterns(args.ssrOnly) : defaults.ssrOnly;
174
176
  const ssrShared = args.ssrShared ? parseGlobPatterns(args.ssrShared) : defaults.ssrShared;
175
- const buildDirectory = context.services.resolveWithProjectDirectory(args.buildDirectory ?? defaults.buildDirectory);
177
+ const buildDirectory = context.services.resolveWithProjectDirectory(args.buildDirectory ?? defaults.buildDirectory, args.projectDirectory);
176
178
  // Log all computed variables before pushing bundle
177
179
  const logger = getLogger();
178
180
  logger.debug({
@@ -196,7 +198,11 @@ function createMrtBundlePushTool(loadServices, injections) {
196
198
  target: environment,
197
199
  origin, // MRT API origin URL (optional, defaults to https://cloud.mobify.com)
198
200
  }, context.mrtConfig.auth);
199
- return result;
201
+ return {
202
+ ...result,
203
+ projectDirectory,
204
+ resolvedBuildDirectory: buildDirectory,
205
+ };
200
206
  },
201
207
  formatOutput: (output) => jsonResult(output),
202
208
  }, loadServices);
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ /** Input shared by MCP tools that resolve files or configuration from a project. */
3
+ export interface ProjectContextInput {
4
+ /** Per-call project directory override. */
5
+ projectDirectory?: string;
6
+ /** Per-call explicit dw.json-format configuration path. */
7
+ configPath?: string;
8
+ }
9
+ /** Effective project directory and the source that selected it. */
10
+ export interface ProjectDirectoryInfo {
11
+ path: string;
12
+ source: 'argument' | 'config' | 'cwd';
13
+ }
14
+ /** Shared schema field injected into every project-aware MCP tool. */
15
+ export declare const projectDirectoryInput: z.ZodOptional<z.ZodString>;
16
+ /** Shared explicit dw.json path field injected into every project-aware MCP tool. */
17
+ export declare const configPathInput: z.ZodOptional<z.ZodString>;
18
+ /** Shared schema fields injected into every project-aware MCP tool. */
19
+ export declare const projectContextInputSchema: {
20
+ projectDirectory: z.ZodOptional<z.ZodString>;
21
+ configPath: z.ZodOptional<z.ZodString>;
22
+ };
@@ -0,0 +1,22 @@
1
+ /*
2
+ * Copyright (c) 2025, Salesforce, Inc.
3
+ * SPDX-License-Identifier: Apache-2
4
+ * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5
+ */
6
+ import { z } from 'zod';
7
+ /** Shared schema field injected into every project-aware MCP tool. */
8
+ export const projectDirectoryInput = z
9
+ .string()
10
+ .optional()
11
+ .describe('Absolute project directory for this call. Overrides --project-directory / SFCC_PROJECT_DIRECTORY and the MCP process working directory. Also controls project-local configuration discovery.');
12
+ /** Shared explicit dw.json path field injected into every project-aware MCP tool. */
13
+ export const configPathInput = z
14
+ .string()
15
+ .optional()
16
+ .describe('Explicit path to a dw.json-format configuration file for this call. Overrides startup --config / SFCC_CONFIG and project .env SFCC_CONFIG. Relative paths resolve from projectDirectory.');
17
+ /** Shared schema fields injected into every project-aware MCP tool. */
18
+ export const projectContextInputSchema = {
19
+ projectDirectory: projectDirectoryInput,
20
+ configPath: configPathInput,
21
+ };
22
+ //# sourceMappingURL=project-context.js.map
@@ -63,6 +63,7 @@ Retrieve observability metrics time-series for a B2C Commerce tenant. Returns me
63
63
  toolsets: ['SCAPI'],
64
64
  isGA: false,
65
65
  requiresInstance: false, // SCAPI uses OAuth directly
66
+ usesProjectContext: true,
66
67
  inputSchema: {
67
68
  category: z
68
69
  .enum(['overall', 'sales', 'ecdn', 'third-party', 'scapi', 'scapi-hooks', 'mrt', 'controller', 'ocapi'])
@@ -1,5 +1,6 @@
1
1
  import type { Services } from '../../services.js';
2
2
  import type { McpTool } from '../../utils/index.js';
3
+ import type { ProjectContextInput, ProjectDirectoryInfo } from '../project-context.js';
3
4
  import type { Scaffold, ResolvedParameters, ResolveParametersOptions } from '@salesforce/b2c-tooling-sdk/scaffold';
4
5
  /** Optional overrides for testing (scaffold not found, missing required). */
5
6
  export interface ScaffoldCustomApiExecuteOverrides {
@@ -12,7 +13,7 @@ export interface ScaffoldCustomApiExecuteOverrides {
12
13
  * Input schema for scapi_custom_api_generate_scaffold tool.
13
14
  * Parameters match the custom-api scaffold: apiName, apiType, cartridgeName, etc.
14
15
  */
15
- interface ScaffoldCustomApiInput {
16
+ interface ScaffoldCustomApiInput extends ProjectContextInput {
16
17
  /** API name (kebab-case, e.g. my-products). Required. */
17
18
  apiName: string;
18
19
  /** Cartridge name that will contain the API. Optional; defaults to first cartridge found in project. */
@@ -40,6 +41,8 @@ interface ScaffoldCustomApiOutput {
40
41
  }>;
41
42
  postInstructions?: string;
42
43
  error?: string;
44
+ projectDirectory: ProjectDirectoryInfo;
45
+ projectRoot: string;
43
46
  }
44
47
  /**
45
48
  * Core execute logic for the custom API scaffold tool.
@@ -21,7 +21,8 @@ const CUSTOM_API_SCAFFOLD_ID = 'custom-api';
21
21
  * Exported for tests so we can inject getScaffold / resolveScaffoldParameters and cover error branches.
22
22
  */
23
23
  export async function executeScaffoldCustomApi(args, services, overrides) {
24
- const projectRoot = services.resolveWithProjectDirectory(args.projectRoot);
24
+ const projectDirectory = services.resolveProjectDirectory(args.projectDirectory);
25
+ const projectRoot = services.resolveWithProjectDirectory(args.projectRoot, args.projectDirectory);
25
26
  const getScaffold = overrides?.getScaffold ??
26
27
  (async (id, opts) => {
27
28
  const registry = createScaffoldRegistry();
@@ -34,6 +35,8 @@ export async function executeScaffoldCustomApi(args, services, overrides) {
34
35
  outputDir: projectRoot,
35
36
  dryRun: false,
36
37
  files: [],
38
+ projectDirectory,
39
+ projectRoot,
37
40
  error: `Scaffold not found: ${CUSTOM_API_SCAFFOLD_ID}. Ensure @salesforce/b2c-tooling-sdk is installed.`,
38
41
  };
39
42
  }
@@ -44,6 +47,8 @@ export async function executeScaffoldCustomApi(args, services, overrides) {
44
47
  outputDir: projectRoot,
45
48
  dryRun: false,
46
49
  files: [],
50
+ projectDirectory,
51
+ projectRoot,
47
52
  error: 'No cartridges found in project. Custom API scaffold requires an existing cartridge. Create a cartridge first: use `b2c scaffold cartridge --name app_custom`, or manually create a directory with a `.project` file (e.g., cartridges/app_custom/.project).',
48
53
  };
49
54
  }
@@ -73,6 +78,8 @@ export async function executeScaffoldCustomApi(args, services, overrides) {
73
78
  outputDir: projectRoot,
74
79
  dryRun: false,
75
80
  files: [],
81
+ projectDirectory,
82
+ projectRoot,
76
83
  error: `Parameter validation failed: ${message}`,
77
84
  };
78
85
  }
@@ -83,6 +90,8 @@ export async function executeScaffoldCustomApi(args, services, overrides) {
83
90
  outputDir: projectRoot,
84
91
  dryRun: false,
85
92
  files: [],
93
+ projectDirectory,
94
+ projectRoot,
86
95
  error: `Missing required parameter: ${missingRequired[0].name}. For cartridgeName, ensure the cartridge exists in the project (under projectRoot).`,
87
96
  };
88
97
  }
@@ -108,6 +117,8 @@ export async function executeScaffoldCustomApi(args, services, overrides) {
108
117
  skipReason: f.skipReason,
109
118
  })),
110
119
  postInstructions: result.postInstructions,
120
+ projectDirectory,
121
+ projectRoot,
111
122
  };
112
123
  }
113
124
  catch (error) {
@@ -117,6 +128,8 @@ export async function executeScaffoldCustomApi(args, services, overrides) {
117
128
  outputDir,
118
129
  dryRun: false,
119
130
  files: [],
131
+ projectDirectory,
132
+ projectRoot,
120
133
  error: `Scaffold generation failed: ${message}`,
121
134
  };
122
135
  }
@@ -140,6 +153,7 @@ apiDescription, projectRoot, outputDir.`,
140
153
  toolsets: ['PWAV3', 'SCAPI', 'STOREFRONTNEXT'],
141
154
  isGA: true,
142
155
  requiresInstance: false,
156
+ usesProjectContext: true,
143
157
  inputSchema: {
144
158
  apiName: z
145
159
  .string()
@@ -158,7 +172,7 @@ apiDescription, projectRoot, outputDir.`,
158
172
  projectRoot: z
159
173
  .string()
160
174
  .nullish()
161
- .describe('Project root for cartridge discovery. Default: project directory. Set to override the project directory.'),
175
+ .describe('Optional cartridge discovery/output root, resolved relative to projectDirectory. Defaults to projectDirectory.'),
162
176
  outputDir: z.string().optional().describe('Output directory override. Default: project root'),
163
177
  },
164
178
  async execute(args, { services }) {
@@ -94,6 +94,7 @@ CLI: b2c scapi custom status`,
94
94
  toolsets: ['PWAV3', 'SCAPI', 'STOREFRONTNEXT'],
95
95
  isGA: true,
96
96
  requiresInstance: false,
97
+ usesProjectContext: true,
97
98
  inputSchema: {
98
99
  status: z.enum(['active', 'not_registered']).optional().describe('Filter by status. Omit for all.'),
99
100
  groupBy: z.enum(['site', 'type']).optional().describe('Group by siteId or type (Admin/Shopper).'),
@@ -12,11 +12,14 @@ import type { Services } from '../../../../services.js';
12
12
  export declare const figmaToComponentSchema: z.ZodObject<{
13
13
  figmaUrl: z.ZodString;
14
14
  workflowFilePath: z.ZodOptional<z.ZodString>;
15
+ projectDirectory: z.ZodOptional<z.ZodString>;
15
16
  }, "strict", z.ZodTypeAny, {
16
17
  figmaUrl: string;
18
+ projectDirectory?: string | undefined;
17
19
  workflowFilePath?: string | undefined;
18
20
  }, {
19
21
  figmaUrl: string;
22
+ projectDirectory?: string | undefined;
20
23
  workflowFilePath?: string | undefined;
21
24
  }>;
22
25
  export type FigmaToComponentInput = z.infer<typeof figmaToComponentSchema>;
@@ -14,6 +14,7 @@
14
14
  import { z } from 'zod';
15
15
  import { readFileSync, existsSync } from 'node:fs';
16
16
  import { createToolAdapter, textResult } from '../../../adapter.js';
17
+ import { projectDirectoryInput } from '../../../project-context.js';
17
18
  import { parseFigmaUrl } from './figma-url-parser.js';
18
19
  // prettier-ignore
19
20
  const DEFAULT_WORKFLOW_CONTENT = `---
@@ -169,7 +170,8 @@ export const figmaToComponentSchema = z
169
170
  workflowFilePath: z
170
171
  .string()
171
172
  .optional()
172
- .describe('Optional absolute path to custom workflow .md file. If not provided, uses default built-in workflow.'),
173
+ .describe('Optional path to a custom workflow .md file, resolved relative to projectDirectory when needed. If omitted, uses the default built-in workflow.'),
174
+ projectDirectory: projectDirectoryInput,
173
175
  })
174
176
  .strict();
175
177
  function extractWorkflowContent(content) {
@@ -316,9 +318,13 @@ export function createFigmaToComponentTool(loadServices) {
316
318
  toolsets: ['STOREFRONTNEXT_DEPRECATED'],
317
319
  isGA: false,
318
320
  requiresInstance: false,
321
+ usesProjectContext: true,
319
322
  inputSchema: figmaToComponentSchema.shape,
320
- async execute(args) {
321
- return generateWorkflowResponse(args.figmaUrl, args.workflowFilePath);
323
+ async execute(args, context) {
324
+ const workflowFilePath = args.workflowFilePath
325
+ ? context.services.resolveWithProjectDirectory(args.workflowFilePath, args.projectDirectory)
326
+ : undefined;
327
+ return generateWorkflowResponse(args.figmaUrl, workflowFilePath);
322
328
  },
323
329
  formatOutput: (output) => textResult(output),
324
330
  }, loadServices);
@@ -48,6 +48,7 @@ export declare const generateComponentSchema: z.ZodObject<{
48
48
  matchType: "name" | "structure" | "visual";
49
49
  }>, "many">;
50
50
  workspacePath: z.ZodOptional<z.ZodString>;
51
+ projectDirectory: z.ZodOptional<z.ZodString>;
51
52
  }, "strict", z.ZodTypeAny, {
52
53
  figmaMetadata: string;
53
54
  figmaCode: string;
@@ -59,6 +60,7 @@ export declare const generateComponentSchema: z.ZodObject<{
59
60
  similarity: number;
60
61
  matchType: "name" | "structure" | "visual";
61
62
  }[];
63
+ projectDirectory?: string | undefined;
62
64
  workspacePath?: string | undefined;
63
65
  }, {
64
66
  figmaMetadata: string;
@@ -71,6 +73,7 @@ export declare const generateComponentSchema: z.ZodObject<{
71
73
  similarity: number;
72
74
  matchType: "name" | "structure" | "visual";
73
75
  }[];
76
+ projectDirectory?: string | undefined;
74
77
  workspacePath?: string | undefined;
75
78
  }>;
76
79
  export type GenerateComponentInput = z.infer<typeof generateComponentSchema>;
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { z } from 'zod';
14
14
  import { createToolAdapter, textResult } from '../../../adapter.js';
15
+ import { projectDirectoryInput } from '../../../project-context.js';
15
16
  import { analyzeComponentDifferences, determineAction } from './decision.js';
16
17
  import { formatRecommendation } from './formatter.js';
17
18
  const discoveredComponentSchema = z.object({
@@ -33,6 +34,7 @@ export const generateComponentSchema = z
33
34
  .string()
34
35
  .optional()
35
36
  .describe('Optional workspace root path. Defaults to the MCP server project directory.'),
37
+ projectDirectory: projectDirectoryInput,
36
38
  })
37
39
  .strict();
38
40
  function analyzeComponent(input) {
@@ -86,11 +88,12 @@ export function createGenerateComponentTool(loadServices) {
86
88
  toolsets: ['STOREFRONTNEXT_DEPRECATED'],
87
89
  isGA: false,
88
90
  requiresInstance: false,
91
+ usesProjectContext: true,
89
92
  inputSchema: generateComponentSchema.shape,
90
93
  async execute(args, context) {
91
94
  return generateComponentRecommendation({
92
95
  ...args,
93
- workspacePath: args.workspacePath ?? context.services.resolveWithProjectDirectory(),
96
+ workspacePath: context.services.resolveWithProjectDirectory(args.workspacePath, args.projectDirectory),
94
97
  });
95
98
  },
96
99
  formatOutput: (output) => textResult(output),
@@ -26,6 +26,7 @@ export declare const mapTokensToThemeSchema: z.ZodObject<{
26
26
  description?: string | undefined;
27
27
  }>, "many">;
28
28
  themeFilePath: z.ZodOptional<z.ZodString>;
29
+ projectDirectory: z.ZodOptional<z.ZodString>;
29
30
  }, "strict", z.ZodTypeAny, {
30
31
  figmaTokens: {
31
32
  type: "color" | "fontFamily" | "fontSize" | "opacity" | "other" | "radius" | "spacing";
@@ -33,6 +34,7 @@ export declare const mapTokensToThemeSchema: z.ZodObject<{
33
34
  value: string;
34
35
  description?: string | undefined;
35
36
  }[];
37
+ projectDirectory?: string | undefined;
36
38
  themeFilePath?: string | undefined;
37
39
  }, {
38
40
  figmaTokens: {
@@ -41,6 +43,7 @@ export declare const mapTokensToThemeSchema: z.ZodObject<{
41
43
  value: string;
42
44
  description?: string | undefined;
43
45
  }[];
46
+ projectDirectory?: string | undefined;
44
47
  themeFilePath?: string | undefined;
45
48
  }>;
46
49
  export type MapTokensToThemeInput = z.infer<typeof mapTokensToThemeSchema>;
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { z } from 'zod';
14
14
  import { createToolAdapter, textResult } from '../../../adapter.js';
15
+ import { projectDirectoryInput } from '../../../project-context.js';
15
16
  import { parseThemeFile } from './css-parser.js';
16
17
  import { matchTokens } from './token-matcher.js';
17
18
  export const mapTokensToThemeSchema = z
@@ -29,7 +30,8 @@ export const mapTokensToThemeSchema = z
29
30
  themeFilePath: z
30
31
  .string()
31
32
  .optional()
32
- .describe('Optional absolute path to theme CSS file. If not provided, will search for app.css in common locations.'),
33
+ .describe('Optional path to the theme CSS file, resolved relative to projectDirectory when needed. If omitted, searches for app.css in common locations.'),
34
+ projectDirectory: projectDirectoryInput,
33
35
  })
34
36
  .strict();
35
37
  function formatTokenMatch(match) {
@@ -225,9 +227,14 @@ export function createMapTokensToThemeTool(loadServices) {
225
227
  toolsets: ['STOREFRONTNEXT_DEPRECATED'],
226
228
  isGA: false,
227
229
  requiresInstance: false,
230
+ usesProjectContext: true,
228
231
  inputSchema: mapTokensToThemeSchema.shape,
229
232
  async execute(args, context) {
230
- return mapFigmaTokensToTheme(args, context.services.resolveWithProjectDirectory());
233
+ const workspaceRoot = context.services.resolveWithProjectDirectory(undefined, args.projectDirectory);
234
+ const themeFilePath = args.themeFilePath
235
+ ? context.services.resolveWithProjectDirectory(args.themeFilePath, args.projectDirectory)
236
+ : undefined;
237
+ return mapFigmaTokensToTheme({ ...args, themeFilePath }, workspaceRoot);
231
238
  },
232
239
  formatOutput: (output) => textResult(output),
233
240
  }, loadServices);
@@ -1,7 +1,10 @@
1
1
  import { z } from 'zod';
2
2
  import type { McpTool } from '../../../utils/index.js';
3
3
  import type { Services } from '../../../services.js';
4
+ import { type ProjectContextInput } from '../../project-context.js';
4
5
  export declare const pageDesignerDecoratorSchema: z.ZodObject<{
6
+ projectDirectory: z.ZodOptional<z.ZodString>;
7
+ configPath: z.ZodOptional<z.ZodString>;
5
8
  component: z.ZodString;
6
9
  searchPaths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
7
10
  autoMode: z.ZodOptional<z.ZodBoolean>;
@@ -167,6 +170,8 @@ export declare const pageDesignerDecoratorSchema: z.ZodObject<{
167
170
  }>>;
168
171
  }, "strict", z.ZodTypeAny, {
169
172
  component: string;
173
+ projectDirectory?: string | undefined;
174
+ configPath?: string | undefined;
170
175
  searchPaths?: string[] | undefined;
171
176
  autoMode?: boolean | undefined;
172
177
  componentId?: string | undefined;
@@ -205,6 +210,8 @@ export declare const pageDesignerDecoratorSchema: z.ZodObject<{
205
210
  } | undefined;
206
211
  }, {
207
212
  component: string;
213
+ projectDirectory?: string | undefined;
214
+ configPath?: string | undefined;
208
215
  searchPaths?: string[] | undefined;
209
216
  autoMode?: boolean | undefined;
210
217
  componentId?: string | undefined;
@@ -249,4 +256,4 @@ export type PageDesignerDecoratorInput = z.infer<typeof pageDesignerDecoratorSch
249
256
  * @param loadServices - Function that loads configuration and returns Services instance
250
257
  * @returns The configured MCP tool
251
258
  */
252
- export declare function createPageDesignerDecoratorTool(loadServices: () => Promise<Services> | Services): McpTool;
259
+ export declare function createPageDesignerDecoratorTool(loadServices: (projectContext?: ProjectContextInput) => Promise<Services> | Services): McpTool;
@@ -7,6 +7,7 @@ import { z } from 'zod';
7
7
  import { componentAnalyzer, generateTypeSuggestions, resolveComponent } from './analyzer.js';
8
8
  import { generateDecoratorCode } from './templates/decorator-generator.js';
9
9
  import { pageDesignerDecoratorRules } from './rules.js';
10
+ import { projectContextInputSchema } from '../../project-context.js';
10
11
  // ============================================================================
11
12
  // SCHEMA DEFINITION
12
13
  // ============================================================================
@@ -93,6 +94,7 @@ export const pageDesignerDecoratorSchema = z
93
94
  })
94
95
  .optional()
95
96
  .describe('Conversation state for multi-turn interaction'),
97
+ ...projectContextInputSchema,
96
98
  })
97
99
  .strict();
98
100
  // ============================================================================
@@ -543,8 +545,11 @@ export function createPageDesignerDecoratorTool(loadServices) {
543
545
  const validatedArgs = pageDesignerDecoratorSchema.parse(args);
544
546
  // Use projectDirectory from services to ensure we search in the correct project directory
545
547
  // This prevents searches in the home folder when MCP clients spawn servers from ~
546
- const services = await loadServices();
547
- const workspaceRoot = services.resolveWithProjectDirectory();
548
+ const services = await loadServices({
549
+ projectDirectory: validatedArgs.projectDirectory,
550
+ configPath: validatedArgs.configPath,
551
+ });
552
+ const workspaceRoot = services.resolveWithProjectDirectory(undefined, validatedArgs.projectDirectory);
548
553
  if (validatedArgs.autoMode === undefined && !validatedArgs.conversationContext) {
549
554
  const fullPath = resolveComponent(validatedArgs.component, workspaceRoot, validatedArgs.searchPaths);
550
555
  const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { z } from 'zod';
15
15
  import { createToolAdapter, textResult, errorResult } from '../../adapter.js';
16
+ import { projectDirectoryInput } from '../../project-context.js';
16
17
  import { siteThemingStore } from './theming-store.js';
17
18
  import { mergeGuidance } from './guidance-merger.js';
18
19
  import { generateResponse } from './response-builder.js';
@@ -42,7 +43,9 @@ export function createSiteThemingTool(loadServices) {
42
43
  toolsets: ['STOREFRONTNEXT_DEPRECATED'],
43
44
  isGA: false,
44
45
  requiresInstance: false,
46
+ usesProjectContext: true,
45
47
  inputSchema: {
48
+ projectDirectory: projectDirectoryInput,
46
49
  fileKeys: z
47
50
  .array(z.string())
48
51
  .optional()
@@ -63,7 +66,9 @@ export function createSiteThemingTool(loadServices) {
63
66
  .describe('Context from previous conversation rounds'),
64
67
  },
65
68
  async execute(args, context) {
66
- siteThemingStore.initialize(context.services.resolveWithProjectDirectory());
69
+ siteThemingStore.initialize(context.services.resolveWithProjectDirectory(undefined, args.projectDirectory), {
70
+ themingFiles: context.services.getEnvironmentVariable('THEMING_FILES'),
71
+ });
67
72
  const defaultFileKeys = ['theming-questions', 'theming-validation', 'theming-accessibility'];
68
73
  let fileKeys;
69
74
  if (args.fileKeys && args.fileKeys.length > 0) {
@@ -41,6 +41,8 @@ export interface ThemingGuidance {
41
41
  export interface InitializeOptions {
42
42
  /** Override content directory for default files (used in tests). */
43
43
  contentDirOverride?: string;
44
+ /** Project-scoped THEMING_FILES value. */
45
+ themingFiles?: string;
44
46
  }
45
47
  declare class ThemingStore {
46
48
  private initializedForRoot;
@@ -365,7 +365,7 @@ class ThemingStore {
365
365
  }
366
366
  }
367
367
  }
368
- const themingFilesEnv = process.env.THEMING_FILES;
368
+ const themingFilesEnv = options?.themingFiles ?? process.env.THEMING_FILES;
369
369
  if (themingFilesEnv) {
370
370
  try {
371
371
  this.loadThemingFilesFromEnv(themingFilesEnv, root);
@@ -32,4 +32,5 @@ export interface ConversationContext {
32
32
  export interface SiteThemingInput {
33
33
  fileKeys?: string[];
34
34
  conversationContext?: ConversationContext;
35
+ projectDirectory?: string;
35
36
  }
@@ -429,5 +429,5 @@
429
429
  "enableJsonFlag": false
430
430
  }
431
431
  },
432
- "version": "1.10.1"
432
+ "version": "2.0.0"
433
433
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@salesforce/b2c-dx-mcp",
3
3
  "description": "MCP server for B2C Commerce developer experience tools",
4
- "version": "1.10.1",
4
+ "version": "2.0.0",
5
5
  "author": "Salesforce",
6
6
  "license": "Apache-2.0",
7
7
  "repository": "SalesforceCommerceCloud/b2c-developer-tooling",
@@ -80,7 +80,7 @@
80
80
  "yaml": "2.9.0",
81
81
  "postcss": "8.5.15",
82
82
  "zod": "3.25.76",
83
- "@salesforce/b2c-tooling-sdk": "1.23.0"
83
+ "@salesforce/b2c-tooling-sdk": "1.24.0"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@eslint/compat": "^1",