1
1
/// <reference types="npm:@types/[email protected] " />
2
2
3
3
import './polyfill.ts'
4
- import http from 'node:http'
5
4
import { randomUUID } from 'node:crypto'
5
+ import http , { type IncomingMessage , type ServerResponse } from 'node:http'
6
6
import { parseArgs } from '@std/cli/parse-args'
7
7
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
8
8
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
@@ -11,14 +11,13 @@ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'
11
11
import { type LoggingLevel , SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js'
12
12
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
13
13
import { z } from 'zod'
14
-
15
- import { asXml , runCode } from './runCode.ts'
16
14
import { Buffer } from 'node:buffer'
15
+ import { asXml , runCode , runCodeWithToolInjection , type ToolInjectionConfig } from './runCode.ts'
17
16
18
- const VERSION = '0.0.13 '
17
+ const VERSION = '0.0.14 '
19
18
20
19
export async function main ( ) {
21
- const { args } = Deno
20
+ const args = globalThis . Deno ?. args || [ ]
22
21
if ( args . length === 1 && args [ 0 ] === 'stdio' ) {
23
22
await runStdio ( )
24
23
} else if ( args . length >= 1 && args [ 0 ] === 'streamable_http' ) {
@@ -29,7 +28,7 @@ export async function main() {
29
28
const port = parseInt ( flags . port )
30
29
runStreamableHttp ( port )
31
30
} else if ( args . length >= 1 && args [ 0 ] === 'sse' ) {
32
- const flags = parseArgs ( Deno . args , {
31
+ const flags = parseArgs ( args , {
33
32
string : [ 'port' ] ,
34
33
default : { port : '3001' } ,
35
34
} )
@@ -47,7 +46,7 @@ Usage: deno run -N -R=node_modules -W=node_modules --node-modules-dir=auto jsr:@
47
46
options:
48
47
--port <port> Port to run the SSE server on (default: 3001)` ,
49
48
)
50
- Deno . exit ( 1 )
49
+ globalThis . Deno ? .exit ( 1 )
51
50
}
52
51
}
53
52
@@ -57,13 +56,14 @@ options:
57
56
function createServer ( ) : McpServer {
58
57
const server = new McpServer (
59
58
{
60
- name : 'MCP Run Python' ,
59
+ name : 'MCP Run Python with Tool Injection ' ,
61
60
version : VERSION ,
62
61
} ,
63
62
{
64
63
instructions : 'Call the "run_python_code" tool with the Python code to run.' ,
65
64
capabilities : {
66
65
logging : { } ,
66
+ elicitation : { } ,
67
67
} ,
68
68
} ,
69
69
)
@@ -81,6 +81,13 @@ with a comment of the form:
81
81
# dependencies = ['pydantic']
82
82
# ///
83
83
print('python code here')
84
+
85
+ TOOL INJECTION: When 'tools' parameter is provided, the specified tool functions become available directly in Python's global namespace. You can call them directly like any other function. For example, if 'web_search' is provided as a tool, you can call it directly:
86
+
87
+ result = web_search("search query")
88
+ print(result)
89
+
90
+ The tools are injected into the global namespace automatically - no discovery functions needed.
84
91
`
85
92
86
93
let setLogLevel : LoggingLevel = 'emergency'
@@ -93,21 +100,115 @@ print('python code here')
93
100
server . tool (
94
101
'run_python_code' ,
95
102
toolDescription ,
96
- { python_code : z . string ( ) . describe ( 'Python code to run' ) } ,
97
- async ( { python_code } : { python_code : string } ) => {
103
+ {
104
+ python_code : z . string ( ) . describe ( 'Python code to run' ) ,
105
+ tools : z
106
+ . array ( z . string ( ) )
107
+ . optional ( )
108
+ . describe ( 'List of available tools for injection (enables tool injection when provided)' ) ,
109
+ } ,
110
+ async ( {
111
+ python_code,
112
+ tools = [ ] ,
113
+ } : {
114
+ python_code : string
115
+ tools ?: string [ ]
116
+ } ) => {
98
117
const logPromises : Promise < void > [ ] = [ ]
99
- const result = await runCode ( [ {
100
- name : 'main.py' ,
101
- content : python_code ,
102
- active : true ,
103
- } ] , ( level , data ) => {
104
- if ( LogLevels . indexOf ( level ) >= LogLevels . indexOf ( setLogLevel ) ) {
105
- logPromises . push ( server . server . sendLoggingMessage ( { level, data } ) )
118
+
119
+ // Check if tools are provided
120
+ if ( tools . length > 0 ) {
121
+ // Create elicitation callback
122
+ // deno-lint-ignore no-explicit-any
123
+ const elicitationCallback = async ( elicitationRequest : any ) => {
124
+ // Convert Python dict to JavaScript object if needed
125
+ let jsRequest
126
+ if ( elicitationRequest && typeof elicitationRequest === 'object' && elicitationRequest . toJs ) {
127
+ jsRequest = elicitationRequest . toJs ( )
128
+ } else if ( elicitationRequest && typeof elicitationRequest === 'object' ) {
129
+ // Handle Python dict-like objects
130
+ jsRequest = {
131
+ message : elicitationRequest . message || elicitationRequest . get ?.( 'message' ) ,
132
+ requestedSchema : elicitationRequest . requestedSchema || elicitationRequest . get ?.( 'requestedSchema' ) ,
133
+ }
134
+ } else {
135
+ jsRequest = elicitationRequest
136
+ }
137
+
138
+ try {
139
+ const elicitationResult = await server . server . request (
140
+ {
141
+ method : 'elicitation/create' ,
142
+ params : {
143
+ message : jsRequest . message ,
144
+ requestedSchema : jsRequest . requestedSchema ,
145
+ } ,
146
+ } ,
147
+ z . object ( {
148
+ action : z . enum ( [ 'accept' , 'decline' , 'cancel' ] ) ,
149
+ content : z . optional ( z . record ( z . string ( ) , z . unknown ( ) ) ) ,
150
+ } ) ,
151
+ )
152
+
153
+ return elicitationResult
154
+ } catch ( error ) {
155
+ logPromises . push (
156
+ server . server . sendLoggingMessage ( {
157
+ level : 'error' ,
158
+ data : `Elicitation error: ${ error } ` ,
159
+ } ) ,
160
+ )
161
+ throw error
162
+ }
163
+ }
164
+
165
+ // Use tool injection mode
166
+ const result = await runCodeWithToolInjection (
167
+ [
168
+ {
169
+ name : 'main.py' ,
170
+ content : python_code ,
171
+ active : true ,
172
+ } ,
173
+ ] ,
174
+ ( level , data ) => {
175
+ if ( LogLevels . indexOf ( level ) >= LogLevels . indexOf ( setLogLevel ) ) {
176
+ logPromises . push ( server . server . sendLoggingMessage ( { level, data } ) )
177
+ }
178
+ } ,
179
+ {
180
+ enableToolInjection : true ,
181
+ availableTools : tools ,
182
+ timeoutSeconds : 30 ,
183
+ elicitationCallback,
184
+ } as ToolInjectionConfig ,
185
+ )
186
+
187
+ await Promise . all ( logPromises )
188
+
189
+ return {
190
+ content : [ { type : 'text' , text : asXml ( result ) } ] ,
191
+ }
192
+ } else {
193
+ // Use basic mode without tool injection
194
+ const result = await runCode (
195
+ [
196
+ {
197
+ name : 'main.py' ,
198
+ content : python_code ,
199
+ active : true ,
200
+ } ,
201
+ ] ,
202
+ ( level , data ) => {
203
+ if ( LogLevels . indexOf ( level ) >= LogLevels . indexOf ( setLogLevel ) ) {
204
+ logPromises . push ( server . server . sendLoggingMessage ( { level, data } ) )
205
+ }
206
+ } ,
207
+ )
208
+ await Promise . all ( logPromises )
209
+ return {
210
+ content : [ { type : 'text' , text : asXml ( result ) } ] ,
106
211
}
107
- } )
108
- await Promise . all ( logPromises )
109
- return {
110
- content : [ { type : 'text' , text : asXml ( result ) } ] ,
111
212
}
112
213
} ,
113
214
)
@@ -167,7 +268,7 @@ function runStreamableHttp(port: number) {
167
268
const mcpServer = createServer ( )
168
269
const transports : { [ sessionId : string ] : StreamableHTTPServerTransport } = { }
169
270
170
- const server = http . createServer ( async ( req , res ) => {
271
+ const server = http . createServer ( async ( req : IncomingMessage , res : ServerResponse ) => {
171
272
const url = httpGetUrl ( req )
172
273
let pathMatch = false
173
274
function match ( method : string , path : string ) : boolean {
@@ -309,21 +410,66 @@ async function warmup() {
309
410
console . error (
310
411
`Running warmup script for MCP Run Python version ${ VERSION } ...` ,
311
412
)
413
+
312
414
const code = `
313
415
import numpy
314
416
a = numpy.array([1, 2, 3])
315
417
print('numpy array:', a)
316
418
a
317
419
`
318
- const result = await runCode ( [ {
319
- name : 'warmup.py' ,
320
- content : code ,
321
- active : true ,
322
- } ] , ( level , data ) =>
323
- // use warn to avoid recursion since console.log is patched in runCode
324
- console . error ( `${ level } : ${ data } ` ) )
325
- console . log ( 'Tool return value:' )
420
+ const result = await runCode (
421
+ [
422
+ {
423
+ name : 'warmup.py' ,
424
+ content : code ,
425
+ active : true ,
426
+ } ,
427
+ ] ,
428
+ ( level , data ) => console . error ( `${ level } : ${ data } ` ) ,
429
+ )
326
430
console . log ( asXml ( result ) )
431
+
432
+ // Test tool injection functionality
433
+ console . error ( 'Testing tool injection framework...' )
434
+ const toolCode = `
435
+ # Test tool injection - directly call an injected tool
436
+ result = web_search("test query")
437
+ print(f"Tool result: {result}")
438
+ "tool_test_complete"
439
+ `
440
+
441
+ try {
442
+ const toolResult = await runCodeWithToolInjection (
443
+ [
444
+ {
445
+ name : 'tool_test.py' ,
446
+ content : toolCode ,
447
+ active : true ,
448
+ } ,
449
+ ] ,
450
+ ( level , data ) => console . error ( `${ level } : ${ data } ` ) ,
451
+ {
452
+ enableToolInjection : true ,
453
+ availableTools : [ 'web_search' , 'send_email' ] ,
454
+ timeoutSeconds : 30 ,
455
+ // deno-lint-ignore no-explicit-any require-await
456
+ elicitationCallback : async ( _elicitationRequest : any ) => {
457
+ // Mock callback for warmup test
458
+ return {
459
+ action : 'accept' ,
460
+ content : {
461
+ result : '{"status": "mock success"}' ,
462
+ } ,
463
+ }
464
+ } ,
465
+ } as ToolInjectionConfig ,
466
+ )
467
+ console . log ( 'Tool injection result:' )
468
+ console . log ( asXml ( toolResult ) )
469
+ } catch ( error ) {
470
+ console . error ( 'Tool injection test failed:' , error )
471
+ }
472
+
327
473
console . log ( '\nwarmup successful 🎉' )
328
474
}
329
475
0 commit comments