99 *
1010 * This module provides utilities for creating standardized MCP tools that:
1111 * - Validate input using Zod schemas
12- * - Inject pre-resolved B2CInstance for WebDAV/OCAPI operations (requiresInstance)
13- * - Inject pre-resolved MRT auth for MRT API operations (requiresMrtAuth)
12+ * - Inject loaded B2CInstance for WebDAV/OCAPI operations (requiresInstance)
13+ * - Inject loaded MRT auth for MRT API operations (requiresMrtAuth)
1414 * - Format output consistently (textResult, jsonResult, errorResult)
1515 *
1616 * ## Configuration Resolution
1717 *
18- * Both B2C instance and MRT auth are resolved once at server startup via
19- * {@link Services.fromResolvedConfig} and reused for all tool calls :
18+ * Both B2C instance and MRT auth are loaded before each tool call via
19+ * a loader function that calls {@link Services.fromResolvedConfig}:
2020 *
21- * - **B2CInstance**: Resolved from flags + dw.json. Available when `requiresInstance: true`.
22- * - **MRT Auth**: Resolved from --api-key → SFCC_MRT_API_KEY → ~/.mobify. Available when `requiresMrtAuth: true`.
21+ * - **B2CInstance**: Loaded from flags + dw.json on each call . Available when `requiresInstance: true`.
22+ * - **MRT Auth**: Loaded from --api-key → SFCC_MRT_API_KEY → ~/.mobify on each call . Available when `requiresMrtAuth: true`.
2323 *
24- * This "resolve eagerly at startup " pattern provides:
25- * - Fail-fast behavior (configuration errors surface at startup )
26- * - Consistent mental model (both resolved the same way)
27- * - Better performance (no resolution on each tool call)
24+ * This "load on each call " pattern provides:
25+ * - Fresh configuration on each tool invocation (picks up changes to config files )
26+ * - Consistent mental model (both loaded the same way)
27+ * - Tools can respond to configuration changes without server restart
2828 *
2929 * @module tools/adapter
3030 *
4848 *
4949 * @example MRT tool (MRT API)
5050 * ```typescript
51- * // Services created from already-resolved config at startup
52- * const services = Services.fromResolvedConfig(this.resolvedConfig);
51+ * // Loader function that loads config and creates Services on each tool call
52+ * const loadServices = () => {
53+ * const config = this.loadConfiguration();
54+ * return Services.fromResolvedConfig(config);
55+ * };
5356 *
5457 * const mrtTool = createToolAdapter({
5558 * name: 'mrt_bundle_push',
5962 * inputSchema: {
6063 * projectSlug: z.string().describe('MRT project slug'),
6164 * },
62- * execute: async (args, { mrtAuth }) => {
63- * const result = await pushBundle({ projectSlug: args.projectSlug }, mrtAuth );
65+ * execute: async (args, { mrtConfig }) => {
66+ * const result = await pushBundle({ projectSlug: args.projectSlug }, mrtConfig.auth );
6467 * return result;
6568 * },
6669 * formatOutput: (output) => jsonResult(output),
67- * }, services );
70+ * }, loadServices );
6871 * ```
6972 */
7073
@@ -87,7 +90,7 @@ export interface ToolExecutionContext {
8790
8891 /**
8992 * MRT configuration (auth, project, environment, origin).
90- * Pre-resolved at server startup .
93+ * Loaded before each tool call .
9194 * Only populated when requiresMrtAuth is true.
9295 */
9396 mrtConfig ?: MrtConfig ;
@@ -222,31 +225,36 @@ function formatZodErrors(error: z.ZodError): string {
222225 * @template TInput - The validated input type (inferred from inputSchema)
223226 * @template TOutput - The output type from the execute function
224227 * @param options - Tool adapter configuration
225- * @param services - Services instance for dependency injection
228+ * @param loadServices - Function that loads configuration and returns Services instance
226229 * @returns An McpTool ready for registration
227230 *
228231 * @example
229232 * ```typescript
230233 * import { z } from 'zod';
231234 * import { createToolAdapter, jsonResult, errorResult } from './adapter.js';
232235 *
236+ * const loadServices = () => {
237+ * const config = this.loadConfiguration();
238+ * return Services.fromResolvedConfig(config);
239+ * };
240+ *
233241 * const listCodeVersionsTool = createToolAdapter({
234242 * name: 'code_version_list',
235243 * description: 'List all code versions on the instance',
236244 * toolsets: ['CARTRIDGES'],
237245 * inputSchema: {},
238- * execute: async (_args, { instance }) => {
239- * const result = await instance .ocapi.GET('/code_versions', {});
246+ * execute: async (_args, { b2cInstance }) => {
247+ * const result = await b2cInstance .ocapi.GET('/code_versions', {});
240248 * if (result.error) throw new Error(result.error.message);
241249 * return result.data;
242250 * },
243251 * formatOutput: (data) => jsonResult(data),
244- * }, services );
252+ * }, loadServices );
245253 * ```
246254 */
247255export function createToolAdapter < TInput , TOutput > (
248256 options : ToolAdapterOptions < TInput , TOutput > ,
249- services : Services ,
257+ loadServices : ( ) => Services ,
250258) : McpTool {
251259 const {
252260 name,
@@ -279,7 +287,10 @@ export function createToolAdapter<TInput, TOutput>(
279287 const args = parseResult . data as TInput ;
280288
281289 try {
282- // 2. Get B2CInstance if required (pre-resolved at startup)
290+ // 2. Load Services to get fresh configuration (re-reads config files)
291+ const services = loadServices ( ) ;
292+
293+ // 3. Get B2CInstance if required (loaded on each call)
283294 let b2cInstance : B2CInstance | undefined ;
284295 if ( requiresInstance ) {
285296 if ( ! services . b2cInstance ) {
@@ -290,7 +301,7 @@ export function createToolAdapter<TInput, TOutput>(
290301 b2cInstance = services . b2cInstance ;
291302 }
292303
293- // 3 . Get MRT config if required (pre-resolved at startup )
304+ // 4 . Get MRT config if required (loaded on each call )
294305 let mrtConfig : ToolExecutionContext [ 'mrtConfig' ] ;
295306 if ( requiresMrtAuth ) {
296307 if ( ! services . mrtConfig . auth ) {
@@ -306,15 +317,15 @@ export function createToolAdapter<TInput, TOutput>(
306317 } ;
307318 }
308319
309- // 4 . Execute the operation
320+ // 5 . Execute the operation
310321 const context : ToolExecutionContext = {
311322 b2cInstance,
312323 mrtConfig,
313324 services,
314325 } ;
315326 const output = await execute ( args , context ) ;
316327
317- // 5 . Format output
328+ // 6 . Format output
318329 return formatOutput ( output ) ;
319330 } catch ( error ) {
320331 // Handle execution errors
0 commit comments