UNPKG

n8n-nodes-feishu-lark

Version:

n8n custom nodes for n8n to interact with Feishu/Lark, including Lark Bot, Lark MCP, and Lark Trigger.

357 lines 17.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LarkMcp = void 0; const n8n_workflow_1 = require("n8n-workflow"); const tools_1 = require("@langchain/core/tools"); const zod_1 = require("zod"); const zod_to_json_schema_1 = require("zod-to-json-schema"); const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); class LarkMcp { constructor() { this.description = { displayName: 'Lark MCP', name: 'larkMcp', icon: 'file:lark_icon.svg', group: ['transform'], version: [1], defaultVersion: 1, subtitle: '={{$parameter["operation"]}}', description: 'Use Lark MCP client', defaults: { name: 'Lark MCP', }, inputs: [{ type: "main" }], outputs: [{ type: "main" }], usableAsTool: true, credentials: [ { name: "larkApi", required: true, }, ], properties: [ { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, options: [ { name: 'Execute Tool | 执行工具', value: 'executeTool', description: 'Execute a specific tool', action: 'Execute a tool', }, { name: 'List Tools | 获取工具列表', value: 'listTools', description: 'Get available tools', action: 'List available tools', }, ], default: 'listTools', required: true, }, { displayName: 'Tool Name', name: 'toolName', type: 'string', required: true, displayOptions: { show: { operation: ['executeTool'], }, }, default: '', description: 'Name of the tool to execute', }, { displayName: 'Tool Parameters', name: 'toolParameters', type: 'json', required: true, displayOptions: { show: { operation: ['executeTool'], }, }, default: '{}', description: 'Parameters to pass to the tool in JSON format', }, ], }; } async execute() { const credentials = (await this.getCredentials("larkApi")); if (!(credentials.appid && credentials.appsecret)) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Missing required Lark credentials'); } const returnData = []; const operation = this.getNodeParameter('operation', 0); let transport; let timeout = 600000; try { const env = { PATH: process.env.PATH || '', }; this.logger.debug(`Original PATH: ${process.env.PATH}`); for (const key in process.env) { if (key.startsWith('MCP_') && process.env[key]) { const envName = key.substring(4); env[envName] = process.env[key]; } } const { appid: appId, appsecret: appSecret, baseUrl } = credentials; const args = `-y @larksuiteoapi/lark-mcp mcp -a ${appId} -s ${appSecret} -d https://${baseUrl} --oauth`; transport = new stdio_js_1.StdioClientTransport({ command: 'npx', args: args.split(' ') || [], env: env, }); this.logger.debug(`Transport created for MCP client PATH: ${env.PATH}`); if (transport) { transport.onerror = (error) => { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Transport error: ${error.message}`); }; } const client = new index_js_1.Client({ name: `LarkMcp-client`, version: '1.0.0', }, { capabilities: { prompts: {}, resources: {}, tools: {}, }, }); try { if (!transport) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No transport available'); } await client.connect(transport); this.logger.debug('Client connected to MCP server'); } catch (connectionError) { this.logger.error(`MCP client connection error: ${connectionError.message}`); throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to connect to MCP server: ${connectionError.message}`); } const requestOptions = {}; requestOptions.timeout = timeout; switch (operation) { case 'listResources': { const resources = await client.listResources(); returnData.push({ json: { resources }, }); break; } case 'listResourceTemplates': { const resourceTemplates = await client.listResourceTemplates(); returnData.push({ json: { resourceTemplates }, }); break; } case 'readResource': { const uri = this.getNodeParameter('resourceUri', 0); const resource = await client.readResource({ uri, }); returnData.push({ json: { resource }, }); break; } case 'listTools': { const rawTools = await client.listTools(); const tools = Array.isArray(rawTools) ? rawTools : Array.isArray(rawTools === null || rawTools === void 0 ? void 0 : rawTools.tools) ? rawTools.tools : typeof (rawTools === null || rawTools === void 0 ? void 0 : rawTools.tools) === 'object' && rawTools.tools !== null ? Object.values(rawTools.tools) : []; if (!tools.length) { this.logger.warn('No tools found from MCP client response.'); throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No tools found from MCP client'); } const aiTools = tools.map((tool) => { var _a; const paramSchema = ((_a = tool.inputSchema) === null || _a === void 0 ? void 0 : _a.properties) ? zod_1.z.object(Object.entries(tool.inputSchema.properties).reduce((acc, [key, prop]) => { var _a, _b, _c, _d, _e; let zodType; switch (prop.type) { case 'string': zodType = zod_1.z.string(); break; case 'number': zodType = zod_1.z.number(); break; case 'integer': zodType = zod_1.z.number().int(); break; case 'boolean': zodType = zod_1.z.boolean(); break; case 'array': if (((_a = prop.items) === null || _a === void 0 ? void 0 : _a.type) === 'string') { zodType = zod_1.z.array(zod_1.z.string()); } else if (((_b = prop.items) === null || _b === void 0 ? void 0 : _b.type) === 'number') { zodType = zod_1.z.array(zod_1.z.number()); } else if (((_c = prop.items) === null || _c === void 0 ? void 0 : _c.type) === 'boolean') { zodType = zod_1.z.array(zod_1.z.boolean()); } else { zodType = zod_1.z.array(zod_1.z.any()); } break; case 'object': zodType = zod_1.z.record(zod_1.z.string(), zod_1.z.any()); break; default: zodType = zod_1.z.any(); } if (prop.description) { zodType = zodType.describe(prop.description); } if (!((_e = (_d = tool.inputSchema) === null || _d === void 0 ? void 0 : _d.required) === null || _e === void 0 ? void 0 : _e.includes(key))) { zodType = zodType.optional(); } return { ...acc, [key]: zodType, }; }, {})) : zod_1.z.object({}); return new tools_1.DynamicStructuredTool({ name: tool.name, description: tool.description || `Execute the ${tool.name} tool`, schema: paramSchema, func: async (params) => { try { const result = await client.callTool({ name: tool.name, arguments: params, }, types_js_1.CallToolResultSchema, requestOptions); return typeof result === 'object' ? JSON.stringify(result) : String(result); } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to execute ${tool.name}: ${error.message}`); } }, }); }); returnData.push({ json: { tools: aiTools.map((t) => ({ name: t.name, description: t.description, schema: (0, zod_to_json_schema_1.zodToJsonSchema)(t.schema || zod_1.z.object({})), })), }, }); break; } case 'executeTool': { const toolName = this.getNodeParameter('toolName', 0); let toolParams; try { const rawParams = this.getNodeParameter('toolParameters', 0); this.logger.debug(`Raw tool parameters: ${JSON.stringify(rawParams)}`); if (rawParams === undefined || rawParams === null) { toolParams = {}; } else if (typeof rawParams === 'string') { if (!rawParams || rawParams.trim() === '') { toolParams = {}; } else { toolParams = JSON.parse(rawParams); } } else if (typeof rawParams === 'object') { toolParams = rawParams; } else { try { toolParams = JSON.parse(JSON.stringify(rawParams)); } catch (parseError) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid parameter type: ${typeof rawParams}`); } } if (typeof toolParams !== 'object' || toolParams === null || Array.isArray(toolParams)) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Tool parameters must be a JSON object'); } } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to parse tool parameters: ${error.message}. Make sure the parameters are valid JSON.`); } try { const availableTools = await client.listTools(); const toolsList = Array.isArray(availableTools) ? availableTools : Array.isArray(availableTools === null || availableTools === void 0 ? void 0 : availableTools.tools) ? availableTools.tools : Object.values((availableTools === null || availableTools === void 0 ? void 0 : availableTools.tools) || {}); const toolExists = toolsList.some((tool) => tool.name === toolName); if (!toolExists) { const availableToolNames = toolsList.map((t) => t.name).join(', '); throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Tool '${toolName}' does not exist. Available tools: ${availableToolNames}`); } this.logger.debug(`Executing tool: ${toolName} with params: ${JSON.stringify(toolParams)}`); const result = await client.callTool({ name: toolName, arguments: toolParams, }, types_js_1.CallToolResultSchema, requestOptions); this.logger.debug(`Tool executed successfully: ${JSON.stringify(result)}`); returnData.push({ json: { result }, }); } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to execute tool '${toolName}': ${error.message}`); } break; } case 'listPrompts': { const prompts = await client.listPrompts(); returnData.push({ json: { prompts }, }); break; } case 'getPrompt': { const promptName = this.getNodeParameter('promptName', 0); const prompt = await client.getPrompt({ name: promptName, }); returnData.push({ json: { prompt }, }); break; } default: throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Operation ${operation} not supported`); } return [returnData]; } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to execute operation: ${error.message}`); } finally { if (transport) { await transport.close(); } } } } exports.LarkMcp = LarkMcp; //# sourceMappingURL=LarkMcp.node.js.map