Feature/MCP (Model Context Protocol) (#4134)

add mcp tools
This commit is contained in:
Henry Heng 2025-03-06 13:57:18 +00:00 committed by GitHub
parent 9c22bee991
commit 713ed26971
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1321 additions and 105 deletions

View File

@ -0,0 +1,25 @@
import { INodeParams, INodeCredential } from '../src/Interface'
class PostgresUrl implements INodeCredential {
label: string
name: string
version: number
description: string
inputs: INodeParams[]
constructor() {
this.label = 'Postgres URL'
this.name = 'PostgresUrl'
this.version = 1.0
this.inputs = [
{
label: 'Postgres URL',
name: 'postgresUrl',
type: 'string',
placeholder: 'postgresql://localhost/mydb'
}
]
}
}
module.exports = { credClass: PostgresUrl }

View File

@ -0,0 +1,32 @@
import { INodeParams, INodeCredential } from '../src/Interface'
class SlackApi implements INodeCredential {
label: string
name: string
version: number
description: string
inputs: INodeParams[]
constructor() {
this.label = 'Slack API'
this.name = 'slackApi'
this.version = 1.0
this.description =
'Refer to <a target="_blank" href="https://github.com/modelcontextprotocol/servers/tree/main/src/slack">official guide</a> on how to get botToken and teamId on Slack'
this.inputs = [
{
label: 'Bot Token',
name: 'botToken',
type: 'password'
},
{
label: 'Team ID',
name: 'teamId',
type: 'string',
placeholder: '<SLACK_TEAM_ID>'
}
]
}
}
module.exports = { credClass: SlackApi }

View File

@ -0,0 +1,74 @@
import { z } from 'zod'
import { INode } from '../../../src/Interface'
import { DynamicStructuredTool } from '../CustomTool/core'
const code = `
const now = new Date();
// Format date as YYYY-MM-DD
const date = now.toISOString().split('T')[0];
// Get time in HH:MM:SS format
const time = now.toTimeString().split(' ')[0];
// Get day of week
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const day = days[now.getDay()];
// Get timezone information
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const timezoneOffset = now.getTimezoneOffset();
const timezoneOffsetHours = Math.abs(Math.floor(timezoneOffset / 60));
const timezoneOffsetMinutes = Math.abs(timezoneOffset % 60);
const timezoneOffsetFormatted =
(timezoneOffset <= 0 ? '+' : '-') +
timezoneOffsetHours.toString().padStart(2, '0') + ':' +
timezoneOffsetMinutes.toString().padStart(2, '0');
return {
date,
time,
day,
timezone,
timezoneOffset: timezoneOffsetFormatted,
iso8601: now.toISOString(),
unix_timestamp: Math.floor(now.getTime() / 1000)
};
`
class CurrentDateTime_Tools implements INode {
label: string
name: string
version: number
description: string
type: string
icon: string
category: string
baseClasses: string[]
constructor() {
this.label = 'CurrentDateTime'
this.name = 'currentDateTime'
this.version = 1.0
this.type = 'CurrentDateTime'
this.icon = 'currentDateTime.svg'
this.category = 'Tools'
this.description = 'Get todays day, date and time.'
this.baseClasses = [this.type, 'Tool']
}
async init(): Promise<any> {
const obj = {
name: 'current_date_time',
description: 'Useful to get current day, date and time.',
schema: z.object({}),
code: code
}
let dynamicStructuredTool = new DynamicStructuredTool(obj)
return dynamicStructuredTool
}
}
module.exports = { nodeClass: CurrentDateTime_Tools }

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-timezone"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M20.884 10.554a9 9 0 1 0 -10.337 10.328" /><path d="M3.6 9h16.8" /><path d="M3.6 15h6.9" /><path d="M11.5 3a17 17 0 0 0 -1.502 14.954" /><path d="M12.5 3a17 17 0 0 1 2.52 7.603" /><path d="M18 18m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M18 16.5v1.5l.5 .5" /></svg>

After

Width:  |  Height:  |  Size: 588 B

View File

@ -0,0 +1,108 @@
import { Tool } from '@langchain/core/tools'
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
import { getCredentialData, getCredentialParam, getNodeModulesPackagePath } from '../../../../src/utils'
import { MCPToolkit } from '../core'
class BraveSearch_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 = 'Brave Search MCP'
this.name = 'braveSearchMCP'
this.version = 1.0
this.type = 'BraveSearch MCP Tool'
this.icon = 'brave.svg'
this.category = 'Tools (MCP)'
this.description = 'MCP server that integrates the Brave Search API - a real-time API to access web search capabilities'
this.documentation = 'https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search'
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['braveSearchApi']
}
this.inputs = [
{
label: 'Available Actions',
name: 'mcpActions',
type: 'asyncMultiOptions',
loadMethod: 'listActions',
refresh: true
}
]
this.baseClasses = ['Tool']
}
//@ts-ignore
loadMethods = {
listActions: async (nodeData: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> => {
try {
const toolset = await this.getTools(nodeData, options)
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, _: string, options: ICommonObject): Promise<any> {
const tools = await this.getTools(nodeData, options)
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, options: ICommonObject): Promise<Tool[]> {
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const braveApiKey = getCredentialParam('braveApiKey', credentialData, nodeData)
const packagePath = getNodeModulesPackagePath('@modelcontextprotocol/server-brave-search/dist/index.js')
const serverParams = {
command: 'node',
args: [packagePath],
env: {
BRAVE_API_KEY: braveApiKey
}
}
const toolkit = new MCPToolkit(serverParams, 'stdio')
await toolkit.initialize()
const tools = toolkit.tools ?? []
return tools as Tool[]
}
}
module.exports = { nodeClass: BraveSearch_MCP }

View File

@ -0,0 +1,8 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.2866 9.22667L26.8999 7.73333C26.8999 7.73333 26.1333 6.9 25.1866 5.96C24.2399 5.02 22.2466 5.56 22.2466 5.56L19.9999 3H11.9999L9.73325 5.58C9.73325 5.58 7.73325 5.03333 6.79325 5.97333C5.85325 6.91333 5.07992 7.75333 5.07992 7.75333L5.69326 9.24667L4.91992 11.4533C4.91992 11.4533 7.20659 20.12 7.46659 21.1533C7.99992 23.2267 8.35326 24.0333 9.84659 25.1C11.3399 26.1667 14.0466 27.9733 14.5133 28.2533C14.9415 28.6183 15.4515 28.8745 15.9999 29C16.4933 29 17.0466 28.5267 17.4999 28.2533C17.9533 27.98 20.6533 26.14 22.1666 25.1C23.6799 24.06 23.9999 23.2467 24.5333 21.1533L27.0799 11.4533L26.2866 9.22667Z" fill="#FF6520" stroke="#FF6520" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.4735 21.0133C11.7428 20.2529 11.1071 19.4067 10.5801 18.4933C10.4452 18.1438 10.3942 17.7674 10.4312 17.3946C10.4683 17.0217 10.5924 16.6628 10.7935 16.3466C10.8686 16.1363 10.8707 15.9067 10.7993 15.695C10.728 15.4833 10.5873 15.3019 10.4001 15.18C10.2068 14.9666 8.5868 13.2533 8.21346 12.8533C7.84013 12.4533 7.4668 12.2866 7.4668 11.52C7.4668 10.7533 10.3801 7.22665 10.3801 7.22665C10.3801 7.22665 12.8468 7.69998 13.1801 7.69998C13.7659 7.59152 14.3397 7.42629 14.8935 7.20665C15.2496 7.07842 15.6223 7.00208 16.0001 6.97998L14.8935 7.19998C15.2499 7.07402 15.6226 6.99993 16.0001 6.97998C16.3777 6.99993 16.7503 7.07402 17.1068 7.19998C17.6605 7.41963 18.2344 7.58485 18.8201 7.69331C19.1535 7.69331 21.6201 7.21998 21.6201 7.21998C21.6201 7.21998 24.5335 10.7466 24.5335 11.5133C24.5335 12.28 24.1601 12.46 23.7868 12.8466C23.4135 13.2333 21.7868 14.96 21.6001 15.1733C21.4129 15.2952 21.2723 15.4767 21.2009 15.6884C21.1295 15.9 21.1316 16.1296 21.2068 16.34C21.4079 16.6561 21.532 17.0151 21.569 17.3879C21.6061 17.7608 21.5551 18.1371 21.4201 18.4866C20.8932 19.4001 20.2574 20.2463 19.5268 21.0066" fill="white"/>
<path d="M21.46 9.87996C20.6375 9.74822 19.8011 9.72803 18.9733 9.81996C18.3639 9.94895 17.7696 10.1411 17.2 10.3933C17.1266 10.5266 17.0466 10.5266 17.1266 11.02C17.2066 11.5133 17.6733 13.82 17.7133 14.2333C17.7533 14.6466 17.8533 14.9 17.4 15.0266C16.926 15.1471 16.4451 15.2384 15.96 15.3C15.4749 15.2378 14.9941 15.1465 14.52 15.0266C14.0866 14.9266 14.1666 14.6466 14.2066 14.2333C14.2466 13.82 14.7 11.5133 14.8 11.02C14.9 10.5266 14.8 10.5266 14.72 10.3933C14.1516 10.1379 13.5569 9.94565 12.9466 9.81996C12.1193 9.71801 11.2814 9.73823 10.46 9.87996M16 19.1666V15.3333V19.1666ZM19.4266 20.72C19.6066 20.84 19.5066 21.0533 19.3333 21.1533C19.16 21.2533 16.9466 22.9866 16.7466 23.1866C16.5466 23.3866 16.22 23.68 16 23.68C15.78 23.68 15.4666 23.36 15.2533 23.1866C15.04 23.0133 12.8266 21.2733 12.6666 21.1533C12.5066 21.0333 12.3933 20.82 12.5733 20.72C12.7533 20.62 13.32 20.3266 14.0866 19.9133C14.6912 19.5865 15.3338 19.3357 16 19.1666C16.6661 19.3357 17.3087 19.5865 17.9133 19.9133L19.4266 20.72Z" fill="white"/>
<path d="M21.46 9.87996C20.6375 9.74822 19.8011 9.72803 18.9733 9.81996C18.3639 9.94895 17.7696 10.1411 17.2 10.3933C17.1266 10.5266 17.0466 10.5266 17.1266 11.02C17.2066 11.5133 17.6733 13.82 17.7133 14.2333C17.7533 14.6466 17.8533 14.9 17.4 15.0266C16.926 15.1471 16.4451 15.2384 15.96 15.3C15.4749 15.2378 14.9941 15.1465 14.52 15.0266C14.0866 14.9266 14.1666 14.6466 14.2066 14.2333C14.2466 13.82 14.7 11.5133 14.8 11.02C14.9 10.5266 14.8 10.5266 14.72 10.3933C14.1516 10.1379 13.5569 9.94565 12.9466 9.81996C12.1193 9.71801 11.2814 9.73823 10.46 9.87996M16 19.1666V15.3333M16 19.1666C15.3338 19.3357 14.6912 19.5865 14.0866 19.9133C13.32 20.3266 12.7533 20.62 12.5733 20.72C12.3933 20.82 12.5066 21.0333 12.6666 21.1533C12.8266 21.2733 15.04 23.0133 15.2533 23.1866C15.4666 23.36 15.78 23.68 16 23.68C16.22 23.68 16.5466 23.3866 16.7466 23.1866C16.9466 22.9866 19.16 21.2533 19.3333 21.1533C19.5066 21.0533 19.6066 20.84 19.4266 20.72L17.9133 19.9133C17.3087 19.5865 16.6661 19.3357 16 19.1666Z" stroke="#FF6520" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.839 21.7606C18.9867 21.672 19.1161 21.4053 18.9627 21.299L17.6582 20.6046C17.143 20.3149 16.5426 20.0811 15.9749 19.9313C15.4073 20.0811 14.8069 20.336 14.2917 20.6256C13.6383 20.9919 13.1932 21.2524 13.0398 21.341C12.8864 21.4297 13.0212 21.6543 13.1575 21.7606C13.2939 21.867 15.1801 23.4091 15.3619 23.5627C15.5438 23.7163 15.8108 23.9999 15.9983 23.9999C16.1858 23.9999 16.4641 23.74 16.6346 23.5627C16.805 23.3855 18.6913 21.8493 18.839 21.7606Z" fill="white"/>
<path d="M18.839 21.7606C18.9867 21.672 19.1161 21.4053 18.9627 21.299L17.6582 20.6046C17.143 20.3149 16.5426 20.0811 15.9749 19.9313C15.4073 20.0811 14.8069 20.336 14.2917 20.6256C13.6383 20.9919 13.1932 21.2524 13.0398 21.341C12.8864 21.4297 13.0212 21.6543 13.1575 21.7606C13.2939 21.867 15.1801 23.4091 15.3619 23.5627C15.5438 23.7163 15.8108 23.9999 15.9983 23.9999C16.1858 23.9999 16.4641 23.74 16.6346 23.5627C16.805 23.3855 18.6913 21.8493 18.839 21.7606Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,114 @@
import { Tool } from '@langchain/core/tools'
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
import { getCredentialData, getCredentialParam, getNodeModulesPackagePath } from '../../../../src/utils'
import { MCPToolkit } from '../core'
class Github_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 = 'Github MCP'
this.name = 'githubMCP'
this.version = 1.0
this.type = 'Github MCP Tool'
this.icon = 'github.svg'
this.category = 'Tools (MCP)'
this.description = 'MCP Server for the GitHub API'
this.documentation = 'https://github.com/modelcontextprotocol/servers/tree/main/src/github'
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['githubApi']
}
this.inputs = [
{
label: 'Available Actions',
name: 'mcpActions',
type: 'asyncMultiOptions',
loadMethod: 'listActions',
refresh: true
}
]
this.baseClasses = ['Tool']
}
//@ts-ignore
loadMethods = {
listActions: async (nodeData: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> => {
try {
const toolset = await this.getTools(nodeData, options)
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) {
console.error('Error listing actions:', error)
return [
{
label: 'No Available Actions',
name: 'error',
description: 'No available actions, please check your Github Access Token and refresh'
}
]
}
}
}
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const tools = await this.getTools(nodeData, options)
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, options: ICommonObject): Promise<Tool[]> {
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const accessToken = getCredentialParam('accessToken', credentialData, nodeData)
if (!accessToken) {
throw new Error('Missing Github Access Token')
}
const packagePath = getNodeModulesPackagePath('@modelcontextprotocol/server-github/dist/index.js')
const serverParams = {
command: 'node',
args: [packagePath],
env: {
GITHUB_PERSONAL_ACCESS_TOKEN: accessToken
}
}
const toolkit = new MCPToolkit(serverParams, 'stdio')
await toolkit.initialize()
const tools = toolkit.tools ?? []
return tools as Tool[]
}
}
module.exports = { nodeClass: Github_MCP }

