Skip to content

Commit 7b6b0d0

Browse files
committed
@W-20591323 workspace type auto-discovevry
1 parent 9cbd448 commit 7b6b0d0

28 files changed

Lines changed: 1553 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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,62 @@ 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, folder structures, and config files
65+
3. It enables all toolsets that match any detected project type
66+
67+
**Project Types and Toolsets:**
68+
69+
| Project Type | Detection | Toolsets Enabled |
70+
|--------------|-----------|------------------|
71+
| **PWA Kit v3** | `@salesforce/pwa-kit-*` packages in package.json | PWAV3, MRT, SCAPI |
72+
| **Storefront Next** | `@salesforce/storefront-next-*` packages in package.json | STOREFRONTNEXT, MRT, SCAPI |
73+
| **SFRA** | `cartridges/` folder with controllers or templates | CARTRIDGES, SCAPI |
74+
| **Custom API** | `rest-apis/*/api.json` or `rest-apis/*/schema.yaml` files | CARTRIDGES, SCAPI |
75+
| **Headless** | `dw.json` file (no specific framework detected) | SCAPI |
76+
| **Unknown** | No B2C project markers found | SCAPI (fallback) |
77+
78+
**Hybrid Projects:**
79+
80+
If multiple project types are detected (e.g., SFRA + Custom API), toolsets from all matched types are combined.
81+
82+
**Example:**
83+
84+
**Cursor** (supports `${workspaceFolder}`):
85+
86+
```json
87+
{
88+
"mcpServers": {
89+
"b2c-dx": {
90+
"command": "/path/to/packages/b2c-dx-mcp/bin/dev.js",
91+
"args": ["--working-directory", "${workspaceFolder}", "--allow-non-ga-tools"]
92+
}
93+
}
94+
}
95+
```
96+
97+
**Claude Desktop** (use explicit path):
98+
99+
```json
100+
{
101+
"mcpServers": {
102+
"b2c-dx": {
103+
"command": "/path/to/packages/b2c-dx-mcp/bin/dev.js",
104+
"args": ["--working-directory", "/path/to/your/project", "--allow-non-ga-tools"]
105+
}
106+
}
107+
}
108+
```
109+
110+
> **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.
55111
56112
### Configuration Examples
57113

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: 91 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,59 @@ 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+
* Maps a single project type to its associated MCP toolsets.
21+
*/
22+
function getToolsetsForProjectType(projectType: ProjectType): Toolset[] {
23+
switch (projectType) {
24+
case 'custom-api': {
25+
return ['CARTRIDGES', 'SCAPI'];
26+
}
27+
case 'headless': {
28+
return ['SCAPI'];
29+
}
30+
case 'pwa-kit-v3': {
31+
return ['PWAV3', 'MRT', 'SCAPI'];
32+
}
33+
case 'sfra': {
34+
return ['CARTRIDGES', 'SCAPI'];
35+
}
36+
case 'storefront-next': {
37+
return ['STOREFRONTNEXT', 'MRT', 'SCAPI'];
38+
}
39+
default: {
40+
// Fallback: provide basic SCAPI tools for unknown projects
41+
return ['SCAPI'];
42+
}
43+
}
44+
}
45+
46+
/**
47+
* Maps multiple detected project types to a union of MCP toolsets.
48+
*
49+
* Combines toolsets from all matched project types, enabling hybrid
50+
* project support (e.g., SFRA + Custom API gets both CARTRIDGES and SCAPI).
51+
*
52+
* @param projectTypes - Array of detected project types
53+
* @returns Union of all toolsets for the detected project types
54+
*/
55+
function getToolsetsForProjectTypes(projectTypes: ProjectType[]): Toolset[] {
56+
// Fallback to SCAPI when no project types detected
57+
if (projectTypes.length === 0) {
58+
return ['SCAPI'];
59+
}
60+
61+
const toolsetSet = new Set<Toolset>();
62+
63+
for (const projectType of projectTypes) {
64+
for (const toolset of getToolsetsForProjectType(projectType)) {
65+
toolsetSet.add(toolset);
66+
}
67+
}
68+
69+
return [...toolsetSet];
70+
}
71+
1872
/**
1973
* Registry of tools organized by toolset.
2074
* Tools can belong to multiple toolsets via their `toolsets` array.
@@ -61,8 +115,9 @@ export function createToolRegistry(services: Services): ToolRegistry {
61115
* Register tools with the MCP server based on startup flags.
62116
*
63117
* Tool selection logic:
64-
* 1. Start with all tools from --toolsets
65-
* 2. Add individual tools from --tools (can be from any toolset)
118+
* 1. If neither --toolsets nor --tools are provided, perform auto-discovery
119+
* 2. Start with all tools from --toolsets (or auto-discovered toolsets)
120+
* 3. Add individual tools from --tools (can be from any toolset)
66121
*
67122
* Example:
68123
* --toolsets STOREFRONTNEXT,MRT --tools cartridge_deploy
@@ -73,12 +128,43 @@ export function createToolRegistry(services: Services): ToolRegistry {
73128
* @param services - Services instance
74129
*/
75130
export async function registerToolsets(flags: StartupFlags, server: B2CDxMcpServer, services: Services): Promise<void> {
76-
const toolsets = flags.toolsets ?? [];
131+
let toolsets = flags.toolsets ?? [];
77132
const individualTools = flags.tools ?? [];
78133
const allowNonGaTools = flags.allowNonGaTools ?? false;
134+
const logger = getLogger();
135+
136+
// Auto-discovery: When no --toolsets or --tools flags are provided,
137+
// detect project type and enable appropriate toolsets automatically.
138+
if (toolsets.length === 0 && individualTools.length === 0) {
139+
// Working directory from --working-directory flag or SFCC_WORKING_DIRECTORY env var
140+
const workingDirectory = flags.workingDirectory ?? process.cwd();
141+
142+
// Warn if working directory wasn't explicitly configured
143+
if (!flags.workingDirectory) {
144+
logger.warn(
145+
{cwd: workingDirectory},
146+
'No --working-directory flag or SFCC_WORKING_DIRECTORY env var provided. ' +
147+
'MCP clients like Cursor and Claude Desktop often spawn servers from ~ instead of the project directory. ' +
148+
'Set --working-directory or SFCC_WORKING_DIRECTORY for reliable auto-discovery.',
149+
);
150+
}
151+
152+
const detectionResult = await detectWorkspaceType(workingDirectory);
153+
154+
// Map all detected project types to MCP toolsets (union)
155+
const mappedToolsets = getToolsetsForProjectTypes(detectionResult.projectTypes);
156+
157+
logger.info(
158+
{
159+
projectTypes: detectionResult.projectTypes,
160+
matchedPatterns: detectionResult.matchedPatterns,
161+
enabledToolsets: mappedToolsets,
162+
},
163+
`Auto-discovered project types: ${detectionResult.projectTypes.join(', ') || 'none'}`,
164+
);
79165

80-
// NOTE: When no --toolsets or --tools flags are provided, auto-discovery
81-
// will detect project type and enable appropriate toolsets automatically.
166+
toolsets = mappedToolsets;
167+
}
82168

83169
// Create the tool registry (all available tools)
84170
const toolRegistry = createToolRegistry(services);
@@ -88,8 +174,6 @@ export async function registerToolsets(flags: StartupFlags, server: B2CDxMcpServ
88174
const allToolsByName = new Map(allTools.map((tool) => [tool.name, tool]));
89175
const existingToolNames = new Set(allToolsByName.keys());
90176

91-
const logger = getLogger();
92-
93177
// Warn about invalid --tools names (but continue with valid ones)
94178
const invalidTools = individualTools.filter((name) => !existingToolNames.has(name));
95179
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)