Skip to content

Commit 713ed26

Browse files
authored
Feature/MCP (Model Context Protocol) (#4134)
add mcp tools
1 parent 9c22bee commit 713ed26

21 files changed

+1321
-105
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { INodeParams, INodeCredential } from '../src/Interface'
2+
3+
class PostgresUrl implements INodeCredential {
4+
label: string
5+
name: string
6+
version: number
7+
description: string
8+
inputs: INodeParams[]
9+
10+
constructor() {
11+
this.label = 'Postgres URL'
12+
this.name = 'PostgresUrl'
13+
this.version = 1.0
14+
this.inputs = [
15+
{
16+
label: 'Postgres URL',
17+
name: 'postgresUrl',
18+
type: 'string',
19+
placeholder: 'postgresql://localhost/mydb'
20+
}
21+
]
22+
}
23+
}
24+
25+
module.exports = { credClass: PostgresUrl }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { INodeParams, INodeCredential } from '../src/Interface'
2+
3+
class SlackApi implements INodeCredential {
4+
label: string
5+
name: string
6+
version: number
7+
description: string
8+
inputs: INodeParams[]
9+
10+
constructor() {
11+
this.label = 'Slack API'
12+
this.name = 'slackApi'
13+
this.version = 1.0
14+
this.description =
15+
'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'
16+
this.inputs = [
17+
{
18+
label: 'Bot Token',
19+
name: 'botToken',
20+
type: 'password'
21+
},
22+
{
23+
label: 'Team ID',
24+
name: 'teamId',
25+
type: 'string',
26+
placeholder: '<SLACK_TEAM_ID>'
27+
}
28+
]
29+
}
30+
}
31+
32+
module.exports = { credClass: SlackApi }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { z } from 'zod'
2+
import { INode } from '../../../src/Interface'
3+
import { DynamicStructuredTool } from '../CustomTool/core'
4+
5+
const code = `
6+
const now = new Date();
7+
8+
// Format date as YYYY-MM-DD
9+
const date = now.toISOString().split('T')[0];
10+
11+
// Get time in HH:MM:SS format
12+
const time = now.toTimeString().split(' ')[0];
13+
14+
// Get day of week
15+
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
16+
const day = days[now.getDay()];
17+
18+
// Get timezone information
19+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
20+
const timezoneOffset = now.getTimezoneOffset();
21+
const timezoneOffsetHours = Math.abs(Math.floor(timezoneOffset / 60));
22+
const timezoneOffsetMinutes = Math.abs(timezoneOffset % 60);
23+
const timezoneOffsetFormatted =
24+
(timezoneOffset <= 0 ? '+' : '-') +
25+
timezoneOffsetHours.toString().padStart(2, '0') + ':' +
26+
timezoneOffsetMinutes.toString().padStart(2, '0');
27+
28+
return {
29+
date,
30+
time,
31+
day,
32+
timezone,
33+
timezoneOffset: timezoneOffsetFormatted,
34+
iso8601: now.toISOString(),
35+
unix_timestamp: Math.floor(now.getTime() / 1000)
36+
};
37+
`
38+
39+
class CurrentDateTime_Tools implements INode {
40+
label: string
41+
name: string
42+
version: number
43+
description: string
44+
type: string
45+
icon: string
46+
category: string
47+
baseClasses: string[]
48+
49+
constructor() {
50+
this.label = 'CurrentDateTime'
51+
this.name = 'currentDateTime'
52+
this.version = 1.0
53+
this.type = 'CurrentDateTime'
54+
this.icon = 'currentDateTime.svg'
55+
this.category = 'Tools'
56+
this.description = 'Get todays day, date and time.'
57+
this.baseClasses = [this.type, 'Tool']
58+
}
59+
60+
async init(): Promise<any> {
61+
const obj = {
62+
name: 'current_date_time',
63+
description: 'Useful to get current day, date and time.',
64+
schema: z.object({}),
65+
code: code
66+
}
67+
68+
let dynamicStructuredTool = new DynamicStructuredTool(obj)
69+
70+
return dynamicStructuredTool
71+
}
72+
}
73+
74+
module.exports = { nodeClass: CurrentDateTime_Tools }
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { Tool } from '@langchain/core/tools'
2+
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
3+
import { getCredentialData, getCredentialParam, getNodeModulesPackagePath } from '../../../../src/utils'
4+
import { MCPToolkit } from '../core'
5+
6+
class BraveSearch_MCP implements INode {
7+
label: string
8+
name: string
9+
version: number
10+
description: string
11+
type: string
12+
icon: string
13+
category: string
14+
baseClasses: string[]
15+
documentation: string
16+
credential: INodeParams
17+
inputs: INodeParams[]
18+
19+
constructor() {
20+
this.label = 'Brave Search MCP'
21+
this.name = 'braveSearchMCP'
22+
this.version = 1.0
23+
this.type = 'BraveSearch MCP Tool'
24+
this.icon = 'brave.svg'
25+
this.category = 'Tools (MCP)'
26+
this.description = 'MCP server that integrates the Brave Search API - a real-time API to access web search capabilities'
27+
this.documentation = 'https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search'
28+
this.credential = {
29+
label: 'Connect Credential',
30+
name: 'credential',
31+
type: 'credential',
32+
credentialNames: ['braveSearchApi']
33+
}
34+
this.inputs = [
35+
{
36+
label: 'Available Actions',
37+
name: 'mcpActions',
38+
type: 'asyncMultiOptions',
39+
loadMethod: 'listActions',
40+
refresh: true
41+
}
42+
]
43+
this.baseClasses = ['Tool']
44+
}
45+
46+
//@ts-ignore
47+
loadMethods = {
48+
listActions: async (nodeData: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> => {
49+
try {
50+
const toolset = await this.getTools(nodeData, options)
51+
toolset.sort((a: any, b: any) => a.name.localeCompare(b.name))
52+
53+
return toolset.map(({ name, ...rest }) => ({
54+
label: name.toUpperCase(),
55+
name: name,
56+
description: rest.description || name
57+
}))
58+
} catch (error) {
59+
return [
60+
{
61+
label: 'No Available Actions',
62+
name: 'error',
63+
description: 'No available actions, please check your API key and refresh'
64+
}
65+
]
66+
}
67+
}
68+
}
69+
70+
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
71+
const tools = await this.getTools(nodeData, options)
72+
73+
const _mcpActions = nodeData.inputs?.mcpActions
74+
let mcpActions = []
75+
if (_mcpActions) {
76+
try {
77+
mcpActions = typeof _mcpActions === 'string' ? JSON.parse(_mcpActions) : _mcpActions
78+
} catch (error) {
79+
console.error('Error parsing mcp actions:', error)
80+
}
81+
}
82+
83+
return tools.filter((tool: any) => mcpActions.includes(tool.name))
84+
}
85+
86+
async getTools(nodeData: INodeData, options: ICommonObject): Promise<Tool[]> {
87+
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
88+
const braveApiKey = getCredentialParam('braveApiKey', credentialData, nodeData)
89+
const packagePath = getNodeModulesPackagePath('@modelcontextprotocol/server-brave-search/dist/index.js')
90+
91+
const serverParams = {
92+
command: 'node',
93+
args: [packagePath],
94+
env: {
95+
BRAVE_API_KEY: braveApiKey
96+
}
97+
}
98+
99+
const toolkit = new MCPToolkit(serverParams, 'stdio')
100+
await toolkit.initialize()
101+
102+
const tools = toolkit.tools ?? []
103+
104+
return tools as Tool[]
105+
}
106+
}
107+
108+
module.exports = { nodeClass: BraveSearch_MCP }
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Tool } from '@langchain/core/tools'
2+
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
3+
import { getCredentialData, getCredentialParam, getNodeModulesPackagePath } from '../../../../src/utils'
4+
import { MCPToolkit } from '../core'
5+
6+
class Github_MCP implements INode {
7+
label: string
8+
name: string
9+
version: number
10+
description: string
11+
type: string
12+
icon: string
13+
category: string
14+
baseClasses: string[]
15+
documentation: string
16+
credential: INodeParams
17+
inputs: INodeParams[]
18+
19+
constructor() {
20+
this.label = 'Github MCP'
21+
this.name = 'githubMCP'
22+
this.version = 1.0
23+
this.type = 'Github MCP Tool'
24+
this.icon = 'github.svg'
25+
this.category = 'Tools (MCP)'
26+
this.description = 'MCP Server for the GitHub API'
27+
this.documentation = 'https://github.com/modelcontextprotocol/servers/tree/main/src/github'
28+
this.credential = {
29+
label: 'Connect Credential',
30+
name: 'credential',
31+
type: 'credential',
32+
credentialNames: ['githubApi']
33+
}
34+
this.inputs = [
35+
{
36+
label: 'Available Actions',
37+
name: 'mcpActions',
38+
type: 'asyncMultiOptions',
39+
loadMethod: 'listActions',
40+
refresh: true
41+
}
42+
]
43+
this.baseClasses = ['Tool']
44+
}
45+
46+
//@ts-ignore
47+
loadMethods = {
48+
listActions: async (nodeData: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> => {
49+
try {
50+
const toolset = await this.getTools(nodeData, options)
51+
toolset.sort((a: any, b: any) => a.name.localeCompare(b.name))
52+
53+
return toolset.map(({ name, ...rest }) => ({
54+
label: name.toUpperCase(),
55+
name: name,
56+
description: rest.description || name
57+
}))
58+
} catch (error) {
59+
console.error('Error listing actions:', error)
60+
return [
61+
{
62+
label: 'No Available Actions',
63+
name: 'error',
64+
description: 'No available actions, please check your Github Access Token and refresh'
65+
}
66+
]
67+
}
68+
}
69+
}
70+
71+
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
72+
const tools = await this.getTools(nodeData, options)
73+
74+
const _mcpActions = nodeData.inputs?.mcpActions
75+
let mcpActions = []
76+
if (_mcpActions) {
77+
try {
78+
mcpActions = typeof _mcpActions === 'string' ? JSON.parse(_mcpActions) : _mcpActions
79+
} catch (error) {
80+
console.error('Error parsing mcp actions:', error)
81+
}
82+
}
83+
84+
return tools.filter((tool: any) => mcpActions.includes(tool.name))
85+
}
86+
87+
async getTools(nodeData: INodeData, options: ICommonObject): Promise<Tool[]> {
88+
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
89+
const accessToken = getCredentialParam('accessToken', credentialData, nodeData)
90+
91+
if (!accessToken) {
92+
throw new Error('Missing Github Access Token')
93+
}
94+
95+
const packagePath = getNodeModulesPackagePath('@modelcontextprotocol/server-github/dist/index.js')
96+
97+
const serverParams = {
98+
command: 'node',
99+
args: [packagePath],
100+
env: {
101+
GITHUB_PERSONAL_ACCESS_TOKEN: accessToken
102+
}
103+
}
104+
105+
const toolkit = new MCPToolkit(serverParams, 'stdio')
106+
await toolkit.initialize()
107+
108+
const tools = toolkit.tools ?? []
109+
110+
return tools as Tool[]
111+
}
112+
}
113+
114+
module.exports = { nodeClass: Github_MCP }
Loading

0 commit comments

Comments
 (0)