@net3/queuer
Version:
200 lines (199 loc) • 8.06 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MCPManager = void 0;
const pieces_common_1 = require("@activepieces/pieces-common");
/**
* Helper class for interacting with MCP servers using the standard MCP protocol.
*/
class MCPManager {
/**
* Parse SSE response format to extract JSON data
*/
static parseSSEResponse(sseData) {
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) {
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
*/
static async initializeMCPSession(config) {
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 pieces_common_1.httpClient.sendRequest({
method: pieces_common_1.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 pieces_common_1.httpClient.sendRequest({
method: pieces_common_1.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) {
// 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) {
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) {
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 pieces_common_1.httpClient.sendRequest({
method: pieces_common_1.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) {
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, params, config) {
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 pieces_common_1.httpClient.sendRequest({
method: pieces_common_1.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) {
return { success: false, error: `MCP execution failed: ${err.message}` };
}
}
}
exports.MCPManager = MCPManager;
MCPManager.initialized = new Map();