Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#GUSINFO:CC Cosmos,B2C CLI and Developer Tools
* @clavery
/packages/b2c-dx-mcp/ @yhsieh1
5 changes: 4 additions & 1 deletion docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,13 @@ See [Configure WebDAV File Access](https://help.salesforce.com/s/articleView?id=

## Environment Variables

You can configure authentication using environment variables:
You can configure the CLI using environment variables:

| Variable | Description |
|----------|-------------|
| `SFCC_WORKING_DIRECTORY` | Project working directory |
| `SFCC_CONFIG` | Path to config file (dw.json format) |
| `SFCC_INSTANCE` | Instance name from config file |
| `SFCC_SERVER` | The B2C instance hostname |
| `SFCC_CLIENT_ID` | OAuth client ID |
| `SFCC_CLIENT_SECRET` | OAuth client secret |
Expand Down
56 changes: 56 additions & 0 deletions packages/b2c-dx-mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,62 @@ Since the package is not yet published to npm, see the [Development](#developmen
| `--debug` | Enable debug logging |
| `--json` | Output logs as JSON lines |
| `--lang` | Language for messages |
| `--working-directory` | Project working directory (env: `SFCC_WORKING_DIRECTORY`) |

### Workspace Auto-Discovery

When neither `--toolsets` nor `--tools` are provided, the MCP server automatically detects your project type and enables the appropriate toolsets.

**How it works:**

1. The server analyzes your working directory (from `--working-directory` flag, `SFCC_WORKING_DIRECTORY` env var, or current directory)
2. It checks for project markers like `package.json` dependencies, folder structures, and config files
3. It enables all toolsets that match any detected project type

**Project Types and Toolsets:**

| Project Type | Detection | Toolsets Enabled |
|--------------|-----------|------------------|
| **PWA Kit v3** | `@salesforce/pwa-kit-*`, `@salesforce/retail-react-app`, or `ccExtensibility` | PWAV3, MRT, SCAPI |
| **Storefront Next** | `@salesforce/storefront-next-*` packages in package.json | STOREFRONTNEXT, MRT, SCAPI |
| **SFRA** | `cartridges/` folder with controllers or templates | CARTRIDGES, SCAPI |
| **Custom API** | `rest-apis/*/api.json` or `rest-apis/*/schema.yaml` files | CARTRIDGES, SCAPI |
| **Headless** | `dw.json` file (no specific framework detected) | SCAPI |
| **Unknown** | No B2C project markers found | SCAPI (fallback) |

**Hybrid Projects:**

If multiple project types are detected (e.g., SFRA + Custom API), toolsets from all matched types are combined.

**Example:**

**Cursor** (supports `${workspaceFolder}`):

```json
{
"mcpServers": {
"b2c-dx": {
"command": "/path/to/packages/b2c-dx-mcp/bin/dev.js",
"args": ["--working-directory", "${workspaceFolder}", "--allow-non-ga-tools"]
}
}
}
```

**Claude Desktop** (use explicit path):

```json
{
"mcpServers": {
"b2c-dx": {
"command": "/path/to/packages/b2c-dx-mcp/bin/dev.js",
"args": ["--working-directory", "/path/to/your/project", "--allow-non-ga-tools"]
}
}
}
```

> **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.

### Configuration Examples

Expand Down
37 changes: 24 additions & 13 deletions packages/b2c-dx-mcp/src/commands/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
* ## Flags
*
* ### MCP-Specific Flags
* | Flag | Description |
* |------|-------------|
* | `--toolsets` | Comma-separated toolsets to enable (case-insensitive) |
* | `--tools` | Comma-separated individual tools to enable (case-insensitive) |
* | `--allow-non-ga-tools` | Enable experimental/non-GA tools |
* | Flag | Env Variable | Description |
* |------|--------------|-------------|
* | `--toolsets` | `SFCC_TOOLSETS` | Comma-separated toolsets to enable (case-insensitive) |
* | `--tools` | `SFCC_TOOLS` | Comma-separated individual tools to enable (case-insensitive) |
* | `--allow-non-ga-tools` | `SFCC_ALLOW_NON_GA_TOOLS` | Enable experimental/non-GA tools |
*
* ### MRT Flags (from MrtCommand.baseFlags)
* | Flag | Env Variable | Description |
Expand All @@ -39,14 +39,23 @@
* | `--client-secret` | `SFCC_CLIENT_SECRET` | OAuth client secret |
*
* ### Global Flags (inherited from BaseCommand)
* | Flag | Description |
* |------|-------------|
* | `--config` | Path to dw.json config file (auto-discovered if not provided) |
* | `--instance` | Instance name from configuration file |
* | `--log-level` | Set logging verbosity (trace, debug, info, warn, error, silent) |
* | `--debug` | Enable debug logging |
* | `--json` | Output logs as JSON lines |
* | `--lang` | Language for messages |
* | Flag | Env Variable | Description |
* |------|--------------|-------------|
* | `--working-directory` | `SFCC_WORKING_DIRECTORY` | Project working directory (see note below) |
* | `--config` | `SFCC_CONFIG` | Path to dw.json config file (auto-discovered if not provided) |
* | `--instance` | `SFCC_INSTANCE` | Instance name from configuration file |
* | `--log-level` | `SFCC_LOG_LEVEL` | Set logging verbosity (trace, debug, info, warn, error, silent) |
* | `--debug` | `SFCC_DEBUG` | Enable debug logging |
* | `--json` | - | Output logs as JSON lines |
* | `--lang` | - | Language for messages |
*
* **Note on `--working-directory`**: Many MCP clients (Cursor, Claude Desktop) spawn servers from the
* user's home directory (`~`) rather than the project directory. This flag is used for:
* - Auto-discovery (detecting project type when no `--toolsets` or `--tools` are provided)
* - Scaffolding tools (creating files in the correct project location)
* - Any tool that needs to operate on the project directory
*
* Use `--working-directory` or `SFCC_WORKING_DIRECTORY` env var to specify the actual project path.
*
* ## Configuration
*
Expand Down Expand Up @@ -227,6 +236,8 @@ export default class McpServerCommand extends BaseCommand<typeof McpServerComman
tools: this.flags.tools ? this.flags.tools.split(',').map((s) => s.trim()) : undefined,
allowNonGaTools: this.flags['allow-non-ga-tools'],
configPath: this.flags.config,
// Working directory for auto-discovery. oclif handles flag with env fallback.
workingDirectory: this.flags['working-directory'],
};

// TODO: Telemetry - Initialize telemetry unless disabled
Expand Down
98 changes: 91 additions & 7 deletions packages/b2c-dx-mcp/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import {getLogger} from '@salesforce/b2c-tooling-sdk/logging';
import {detectWorkspaceType, type ProjectType} from '@salesforce/b2c-tooling-sdk/discovery';
import type {McpTool, Toolset, StartupFlags} from './utils/index.js';
import {ALL_TOOLSETS, TOOLSETS, VALID_TOOLSET_NAMES} from './utils/index.js';
import type {B2CDxMcpServer} from './server.js';
Expand All @@ -15,6 +16,59 @@ import {createPwav3Tools} from './tools/pwav3/index.js';
import {createScapiTools} from './tools/scapi/index.js';
import {createStorefrontNextTools} from './tools/storefrontnext/index.js';

/**
* Maps a single project type to its associated MCP toolsets.
*/
function getToolsetsForProjectType(projectType: ProjectType): Toolset[] {
switch (projectType) {
case 'custom-api': {
return ['CARTRIDGES', 'SCAPI'];
}
case 'headless': {
return ['SCAPI'];
}
case 'pwa-kit-v3': {
return ['PWAV3', 'MRT', 'SCAPI'];
}
case 'sfra': {
return ['CARTRIDGES', 'SCAPI'];
}
case 'storefront-next': {
return ['STOREFRONTNEXT', 'MRT', 'SCAPI'];
}
default: {
// Fallback: provide basic SCAPI tools
return ['SCAPI'];
}
}
}

/**
* Maps multiple detected project types to a union of MCP toolsets.
*
* Combines toolsets from all matched project types, enabling hybrid
* project support (e.g., SFRA + Custom API gets both CARTRIDGES and SCAPI).
*
* @param projectTypes - Array of detected project types
* @returns Union of all toolsets for the detected project types
*/
function getToolsetsForProjectTypes(projectTypes: ProjectType[]): Toolset[] {
// Fallback to SCAPI when no project types detected
if (projectTypes.length === 0) {
return ['SCAPI'];
}

const toolsetSet = new Set<Toolset>();

for (const projectType of projectTypes) {
for (const toolset of getToolsetsForProjectType(projectType)) {
toolsetSet.add(toolset);
}
}

return [...toolsetSet];
}

/**
* Registry of tools organized by toolset.
* Tools can belong to multiple toolsets via their `toolsets` array.
Expand Down Expand Up @@ -61,8 +115,9 @@ export function createToolRegistry(services: Services): ToolRegistry {
* Register tools with the MCP server based on startup flags.
*
* Tool selection logic:
* 1. Start with all tools from --toolsets
* 2. Add individual tools from --tools (can be from any toolset)
* 1. If neither --toolsets nor --tools are provided, perform auto-discovery
* 2. Start with all tools from --toolsets (or auto-discovered toolsets)
* 3. Add individual tools from --tools (can be from any toolset)
*
* Example:
* --toolsets STOREFRONTNEXT,MRT --tools cartridge_deploy
Expand All @@ -73,12 +128,43 @@ export function createToolRegistry(services: Services): ToolRegistry {
* @param services - Services instance
*/
export async function registerToolsets(flags: StartupFlags, server: B2CDxMcpServer, services: Services): Promise<void> {
const toolsets = flags.toolsets ?? [];
let toolsets = flags.toolsets ?? [];
const individualTools = flags.tools ?? [];
const allowNonGaTools = flags.allowNonGaTools ?? false;
const logger = getLogger();

// Auto-discovery: When no --toolsets or --tools flags are provided,
// detect project type and enable appropriate toolsets automatically.
if (toolsets.length === 0 && individualTools.length === 0) {
// Working directory from --working-directory flag or SFCC_WORKING_DIRECTORY env var
const workingDirectory = flags.workingDirectory ?? process.cwd();

// Warn if working directory wasn't explicitly configured
if (!flags.workingDirectory) {
logger.warn(
{cwd: workingDirectory},
'No --working-directory flag or SFCC_WORKING_DIRECTORY env var provided. ' +
'MCP clients like Cursor and Claude Desktop often spawn servers from ~ instead of the project directory. ' +
'Set --working-directory or SFCC_WORKING_DIRECTORY for reliable auto-discovery.',
);
}

const detectionResult = await detectWorkspaceType(workingDirectory);

// Map all detected project types to MCP toolsets (union)
const mappedToolsets = getToolsetsForProjectTypes(detectionResult.projectTypes);

logger.info(
{
projectTypes: detectionResult.projectTypes,
matchedPatterns: detectionResult.matchedPatterns,
enabledToolsets: mappedToolsets,
},
`Auto-discovered project types: ${detectionResult.projectTypes.join(', ') || 'none'}`,
);

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

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

const logger = getLogger();

// Warn about invalid --tools names (but continue with valid ones)
const invalidTools = individualTools.filter((name) => !existingToolNames.has(name));
if (invalidTools.length > 0) {
Expand Down
2 changes: 2 additions & 0 deletions packages/b2c-dx-mcp/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ export interface StartupFlags {
allowNonGaTools?: boolean;
/** Path to config file (dw.json format) */
configPath?: string;
/** Project working directory for tools (auto-discovery, scaffolding, etc.) */
workingDirectory?: string;
}
6 changes: 6 additions & 0 deletions packages/b2c-dx-mcp/test/commands/mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ describe('McpServerCommand', () => {
expect(flag).to.not.be.undefined;
expect(flag.env).to.equal('SFCC_MRT_API_KEY');
});

it('should define working-directory flag with env var support', () => {
const flag = McpServerCommand.flags['working-directory'];
expect(flag).to.not.be.undefined;
expect(flag.env).to.equal('SFCC_WORKING_DIRECTORY');
});
});

describe('flag parse functions', () => {
Expand Down
Loading
Loading