@gohcltech/bitbucket-mcp
Version:
Bitbucket integration for Claude via Model Context Protocol
221 lines • 8.34 kB
JavaScript
/**
* @fileoverview Abstract base class for consolidated tools with action routing.
*
* This module provides the foundation for all v2.1 consolidated tools,
* implementing the action-based consolidation pattern where multiple operations
* are grouped into a single tool with an `action` parameter.
*
* Key features:
* - Action routing to appropriate handlers
* - Automatic parameter validation per action
* - Consistent error handling and logging
* - Type-safe with TypeScript generics
* - Extends BaseTool for all inherited functionality
*/
import { BaseTool } from './base-tool.js';
/**
* Abstract base class for consolidated tools with action-based routing.
*
* Consolidated tools group related operations under a single tool with an `action`
* parameter that determines which handler to invoke. This reduces the number of tools
* while maintaining all functionality.
*
* Example: Instead of `list_issues`, `get_issue`, `create_issue`, etc., we have
* a single `manage_issues` tool with action='list', action='get', action='create', etc.
*
* @template TAction - Union type of all valid action values (e.g., 'list' | 'get' | 'create')
*
* @example
* ```typescript
* type IssueAction = 'list' | 'get' | 'create' | 'update' | 'delete';
*
* class ManageIssuesTool extends ConsolidatedBaseTool<IssueAction> {
* protected getActionHandler(action: IssueAction): ToolHandler | null {
* const handlers: Record<IssueAction, ToolHandler> = {
* list: (args) => this.handleListIssues(args),
* get: (args) => this.handleGetIssue(args),
* create: (args) => this.handleCreateIssue(args),
* update: (args) => this.handleUpdateIssue(args),
* delete: (args) => this.handleDeleteIssue(args),
* };
* return handlers[action] || null;
* }
*
* protected validateActionParams(action: IssueAction, args: any): void {
* switch (action) {
* case 'list':
* this.validateRequired(args, ['workspaceSlug', 'repoSlug'], 'list_issues');
* break;
* case 'get':
* this.validateRequired(args, ['workspaceSlug', 'repoSlug', 'issueId'], 'get_issue');
* break;
* // ... etc
* }
* }
* }
* ```
*/
export class ConsolidatedBaseTool extends BaseTool {
/**
* Gets the handlers for this consolidated tool.
*
* Returns a map with the tool name as key, delegating to action-based routing.
* This implementation automatically wraps action handlers so they work with the
* MCP protocol's tool name-based invocation system.
*
* @returns Map from tool name to the main consolidated handler
*/
getHandlers() {
const handlers = new Map();
const tools = this.getTools();
if (tools.length === 0) {
throw new Error('Consolidated tool must define at least one tool in getTools()');
}
const toolName = tools[0].name;
// Create the main handler that dispatches to action-based routing
const mainHandler = async (args) => {
const action = args.action;
if (!action) {
return this.createErrorResponse(new Error('Missing required parameter: action'), toolName);
}
return this.handleAction(toolName, action, args);
};
handlers.set(toolName, mainHandler);
return handlers;
}
/**
* Main handler that routes to action-specific handlers.
*
* This is the primary entry point for tool execution. It:
* 1. Logs the action invocation
* 2. Looks up the appropriate handler for the action
* 3. Validates action-specific parameters
* 4. Executes the handler with error handling
*
* @param toolName - The name of the consolidated tool (e.g., 'manage_issues')
* @param action - The action to perform (e.g., 'list', 'get', 'create')
* @param args - Tool arguments including the action and all action-specific parameters
* @returns Promise resolving to a formatted tool response
*
* @example
* ```typescript
* const response = await tool.handleAction('manage_issues', 'list', {
* action: 'list',
* workspaceSlug: 'my-workspace',
* repoSlug: 'my-repo'
* });
* ```
*/
async handleAction(toolName, action, args) {
// Log the action invocation for debugging and monitoring
this.logActionInvocation(toolName, action, args);
// Get the handler function for this action
const handler = this.getActionHandler(action);
if (!handler) {
return this.createUnknownActionError(action, toolName);
}
// Validate parameters required for this specific action
try {
this.validateActionParams(action, args);
}
catch (error) {
return this.createErrorResponse(error, `${toolName}.${action}`);
}
// Execute the handler with error handling
try {
return await handler(args);
}
catch (error) {
return this.createErrorResponse(error, `${toolName}.${action}`);
}
}
/**
* Validates that required parameters are present in the arguments.
*
* Helper method for parameter validation. Checks that all specified fields
* are present in args and are not null or undefined.
*
* @param args - The arguments object to validate
* @param fields - Array of required field names
* @param operation - Name of the operation (for error messages)
* @throws {Error} If any required fields are missing
*
* @example
* ```typescript
* this.validateRequired(
* args,
* ['workspaceSlug', 'repoSlug', 'issueId'],
* 'get_issue'
* );
* // Throws: "Missing required parameter(s) for get_issue: issueId"
* ```
*/
validateRequired(args, fields, operation) {
const missing = fields.filter(f => args[f] === undefined || args[f] === null);
if (missing.length > 0) {
throw new Error(`Missing required parameter(s) for ${operation}: ${missing.join(', ')}`);
}
}
/**
* Validates that a value is one of the allowed enum values.
*
* Helper method for validating enum-like parameters.
*
* @template T - The type of the enum values
* @param value - The value to validate
* @param enumValues - Array of allowed values
* @param paramName - Name of the parameter (for error messages)
* @throws {Error} If value is not in enumValues
*
* @example
* ```typescript
* this.validateEnum(
* args.action,
* ['list', 'get', 'create'] as const,
* 'action'
* );
* ```
*/
validateEnum(value, enumValues, paramName) {
if (!enumValues.includes(value)) {
throw new Error(`Invalid value for ${paramName}: ${value}. Must be one of: ${enumValues.join(', ')}`);
}
}
/**
* Creates an error response for an unknown action.
*
* Called when an action is not recognized by the tool.
*
* @param action - The unknown action name
* @param toolName - The name of the tool
* @returns Formatted error response
*
* @example
* ```typescript
* return this.createUnknownActionError('invalid_action', 'manage_issues');
* // Returns error response with message about unknown action
* ```
*/
createUnknownActionError(action, toolName) {
return this.createErrorResponse(new Error(`Unknown action '${action}' for tool '${toolName}'`), toolName);
}
/**
* Logs the invocation of an action for monitoring and debugging.
*
* Called at the start of handleAction to record which action was invoked.
* Includes relevant context from args like workspace and repository.
*
* @param toolName - The name of the tool
* @param action - The action being performed
* @param args - The arguments passed to the tool
*/
logActionInvocation(toolName, action, args) {
this.logger.debug(`Executing ${toolName}.${action}`, {
tool: toolName,
action,
workspace: args.workspaceSlug,
repo: args.repoSlug,
});
}
}
//# sourceMappingURL=consolidated-base-tool.js.map