Skip to content

Commit 2f35511

Browse files
authored
@W-20591323 workspace type auto-discovery (#42)
1 parent 89600c1 commit 2f35511

26 files changed

Lines changed: 1598 additions & 25 deletions

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
#GUSINFO:CC Cosmos,B2C CLI and Developer Tools
22
* @clavery
3+
/packages/b2c-dx-mcp/ @yhsieh1

docs/guide/configuration.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,13 @@ See [Configure WebDAV File Access](https://help.salesforce.com/s/articleView?id=
4949

5050
## Environment Variables
5151

52-
You can configure authentication using environment variables:
52+
You can configure the CLI using environment variables:
5353

5454
| Variable | Description |
5555
|----------|-------------|
56+
| `SFCC_WORKING_DIRECTORY` | Project working directory |
57+
| `SFCC_CONFIG` | Path to config file (dw.json format) |
58+
| `SFCC_INSTANCE` | Instance name from config file |
5659
| `SFCC_SERVER` | The B2C instance hostname |
5760
| `SFCC_CLIENT_ID` | OAuth client ID |
5861
| `SFCC_CLIENT_SECRET` | OAuth client secret |

packages/b2c-dx-mcp/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,66 @@ Since the package is not yet published to npm, see the [Development](#developmen
5252
| `--debug` | Enable debug logging |
5353
| `--json` | Output logs as JSON lines |
5454
| `--lang` | Language for messages |
55+
| `--working-directory` | Project working directory (env: `SFCC_WORKING_DIRECTORY`) |
56+
57+
### Workspace Auto-Discovery
58+
59+
When neither `--toolsets` nor `--tools` are provided, the MCP server automatically detects your project type and enables the appropriate toolsets.
60+
61+
**How it works:**
62+
63+
1. The server analyzes your working directory (from `--working-directory` flag, `SFCC_WORKING_DIRECTORY` env var, or current directory)
64+
2. It checks for project markers like `package.json` dependencies and `.project` files
65+
3. It enables all toolsets that match any detected project type, plus the base SCAPI toolset
66+
67+
**Base Toolset:**
68+
69+
The **SCAPI** toolset is always enabled, providing API discovery and custom API scaffolding capabilities.
70+
71+
**Project Types and Toolsets:**
72+
73+
| Project Type | Detection | Toolsets Enabled |
74+
|--------------|-----------|------------------|
75+
| **PWA Kit v3** | `@salesforce/pwa-kit-*`, `@salesforce/retail-react-app`, or `ccExtensibility` | PWAV3, MRT, SCAPI |
76+
| **Storefront Next** | `@salesforce/storefront-next-*` packages in package.json | STOREFRONTNEXT, MRT, SCAPI |
77+
| **Cartridges** | Any cartridge with `.project` file (detected via `findCartridges`) | CARTRIDGES, SCAPI |
78+
| **No project detected** | No B2C project markers found | SCAPI (base toolset only) |
79+
80+
**Hybrid Projects:**
81+
82+
If multiple project types are detected (e.g., cartridges + PWA Kit v3), toolsets from all matched types are combined.
83+
84+
**Example:**
85+
86+
**Cursor** (supports `${workspaceFolder}`):
87+
88+
```json
89+
{
90+
"mcpServers": {
91+
"b2c-dx": {
92+
"command": "/path/to/packages/b2c-dx-mcp/bin/dev.js",
93+
"args": ["--working-directory", "${workspaceFolder}", "--allow-non-ga-tools"]
94+
}
95+
}
96+
}
97+
```
98+
99+
**Claude Desktop** (use explicit path):
100+
101+
```json
102+
{
103+
"mcpServers": {
104+
"b2c-dx": {
105+
"command": "/path/to/packages/b2c-dx-mcp/bin/dev.js",
106+
"args": ["--working-directory", "/path/to/your/project", "--allow-non-ga-tools"]
107+
}
108+
}
109+
}
110+
```
111+
112+
> **Note:** Cursor supports `${workspaceFolder}` variable expansion, but Claude Desktop does not. For Claude Desktop, use an explicit path or set the `SFCC_WORKING_DIRECTORY` environment variable.
113+
114+
> **Warning:** MCP clients like Cursor and Claude Desktop often spawn servers from the home directory (`~`) rather than the project directory. Always set `--working-directory` or `SFCC_WORKING_DIRECTORY` for reliable auto-discovery and scaffolding operations.
55115
56116
### Configuration Examples
57117

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

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
* ## Flags
1515
*
1616
* ### MCP-Specific Flags
17-
* | Flag | Description |
18-
* |------|-------------|
19-
* | `--toolsets` | Comma-separated toolsets to enable (case-insensitive) |
20-
* | `--tools` | Comma-separated individual tools to enable (case-insensitive) |
21-
* | `--allow-non-ga-tools` | Enable experimental/non-GA tools |
17+
* | Flag | Env Variable | Description |
18+
* |------|--------------|-------------|
19+
* | `--toolsets` | `SFCC_TOOLSETS` | Comma-separated toolsets to enable (case-insensitive) |
20+
* | `--tools` | `SFCC_TOOLS` | Comma-separated individual tools to enable (case-insensitive) |
21+
* | `--allow-non-ga-tools` | `SFCC_ALLOW_NON_GA_TOOLS` | Enable experimental/non-GA tools |
2222
*
2323
* ### MRT Flags (from MrtCommand.baseFlags)
2424
* | Flag | Env Variable | Description |
@@ -39,14 +39,23 @@
3939
* | `--client-secret` | `SFCC_CLIENT_SECRET` | OAuth client secret |
4040
*
4141
* ### Global Flags (inherited from BaseCommand)
42-
* | Flag | Description |
43-
* |------|-------------|
44-
* | `--config` | Path to dw.json config file (auto-discovered if not provided) |
45-
* | `--instance` | Instance name from configuration file |
46-
* | `--log-level` | Set logging verbosity (trace, debug, info, warn, error, silent) |
47-
* | `--debug` | Enable debug logging |
48-
* | `--json` | Output logs as JSON lines |
49-
* | `--lang` | Language for messages |
42+
* | Flag | Env Variable | Description |
43+
* |------|--------------|-------------|
44+
* | `--working-directory` | `SFCC_WORKING_DIRECTORY` | Project working directory (see note below) |
45+
* | `--config` | `SFCC_CONFIG` | Path to dw.json config file (auto-discovered if not provided) |
46+
* | `--instance` | `SFCC_INSTANCE` | Instance name from configuration file |
47+
* | `--log-level` | `SFCC_LOG_LEVEL` | Set logging verbosity (trace, debug, info, warn, error, silent) |
48+
* | `--debug` | `SFCC_DEBUG` | Enable debug logging |
49+
* | `--json` | - | Output logs as JSON lines |
50+
* | `--lang` | - | Language for messages |
51+
*
52+
* **Note on `--working-directory`**: Many MCP clients (Cursor, Claude Desktop) spawn servers from the
53+
* user's home directory (`~`) rather than the project directory. This flag is used for:
54+
* - Auto-discovery (detecting project type when no `--toolsets` or `--tools` are provided)
55+
* - Scaffolding tools (creating files in the correct project location)
56+
* - Any tool that needs to operate on the project directory
57+
*
58+
* Use `--working-directory` or `SFCC_WORKING_DIRECTORY` env var to specify the actual project path.
5059
*
5160
* ## Configuration
5261
*
@@ -227,6 +236,8 @@ export default class McpServerCommand extends BaseCommand<typeof McpServerComman
227236
tools: this.flags.tools ? this.flags.tools.split(',').map((s) => s.trim()) : undefined,
228237
allowNonGaTools: this.flags['allow-non-ga-tools'],
229238
configPath: this.flags.config,
239+
// Working directory for auto-discovery. oclif handles flag with env fallback.
240+
workingDirectory: this.flags['working-directory'],
230241
};
231242

232243
// TODO: Telemetry - Initialize telemetry unless disabled

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

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import {getLogger} from '@salesforce/b2c-tooling-sdk/logging';
8+
import {detectWorkspaceType, type ProjectType} from '@salesforce/b2c-tooling-sdk/discovery';
89
import type {McpTool, Toolset, StartupFlags} from './utils/index.js';
910
import {ALL_TOOLSETS, TOOLSETS, VALID_TOOLSET_NAMES} from './utils/index.js';
1011
import type {B2CDxMcpServer} from './server.js';
@@ -15,6 +16,55 @@ import {createPwav3Tools} from './tools/pwav3/index.js';
1516
import {createScapiTools} from './tools/scapi/index.js';
1617
import {createStorefrontNextTools} from './tools/storefrontnext/index.js';
1718

19+
/**
20+
* Base toolset that is always enabled.
21+
* Provides SCAPI discovery and custom API scaffolding tools.
22+
*/
23+
const BASE_TOOLSET: Toolset = 'SCAPI';
24+
25+
/**
26+
* Toolset mapping by project type.
27+
* Each project type enables specific toolsets IN ADDITION to the base toolset.
28+
*/
29+
const PROJECT_TYPE_TOOLSETS: Record<ProjectType, Toolset[]> = {
30+
cartridges: ['CARTRIDGES'],
31+
'pwa-kit-v3': ['PWAV3', 'MRT'],
32+
'storefront-next': ['STOREFRONTNEXT', 'MRT'],
33+
};
34+
35+
/**
36+
* Gets toolsets for a project type, always including the base toolset.
37+
*/
38+
function getToolsetsForProjectType(projectType: ProjectType): Toolset[] {
39+
const additionalToolsets = PROJECT_TYPE_TOOLSETS[projectType] ?? [];
40+
return [...additionalToolsets, BASE_TOOLSET];
41+
}
42+
43+
/**
44+
* Maps multiple detected project types to a union of MCP toolsets.
45+
*
46+
* Combines toolsets from all matched project types, enabling hybrid
47+
* project support (e.g., cartridges + pwa-kit-v3 gets CARTRIDGES + PWAV3 + MRT + SCAPI).
48+
*
49+
* @param projectTypes - Array of detected project types
50+
* @returns Union of all toolsets for the detected project types (always includes base toolset)
51+
*/
52+
function getToolsetsForProjectTypes(projectTypes: ProjectType[]): Toolset[] {
53+
const toolsetSet = new Set<Toolset>();
54+
55+
// Always include base toolset
56+
toolsetSet.add(BASE_TOOLSET);
57+
58+
// Add toolsets for each detected project type
59+
for (const projectType of projectTypes) {
60+
for (const toolset of getToolsetsForProjectType(projectType)) {
61+
toolsetSet.add(toolset);
62+
}
63+
}
64+
65+
return [...toolsetSet];
66+
}
67+
1868
/**
1969
* Registry of tools organized by toolset.
2070
* Tools can belong to multiple toolsets via their `toolsets` array.
@@ -61,8 +111,9 @@ export function createToolRegistry(services: Services): ToolRegistry {
61111
* Register tools with the MCP server based on startup flags.
62112
*
63113
* Tool selection logic:
64-
* 1. Start with all tools from --toolsets
65-
* 2. Add individual tools from --tools (can be from any toolset)
114+
* 1. If neither --toolsets nor --tools are provided, perform auto-discovery
115+
* 2. Start with all tools from --toolsets (or auto-discovered toolsets)
116+
* 3. Add individual tools from --tools (can be from any toolset)
66117
*
67118
* Example:
68119
* --toolsets STOREFRONTNEXT,MRT --tools cartridge_deploy
@@ -73,12 +124,43 @@ export function createToolRegistry(services: Services): ToolRegistry {
73124
* @param services - Services instance
74125
*/
75126
export async function registerToolsets(flags: StartupFlags, server: B2CDxMcpServer, services: Services): Promise<void> {
76-
const toolsets = flags.toolsets ?? [];
127+
let toolsets = flags.toolsets ?? [];
77128
const individualTools = flags.tools ?? [];
78129
const allowNonGaTools = flags.allowNonGaTools ?? false;
130+
const logger = getLogger();
131+
132+
// Auto-discovery: When no --toolsets or --tools flags are provided,
133+
// detect project type and enable appropriate toolsets automatically.
134+
if (toolsets.length === 0 && individualTools.length === 0) {
135+
// Working directory from --working-directory flag or SFCC_WORKING_DIRECTORY env var
136+
const workingDirectory = flags.workingDirectory ?? process.cwd();
137+
138+
// Warn if working directory wasn't explicitly configured
139+
if (!flags.workingDirectory) {
140+
logger.warn(
141+
{cwd: workingDirectory},
142+
'No --working-directory flag or SFCC_WORKING_DIRECTORY env var provided. ' +
143+
'MCP clients like Cursor and Claude Desktop often spawn servers from ~ instead of the project directory. ' +
144+
'Set --working-directory or SFCC_WORKING_DIRECTORY for reliable auto-discovery.',
145+
);
146+
}
79147

80-
// NOTE: When no --toolsets or --tools flags are provided, auto-discovery
81-
// will detect project type and enable appropriate toolsets automatically.
148+
const detectionResult = await detectWorkspaceType(workingDirectory);
149+
150+
// Map all detected project types to MCP toolsets (union)
151+
const mappedToolsets = getToolsetsForProjectTypes(detectionResult.projectTypes);
152+
153+
logger.info(
154+
{
155+
projectTypes: detectionResult.projectTypes,
156+
matchedPatterns: detectionResult.matchedPatterns,
157+
enabledToolsets: mappedToolsets,
158+
},
159+
`Auto-discovered project types: ${detectionResult.projectTypes.join(', ') || 'none'}`,
160+
);
161+
162+
toolsets = mappedToolsets;
163+
}
82164

83165
// Create the tool registry (all available tools)
84166
const toolRegistry = createToolRegistry(services);
@@ -88,8 +170,6 @@ export async function registerToolsets(flags: StartupFlags, server: B2CDxMcpServ
88170
const allToolsByName = new Map(allTools.map((tool) => [tool.name, tool]));
89171
const existingToolNames = new Set(allToolsByName.keys());
90172

91-
const logger = getLogger();
92-
93173
// Warn about invalid --tools names (but continue with valid ones)
94174
const invalidTools = individualTools.filter((name) => !existingToolNames.has(name));
95175
if (invalidTools.length > 0) {

packages/b2c-dx-mcp/src/utils/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,6 @@ export interface StartupFlags {
5050
allowNonGaTools?: boolean;
5151
/** Path to config file (dw.json format) */
5252
configPath?: string;
53+
/** Project working directory for tools (auto-discovery, scaffolding, etc.) */
54+
workingDirectory?: string;
5355
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ describe('McpServerCommand', () => {
6464
expect(flag).to.not.be.undefined;
6565
expect(flag.env).to.equal('SFCC_MRT_API_KEY');
6666
});
67+
68+
it('should define working-directory flag with env var support', () => {
69+
const flag = McpServerCommand.flags['working-directory'];
70+
expect(flag).to.not.be.undefined;
71+
expect(flag.env).to.equal('SFCC_WORKING_DIRECTORY');
72+
});
6773
});
6874

6975
describe('flag parse functions', () => {

0 commit comments

Comments
 (0)