View File

@ -0,0 +1,13 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_115_14646)">
<path d="M13.9788 7C13.3909 6.0801 12.5809 5.32307 11.6234 4.79876C10.6658 4.27445 9.59167 3.99974 8.5 4C8.0144 4.83956 7.72314 5.77729 7.64765 6.74423C7.57215 7.71117 7.71434 8.68274 8.06375 9.5875C7.37968 10.5949 7.00951 11.7824 7 13V14C7 15.5913 7.63214 17.1174 8.75736 18.2426C9.88258 19.3679 11.4087 20 13 20H19C20.5913 20 22.1174 19.3679 23.2426 18.2426C24.3679 17.1174 25 15.5913 25 14V13C24.9905 11.7824 24.6203 10.5949 23.9363 9.5875C24.2857 8.68274 24.4278 7.71117 24.3524 6.74423C24.2769 5.77729 23.9856 4.83956 23.5 4C22.4083 3.99974 21.3342 4.27445 20.3766 4.79876C19.4191 5.32307 18.6091 6.0801 18.0212 7H13.9788Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 29V24C12 21.7909 13.7909 20 16 20V20C18.2091 20 20 21.7909 20 24V29" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 26H10C8.93913 26 7.92172 25.5786 7.17157 24.8284C6.42143 24.0783 6 23.0609 6 22C6 20.9391 5.57857 19.9217 4.82843 19.1716C4.07828 18.4214 3.06087 18 2 18" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 26H10C7.79086 26 6 24.2091 6 22V22C6 19.7909 4.20914 18 2 18V18" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_115_14646">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,111 @@
import { Tool } from '@langchain/core/tools'
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
import { getCredentialData, getCredentialParam, getNodeModulesPackagePath } from '../../../../src/utils'
import { MCPToolkit } from '../core'
class PostgreSQL_MCP implements INode {
label: string
name: string
version: number
description: string
type: string
icon: string
category: string
baseClasses: string[]
credential: INodeParams
documentation: string
inputs: INodeParams[]
constructor() {
this.label = 'PostgreSQL MCP'
this.name = 'postgreSQLMCP'
this.version = 1.0
this.type = 'PostgreSQL MCP Tool'
this.icon = 'postgres.svg'
this.category = 'Tools (MCP)'
this.description = 'MCP server that provides read-only access to PostgreSQL databases'
this.documentation = 'https://github.com/modelcontextprotocol/servers/tree/main/src/postgres'
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['PostgresUrl']
}
this.inputs = [
{
label: 'Available Actions',
name: 'mcpActions',
type: 'asyncMultiOptions',
loadMethod: 'listActions',
refresh: true
}
]
this.baseClasses = ['Tool']
}
//@ts-ignore
loadMethods = {
listActions: async (nodeData: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> => {
try {
const toolset = await this.getTools(nodeData, options)
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) {
console.error('Error listing actions:', error)
return [
{
label: 'No Available Actions',
name: 'error',
description: 'No available actions, please check your postgres url and refresh'
}
]
}
}
}
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const tools = await this.getTools(nodeData, options)
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, options: ICommonObject): Promise<Tool[]> {
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const postgresUrl = getCredentialParam('postgresUrl', credentialData, nodeData)
if (!postgresUrl) {
throw new Error('No postgres url provided')
}
const packagePath = getNodeModulesPackagePath('@modelcontextprotocol/server-postgres/dist/index.js')
const serverParams = {
command: 'node',
args: [packagePath, postgresUrl]
}
const toolkit = new MCPToolkit(serverParams, 'stdio')
await toolkit.initialize()
const tools = toolkit.tools ?? []
return tools as Tool[]
}
}
module.exports = { nodeClass: PostgreSQL_MCP }

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -0,0 +1,116 @@
import { Tool } from '@langchain/core/tools'
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
import { getCredentialData, getCredentialParam, getNodeModulesPackagePath } from '../../../../src/utils'
import { MCPToolkit } from '../core'
class Slack_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 = 'Slack MCP'
this.name = 'slackMCP'
this.version = 1.0
this.type = 'Slack MCP Tool'
this.icon = 'slack.svg'
this.category = 'Tools (MCP)'
this.description = 'MCP Server for the Slack API'
this.documentation = 'https://github.com/modelcontextprotocol/servers/tree/main/src/slack'
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['slackApi']
}
this.inputs = [
{
label: 'Available Actions',
name: 'mcpActions',
type: 'asyncMultiOptions',
loadMethod: 'listActions',
refresh: true
}
]
this.baseClasses = ['Tool']
}
//@ts-ignore
loadMethods = {
listActions: async (nodeData: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> => {
try {
const toolset = await this.getTools(nodeData, options)
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) {
console.error('Error listing actions:', error)
return [
{
label: 'No Available Actions',
name: 'error',
description: 'No available actions, please check your Slack Bot Token and refresh'
}
]
}
}
}
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const tools = await this.getTools(nodeData, options)
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, options: ICommonObject): Promise<Tool[]> {
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const botToken = getCredentialParam('botToken', credentialData, nodeData)
const teamId = getCredentialParam('teamId', credentialData, nodeData)
if (!botToken || !teamId) {
throw new Error('Missing Credentials')
}
const packagePath = getNodeModulesPackagePath('@modelcontextprotocol/server-slack/dist/index.js')
const serverParams = {
command: 'node',
args: [packagePath],
env: {
SLACK_BOT_TOKEN: botToken,
SLACK_TEAM_ID: teamId
}
}
const toolkit = new MCPToolkit(serverParams, 'stdio')
await toolkit.initialize()
const tools = toolkit.tools ?? []
return tools as Tool[]
}
}
module.exports = { nodeClass: Slack_MCP }

