1
1
import { Tool } from '@langchain/core/tools'
2
- import { INode , INodeData , INodeOptionsValue , INodeParams } from '../../../../src/Interface'
2
+ import { ICommonObject , IDatabaseEntity , INode , INodeData , INodeOptionsValue , INodeParams } from '../../../../src/Interface'
3
3
import { MCPToolkit } from '../core'
4
+ import { getVars , prepareSandboxVars } from '../../../../src/utils'
5
+ import { DataSource } from 'typeorm'
4
6
5
7
const mcpServerConfig = `{
6
8
"command": "npx",
7
9
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/files"]
8
10
}`
9
11
12
+ const howToUseCode = `
13
+ You can use variables in the MCP Server Config with double curly braces \`{{ }}\` and prefix \`$vars.<variableName>\`.
14
+
15
+ For example, you have a variable called "var1":
16
+ \`\`\`json
17
+ {
18
+ "command": "docker",
19
+ "args": [
20
+ "run",
21
+ "-i",
22
+ "--rm",
23
+ "-e", "API_TOKEN"
24
+ ],
25
+ "env": {
26
+ "API_TOKEN": "{{$vars.var1}}"
27
+ }
28
+ }
29
+ \`\`\`
30
+ `
31
+
10
32
class Custom_MCP implements INode {
11
33
label : string
12
34
name : string
@@ -23,7 +45,7 @@ class Custom_MCP implements INode {
23
45
constructor ( ) {
24
46
this . label = 'Custom MCP'
25
47
this . name = 'customMCP'
26
- this . version = 1.0
48
+ this . version = 1.1
27
49
this . type = 'Custom MCP Tool'
28
50
this . icon = 'customMCP.png'
29
51
this . category = 'Tools (MCP)'
@@ -35,6 +57,10 @@ class Custom_MCP implements INode {
35
57
name : 'mcpServerConfig' ,
36
58
type : 'code' ,
37
59
hideCodeExecute : true ,
60
+ hint : {
61
+ label : 'How to use' ,
62
+ value : howToUseCode
63
+ } ,
38
64
placeholder : mcpServerConfig
39
65
} ,
40
66
{
@@ -50,9 +76,9 @@ class Custom_MCP implements INode {
50
76
51
77
//@ts -ignore
52
78
loadMethods = {
53
- listActions : async ( nodeData : INodeData ) : Promise < INodeOptionsValue [ ] > => {
79
+ listActions : async ( nodeData : INodeData , options : ICommonObject ) : Promise < INodeOptionsValue [ ] > => {
54
80
try {
55
- const toolset = await this . getTools ( nodeData )
81
+ const toolset = await this . getTools ( nodeData , options )
56
82
toolset . sort ( ( a : any , b : any ) => a . name . localeCompare ( b . name ) )
57
83
58
84
return toolset . map ( ( { name, ...rest } ) => ( {
@@ -72,8 +98,8 @@ class Custom_MCP implements INode {
72
98
}
73
99
}
74
100
75
- async init ( nodeData : INodeData ) : Promise < any > {
76
- const tools = await this . getTools ( nodeData )
101
+ async init ( nodeData : INodeData , _ : string , options : ICommonObject ) : Promise < any > {
102
+ const tools = await this . getTools ( nodeData , options )
77
103
78
104
const _mcpActions = nodeData . inputs ?. mcpActions
79
105
let mcpActions = [ ]
@@ -88,19 +114,29 @@ class Custom_MCP implements INode {
88
114
return tools . filter ( ( tool : any ) => mcpActions . includes ( tool . name ) )
89
115
}
90
116
91
- async getTools ( nodeData : INodeData ) : Promise < Tool [ ] > {
117
+ async getTools ( nodeData : INodeData , options : ICommonObject ) : Promise < Tool [ ] > {
92
118
const mcpServerConfig = nodeData . inputs ?. mcpServerConfig as string
93
-
94
119
if ( ! mcpServerConfig ) {
95
120
throw new Error ( 'MCP Server Config is required' )
96
121
}
97
122
123
+ let sandbox : ICommonObject = { }
124
+
125
+ if ( mcpServerConfig . includes ( '$vars' ) ) {
126
+ const appDataSource = options . appDataSource as DataSource
127
+ const databaseEntities = options . databaseEntities as IDatabaseEntity
128
+
129
+ const variables = await getVars ( appDataSource , databaseEntities , nodeData , options )
130
+ sandbox [ '$vars' ] = prepareSandboxVars ( variables )
131
+ }
132
+
98
133
try {
99
134
let serverParams
100
135
if ( typeof mcpServerConfig === 'object' ) {
101
- serverParams = mcpServerConfig
136
+ serverParams = substituteVariablesInObject ( mcpServerConfig , sandbox )
102
137
} else if ( typeof mcpServerConfig === 'string' ) {
103
- const serverParamsString = convertToValidJSONString ( mcpServerConfig )
138
+ const substitutedString = substituteVariablesInString ( mcpServerConfig , sandbox )
139
+ const serverParamsString = convertToValidJSONString ( substitutedString )
104
140
serverParams = JSON . parse ( serverParamsString )
105
141
}
106
142
@@ -123,6 +159,67 @@ class Custom_MCP implements INode {
123
159
}
124
160
}
125
161
162
+ function substituteVariablesInObject ( obj : any , sandbox : any ) : any {
163
+ if ( typeof obj === 'string' ) {
164
+ // Replace variables in string values
165
+ return substituteVariablesInString ( obj , sandbox )
166
+ } else if ( Array . isArray ( obj ) ) {
167
+ // Recursively process arrays
168
+ return obj . map ( ( item ) => substituteVariablesInObject ( item , sandbox ) )
169
+ } else if ( obj !== null && typeof obj === 'object' ) {
170
+ // Recursively process object properties
171
+ const result : any = { }
172
+ for ( const [ key , value ] of Object . entries ( obj ) ) {
173
+ result [ key ] = substituteVariablesInObject ( value , sandbox )
174
+ }
175
+ return result
176
+ }
177
+ // Return primitive values as-is
178
+ return obj
179
+ }
180
+
181
+ function substituteVariablesInString ( str : string , sandbox : any ) : string {
182
+ // Use regex to find {{$variableName.property}} patterns and replace with sandbox values
183
+ return str . replace ( / \{ \{ \$ ( [ a - z A - Z _ ] [ a - z A - Z 0 - 9 _ ] * (?: \. [ a - z A - Z _ ] [ a - z A - Z 0 - 9 _ ] * ) * ) \} \} / g, ( match , variablePath ) => {
184
+ try {
185
+ // Split the path into parts (e.g., "vars.testvar1" -> ["vars", "testvar1"])
186
+ const pathParts = variablePath . split ( '.' )
187
+
188
+ // Start with the sandbox object
189
+ let current = sandbox
190
+
191
+ // Navigate through the path
192
+ for ( const part of pathParts ) {
193
+ // For the first part, check if it exists with $ prefix
194
+ if ( current === sandbox ) {
195
+ const sandboxKey = `$${ part } `
196
+ if ( Object . keys ( current ) . includes ( sandboxKey ) ) {
197
+ current = current [ sandboxKey ]
198
+ } else {
199
+ // If the key doesn't exist, return the original match
200
+ return match
201
+ }
202
+ } else {
203
+ // For subsequent parts, access directly
204
+ if ( current && typeof current === 'object' && part in current ) {
205
+ current = current [ part ]
206
+ } else {
207
+ // If the property doesn't exist, return the original match
208
+ return match
209
+ }
210
+ }
211
+ }
212
+
213
+ // Return the resolved value, converting to string if necessary
214
+ return typeof current === 'string' ? current : JSON . stringify ( current )
215
+ } catch ( error ) {
216
+ // If any error occurs during resolution, return the original match
217
+ console . warn ( `Error resolving variable ${ match } :` , error )
218
+ return match
219
+ }
220
+ } )
221
+ }
222
+
126
223
function convertToValidJSONString ( inputString : string ) {
127
224
try {
128
225
const jsObject = Function ( 'return ' + inputString ) ( )
0 commit comments