Skip to content

Commit ca4ca32

Browse files
MCP project folder resolution clean-up
1 parent 12bd455 commit ca4ca32

File tree

8 files changed

+57
-46
lines changed

8 files changed

+57
-46
lines changed

packages/b2c-dx-mcp/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ Discover schema metadata and fetch OpenAPI specs for both standard and custom SC
140140

141141
**Custom API Scaffold (tool: `scapi_customapi_scaffold`):**
142142

143-
Generate a new custom SCAPI endpoint in an existing cartridge (OAS 3.0 schema.yaml, api.json, script.js with example GET endpoints). Requires **apiName** (kebab-case). Optional: **cartridgeName** (omit to use the first cartridge found under the working directory), **apiType** (shopper | admin; default shopper), **apiDescription**, **projectRoot**, **outputDir**. Set `--working-directory` (or SFCC_WORKING_DIRECTORY) so the server discovers cartridges in your project. Files are always generated (no dry run) and existing files are never overwritten.
143+
Generate a new custom SCAPI endpoint in an existing cartridge (OAS 3.0 schema.yaml, api.json, script.js with example GET endpoints). Requires **apiName** (kebab-case). Optional: **cartridgeName** (omit to use the first cartridge found under the working directory), **apiType** (shopper | admin; default shopper), **apiDescription**, **projectRoot**, **outputDir**. Set `--project-directory` (or SFCC_PROJECT_DIRECTORY) so the server discovers cartridges in your project. Files are always generated (no dry run) and existing files are never overwritten.
144144

145145
- ✅ "Use the MCP tool to scaffold a new custom API named my-products."
146146
- ✅ "Use the MCP tool to create a custom admin API called customer-trips."

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

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -313,19 +313,6 @@ export class Services {
313313
return this.b2cInstance.webdav;
314314
}
315315

316-
/**
317-
* Get the project project directory.
318-
* Falls back to process.cwd() if not explicitly set.
319-
*
320-
* This is the directory where the project is located, which may differ from process.cwd()
321-
* when MCP clients spawn servers from a different location (e.g., home directory).
322-
*
323-
* @returns Project project directory path
324-
*/
325-
public getWorkingDirectory(): string {
326-
return this.resolvedConfig.values.projectDirectory ?? process.cwd();
327-
}
328-
329316
/**
330317
* Join path segments.
331318
*
@@ -371,6 +358,26 @@ export class Services {
371358
return path.resolve(...segments);
372359
}
373360

361+
/**
362+
* Resolve a path relative to the project directory.
363+
* If path is not supplied, returns the project directory.
364+
* If path is absolute, returns it as-is.
365+
* If path is relative, resolves it relative to the project directory.
366+
*
367+
* @param pathArg - Optional path to resolve
368+
* @returns Resolved absolute path
369+
*/
370+
public resolveWithProjectDirectory(pathArg?: string): string {
371+
const projectDir = this.resolvedConfig.values.projectDirectory ?? process.cwd();
372+
if (!pathArg) {
373+
return projectDir;
374+
}
375+
if (path.isAbsolute(pathArg)) {
376+
return pathArg;
377+
}
378+
return path.resolve(projectDir, pathArg);
379+
}
380+
374381
/**
375382
* Get file or directory stats.
376383
*

packages/b2c-dx-mcp/src/tools/cartridges/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
* @module tools/cartridges
1313
*/
1414

15-
import path from 'node:path';
1615
import {z} from 'zod';
1716
import type {McpTool} from '../../utils/index.js';
1817
import type {Services} from '../../services.js';
@@ -129,11 +128,7 @@ function createCartridgeDeployTool(loadServices: () => Services, injections?: Ca
129128
}
130129

131130
// Resolve directory path: relative paths are resolved relative to project directory, absolute paths are used as-is
132-
const directory = args.directory
133-
? path.isAbsolute(args.directory)
134-
? args.directory
135-
: path.resolve(context.services.getWorkingDirectory(), args.directory)
136-
: context.services.getWorkingDirectory();
131+
const directory = context.services.resolveWithProjectDirectory(args.directory);
137132

