/** * Services module providing dependency injection for MCP tools. * * The {@link Services} class is the central dependency container for tools, * providing: * - Pre-resolved B2CInstance for WebDAV/OCAPI operations * - Pre-resolved MRT authentication for Managed Runtime operations * - MRT project/environment configuration * - File system utilities for local operations * * ## Creating Services * * Use {@link Services.fromResolvedConfig} with an already-resolved configuration: * * ```typescript * // In a command that extends BaseCommand * const services = Services.fromResolvedConfig(this.resolvedConfig); * ``` * * ## Resolution Pattern * * B2C instance and MRT auth are resolved for each tool call, so edits to project * configuration are picked up without restarting the server. * * **B2C Instance** (for WebDAV/OCAPI tools): * - Flags (highest priority) merged with dw.json (auto-discovered or via --config) * * **MRT Auth** (for Managed Runtime tools): * 1. `--api-key` flag (oclif also checks `MRT_API_KEY` env var; `SFCC_MRT_API_KEY` also supported) * 2. `~/.mobify` config file (or `~/.mobify--[hostname]` if `--cloud-origin` is set) * * **MRT Origin** (for Managed Runtime API URL): * 1. `--cloud-origin` flag (oclif also checks `MRT_CLOUD_ORIGIN` env var; `SFCC_MRT_CLOUD_ORIGIN` also supported) * 2. `mrtOrigin` field in dw.json * 3. Default: `https://cloud.mobify.com` * * @module services */ import fs from 'node:fs'; import type { B2CInstance } from '@salesforce/b2c-tooling-sdk'; import { type AuthStrategy, type InstanceAuthSummary } from '@salesforce/b2c-tooling-sdk/auth'; import { type ResolvedB2CConfig } from '@salesforce/b2c-tooling-sdk/config'; import { ObjectTypesClient } from '@salesforce/b2c-tooling-sdk/operations/object-types'; import type { ConfigurationResolutionSource, ProjectDirectoryInfo, ToolResolution } from './tools/project-context.js'; import { WebDavClient, type CustomApisClient, type MetricsClient, type ScapiSchemasClient } from '@salesforce/b2c-tooling-sdk/clients'; /** * MRT (Managed Runtime) configuration. * Groups auth, project, environment, and origin settings. */ export interface MrtConfig { /** Pre-resolved auth strategy for MRT API operations */ auth?: AuthStrategy; /** MRT project slug from --project flag or MRT_PROJECT env var */ project?: string; /** MRT environment from --environment flag or MRT_ENVIRONMENT env var */ environment?: string; /** MRT API origin URL from --cloud-origin flag, MRT_CLOUD_ORIGIN env var, or mrtOrigin in dw.json */ origin?: string; } /** * Options for Services constructor (internal). */ export interface ServicesOptions { /** Pre-resolved B2C instance (if configured) */ b2cInstance?: B2CInstance; /** Pre-resolved MRT configuration (auth, project, environment) */ mrtConfig?: MrtConfig; /** Resolved configuration for access to SCAPI settings */ resolvedConfig: ResolvedB2CConfig; /** Project-scoped environment parsed from the effective project's .env file. */ projectEnvironment?: Readonly>; /** Inputs needed to attribute project and primary-configuration selection. */ resolution?: ServicesResolutionInputs; } /** Resolution inputs captured centrally by the MCP command for one tool call. */ export interface ServicesResolutionInputs { /** Effective project directory and its selection source. */ projectDirectory: ProjectDirectoryInfo; /** Primary configuration candidate selected before the SDK resolver runs. */ primaryConfiguration?: { path: string; source: Exclude; }; } /** * Services class that provides utilities for MCP tools. * * Use the static `Services.fromResolvedConfig()` factory method to create * an instance from an already-resolved configuration. * * @example * ```typescript * // In a command that extends BaseCommand * const services = Services.fromResolvedConfig(this.resolvedConfig); * * // Access resolved config * services.b2cInstance; // B2CInstance | undefined * services.mrtConfig.auth; // AuthStrategy | undefined * services.mrtConfig.project; // string | undefined * ``` */ export declare class Services { /** * Pre-resolved B2C instance for WebDAV/OCAPI operations. * Resolved for this tool call from server flags and project configuration. * Undefined if no B2C instance configuration was available. */ readonly b2cInstance?: B2CInstance; /** * Pre-resolved MRT configuration (auth, project, environment, origin). * Resolved for this tool call from server flags and MRT configuration. */ readonly mrtConfig: MrtConfig; /** * Resolved configuration for accessing SCAPI settings. * Provides access to shortCode, tenantId, and OAuth credentials. * @private */ private readonly projectEnvironment; private readonly resolution; private readonly resolvedConfig; constructor(opts: ServicesOptions); /** * Creates a Services instance from an already-resolved configuration. * * @param config - Already-resolved configuration from BaseCommand.resolvedConfig * @returns Services instance with resolved config * * @example * ```typescript * // In a command that extends BaseCommand * const services = Services.fromResolvedConfig(this.resolvedConfig); * ``` */ static fromResolvedConfig(config: ResolvedB2CConfig, projectEnvironment?: Readonly>, resolution?: ServicesResolutionInputs): Services; /** * Suffix for "X required" configuration errors: points at the configuration guide when nothing * loaded, otherwise names the source kinds that did; both suggest config_inspect. */ configHint(): string; /** * Check if a file or directory exists. * * @param targetPath - Path to check * @returns True if exists, false otherwise */ exists(targetPath: string): boolean; /** * Get Basic auth credentials for SDAPI operations (script debugger). * Returns undefined if credentials are not configured. */ getBasicAuthCredentials(): undefined | { hostname: string; username: string; password: string; }; /** * Get Custom APIs client for managing custom SCAPI endpoints. * Requires shortCode, tenantId, and OAuth credentials to be configured. * * @throws Error if shortCode, tenantId, or OAuth credentials are missing * @returns Typed Custom APIs client */ getCustomApisClient(): CustomApisClient; /** * Get the current working directory. */ getCwd(): string; /** * Read an environment variable with the ambient process environment taking * precedence over the project-scoped .env value. */ getEnvironmentVariable(name: string): string | undefined; /** * Get the user's home directory. */ getHomeDir(): string; /** * Describe how the configured instance authenticates and which stored * Account Manager sessions it uses. Reflects the session store at call * time, so a sign-in during a tool call shows up afterwards. */ getInstanceAuth(): InstanceAuthSummary; /** * Get Metrics client for accessing SCAPI observability metrics. * Requires shortCode, tenantId, and OAuth credentials to be configured. * * @throws Error if shortCode, tenantId, or OAuth credentials are missing * @returns Typed Metrics client */ getMetricsClient(): MetricsClient; /** * Get an Object Definitions API client for system object type metadata. * Requires shortCode, tenantId, and OAuth credentials to be configured. * * @throws Error if shortCode, tenantId, or OAuth credentials are missing */ getObjectTypesClient(): ObjectTypesClient; /** * Get organization ID for SCAPI API calls. * Ensures the tenant ID has the required f_ecom_ prefix. * * @throws Error if tenantId is not configured * @returns Organization ID with f_ecom_ prefix */ getOrganizationId(): string; /** * Get OS platform information. */ getPlatform(): NodeJS.Platform; /** * Get compact project and selected-configuration provenance for MCP results. * Detailed source graphs remain available through {@link getResolvedConfig} * for the config_inspect tool. */ getResolution(): ToolResolution; /** * Get the resolved configuration (values, sources, warnings). * * Exposed for the `config_inspect` tool so agents can see the effective, * source-attributed configuration the server resolved. Callers displaying * these values must redact secrets (see `redactConfigValues`). * * @returns The resolved B2C configuration */ getResolvedConfig(): ResolvedB2CConfig; /** * Get SCAPI Schemas client for discovering available SCAPI APIs. * Requires shortCode, tenantId, and OAuth credentials to be configured. * * @throws Error if shortCode, tenantId, or OAuth credentials are missing * @returns Typed SCAPI Schemas client */ getScapiSchemasClient(): ScapiSchemasClient; /** * Get SCAPI shortCode from configuration. * Returns undefined if not configured. * * @returns shortCode or undefined */ getShortCode(): string | undefined; /** * Get tenant ID from configuration. * Returns undefined if not configured. * * @returns tenantId or undefined */ getTenantId(): string | undefined; /** * Get system temporary directory. */ getTmpDir(): string; /** * Get WebDAV client for file operations on B2C instances. * Requires hostname and WebDAV credentials to be configured. * * @throws Error if hostname or B2C instance is missing * @returns WebDAV client instance */ getWebDavClient(): WebDavClient; /** * Join path segments. * * @param segments - Path segments to join * @returns Joined path */ joinPath(...segments: string[]): string; /** * List directory contents. * * @param dirPath - Directory path to list * @returns Array of directory entries */ listDirectory(dirPath: string): fs.Dirent[]; /** * Read a file from the filesystem. * * @param filePath - Path to the file * @param encoding - File encoding (default: utf8) * @returns File contents as a string */ readFile(filePath: string, encoding?: 'ascii' | 'base64' | 'hex' | 'latin1' | 'utf8'): string; /** * Resolve a path relative to the current working directory. * * @param segments - Path segments to join and resolve * @returns Absolute path */ resolvePath(...segments: string[]): string; /** * Resolve the effective project directory for a tool call, reporting which * source it came from. * * MCP clients disagree on the working directory a stdio server is spawned * with (Claude Code / Cursor often use the user's home directory rather than * the open project — see https://agent-plugins.org/plugin-authors/mcp-servers), * so the resolved value is deliberately explicit. Precedence: * * 1. `override` — a per-call `projectDirectory` tool argument (highest) * 2. `projectDirectory` from `--project-directory` / `SFCC_PROJECT_DIRECTORY` * 3. `process.cwd()` (fallback; unreliable across clients) * * Tools should surface the returned `{path, source}` in their output so the * agent can see which directory was used when it did not pass one explicitly. * * The `override` and configured values are returned as-supplied (not * re-resolved against cwd); callers pass absolute paths, and `path.resolve` * would otherwise drive-prefix a POSIX-style path on Windows. * * @param override - Optional explicit project directory from a tool argument * @returns The project directory and the source it was resolved from */ resolveProjectDirectory(override?: string): { path: string; source: 'argument' | 'config' | 'cwd'; }; /** * Resolve a path relative to the project directory. * If path is not supplied, returns the project directory. * Relative and absolute paths must resolve inside the project directory; * `..` segments or absolute paths that escape it are rejected. This check is * lexical; callers that read or create files should also confirm the real * path (see `resolveLocalPath` in the WebDAV tools) so symlinks cannot escape. * * An optional explicit project-directory override (typically a per-call * `projectDirectory` tool argument) takes precedence over the configured * project directory and cwd — see {@link Services.resolveProjectDirectory}. * * @param pathArg - Optional path to resolve * @param projectDirectoryOverride - Optional explicit project directory to resolve against * @returns Resolved absolute path * @throws Error if the path resolves outside the project directory */ resolveWithProjectDirectory(pathArg?: string, projectDirectoryOverride?: string): string; /** Run a tool with its resolved safety policy, without changing other calls. */ runWithSafety(callback: () => T): T; /** * Get file or directory stats. * * @param targetPath - Path to get stats for * @returns File stats object */ stat(targetPath: string): fs.Stats; /** * Get OAuth strategy from resolved configuration. * Mirrors the pattern from OAuthCommand.getOAuthStrategy(). * * @throws Error if OAuth credentials are not configured * @returns OAuth auth strategy * @private */ private getOAuthStrategy; }