View File

@ -0,0 +1 @@
<svg enable-background="new 0 0 2447.6 2452.5" viewBox="0 0 2447.6 2452.5" xmlns="http://www.w3.org/2000/svg"><g clip-rule="evenodd" fill-rule="evenodd"><path d="m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z" fill="#36c5f0"/><path d="m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z" fill="#2eb67d"/><path d="m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z" fill="#ecb22e"/><path d="m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0" fill="#e01e5a"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,104 @@
import { CallToolRequest, CallToolResultSchema, ListToolsResult, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StdioClientTransport, StdioServerParameters } from '@modelcontextprotocol/sdk/client/stdio.js'
import { BaseToolkit, tool, Tool } from '@langchain/core/tools'
import { z } from 'zod'
export class MCPToolkit extends BaseToolkit {
tools: Tool[] = []
_tools: ListToolsResult | null = null
model_config: any
transport: StdioClientTransport | null = null
client: Client | null = null
constructor(serverParams: StdioServerParameters | any, transport: 'stdio' | 'sse') {
super()
if (transport === 'stdio') {
this.transport = new StdioClientTransport(serverParams as StdioServerParameters)
} else {
//this.transport = new SSEClientTransport(serverParams.url);
}
}
async initialize() {
if (this._tools === null) {
this.client = new Client(
{
name: 'langchain-js-client',
version: '1.0.0'
},
{
capabilities: {}
}
)
if (this.transport === null) {
throw new Error('Transport is not initialized')
}
await this.client.connect(this.transport)
this._tools = await this.client.request({ method: 'tools/list' }, ListToolsResultSchema)
this.tools = await this.get_tools()
}
}
async get_tools(): Promise<Tool[]> {
if (this._tools === null || this.client === null) {
throw new Error('Must initialize the toolkit first')
}
const toolsPromises = this._tools.tools.map(async (tool: any) => {
if (this.client === null) {
throw new Error('Client is not initialized')
}
return await MCPTool({
client: this.client,
name: tool.name,
description: tool.description || '',
argsSchema: createSchemaModel(tool.inputSchema)
})
})
return Promise.all(toolsPromises)
}
}
export async function MCPTool({
client,
name,
description,
argsSchema
}: {
client: Client
name: string
description: string
argsSchema: any
}): Promise<Tool> {
return tool(
async (input): Promise<string> => {
const req: CallToolRequest = { method: 'tools/call', params: { name: name, arguments: input } }
const res = await client.request(req, CallToolResultSchema)
const content = res.content
const contentString = JSON.stringify(content)
return contentString
},
{
name: name,
description: description,
schema: argsSchema
}
)
}
function createSchemaModel(
inputSchema: {
type: 'object'
properties?: import('zod').objectOutputType<{}, import('zod').ZodTypeAny, 'passthrough'> | undefined
} & { [k: string]: unknown }
): any {
if (inputSchema.type !== 'object' || !inputSchema.properties) {
throw new Error('Invalid schema type or missing properties')
}
const schemaProperties = Object.entries(inputSchema.properties).reduce((acc, [key, _]) => {
acc[key] = z.any()
return acc
}, {} as Record<string, import('zod').ZodTypeAny>)
return z.object(schemaProperties)
}

