UNPKG

@salesforce/pwa-kit-mcp

Version:

MCP server that helps you build Salesforce Commerce Cloud PWA Kit Composable Storefront

278 lines (265 loc) 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _utils = require("../utils/utils.js"); var _webdavUtils = require("../utils/webdav-utils.js"); function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); } function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; } /* * Copyright (c) 2025, Salesforce, Inc. * All rights reserved. * SPDX-License-Identifier: BSD-3-Clause * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ /** * Creates a structured JSON response object */ function toJsonResponse(data, activeCodeVersion = null) { const response = { metadata: { activeCodeVersion: activeCodeVersion, timestamp: new Date().toISOString(), totalApis: (data === null || data === void 0 ? void 0 : data.length) || 0 }, customApis: data || [] }; return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] }; } /** * Creates an error response object */ function toErrorResponse(error, customApis = []) { const errorResponse = { error: error.message, customApis: customApis }; return { content: [{ type: 'text', text: JSON.stringify(errorResponse, null, 2) }] }; } /** * Fetches and validates OAuth token */ function fetchAndValidateOAuthToken(_x, _x2, _x3) { return _fetchAndValidateOAuthToken.apply(this, arguments); } /** * Fetches and validates Custom API DX response */ function _fetchAndValidateOAuthToken() { _fetchAndValidateOAuthToken = _asyncToGenerator(function* (clientId, clientSecret, oauthScope) { const response = yield (0, _utils.getOAuthToken)(clientId, clientSecret, oauthScope); const responseData = yield response.json(); if (!response.ok) { const errorMessage = `Invalid OAuth response. Status: ${response.status}. Error: ${response.statusText}. Description: ${responseData.error_description}`; throw new Error(errorMessage); } return responseData; }); return _fetchAndValidateOAuthToken.apply(this, arguments); } function fetchAndValidateCustomApiDxResponse(_x4, _x5, _x6) { return _fetchAndValidateCustomApiDxResponse.apply(this, arguments); } /** * Fetches and validates configuration from dw.json or environment variables */ function _fetchAndValidateCustomApiDxResponse() { _fetchAndValidateCustomApiDxResponse = _asyncToGenerator(function* (accessToken, customApiHost, organizationId) { const response = yield (0, _utils.callCustomApiDxEndpoint)(accessToken, customApiHost, organizationId); const responseData = yield response.json(); if (!response.ok) { const errorMessage = `Invalid Custom API DX response. Status: ${response.status}. Error: ${response.statusText}. Description: ${responseData.detail}`; throw new Error(errorMessage); } return responseData; }); return _fetchAndValidateCustomApiDxResponse.apply(this, arguments); } function fetchAndValidateConfigs() { // Load configuration from dw.json or environment variables const config = (0, _utils.loadConfig)(); const { clientId, clientSecret, organizationId, instanceId, shortCode, hostname } = config; // Validate configuration fields const nullConfigFields = Object.entries(config).filter(([, value]) => value === null || value === undefined).map(([key]) => key); if (nullConfigFields.length > 0) { throw new Error(`Required configuration fields are null: ${nullConfigFields.join(', ')}`); } return { clientId, clientSecret, organizationId, instanceId, shortCode, hostname }; } /** * Recursively searches for files related to an endpoint within a cartridge */ function searchForEndpointFiles(_x7, _x8, _x9, _x10, _x11) { return _searchForEndpointFiles.apply(this, arguments); } /** * Recursively search for API name folder in a directory and its subdirectories */ function _searchForEndpointFiles() { _searchForEndpointFiles = _asyncToGenerator(function* (hostname, accessToken, activeCodeVersion, cartridgeName, apiName) { const searchResults = []; const baseUrl = `${hostname}/on/demandware.servlet/webdav/Sites/Cartridges/${activeCodeVersion}/${cartridgeName}/`; try { // First, get the root cartridge directory structure const response = yield (0, _webdavUtils.makeWebDAVPropfindRequest)(baseUrl, accessToken); (0, _webdavUtils.validateWebDAVResponse)(response); // Parse the XML response to find directories to search for the API name folder const responseText = yield response.text(); const directories = (0, _webdavUtils.parseWebDAVDirectories)(responseText); // Search recursively in each subdirectory for the API name folder for (const dir of directories) { const foundInDir = yield searchRecursivelyForApiName(baseUrl, dir, accessToken, apiName, '', 0); if (foundInDir.searchResults && foundInDir.searchResults.length > 0) { searchResults.push(...foundInDir.searchResults); } } } catch (error) { (0, _utils.logMCPMessage)(`Error searching for endpoint files: ${error}`); } return { searchResults }; }); return _searchForEndpointFiles.apply(this, arguments); } function searchRecursivelyForApiName(_x12, _x13, _x14, _x15, _x16) { return _searchRecursivelyForApiName.apply(this, arguments); } function _searchRecursivelyForApiName() { _searchRecursivelyForApiName = _asyncToGenerator(function* (baseUrl, currentDir, accessToken, apiName, currentPath, depth = 0) { const searchResults = []; // Normalize path building to avoid double slashes const dirUrl = `${baseUrl.replace(/\/$/, '')}/${currentDir.replace(/^\//, '')}/`; const fullPath = currentPath ? `${currentPath}/${currentDir}` : currentDir; try { const dirResponse = yield (0, _webdavUtils.makeWebDAVPropfindRequest)(dirUrl, accessToken); (0, _webdavUtils.validateWebDAVResponse)(dirResponse); const dirText = yield dirResponse.text(); const items = (0, _webdavUtils.parseWebDAVResponse)(dirText); // Check if the API name folder is in this directory const apiNameFolder = items.find(item => item.toLowerCase() === apiName.toLowerCase()); // Only try to fetch schema if folder exists if (apiNameFolder) { let schemaContent = null; const schemaUrl = `${dirUrl}${apiNameFolder}/schema.yaml`; const schemaResponse = yield (0, _webdavUtils.makeWebDAVGetRequest)(schemaUrl, accessToken); (0, _webdavUtils.validateWebDAVResponse)(schemaResponse); schemaContent = yield schemaResponse.text(); searchResults.push({ directory: fullPath, apiNameFolder: apiNameFolder, fullPath: `${fullPath}/${apiNameFolder}`, schemaContent: schemaContent }); } // Get subdirectories using proper directory detection const subdirs = (0, _webdavUtils.parseWebDAVDirectories)(dirText); for (const subdir of subdirs) { const subResults = yield searchRecursivelyForApiName(baseUrl, `${currentDir}/${subdir}`, accessToken, apiName, fullPath, depth + 1); searchResults.push(...subResults.searchResults); } } catch (error) { (0, _utils.logMCPMessage)(`Error searching recursively for API name: ${error}`); } return { searchResults }; }); return _searchRecursivelyForApiName.apply(this, arguments); } var _default = exports.default = { name: 'scapi_custom_api_discovery', description: 'Discovers and retrieves information about custom APIs deployed in Salesforce Commerce Cloud instances. Use this tool when you need to: find available custom APIs, get API schemas/documentation, understand API endpoints and methods, or analyze custom API implementations. This tool searches through SFCC cartridges, retrieves OAuth tokens, and fetches comprehensive API metadata including endpoints, HTTP methods, security schemes, and OpenAPI schemas.', inputSchema: {}, fn: function () { var _ref = _asyncToGenerator(function* () { let dxEndpointResponse = null; let activeCodeVersion = null; const { clientId, clientSecret, organizationId, instanceId, shortCode, hostname } = fetchAndValidateConfigs(); const customApiHost = `${shortCode}.api.commercecloud.salesforce.com`; const oauthScope = `SALESFORCE_COMMERCE_API:${instanceId} sfcc.custom-apis`; try { // Get OAuth token const tokenData = yield fetchAndValidateOAuthToken(clientId, clientSecret, oauthScope); // Call custom API DX endpoint and retrieve custom APIs on the instance dxEndpointResponse = yield fetchAndValidateCustomApiDxResponse(tokenData.access_token, customApiHost, organizationId); activeCodeVersion = dxEndpointResponse.activeCodeVersion; if (!dxEndpointResponse.data) { return toJsonResponse([], activeCodeVersion); } // Process each custom API and attempt to get the schema content from WebDAV // If the schema content is not found, still create the entry with content from DX response const processedEntries = []; for (const entry of dxEndpointResponse.data) { if (entry.cartridgeName) { const endpointPath = entry.endpointPath.startsWith('/') ? entry.endpointPath.substring(1) : entry.endpointPath; let customApiBaseUrl = null; let schemaContent = null; try { var _webdavResponse$searc, _webdavResponse$searc2; // Construct the custom API base URL customApiBaseUrl = `https://${shortCode}.api.commercecloud.salesforce.com/custom/${entry.apiName}/${entry.apiVersion}/organizations/${organizationId}/${endpointPath}`; const webdavResponse = yield searchForEndpointFiles(hostname, tokenData.access_token, activeCodeVersion, entry.cartridgeName, entry.apiName); // Extract schema content from the first successful result schemaContent = (webdavResponse === null || webdavResponse === void 0 ? void 0 : (_webdavResponse$searc = webdavResponse.searchResults) === null || _webdavResponse$searc === void 0 ? void 0 : (_webdavResponse$searc2 = _webdavResponse$searc[0]) === null || _webdavResponse$searc2 === void 0 ? void 0 : _webdavResponse$searc2.schemaContent) || null; } catch (webdavError) { (0, _utils.logMCPMessage)(`Error fetching custom API schema: ${webdavError}`); } // Create the processed entry with necessary fields const processedEntry = { apiName: entry.apiName, apiVersion: entry.apiVersion, cartridgeName: entry.cartridgeName, endpointPath: endpointPath, httpMethod: entry.httpMethod, status: entry.status, securityScheme: entry.securityScheme, siteId: entry.siteId, baseUrl: customApiBaseUrl, schema: schemaContent }; processedEntries.push(processedEntry); } } return toJsonResponse(processedEntries, activeCodeVersion); } catch (error) { var _dxEndpointResponse; return toErrorResponse(error, ((_dxEndpointResponse = dxEndpointResponse) === null || _dxEndpointResponse === void 0 ? void 0 : _dxEndpointResponse.data) || []); } }); return function fn() { return _ref.apply(this, arguments); }; }() };