Skip to content

Commit 00be5df

Browse files
committed
refactor(mcp): use Services.workingDirectory for component discovery
Add workingDirectory property to Services and update page-designer-decorator tool to use it instead of reading process.env.SFCC_WORKING_DIRECTORY directly. This ensures component searches start from the correct project directory when MCP clients spawn servers from the home directory. - Add workingDirectory to Services (resolved from --working-directory flag/env) - Update page-designer-decorator to use services.workingDirectory - Simplify tool description for LLM consumption - Update tests and documentation accordingly
1 parent 9034319 commit 00be5df

6 files changed

Lines changed: 45 additions & 67 deletions

File tree

packages/b2c-dx-mcp/src/commands/mcp.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,8 @@ export default class McpServerCommand extends BaseCommand<typeof McpServerComman
337337
);
338338

339339
// Create services from already-resolved config (BaseCommand.init() already resolved it)
340-
const services = Services.fromResolvedConfig(this.resolvedConfig);
340+
// Pass workingDirectory so tools can use it instead of process.cwd() (which may be ~)
341+
const services = Services.fromResolvedConfig(this.resolvedConfig, startupFlags.workingDirectory);
341342

342343
// Register toolsets
343344
await registerToolsets(startupFlags, server, services);

packages/b2c-dx-mcp/src/services.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ export interface ServicesOptions {
7373
b2cInstance?: B2CInstance;
7474
/** Pre-resolved MRT configuration (auth, project, environment) */
7575
mrtConfig?: MrtConfig;
76+
/** Project working directory for file operations */
77+
workingDirectory?: string;
7678
}
7779

