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
3 changes: 3 additions & 0 deletions packages/b2c-cli/bin/dev.cmd
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@echo off

@REM Disable telemetry in development mode to avoid polluting production data
if not defined SFCC_DISABLE_TELEMETRY set SFCC_DISABLE_TELEMETRY=true

node --import tsx "%~dp0\dev" %*
3 changes: 3 additions & 0 deletions packages/b2c-cli/bin/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/

// Disable telemetry in development mode to avoid polluting production data
process.env.SFCC_DISABLE_TELEMETRY = process.env.SFCC_DISABLE_TELEMETRY || 'true';

// Load .env file if present (Node.js native support)
try {
process.loadEnvFile();
Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@
"bin": "b2c",
"dirname": "b2c",
"commands": "./dist/commands",
"telemetry": {
"connectionString": "InstrumentationKey=6fcc215f-0b11-4864-ad5c-3945ae19e2f3;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=a60f17ec-265a-4dfc-b8df-03a64695697d"
Comment thread
allison-grady marked this conversation as resolved.
},
"plugins": [
"@oclif/plugin-help",
"@oclif/plugin-plugins",
Expand Down
4 changes: 3 additions & 1 deletion packages/b2c-cli/test/commands/ecdn/security/get.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ describe('ecdn security get', () => {
}),
});

const result = (await runSilent(() => command.run())) as {settings: {securityLevel: string; wafEnabled: boolean}};
const result = (await runSilent(() => command.run())) as {
settings: {securityLevel: string; wafEnabled: boolean};
};

expect(result.settings.securityLevel).to.equal('high');
expect(result.settings.wafEnabled).to.be.true;
Expand Down
5 changes: 4 additions & 1 deletion packages/b2c-cli/test/commands/ecdn/zones/list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ describe('ecdn zones list', () => {
}),
});

const result = (await runSilent(() => command.run())) as {total: number; zones: Array<{name: string}>};
const result = (await runSilent(() => command.run())) as {
total: number;
zones: Array<{name: string}>;
};

expect(result).to.have.property('total', 1);
expect(result.zones).to.have.lengthOf(1);
Expand Down
37 changes: 37 additions & 0 deletions packages/b2c-dx-mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,43 @@ Storefront Next development tools for building modern storefronts.

> **Note:** Some tools appear in multiple toolsets (e.g., `mrt_bundle_push`, `scapi_discovery`). When using multiple toolsets, tools are automatically deduplicated.

## Telemetry

The MCP server collects anonymous usage telemetry to help improve the developer experience. Telemetry is enabled by default.

**Development mode**: Telemetry is automatically disabled when using `bin/dev.js`, so local development and testing won't pollute production data.

### Disabling Telemetry

Set one of these environment variables to disable telemetry:

```bash
# Salesforce CLI standard (recommended)
SF_DISABLE_TELEMETRY=true

# Or SFCC-specific
SFCC_DISABLE_TELEMETRY=true
```

You can also override the telemetry connection string for testing:

```bash
SFCC_APP_INSIGHTS_KEY=your-connection-string
```

### What We Collect

- **Server lifecycle events**: When the server starts, stops, or encounters errors
- **Tool usage**: Which tools are called and their execution time (not the arguments or results)
- **Command metrics**: Command duration and success/failure status
- **Environment info**: Platform, architecture, Node.js version, and package version

### What We Don't Collect

- **No credentials**: No API keys, passwords, or secrets
- **No business data**: No product data, customer information, or site content
- **No tool arguments**: No input parameters or output results from tool calls
- **No file contents**: No source code, configuration files, or project data

## Development

Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-dx-mcp/bin/dev.cmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
@echo off

@REM Disable telemetry in development mode to avoid polluting production data
if not defined SFCC_DISABLE_TELEMETRY set SFCC_DISABLE_TELEMETRY=true

node --conditions development --import tsx "%~dp0\dev" %*

3 changes: 3 additions & 0 deletions packages/b2c-dx-mcp/bin/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/

// Disable telemetry in development mode to avoid polluting production data
process.env.SFCC_DISABLE_TELEMETRY = process.env.SFCC_DISABLE_TELEMETRY || 'true';

