diff --git a/packages/b2c-cli/.prettierrc.json b/.prettierrc.json similarity index 100% rename from packages/b2c-cli/.prettierrc.json rename to .prettierrc.json diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..79353dbe --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 + */ + +/** + * Shared ESLint configuration for the b2c-cli monorepo. + * Packages import from this file to maintain consistency. + */ + +import prettierPlugin from 'eslint-plugin-prettier/recommended'; + +/** + * The standard copyright header block used across all packages. + */ +export const copyrightHeader = [ + '', + ' * Copyright (c) 2025, Salesforce, Inc.', + ' * SPDX-License-Identifier: Apache-2', + ' * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0', + ' ', +]; + +/** + * Shared rules used across all packages. + */ +export const sharedRules = { + // Allow underscore-prefixed unused variables (common convention for intentionally unused params) + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + // Disable new-cap - incompatible with openapi-fetch (uses GET, POST, etc. methods) + 'new-cap': 'off', +}; + +/** + * Rules for packages using eslint-config-oclif. + * Disables perfectionist and stylistic rules that conflict with our style. + */ +export const oclifRules = { + // Disable perfectionist rules - we use prettier for formatting + 'perfectionist/sort-imports': 'off', + 'perfectionist/sort-objects': 'off', + 'perfectionist/sort-object-types': 'off', + 'perfectionist/sort-interfaces': 'off', + 'perfectionist/sort-named-exports': 'off', + 'perfectionist/sort-named-imports': 'off', + // Disable stylistic rules that conflict with our style + '@stylistic/lines-between-class-members': 'off', + '@stylistic/padding-line-between-statements': 'off', + // Allow TODO comments + 'no-warning-comments': 'off', + // Don't require destructuring + 'prefer-destructuring': 'off', +}; + +/** + * Rules for test files using Chai assertions. + */ +export const chaiTestRules = { + // Allow Chai property-based assertions in test files (e.g., expect(x).to.be.true) + '@typescript-eslint/no-unused-expressions': 'off', +}; + +/** + * Re-export prettier plugin for convenience. + */ +export {prettierPlugin}; diff --git a/package.json b/package.json index 1e746080..cea5de09 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "license": "Apache-2.0", "packageManager": "pnpm@10.17.1", "devDependencies": { + "eslint-plugin-prettier": "^5.5.4", + "prettier": "^3.6.2", "typedoc": "^0.28.14", "typedoc-plugin-markdown": "^4.9.0", "typedoc-vitepress-theme": "^1.1.2", diff --git a/packages/b2c-cli/bin/dev.js b/packages/b2c-cli/bin/dev.js index 5163dbf2..e383e090 100755 --- a/packages/b2c-cli/bin/dev.js +++ b/packages/b2c-cli/bin/dev.js @@ -1,4 +1,4 @@ -#!/usr/bin/env -S node --conditions development --import tsx +#!/usr/bin/env -S node --conditions development /* * Copyright (c) 2025, Salesforce, Inc. * SPDX-License-Identifier: Apache-2 diff --git a/packages/b2c-cli/eslint.config.mjs b/packages/b2c-cli/eslint.config.mjs index 6381da13..383bd079 100644 --- a/packages/b2c-cli/eslint.config.mjs +++ b/packages/b2c-cli/eslint.config.mjs @@ -5,12 +5,14 @@ */ import {includeIgnoreFile} from '@eslint/compat'; import oclif from 'eslint-config-oclif'; -import prettierPlugin from 'eslint-plugin-prettier/recommended'; -import headerPlugin from '@tony.ganchev/eslint-plugin-header'; +import headerPlugin from 'eslint-plugin-header'; import path from 'node:path'; import {fileURLToPath} from 'node:url'; +import {copyrightHeader, sharedRules, oclifRules, prettierPlugin} from '../../eslint.config.mjs'; + const gitignorePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '.gitignore'); +headerPlugin.rules.header.meta.schema = false; export default [ includeIgnoreFile(gitignorePath), @@ -24,41 +26,9 @@ export default [ header: headerPlugin, }, rules: { - 'header/header': [ - 'error', - 'block', - [ - '', - ' * Copyright (c) 2025, Salesforce, Inc.', - ' * SPDX-License-Identifier: Apache-2', - ' * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0', - ' ', - ], - ], - // Disable perfectionist rules - we use prettier for formatting - 'perfectionist/sort-imports': 'off', - 'perfectionist/sort-objects': 'off', - 'perfectionist/sort-object-types': 'off', - 'perfectionist/sort-interfaces': 'off', - 'perfectionist/sort-named-exports': 'off', - 'perfectionist/sort-named-imports': 'off', - // Disable stylistic rules that conflict with our style - '@stylistic/lines-between-class-members': 'off', - '@stylistic/padding-line-between-statements': 'off', - // Allow TODO comments - 'no-warning-comments': 'off', - // Don't require destructuring - 'prefer-destructuring': 'off', - // Disable new-cap - incompatible with openapi-fetch (uses GET, POST, etc. methods) - 'new-cap': 'off', - // Allow underscore-prefixed unused variables - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - }, - ], + 'header/header': ['error', 'block', copyrightHeader], + ...sharedRules, + ...oclifRules, }, }, ]; diff --git a/packages/b2c-cli/package.json b/packages/b2c-cli/package.json index 7b485d95..47a267db 100644 --- a/packages/b2c-cli/package.json +++ b/packages/b2c-cli/package.json @@ -22,7 +22,7 @@ "@oclif/prettier-config": "^0.2.1", "@oclif/test": "^4", "@salesforce/dev-config": "^4.3.2", - "@tony.ganchev/eslint-plugin-header": "^3.1.11", + "eslint-plugin-header": "^3.1.1", "@types/chai": "^4", "@types/mocha": "^10", "@types/node": "^18", diff --git a/packages/b2c-cli/src/commands/_test/index.ts b/packages/b2c-cli/src/commands/_test/index.ts index b4088c79..3b565a6d 100644 --- a/packages/b2c-cli/src/commands/_test/index.ts +++ b/packages/b2c-cli/src/commands/_test/index.ts @@ -11,6 +11,7 @@ export default class Test extends BaseCommand { async run(): Promise { // Test this.log() which now uses pino + this.baseCommandTest(); this.log('Using this.log() - goes through pino'); this.warn('Using this.warn() - goes through pino'); diff --git a/packages/b2c-dx-mcp/.gitignore b/packages/b2c-dx-mcp/.gitignore new file mode 100644 index 00000000..0fd5a82f --- /dev/null +++ b/packages/b2c-dx-mcp/.gitignore @@ -0,0 +1,19 @@ +*-debug.log +*-error.log +**/.DS_Store +/.idea +/dist +/build +/tmp +/node_modules +/coverage +test-results.json +oclif.manifest.json + + +yarn.lock +package-lock.json + +dw.json + +export/ diff --git a/packages/b2c-dx-mcp/bin/dev.js b/packages/b2c-dx-mcp/bin/dev.js old mode 100644 new mode 100755 index 5aae5d2e..e383e090 --- a/packages/b2c-dx-mcp/bin/dev.js +++ b/packages/b2c-dx-mcp/bin/dev.js @@ -1,23 +1,9 @@ -#!/usr/bin/env -S node --conditions development --import tsx +#!/usr/bin/env -S node --conditions development /* * Copyright (c) 2025, Salesforce, Inc. * SPDX-License-Identifier: Apache-2 * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ -/* global process */ - -/** - * Development entry point for MCP server using oclif. - * - * This uses oclif's development mode which: - * - Uses TypeScript source directly (via tsx loader in shebang) - * - Supports the 'development' condition for exports - * - Loads .env file if present for local configuration - * - Provides better error messages and stack traces - * - * Run directly: ./bin/dev.js mcp --toolsets all - * Or with node: node --conditions development --import tsx bin/dev.js mcp --toolsets all - */ // Load .env file if present (Node.js native support) try { diff --git a/packages/b2c-dx-mcp/eslint.config.mjs b/packages/b2c-dx-mcp/eslint.config.mjs index 5e68491f..273b5c4a 100644 --- a/packages/b2c-dx-mcp/eslint.config.mjs +++ b/packages/b2c-dx-mcp/eslint.config.mjs @@ -1,42 +1,31 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ +import {includeIgnoreFile} from '@eslint/compat'; +import oclif from 'eslint-config-oclif'; +import headerPlugin from 'eslint-plugin-header'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; -import prettierConfig from 'eslint-config-prettier'; +import {copyrightHeader, sharedRules, oclifRules, prettierPlugin} from '../../eslint.config.mjs'; -export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommended, - prettierConfig, - { - ignores: ['dist/**', 'node_modules/**'], - }, +const gitignorePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '.gitignore'); +headerPlugin.rules.header.meta.schema = false; + +export default [ + includeIgnoreFile(gitignorePath), + ...oclif, + prettierPlugin, { - rules: { - '@typescript-eslint/no-unused-vars': ['error', {argsIgnorePattern: '^_'}], - '@typescript-eslint/no-explicit-any': 'warn', + plugins: { + header: headerPlugin, }, - }, - // Allow chai assertion expressions in test files - { - files: ['test/**/*.ts'], rules: { - '@typescript-eslint/no-unused-expressions': 'off', + 'header/header': ['error', 'block', copyrightHeader], + ...sharedRules, + ...oclifRules, }, }, -); - +]; diff --git a/packages/b2c-dx-mcp/package.json b/packages/b2c-dx-mcp/package.json index 63915bf4..ea2c474b 100644 --- a/packages/b2c-dx-mcp/package.json +++ b/packages/b2c-dx-mcp/package.json @@ -59,7 +59,7 @@ } }, "scripts": { - "build": "tsc", + "build": "shx rm -rf dist && tsc -p tsconfig.build.json", "clean": "shx rm -rf dist", "lint": "eslint", "format": "prettier --write src", @@ -90,6 +90,8 @@ "@types/node": "^22.16.5", "chai": "^4", "eslint": "^9", + "eslint-config-oclif": "^6", + "eslint-plugin-header": "^3.1.1", "eslint-config-prettier": "^10", "eslint-plugin-prettier": "^5.5.4", "mocha": "^10", diff --git a/packages/b2c-dx-mcp/src/commands/mcp.ts b/packages/b2c-dx-mcp/src/commands/mcp.ts index e0e4962c..69027e58 100644 --- a/packages/b2c-dx-mcp/src/commands/mcp.ts +++ b/packages/b2c-dx-mcp/src/commands/mcp.ts @@ -1,17 +1,7 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ /** @@ -82,13 +72,13 @@ * ``` */ -import { Flags } from "@oclif/core"; -import { BaseCommand } from "@salesforce/b2c-tooling-sdk/cli"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { B2CDxMcpServer } from "../server.js"; -import { Services } from "../services.js"; -import { registerToolsets } from "../registry.js"; -import { TOOLSETS, type StartupFlags } from "../utils/index.js"; +import {Flags} from '@oclif/core'; +import {BaseCommand} from '@salesforce/b2c-tooling-sdk/cli'; +import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js'; +import {B2CDxMcpServer} from '../server.js'; +import {Services} from '../services.js'; +import {registerToolsets} from '../registry.js'; +import {TOOLSETS, type StartupFlags} from '../utils/index.js'; /** * oclif Command that starts the B2C DX MCP server. @@ -100,38 +90,36 @@ import { TOOLSETS, type StartupFlags } from "../utils/index.js"; * - Automatic dw.json loading via `this.resolvedConfig` * - `this.config` - package.json metadata and standard config paths */ -export default class McpServerCommand extends BaseCommand< - typeof McpServerCommand -> { +export default class McpServerCommand extends BaseCommand { static description = - "Salesforce B2C Commerce Cloud Developer Experience MCP Server - Expose B2C Commerce Developer Experience tools to AI assistants"; + 'Salesforce B2C Commerce Cloud Developer Experience MCP Server - Expose B2C Commerce Developer Experience tools to AI assistants'; static examples = [ - "<%= config.bin %> <%= command.id %> --toolsets all", - "<%= config.bin %> <%= command.id %> --toolsets STOREFRONTNEXT,MRT", - "<%= config.bin %> <%= command.id %> --tools sfnext_deploy,mrt_bundle_push", - "<%= config.bin %> <%= command.id %> --toolsets STOREFRONTNEXT --tools sfnext_deploy", - "<%= config.bin %> <%= command.id %> --toolsets MRT --config /path/to/dw.json", - "<%= config.bin %> <%= command.id %> --toolsets all --debug", + '<%= config.bin %> <%= command.id %> --toolsets all', + '<%= config.bin %> <%= command.id %> --toolsets STOREFRONTNEXT,MRT', + '<%= config.bin %> <%= command.id %> --tools sfnext_deploy,mrt_bundle_push', + '<%= config.bin %> <%= command.id %> --toolsets STOREFRONTNEXT --tools sfnext_deploy', + '<%= config.bin %> <%= command.id %> --toolsets MRT --config /path/to/dw.json', + '<%= config.bin %> <%= command.id %> --toolsets all --debug', ]; static flags = { // Toolset selection flags toolsets: Flags.string({ - description: `Toolsets to enable (comma-separated). Options: all, ${TOOLSETS.join(", ")}`, - env: "SFCC_TOOLSETS", + description: `Toolsets to enable (comma-separated). Options: all, ${TOOLSETS.join(', ')}`, + env: 'SFCC_TOOLSETS', parse: async (input) => input.toUpperCase(), }), tools: Flags.string({ - description: "Individual tools to enable (comma-separated)", - env: "SFCC_TOOLS", + description: 'Individual tools to enable (comma-separated)', + env: 'SFCC_TOOLS', parse: async (input) => input.toLowerCase(), }), // Feature flags - "allow-non-ga-tools": Flags.boolean({ - description: "Enable non-GA (experimental) tools", - env: "SFCC_ALLOW_NON_GA_TOOLS", + 'allow-non-ga-tools': Flags.boolean({ + description: 'Enable non-GA (experimental) tools', + env: 'SFCC_ALLOW_NON_GA_TOOLS', default: false, }), }; @@ -150,7 +138,6 @@ export default class McpServerCommand extends BaseCommand< * * @throws Never throws - invalid toolsets are filtered, not rejected * - * @remarks * BaseCommand provides: * - `this.flags` - Parsed flags including global flags (config, debug, log-level, etc.) * - `this.resolvedConfig` - Loaded dw.json configuration @@ -167,13 +154,9 @@ export default class McpServerCommand extends BaseCommand< // Parse toolsets and tools from comma-separated strings // Note: toolsets are uppercased, tools are lowercased by their parse functions const startupFlags: StartupFlags = { - toolsets: this.flags.toolsets - ? this.flags.toolsets.split(",").map((s) => s.trim()) - : undefined, - tools: this.flags.tools - ? this.flags.tools.split(",").map((s) => s.trim()) - : undefined, - allowNonGaTools: this.flags["allow-non-ga-tools"], + toolsets: this.flags.toolsets ? this.flags.toolsets.split(',').map((s) => s.trim()) : undefined, + tools: this.flags.tools ? this.flags.tools.split(',').map((s) => s.trim()) : undefined, + allowNonGaTools: this.flags['allow-non-ga-tools'], configPath: this.flags.config, }; @@ -219,13 +202,7 @@ export default class McpServerCommand extends BaseCommand< await server.connect(transport); // Log startup message using the structured logger - this.logger.info( - { version: this.config.version, toolsets: TOOLSETS }, - "MCP Server running on stdio", - ); - this.logger.info( - { enabled: startupFlags.toolsets ?? [] }, - "Enabled toolsets", - ); + this.logger.info({version: this.config.version, toolsets: TOOLSETS}, 'MCP Server running on stdio'); + this.logger.info({enabled: startupFlags.toolsets ?? []}, 'Enabled toolsets'); } } diff --git a/packages/b2c-dx-mcp/src/registry.ts b/packages/b2c-dx-mcp/src/registry.ts index 2341f3a8..a170853f 100644 --- a/packages/b2c-dx-mcp/src/registry.ts +++ b/packages/b2c-dx-mcp/src/registry.ts @@ -1,29 +1,19 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ -import { EOL } from "node:os"; -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"; -import type { Services } from "./services.js"; -import { createCartridgesTools } from "./tools/cartridges/index.js"; -import { createMrtTools } from "./tools/mrt/index.js"; -import { createPwav3Tools } from "./tools/pwav3/index.js"; -import { createScapiTools } from "./tools/scapi/index.js"; -import { createStorefrontNextTools } from "./tools/storefrontnext/index.js"; +import {EOL} from 'node:os'; +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'; +import type {Services} from './services.js'; +import {createCartridgesTools} from './tools/cartridges/index.js'; +import {createMrtTools} from './tools/mrt/index.js'; +import {createPwav3Tools} from './tools/pwav3/index.js'; +import {createScapiTools} from './tools/scapi/index.js'; +import {createStorefrontNextTools} from './tools/storefrontnext/index.js'; /** * Registry of tools organized by toolset. @@ -82,11 +72,7 @@ export function createToolRegistry(services: Services): ToolRegistry { * @param server - B2CDxMcpServer instance * @param services - Services instance */ -export async function registerToolsets( - flags: StartupFlags, - server: B2CDxMcpServer, - services: Services, -): Promise { +export async function registerToolsets(flags: StartupFlags, server: B2CDxMcpServer, services: Services): Promise { const toolsets = flags.toolsets ?? []; const individualTools = flags.tools ?? []; const allowNonGaTools = flags.allowNonGaTools ?? false; @@ -103,35 +89,28 @@ export async function registerToolsets( const existingToolNames = new Set(allToolsByName.keys()); // Warn about invalid --tools names (but continue with valid ones) - const invalidTools = individualTools.filter( - (name) => !existingToolNames.has(name), - ); + const invalidTools = individualTools.filter((name) => !existingToolNames.has(name)); if (invalidTools.length > 0) { console.error( `⚠️ Ignoring invalid tool name(s): "${invalidTools.join('", "')}"${EOL}` + - ` Valid tools: ${Array.from(existingToolNames).join(", ")}`, + ` Valid tools: ${[...existingToolNames].join(', ')}`, ); } // Validate --toolsets names const invalidToolsets = toolsets.filter( - (t) => - !VALID_TOOLSET_NAMES.includes(t as (typeof VALID_TOOLSET_NAMES)[number]), + (t) => !VALID_TOOLSET_NAMES.includes(t as (typeof VALID_TOOLSET_NAMES)[number]), ); if (invalidToolsets.length > 0) { console.error( `⚠️ Ignoring invalid toolset(s): "${invalidToolsets.join('", "')}"\n` + - ` Valid toolsets: ${VALID_TOOLSET_NAMES.join(", ")}`, + ` Valid toolsets: ${VALID_TOOLSET_NAMES.join(', ')}`, ); } // Determine which toolsets to enable - const validToolsets = toolsets.filter((t): t is Toolset => - TOOLSETS.includes(t as Toolset), - ); - const toolsetsToEnable = new Set( - toolsets.includes(ALL_TOOLSETS) ? TOOLSETS : validToolsets, - ); + const validToolsets = toolsets.filter((t): t is Toolset => TOOLSETS.includes(t as Toolset)); + const toolsetsToEnable = new Set(toolsets.includes(ALL_TOOLSETS) ? TOOLSETS : validToolsets); // Build the set of tools to register: // 1. Start with tools from enabled toolsets @@ -165,11 +144,7 @@ export async function registerToolsets( /** * Register a list of tools with the server. */ -async function registerTools( - tools: McpTool[], - server: B2CDxMcpServer, - allowNonGaTools: boolean, -): Promise { +async function registerTools(tools: McpTool[], server: B2CDxMcpServer, allowNonGaTools: boolean): Promise { for (const tool of tools) { // Skip non-GA tools if not allowed if (tool.isGA === false && !allowNonGaTools) { @@ -178,11 +153,6 @@ async function registerTools( // Register the tool // TODO: Telemetry - Tool registration includes timing/error tracking - server.addTool( - tool.name, - tool.description, - tool.inputSchema, - async (args) => tool.handler(args), - ); + server.addTool(tool.name, tool.description, tool.inputSchema, async (args) => tool.handler(args)); } } diff --git a/packages/b2c-dx-mcp/src/server.ts b/packages/b2c-dx-mcp/src/server.ts index afef66a3..d393dea5 100644 --- a/packages/b2c-dx-mcp/src/server.ts +++ b/packages/b2c-dx-mcp/src/server.ts @@ -1,29 +1,19 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; import type { CallToolResult, Implementation, ServerNotification, ServerRequest, -} from "@modelcontextprotocol/sdk/types.js"; -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"; +} from '@modelcontextprotocol/sdk/types.js'; +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'; /** * Extended server options. @@ -34,7 +24,7 @@ export type B2CDxMcpServerOptions = ServerOptions; * A server implementation that extends the base MCP server. * * - * @extends {McpServer} + * @augments {McpServer} */ export class B2CDxMcpServer extends McpServer { /** @@ -43,10 +33,7 @@ export class B2CDxMcpServer extends McpServer { * @param serverInfo - The server implementation details * @param options - Optional server configuration */ - public constructor( - serverInfo: Implementation, - options?: B2CDxMcpServerOptions, - ) { + public constructor(serverInfo: Implementation, options?: B2CDxMcpServerOptions) { super(serverInfo, options); // Set up oninitialized handler @@ -60,17 +47,6 @@ export class B2CDxMcpServer extends McpServer { }; } - /** - * Connect to a transport. - */ - public override async connect(transport: Transport): Promise { - await super.connect(transport); - if (!this.isConnected()) { - // TODO: Telemetry - Send SERVER_START_ERROR event with "Server not connected" - } - // TODO: Telemetry - wrap with try/catch to send SERVER_START_ERROR event with error details - } - /** * Register a tool with the server. * @@ -93,10 +69,9 @@ export class B2CDxMcpServer extends McpServer { ): Promise => { const startTime = Date.now(); const result = await handler(args); - const runtimeMs = Date.now() - startTime; - - // TODO: Telemetry - Send TOOL_CALLED event with { name, runtimeMs, isError: result.isError } - void runtimeMs; // Silence unused variable warning until telemetry is implemented + // TODO: Telemetry - Send TOOL_CALLED event with { name, _runtimeMs, isError: result.isError } + // @ts-expect-error Ignore unused variable + const _runtimeMs = Date.now() - startTime; return result; }; @@ -104,4 +79,15 @@ export class B2CDxMcpServer extends McpServer { // Use the base server.tool method which handles the registration this.tool(name, description, inputSchema, wrappedHandler); } + + /** + * Connect to a transport. + */ + public override async connect(transport: Transport): Promise { + await super.connect(transport); + if (!this.isConnected()) { + // TODO: Telemetry - Send SERVER_START_ERROR event with "Server not connected" + } + // TODO: Telemetry - wrap with try/catch to send SERVER_START_ERROR event with error details + } } diff --git a/packages/b2c-dx-mcp/src/services.ts b/packages/b2c-dx-mcp/src/services.ts index 410c438c..7fe99142 100644 --- a/packages/b2c-dx-mcp/src/services.ts +++ b/packages/b2c-dx-mcp/src/services.ts @@ -1,22 +1,12 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ -import * as fs from "node:fs"; -import * as path from "node:path"; -import * as os from "node:os"; +import fs from 'node:fs'; +import path from 'node:path'; +import os from 'node:os'; /** * Services class that provides utilities for MCP tools. @@ -32,7 +22,7 @@ export class Services { */ public readonly configPath?: string; - public constructor(opts: { configPath?: string } = {}) { + public constructor(opts: {configPath?: string} = {}) { this.configPath = opts.configPath; } @@ -41,17 +31,6 @@ export class Services { // These are for internal use by tools, not exposed to AI assistants // ============================================ - /** - * Read a file from the filesystem. - * - * @param filePath - Path to the file - * @param encoding - File encoding (default: utf8) - * @returns File contents as a string - */ - public readFile(filePath: string, encoding: BufferEncoding = "utf8"): string { - return fs.readFileSync(filePath, { encoding }); - } - /** * Check if a file or directory exists. * @@ -62,26 +41,6 @@ export class Services { return fs.existsSync(targetPath); } - /** - * Get file or directory stats. - * - * @param targetPath - Path to get stats for - * @returns File stats object - */ - public stat(targetPath: string): fs.Stats { - return fs.statSync(targetPath); - } - - /** - * List directory contents. - * - * @param dirPath - Directory path to list - * @returns Array of directory entries - */ - public listDirectory(dirPath: string): fs.Dirent[] { - return fs.readdirSync(dirPath, { withFileTypes: true }); - } - /** * Get the current working directory. */ @@ -97,20 +56,17 @@ export class Services { } /** - * Get system temporary directory. + * Get OS platform information. */ - public getTmpDir(): string { - return os.tmpdir(); + public getPlatform(): NodeJS.Platform { + return os.platform(); } /** - * Resolve a path relative to the current working directory. - * - * @param segments - Path segments to join and resolve - * @returns Absolute path + * Get system temporary directory. */ - public resolvePath(...segments: string[]): string { - return path.resolve(...segments); + public getTmpDir(): string { + return os.tmpdir(); } /** @@ -124,9 +80,43 @@ export class Services { } /** - * Get OS platform information. + * List directory contents. + * + * @param dirPath - Directory path to list + * @returns Array of directory entries */ - public getPlatform(): NodeJS.Platform { - return os.platform(); + public listDirectory(dirPath: string): fs.Dirent[] { + return fs.readdirSync(dirPath, {withFileTypes: true}); + } + + /** + * Read a file from the filesystem. + * + * @param filePath - Path to the file + * @param encoding - File encoding (default: utf8) + * @returns File contents as a string + */ + public readFile(filePath: string, encoding: 'ascii' | 'base64' | 'hex' | 'latin1' | 'utf8' = 'utf8'): string { + return fs.readFileSync(filePath, {encoding}); + } + + /** + * Resolve a path relative to the current working directory. + * + * @param segments - Path segments to join and resolve + * @returns Absolute path + */ + public resolvePath(...segments: string[]): string { + return path.resolve(...segments); + } + + /** + * Get file or directory stats. + * + * @param targetPath - Path to get stats for + * @returns File stats object + */ + public stat(targetPath: string): fs.Stats { + return fs.statSync(targetPath); } } diff --git a/packages/b2c-dx-mcp/src/tools/cartridges/index.ts b/packages/b2c-dx-mcp/src/tools/cartridges/index.ts index 8cb7a63c..6df104bb 100644 --- a/packages/b2c-dx-mcp/src/tools/cartridges/index.ts +++ b/packages/b2c-dx-mcp/src/tools/cartridges/index.ts @@ -1,17 +1,7 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ /** @@ -22,41 +12,34 @@ * @module tools/cartridges */ -import { z } from "zod"; -import type { McpTool } from "../../utils/index.js"; -import type { Services } from "../../services.js"; +import {z} from 'zod'; +import type {McpTool} from '../../utils/index.js'; +import type {Services} from '../../services.js'; /** * Creates a placeholder tool that logs and returns a mock response. */ -function createPlaceholderTool( - name: string, - description: string, - _services: Services, -): McpTool { +function createPlaceholderTool(name: string, description: string, _services: Services): McpTool { return { name, description: `[PLACEHOLDER] ${description}`, inputSchema: { - message: z.string().optional().describe("Optional message to echo"), + message: z.string().optional().describe('Optional message to echo'), }, - toolsets: ["CARTRIDGES"], + toolsets: ['CARTRIDGES'], isGA: false, - handler: async (args) => { + async handler(args) { const timestamp = new Date().toISOString(); - console.error( - `[${timestamp}] CARTRIDGES tool '${name}' called with:`, - args, - ); + console.error(`[${timestamp}] CARTRIDGES tool '${name}' called with:`, args); return { content: [ { - type: "text" as const, + type: 'text' as const, text: JSON.stringify( { tool: name, - status: "placeholder", + status: 'placeholder', message: `This is a placeholder implementation for '${name}'. The actual implementation is coming soon.`, input: args, timestamp, @@ -78,11 +61,5 @@ function createPlaceholderTool( * @returns Array of MCP tools */ export function createCartridgesTools(services: Services): McpTool[] { - return [ - createPlaceholderTool( - "cartridge_deploy", - "Deploy cartridges to a B2C Commerce instance", - services, - ), - ]; + return [createPlaceholderTool('cartridge_deploy', 'Deploy cartridges to a B2C Commerce instance', services)]; } diff --git a/packages/b2c-dx-mcp/src/tools/index.ts b/packages/b2c-dx-mcp/src/tools/index.ts index efad5526..8ca8b7db 100644 --- a/packages/b2c-dx-mcp/src/tools/index.ts +++ b/packages/b2c-dx-mcp/src/tools/index.ts @@ -1,17 +1,7 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ /** @@ -24,7 +14,7 @@ */ // Toolset exports -export * from "./cartridges/index.js"; -export * from "./mrt/index.js"; -export * from "./pwav3/index.js"; -export * from "./storefrontnext/index.js"; +export * from './cartridges/index.js'; +export * from './mrt/index.js'; +export * from './pwav3/index.js'; +export * from './storefrontnext/index.js'; diff --git a/packages/b2c-dx-mcp/src/tools/mrt/index.ts b/packages/b2c-dx-mcp/src/tools/mrt/index.ts index b3bc7dce..22d003e7 100644 --- a/packages/b2c-dx-mcp/src/tools/mrt/index.ts +++ b/packages/b2c-dx-mcp/src/tools/mrt/index.ts @@ -1,17 +1,7 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ /** @@ -22,9 +12,9 @@ * @module tools/mrt */ -import { z } from "zod"; -import type { McpTool } from "../../utils/index.js"; -import type { Services } from "../../services.js"; +import {z} from 'zod'; +import type {McpTool} from '../../utils/index.js'; +import type {Services} from '../../services.js'; /** * Creates the mrt_bundle_push tool. @@ -37,27 +27,27 @@ import type { Services } from "../../services.js"; */ function createMrtBundlePushTool(_services: Services): McpTool { return { - name: "mrt_bundle_push", - description: "[PLACEHOLDER] Build, push bundle (optionally deploy)", + name: 'mrt_bundle_push', + description: '[PLACEHOLDER] Build, push bundle (optionally deploy)', inputSchema: { - projectId: z.string().optional().describe("MRT project ID"), - environmentId: z.string().optional().describe("Target environment ID"), - message: z.string().optional().describe("Deployment message"), + projectId: z.string().optional().describe('MRT project ID'), + environmentId: z.string().optional().describe('Target environment ID'), + message: z.string().optional().describe('Deployment message'), }, - toolsets: ["MRT", "PWAV3", "STOREFRONTNEXT"], + toolsets: ['MRT', 'PWAV3', 'STOREFRONTNEXT'], isGA: false, - handler: async (args) => { + async handler(args) { const timestamp = new Date().toISOString(); console.error(`[${timestamp}] mrt_bundle_push called with:`, args); return { content: [ { - type: "text" as const, + type: 'text' as const, text: JSON.stringify( { - tool: "mrt_bundle_push", - status: "placeholder", + tool: 'mrt_bundle_push', + status: 'placeholder', message: "This is a placeholder implementation for 'mrt_bundle_push'. The actual implementation is coming soon.", input: args, diff --git a/packages/b2c-dx-mcp/src/tools/pwav3/index.ts b/packages/b2c-dx-mcp/src/tools/pwav3/index.ts index d1006d48..e20db131 100644 --- a/packages/b2c-dx-mcp/src/tools/pwav3/index.ts +++ b/packages/b2c-dx-mcp/src/tools/pwav3/index.ts @@ -1,17 +1,7 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ /** @@ -22,39 +12,34 @@ * @module tools/pwav3 */ -import { z } from "zod"; -import type { McpTool, Toolset } from "../../utils/index.js"; -import type { Services } from "../../services.js"; +import {z} from 'zod'; +import type {McpTool, Toolset} from '../../utils/index.js'; +import type {Services} from '../../services.js'; /** * Creates a placeholder tool that logs and returns a mock response. */ -function createPlaceholderTool( - name: string, - description: string, - toolsets: Toolset[], - _services: Services, -): McpTool { +function createPlaceholderTool(name: string, description: string, toolsets: Toolset[], _services: Services): McpTool { return { name, description: `[PLACEHOLDER] ${description}`, inputSchema: { - message: z.string().optional().describe("Optional message to echo"), + message: z.string().optional().describe('Optional message to echo'), }, toolsets, isGA: false, - handler: async (args) => { + async handler(args) { const timestamp = new Date().toISOString(); console.error(`[${timestamp}] Tool '${name}' called with:`, args); return { content: [ { - type: "text" as const, + type: 'text' as const, text: JSON.stringify( { tool: name, - status: "placeholder", + status: 'placeholder', message: `This is a placeholder implementation for '${name}'. The actual implementation is coming soon.`, input: args, timestamp, @@ -82,52 +67,37 @@ function createPlaceholderTool( export function createPwav3Tools(services: Services): McpTool[] { return [ // PWA Kit development tools + createPlaceholderTool('pwakit_create_storefront', 'Create a new PWA Kit storefront project', ['PWAV3'], services), + createPlaceholderTool('pwakit_create_page', 'Create a new page component in PWA Kit project', ['PWAV3'], services), createPlaceholderTool( - "pwakit_create_storefront", - "Create a new PWA Kit storefront project", - ["PWAV3"], - services, - ), - createPlaceholderTool( - "pwakit_create_page", - "Create a new page component in PWA Kit project", - ["PWAV3"], - services, - ), - createPlaceholderTool( - "pwakit_create_component", - "Create a new React component in PWA Kit project", - ["PWAV3"], - services, - ), - createPlaceholderTool( - "pwakit_get_dev_guidelines", - "Get PWA Kit development guidelines and best practices", - ["PWAV3"], + 'pwakit_create_component', + 'Create a new React component in PWA Kit project', + ['PWAV3'], services, ), createPlaceholderTool( - "pwakit_recommend_hooks", - "Recommend appropriate React hooks for PWA Kit use cases", - ["PWAV3"], + 'pwakit_get_dev_guidelines', + 'Get PWA Kit development guidelines and best practices', + ['PWAV3'], services, ), createPlaceholderTool( - "pwakit_run_site_test", - "Run site tests for PWA Kit project", - ["PWAV3"], + 'pwakit_recommend_hooks', + 'Recommend appropriate React hooks for PWA Kit use cases', + ['PWAV3'], services, ), + createPlaceholderTool('pwakit_run_site_test', 'Run site tests for PWA Kit project', ['PWAV3'], services), createPlaceholderTool( - "pwakit_install_agent_rules", - "Install AI agent rules for PWA Kit development", - ["PWAV3"], + 'pwakit_install_agent_rules', + 'Install AI agent rules for PWA Kit development', + ['PWAV3'], services, ), createPlaceholderTool( - "pwakit_explore_scapi_shop_api", - "Explore SCAPI Shop API endpoints and capabilities", - ["PWAV3"], + 'pwakit_explore_scapi_shop_api', + 'Explore SCAPI Shop API endpoints and capabilities', + ['PWAV3'], services, ), ]; diff --git a/packages/b2c-dx-mcp/src/tools/scapi/index.ts b/packages/b2c-dx-mcp/src/tools/scapi/index.ts index c22d49c6..f0d302b5 100644 --- a/packages/b2c-dx-mcp/src/tools/scapi/index.ts +++ b/packages/b2c-dx-mcp/src/tools/scapi/index.ts @@ -1,17 +1,7 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ /** @@ -22,39 +12,34 @@ * @module tools/scapi */ -import { z } from "zod"; -import type { McpTool, Toolset } from "../../utils/index.js"; -import type { Services } from "../../services.js"; +import {z} from 'zod'; +import type {McpTool, Toolset} from '../../utils/index.js'; +import type {Services} from '../../services.js'; /** * Creates a placeholder tool that logs and returns a mock response. */ -function createPlaceholderTool( - name: string, - description: string, - toolsets: Toolset[], - _services: Services, -): McpTool { +function createPlaceholderTool(name: string, description: string, toolsets: Toolset[], _services: Services): McpTool { return { name, description: `[PLACEHOLDER] ${description}`, inputSchema: { - message: z.string().optional().describe("Optional message to echo"), + message: z.string().optional().describe('Optional message to echo'), }, toolsets, isGA: false, - handler: async (args) => { + async handler(args) { const timestamp = new Date().toISOString(); console.error(`[${timestamp}] SCAPI tool '${name}' called with:`, args); return { content: [ { - type: "text" as const, + type: 'text' as const, text: JSON.stringify( { tool: name, - status: "placeholder", + status: 'placeholder', message: `This is a placeholder implementation for '${name}'. The actual implementation is coming soon.`, input: args, timestamp, @@ -78,21 +63,16 @@ function createPlaceholderTool( export function createScapiTools(services: Services): McpTool[] { return [ createPlaceholderTool( - "scapi_discovery", - "Discover available SCAPI endpoints and capabilities", - ["PWAV3", "SCAPI", "STOREFRONTNEXT"], - services, - ), - createPlaceholderTool( - "scapi_customapi_scaffold", - "Scaffold a new custom SCAPI API", - ["SCAPI"], + 'scapi_discovery', + 'Discover available SCAPI endpoints and capabilities', + ['PWAV3', 'SCAPI', 'STOREFRONTNEXT'], services, ), + createPlaceholderTool('scapi_customapi_scaffold', 'Scaffold a new custom SCAPI API', ['SCAPI'], services), createPlaceholderTool( - "scapi_custom_api_discovery", - "Discover custom SCAPI API endpoints", - ["PWAV3", "SCAPI", "STOREFRONTNEXT"], + 'scapi_custom_api_discovery', + 'Discover custom SCAPI API endpoints', + ['PWAV3', 'SCAPI', 'STOREFRONTNEXT'], services, ), ]; diff --git a/packages/b2c-dx-mcp/src/tools/storefrontnext/index.ts b/packages/b2c-dx-mcp/src/tools/storefrontnext/index.ts index 4b190df9..24f809ae 100644 --- a/packages/b2c-dx-mcp/src/tools/storefrontnext/index.ts +++ b/packages/b2c-dx-mcp/src/tools/storefrontnext/index.ts @@ -1,17 +1,7 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ /** @@ -22,41 +12,34 @@ * @module tools/storefrontnext */ -import { z } from "zod"; -import type { McpTool } from "../../utils/index.js"; -import type { Services } from "../../services.js"; +import {z} from 'zod'; +import type {McpTool} from '../../utils/index.js'; +import type {Services} from '../../services.js'; /** * Creates a placeholder tool that logs and returns a mock response. */ -function createPlaceholderTool( - name: string, - description: string, - _services: Services, -): McpTool { +function createPlaceholderTool(name: string, description: string, _services: Services): McpTool { return { name, description: `[PLACEHOLDER] ${description}`, inputSchema: { - message: z.string().optional().describe("Optional message to echo"), + message: z.string().optional().describe('Optional message to echo'), }, - toolsets: ["STOREFRONTNEXT"], + toolsets: ['STOREFRONTNEXT'], isGA: false, - handler: async (args) => { + async handler(args) { const timestamp = new Date().toISOString(); - console.error( - `[${timestamp}] STOREFRONTNEXT tool '${name}' called with:`, - args, - ); + console.error(`[${timestamp}] STOREFRONTNEXT tool '${name}' called with:`, args); return { content: [ { - type: "text" as const, + type: 'text' as const, text: JSON.stringify( { tool: name, - status: "placeholder", + status: 'placeholder', message: `This is a placeholder implementation for '${name}'. The actual implementation is coming soon.`, input: args, timestamp, @@ -84,38 +67,26 @@ function createPlaceholderTool( export function createStorefrontNextTools(services: Services): McpTool[] { return [ createPlaceholderTool( - "sfnext_development_guidelines", - "Get Storefront Next development guidelines and best practices", - services, - ), - createPlaceholderTool( - "sfnext_site_theming", - "Configure and manage site theming for Storefront Next", - services, - ), - createPlaceholderTool( - "sfnext_figma_to_component_workflow", - "Convert Figma designs to Storefront Next components", - services, - ), - createPlaceholderTool( - "sfnext_generate_component", - "Generate a new Storefront Next component", + 'sfnext_development_guidelines', + 'Get Storefront Next development guidelines and best practices', services, ), + createPlaceholderTool('sfnext_site_theming', 'Configure and manage site theming for Storefront Next', services), createPlaceholderTool( - "sfnext_map_tokens_to_theme", - "Map design tokens to Storefront Next theme configuration", + 'sfnext_figma_to_component_workflow', + 'Convert Figma designs to Storefront Next components', services, ), + createPlaceholderTool('sfnext_generate_component', 'Generate a new Storefront Next component', services), createPlaceholderTool( - "sfnext_design_decorator", - "Apply design decorators to Storefront Next components", + 'sfnext_map_tokens_to_theme', + 'Map design tokens to Storefront Next theme configuration', services, ), + createPlaceholderTool('sfnext_design_decorator', 'Apply design decorators to Storefront Next components', services), createPlaceholderTool( - "sfnext_generate_page_designer_metadata", - "Generate Page Designer metadata for Storefront Next components", + 'sfnext_generate_page_designer_metadata', + 'Generate Page Designer metadata for Storefront Next components', services, ), ]; diff --git a/packages/b2c-dx-mcp/src/utils/constants.ts b/packages/b2c-dx-mcp/src/utils/constants.ts index 7950d4ce..5a41601f 100644 --- a/packages/b2c-dx-mcp/src/utils/constants.ts +++ b/packages/b2c-dx-mcp/src/utils/constants.ts @@ -1,34 +1,18 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ /** * Special toolset value that enables all toolsets. */ -export const ALL_TOOLSETS = "ALL"; +export const ALL_TOOLSETS = 'ALL'; /** * Available toolsets that can be enabled. */ -export const TOOLSETS = [ - "CARTRIDGES", - "MRT", - "PWAV3", - "SCAPI", - "STOREFRONTNEXT", -] as const; +export const TOOLSETS = ['CARTRIDGES', 'MRT', 'PWAV3', 'SCAPI', 'STOREFRONTNEXT'] as const; /** * Valid toolset names including the special "ALL" value. diff --git a/packages/b2c-dx-mcp/src/utils/index.ts b/packages/b2c-dx-mcp/src/utils/index.ts index 3357bcff..fe59f4d0 100644 --- a/packages/b2c-dx-mcp/src/utils/index.ts +++ b/packages/b2c-dx-mcp/src/utils/index.ts @@ -1,17 +1,7 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ /** @@ -23,5 +13,5 @@ // Note: We use .js extensions in imports for ESM compatibility. // TypeScript resolves .js → .ts at compile time, but the compiled // output needs .js extensions to work at runtime with Node.js ESM. -export * from "./constants.js"; -export * from "./types.js"; +export * from './constants.js'; +export * from './types.js'; diff --git a/packages/b2c-dx-mcp/src/utils/types.ts b/packages/b2c-dx-mcp/src/utils/types.ts index 801dacff..0d593b79 100644 --- a/packages/b2c-dx-mcp/src/utils/types.ts +++ b/packages/b2c-dx-mcp/src/utils/types.ts @@ -1,22 +1,12 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ -import type { z, ZodRawShape } from "zod"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import type { Toolset } from "./constants.js"; +import type {z, ZodRawShape} from 'zod'; +import type {CallToolResult} from '@modelcontextprotocol/sdk/types.js'; +import type {Toolset} from './constants.js'; /** * Result returned from MCP tool execution. @@ -43,8 +33,7 @@ export interface McpToolConfig { /** * MCP Tool definition with handler. */ -export interface McpTool - extends McpToolConfig { +export interface McpTool extends McpToolConfig { /** Handler function that executes the tool */ handler: (args: z.infer>) => Promise; } diff --git a/packages/b2c-dx-mcp/test/commands/mcp.test.ts b/packages/b2c-dx-mcp/test/commands/mcp.test.ts index 2e6d975c..7c48159b 100644 --- a/packages/b2c-dx-mcp/test/commands/mcp.test.ts +++ b/packages/b2c-dx-mcp/test/commands/mcp.test.ts @@ -1,101 +1,80 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ -import { expect } from "chai"; -import McpServerCommand from "../../src/commands/mcp.js"; +import {expect} from 'chai'; +import McpServerCommand from '../../src/commands/mcp.js'; -describe("McpServerCommand", () => { - describe("static properties", () => { - it("should have a description", () => { - expect(McpServerCommand.description).to.be.a("string"); - expect(McpServerCommand.description).to.include("MCP Server"); +describe('McpServerCommand', () => { + describe('static properties', () => { + it('should have a description', () => { + expect(McpServerCommand.description).to.be.a('string'); + expect(McpServerCommand.description).to.include('MCP Server'); }); - it("should have examples", () => { - expect(McpServerCommand.examples).to.be.an("array"); + it('should have examples', () => { + expect(McpServerCommand.examples).to.be.an('array'); expect(McpServerCommand.examples.length).to.be.greaterThan(0); }); - it("should define toolsets flag", () => { + it('should define toolsets flag', () => { const toolsetsFlag = McpServerCommand.flags.toolsets; expect(toolsetsFlag).to.not.be.undefined; }); - it("should define tools flag", () => { + it('should define tools flag', () => { const toolsFlag = McpServerCommand.flags.tools; expect(toolsFlag).to.not.be.undefined; }); - it("should define allow-non-ga-tools flag with default false", () => { - const flag = McpServerCommand.flags["allow-non-ga-tools"]; + it('should define allow-non-ga-tools flag with default false', () => { + const flag = McpServerCommand.flags['allow-non-ga-tools']; expect(flag).to.not.be.undefined; expect(flag.default).to.equal(false); }); - it("should inherit config flag from BaseCommand", () => { + it('should inherit config flag from BaseCommand', () => { // config flag is inherited from BaseCommand.baseFlags const flag = McpServerCommand.baseFlags.config; expect(flag).to.not.be.undefined; }); - it("should inherit debug flag from BaseCommand", () => { + it('should inherit debug flag from BaseCommand', () => { const flag = McpServerCommand.baseFlags.debug; expect(flag).to.not.be.undefined; }); - it("should inherit log-level flag from BaseCommand", () => { - const flag = McpServerCommand.baseFlags["log-level"]; + it('should inherit log-level flag from BaseCommand', () => { + const flag = McpServerCommand.baseFlags['log-level']; expect(flag).to.not.be.undefined; }); - it("should support environment variables for flags", () => { - expect(McpServerCommand.flags.toolsets.env).to.equal("SFCC_TOOLSETS"); - expect(McpServerCommand.flags.tools.env).to.equal("SFCC_TOOLS"); - expect(McpServerCommand.flags["allow-non-ga-tools"].env).to.equal( - "SFCC_ALLOW_NON_GA_TOOLS", - ); + it('should support environment variables for flags', () => { + expect(McpServerCommand.flags.toolsets.env).to.equal('SFCC_TOOLSETS'); + expect(McpServerCommand.flags.tools.env).to.equal('SFCC_TOOLS'); + expect(McpServerCommand.flags['allow-non-ga-tools'].env).to.equal('SFCC_ALLOW_NON_GA_TOOLS'); // config flag env is inherited from BaseCommand - expect(McpServerCommand.baseFlags.config.env).to.equal("SFCC_CONFIG"); + expect(McpServerCommand.baseFlags.config.env).to.equal('SFCC_CONFIG'); }); }); - describe("flag parse functions", () => { - it("should uppercase toolsets input", async () => { + describe('flag parse functions', () => { + it('should uppercase toolsets input', async () => { const parse = McpServerCommand.flags.toolsets.parse; if (parse) { - const result = await parse( - "cartridges,mrt", - {} as never, - {} as never, - ); - expect(result).to.equal("CARTRIDGES,MRT"); + const result = await parse('cartridges,mrt', {} as never, {} as never); + expect(result).to.equal('CARTRIDGES,MRT'); } }); - it("should lowercase tools input", async () => { + it('should lowercase tools input', async () => { const parse = McpServerCommand.flags.tools.parse; if (parse) { - const result = await parse( - "CARTRIDGE_DEPLOY,MRT_BUNDLE_PUSH", - {} as never, - {} as never, - ); - expect(result).to.equal("cartridge_deploy,mrt_bundle_push"); + const result = await parse('CARTRIDGE_DEPLOY,MRT_BUNDLE_PUSH', {} as never, {} as never); + expect(result).to.equal('cartridge_deploy,mrt_bundle_push'); } }); }); }); - diff --git a/packages/b2c-dx-mcp/test/registry.test.ts b/packages/b2c-dx-mcp/test/registry.test.ts index 3151523e..cc0f38cf 100644 --- a/packages/b2c-dx-mcp/test/registry.test.ts +++ b/packages/b2c-dx-mcp/test/registry.test.ts @@ -1,147 +1,137 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ -import { expect } from "chai"; -import { createToolRegistry, registerToolsets } from "../src/registry.js"; -import { Services } from "../src/services.js"; -import { B2CDxMcpServer } from "../src/server.js"; -import type { StartupFlags } from "../src/utils/types.js"; - -describe("registry", () => { - // Create a mock services instance for testing - const createMockServices = (configPath?: string): Services => { - return new Services({ configPath }); - }; - - // Create a mock server that tracks registered tools - const createMockServer = (): B2CDxMcpServer & { registeredTools: string[] } => { - const registeredTools: string[] = []; - const server = { - registeredTools, - addTool: (name: string) => { - registeredTools.push(name); - return { name, enabled: true }; - }, - } as unknown as B2CDxMcpServer & { registeredTools: string[] }; - return server; - }; - - describe("createToolRegistry", () => { - it("should create a registry with all toolsets", () => { +import {expect} from 'chai'; +import {createToolRegistry, registerToolsets} from '../src/registry.js'; +import {Services} from '../src/services.js'; +import {B2CDxMcpServer} from '../src/server.js'; +import type {StartupFlags} from '../src/utils/types.js'; + +// Create a mock services instance for testing +function createMockServices(configPath?: string): Services { + return new Services({configPath}); +} + +// Create a mock server that tracks registered tools +function createMockServer(): B2CDxMcpServer & {registeredTools: string[]} { + const registeredTools: string[] = []; + const server = { + registeredTools, + addTool(name: string) { + registeredTools.push(name); + return {name, enabled: true}; + }, + } as unknown as B2CDxMcpServer & {registeredTools: string[]}; + return server; +} + +describe('registry', () => { + describe('createToolRegistry', () => { + it('should create a registry with all toolsets', () => { const services = createMockServices(); const registry = createToolRegistry(services); // Verify all expected toolsets exist - expect(registry).to.have.property("CARTRIDGES"); - expect(registry).to.have.property("MRT"); - expect(registry).to.have.property("PWAV3"); - expect(registry).to.have.property("SCAPI"); - expect(registry).to.have.property("STOREFRONTNEXT"); + expect(registry).to.have.property('CARTRIDGES'); + expect(registry).to.have.property('MRT'); + expect(registry).to.have.property('PWAV3'); + expect(registry).to.have.property('SCAPI'); + expect(registry).to.have.property('STOREFRONTNEXT'); }); - it("should create CARTRIDGES tools", () => { + it('should create CARTRIDGES tools', () => { const services = createMockServices(); const registry = createToolRegistry(services); - expect(registry.CARTRIDGES).to.be.an("array"); + expect(registry.CARTRIDGES).to.be.an('array'); expect(registry.CARTRIDGES.length).to.be.greaterThan(0); const toolNames = registry.CARTRIDGES.map((t) => t.name); - expect(toolNames).to.include("cartridge_deploy"); + expect(toolNames).to.include('cartridge_deploy'); }); - it("should create MRT tools", () => { + it('should create MRT tools', () => { const services = createMockServices(); const registry = createToolRegistry(services); - expect(registry.MRT).to.be.an("array"); + expect(registry.MRT).to.be.an('array'); expect(registry.MRT.length).to.be.greaterThan(0); const toolNames = registry.MRT.map((t) => t.name); - expect(toolNames).to.include("mrt_bundle_push"); + expect(toolNames).to.include('mrt_bundle_push'); }); - it("should create PWAV3 tools", () => { + it('should create PWAV3 tools', () => { const services = createMockServices(); const registry = createToolRegistry(services); - expect(registry.PWAV3).to.be.an("array"); + expect(registry.PWAV3).to.be.an('array'); expect(registry.PWAV3.length).to.be.greaterThan(0); const toolNames = registry.PWAV3.map((t) => t.name); - expect(toolNames).to.include("pwakit_create_storefront"); - expect(toolNames).to.include("pwakit_create_page"); - expect(toolNames).to.include("pwakit_create_component"); + expect(toolNames).to.include('pwakit_create_storefront'); + expect(toolNames).to.include('pwakit_create_page'); + expect(toolNames).to.include('pwakit_create_component'); // mrt_bundle_push should also appear in PWAV3 (multi-toolset) - expect(toolNames).to.include("mrt_bundle_push"); + expect(toolNames).to.include('mrt_bundle_push'); }); - it("should create SCAPI tools", () => { + it('should create SCAPI tools', () => { const services = createMockServices(); const registry = createToolRegistry(services); - expect(registry.SCAPI).to.be.an("array"); + expect(registry.SCAPI).to.be.an('array'); expect(registry.SCAPI.length).to.be.greaterThan(0); const toolNames = registry.SCAPI.map((t) => t.name); - expect(toolNames).to.include("scapi_discovery"); - expect(toolNames).to.include("scapi_customapi_scaffold"); - expect(toolNames).to.include("scapi_custom_api_discovery"); + expect(toolNames).to.include('scapi_discovery'); + expect(toolNames).to.include('scapi_customapi_scaffold'); + expect(toolNames).to.include('scapi_custom_api_discovery'); }); - it("should create STOREFRONTNEXT tools", () => { + it('should create STOREFRONTNEXT tools', () => { const services = createMockServices(); const registry = createToolRegistry(services); - expect(registry.STOREFRONTNEXT).to.be.an("array"); + expect(registry.STOREFRONTNEXT).to.be.an('array'); expect(registry.STOREFRONTNEXT.length).to.be.greaterThan(0); const toolNames = registry.STOREFRONTNEXT.map((t) => t.name); - expect(toolNames).to.include("sfnext_development_guidelines"); - expect(toolNames).to.include("sfnext_site_theming"); - expect(toolNames).to.include("sfnext_generate_component"); + expect(toolNames).to.include('sfnext_development_guidelines'); + expect(toolNames).to.include('sfnext_site_theming'); + expect(toolNames).to.include('sfnext_generate_component'); // mrt_bundle_push should also appear in STOREFRONTNEXT (multi-toolset) - expect(toolNames).to.include("mrt_bundle_push"); + expect(toolNames).to.include('mrt_bundle_push'); }); - it("should assign correct toolsets to each tool", () => { + it('should assign correct toolsets to each tool', () => { const services = createMockServices(); const registry = createToolRegistry(services); // Verify tools have correct toolset assignments for (const tool of registry.CARTRIDGES) { - expect(tool.toolsets).to.include("CARTRIDGES"); + expect(tool.toolsets).to.include('CARTRIDGES'); } for (const tool of registry.MRT) { - expect(tool.toolsets).to.include("MRT"); + expect(tool.toolsets).to.include('MRT'); } for (const tool of registry.PWAV3) { - expect(tool.toolsets).to.include("PWAV3"); + expect(tool.toolsets).to.include('PWAV3'); } for (const tool of registry.SCAPI) { - expect(tool.toolsets).to.include("SCAPI"); + expect(tool.toolsets).to.include('SCAPI'); } for (const tool of registry.STOREFRONTNEXT) { - expect(tool.toolsets).to.include("STOREFRONTNEXT"); + expect(tool.toolsets).to.include('STOREFRONTNEXT'); } }); }); - describe("registerToolsets", () => { - it("should register no tools when no toolsets or tools provided", async () => { + describe('registerToolsets', () => { + it('should register no tools when no toolsets or tools provided', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = {}; @@ -151,114 +141,112 @@ describe("registry", () => { expect(server.registeredTools).to.have.lengthOf(0); }); - it("should register tools from a single toolset", async () => { + it('should register tools from a single toolset', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = { - toolsets: ["CARTRIDGES"], + toolsets: ['CARTRIDGES'], allowNonGaTools: true, }; await registerToolsets(flags, server, services); - expect(server.registeredTools).to.include("cartridge_deploy"); + expect(server.registeredTools).to.include('cartridge_deploy'); // Should not include tools exclusive to other toolsets - expect(server.registeredTools).to.not.include("scapi_customapi_scaffold"); + expect(server.registeredTools).to.not.include('scapi_customapi_scaffold'); }); - it("should register tools from multiple toolsets", async () => { + it('should register tools from multiple toolsets', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = { - toolsets: ["CARTRIDGES", "MRT"], + toolsets: ['CARTRIDGES', 'MRT'], allowNonGaTools: true, }; await registerToolsets(flags, server, services); // Should include CARTRIDGES tools - expect(server.registeredTools).to.include("cartridge_deploy"); + expect(server.registeredTools).to.include('cartridge_deploy'); // Should include MRT tools - expect(server.registeredTools).to.include("mrt_bundle_push"); + expect(server.registeredTools).to.include('mrt_bundle_push'); // Should not include PWAV3-only tools - expect(server.registeredTools).to.not.include("pwakit_create_storefront"); + expect(server.registeredTools).to.not.include('pwakit_create_storefront'); }); - it("should register all toolsets when ALL is specified", async () => { + it('should register all toolsets when ALL is specified', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = { - toolsets: ["ALL"], + toolsets: ['ALL'], allowNonGaTools: true, }; await registerToolsets(flags, server, services); // Should include tools from all toolsets - expect(server.registeredTools).to.include("cartridge_deploy"); - expect(server.registeredTools).to.include("mrt_bundle_push"); - expect(server.registeredTools).to.include("pwakit_create_storefront"); - expect(server.registeredTools).to.include("scapi_discovery"); - expect(server.registeredTools).to.include("sfnext_development_guidelines"); + expect(server.registeredTools).to.include('cartridge_deploy'); + expect(server.registeredTools).to.include('mrt_bundle_push'); + expect(server.registeredTools).to.include('pwakit_create_storefront'); + expect(server.registeredTools).to.include('scapi_discovery'); + expect(server.registeredTools).to.include('sfnext_development_guidelines'); }); - it("should register individual tools via --tools flag", async () => { + it('should register individual tools via --tools flag', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = { - tools: ["cartridge_deploy", "mrt_bundle_push"], + tools: ['cartridge_deploy', 'mrt_bundle_push'], allowNonGaTools: true, }; await registerToolsets(flags, server, services); expect(server.registeredTools).to.have.lengthOf(2); - expect(server.registeredTools).to.include("cartridge_deploy"); - expect(server.registeredTools).to.include("mrt_bundle_push"); + expect(server.registeredTools).to.include('cartridge_deploy'); + expect(server.registeredTools).to.include('mrt_bundle_push'); }); - it("should combine toolsets and individual tools", async () => { + it('should combine toolsets and individual tools', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = { - toolsets: ["CARTRIDGES"], - tools: ["scapi_customapi_scaffold"], + toolsets: ['CARTRIDGES'], + tools: ['scapi_customapi_scaffold'], allowNonGaTools: true, }; await registerToolsets(flags, server, services); // Should include all CARTRIDGES tools - expect(server.registeredTools).to.include("cartridge_deploy"); + expect(server.registeredTools).to.include('cartridge_deploy'); // Should also include the individual SCAPI tool - expect(server.registeredTools).to.include("scapi_customapi_scaffold"); + expect(server.registeredTools).to.include('scapi_customapi_scaffold'); // Should not include other SCAPI tools not in CARTRIDGES - expect(server.registeredTools).to.not.include("scapi_discovery"); + expect(server.registeredTools).to.not.include('scapi_discovery'); }); - it("should not duplicate tools when specified in both toolset and --tools", async () => { + it('should not duplicate tools when specified in both toolset and --tools', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = { - toolsets: ["CARTRIDGES"], - tools: ["cartridge_deploy"], // Already in CARTRIDGES + toolsets: ['CARTRIDGES'], + tools: ['cartridge_deploy'], // Already in CARTRIDGES allowNonGaTools: true, }; await registerToolsets(flags, server, services); // Should only have one instance of cartridge_deploy - const cartridgeDeployCount = server.registeredTools.filter( - (t) => t === "cartridge_deploy", - ).length; + const cartridgeDeployCount = server.registeredTools.filter((t) => t === 'cartridge_deploy').length; expect(cartridgeDeployCount).to.equal(1); }); - it("should warn and skip invalid tool names", async () => { + it('should warn and skip invalid tool names', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = { - tools: ["nonexistent_tool", "cartridge_deploy"], + tools: ['nonexistent_tool', 'cartridge_deploy'], allowNonGaTools: true, }; @@ -266,16 +254,16 @@ describe("registry", () => { await registerToolsets(flags, server, services); // Valid tool should be registered - expect(server.registeredTools).to.include("cartridge_deploy"); + expect(server.registeredTools).to.include('cartridge_deploy'); // Invalid tool should be skipped (not cause error) - expect(server.registeredTools).to.not.include("nonexistent_tool"); + expect(server.registeredTools).to.not.include('nonexistent_tool'); }); - it("should warn and skip invalid toolset names", async () => { + it('should warn and skip invalid toolset names', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = { - toolsets: ["INVALID_TOOLSET", "CARTRIDGES"], + toolsets: ['INVALID_TOOLSET', 'CARTRIDGES'], allowNonGaTools: true, }; @@ -283,14 +271,14 @@ describe("registry", () => { await registerToolsets(flags, server, services); // Valid toolset's tools should be registered - expect(server.registeredTools).to.include("cartridge_deploy"); + expect(server.registeredTools).to.include('cartridge_deploy'); }); - it("should register no tools when all toolsets are invalid", async () => { + it('should register no tools when all toolsets are invalid', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = { - toolsets: ["INVALID1", "INVALID2"], + toolsets: ['INVALID1', 'INVALID2'], allowNonGaTools: true, }; @@ -301,11 +289,11 @@ describe("registry", () => { expect(server.registeredTools).to.have.lengthOf(0); }); - it("should skip non-GA tools when allowNonGaTools is false", async () => { + it('should skip non-GA tools when allowNonGaTools is false', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = { - toolsets: ["CARTRIDGES"], + toolsets: ['CARTRIDGES'], allowNonGaTools: false, }; @@ -316,11 +304,11 @@ describe("registry", () => { expect(server.registeredTools).to.have.lengthOf(0); }); - it("should register GA tools even when allowNonGaTools is false", async () => { + it('should register GA tools even when allowNonGaTools is false', async () => { const services = createMockServices(); const server = createMockServer(); const flags: StartupFlags = { - toolsets: ["ALL"], + toolsets: ['ALL'], allowNonGaTools: false, }; @@ -332,4 +320,3 @@ describe("registry", () => { }); }); }); - diff --git a/packages/b2c-dx-mcp/test/server.test.ts b/packages/b2c-dx-mcp/test/server.test.ts index 2959172e..d8965d39 100644 --- a/packages/b2c-dx-mcp/test/server.test.ts +++ b/packages/b2c-dx-mcp/test/server.test.ts @@ -1,112 +1,89 @@ /* - * Copyright 2025, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ -import { expect } from "chai"; -import { B2CDxMcpServer } from "../src/server.js"; +import {expect} from 'chai'; +import {B2CDxMcpServer} from '../src/server.js'; -describe("B2CDxMcpServer", () => { - describe("constructor", () => { - it("should create server with name and version", () => { +describe('B2CDxMcpServer', () => { + describe('constructor', () => { + it('should create server with name and version', () => { const server = new B2CDxMcpServer({ - name: "test-server", - version: "1.0.0", + name: 'test-server', + version: '1.0.0', }); expect(server).to.be.instanceOf(B2CDxMcpServer); }); - it("should accept optional server options", () => { - const server = new B2CDxMcpServer( - { name: "test-server", version: "1.0.0" }, - { capabilities: {} }, - ); + it('should accept optional server options', () => { + const server = new B2CDxMcpServer({name: 'test-server', version: '1.0.0'}, {capabilities: {}}); expect(server).to.be.instanceOf(B2CDxMcpServer); }); }); - describe("addTool", () => { + describe('addTool', () => { let server: B2CDxMcpServer; beforeEach(() => { server = new B2CDxMcpServer({ - name: "test-server", - version: "1.0.0", + name: 'test-server', + version: '1.0.0', }); }); - it("should register a tool without throwing", () => { + it('should register a tool without throwing', () => { expect(() => { - server.addTool( - "test_tool", - "A test tool", - { type: "object", properties: {} }, - async () => ({ content: [{ type: "text", text: "ok" }] }), - ); + server.addTool('test_tool', 'A test tool', {type: 'object', properties: {}}, async () => ({ + content: [{type: 'text', text: 'ok'}], + })); }).to.not.throw(); }); - it("should register multiple tools", () => { + it('should register multiple tools', () => { expect(() => { - server.addTool( - "tool_one", - "First tool", - { type: "object", properties: {} }, - async () => ({ content: [{ type: "text", text: "one" }] }), - ); + server.addTool('tool_one', 'First tool', {type: 'object', properties: {}}, async () => ({ + content: [{type: 'text', text: 'one'}], + })); - server.addTool( - "tool_two", - "Second tool", - { type: "object", properties: {} }, - async () => ({ content: [{ type: "text", text: "two" }] }), - ); + server.addTool('tool_two', 'Second tool', {type: 'object', properties: {}}, async () => ({ + content: [{type: 'text', text: 'two'}], + })); }).to.not.throw(); }); - it("should accept tools with input schema", () => { + it('should accept tools with input schema', () => { expect(() => { server.addTool( - "parameterized_tool", - "A tool with parameters", + 'parameterized_tool', + 'A tool with parameters', { - type: "object", + type: 'object', properties: { - name: { type: "string", description: "Name parameter" }, - count: { type: "number", description: "Count parameter" }, + name: {type: 'string', description: 'Name parameter'}, + count: {type: 'number', description: 'Count parameter'}, }, - required: ["name"], + required: ['name'], }, async (args) => ({ - content: [{ type: "text", text: `Hello ${args.name}` }], + content: [{type: 'text', text: `Hello ${args.name}`}], }), ); }).to.not.throw(); }); }); - describe("isConnected", () => { - it("should return false when not connected", () => { + describe('isConnected', () => { + it('should return false when not connected', () => { const server = new B2CDxMcpServer({ - name: "test-server", - version: "1.0.0", + name: 'test-server', + version: '1.0.0', }); expect(server.isConnected()).to.be.false; }); }); }); - diff --git a/packages/b2c-dx-mcp/tsconfig.build.json b/packages/b2c-dx-mcp/tsconfig.build.json new file mode 100644 index 00000000..00fdd88f --- /dev/null +++ b/packages/b2c-dx-mcp/tsconfig.build.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "customConditions": [] + } +} diff --git a/packages/b2c-dx-mcp/tsconfig.json b/packages/b2c-dx-mcp/tsconfig.json index c8e51d48..8b5fb1d5 100644 --- a/packages/b2c-dx-mcp/tsconfig.json +++ b/packages/b2c-dx-mcp/tsconfig.json @@ -1,20 +1,13 @@ { + "extends": "@salesforce/dev-config/tsconfig-strict-esm", "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "declaration": true, - "declarationMap": true, - "sourceMap": true, "outDir": "dist", "rootDir": "src", - "strict": true, - "esModuleInterop": true, "skipLibCheck": true, + "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true + "verbatimModuleSyntax": true, + "customConditions": ["development"] }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] + "include": ["./src/**/*"] } - diff --git a/packages/b2c-tooling-sdk/.prettierrc.json b/packages/b2c-tooling-sdk/.prettierrc.json deleted file mode 100644 index 9008f3b0..00000000 --- a/packages/b2c-tooling-sdk/.prettierrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "singleQuote": true, - "printWidth": 120, - "endOfLine": "auto", - "semi": true, - "tabWidth": 2, - "bracketSpacing": false, - "trailingComma": "all" -} diff --git a/packages/b2c-tooling-sdk/eslint.config.mjs b/packages/b2c-tooling-sdk/eslint.config.mjs index 134f0960..7259afb3 100644 --- a/packages/b2c-tooling-sdk/eslint.config.mjs +++ b/packages/b2c-tooling-sdk/eslint.config.mjs @@ -1,10 +1,16 @@ +/* + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 + */ import {includeIgnoreFile} from '@eslint/compat'; -import prettierPlugin from 'eslint-plugin-prettier/recommended'; import headerPlugin from '@tony.ganchev/eslint-plugin-header'; import tseslint from 'typescript-eslint'; import path from 'node:path'; import {fileURLToPath} from 'node:url'; +import {copyrightHeader, sharedRules, chaiTestRules, prettierPlugin} from '../../eslint.config.mjs'; + const gitignorePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '.gitignore'); export default [ @@ -26,34 +32,14 @@ export default [ }, }, rules: { - 'header/header': [ - 'error', - 'block', - [ - '', - ' * Copyright (c) 2025, Salesforce, Inc.', - ' * SPDX-License-Identifier: Apache-2', - ' * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0', - ' ', - ], - ], - // Allow underscore-prefixed unused variables (common convention for intentionally unused params) - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - }, - ], - // Disable new-cap - incompatible with openapi-fetch (uses GET, POST, etc. methods) - 'new-cap': 'off', + 'header/header': ['error', 'block', copyrightHeader], + ...sharedRules, }, }, { - // Allow Chai property-based assertions in test files (e.g., expect(x).to.be.true) files: ['test/**/*.ts'], rules: { - '@typescript-eslint/no-unused-expressions': 'off', + ...chaiTestRules, }, }, ]; diff --git a/packages/b2c-tooling-sdk/src/cli/base-command.ts b/packages/b2c-tooling-sdk/src/cli/base-command.ts index 3d2aea97..6f45ceaa 100644 --- a/packages/b2c-tooling-sdk/src/cli/base-command.ts +++ b/packages/b2c-tooling-sdk/src/cli/base-command.ts @@ -181,6 +181,10 @@ export abstract class BaseCommand extends Command { this.error(err.message, {exit: err.exitCode ?? 1}); } + public baseCommandTest(): void { + this.logger.info('BaseCommand initialized'); + } + /** * Parse extra params from --extra-query and --extra-body flags. * Returns undefined if no extra params are specified. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28860885..12329049 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: devDependencies: + eslint-plugin-prettier: + specifier: ^5.5.4 + version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.6.2) + prettier: + specifier: ^3.6.2 + version: 3.6.2 typedoc: specifier: ^0.28.14 version: 0.28.14(typescript@5.9.3) @@ -60,9 +66,6 @@ importers: '@salesforce/dev-config': specifier: ^4.3.2 version: 4.3.2 - '@tony.ganchev/eslint-plugin-header': - specifier: ^3.1.11 - version: 3.1.11(eslint@9.39.1) '@types/chai': specifier: ^4 version: 4.3.20 @@ -87,6 +90,9 @@ importers: eslint-config-prettier: specifier: ^10 version: 10.1.8(eslint@9.39.1) + eslint-plugin-header: + specifier: ^3.1.1 + version: 3.1.1(eslint@9.39.1) eslint-plugin-prettier: specifier: ^5.5.4 version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.6.2) @@ -154,9 +160,15 @@ importers: eslint: specifier: ^9 version: 9.39.1 + eslint-config-oclif: + specifier: ^6 + version: 6.0.116(eslint@9.39.1)(typescript@5.9.3) eslint-config-prettier: specifier: ^10 version: 10.1.8(eslint@9.39.1) + eslint-plugin-header: + specifier: ^3.1.1 + version: 3.1.1(eslint@9.39.1) eslint-plugin-prettier: specifier: ^5.5.4 version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.6.2) @@ -3250,6 +3262,11 @@ packages: peerDependencies: eslint: '>=4.19.1' + eslint-plugin-header@3.1.1: + resolution: {integrity: sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==} + peerDependencies: + eslint: '>=7.7.0' + eslint-plugin-import@2.32.0: resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} engines: {node: '>=4'} @@ -9273,8 +9290,8 @@ snapshots: eslint-config-oclif: 5.2.2(eslint@9.39.1) eslint-config-xo: 0.49.0(eslint@9.39.1) eslint-config-xo-space: 0.35.0(eslint@9.39.1) - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1) eslint-plugin-jsdoc: 50.8.0(eslint@9.39.1) eslint-plugin-mocha: 10.5.0(eslint@9.39.1) eslint-plugin-n: 17.23.1(eslint@9.39.1)(typescript@5.9.3) @@ -9319,7 +9336,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3(supports-color@10.2.2) @@ -9330,18 +9347,18 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1) transitivePeerDependencies: - supports-color @@ -9358,7 +9375,11 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1): + eslint-plugin-header@3.1.1(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -9369,7 +9390,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3