Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
60 changes: 60 additions & 0 deletions packages/b2c-dx-mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,66 @@ 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 and `.project` files
3. It enables all toolsets that match any detected project type, plus the base SCAPI toolset

**Base Toolset:**

The **SCAPI** toolset is always enabled, providing API discovery and custom API scaffolding capabilities.

**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 |
| **Cartridges** | Any cartridge with `.project` file (detected via `findCartridges`) | CARTRIDGES, SCAPI |
| **No project detected** | No B2C project markers found | SCAPI (base toolset only) |

**Hybrid Projects:**

If multiple project types are detected (e.g., cartridges + PWA Kit v3), 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.

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

### 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
94 changes: 87 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,55 @@ import {createPwav3Tools} from './tools/pwav3/index.js';
import {createScapiTools} from './tools/scapi/index.js';
import {createStorefrontNextTools} from './tools/storefrontnext/index.js';

/**
* Base toolset that is always enabled.
* Provides SCAPI discovery and custom API scaffolding tools.
*/
const BASE_TOOLSET: Toolset = 'SCAPI';

/**
* Toolset mapping by project type.
* Each project type enables specific toolsets IN ADDITION to the base toolset.
*/
const PROJECT_TYPE_TOOLSETS: Record<ProjectType, Toolset[]> = {
cartridges: ['CARTRIDGES'],
'pwa-kit-v3': ['PWAV3', 'MRT'],
'storefront-next': ['STOREFRONTNEXT', 'MRT'],
};

/**
* Gets toolsets for a project type, always including the base toolset.
*/
function getToolsetsForProjectType(projectType: ProjectType): Toolset[] {
const additionalToolsets = PROJECT_TYPE_TOOLSETS[projectType] ?? [];
return [...additionalToolsets, BASE_TOOLSET];
}

/**
* Maps multiple detected project types to a union of MCP toolsets.
*
* Combines toolsets from all matched project types, enabling hybrid
* project support (e.g., cartridges + pwa-kit-v3 gets CARTRIDGES + PWAV3 + MRT + SCAPI).
*
* @param projectTypes - Array of detected project types
* @returns Union of all toolsets for the detected project types (always includes base toolset)
*/
function getToolsetsForProjectTypes(projectTypes: ProjectType[]): Toolset[] {
const toolsetSet = new Set<Toolset>();

// Always include base toolset
toolsetSet.add(BASE_TOOLSET);

// Add toolsets for each detected project type
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 +111,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 +124,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.',
);
}

// NOTE: When no --toolsets or --tools flags are provided, auto-discovery
// will detect project type and enable appropriate toolsets automatically.
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'}`,
);

toolsets = mappedToolsets;
}

// Create the tool registry (all available tools)
const toolRegistry = createToolRegistry(services);
Expand All @@ -88,8 +170,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