// Load .env file if present (Node.js native support)
try {
process.loadEnvFile();
Expand Down
7 changes: 6 additions & 1 deletion packages/b2c-dx-mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@
"strategy": "single",
"target": "./dist/commands/mcp.js"
},
"topicSeparator": " "
"topicSeparator": " ",
"telemetry": {
"connectionString": "InstrumentationKey=6fcc215f-0b11-4864-ad5c-3945ae19e2f3;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=a60f17ec-265a-4dfc-b8df-03a64695697d"
}
},
"files": [
"bin",
Expand Down Expand Up @@ -99,6 +102,7 @@
"@types/chai": "^4",
"@types/mocha": "^10",
"@types/node": "^22.16.5",
"@types/sinon": "^21.0.0",
"chai": "^4",
"eslint": "^9",
"eslint-config-oclif": "^6",
Expand All @@ -109,6 +113,7 @@
"oclif": "^4",
"prettier": "^3.6.2",
"shx": "^0.3.3",
"sinon": "^21.0.1",
"tsx": "^4",
"typescript": "^5",
"typescript-eslint": "^8"
Expand Down
40 changes: 24 additions & 16 deletions packages/b2c-dx-mcp/src/commands/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
* | `--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 |
*
* ### Environment Variables for Telemetry
* | Env Variable | Description |
* |--------------|-------------|
* | `SF_DISABLE_TELEMETRY` | Set to `true` to disable telemetry (sf CLI standard) |
* | `SFCC_DISABLE_TELEMETRY` | Set to `true` to disable telemetry |
* | `SFCC_APP_INSIGHTS_KEY` | Override connection string from package.json |
*
* ### MRT Flags (from MrtCommand.baseFlags)
* | Flag | Env Variable | Description |
* |------|--------------|-------------|
Expand Down Expand Up @@ -152,6 +159,7 @@ import {TOOLSETS, type StartupFlags} from '../utils/index.js';
* - Global flags for config, logging, and debugging
* - Structured pino logging via `this.logger`
* - Automatic dw.json loading via `this.resolvedConfig`
* - Automatic telemetry initialization via `this.telemetry`
* - `this.config` - package.json metadata and standard config paths
*/
export default class McpServerCommand extends BaseCommand<typeof McpServerCommand> {
Expand Down Expand Up @@ -247,9 +255,9 @@ export default class McpServerCommand extends BaseCommand<typeof McpServerComman
* Main entry point - starts the MCP server.
*
* Execution flow:
* 1. BaseCommand.init() parses flags and loads config
* 1. BaseCommand.init() parses flags, loads config, and initializes telemetry
* 2. Filter and validate toolsets (invalid ones are skipped with warning)
* 3. Create B2CDxMcpServer instance
* 3. Create B2CDxMcpServer instance with telemetry from BaseCommand
* 4. Create Services via Services.fromResolvedConfig() using already-resolved config
* 5. Register tools based on --toolsets and --tools flags
* 6. Connect to stdio transport (JSON-RPC over stdin/stdout)
Expand All @@ -261,6 +269,7 @@ export default class McpServerCommand extends BaseCommand<typeof McpServerComman
* - `this.flags` - Parsed flags including global flags (config, debug, log-level, etc.)
* - `this.resolvedConfig` - Loaded dw.json configuration
* - `this.logger` - Structured pino logger
* - `this.telemetry` - Telemetry instance (auto-initialized from package.json config)
*
* oclif provides standard config paths via `this.config`:
* - `this.config.configDir` - User config (~/.config/b2c-dx-mcp)
Expand All @@ -281,21 +290,12 @@ export default class McpServerCommand extends BaseCommand<typeof McpServerComman
workingDirectory: this.flags['working-directory'],
};

// TODO: Telemetry - Initialize telemetry unless disabled
// if (!flags["no-telemetry"]) {
// telemetry = new Telemetry({
// toolsets: (startupFlags.toolsets ?? []).join(", "),
// configDir,
// version: this.config.version,
// });
// await telemetry.start();
// process.stdin.on("close", (err) => {
// telemetry?.sendEvent(err ? "SERVER_STOPPED_ERROR" : "SERVER_STOPPED_SUCCESS");
// telemetry?.stop();
// });
// }
// Add toolsets to telemetry attributes
if (this.telemetry && startupFlags.toolsets) {
this.telemetry.addAttributes({toolsets: startupFlags.toolsets.join(', ')});
}

// Create MCP server
// Create MCP server with telemetry from BaseCommand
const server = new B2CDxMcpServer(
{
name: this.config.name,
Expand All @@ -306,6 +306,7 @@ export default class McpServerCommand extends BaseCommand<typeof McpServerComman
resources: {},
tools: {},
},
telemetry: this.telemetry,
},
);

Expand All @@ -319,6 +320,13 @@ export default class McpServerCommand extends BaseCommand<typeof McpServerComman
const transport = new StdioServerTransport();
await server.connect(transport);

// Track server stop when stdin closes (MCP client disconnects)
// Note: The 'close' event has no arguments - it's just a signal that the stream closed
process.stdin.on('close', () => {
this.telemetry?.sendEvent('SERVER_STOPPED');
// Don't call stop() here - let finally() handle telemetry cleanup
});

// Log startup message using the structured logger
this.logger.info({version: this.config.version}, 'MCP Server running on stdio');
}
Expand Down
68 changes: 53 additions & 15 deletions packages/b2c-dx-mcp/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,27 @@ import type {ServerOptions} from '@modelcontextprotocol/sdk/server/index.js';
import type {RequestHandlerExtra} from '@modelcontextprotocol/sdk/shared/protocol.js';
import type {Transport} from '@modelcontextprotocol/sdk/shared/transport.js';
import type {ZodRawShape} from 'zod';
import type {Telemetry} from '@salesforce/b2c-tooling-sdk/telemetry';

/**
* Extended server options.
*/
export type B2CDxMcpServerOptions = ServerOptions;
export interface B2CDxMcpServerOptions extends ServerOptions {
/**
* Telemetry instance for tracking server and tool events.
* If not provided, telemetry is disabled.
*/
telemetry?: Telemetry;
}

/**
* A server implementation that extends the base MCP server.
*
*
* @augments {McpServer}
*/
export class B2CDxMcpServer extends McpServer {
private telemetry?: Telemetry;

/**
* Creates a new B2CDxMcpServer instance
*
Expand All @@ -36,15 +44,17 @@ export class B2CDxMcpServer extends McpServer {
*/
public constructor(serverInfo: Implementation, options?: B2CDxMcpServerOptions) {
super(serverInfo, options);
this.telemetry = options?.telemetry;

// Set up oninitialized handler
this.server.oninitialized = (): void => {
const clientInfo = this.server.getClientVersion();
if (clientInfo) {
// TODO: Telemetry - Add client info attributes
// telemetry.addAttributes({ clientName: clientInfo.name, clientVersion: clientInfo.version });
this.telemetry?.addAttributes({
clientName: clientInfo.name,
clientVersion: clientInfo.version,
});
}
// TODO: Telemetry - Send SERVER_START_SUCCESS event
};
}

Expand All @@ -68,13 +78,29 @@ export class B2CDxMcpServer extends McpServer {
args: Record<string, unknown>,
_extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
): Promise<CallToolResult> => {
// TODO: Telemetry - Track timing and send TOOL_CALLED event
// const startTime = Date.now();
const result = await handler(args);
// const runtimeMs = Date.now() - startTime;
// telemetry.sendEvent('TOOL_CALLED', { name, runtimeMs, isError: result.isError });
const startTime = Date.now();
try {
const result = await handler(args);
const runTimeMs = Date.now() - startTime;

return result;
this.telemetry?.sendEvent('TOOL_CALLED', {
toolName: name,
runTimeMs,
isError: result.isError ?? false,
});

return result;
} catch (error) {
const runTimeMs = Date.now() - startTime;

this.telemetry?.sendEvent('TOOL_CALLED', {
toolName: name,
runTimeMs,
isError: true,
});

throw error;
}
};

// Use the new registerTool API (tool() is deprecated)
Expand All @@ -85,10 +111,22 @@ export class B2CDxMcpServer extends McpServer {
* Connect to a transport.
*/
public override async connect(transport: Transport): Promise<void> {
await super.connect(transport);
if (!this.isConnected()) {
// TODO: Telemetry - Send SERVER_START_ERROR event with "Server not connected"
try {
await super.connect(transport);
if (this.isConnected()) {
this.telemetry?.sendEvent('SERVER_STATUS', {status: 'started'});
} else {
this.telemetry?.sendEvent('SERVER_STATUS', {
status: 'error',
errorMessage: 'Server not connected after connect() call',
});
}
} catch (error) {
this.telemetry?.sendEvent('SERVER_STATUS', {
status: 'error',
errorMessage: error instanceof Error ? error.message : String(error),
});
throw error;
}
// TODO: Telemetry - wrap with try/catch to send SERVER_START_ERROR event with error details
}
}
Loading
Loading