Skip to main content
Glama

SFCC Development MCP Server

by taurgis
base-handler.ts7.59 kB
import { Logger } from '../../utils/logger.js'; import { SFCCConfig } from '../../types/types.js'; export interface HandlerContext { logger: Logger; config: SFCCConfig; capabilities: { canAccessLogs: boolean; canAccessOCAPI: boolean; }; } export interface ToolExecutionResult { content: Array<{ type: 'text'; text: string }>; isError?: boolean; } export interface ToolArguments { [key: string]: any; } /** * Generic tool specification interface * Defines the contract for declarative tool configuration */ export interface GenericToolSpec<TArgs = ToolArguments, TResult = any> { /** Optional validation function for tool arguments */ validate?: (args: TArgs, toolName: string) => void; /** Optional function to apply default values to arguments */ defaults?: (args: TArgs) => Partial<TArgs>; /** Main execution function for the tool */ exec: (args: TArgs, context: ToolExecutionContext) => Promise<TResult>; /** Function to generate log message for the tool execution */ logMessage: (args: TArgs) => string; } /** * Context provided to tool execution functions * Allows tools to access clients and other resources */ export interface ToolExecutionContext { /** Handler context with configuration and capabilities */ handlerContext: HandlerContext; /** Logger instance for the handler */ logger: any; /** Additional context data that can be provided by concrete handlers */ [key: string]: any; } export class HandlerError extends Error { constructor( message: string, public readonly toolName: string, public readonly code: string = 'HANDLER_ERROR', public readonly details?: any, ) { super(message); this.name = 'HandlerError'; } } export abstract class BaseToolHandler<TToolName extends string = string> { protected context: HandlerContext; protected logger: Logger; private _isInitialized = false; constructor(context: HandlerContext, subLoggerName: string) { this.context = context; this.logger = Logger.getChildLogger(`Handler:${subLoggerName}`); } /** * Abstract method to get tool configuration * Each concrete handler implements this with their specific config */ protected abstract getToolConfig(): Record<TToolName, GenericToolSpec>; /** * Abstract method to get tool name set for O(1) lookup * Each concrete handler implements this with their specific tool set */ protected abstract getToolNameSet(): Set<string>; /** * Abstract method to create execution context * Each concrete handler can provide specialized context */ protected abstract createExecutionContext(): Promise<ToolExecutionContext>; /** * Check if this handler can handle the given tool */ canHandle(toolName: string): boolean { return this.getToolNameSet().has(toolName); } /** * Config-driven tool execution * Handles validation, defaults, execution, and logging uniformly */ async handle(toolName: string, args: ToolArguments, startTime: number): Promise<ToolExecutionResult> { if (!this.canHandle(toolName)) { throw new Error(`Unsupported tool: ${toolName}`); } const toolConfig = this.getToolConfig(); const spec = toolConfig[toolName as TToolName]; if (!spec) { throw new Error(`No configuration found for tool: ${toolName}`); } return this.executeWithLogging( toolName, startTime, () => this.dispatchTool(spec, args), spec.logMessage(this.applyDefaults(spec, args)), ); } /** * Generic tool dispatch using configuration * Handles validation, defaults, and execution */ private async dispatchTool(spec: GenericToolSpec, args: ToolArguments): Promise<any> { const context = await this.createExecutionContext(); const processedArgs = this.createValidatedArgs(spec, args, 'tool'); return spec.exec(processedArgs, context); } /** * Apply default values to arguments */ private applyDefaults(spec: GenericToolSpec, args: ToolArguments): ToolArguments { if (!spec.defaults) { return args; } const defaults = spec.defaults(args); return { ...args, ...defaults }; } /** * Create validated arguments with defaults applied */ private createValidatedArgs(spec: GenericToolSpec, args: ToolArguments, toolName: string): ToolArguments { // Apply defaults first const processedArgs = this.applyDefaults(spec, args); // Validate if validator exists if (spec.validate) { spec.validate(processedArgs, toolName); } return processedArgs; } /** * Initialize the handler (lazy initialization) */ protected async initialize(): Promise<void> { if (this._isInitialized) { return; } await this.onInitialize(); this._isInitialized = true; } /** * Override this method for custom initialization logic */ protected async onInitialize(): Promise<void> { // Default: no-op } /** * Clean up resources when handler is destroyed */ async dispose(): Promise<void> { await this.onDispose(); this._isInitialized = false; } /** * Override this method for custom cleanup logic */ protected async onDispose(): Promise<void> { // Default: no-op } /** * Validate required arguments */ protected validateArgs(args: ToolArguments, required: string[], toolName: string): void { for (const field of required) { if (!args?.[field]) { throw new HandlerError( `${field} is required`, toolName, 'MISSING_ARGUMENT', { required, provided: Object.keys(args || {}) }, ); } } } /** * Create a standardized response */ protected createResponse(data: any, stringify: boolean = true): ToolExecutionResult { return { content: [ { type: 'text', text: stringify ? JSON.stringify(data, null, 2) : data }, ], isError: false, }; } /** * Create an error response */ protected createErrorResponse(error: Error, toolName: string): ToolExecutionResult { this.logger.error(`Error in ${toolName}:`, error); return { content: [ { type: 'text', text: error instanceof HandlerError ? `Error: ${error.message}` : `Error: ${error.message}`, }, ], isError: true, }; } /** * Execute a tool operation with standardized logging and error handling */ protected async executeWithLogging( toolName: string, startTime: number, operation: () => Promise<any>, logMessage?: string, ): Promise<ToolExecutionResult> { try { await this.initialize(); if (logMessage) { this.logger.debug(logMessage); } const result = await operation(); this.logger.timing(toolName, startTime); // Log result metadata for debugging this.logger.debug(`${toolName} completed successfully`, { resultType: typeof result, resultLength: Array.isArray(result) ? result.length : undefined, hasData: result != null, }); return this.createResponse(result); } catch (error) { this.logger.timing(`${toolName}_error`, startTime); return this.createErrorResponse(error as Error, toolName); } } /** * @deprecated Use executeWithLogging instead */ protected async wrap( toolName: string, startTime: number, fn: () => Promise<any>, logMessage?: string, ): Promise<ToolExecutionResult> { return this.executeWithLogging(toolName, startTime, fn, logMessage); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/taurgis/sfcc-dev-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server