@salesforce/b2c-dx-mcp 0.0.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +422 -29
  2. package/bin/run.cmd +3 -0
  3. package/bin/run.js +27 -0
  4. package/content/auth.md +62 -0
  5. package/content/components.md +123 -0
  6. package/content/config.md +180 -0
  7. package/content/data-fetching.md +323 -0
  8. package/content/extensions.md +80 -0
  9. package/content/i18n.md +121 -0
  10. package/content/page-designer.md +86 -0
  11. package/content/performance.md +80 -0
  12. package/content/pitfalls.md +141 -0
  13. package/content/quick-reference.md +226 -0
  14. package/content/state-management.md +75 -0
  15. package/content/styling.md +51 -0
  16. package/content/testing.md +232 -0
  17. package/dist/commands/mcp.d.ts +110 -0
  18. package/dist/commands/mcp.js +333 -0
  19. package/dist/registry.d.ts +37 -0
  20. package/dist/registry.js +212 -0
  21. package/dist/server.d.ts +46 -0
  22. package/dist/server.js +98 -0
  23. package/dist/services.d.ts +168 -0
  24. package/dist/services.js +191 -0
  25. package/dist/tools/adapter.d.ts +201 -0
  26. package/dist/tools/adapter.js +220 -0
  27. package/dist/tools/cartridges/index.d.ts +20 -0
  28. package/dist/tools/cartridges/index.js +101 -0
  29. package/dist/tools/index.d.ts +17 -0
  30. package/dist/tools/index.js +25 -0
  31. package/dist/tools/mrt/index.d.ts +20 -0
  32. package/dist/tools/mrt/index.js +101 -0
  33. package/dist/tools/pwav3/index.d.ts +13 -0
  34. package/dist/tools/pwav3/index.js +78 -0
  35. package/dist/tools/scapi/index.d.ts +9 -0
  36. package/dist/tools/scapi/index.js +68 -0
  37. package/dist/tools/storefrontnext/developer-guidelines.d.ts +9 -0
  38. package/dist/tools/storefrontnext/developer-guidelines.js +140 -0
  39. package/dist/tools/storefrontnext/index.d.ts +13 -0
  40. package/dist/tools/storefrontnext/index.js +83 -0
  41. package/dist/utils/constants.d.ts +16 -0
  42. package/dist/utils/constants.js +18 -0
  43. package/dist/utils/index.d.ts +7 -0
  44. package/dist/utils/index.js +16 -0
  45. package/dist/utils/types.d.ts +45 -0
  46. package/dist/utils/types.js +7 -0
  47. package/oclif.manifest.json +377 -0
  48. package/package.json +123 -7