7880
/**
@@ -90,6 +92,7 @@ export interface ServicesOptions {
9092
* services.b2cInstance; // B2CInstance | undefined
9193
* services.mrtConfig.auth; // AuthStrategy | undefined
9294
* services.mrtConfig.project; // string | undefined
95+
* services.workingDirectory; // string (project directory)
9396
* ```
9497
*/
9598
export class Services {
@@ -106,24 +109,34 @@ export class Services {
106109
*/
107110
public readonly mrtConfig: MrtConfig;
108111

112+
/**
113+
* Project working directory for file operations.
114+
* Used by tools to ensure they operate on the correct project directory,
115+
* especially when MCP clients spawn servers from the home directory.
116+
* Defaults to process.cwd() if not provided.
117+
*/
118+
public readonly workingDirectory: string;
119+
109120
public constructor(opts: ServicesOptions = {}) {
110121
this.b2cInstance = opts.b2cInstance;
111122
this.mrtConfig = opts.mrtConfig ?? {};
123+
this.workingDirectory = opts.workingDirectory ?? process.cwd();
112124
}
113125

114126
/**
115127
* Creates a Services instance from an already-resolved configuration.
116128
*
117129
* @param config - Already-resolved configuration from BaseCommand.resolvedConfig
130+
* @param workingDirectory - Optional working directory (defaults to process.cwd())
118131
* @returns Services instance with resolved config
119132
*
120133
* @example
121134
* ```typescript
122135
* // In a command that extends BaseCommand
123-
* const services = Services.fromResolvedConfig(this.resolvedConfig);
136+
* const services = Services.fromResolvedConfig(this.resolvedConfig, this.flags['working-directory']);
124137
* ```
125138
*/
126-
public static fromResolvedConfig(config: ResolvedB2CConfig): Services {
139+
public static fromResolvedConfig(config: ResolvedB2CConfig, workingDirectory?: string): Services {
127140
// Build MRT config using factory methods
128141
const mrtConfig: MrtConfig = {
129142
auth: config.hasMrtConfig() ? config.createMrtAuth() : undefined,
@@ -138,6 +151,7 @@ export class Services {
138151
return new Services({
139152
b2cInstance,
140153
mrtConfig,
154+
workingDirectory,
141155
});
142156
}
143157

packages/b2c-dx-mcp/src/tools/page-designer-decorator/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ The tool automatically searches for components in these locations (in order):
9090
4. `src/**` (broader search)
9191
5. Custom paths (if provided via `searchPaths`)
9292

93+
**Working Directory:**
94+
Component discovery uses the working directory resolved from `--working-directory` flag or `SFCC_WORKING_DIRECTORY` environment variable (via Services). This ensures searches start from the correct project directory, especially when MCP clients spawn servers from the home directory.
95+
9396
**Examples:**
9497

9598
- `"ProductCard"` → finds `src/components/product-tile/ProductCard.tsx`
@@ -101,6 +104,7 @@ The tool automatically searches for components in these locations (in order):
101104
- Use component name for portability
102105
- Use path for unusual locations
103106
- Add `searchPaths` for monorepos or non-standard structures
107+
- Ensure `--working-directory` flag or `SFCC_WORKING_DIRECTORY` env var is set correctly
104108

105109
## 🏗️ Architecture
106110

@@ -242,7 +246,7 @@ The test suite covers:
242246
- Interactive mode (all steps: analyze, select_props, configure_attrs, configure_regions, confirm_generation)
243247
- Error handling (invalid input, invalid step name, missing parameters)
244248
- Edge cases (no props, only complex props, optional props, union types, already decorated components)
245-
- Environment variables (SFCC_WORKING_DIRECTORY)
249+
- Working directory resolution (from --working-directory flag or SFCC_WORKING_DIRECTORY env var via Services)
246250

247251
See [`test/tools/page-designer-decorator/README.md`](../../../test/tools/page-designer-decorator/README.md) for detailed testing instructions.
248252

packages/b2c-dx-mcp/src/tools/page-designer-decorator/analyzer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ export function resolveComponent(input: string, workspaceRoot: string, searchPat
603603
`Full path checked: ${fullPath}\n\n` +
604604
`Tips:\n` +
605605
` 1. Use component name instead (e.g., "ProductCard") for automatic discovery\n` +
606-
` 2. If components are in a different repo, set: SFCC_WORKING_DIRECTORY=/path/to/storefront-next`,
606+
` 2. If components are in a different repo, set --working-directory flag or SFCC_WORKING_DIRECTORY env var`,
607607
);
608608
}
609609

@@ -630,7 +630,7 @@ export function resolveComponent(input: string, workspaceRoot: string, searchPat
630630
` 1. Provide full path: component: "src/components/ProductCard.tsx"\n` +
631631
` 2. Add custom search: searchPaths: ["packages/retail/src"]\n` +
632632
` 3. Check component name spelling and casing\n` +
633-
` 4. If components are in a different repo, set: SFCC_WORKING_DIRECTORY=/path/to/storefront-next`,
633+
` 4. If components are in a different repo, set --working-directory flag or SFCC_WORKING_DIRECTORY env var`,
634634
);
635635
}
636636

packages/b2c-dx-mcp/src/tools/page-designer-decorator/index.ts

Lines changed: 11 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -599,59 +599,19 @@ function handleAutoMode(args: PageDesignerDecoratorInput, workspaceRoot: string)
599599
/**
600600
* Creates the Page Designer decorator tool for Storefront Next.
601601
*
602-
* @param _services - MCP services (not used by this tool)
602+
* @param services - MCP services (provides workingDirectory for component search)
603603
* @returns The configured MCP tool
604604
*/
605-
export function createPageDesignerDecoratorTool(_services: Services): McpTool {
605+
export function createPageDesignerDecoratorTool(services: Services): McpTool {
606606
return {
607607
name: 'add_page_designer_decorator',
608608

609-
description: `⚠️ MANDATORY: Add Page Designer decorators to an existing component (Template Literals Version).
610-
611-
Analyzes component structure and guides through adding @Component, @AttributeDefinition, and @RegionDefinition decorators.
612-
613-
ENVIRONMENT SETUP:
614-
Set SFCC_WORKING_DIRECTORY environment variable to the Storefront Next repository path.
615-
If not set, uses current working directory.
616-
617-
INITIAL CALL - MODE SELECTION:
618-
When called with ONLY the 'component' parameter (no autoMode, no conversationContext), the tool will:
619-
- Analyze the component (automatically finds it by name)
620-
- Present mode selection options to the user
621-
- WAIT for user to choose between Auto Mode or Interactive Mode
622-
- DO NOT proceed until user selects a mode
623-
624-
MODES:
625-
626-
**AUTO MODE** (set autoMode: true):
627-
- Automatically analyzes component and generates decorators with sensible defaults
628-
- Skips all interactive steps
629-
- Auto-selects suitable props (excludes complex and UI-only props)
630-
- Auto-infers types based on naming patterns
631-
- Immediately generates code - no confirmation needed
632-
- Best for: Quick setup, standard components, batch processing
633-
634-
**INTERACTIVE MODE** (set conversationContext.step: "analyze"):
635-
- Multi-step workflow with user confirmation at each stage
636-
- Allows fine-tuned control over all settings
637-
- MUST ask questions and WAIT for responses
638-
- DO NOT generate code until user confirms configuration
639-
- Best for: Complex components, custom requirements, learning
640-
641-
WORKFLOW STEPS (Interactive Mode):
642-
1. analyze: Parse component, identify props, provide recommendations
643-
2. select_props: Confirm user's selections and prepare for configuration
644-
3. configure_attrs: Set explicit types, names, defaults where needed
645-
4. configure_regions: Configure nested content areas (optional)
646-
5. confirm_generation: Generate final decorator code
647-
648-
INTELLIGENT FEATURES:
649-
- Auto-infers types when possible (string, number, boolean)
650-
- Suggests explicit types when needed (url, image, enum, markup)
651-
- Detects unsuitable props (complex types, UI-only props)
652-
- Provides smart defaults based on naming patterns
653-
654-
Use conversationContext parameter for multi-turn conversation (interactive mode only).`,
609+
description:
610+
'Adds Page Designer decorators (@Component, @AttributeDefinition, @RegionDefinition) to React components. ' +
611+
'Two modes: autoMode=true for quick setup with defaults, or interactive mode via conversationContext.step. ' +
612+
'Component discovery uses workingDirectory from flags/env. ' +
613+
'Auto mode: selects suitable props, infers types, generates code immediately. ' +
614+
'Interactive mode: multi-step workflow (analyze → select_props → configure_attrs → configure_regions → confirm_generation).',
655615

656616
inputSchema: pageDesignerDecoratorSchema.shape as ZodRawShape,
657617
toolsets: ['STOREFRONTNEXT'],
@@ -661,10 +621,9 @@ export function createPageDesignerDecoratorTool(_services: Services): McpTool {
661621
try {
662622
// Validate and parse input
663623
const validatedArgs = pageDesignerDecoratorSchema.parse(args) as PageDesignerDecoratorInput;
664-
// Workspace resolution:
665-
// 1. SFCC_WORKING_DIRECTORY (Storefront Next convention, recommended)
666-
// 2. process.cwd() (fallback)
667-
const workspaceRoot = process.env.SFCC_WORKING_DIRECTORY || process.cwd();
624+
// Use workingDirectory from services to ensure we search in the correct project directory
625+
// This prevents searches in the home folder when MCP clients spawn servers from ~
626+
const workspaceRoot = services.workingDirectory;
668627

669628
if (validatedArgs.autoMode === undefined && !validatedArgs.conversationContext) {
670629
const fullPath = resolveComponent(validatedArgs.component, workspaceRoot, validatedArgs.searchPaths);

packages/b2c-dx-mcp/test/tools/page-designer-decorator/index.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ function getResultText(result: ToolResult): string {
3131
/**
3232
* Create a mock services instance for testing.
3333
*
34+
* @param workingDirectory - Optional working directory (defaults to process.cwd())
3435
* @returns A new Services instance with empty configuration
3536
*/
36-
function createMockServices(): Services {
37-
return new Services({});
37+
function createMockServices(workingDirectory?: string): Services {
38+
return new Services({workingDirectory});
3839
}
3940

4041
/**
@@ -114,22 +115,20 @@ describe('tools/page-designer-decorator', () => {
114115
let originalCwd: string;
115116

116117
beforeEach(() => {
117-
services = createMockServices();
118118
// Create a temporary directory for test components
119119
testDir = path.join(tmpdir(), `b2c-mcp-test-${Date.now()}`);
120120
mkdirSync(testDir, {recursive: true});
121121
originalCwd = process.cwd();
122122
process.chdir(testDir);
123-
// Set SFCC_WORKING_DIRECTORY to the test directory
124-
process.env.SFCC_WORKING_DIRECTORY = testDir;
123+
// Create services with workingDirectory set to test directory
124+
services = createMockServices(testDir);
125125
});
126126

127127
afterEach(() => {
128128
process.chdir(originalCwd);
129129
if (existsSync(testDir)) {
130130
rmSync(testDir, {recursive: true, force: true});
131131
}
132-
delete process.env.SFCC_WORKING_DIRECTORY;
133132
});
134133

135134
describe('tool metadata', () => {
@@ -186,13 +185,14 @@ describe('tools/page-designer-decorator', () => {
186185
expect(text).to.include('TestComponent');
187186
});
188187

189-
it('should use SFCC_WORKING_DIRECTORY if set', async () => {
190-
const tool = createPageDesignerDecoratorTool(services);
188+
it('should use workingDirectory from Services', async () => {
191189
const customDir = path.join(tmpdir(), `b2c-mcp-test-custom-${Date.now()}`);
192190
mkdirSync(customDir, {recursive: true});
193191
createTestComponent(customDir, 'CustomComponent');
194192

195-
process.env.SFCC_WORKING_DIRECTORY = customDir;
193+
// Create services with custom workingDirectory
194+
const customServices = createMockServices(customDir);
195+
const tool = createPageDesignerDecoratorTool(customServices);
196196

197197
const result = await tool.handler({
198198
component: 'CustomComponent',

0 commit comments

Comments
 (0)