View File

@ -59,6 +59,11 @@
"@langchain/xai": "^0.0.1", "@langchain/xai": "^0.0.1",
"@mendable/firecrawl-js": "^0.0.28", "@mendable/firecrawl-js": "^0.0.28",
"@mistralai/mistralai": "0.1.3", "@mistralai/mistralai": "0.1.3",
"@modelcontextprotocol/sdk": "^1.6.1",
"@modelcontextprotocol/server-brave-search": "^0.6.2",
"@modelcontextprotocol/server-github": "^2025.1.23",
"@modelcontextprotocol/server-postgres": "^0.6.2",
"@modelcontextprotocol/server-slack": "^2025.1.17",
"@notionhq/client": "^2.2.8", "@notionhq/client": "^2.2.8",
"@opensearch-project/opensearch": "^1.2.0", "@opensearch-project/opensearch": "^1.2.0",
"@pinecone-database/pinecone": "4.0.0", "@pinecone-database/pinecone": "4.0.0",

View File

@ -167,6 +167,7 @@ export interface IUsedTool {
toolInput: object toolInput: object
toolOutput: string | object toolOutput: string | object
sourceDocuments?: ICommonObject[] sourceDocuments?: ICommonObject[]
error?: string
} }
export interface IMultiAgentNode { export interface IMultiAgentNode {

View File

@ -24,6 +24,7 @@ import {
} from 'langchain/agents' } from 'langchain/agents'
import { formatLogToString } from 'langchain/agents/format_scratchpad/log' import { formatLogToString } from 'langchain/agents/format_scratchpad/log'
import { IUsedTool } from './Interface' import { IUsedTool } from './Interface'
import { getErrorMessage } from './error'
export const SOURCE_DOCUMENTS_PREFIX = '\n\n----FLOWISE_SOURCE_DOCUMENTS----\n\n' export const SOURCE_DOCUMENTS_PREFIX = '\n\n----FLOWISE_SOURCE_DOCUMENTS----\n\n'
export const ARTIFACTS_PREFIX = '\n\n----FLOWISE_ARTIFACTS----\n\n' export const ARTIFACTS_PREFIX = '\n\n----FLOWISE_ARTIFACTS----\n\n'
@ -463,7 +464,21 @@ export class AgentExecutor extends BaseChain<ChainValues, AgentExecutorOutput> {
throw e throw e
} }
observation = await new ExceptionTool().call(observation, runManager?.getChild()) observation = await new ExceptionTool().call(observation, runManager?.getChild())
usedTools.push({
tool: tool.name,
toolInput: action.toolInput as any,
toolOutput: '',
error: getErrorMessage(e)
})
return { action, observation: observation ?? '' } return { action, observation: observation ?? '' }
} else {
usedTools.push({
tool: tool.name,
toolInput: action.toolInput as any,
toolOutput: '',
error: getErrorMessage(e)
})
return { action, observation: getErrorMessage(e) }
} }
} }
if (typeof observation === 'string' && observation.includes(SOURCE_DOCUMENTS_PREFIX)) { if (typeof observation === 'string' && observation.includes(SOURCE_DOCUMENTS_PREFIX)) {

View File

@ -0,0 +1,25 @@
type ErrorWithMessage = {
message: string
}
const isErrorWithMessage = (error: unknown): error is ErrorWithMessage => {
return (
typeof error === 'object' && error !== null && 'message' in error && typeof (error as Record<string, unknown>).message === 'string'
)
}
const toErrorWithMessage = (maybeError: unknown): ErrorWithMessage => {
if (isErrorWithMessage(maybeError)) return maybeError
try {
return new Error(JSON.stringify(maybeError))
} catch {
// fallback in case there's an error stringifying the maybeError
// like with circular references for example.
return new Error(String(maybeError))
}
}
export const getErrorMessage = (error: unknown) => {
return toErrorWithMessage(error).message
}

View File

@ -2,7 +2,7 @@ import { createPortal } from 'react-dom'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Dialog, DialogContent, DialogTitle } from '@mui/material' import { Box, Dialog, DialogContent, DialogTitle, Typography } from '@mui/material'
import ReactJson from 'flowise-react-json-view' import ReactJson from 'flowise-react-json-view'
const SourceDocDialog = ({ show, dialogProps, onCancel }) => { const SourceDocDialog = ({ show, dialogProps, onCancel }) => {
@ -32,6 +32,25 @@ const SourceDocDialog = ({ show, dialogProps, onCancel }) => {
{dialogProps.title ?? 'Source Documents'} {dialogProps.title ?? 'Source Documents'}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
{data.error && (
<Box
sx={{
p: 2,
borderRadius: 1,
bgcolor: 'error.light',
color: 'error.dark',
overflowX: 'auto',
wordBreak: 'break-word'
}}
>
<Typography variant='body2' fontWeight='medium'>
Error:
</Typography>
<Typography variant='body2' sx={{ whiteSpace: 'pre-wrap' }}>
{data.error}
</Typography>
</Box>
)}
<ReactJson <ReactJson
theme={customization.isDarkMode ? 'ocean' : 'rjv-default'} theme={customization.isDarkMode ? 'ocean' : 'rjv-default'}
style={{ padding: 10, borderRadius: 10 }} style={{ padding: 10, borderRadius: 10 }}

View File

@ -1214,10 +1214,30 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
key={index} key={index}
label={tool.tool} label={tool.tool}
component='a' component='a'
sx={{ mr: 1, mt: 1 }} sx={{
mr: 1,
mt: 1,
borderColor: tool.error
? 'error.main'
: undefined,
color: tool.error
? 'error.main'
: undefined
}}
variant='outlined' variant='outlined'
clickable clickable
icon={<IconTool size={15} />} icon={
<IconTool
size={15}
color={
tool.error
? theme.palette
.error
.main
: undefined
}
/>
}
onClick={() => onClick={() =>
onSourceDialogClick( onSourceDialogClick(
tool, tool,
@ -1407,9 +1427,24 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
key={index} key={index}
label={tool.tool} label={tool.tool}
component='a' component='a'
sx={{ mr: 1, mt: 1 }} sx={{
mr: 1,
mt: 1,
borderColor: tool.error ? 'error.main' : undefined,
color: tool.error ? 'error.main' : undefined
}}
variant='outlined' variant='outlined'
clickable clickable
icon={
<IconTool
size={15}
color={
tool.error
? theme.palette.error.main
: undefined
}
/>
}
onClick={() => onSourceDialogClick(tool, 'Used Tools')} onClick={() => onSourceDialogClick(tool, 'Used Tools')}
/> />
) )

View File

@ -1677,10 +1677,24 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
key={index} key={index}
label={tool.tool} label={tool.tool}
component='a' component='a'
sx={{ mr: 1, mt: 1 }} sx={{
mr: 1,
mt: 1,
borderColor: tool.error ? 'error.main' : undefined,
color: tool.error ? 'error.main' : undefined
}}
variant='outlined' variant='outlined'
clickable clickable
icon={<IconTool size={15} />} icon={
<IconTool
size={15}
color={
tool.error
? theme.palette.error.main
: undefined
}
/>
}
onClick={() => onSourceDialogClick(tool, 'Used Tools')} onClick={() => onSourceDialogClick(tool, 'Used Tools')}
/> />
) : null ) : null
@ -1808,10 +1822,20 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
key={index} key={index}
label={tool.tool} label={tool.tool}
component='a' component='a'
sx={{ mr: 1, mt: 1 }} sx={{
mr: 1,
mt: 1,
borderColor: tool.error ? 'error.main' : undefined,
color: tool.error ? 'error.main' : undefined
}}
variant='outlined' variant='outlined'
clickable clickable
icon={<IconTool size={15} />} icon={
<IconTool
size={15}
color={tool.error ? theme.palette.error.main : undefined}
/>
}
onClick={() => onSourceDialogClick(tool, 'Used Tools')} onClick={() => onSourceDialogClick(tool, 'Used Tools')}
/> />
) : null ) : null

File diff suppressed because one or more lines are too long