diff --git a/packages/mcp/README.md b/packages/mcp/README.md new file mode 100644 index 000000000..420c04b21 --- /dev/null +++ b/packages/mcp/README.md @@ -0,0 +1,98 @@ +# @vlocode/mcp + +Vlocode **Model Context Protocol (MCP)** server that enables AI agents (GitHub Copilot, Claude, etc.) to interact with Salesforce and Vlocity datapacks directly from your IDE. + +## Features + +The server exposes the following tools: + +| Tool | Description | +|------|-------------| +| `deploy_datapacks` | Deploy Vlocity datapacks from local folders to Salesforce | +| `retrieve_datapacks` | Export/retrieve Salesforce objects as Vlocity datapacks | +| `deploy_metadata` | Deploy Salesforce metadata (Apex, LWC, etc.) | +| `list_sobjects` | List all Salesforce objects in the connected org | +| `describe_sobject` | Describe a Salesforce object with its fields and metadata | +| `describe_sobject_field` | Describe a specific field on a Salesforce object | +| `list_profiles` | List all Salesforce profiles | +| `add_fls_to_profile` | Add Field Level Security permissions to a profile | + +## Installation + +```bash +npm install -g @vlocode/mcp +``` + +## Usage + +### Command-line options + +``` +vlocode-mcp [options] + +Options: + -u, --user SFDX username or alias to connect to Salesforce + -i, --instance Salesforce instance URL (default: login.salesforce.com) + --log-level Log level: debug | verbose | info | warn | error + --debug Enable debug logging + --verbose Enable verbose logging + -h, --help Show help +``` + +### VS Code MCP configuration + +Add the following to your `.vscode/mcp.json` (or VS Code User Settings) to make the Vlocode MCP server available to GitHub Copilot and other agents: + +```json +{ + "servers": { + "vlocode": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@vlocode/mcp", "--user", "myorg@example.com"] + } + } +} +``` + +Replace `myorg@example.com` with your SFDX org username or alias. + +### Authentication + +The server uses SFDX authentication. Ensure you have authenticated with the target org before starting the server: + +```bash +sf org login web --alias myorg +``` + +## Examples + +### Deploy datapacks + +An agent can deploy datapacks using the `deploy_datapacks` tool: + +``` +Deploy the datapacks in ./datapacks/OmniScript to my Salesforce org +``` + +### Retrieve datapacks + +``` +Export the OmniScript record with ID a1B000000000000 as a datapack to ./output +``` + +### Describe a Salesforce object + +``` +What fields does the Account object have? +``` + +### Add FLS to a profile + +``` +Grant read access to Account.Phone and Account.Fax for the Standard profile +``` + +## License + +MIT diff --git a/packages/mcp/bin/run b/packages/mcp/bin/run new file mode 100755 index 000000000..08da17f99 --- /dev/null +++ b/packages/mcp/bin/run @@ -0,0 +1,17 @@ +#!/usr/bin/env node +import { existsSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// Recreate __dirname for ESM +const __dirname = dirname(fileURLToPath(import.meta.url)); + +if (existsSync(join(__dirname, '../dist/mcp.mjs'))) { + await import('../dist/mcp.mjs'); +} else { + // Development mode: run via ts-node / tsx + const { register } = await import('node:module'); + const { pathToFileURL } = await import('node:url'); + register('ts-node/esm', pathToFileURL('./')); + await import('../src/index.ts'); +} diff --git a/packages/mcp/package.json b/packages/mcp/package.json new file mode 100644 index 000000000..c6afe1a38 --- /dev/null +++ b/packages/mcp/package.json @@ -0,0 +1,61 @@ +{ + "name": "@vlocode/mcp", + "version": "1.41.2", + "description": "Vlocode MCP (Model Context Protocol) server for Salesforce and Vlocity datapack operations", + "keywords": [ + "Vlocity", + "Salesforce", + "Datapacks", + "MCP", + "ModelContextProtocol" + ], + "bin": { + "vlocode-mcp": "bin/run" + }, + "main": "src/index.ts", + "type": "module", + "publishConfig": { + "main": "dist/mcp.mjs", + "access": "public" + }, + "readme": "./README.md", + "engines": { + "node": ">=20.0.0" + }, + "scripts": { + "watch": "tsdown -c tsdown.config.mts --watch", + "build": "tsdown -c tsdown.config.mts", + "prepublish": "pnpm run clean && pnpm run build", + "pack-only": "pnpm pack", + "clean": "shx rm -rf ./dist './*.tgz' './src/**/*.{d.ts,ts.map,js.map,js}'" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Codeneos/vlocode.git" + }, + "author": { + "name": "Peter van Gulik", + "email": "peter@curlybracket.nl" + }, + "files": [ + "/dist", + "/bin" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/Codeneos/vlocode/issues" + }, + "homepage": "https://github.com/Codeneos/vlocode#readme", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.27.1" + }, + "devDependencies": { + "@vlocode/core": "workspace:*", + "@vlocode/salesforce": "workspace:*", + "@vlocode/util": "workspace:*", + "@vlocode/vlocity": "workspace:*", + "@vlocode/vlocity-deploy": "workspace:*", + "shx": "^0.3.4" + }, + "publisher": "curlybracket" +} diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts new file mode 100644 index 000000000..056c6fd84 --- /dev/null +++ b/packages/mcp/src/index.ts @@ -0,0 +1,82 @@ +import { LogLevel } from '@vlocode/core'; +import { startMcpServer, type VlocodeMcpOptions } from './server.js'; + +/** + * Parse a minimal set of command-line options for the MCP server. + * We deliberately keep this lightweight (no Commander dependency) + * because the MCP server is meant to be embedded in agent configurations. + */ +function parseArgs(argv: string[]): VlocodeMcpOptions { + const opts: VlocodeMcpOptions = {}; + + for (let i = 2; i < argv.length; i++) { + const arg = argv[i]; + + if ((arg === '-u' || arg === '--user') && argv[i + 1]) { + opts.user = argv[++i]; + } else if ((arg === '-i' || arg === '--instance') && argv[i + 1]) { + opts.instance = argv[++i]; + } else if (arg === '--debug') { + opts.logLevel = LogLevel.debug; + } else if (arg === '--verbose') { + opts.logLevel = LogLevel.verbose; + } else if ((arg === '--log-level') && argv[i + 1]) { + const level = argv[++i].toLowerCase(); + const levelMap: Record = { + debug: LogLevel.debug, + verbose: LogLevel.verbose, + info: LogLevel.info, + warn: LogLevel.warn, + error: LogLevel.error, + }; + opts.logLevel = levelMap[level] ?? LogLevel.warn; + } else if (arg === '--help' || arg === '-h') { + printHelp(); + process.exit(0); + } + } + + return opts; +} + +function printHelp() { + process.stderr.write( + [ + 'Usage: vlocode-mcp [options]', + '', + 'Start the Vlocode MCP (Model Context Protocol) server over stdio.', + '', + 'Options:', + ' -u, --user SFDX username or alias to connect to Salesforce', + ' -i, --instance Salesforce instance URL (default: login.salesforce.com)', + ' --log-level Log level: debug | verbose | info | warn | error (default: warn)', + ' --debug Enable debug logging', + ' --verbose Enable verbose logging', + ' -h, --help Show this help message', + '', + 'MCP tools exposed:', + ' deploy_datapacks Deploy Vlocity datapacks from local folders', + ' retrieve_datapacks Export Salesforce objects as datapacks', + ' deploy_metadata Deploy Salesforce metadata', + ' list_sobjects List all Salesforce objects', + ' describe_sobject Describe a Salesforce object and its fields', + ' describe_sobject_field Describe a specific field on a Salesforce object', + ' list_profiles List all Salesforce profiles', + ' add_fls_to_profile Add Field Level Security to a profile', + '', + 'Example VS Code MCP configuration (.vscode/mcp.json):', + ' {', + ' "servers": {', + ' "vlocode": {', + ' "type": "stdio",', + ' "command": "npx",', + ' "args": ["-y", "@vlocode/mcp", "--user", "myorg@example.com"]', + ' }', + ' }', + ' }', + ].join('\n') + '\n' + ); +} + +const options = parseArgs(process.argv); +await startMcpServer(options); diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts new file mode 100644 index 000000000..b14908e93 --- /dev/null +++ b/packages/mcp/src/server.ts @@ -0,0 +1,165 @@ +import { CachedFileSystemAdapter, container, Logger, LogLevel, LogManager, NodeFileSystem, FileSystem, type LogEntry, type LogWriter } from '@vlocode/core'; +import { + InteractiveConnectionProvider, + SalesforceConnectionProvider, + SfdxConnectionProvider, +} from '@vlocode/salesforce'; +import { VlocityNamespaceService } from '@vlocode/vlocity'; +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ErrorCode, + ListToolsRequestSchema, + McpError, +} from '@modelcontextprotocol/sdk/types.js'; +import { readFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +import { datapackTools, executeDatapackTool } from './tools/datapacks.js'; +import { metadataTools, executeMetadataTool } from './tools/metadata.js'; +import { schemaTools, executeSchemaTool } from './tools/schema.js'; +import { profileTools, executeProfileTool } from './tools/profiles.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// Read version from package.json +let version = '1.0.0'; +try { + const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')); + version = pkg.version ?? version; +} catch { + // ignore +} + +export interface VlocodeMcpOptions { + /** SFDX username or alias to connect to Salesforce */ + user?: string; + /** Salesforce instance URL (e.g. login.salesforce.com) */ + instance?: string; + /** Log level */ + logLevel?: LogLevel; +} + +/** + * Starts the Vlocode MCP server over stdio. + * + * The server exposes tools for deploying and retrieving Vlocity datapacks, + * describing Salesforce objects, managing profiles, and deploying metadata. + */ +export async function startMcpServer(options: VlocodeMcpOptions = {}) { + // Configure logging — MCP communicates over stdio so all log output must + // go to stderr to avoid corrupting the protocol stream. + const stderrWriter: LogWriter = { + write({ level, time, category, message }: LogEntry) { + const ts = (time ?? new Date()).toISOString(); + process.stderr.write(`${ts} [${LogLevel[level]}] [${category}]: ${message}\n`); + }, + }; + LogManager.registerWriter(stderrWriter); + LogManager.setGlobalLogLevel(options.logLevel ?? LogLevel.warn); + + const logger = LogManager.get('vlocode-mcp'); + logger.info('Starting Vlocode MCP server v%s', version); + + // Create an isolated DI container for this server instance + const localContainer = container.create(); + + // Register connection provider based on authentication options + if (options.user) { + localContainer.add(new SfdxConnectionProvider(options.user), { + provides: [SalesforceConnectionProvider], + }); + } else if (options.instance) { + localContainer.add( + new InteractiveConnectionProvider(`https://${options.instance}`), + { provides: [SalesforceConnectionProvider] } + ); + } else { + // Fall back to interactive provider using the default Salesforce login URL + localContainer.add( + new InteractiveConnectionProvider('https://login.salesforce.com'), + { provides: [SalesforceConnectionProvider] } + ); + } + + // Provide a cached file system adapter for loading/writing local files + localContainer.add(new CachedFileSystemAdapter(new NodeFileSystem()), { + provides: [FileSystem], + }); + + // Register a named logger in the container + localContainer.registerProvider(Logger, (receiver) => + LogManager.get(receiver ?? 'vlocode-mcp') + ); + + // The Vlocity namespace service resolves namespace placeholders in queries + // and SObject type names. Initialization is deferred to the first tool call + // so the MCP server starts immediately without requiring a Salesforce connection. + let nsInitialized = false; + + async function ensureNamespaceInitialized() { + if (!nsInitialized) { + const nsService = new VlocityNamespaceService(); + await nsService.initialize(localContainer.get(SalesforceConnectionProvider)); + localContainer.add(nsService); + nsInitialized = true; + } + } + + // ------------------------------------------------------------------ // + // Build the MCP server + // ------------------------------------------------------------------ // + const server = new Server( + { name: 'vlocode', version }, + { capabilities: { tools: {} } } + ); + + // List all available tools + server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + ...datapackTools, + ...metadataTools, + ...schemaTools, + ...profileTools, + ], + })); + + // Dispatch tool calls to the appropriate handler + server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + // Initialize the Vlocity namespace service on the first tool call + await ensureNamespaceInitialized(); + + if (datapackTools.some((t) => t.name === name)) { + return await executeDatapackTool(name, args ?? {}, localContainer); + } + if (metadataTools.some((t) => t.name === name)) { + return await executeMetadataTool(name, args ?? {}, localContainer); + } + if (schemaTools.some((t) => t.name === name)) { + return await executeSchemaTool(name, args ?? {}, localContainer); + } + if (profileTools.some((t) => t.name === name)) { + return await executeProfileTool(name, args ?? {}, localContainer); + } + throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); + } catch (err: unknown) { + if (err instanceof McpError) { + throw err; + } + const message = err instanceof Error ? err.message : String(err); + return { + content: [{ type: 'text' as const, text: `Error: ${message}` }], + isError: true, + }; + } + }); + + const transport = new StdioServerTransport(); + await server.connect(transport); + logger.info('Vlocode MCP server ready'); +} diff --git a/packages/mcp/src/tools/datapacks.ts b/packages/mcp/src/tools/datapacks.ts new file mode 100644 index 000000000..ec7def4a5 --- /dev/null +++ b/packages/mcp/src/tools/datapacks.ts @@ -0,0 +1,262 @@ +import { mkdir, writeFile } from 'fs/promises'; +import { join } from 'path'; +import type { Container } from '@vlocode/core'; +import { + DatapackDeployer, + DatapackDeploymentOptions, + DatapackExporter, + DatapackExportDefinitionStore, +} from '@vlocode/vlocity-deploy'; +import { DatapackLoader } from '@vlocode/vlocity'; +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; + +export const datapackTools: Tool[] = [ + { + name: 'deploy_datapacks', + description: + 'Deploy one or more Vlocity datapacks from local folders to Salesforce. ' + + 'Provide a list of folder paths that contain datapack files (*_DataPack.json).', + inputSchema: { + type: 'object', + properties: { + paths: { + type: 'array', + items: { type: 'string' }, + description: + 'List of absolute or relative paths to folders containing datapack files.', + minItems: 1, + }, + continueOnError: { + type: 'boolean', + description: + 'Continue deploying even if some datapacks fail to load. Defaults to false.', + default: false, + }, + strictOrder: { + type: 'boolean', + description: + 'Enforce strict deployment order for dependent datapacks. Defaults to false.', + default: false, + }, + deltaCheck: { + type: 'boolean', + description: + 'Only deploy datapacks that differ from the target org. Defaults to false.', + default: false, + }, + }, + required: ['paths'], + }, + }, + { + name: 'retrieve_datapacks', + description: + 'Export/retrieve one or more Salesforce objects as Vlocity datapacks. ' + + 'Objects are identified by their Salesforce IDs or by a SOQL query. ' + + 'The datapacks are written to the specified output directory.', + inputSchema: { + type: 'object', + properties: { + ids: { + type: 'array', + items: { type: 'string' }, + description: + 'List of Salesforce record IDs to export as datapacks.', + }, + query: { + type: 'string', + description: + 'SOQL query to identify the records to export. ' + + 'Use this instead of `ids` when you need to filter records.', + }, + outputDir: { + type: 'string', + description: + 'Absolute or relative path to the directory where the exported datapack files will be written.', + }, + expand: { + type: 'boolean', + description: + 'Expand each datapack into separate files (one file per child record / resource). Defaults to true.', + default: true, + }, + exportDefinitions: { + type: 'string', + description: + 'Path to a YAML or JSON file containing custom export definitions. ' + + 'When omitted, the built-in definitions are used.', + }, + }, + required: ['outputDir'], + }, + }, +]; + +export async function executeDatapackTool( + name: string, + args: Record, + localContainer: Container +) { + if (name === 'deploy_datapacks') { + return deployDatapacks(args, localContainer); + } + if (name === 'retrieve_datapacks') { + return retrieveDatapacks(args, localContainer); + } + throw new Error(`Unknown datapack tool: ${name}`); +} + +async function deployDatapacks( + args: Record, + localContainer: Container +) { + const paths = args['paths'] as string[]; + const options: DatapackDeploymentOptions = { + continueOnError: (args['continueOnError'] as boolean) ?? false, + strictOrder: (args['strictOrder'] as boolean) ?? false, + deltaCheck: (args['deltaCheck'] as boolean) ?? false, + }; + + const loader = localContainer.new(DatapackLoader); + const datapacks = ( + await Promise.all(paths.map((p) => loader.loadDatapacksFromFolder(p))) + ).flat(1); + + if (datapacks.length === 0) { + return { + content: [ + { + type: 'text' as const, + text: `No datapacks found in the specified folders: ${paths.join(', ')}`, + }, + ], + }; + } + + const deployer = localContainer.new(DatapackDeployer); + const deployment = await deployer.deploy(datapacks, options); + const status = deployment.getStatus(); + + const succeeded = status.datapacks.filter((d) => d.status === 'success').length; + const failed = status.datapacks.filter((d) => d.status === 'error').length; + const partial = status.datapacks.filter((d) => d.status === 'partialSuccess').length; + + const lines: string[] = [ + `Deployment complete: ${succeeded} succeeded, ${failed} failed, ${partial} partial (${status.total} total)`, + ]; + + for (const d of status.datapacks) { + if (d.status === 'error' || d.status === 'partialSuccess') { + lines.push(` [${d.status.toUpperCase()}] ${d.datapack}:`); + for (const msg of d.messages) { + lines.push( + ` ${msg.type === 'error' ? '[ERROR]' : '[WARN]'} ${msg.message}` + ); + } + } + } + + return { + content: [{ type: 'text' as const, text: lines.join('\n') }], + isError: failed > 0, + }; +} + +async function retrieveDatapacks( + args: Record, + localContainer: Container +) { + const outputDir = args['outputDir'] as string; + const doExpand = (args['expand'] as boolean) ?? true; + + // Load custom export definitions when provided + if (args['exportDefinitions']) { + const { readFileSync } = await import('fs'); + const content = readFileSync(args['exportDefinitions'] as string, 'utf-8'); + let definitions: Record; + try { + const yaml = await import('js-yaml'); + definitions = yaml.load(content) as Record; + } catch { + definitions = JSON.parse(content) as Record; + } + localContainer.get(DatapackExportDefinitionStore).load( + definitions as Record> + ); + } + + // Resolve the list of IDs to export + let ids: string[]; + if (args['query']) { + const { SalesforceService } = await import('@vlocode/salesforce'); + const records = await localContainer + .get(SalesforceService) + .data.query(args['query'] as string); + if (records.length === 0) { + return { + content: [ + { + type: 'text' as const, + text: `No records found for the query: ${args['query']}`, + }, + ], + }; + } + ids = records.map((r) => r.Id as string); + } else if (args['ids'] && Array.isArray(args['ids'])) { + ids = args['ids'] as string[]; + } else { + return { + content: [ + { + type: 'text' as const, + text: 'Either `ids` or `query` must be specified.', + }, + ], + isError: true, + }; + } + + const exporter = localContainer.new(DatapackExporter); + const written: string[] = []; + await mkdir(outputDir, { recursive: true }); + + for (const id of ids) { + if (doExpand) { + const expanded = await exporter.exportObjectAndExpand(id); + const folderPath = join(outputDir, expanded.folder); + await mkdir(folderPath, { recursive: true }); + + for (const [fileName, fileData] of Object.entries(expanded.files)) { + const filePath = join(folderPath, fileName); + await writeFile(filePath, fileData); + written.push(filePath); + } + } else { + const result = await exporter.exportObject(id); + const baseName = (result.datapack['Name'] as string | undefined) ?? id; + const dpPath = join(outputDir, baseName + '_DataPack.json'); + const pkPath = join(outputDir, baseName + '_ParentKeys.json'); + await writeFile(dpPath, JSON.stringify(result.datapack, null, 4), 'utf-8'); + await writeFile( + pkPath, + JSON.stringify(result.parentKeys, null, 4), + 'utf-8' + ); + written.push(dpPath, pkPath); + } + } + + return { + content: [ + { + type: 'text' as const, + text: [ + `Exported ${ids.length} datapack(s) to '${outputDir}'.`, + 'Files written:', + ...written.map((f) => ` ${f}`), + ].join('\n'), + }, + ], + }; +} diff --git a/packages/mcp/src/tools/metadata.ts b/packages/mcp/src/tools/metadata.ts new file mode 100644 index 000000000..fa7ff45d7 --- /dev/null +++ b/packages/mcp/src/tools/metadata.ts @@ -0,0 +1,133 @@ +import type { Container } from '@vlocode/core'; +import { + SalesforceDeployService, + SalesforcePackageBuilder, + SalesforcePackageType, +} from '@vlocode/salesforce'; +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; + +export const metadataTools: Tool[] = [ + { + name: 'deploy_metadata', + description: + 'Deploy Salesforce metadata (Apex classes, triggers, LWC components, etc.) ' + + 'from local source files or directories to the target Salesforce org. ' + + 'Accepts any files or folders that form a valid Salesforce metadata package.', + inputSchema: { + type: 'object', + properties: { + paths: { + type: 'array', + items: { type: 'string' }, + description: + 'List of absolute paths to metadata source files or directories to deploy. ' + + 'Directories are scanned recursively. ' + + 'Accepts standard Salesforce metadata layout (e.g. force-app/main/default).', + minItems: 1, + }, + checkOnly: { + type: 'boolean', + description: + 'Validate the deployment without committing changes (dry-run). Defaults to false.', + default: false, + }, + ignoreWarnings: { + type: 'boolean', + description: + 'Treat deployment warnings as non-fatal. Defaults to true.', + default: true, + }, + rollbackOnError: { + type: 'boolean', + description: + 'Roll back the entire deployment when any component fails. Defaults to false.', + default: false, + }, + apiVersion: { + type: 'string', + description: + 'Salesforce API version to use for the deployment (e.g. "59.0"). ' + + 'Defaults to the connection\'s default API version.', + }, + }, + required: ['paths'], + }, + }, +]; + +export async function executeMetadataTool( + name: string, + args: Record, + localContainer: Container +) { + if (name === 'deploy_metadata') { + return deployMetadata(args, localContainer); + } + throw new Error(`Unknown metadata tool: ${name}`); +} + +async function deployMetadata( + args: Record, + localContainer: Container +) { + const paths = args['paths'] as string[]; + const apiVersion = args['apiVersion'] as string | undefined; + const checkOnly = (args['checkOnly'] as boolean) ?? false; + const ignoreWarnings = (args['ignoreWarnings'] as boolean) ?? true; + const rollbackOnError = (args['rollbackOnError'] as boolean) ?? false; + + // Build the metadata package + const packageBuilder = localContainer.new( + SalesforcePackageBuilder, + SalesforcePackageType.deploy, + apiVersion + ); + await packageBuilder.addFiles(paths); + + const components = packageBuilder.getPackageComponents(); + if (components.length === 0) { + return { + content: [ + { + type: 'text' as const, + text: `No deployable metadata components found in the specified paths: ${paths.join(', ')}`, + }, + ], + }; + } + + const sfPackage = await packageBuilder.build(); + const deployService = localContainer.get(SalesforceDeployService); + + const result = await deployService.deployPackage(sfPackage, { + checkOnly, + ignoreWarnings, + rollbackOnError, + allowMissingFiles: false, + purgeOnDelete: true, + singlePackage: true, + }); + + const lines: string[] = [ + `Deployment ${result.success ? 'SUCCEEDED' : 'FAILED'} [${result.status}]`, + ` Components: ${result.numberComponentsDeployed ?? 0}/${result.numberComponentsTotal ?? components.length} deployed, ${result.numberComponentErrors ?? 0} errors`, + ]; + + if (result.details?.componentFailures) { + const failures = Array.isArray(result.details.componentFailures) + ? result.details.componentFailures + : [result.details.componentFailures]; + for (const failure of failures) { + lines.push(` [ERROR] ${failure.fileName}: ${failure.problem}`); + } + } + + if (result.errorMessage) { + lines.push(` ${result.errorMessage}`); + } + + return { + content: [{ type: 'text' as const, text: lines.join('\n') }], + isError: !result.success, + }; +} diff --git a/packages/mcp/src/tools/profiles.ts b/packages/mcp/src/tools/profiles.ts new file mode 100644 index 000000000..931cea07c --- /dev/null +++ b/packages/mcp/src/tools/profiles.ts @@ -0,0 +1,146 @@ +import type { Container } from '@vlocode/core'; +import { + SalesforceProfileService, + SalesforceConnectionProvider, + SalesforceFieldPermission, +} from '@vlocode/salesforce'; +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; + +export const profileTools: Tool[] = [ + { + name: 'list_profiles', + description: + 'List all Salesforce profiles available in the connected org. ' + + 'Returns the developer name (fullName) for each profile.', + inputSchema: { + type: 'object', + properties: {}, + }, + }, + { + name: 'add_fls_to_profile', + description: + 'Add or update Field Level Security (FLS) permissions for one or more fields on a Salesforce profile. ' + + 'Each field can be set to "editable" (read + write), "readable" (read-only), or "none" (no access).', + inputSchema: { + type: 'object', + properties: { + profile: { + type: 'string', + description: + 'The developer name (fullName) of the profile to update (e.g. "Admin", "Standard"). ' + + 'If omitted, the current user\'s profile is updated.', + }, + fields: { + type: 'array', + description: + 'List of field permission entries to apply to the profile.', + items: { + type: 'object', + properties: { + field: { + type: 'string', + description: + 'The fully qualified field API name in the format "ObjectType.FieldName" ' + + '(e.g. "Account.Name", "Contact.Phone").', + }, + access: { + type: 'string', + enum: ['editable', 'readable', 'none'], + description: + '"editable" grants read + write, "readable" grants read-only, "none" removes all access.', + }, + }, + required: ['field', 'access'], + }, + minItems: 1, + }, + }, + required: ['fields'], + }, + }, +]; + +export async function executeProfileTool( + name: string, + args: Record, + localContainer: Container +) { + if (name === 'list_profiles') { + return listProfiles(localContainer); + } + if (name === 'add_fls_to_profile') { + return addFlsToProfile(args, localContainer); + } + throw new Error(`Unknown profile tool: ${name}`); +} + +async function listProfiles(localContainer: Container) { + const profileService = localContainer.get(SalesforceProfileService); + const profiles = await profileService.listProfiles(); + + return { + content: [ + { + type: 'text' as const, + text: + profiles.length > 0 + ? profiles.join('\n') + : 'No profiles found.', + }, + ], + }; +} + +async function addFlsToProfile( + args: Record, + localContainer: Container +) { + const profileName = args['profile'] as string | undefined; + const fields = args['fields'] as Array<{ field: string; access: string }>; + + const profileService = localContainer.get(SalesforceProfileService); + const profile = await profileService.getProfile(profileName); + + for (const { field, access } of fields) { + if (access === 'none') { + profile.removeField(field); + } else { + profile.addField( + field, + access === 'editable' + ? SalesforceFieldPermission.editable + : SalesforceFieldPermission.readable + ); + } + } + + if (!profile.hasChanges) { + return { + content: [ + { + type: 'text' as const, + text: `Profile '${profile.developerName}': no changes needed (permissions already match).`, + }, + ], + }; + } + + const connection = await localContainer + .get(SalesforceConnectionProvider) + .getJsForceConnection(); + await profile.save(connection); + + return { + content: [ + { + type: 'text' as const, + text: [ + `Profile '${profile.developerName}' updated successfully.`, + `Fields updated (${fields.length}):`, + ...fields.map((f) => ` ${f.field} → ${f.access}`), + ].join('\n'), + }, + ], + }; +} diff --git a/packages/mcp/src/tools/schema.ts b/packages/mcp/src/tools/schema.ts new file mode 100644 index 000000000..fc556316d --- /dev/null +++ b/packages/mcp/src/tools/schema.ts @@ -0,0 +1,202 @@ +import type { Container } from '@vlocode/core'; +import { SalesforceSchemaService } from '@vlocode/salesforce'; +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; + +export const schemaTools: Tool[] = [ + { + name: 'list_sobjects', + description: + 'List all Salesforce objects (SObjects) available in the connected org. ' + + 'Returns the API names and labels for all standard and custom objects.', + inputSchema: { + type: 'object', + properties: { + filter: { + type: 'string', + description: + 'Optional case-insensitive substring filter applied to object API names and labels. ' + + 'For example, "Account" returns objects whose name or label contains "Account".', + }, + }, + }, + }, + { + name: 'describe_sobject', + description: + 'Describe a Salesforce object (SObject) including all its fields, relationships, ' + + 'and metadata. Useful for understanding the structure of a Salesforce record type.', + inputSchema: { + type: 'object', + properties: { + objectType: { + type: 'string', + description: + 'The API name of the Salesforce object to describe (e.g. "Account", "Contact", "vlocity_cmt__OmniScript__c").', + }, + includeFields: { + type: 'boolean', + description: + 'Include field-level details (type, label, required, etc.) in the result. Defaults to true.', + default: true, + }, + }, + required: ['objectType'], + }, + }, + { + name: 'describe_sobject_field', + description: + 'Describe a specific field on a Salesforce object. ' + + 'Returns detailed metadata including type, label, picklist values, and relationship details.', + inputSchema: { + type: 'object', + properties: { + objectType: { + type: 'string', + description: 'The API name of the Salesforce object (e.g. "Account").', + }, + fieldName: { + type: 'string', + description: 'The API name of the field to describe (e.g. "Name", "OwnerId").', + }, + }, + required: ['objectType', 'fieldName'], + }, + }, +]; + +export async function executeSchemaTool( + name: string, + args: Record, + localContainer: Container +) { + if (name === 'list_sobjects') { + return listSObjects(args, localContainer); + } + if (name === 'describe_sobject') { + return describeSObject(args, localContainer); + } + if (name === 'describe_sobject_field') { + return describeSObjectField(args, localContainer); + } + throw new Error(`Unknown schema tool: ${name}`); +} + +async function listSObjects( + args: Record, + localContainer: Container +) { + const filter = (args['filter'] as string | undefined)?.toLowerCase(); + const schemaService = localContainer.get(SalesforceSchemaService); + const sobjects = await schemaService.describeSObjects(); + + const filtered = filter + ? sobjects.filter( + (o) => + o.name.toLowerCase().includes(filter) || + o.label.toLowerCase().includes(filter) + ) + : sobjects; + + const lines = filtered.map((o) => `${o.name} — ${o.label}`); + + return { + content: [ + { + type: 'text' as const, + text: + lines.length > 0 + ? lines.join('\n') + : 'No objects found matching the filter.', + }, + ], + }; +} + +async function describeSObject( + args: Record, + localContainer: Container +) { + const objectType = args['objectType'] as string; + const includeFields = (args['includeFields'] as boolean) ?? true; + + const schemaService = localContainer.get(SalesforceSchemaService); + const describe = await schemaService.describeSObject(objectType); + + const lines: string[] = [ + `Object: ${describe.name}`, + `Label: ${describe.label}`, + `Label (plural): ${describe.labelPlural}`, + `Key prefix: ${describe.keyPrefix ?? 'n/a'}`, + `Custom: ${describe.custom}`, + `Queryable: ${describe.queryable}`, + `Createable: ${describe.createable}`, + `Updateable: ${describe.updateable}`, + `Deletable: ${describe.deletable}`, + `Fields: ${describe.fields.length}`, + ]; + + if (includeFields) { + lines.push('', 'Fields:'); + for (const field of describe.fields) { + const attrs: string[] = [field.type]; + if (field.length) attrs.push(`length: ${field.length}`); + if (!field.nillable) attrs.push('required'); + if (field.externalId) attrs.push('externalId'); + if (field.unique) attrs.push('unique'); + lines.push( + ` ${field.name} (${field.label}) [${attrs.join(', ')}]` + ); + } + } + + return { + content: [{ type: 'text' as const, text: lines.join('\n') }], + }; +} + +async function describeSObjectField( + args: Record, + localContainer: Container +) { + const objectType = args['objectType'] as string; + const fieldName = args['fieldName'] as string; + + const schemaService = localContainer.get(SalesforceSchemaService); + const field = await schemaService.describeSObjectField(objectType, fieldName); + + const lines: string[] = [ + `Field: ${field.name}`, + `Label: ${field.label}`, + `Type: ${field.type}`, + `Length: ${field.length ?? 'n/a'}`, + `Required: ${!field.nillable}`, + `Unique: ${field.unique}`, + `External ID: ${field.externalId}`, + `Createable: ${field.createable}`, + `Updateable: ${field.updateable}`, + `Filterable: ${field.filterable}`, + `Sortable: ${field.sortable}`, + `Calculated: ${field.calculated}`, + ]; + + if (field.referenceTo && field.referenceTo.length > 0) { + lines.push(`References: ${field.referenceTo.join(', ')}`); + lines.push(`Relationship name: ${field.relationshipName ?? 'n/a'}`); + } + + if (field.picklistValues && field.picklistValues.length > 0) { + lines.push('', 'Picklist values:'); + for (const pv of field.picklistValues) { + const parts = [pv.value]; + if (pv.label !== pv.value) parts.push(`(${pv.label})`); + if (pv.defaultValue) parts.push('[default]'); + if (!pv.active) parts.push('[inactive]'); + lines.push(` ${parts.join(' ')}`); + } + } + + return { + content: [{ type: 'text' as const, text: lines.join('\n') }], + }; +} diff --git a/packages/mcp/tsconfig.json b/packages/mcp/tsconfig.json new file mode 100644 index 000000000..b49ae4944 --- /dev/null +++ b/packages/mcp/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "module": "es2022", + "target": "es2022", + "moduleResolution": "node", + "outDir": "dist", + "baseUrl": "src", + "declaration": true, + "noEmit": true, + "declarationMap": true, + "allowImportingTsExtensions": true + }, + "exclude": [ + "../../build/**/*.ts" + ], + "include": [ + "src/**/*", + "../../build/**/*.ts", + "tsdown.config.mts" + ] +} diff --git a/packages/mcp/tsdown.config.mts b/packages/mcp/tsdown.config.mts new file mode 100644 index 000000000..7ff4077ca --- /dev/null +++ b/packages/mcp/tsdown.config.mts @@ -0,0 +1,42 @@ +import { defineConfig } from 'tsdown'; + +import fileTypesPatch from '../../build/patches/file-types.ts'; +import vlocityPatch from '../../build/patches/vlocity.ts'; +import dtracePatch from '../../build/patches/dtrace.ts'; +import jsdomPatch from '../../build/patches/jsdom.ts'; + +const packageExternals: string[] = []; + +export default defineConfig({ + entry: { + 'mcp': './src/index.ts', + }, + outDir: './dist', + format: 'esm', + fixedExtension: true, + external: [...packageExternals], + sourcemap: false, + shims: true, + minify: false, + treeshake: false, + inlineOnly: false, + env: { + NODE_ENV: 'production', + DEBUG: false + }, + nodeProtocol: true, + tsconfig: './tsconfig.json', + inputOptions: { + preserveEntrySignatures: 'strict', + }, + outputOptions: { + keepNames: true, + strictExecutionOrder: true, + }, + plugins: [ + fileTypesPatch(), + vlocityPatch(), + jsdomPatch(), + dtracePatch() + ] +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85c218ce9..e92af9c83 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -326,6 +326,31 @@ importers: specifier: ^29.3.4 version: 29.4.6(@babel/core@7.23.6)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.6))(jest-util@29.7.0)(jest@29.7.0(@types/node@25.2.1)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@25.2.1)(typescript@5.9.3)))(typescript@5.9.3) + packages/mcp: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.27.1 + version: 1.27.1(zod@4.3.6) + devDependencies: + '@vlocode/core': + specifier: workspace:* + version: link:../core + '@vlocode/salesforce': + specifier: workspace:* + version: link:../salesforce + '@vlocode/util': + specifier: workspace:* + version: link:../util + '@vlocode/vlocity': + specifier: workspace:* + version: link:../vlocity + '@vlocode/vlocity-deploy': + specifier: workspace:* + version: link:../vlocity-deploy + shx: + specifier: ^0.3.4 + version: 0.3.4 + packages/omniscript: dependencies: '@vlocode/core': @@ -1210,6 +1235,12 @@ packages: resolution: {integrity: sha512-2JYy//YE2YINTe21hpdVMBNc7aYFkgDeY9JUz/BCjFZmYLn0UjGaCc4BpTcMGXNJwuqoUenw2WGOFGHsJqlIDw==} engines: {node: '>=6.0.0'} + '@hono/node-server@1.19.11': + resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -1571,6 +1602,16 @@ packages: '@microsoft/applicationinsights-web-snippet@1.0.1': resolution: {integrity: sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ==} + '@modelcontextprotocol/sdk@1.27.1': + resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -2635,6 +2676,10 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -2678,6 +2723,14 @@ packages: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -2968,6 +3021,10 @@ packages: bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -3052,6 +3109,10 @@ packages: '@75lb/nature': optional: true + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -3344,6 +3405,14 @@ packages: constant-case@3.0.4: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + continuation-local-storage@3.2.1: resolution: {integrity: sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==} @@ -3382,6 +3451,14 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + core-js-pure@3.26.0: resolution: {integrity: sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA==} @@ -3394,6 +3471,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} @@ -3602,6 +3683,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + detect-indent@7.0.1: resolution: {integrity: sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==} engines: {node: '>=12.20'} @@ -3718,6 +3803,9 @@ packages: resolution: {integrity: sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==} engines: {ecmascript: '>= es5', node: '>=4'} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.286: resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} @@ -3744,6 +3832,10 @@ packages: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} @@ -3833,6 +3925,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -3956,6 +4051,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -3967,6 +4066,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + execa@0.10.0: resolution: {integrity: sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==} engines: {node: '>=4'} @@ -3998,6 +4105,16 @@ packages: exponential-backoff@3.1.1: resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + express-rate-limit@8.3.1: + resolution: {integrity: sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -4119,6 +4236,10 @@ packages: resolution: {integrity: sha512-iXfpBqHrr9v/Kv/I25kQ4fhr2qu9Uwedh3BK1K8c7l0YGNc7PCsZcsmM8Og78bYgrJhlPYkiEiBOroxUAf0XgA==} hasBin: true + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-up@3.0.0: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} engines: {node: '>=6'} @@ -4177,6 +4298,14 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -4455,6 +4584,10 @@ packages: resolution: {integrity: sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==} hasBin: true + hono@4.12.8: + resolution: {integrity: sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==} + engines: {node: '>=16.9.0'} + hookable@6.0.1: resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} @@ -4487,6 +4620,10 @@ packages: http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + http-parser-js@0.5.6: resolution: {integrity: sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==} @@ -4642,10 +4779,18 @@ packages: resolution: {integrity: sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==} engines: {node: '>=4'} + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -4788,6 +4933,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -5087,6 +5235,9 @@ packages: node-notifier: optional: true + jose@6.2.1: + resolution: {integrity: sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==} + js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -5166,6 +5317,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -5454,6 +5608,10 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + mem@4.3.0: resolution: {integrity: sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==} engines: {node: '>=6'} @@ -5467,6 +5625,10 @@ packages: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -5490,6 +5652,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -5812,9 +5978,6 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-inspect@1.13.1: - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} - object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -5853,6 +6016,10 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -6044,6 +6211,10 @@ packages: parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} @@ -6092,6 +6263,9 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -6137,6 +6311,10 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -6240,6 +6418,10 @@ packages: protocols@2.0.2: resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -6268,6 +6450,10 @@ packages: resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} engines: {node: '>=0.6'} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + qs@6.5.3: resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} engines: {node: '>=0.6'} @@ -6285,6 +6471,14 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc-config-loader@4.1.3: resolution: {integrity: sha512-kD7FqML7l800i6pS6pvLyIE2ncbk9Du8Q0gp/4hMPhJU6ZxApkoLcGD8ZeqgiAlfwZ6BlETq6qqe+12DUL207w==} @@ -6481,6 +6675,10 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + rrweb-cssom@0.6.0: resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} @@ -6596,6 +6794,10 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + sentence-case@3.0.4: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} @@ -6603,6 +6805,10 @@ packages: resolution: {integrity: sha512-hJWMZRwP75ocoBM+1/YaCsvS0j5MTPeBHJkS2/wruehl9xwtX30HlDF1Gt6UZ8HHHY8SJa2/IL+jo+JJCd59rA==} engines: {node: '>=0.4.0'} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -6621,6 +6827,9 @@ packages: setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sha.js@2.4.12: resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} engines: {node: '>= 0.10'} @@ -6670,9 +6879,6 @@ packages: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} - side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} - side-channel@1.1.0: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} @@ -6797,6 +7003,10 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} @@ -7057,6 +7267,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -7230,6 +7444,10 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} @@ -7358,6 +7576,10 @@ packages: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unplugin-unused@0.5.7: resolution: {integrity: sha512-quvqrHs6mPhk1hjbGwMoypXfagveue+/22dtgDrEzc2K9485ZAsnVfq7iYIgv7FwooiRa6+HDaLWgK2IavN+2g==} engines: {node: '>=20.19.0'} @@ -7433,6 +7655,10 @@ packages: resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} engines: {node: ^20.17.0 || >=22.9.0} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + verror@1.10.0: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} @@ -7769,6 +7995,14 @@ packages: resolution: {integrity: sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==} engines: {node: '>= 10'} + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + snapshots: '@ampproject/remapping@2.2.1': @@ -8289,7 +8523,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.6 + debug: 4.4.3(supports-color@8.1.1) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -8322,10 +8556,14 @@ snapshots: supports-color: 5.5.0 tslib: 1.14.1 + '@hono/node-server@1.19.11(hono@4.12.8)': + dependencies: + hono: 4.12.8 + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.6 + debug: 4.4.3(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -8932,6 +9170,28 @@ snapshots: '@microsoft/applicationinsights-web-snippet@1.0.1': {} + '@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.11(hono@4.12.8) + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.3.1(express@5.2.1) + hono: 4.12.8 + jose: 6.2.1 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) + transitivePeerDependencies: + - supports-color + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.8.1 @@ -10200,6 +10460,11 @@ snapshots: dependencies: event-target-shim: 5.0.1 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-import-attributes@1.9.5(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -10236,6 +10501,10 @@ snapshots: agent-base@7.1.3: {} + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -10420,7 +10689,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.3 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 @@ -10598,6 +10867,20 @@ snapshots: bluebird@3.7.2: {} + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3(supports-color@8.1.1) + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} boundary@2.0.0: {} @@ -10682,6 +10965,8 @@ snapshots: byte-size@9.0.1: {} + bytes@3.1.2: {} + cac@6.7.14: {} cacache@20.0.3: @@ -11030,6 +11315,10 @@ snapshots: tslib: 2.8.1 upper-case: 2.0.2 + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + continuation-local-storage@3.2.1: dependencies: async-listener: 0.6.10 @@ -11077,6 +11366,10 @@ snapshots: convert-source-map@2.0.0: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + core-js-pure@3.26.0: {} core-js@3.26.0: {} @@ -11085,6 +11378,11 @@ snapshots: core-util-is@1.0.3: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + crc-32@1.2.2: {} crc32-stream@2.0.0: @@ -11298,6 +11596,8 @@ snapshots: delayed-stream@1.0.0: {} + depd@2.0.0: {} + detect-indent@7.0.1: {} detect-libc@1.0.3: @@ -11407,6 +11707,8 @@ snapshots: dependencies: version-range: 4.15.0 + ee-first@1.1.1: {} + electron-to-chromium@1.5.286: {} emitter-listener@1.1.2: @@ -11425,6 +11727,8 @@ snapshots: empathic@2.0.0: {} + encodeurl@2.0.0: {} + encoding@0.1.13: dependencies: iconv-lite: 0.6.3 @@ -11481,7 +11785,7 @@ snapshots: is-string: 1.0.7 is-typed-array: 1.1.13 is-weakref: 1.0.2 - object-inspect: 1.13.1 + object-inspect: 1.13.4 object-keys: 1.1.1 object.assign: 4.1.5 regexp.prototype.flags: 1.5.2 @@ -11556,7 +11860,7 @@ snapshots: es-define-property@1.0.0: dependencies: - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 es-define-property@1.0.1: {} @@ -11572,7 +11876,7 @@ snapshots: es-set-tostringtag@2.0.3: dependencies: - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -11632,6 +11936,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@1.0.5: {} escape-string-regexp@2.0.0: {} @@ -11793,12 +12099,20 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + event-target-shim@5.0.1: {} eventemitter3@5.0.4: {} events@3.3.0: {} + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + execa@0.10.0: dependencies: cross-spawn: 6.0.6 @@ -11861,6 +12175,44 @@ snapshots: exponential-backoff@3.1.1: {} + express-rate-limit@8.3.1(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3(supports-color@8.1.1) + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extend@3.0.2: {} external-editor@3.1.0: @@ -11997,6 +12349,17 @@ snapshots: xmldom: '@xmldom/xmldom@0.9.8' xpath: 0.0.24 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up@3.0.0: dependencies: locate-path: 3.0.0 @@ -12060,6 +12423,10 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + forwarded@0.2.0: {} + + fresh@2.0.0: {} + fs-constants@1.0.0: {} fs-extra@11.3.3: @@ -12163,7 +12530,7 @@ snapshots: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 get-symbol-description@1.1.0: dependencies: @@ -12285,7 +12652,7 @@ snapshots: gopd@1.0.1: dependencies: - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 gopd@1.2.0: {} @@ -12394,6 +12761,8 @@ snapshots: mkdirp: 0.3.0 nopt: 1.0.10 + hono@4.12.8: {} + hookable@6.0.1: {} hosted-git-info@4.1.0: @@ -12427,6 +12796,14 @@ snapshots: http-cache-semantics@4.1.1: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + http-parser-js@0.5.6: {} http-proxy-agent@7.0.2: @@ -12496,7 +12873,6 @@ snapshots: iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 - optional: true ieee754@1.2.1: {} @@ -12578,7 +12954,7 @@ snapshots: dependencies: es-errors: 1.3.0 hasown: 2.0.2 - side-channel: 1.0.4 + side-channel: 1.1.0 internal-slot@1.1.0: dependencies: @@ -12588,15 +12964,19 @@ snapshots: invert-kv@2.0.0: {} + ip-address@10.1.0: {} + ip-address@9.0.5: dependencies: jsbn: 1.1.0 sprintf-js: 1.1.3 + ipaddr.js@1.9.1: {} + is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 is-array-buffer@3.0.5: dependencies: @@ -12723,6 +13103,8 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} + is-regex@1.1.4: dependencies: call-bind: 1.0.7 @@ -13312,6 +13694,8 @@ snapshots: - supports-color - ts-node + jose@6.2.1: {} + js-tokens@10.0.0: {} js-tokens@4.0.0: {} @@ -13440,6 +13824,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-schema@0.4.0: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -13758,6 +14144,8 @@ snapshots: mdurl@2.0.0: {} + media-typer@1.1.0: {} + mem@4.3.0: dependencies: map-age-cleaner: 0.1.3 @@ -13783,6 +14171,8 @@ snapshots: meow@13.2.0: {} + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -13800,6 +14190,10 @@ snapshots: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mime@2.4.6: {} @@ -14125,8 +14519,6 @@ snapshots: object-assign@4.1.1: {} - object-inspect@1.13.1: {} - object-inspect@1.13.4: {} object-keys@0.4.0: {} @@ -14173,6 +14565,10 @@ snapshots: obug@2.1.1: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -14399,6 +14795,8 @@ snapshots: dependencies: entities: 4.5.0 + parseurl@1.3.3: {} + pascal-case@3.1.2: dependencies: no-case: 3.0.4 @@ -14443,6 +14841,8 @@ snapshots: lru-cache: 11.2.1 minipass: 7.1.3 + path-to-regexp@8.3.0: {} + path-type@4.0.0: {} path-type@6.0.0: {} @@ -14467,6 +14867,8 @@ snapshots: pirates@4.0.6: {} + pkce-challenge@5.0.1: {} + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -14559,6 +14961,11 @@ snapshots: protocols@2.0.2: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} psl@1.8.0: {} @@ -14598,6 +15005,10 @@ snapshots: dependencies: side-channel: 1.1.0 + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + qs@6.5.3: {} quansync@1.0.0: {} @@ -14608,6 +15019,15 @@ snapshots: quick-lru@5.1.1: {} + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + rc-config-loader@4.1.3: dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -14858,6 +15278,16 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.3 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.3 + router@2.2.0: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + rrweb-cssom@0.6.0: {} rrweb-cssom@0.7.1: {} @@ -14875,7 +15305,7 @@ snapshots: safe-array-concat@1.1.2: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 has-symbols: 1.0.3 isarray: 2.0.5 @@ -15008,6 +15438,22 @@ snapshots: semver@7.7.4: {} + send@1.2.1: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + sentence-case@3.0.4: dependencies: no-case: 3.0.4 @@ -15016,6 +15462,15 @@ snapshots: sequin@0.1.1: {} + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + set-blocking@2.0.0: {} set-function-length@1.2.2: @@ -15023,7 +15478,7 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 gopd: 1.0.1 has-property-descriptors: 1.0.2 @@ -15042,6 +15497,8 @@ snapshots: setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} + sha.js@2.4.12: dependencies: inherits: 2.0.4 @@ -15094,12 +15551,6 @@ snapshots: object-inspect: 1.13.4 side-channel-map: 1.0.1 - side-channel@1.0.4: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - object-inspect: 1.13.1 - side-channel@1.1.0: dependencies: es-errors: 1.3.0 @@ -15249,6 +15700,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + statuses@2.0.2: {} + stdin-discarder@0.2.2: {} stdout-stderr@0.1.13: @@ -15536,6 +15989,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + tough-cookie@4.1.4: dependencies: psl: 1.8.0 @@ -15742,6 +16197,12 @@ snapshots: type-fest@4.41.0: {} + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + typed-array-buffer@1.0.2: dependencies: call-bind: 1.0.7 @@ -15899,6 +16360,8 @@ snapshots: universalify@2.0.0: {} + unpipe@1.0.0: {} + unplugin-unused@0.5.7: dependencies: empathic: 2.0.0 @@ -15980,6 +16443,8 @@ snapshots: validate-npm-package-name@7.0.2: {} + vary@1.1.2: {} + verror@1.10.0: dependencies: assert-plus: 1.0.0 @@ -16380,3 +16845,9 @@ snapshots: archiver-utils: 2.1.0 compress-commons: 4.1.1 readable-stream: 3.6.0 + + zod-to-json-schema@3.25.1(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} diff --git a/tsconfig.json b/tsconfig.json index 4e99f413d..5aa18a0bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,8 @@ "@vlocode/salesforce": ["./packages/salesforce/src"], "@vlocode/vlocity": ["./packages/vlocity/src"], "@vlocode/vlocity-deploy": ["./packages/vlocity-deploy/src"], - "@vlocode/omniscript": ["./packages/omniscript/src"] + "@vlocode/omniscript": ["./packages/omniscript/src"], + "@vlocode/mcp": ["./packages/mcp/src"] } } }