diff --git a/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts b/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts new file mode 100644 index 000000000..548c595ec --- /dev/null +++ b/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts @@ -0,0 +1,129 @@ +import { Tool } from '@langchain/core/tools' +import { INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface' +import { MCPToolkit } from '../core' + +const mcpServerConfig = `{ + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/files"] +}` + +class Custom_MCP implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + documentation: string + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Custom MCP' + this.name = 'customMCP' + this.version = 1.0 + this.type = 'Custom MCP Tool' + this.icon = 'customMCP.png' + this.category = 'Tools (MCP)' + this.description = 'Custom MCP Config' + this.documentation = 'https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search' + this.inputs = [ + { + label: 'MCP Server Config', + name: 'mcpServerConfig', + type: 'code', + hideCodeExecute: true, + placeholder: mcpServerConfig + }, + { + label: 'Available Actions', + name: 'mcpActions', + type: 'asyncMultiOptions', + loadMethod: 'listActions', + refresh: true + } + ] + this.baseClasses = ['Tool'] + } + + //@ts-ignore + loadMethods = { + listActions: async (nodeData: INodeData): Promise => { + try { + const toolset = await this.getTools(nodeData) + toolset.sort((a: any, b: any) => a.name.localeCompare(b.name)) + + return toolset.map(({ name, ...rest }) => ({ + label: name.toUpperCase(), + name: name, + description: rest.description || name + })) + } catch (error) { + return [ + { + label: 'No Available Actions', + name: 'error', + description: 'No available actions, please check your API key and refresh' + } + ] + } + } + } + + async init(nodeData: INodeData): Promise { + const tools = await this.getTools(nodeData) + + const _mcpActions = nodeData.inputs?.mcpActions + let mcpActions = [] + if (_mcpActions) { + try { + mcpActions = typeof _mcpActions === 'string' ? JSON.parse(_mcpActions) : _mcpActions + } catch (error) { + console.error('Error parsing mcp actions:', error) + } + } + + return tools.filter((tool: any) => mcpActions.includes(tool.name)) + } + + async getTools(nodeData: INodeData): Promise { + const mcpServerConfig = nodeData.inputs?.mcpServerConfig as string + + if (!mcpServerConfig) { + throw new Error('MCP Server Config is required') + } + + try { + let serverParams + if (typeof mcpServerConfig === 'object') { + serverParams = mcpServerConfig + } else if (typeof mcpServerConfig === 'string') { + const serverParamsString = convertToValidJSONString(mcpServerConfig) + serverParams = JSON.parse(serverParamsString) + } + + const toolkit = new MCPToolkit(serverParams, 'stdio') + await toolkit.initialize() + + const tools = toolkit.tools ?? [] + + return tools as Tool[] + } catch (error) { + throw new Error(`Invalid MCP Server Config: ${error}`) + } + } +} + +function convertToValidJSONString(inputString: string) { + try { + const jsObject = Function('return ' + inputString)() + return JSON.stringify(jsObject, null, 2) + } catch (error) { + console.error('Error converting to JSON:', error) + return '' + } +} + +module.exports = { nodeClass: Custom_MCP } diff --git a/packages/components/nodes/tools/MCP/CustomMCP/customMCP.png b/packages/components/nodes/tools/MCP/CustomMCP/customMCP.png new file mode 100644 index 000000000..695023461 Binary files /dev/null and b/packages/components/nodes/tools/MCP/CustomMCP/customMCP.png differ diff --git a/packages/components/nodes/tools/MCP/core.ts b/packages/components/nodes/tools/MCP/core.ts index 3e1846141..235f9d50f 100644 --- a/packages/components/nodes/tools/MCP/core.ts +++ b/packages/components/nodes/tools/MCP/core.ts @@ -15,14 +15,14 @@ export class MCPToolkit extends BaseToolkit { if (transport === 'stdio') { this.transport = new StdioClientTransport(serverParams as StdioServerParameters) } else { - //this.transport = new SSEClientTransport(serverParams.url); + // TODO: this.transport = new SSEClientTransport(serverParams.url); } } async initialize() { if (this._tools === null) { this.client = new Client( { - name: 'langchain-js-client', + name: 'flowise-client', version: '1.0.0' }, {