UNPKG

@net3/queuer

Version:

229 lines (197 loc) 7.1 kB
import { httpClient, HttpMethod } from '@activepieces/pieces-common'; interface MCPConfig { mcpServerUrl: string; } /** * Helper class for interacting with MCP servers using the standard MCP protocol. */ export class MCPManager { private static initialized = new Map<string, boolean>(); /** * Parse SSE response format to extract JSON data */ private static parseSSEResponse(sseData: any): any { try { if (typeof sseData === 'string') { // Handle different response formats if (sseData.startsWith('{') || sseData.startsWith('[')) { // Direct JSON response return JSON.parse(sseData); } // Extract JSON from SSE format: "event: message\ndata: {...}\n\n" const lines = sseData.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const jsonStr = line.substring(6); // Remove "data: " prefix if (jsonStr.trim()) { return JSON.parse(jsonStr); } } } // If no SSE data found, try to parse the entire string as JSON if (sseData.trim()) { return JSON.parse(sseData); } } return sseData; // If it's already parsed or not a string } catch (error: any) { console.error('Failed to parse SSE response:', error); console.error('Raw response data:', sseData); throw new Error(`Failed to parse MCP response: ${error.message}`); } } /** * Initialize MCP session if not already initialized */ private static async initializeMCPSession(config: MCPConfig): Promise<boolean> { const { mcpServerUrl } = config; if (this.initialized.get(mcpServerUrl)) { return true; // Already initialized } try { console.log('Initializing MCP session with server:', mcpServerUrl); // Step 1: Initialize const initRequest = { jsonrpc: '2.0', method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: { tools: {} }, clientInfo: { name: 'activepieces-task-queuer', version: '1.0.0' } }, id: `init-${Date.now()}` }; const initResponse = await httpClient.sendRequest({ method: HttpMethod.POST, url: mcpServerUrl, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream' }, body: initRequest, }); if (!initResponse.body) { throw new Error('Empty response from MCP server during initialization'); } const initData = this.parseSSEResponse(initResponse.body); if (initData.error) { throw new Error(`MCP initialization failed: ${JSON.stringify(initData.error)}`); } console.log('MCP initialization successful'); // Step 2: Send initialized notification const initializedNotification = { jsonrpc: '2.0', method: 'initialized', params: {} }; try { await httpClient.sendRequest({ method: HttpMethod.POST, url: mcpServerUrl, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream' }, body: initializedNotification, }); console.log('MCP initialized notification sent successfully'); } catch (notifError: any) { // Notification might not return a response, which is fine console.log('Initialized notification sent (no response expected)'); } this.initialized.set(mcpServerUrl, true); return true; } catch (error: any) { console.error('MCP initialization error:', error); throw new Error(`Failed to initialize MCP session: ${error.message}`); } } /** * List available MCP tools with their schemas */ static async listMCPTools(config: MCPConfig): Promise<any[]> { try { if (!config?.mcpServerUrl) { throw new Error('MCP Server URL is required'); } // Initialize session first const initialized = await this.initializeMCPSession(config); if (!initialized) { throw new Error('Failed to initialize MCP session'); } const toolsRequest = { jsonrpc: '2.0', method: 'tools/list', params: {}, id: `tools-list-${Date.now()}` }; console.log('Sending MCP tools/list request to:', config.mcpServerUrl); const toolsResponse = await httpClient.sendRequest({ method: HttpMethod.POST, url: config.mcpServerUrl, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream' }, body: toolsRequest, }); if (!toolsResponse.body) { throw new Error('Empty response from MCP server'); } const toolsData = this.parseSSEResponse(toolsResponse.body); if (toolsData.error) { throw new Error(`MCP server error: ${JSON.stringify(toolsData.error)}`); } const tools = toolsData.result?.tools || []; console.log(`Retrieved ${tools.length} MCP tools from server`); return tools; } catch (error: any) { console.error('Error listing MCP tools:', error); throw new Error(`Failed to list MCP tools: ${error.message}`); } } /** * Execute an MCP tool using JSON-RPC. Returns `{ success, result?, error? }`. */ static async executeMCPAction(toolName: string, params: any, config: MCPConfig): Promise<{ success: boolean; result?: any; error?: string; }> { try { // Initialize session first const initialized = await this.initializeMCPSession(config); if (!initialized) { return { success: false, error: 'Failed to initialize MCP session' }; } const requestId = `tool-call-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const rpcRequest = { jsonrpc: '2.0', method: 'tools/call', params: { name: toolName, arguments: params }, id: requestId, }; const res = await httpClient.sendRequest({ method: HttpMethod.POST, url: config.mcpServerUrl, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream', }, body: rpcRequest, }); const responseData = this.parseSSEResponse(res.body); if (responseData.error) { return { success: false, error: responseData.error.message || 'MCP error' }; } return { success: true, result: responseData.result }; } catch (err: any) { return { success: false, error: `MCP execution failed: ${err.message}` }; } } }