Skip to main content
Glama
bulkOperations.ts5.92 kB
/** * Bulk Operations Utility for Local File Tools * * This module provides utilities for processing and formatting bulk query operations * for local file system tools. It follows the pattern established in octocode-mcp * but simplified for local operations. * * ## Public API * - `executeBulkOperation()` - Primary function for tools to process bulk queries * - `processBulkQueries()` - Lower-level parallel query processor * - `createErrorResult()` - Helper for creating error results * * @module bulkOperations */ import { type CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { settleAll } from './promiseUtils.js'; import { createResponseFormat } from './responses.js'; import { validateBulkTokenLimit } from './tokenValidation.js'; import { RESPONSE_KEY_PRIORITY } from '../scheme/responsePriority.js'; import { ERROR_CODES, type ErrorCode } from '../errors/errorCodes.js'; /** * Base result interface with status field */ interface BaseResult { status: 'hasResults' | 'empty' | 'error'; researchGoal?: string; reasoning?: string; error?: string; errorCode?: ErrorCode; hints?: readonly string[]; } /** * Configuration for bulk response formatting */ interface BulkResponseConfig { toolName: string; } /** * Flat query result structure for response * Clean structure: only status and data (no query echo, no duplicated fields) */ interface FlatQueryResult<TResult> { status: 'hasResults' | 'empty' | 'error'; data: TResult; } /** * Bulk operation response structure * Clean structure: no duplicate hints at top level (they're in each result's data) */ interface BulkOperationResponse<TResult> { results: Array<FlatQueryResult<TResult>>; [key: string]: unknown; // Allow additional properties for ToolResponse compatibility } /** * Execute bulk queries and format the response in a single operation. * This is the primary function that tools should use. * * @param queries - Array of query objects to process * @param processor - Async function that processes each query * @param config - Configuration for response formatting (toolName) * @returns Formatted MCP CallToolResult ready to send to client * * @example * return executeBulkOperation( * queries, * searchContent, * { toolName: TOOL_NAMES.LOCAL_SEARCH_CONTENT } * ); */ export async function executeBulkOperation< TQuery extends object, TResult extends BaseResult, >( queries: Array<TQuery>, processor: (query: TQuery) => Promise<TResult>, config: BulkResponseConfig ): Promise<CallToolResult> { const results = await processBulkQueries<TQuery, TResult>(queries, processor); return formatBulkResponse<TResult>(results, config); } /** * Processes multiple queries in parallel with error isolation. * Lower-level function - prefer using executeBulkOperation() instead. * * @param queries - Array of query objects to process * @param processFn - Async function that processes each query * @param options - Optional configuration for error handling * @returns Array of results (including error results) */ export async function processBulkQueries<TQuery, TResult extends BaseResult>( queries: TQuery[], processFn: (query: TQuery) => Promise<TResult>, options: { concurrency?: number; continueOnError?: boolean; } = {} ): Promise<TResult[]> { const { continueOnError = true } = options; const promises = queries.map((query) => processFn(query)); const results = await settleAll(promises); const finalResults: TResult[] = []; for (const [index, result] of results.entries()) { if (result.status === 'fulfilled') { finalResults.push(result.value); } else { if (!continueOnError) { throw result.reason; } const errorResult = createErrorResult<TResult>( result.reason, queries[index] as Partial<TResult> ); finalResults.push(errorResult); } } return finalResults; } /** * Creates an error result for failed queries. * Helper function for manual error handling. * * @param error - Error object or message * @param baseData - Base data to include in the error result (like query fields) * @returns Error result object */ export function createErrorResult<T extends BaseResult>( _error: unknown, baseData: Partial<T> = {} ): T { return { ...baseData, status: 'error', errorCode: ERROR_CODES.QUERY_EXECUTION_FAILED, } as T; } /** * Format bulk query results into an MCP CallToolResult. * Internal function used by executeBulkOperation(). */ function formatBulkResponse<TResult extends BaseResult>( results: Array<TResult>, config: BulkResponseConfig ): CallToolResult { const flatResults: Array<FlatQueryResult<TResult>> = []; for (let i = 0; i < results.length; i++) { const result = results[i]; flatResults.push({ status: result.status, data: result, }); } const responseData: BulkOperationResponse<TResult> = { results: flatResults, }; const formattedText = createResponseFormat(responseData, RESPONSE_KEY_PRIORITY); // CRITICAL: Validate final response size to prevent exceeding MCP 25K token limit const validation = validateBulkTokenLimit( formattedText, config.toolName, flatResults.length ); if (!validation.isValid) { const errorResponse = { errorCode: ERROR_CODES.RESPONSE_TOO_LARGE, hints: validation.hints || [], }; return { content: [ { type: 'text' as const, text: createResponseFormat(errorResponse, ['errorCode', 'hints']), }, ], isError: true, }; } return { content: [ { type: 'text' as const, text: formattedText, }, ], isError: false, }; } // Note: Removed extractResultData, extractField, and filterQueryFields // These are no longer needed with the clean structure that keeps all data intact

Latest Blog Posts

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/bgauryy/local-explorer-mcp'

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