@@ -0,0 +1,232 @@
1
+ # Testing Strategy
2
+
3
+ ## Unit Tests (Vitest)
4
+
5
+ This project uses **Vitest** for unit tests, running under Vite with jsdom as the default test environment.
6
+
7
+ ### Test File Organization
8
+
9
+ Tests live alongside source files with `.test.ts` or `.test.tsx` extension:
10
+
11
+ ```typescript
12
+ // src/components/product-card/product-card.test.tsx
13
+ import { describe, it, expect, vi } from 'vitest';
14
+ import { render, screen } from '@testing-library/react';
15
+ import { ProductCard } from './product-card';
16
+ import { mockProduct } from '@/test-utils/mocks';
17
+
18
+ describe('ProductCard', () => {
19
+ it('renders product name', () => {
20
+ render(<ProductCard product={mockProduct} />);
21
+ expect(screen.getByText(mockProduct.productName)).toBeInTheDocument();
22
+ });
23
+ });
24
+ ```
25
+
26
+ ### Test Utilities
27
+
28
+ Test utilities are available in `src/test-utils/`:
29
+ - `config.ts` - Mock configuration objects and ConfigProvider wrappers
30
+ - `context-provider-utils.ts` - Context provider helpers for testing
31
+ - `context-provider.tsx` - Test context providers
32
+
33
+ ### Running Tests
34
+
35
+ ```bash
36
+ # Run all tests with coverage
37
+ pnpm test
38
+
39
+ # Open Vitest UI (interactive test runner)
40
+ pnpm test:ui
41
+
42
+ # Watch mode (re-run on file changes)
43
+ pnpm test:watch
44
+
45
+ # Generate coverage report
46
+ pnpm test
47
+ # Coverage report outputs to console and coverage/ directory
48
+ ```
49
+
50
+ ### Coverage Requirements
51
+
52
+ Coverage thresholds are enforced in `vitest.thresholds.ts`:
53
+ - Lines: 73%
54
+ - Statements: 73%
55
+ - Functions: 86%
56
+ - Branches: 87%
57
+
58
+ These thresholds represent minimum values that must not be undershot. They should be raised regularly to reflect current status.
59
+
60
+ ### Testing Libraries
61
+
62
+ - **@testing-library/react** - React component testing
63
+ - **@testing-library/jest-dom** - Custom Jest DOM matchers
64
+ - **@testing-library/user-event** - User interaction simulation
65
+ - **@vitest/coverage-v8** - Code coverage
66
+ - **@vitest/ui** - Interactive test UI
67
+
68
+ ## Storybook Testing
69
+
70
+ Every reusable component should have a Storybook story file (`.stories.tsx`).
71
+
72
+ ### Story Structure
73
+
74
+ ```typescript
75
+ // src/components/product-card/product-card.stories.tsx
76
+ import type { Meta, StoryObj } from '@storybook/react-vite';
77
+ import { within, expect } from 'storybook/test';
78
+ import { waitForStorybookReady } from '@storybook/test-utils';
79
+ import { ProductCard } from './product-card';
80
+ import { ConfigProvider } from '@/config/context';
81
+ import { mockConfig } from '@/test-utils/config';
82
+ import { mockProduct } from '@/test-utils/mocks';
83
+
84
+ const meta: Meta<typeof ProductCard> = {
85
+ title: 'Components/ProductCard',
86
+ component: ProductCard,
87
+ tags: ['autodocs', 'interaction'],
88
+ decorators: [
89
+ (Story) => (
90
+ <ConfigProvider config={mockConfig}>
91
+ <Story />
92
+ </ConfigProvider>
93
+ ),
94
+ ],
95
+ };
96
+
97
+ export default meta;
98
+ type Story = StoryObj<typeof ProductCard>;
99
+
100
+ export const Default: Story = {
101
+ args: {
102
+ product: mockProduct,
103
+ },
104
+ play: async ({ canvasElement }) => {
105
+ await waitForStorybookReady(canvasElement);
106
+ const canvas = within(canvasElement);
107
+ await expect(canvas.getByText(mockProduct.productName)).toBeInTheDocument();
108
+ },
109
+ };
110
+ ```
111
+
112
+ ### Storybook Commands
113
+
114
+ ```bash
115
+ # Development server (port 6006)
116
+ pnpm storybook
117
+
118
+ # Build static Storybook
119
+ pnpm build-storybook
120
+
121
+ # Snapshot tests (visual regression)
122
+ pnpm test-storybook:snapshot
123
+ pnpm test-storybook:snapshot:update # Update snapshots
124
+
125
+ # Interaction tests (play functions)
126
+ pnpm test-storybook:interaction
127
+ pnpm test-storybook:static:interaction # Against static build
128
+
129
+ # Accessibility tests
130
+ pnpm test-storybook:a11y
131
+ pnpm test-storybook:static:a11y # Against static build
132
+
133
+ # Generate story tests with coverage
134
+ pnpm generate:story-tests:coverage
135
+ ```
136
+
137
+ ### Storybook Features
138
+
139
+ - **@storybook/addon-docs** - Automatic documentation generation
140
+ - **@storybook/addon-a11y** - Accessibility testing and validation
141
+ - **@storybook/addon-vitest** - Integration with Vitest
142
+ - **@storybook/test-runner** - Automated testing (interaction, a11y)
143
+ - **Viewport Toolbar** - Built-in toolbar for testing different screen sizes
144
+
145
+ > **Important**: Use Storybook's built-in viewport toolbar instead of creating separate Mobile/Tablet/Desktop stories. Use the viewport selector in the Storybook toolbar to test components at different screen sizes.
146
+
147
+ ### Story Tags
148
+
149
+ - `autodocs` - Enable automatic documentation
150
+ - `interaction` - Include in interaction test runs
151
+ - `skip-a11y` - Exclude from a11y tests (use sparingly)
152
+
153
+ ## Testing Best Practices
154
+
155
+ ### Component Testing
156
+
157
+ 1. **Colocate tests** - Keep test files next to source files
158
+ 2. **Use test utilities** - Leverage `@/test-utils` for mocks and providers
159
+ 3. **Mock external dependencies** - Use `vi.mock()` for API clients, context providers, etc.
160
+ 4. **Test user interactions** - Use `@testing-library/user-event` for realistic interactions
161
+ 5. **Test accessibility** - Use Storybook a11y addon and test-runner
162
+
163
+ ### Storybook Stories
164
+
165
+ 1. **Multiple variants** - Create stories for different states (Default, Loading, Error, etc.)
166
+ 2. **Play functions** - Use `play` functions for interaction testing
167
+ 3. **Decorators** - Wrap stories with necessary providers (ConfigProvider, etc.)
168
+ 4. **Documentation** - Include component descriptions and prop documentation
169
+ 5. **Viewport testing** - Use built-in viewport toolbar, not separate stories
170
+
171
+ ### Route Testing
172
+
173
+ Route tests should mock:
174
+ - Loader functions and their return values
175
+ - Action functions
176
+ - React Router context
177
+ - API clients
178
+
179
+ Example:
180
+
181
+ ```typescript
182
+ // src/routes/_app.product.$productId.test.tsx
183
+ import { describe, test, expect, vi } from 'vitest';
184
+ import { render } from '@testing-library/react';
185
+
186
+ vi.mock('@/components/product-view', () => ({
187
+ default: ({ product }: any) => (
188
+ <div data-testid="product-view">
189
+ <div data-testid="product-name">{product?.name}</div>
190
+ </div>
191
+ ),
192
+ }));
193
+
194
+ // Test route component...
195
+ ```
196
+
197
+ ## Testing Recommendations
198
+
199
+ ### SEO Crawler Emulation
200
+
201
+ - Use **Googlebot user agent** in network conditions to emulate SEO crawler behavior
202
+ - This changes React Router's streaming strategy and shows what crawlers see for SSR
203
+ - Helps verify server-side rendering works correctly for search engines
204
+
205
+ ### Coverage Goals
206
+
207
+ - Maintain coverage above thresholds defined in `vitest.thresholds.ts`
208
+ - Raise thresholds regularly as coverage improves
209
+ - Focus on testing critical paths and user-facing functionality
210
+
211
+ ### Test Organization
212
+
213
+ ```
214
+ src/
215
+ ├── components/
216
+ │ ├── product-card/
217
+ │ │ ├── index.tsx # Component
218
+ │ │ ├── index.test.tsx # Unit tests
219
+ │ │ └── index.stories.tsx # Storybook stories
220
+ ├── routes/
221
+ │ ├── _app.product.$productId.tsx
222
+ │ └── _app.product.$productId.test.tsx
223
+ └── test-utils/ # Shared test utilities
224
+ ├── config.ts
225
+ └── context-provider-utils.ts
226
+ ```
227
+
228
+ ## References
229
+
230
+ - **README-TESTS.md** - Complete testing documentation
231
+ - **.storybook/README-STORYBOOK.md** - Storybook setup and usage guide
232
+ - **vitest.thresholds.ts** - Coverage threshold definitions
@@ -0,0 +1,110 @@
1
+ import { BaseCommand } from '@salesforce/b2c-tooling-sdk/cli';
2
+ import type { ResolvedB2CConfig } from '@salesforce/b2c-tooling-sdk/config';
3
+ /**
4
+ * oclif Command that starts the B2C DX MCP server.
5
+ *
6
+ * Uses oclif's single-command strategy - this IS the CLI, not a subcommand.
7
+ * Extends BaseCommand from @salesforce/b2c-tooling-sdk which provides:
8
+ * - Global flags for config, logging, and debugging
9
+ * - Structured pino logging via `this.logger`
10
+ * - Automatic dw.json loading via `this.resolvedConfig`
11
+ * - Automatic telemetry initialization via `this.telemetry`
12
+ * - `this.config` - package.json metadata and standard config paths
13
+ */
14
+ export default class McpServerCommand extends BaseCommand<typeof McpServerCommand> {
15
+ static description: string;
16
+ static examples: {
17
+ description: string;
18
+ command: string;
19
+ }[];
20
+ static flags: {
21
+ toolsets: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ tools: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ 'allow-non-ga-tools': import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
+ server: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
+ 'webdav-server': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
26
+ 'code-version': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
27
+ username: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
28
+ password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
29
+ certificate: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
30
+ passphrase: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
31
+ selfsigned: import("@oclif/core/interfaces").BooleanFlag<boolean>;
32
+ verify: import("@oclif/core/interfaces").BooleanFlag<boolean>;
33
+ 'client-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
34
+ 'client-secret': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
35
+ scope: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
36
+ 'short-code': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
37
+ 'tenant-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
38
+ 'auth-methods': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
39
+ 'user-auth': import("@oclif/core/interfaces").BooleanFlag<boolean>;
40
+ 'account-manager-host': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
41
+ 'log-level': import("@oclif/core/interfaces").OptionFlag<"trace" | "debug" | "info" | "warn" | "error" | "silent" | undefined, import("@oclif/core/interfaces").CustomOptions>;
42
+ debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
43
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
44
+ lang: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
45
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
46
+ instance: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
47
+ 'working-directory': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
48
+ 'extra-query': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
49
+ 'extra-body': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
50
+ 'extra-headers': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
51
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
52
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
53
+ environment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
54
+ 'cloud-origin': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
55
+ 'credentials-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
56
+ };
57
+ /** Signal that triggered shutdown (if any) - used to exit process after finally() */
58
+ private shutdownSignal?;
59
+ /** Promise that resolves when stdin closes (MCP client disconnects) */
60
+ private stdinClosePromise?;
61
+ /**
62
+ * Override finally() to wait for stdin close before stopping telemetry.
63
+ * This ensures SERVER_STOPPED is sent before telemetry.stop() is called.
64
+ */
65
+ protected finally(err: Error | undefined): Promise<void>;
66
+ /**
67
+ * Loads configuration from flags, environment variables, and config files.
68
+ *
69
+ * Combines configuration from both InstanceCommand (B2C instance) and MrtCommand (MRT)
70
+ * since this command supports both B2C instance tools and MRT tools.
71
+ *
72
+ * Uses SDK helper functions for flag extraction:
73
+ * - extractInstanceFlags() - B2C instance flags (--server, --username, etc.)
74
+ * - extractMrtFlags() - MRT flags (--api-key, --project, etc.) and loading options
75
+ *
76
+ * Priority (highest to lowest):
77
+ * 1. CLI flags (--server, --username, --api-key, etc.)
78
+ * 2. Environment variables (SFCC_SERVER, SFCC_USERNAME, SFCC_MRT_API_KEY, etc.)
79
+ * 3. dw.json file (via --config flag or auto-discovered from --working-directory)
80
+ * 4. ~/.mobify file (for MRT API key)
81
+ */
82
+ protected loadConfiguration(): ResolvedB2CConfig;
83
+ /**
84
+ * Main entry point - starts the MCP server.
85
+ *
86
+ * Execution flow:
87
+ * 1. BaseCommand.init() parses flags, loads config, and initializes telemetry
88
+ * 2. Filter and validate toolsets (invalid ones are skipped with warning)
89
+ * 3. Create B2CDxMcpServer instance with telemetry from BaseCommand
90
+ * 4. Create Services via Services.fromResolvedConfig() using already-resolved config
91
+ * 5. Register tools based on --toolsets and --tools flags
92
+ * 6. Connect to stdio transport (JSON-RPC over stdin/stdout)
93
+ * 7. Log startup message via structured logger
94
+ *
95
+ * @throws Never throws - invalid toolsets are filtered, not rejected
96
+ *
97
+ * BaseCommand provides:
98
+ * - `this.flags` - Parsed flags including global flags (config, debug, log-level, etc.)
99
+ * - `this.resolvedConfig` - Loaded dw.json configuration
100
+ * - `this.logger` - Structured pino logger
101
+ * - `this.telemetry` - Telemetry instance (auto-initialized from package.json config)
102
+ *
103
+ * oclif provides standard config paths via `this.config`:
104
+ * - `this.config.configDir` - User config (~/.config/b2c-dx-mcp)
105
+ * - `this.config.dataDir` - User data (~/.local/share/b2c-dx-mcp)
106
+ * - `this.config.cacheDir` - Cache (~/.cache/b2c-dx-mcp)
107
+ * These can be exposed to Services if needed for features like telemetry or caching.
108
+ */
109
+ run(): Promise<void>;
110
+ }
@@ -0,0 +1,333 @@
1
+ /*
2
+ * Copyright (c) 2025, Salesforce, Inc.
3
+ * SPDX-License-Identifier: Apache-2
4
+ * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5
+ */
6
+ /**
7
+ * MCP Server Command - Salesforce B2C Commerce Developer Experience
8
+ *
9
+ * This is the main entry point for the B2C DX MCP server, built with oclif.
10
+ * It exposes B2C Commerce Cloud developer tools to AI assistants via the
11
+ * Model Context Protocol (MCP).
12
+ *
13
+ * ## Flags
14
+ *
15
+ * ### MCP-Specific Flags
16
+ * | Flag | Env Variable | Description |
17
+ * |------|--------------|-------------|
18
+ * | `--toolsets` | `SFCC_TOOLSETS` | Comma-separated toolsets to enable (case-insensitive) |
19
+ * | `--tools` | `SFCC_TOOLS` | Comma-separated individual tools to enable (case-insensitive) |
20
+ * | `--allow-non-ga-tools` | `SFCC_ALLOW_NON_GA_TOOLS` | Enable experimental/non-GA tools |
21
+ *
22
+ * ### Environment Variables for Telemetry
23
+ * | Env Variable | Description |
24
+ * |--------------|-------------|
25
+ * | `SF_DISABLE_TELEMETRY` | Set to `true` to disable telemetry (sf CLI standard) |
26
+ * | `SFCC_DISABLE_TELEMETRY` | Set to `true` to disable telemetry |
27
+ * | `SFCC_APP_INSIGHTS_KEY` | Override connection string from package.json |
28
+ *
29
+ * ### MRT Flags (from MrtCommand.baseFlags)
30
+ * | Flag | Env Variable | Description |
31
+ * |------|--------------|-------------|
32
+ * | `--api-key` | `SFCC_MRT_API_KEY` | MRT API key for Managed Runtime operations |
33
+ * | `--project` | `SFCC_MRT_PROJECT` | MRT project slug (required for MRT tools) |
34
+ * | `--environment` | `SFCC_MRT_ENVIRONMENT` | MRT environment (e.g., staging, production) |
35
+ * | `--cloud-origin` | `SFCC_MRT_CLOUD_ORIGIN` | MRT cloud origin URL for environment-specific ~/.mobify config |
36
+ *
37
+ * ### B2C Instance Flags (from InstanceCommand.baseFlags)
38
+ * | Flag | Env Variable | Description |
39
+ * |------|--------------|-------------|
40
+ * | `--server` | `SFCC_SERVER` | B2C instance hostname |
41
+ * | `--code-version` | `SFCC_CODE_VERSION` | Code version for deployments |
42
+ * | `--username` | `SFCC_USERNAME` | Username for Basic auth (WebDAV) |
43
+ * | `--password` | `SFCC_PASSWORD` | Password/access key for Basic auth |
44
+ * | `--client-id` | `SFCC_CLIENT_ID` | OAuth client ID |
45
+ * | `--client-secret` | `SFCC_CLIENT_SECRET` | OAuth client secret |
46
+ *
47
+ * ### Global Flags (inherited from BaseCommand)
48
+ * | Flag | Env Variable | Description |
49
+ * |------|--------------|-------------|
50
+ * | `--working-directory` | `SFCC_WORKING_DIRECTORY` | Project working directory (see note below) |
51
+ * | `--config` | `SFCC_CONFIG` | Path to dw.json config file (auto-discovered if not provided) |
52
+ * | `--instance` | `SFCC_INSTANCE` | Instance name from configuration file |
53
+ * | `--log-level` | `SFCC_LOG_LEVEL` | Set logging verbosity (trace, debug, info, warn, error, silent) |
54
+ * | `--debug` | `SFCC_DEBUG` | Enable debug logging |
55
+ * | `--json` | - | Output logs as JSON lines |
56
+ * | `--lang` | - | Language for messages |
57
+ *
58
+ * **Note on `--working-directory`**: Many MCP clients (Cursor, Claude Desktop) spawn servers from the
59
+ * user's home directory (`~`) rather than the project directory. This flag is used for:
60
+ * - Auto-discovery (detecting project type when no `--toolsets` or `--tools` are provided)
61
+ * - Scaffolding tools (creating files in the correct project location)
62
+ * - Any tool that needs to operate on the project directory
63
+ *
64
+ * Use `--working-directory` or `SFCC_WORKING_DIRECTORY` env var to specify the actual project path.
65
+ *
66
+ * ## Configuration
67
+ *
68
+ * Different tools require different configuration:
69
+ * - **MRT tools** (e.g., `mrt_bundle_push`) → MRT flags (--project, --api-key)
70
+ * - **B2C instance tools** (e.g., `cartridge_deploy`, SCAPI) → Instance flags or dw.json
71
+ * - **Local tools** (e.g., scaffolding) → None
72
+ *
73
+ * ### B2C Instance Configuration
74
+ * Priority (highest to lowest):
75
+ * 1. Flags (`--server`, `--username`, `--password`, `--client-id`, `--client-secret`, `--code-version`)
76
+ * 2. Environment variables (via oclif flag env support)
77
+ * 3. dw.json file (via `--config` flag or auto-discovered)
78
+ *
79
+ * ### MRT API Key
80
+ * Priority (highest to lowest):
81
+ * 1. `--api-key` flag
82
+ * 2. `SFCC_MRT_API_KEY` environment variable
83
+ * 3. `~/.mobify` config file (or `~/.mobify--[hostname]` if `--cloud-origin` is set)
84
+ *
85
+ * ## Toolset Validation
86
+ *
87
+ * - Invalid toolsets are ignored with a warning (server still starts)
88
+ * - If all toolsets are invalid, auto-discovery kicks in
89
+ *
90
+ * @example mcp.json - All toolsets
91
+ * ```json
92
+ * { "args": ["--toolsets", "all", "--allow-non-ga-tools"] }
93
+ * ```
94
+ *
95
+ * @example mcp.json - Specific toolsets
96
+ * ```json
97
+ * { "args": ["--toolsets", "CARTRIDGES,MRT", "--allow-non-ga-tools"] }
98
+ * ```
99
+ *
100
+ * @example mcp.json - MRT tools with project, environment, and API key
101
+ * ```json
102
+ * {
103
+ * "args": ["--toolsets", "MRT", "--project", "my-project", "--environment", "staging", "--allow-non-ga-tools"],
104
+ * "env": { "SFCC_MRT_API_KEY": "your-api-key" }
105
+ * }
106
+ * ```
107
+ *
108
+ * @example mcp.json - MRT tools with staging cloud origin (uses ~/.mobify--cloud-staging.mobify.com)
109
+ * ```json
110
+ * { "args": ["--toolsets", "MRT", "--project", "my-project", "--cloud-origin", "https://cloud-staging.mobify.com", "--allow-non-ga-tools"] }
111
+ * ```
112
+ *
113
+ * @example mcp.json - Cartridge tools with dw.json config
114
+ * ```json
115
+ * { "args": ["--toolsets", "CARTRIDGES", "--config", "/path/to/dw.json", "--allow-non-ga-tools"] }
116
+ * ```
117
+ *
118
+ * @example mcp.json - Cartridge tools with env vars
119
+ * ```json
120
+ * {
121
+ * "args": ["--toolsets", "CARTRIDGES", "--allow-non-ga-tools"],
122
+ * "env": {
123
+ * "SFCC_HOSTNAME": "your-sandbox.demandware.net",
124
+ * "SFCC_CLIENT_ID": "your-client-id",
125
+ * "SFCC_CLIENT_SECRET": "your-client-secret"
126
+ * }
127
+ * }
128
+ * ```
129
+ *
130
+ * @example mcp.json - Enable debug logging
131
+ * ```json
132
+ * { "args": ["--toolsets", "all", "--allow-non-ga-tools", "--debug"] }
133
+ * ```
134
+ */
135
+ import { Flags } from '@oclif/core';
136
+ import { BaseCommand, MrtCommand, InstanceCommand, loadConfig, extractInstanceFlags, extractMrtFlags, } from '@salesforce/b2c-tooling-sdk/cli';
137
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
138
+ import { B2CDxMcpServer } from '../server.js';
139
+ import { Services } from '../services.js';
140
+ import { registerToolsets } from '../registry.js';
141
+ import { TOOLSETS } from '../utils/index.js';
142
+ /**
143
+ * oclif Command that starts the B2C DX MCP server.
144
+ *
145
+ * Uses oclif's single-command strategy - this IS the CLI, not a subcommand.
146
+ * Extends BaseCommand from @salesforce/b2c-tooling-sdk which provides:
147
+ * - Global flags for config, logging, and debugging
148
+ * - Structured pino logging via `this.logger`
149
+ * - Automatic dw.json loading via `this.resolvedConfig`
150
+ * - Automatic telemetry initialization via `this.telemetry`
151
+ * - `this.config` - package.json metadata and standard config paths
152
+ */
153
+ export default class McpServerCommand extends BaseCommand {
154
+ static description = 'Salesforce B2C Commerce Cloud Developer Experience MCP Server - Expose B2C Commerce Developer Experience tools to AI assistants';
155
+ static examples = [
156
+ {
157
+ description: 'All toolsets',
158
+ command: '<%= config.bin %> --toolsets all --allow-non-ga-tools',
159
+ },
160
+ {
161
+ description: 'MRT tools with project and API key',
162
+ command: '<%= config.bin %> --toolsets MRT --project my-project --api-key your-api-key --allow-non-ga-tools',
163
+ },
164
+ {
165
+ description: 'MRT tools with project, environment, and API key',
166
+ command: '<%= config.bin %> --toolsets MRT --project my-project --environment staging --api-key your-api-key --allow-non-ga-tools',
167
+ },
168
+ {
169
+ description: 'Cartridge tools with explicit config',
170
+ command: '<%= config.bin %> --toolsets CARTRIDGES --config /path/to/dw.json --allow-non-ga-tools',
171
+ },
172
+ {
173
+ description: 'Debug logging',
174
+ command: '<%= config.bin %> --toolsets all --allow-non-ga-tools --debug',
175
+ },
176
+ ];
177
+ static flags = {
178
+ // Inherit MRT flags (api-key, cloud-origin, project, environment)
179
+ // Also includes BaseCommand flags (config, debug, log-level, etc.) - safe to re-spread
180
+ ...MrtCommand.baseFlags,
181
+ // Inherit Instance flags (server, code-version, username, password, client-id, client-secret)
182
+ // These provide B2C instance configuration for tools like cartridge_deploy
183
+ ...InstanceCommand.baseFlags,
184
+ // MCP-specific toolset selection flags
185
+ toolsets: Flags.string({
186
+ description: `Toolsets to enable (comma-separated). Options: all, ${TOOLSETS.join(', ')}`,
187
+ env: 'SFCC_TOOLSETS',
188
+ parse: async (input) => input.toUpperCase(),
189
+ }),
190
+ tools: Flags.string({
191
+ description: 'Individual tools to enable (comma-separated)',
192
+ env: 'SFCC_TOOLS',
193
+ parse: async (input) => input.toLowerCase(),
194
+ }),
195
+ // Feature flags
196
+ 'allow-non-ga-tools': Flags.boolean({
197
+ description: 'Enable non-GA (experimental) tools',
198
+ env: 'SFCC_ALLOW_NON_GA_TOOLS',
199
+ default: false,
200
+ }),
201
+ };
202
+ /** Signal that triggered shutdown (if any) - used to exit process after finally() */
203
+ shutdownSignal;
204
+ /** Promise that resolves when stdin closes (MCP client disconnects) */
205
+ stdinClosePromise;
206
+ /**
207
+ * Override finally() to wait for stdin close before stopping telemetry.
208
+ * This ensures SERVER_STOPPED is sent before telemetry.stop() is called.
209
+ */
210
+ async finally(err) {
211
+ // Wait for stdin to close and SERVER_STOPPED to be sent
212
+ // This keeps the command "running" until the MCP client disconnects
213
+ await this.stdinClosePromise;
214
+ // Now call super.finally() which sends COMMAND_SUCCESS and stops telemetry
215
+ await super.finally(err);
216
+ // Exit process if shutdown was triggered by a signal (SIGINT/SIGTERM)
217
+ // Catching these signals prevents Node's default exit behavior
218
+ if (this.shutdownSignal === 'SIGINT' || this.shutdownSignal === 'SIGTERM') {
219
+ // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
220
+ process.exit(0);
221
+ }
222
+ }
223
+ /**
224
+ * Loads configuration from flags, environment variables, and config files.
225
+ *
226
+ * Combines configuration from both InstanceCommand (B2C instance) and MrtCommand (MRT)
227
+ * since this command supports both B2C instance tools and MRT tools.
228
+ *
229
+ * Uses SDK helper functions for flag extraction:
230
+ * - extractInstanceFlags() - B2C instance flags (--server, --username, etc.)
231
+ * - extractMrtFlags() - MRT flags (--api-key, --project, etc.) and loading options
232
+ *
233
+ * Priority (highest to lowest):
234
+ * 1. CLI flags (--server, --username, --api-key, etc.)
235
+ * 2. Environment variables (SFCC_SERVER, SFCC_USERNAME, SFCC_MRT_API_KEY, etc.)
236
+ * 3. dw.json file (via --config flag or auto-discovered from --working-directory)
237
+ * 4. ~/.mobify file (for MRT API key)
238
+ */
239
+ loadConfiguration() {
240
+ const mrt = extractMrtFlags(this.flags);
241
+ const options = {
242
+ ...this.getBaseConfigOptions(),
243
+ ...mrt.options,
244
+ };
245
+ // Combine B2C instance flags and MRT config flags
246
+ const flagConfig = {
247
+ ...extractInstanceFlags(this.flags),
248
+ ...mrt.config,
249
+ };
250
+ return loadConfig(flagConfig, options);
251
+ }
252
+ /**
253
+ * Main entry point - starts the MCP server.
254
+ *
255
+ * Execution flow:
256
+ * 1. BaseCommand.init() parses flags, loads config, and initializes telemetry
257
+ * 2. Filter and validate toolsets (invalid ones are skipped with warning)
258
+ * 3. Create B2CDxMcpServer instance with telemetry from BaseCommand
259
+ * 4. Create Services via Services.fromResolvedConfig() using already-resolved config
260
+ * 5. Register tools based on --toolsets and --tools flags
261
+ * 6. Connect to stdio transport (JSON-RPC over stdin/stdout)
262
+ * 7. Log startup message via structured logger
263
+ *
264
+ * @throws Never throws - invalid toolsets are filtered, not rejected
265
+ *
266
+ * BaseCommand provides:
267
+ * - `this.flags` - Parsed flags including global flags (config, debug, log-level, etc.)
268
+ * - `this.resolvedConfig` - Loaded dw.json configuration
269
+ * - `this.logger` - Structured pino logger
270
+ * - `this.telemetry` - Telemetry instance (auto-initialized from package.json config)
271
+ *
272
+ * oclif provides standard config paths via `this.config`:
273
+ * - `this.config.configDir` - User config (~/.config/b2c-dx-mcp)
274
+ * - `this.config.dataDir` - User data (~/.local/share/b2c-dx-mcp)
275
+ * - `this.config.cacheDir` - Cache (~/.cache/b2c-dx-mcp)
276
+ * These can be exposed to Services if needed for features like telemetry or caching.
277
+ */
278
+ async run() {
279
+ // Flags are already parsed by BaseCommand.init()
280
+ // Parse toolsets and tools from comma-separated strings
281
+ // Note: toolsets are uppercased, tools are lowercased by their parse functions
282
+ const startupFlags = {
283
+ toolsets: this.flags.toolsets ? this.flags.toolsets.split(',').map((s) => s.trim()) : undefined,
284
+ tools: this.flags.tools ? this.flags.tools.split(',').map((s) => s.trim()) : undefined,
285
+ allowNonGaTools: this.flags['allow-non-ga-tools'],
286
+ configPath: this.flags.config,
287
+ // Working directory for auto-discovery. oclif handles flag with env fallback.
288
+ workingDirectory: this.flags['working-directory'],
289
+ };
290
+ // Add toolsets to telemetry attributes
291
+ if (this.telemetry && startupFlags.toolsets) {
292
+ this.telemetry.addAttributes({ toolsets: startupFlags.toolsets.join(', ') });
293
+ }
294
+ // Create MCP server with telemetry from BaseCommand
295
+ const server = new B2CDxMcpServer({
296
+ name: this.config.name,
297
+ version: this.config.version,
298
+ }, {
299
+ capabilities: {
300
+ resources: {},
301
+ tools: {},
302
+ },
303
+ telemetry: this.telemetry,
304
+ });
305
+ // Create services from already-resolved config (BaseCommand.init() already resolved it)
306
+ const services = Services.fromResolvedConfig(this.resolvedConfig);
307
+ // Register toolsets
308
+ await registerToolsets(startupFlags, server, services);
309
+ // Connect to stdio transport
310
+ const transport = new StdioServerTransport();
311
+ await server.connect(transport);
312
+ // Create promise that resolves when server stops (stdin close or signal)
313
+ // This allows finally() to wait for SERVER_STOPPED before stopping telemetry
314
+ this.stdinClosePromise = new Promise((resolve) => {
315
+ const sendStopAndResolve = (signal) => {
316
+ this.shutdownSignal = signal;
317
+ this.telemetry?.sendEvent('SERVER_STOPPED', { signal });
318
+ // Flush telemetry before resolving to ensure SERVER_STOPPED is sent
319
+ // before finally() proceeds to stop telemetry
320
+ const flushPromise = this.telemetry?.flush() ?? Promise.resolve();
321
+ flushPromise.then(() => resolve()).catch(() => resolve());
322
+ };
323
+ // Handle stdin close (MCP client disconnects normally)
324
+ process.stdin.on('close', () => sendStopAndResolve('stdin_close'));
325
+ // Handle Ctrl+C
326
+ process.on('SIGINT', () => sendStopAndResolve('SIGINT'));
327
+ // Handle kill signal
328
+ process.on('SIGTERM', () => sendStopAndResolve('SIGTERM'));
329
+ });
330
+ this.logger.info({ version: this.config.version }, 'MCP Server running on stdio');
331
+ }
332
+ }
333
+ //# sourceMappingURL=mcp.js.map