138133
// Parse options
139134
const options: DeployOptions = {

packages/b2c-dx-mcp/src/tools/mrt/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
* @module tools/mrt
1313
*/
1414

15-
import path from 'node:path';
1615
import {z} from 'zod';
1716
import type {McpTool} from '../../utils/index.js';
1817
import type {Services} from '../../services.js';
@@ -115,11 +114,7 @@ function createMrtBundlePushTool(loadServices: () => Services, injections?: MrtT
115114
// Parse comma-separated glob patterns (same as CLI defaults)
116115
const ssrOnly = (args.ssrOnly || 'ssr.js,ssr.mjs,server/**/*').split(',').map((s) => s.trim());
117116
const ssrShared = (args.ssrShared || 'static/**/*,client/**/*').split(',').map((s) => s.trim());
118-
const buildDirectory = args.buildDirectory
119-
? path.isAbsolute(args.buildDirectory)
120-
? args.buildDirectory
121-
: path.resolve(context.services.getWorkingDirectory(), args.buildDirectory)
122-
: path.join(context.services.getWorkingDirectory(), 'build');
117+
const buildDirectory = context.services.resolveWithProjectDirectory(args.buildDirectory || 'build');
123118

124119
// Log all computed variables before pushing bundle
125120
const logger = getLogger();

packages/b2c-dx-mcp/src/tools/scapi/scapi-custom-api-scaffold.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
* @module tools/scapi/scapi-custom-api-scaffold
1414
*/
1515

16-
import path from 'node:path';
1716
import {z} from 'zod';
1817
import {createToolAdapter, jsonResult, errorResult} from '../adapter.js';
1918
import type {Services} from '../../services.js';
@@ -48,7 +47,7 @@ interface ScaffoldCustomApiInput {
4847
apiType?: 'admin' | 'shopper';
4948
/** Short description of the API. Default: "A custom B2C Commerce API" */
5049
apiDescription?: string;
51-
/** Project root for cartridge discovery and output. Default: MCP working directory */
50+
/** Project root for cartridge discovery and output. Default: MCP project directory */
5251
projectRoot?: string;
5352
/** Output directory override. Default: scaffold default or project root */
5453
outputDir?: string;
@@ -79,7 +78,7 @@ export async function executeScaffoldCustomApi(
7978
services: Services,
8079
overrides?: ScaffoldCustomApiExecuteOverrides,
8180
): Promise<ScaffoldCustomApiOutput> {
82-
const projectRoot = path.resolve(args.projectRoot ?? services.getWorkingDirectory());
81+
const projectRoot = services.resolveWithProjectDirectory(args.projectRoot);
8382

8483
const getScaffold =
8584
overrides?.getScaffold ??

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ export function createPageDesignerDecoratorTool(loadServices: () => Services): M
649649
// Use projectDirectory from services to ensure we search in the correct project directory
650650
// This prevents searches in the home folder when MCP clients spawn servers from ~
651651
const services = loadServices();
652-
const workspaceRoot = services.getWorkingDirectory();
652+
const workspaceRoot = services.resolveWithProjectDirectory();
653653

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

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

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -172,35 +172,50 @@ describe('services', () => {
172172
});
173173
});
174174

175-
describe('getWorkingDirectory', () => {
176-
it('should return project directory when provided in config', () => {
175+
describe('resolveWithProjectDirectory', () => {
176+
it('should return project directory when provided in config and no path arg', () => {
177177
const workingDir = '/path/to/project';
178178
const config = createMockResolvedConfig({projectDirectory: workingDir});
179179
const services = new Services({resolvedConfig: config});
180180

181-
expect(services.getWorkingDirectory()).to.equal(workingDir);
181+
expect(services.resolveWithProjectDirectory()).to.equal(workingDir);
182182
});
183183

184-
it('should fall back to process.cwd() when not provided', () => {
184+
it('should fall back to process.cwd() when not provided and no path arg', () => {
185185
const config = createMockResolvedConfig();
186186
const services = new Services({resolvedConfig: config});
187187

188-
expect(services.getWorkingDirectory()).to.equal(process.cwd());
188+
expect(services.resolveWithProjectDirectory()).to.equal(process.cwd());
189189
});
190190

191-
it('should return project directory from fromResolvedConfig when provided in config', () => {
191+
it('should return project directory from fromResolvedConfig when provided in config and no path arg', () => {
192192
const projectDir = '/path/to/project';
193193
const config = createMockResolvedConfig({projectDirectory: projectDir});
194194
const services = Services.fromResolvedConfig(config);
195195

196-
expect(services.getWorkingDirectory()).to.equal(projectDir);
196+
expect(services.resolveWithProjectDirectory()).to.equal(projectDir);
197197
});
198198

199-
it('should fall back to process.cwd() from fromResolvedConfig when not provided in config', () => {
199+
it('should fall back to process.cwd() from fromResolvedConfig when not provided in config and no path arg', () => {
200200
const config = createMockResolvedConfig();
201201
const services = Services.fromResolvedConfig(config);
202202

203-
expect(services.getWorkingDirectory()).to.equal(process.cwd());
203+
expect(services.resolveWithProjectDirectory()).to.equal(process.cwd());
204+
});
205+
206+
it('should return absolute path as-is', () => {
207+
const config = createMockResolvedConfig({projectDirectory: '/path/to/project'});
208+
const services = new Services({resolvedConfig: config});
209+
210+
expect(services.resolveWithProjectDirectory('/absolute/path')).to.equal('/absolute/path');
211+
});
212+
213+
it('should resolve relative path relative to project directory', () => {
214+
const projectDir = '/path/to/project';
215+
const config = createMockResolvedConfig({projectDirectory: projectDir});
216+
const services = new Services({resolvedConfig: config});
217+
218+
expect(services.resolveWithProjectDirectory('subdir')).to.equal('/path/to/project/subdir');
204219
});
205220
});
206221

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The page-designer-decorator tool has comprehensive unit tests covering:
1111
- ✅ Error handling (invalid input, invalid step name, missing parameters)
1212
- ✅ Input validation
1313
- ✅ Edge cases (no props, only complex props, optional props, union types, already decorated components)
14-
- ✅ Environment variables (SFCC_WORKING_DIRECTORY)
14+
- ✅ Environment variables (SFCC_PROJECT_DIRECTORY)
1515

1616
All tests use the standard Mocha test framework and run with `pnpm test`.
1717

@@ -57,23 +57,23 @@ npx mcp-inspector --cli node bin/dev.js --toolsets STOREFRONTNEXT --allow-non-ga
5757

5858
### 4. Running Tests Against a Local Storefront Next Installation
5959

60-
The Mocha test suite supports testing against a real Storefront Next installation by setting `SFCC_WORKING_DIRECTORY`:
60+
The Mocha test suite supports testing against a real Storefront Next installation by setting `SFCC_PROJECT_DIRECTORY`:
6161

6262
```bash
6363
cd packages/b2c-dx-mcp
64-
SFCC_WORKING_DIRECTORY=/path/to/storefront-next \
64+
SFCC_PROJECT_DIRECTORY=/path/to/storefront-next \
6565
pnpm run test:agent -- test/tools/storefrontnext/page-designer-decorator/index.test.ts
6666
```
6767

6868
Or set it as an environment variable:
6969
```bash
70-
export SFCC_WORKING_DIRECTORY=/path/to/storefront-next
70+
export SFCC_PROJECT_DIRECTORY=/path/to/storefront-next
7171
cd packages/b2c-dx-mcp
7272
pnpm run test:agent -- test/tools/storefrontnext/page-designer-decorator/index.test.ts
7373
```
7474

7575
**Important Notes for Real Project Mode**:
76-
- Component discovery searches in your real Storefront Next project (`SFCC_WORKING_DIRECTORY`)
76+
- Component discovery searches in your real Storefront Next project (`SFCC_PROJECT_DIRECTORY`)
7777
- Tests create temporary directories for test components (not in your real project)
7878
- Tests will **not** modify your real project files (read-only)
7979
- Tests will use existing components from your real project if they exist
@@ -104,7 +104,7 @@ export default function TestComponent({title, description}: TestComponentProps)
104104

105105
3. Set environment variable:
106106
```bash
107-
export SFCC_WORKING_DIRECTORY=/path/to/storefront-next
107+
export SFCC_PROJECT_DIRECTORY=/path/to/storefront-next
108108
```
109109

110110
4. Use the tool via MCP Inspector or your IDE's MCP integration
@@ -144,7 +144,7 @@ Expected: Returns component analysis
144144
### Component Not Found Errors
145145

146146
If you get "Component not found" errors:
147-
1. Verify `SFCC_WORKING_DIRECTORY` is set correctly
147+
1. Verify `SFCC_PROJECT_DIRECTORY` is set correctly
148148
2. Check that the component file exists at the expected path
149149
3. Try using the full relative path: `"component": "src/components/MyComponent.tsx"`
150150

0 commit comments

Comments
 (0)