From 2e390a3cde027ddd3c6c5f9f007c24b6df664c77 Mon Sep 17 00:00:00 2001 From: luoshasha Date: Wed, 26 Nov 2025 09:58:12 +0800 Subject: [PATCH 01/12] feat: add [--host] parameter for streamable-http mode. --- mcp_run_python/_cli.py | 4 +++- mcp_run_python/deno/src/main.ts | 13 ++++++++----- mcp_run_python/main.py | 19 +++++++++++++++---- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/mcp_run_python/_cli.py b/mcp_run_python/_cli.py index 69bab42..7adc87b 100644 --- a/mcp_run_python/_cli.py +++ b/mcp_run_python/_cli.py @@ -20,7 +20,8 @@ def cli_logic(args_list: Sequence[str] | None = None) -> int: description=f'mcp-run-python CLI v{__version__}\n\nMCP server for running untrusted Python code.\n', formatter_class=argparse.RawTextHelpFormatter, ) - + parser.add_argument('--host', type=str, default='127.0.0.1', + help='Host to bind the HTTP server to (default: 127.0.0.1). Use 0.0.0.0 to bind to all interfaces, e.g. when using Docker.') parser.add_argument('--port', type=int, help='Port to run the server on, default 3001.') parser.add_argument('--deps', '--dependencies', help='Comma separated list of dependencies to install') parser.add_argument( @@ -51,6 +52,7 @@ def cli_logic(args_list: Sequence[str] | None = None) -> int: args.mode.replace('-', '_'), allow_networking=not args.disable_networking, http_port=args.port, + http_host=args.host, dependencies=deps, deps_log_handler=deps_log_handler, verbose=bool(args.verbose), diff --git a/mcp_run_python/deno/src/main.ts b/mcp_run_python/deno/src/main.ts index f71ccbd..4a0e467 100644 --- a/mcp_run_python/deno/src/main.ts +++ b/mcp_run_python/deno/src/main.ts @@ -30,11 +30,13 @@ export async function main() { return } else if (args[0] === 'streamable_http') { const port = parseInt(flags.port) - runStreamableHttp(port, deps, flags['return-mode'], false) + const host = flags.host + runStreamableHttp(port, host, deps, flags['return-mode'], false) return } else if (args[0] === 'streamable_http_stateless') { const port = parseInt(flags.port) - runStreamableHttp(port, deps, flags['return-mode'], true) + const host = flags.host + runStreamableHttp(port, host, deps, flags['return-mode'], true) return } else if (args[0] === 'example') { await example(deps) @@ -52,6 +54,7 @@ Usage: deno ... deno/main.ts [stdio|streamable_http|streamable_http_stateless|ex options: --port Port to run the HTTP server on (default: 3001) +--host Host to run the HTTP server on (default: 127.0.0.1) --deps Comma separated list of dependencies to install --return-mode Return mode for output data (default: xml)`, ) @@ -171,9 +174,9 @@ function httpSetJsonResponse(res: http.ServerResponse, status: number, text: str /* * Run the MCP server using the Streamable HTTP transport */ -function runStreamableHttp(port: number, deps: string[], returnMode: string, stateless: boolean): void { +function runStreamableHttp(port: number, host:string, deps: string[], returnMode: string, stateless: boolean): void { const server = (stateless ? createStatelessHttpServer : createStatefulHttpServer)(deps, returnMode) - server.listen(port, () => { + server.listen(port, host, () => { console.log(`Listening on port ${port}`) }) } @@ -353,4 +356,4 @@ const LogLevels: LoggingLevel[] = [ 'emergency', ] -await main() +await main() \ No newline at end of file diff --git a/mcp_run_python/main.py b/mcp_run_python/main.py index 1b95224..b795a09 100644 --- a/mcp_run_python/main.py +++ b/mcp_run_python/main.py @@ -23,6 +23,7 @@ def run_mcp_server( mode: Mode, *, http_port: int | None = None, + http_host: str | None = None, dependencies: list[str] | None = None, return_mode: Literal['json', 'xml'] = 'xml', deps_log_handler: LogHandler | None = None, @@ -34,6 +35,7 @@ def run_mcp_server( Args: mode: The mode to run the server in. http_port: The port to run the server on if mode is `streamable_http`. + http_host: The host to run the server on if mode is `streamable_http`. dependencies: The dependencies to install. return_mode: The mode to return tool results in. deps_log_handler: Optional function to receive logs emitted while installing dependencies. @@ -49,6 +51,7 @@ def run_mcp_server( mode, dependencies=dependencies, http_port=http_port, + http_host=http_host, return_mode=return_mode, deps_log_handler=deps_log_handler, allow_networking=allow_networking, @@ -78,6 +81,7 @@ def prepare_deno_env( mode: Mode, *, http_port: int | None = None, + http_host: str | None = None, dependencies: list[str] | None = None, return_mode: Literal['json', 'xml'] = 'xml', deps_log_handler: LogHandler | None = None, @@ -92,6 +96,7 @@ def prepare_deno_env( Args: mode: The mode to run the server in. http_port: The port to run the server on if mode is `streamable_http`. + http_host: The host to run the server on if mode is `streamable_http`. dependencies: The dependencies to install. return_mode: The mode to return tool results in. deps_log_handler: Optional function to receive logs emitted while installing dependencies. @@ -126,6 +131,7 @@ def prepare_deno_env( args = _deno_run_args( mode, http_port=http_port, + http_host=http_host, dependencies=dependencies, return_mode=return_mode, allow_networking=allow_networking, @@ -141,6 +147,7 @@ async def async_prepare_deno_env( mode: Mode, *, http_port: int | None = None, + http_host: str | None = None, dependencies: list[str] | None = None, return_mode: Literal['json', 'xml'] = 'xml', deps_log_handler: LogHandler | None = None, @@ -151,6 +158,7 @@ async def async_prepare_deno_env( prepare_deno_env, mode, http_port=http_port, + http_host=http_host, dependencies=dependencies, return_mode=return_mode, deps_log_handler=deps_log_handler, @@ -181,6 +189,7 @@ def _deno_run_args( mode: Mode, *, http_port: int | None = None, + http_host: str | None = None, dependencies: list[str] | None = None, return_mode: Literal['json', 'xml'] = 'xml', allow_networking: bool = True, @@ -197,11 +206,13 @@ def _deno_run_args( ] if dependencies is not None: args.append(f'--deps={",".join(dependencies)}') - if http_port is not None: - if mode in ('streamable_http', 'streamable_http_stateless'): + if mode in ('streamable_http', 'streamable_http_stateless'): + if http_port is not None: args.append(f'--port={http_port}') - else: - raise ValueError('Port is only supported for `streamable_http` modes') + if http_host is not None: + args.append(f'--host={http_host}') + elif http_port is not None or http_host is not None: + raise ValueError('Port and host are only supported for `streamable_http` mode') return args From 5a273727f9675fdb7bb6c9b5eace511f5cd046f2 Mon Sep 17 00:00:00 2001 From: luoshasha Date: Mon, 24 Nov 2025 13:22:49 +0800 Subject: [PATCH 02/12] fix: fix code style conflicts. --- mcp_run_python/deno/src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcp_run_python/deno/src/main.ts b/mcp_run_python/deno/src/main.ts index 4a0e467..5db7d31 100644 --- a/mcp_run_python/deno/src/main.ts +++ b/mcp_run_python/deno/src/main.ts @@ -177,7 +177,7 @@ function httpSetJsonResponse(res: http.ServerResponse, status: number, text: str function runStreamableHttp(port: number, host:string, deps: string[], returnMode: string, stateless: boolean): void { const server = (stateless ? createStatelessHttpServer : createStatefulHttpServer)(deps, returnMode) server.listen(port, host, () => { - console.log(`Listening on port ${port}`) + console.log(`Listening on host ${host} port ${port}`) }) } From aa41469e6a35e4f561387103d111a8e2e7cf41fc Mon Sep 17 00:00:00 2001 From: luoshasha Date: Thu, 20 Nov 2025 11:00:40 +0800 Subject: [PATCH 03/12] feat: add [--host] parameter for streamable-http mode. --- mcp_run_python/deno/src/main.ts | 298 ++++++++++++++++++-------------- mcp_run_python/main.py | 14 +- 2 files changed, 174 insertions(+), 138 deletions(-) diff --git a/mcp_run_python/deno/src/main.ts b/mcp_run_python/deno/src/main.ts index 5db7d31..e34a921 100644 --- a/mcp_run_python/deno/src/main.ts +++ b/mcp_run_python/deno/src/main.ts @@ -1,33 +1,36 @@ // deno-lint-ignore-file no-explicit-any /// -import './polyfill.ts' -import http from 'node:http' -import { randomUUID } from 'node:crypto' -import { parseArgs } from '@std/cli/parse-args' -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' -import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js' -import { type LoggingLevel, SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js' -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' -import { z } from 'zod' - -import { asJson, asXml, RunCode } from './runCode.ts' -import { Buffer } from 'node:buffer' - -const VERSION = '0.0.13' +import './polyfill.ts'; +import http from 'node:http'; +import { randomUUID } from 'node:crypto'; +import { parseArgs } from '@std/cli/parse-args'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; +import { + type LoggingLevel, + SetLevelRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z } from 'zod'; + +import { asJson, asXml, RunCode } from './runCode.ts'; +import { Buffer } from 'node:buffer'; + +const VERSION = '0.0.13'; export async function main() { - const { args } = Deno + const { args } = Deno; const flags = parseArgs(Deno.args, { - string: ['deps', 'return-mode', 'port'], - default: { port: '3001', 'return-mode': 'xml' }, - }) - const deps = flags.deps?.split(',') ?? [] + string: ['deps', 'return-mode', 'port', 'host'], + default: { port: '3001', 'return-mode': 'xml', host: '127.0.0.1' }, + }); + const deps = flags.deps?.split(',') ?? []; if (args.length >= 1) { if (args[0] === 'stdio') { - await runStdio(deps, flags['return-mode']) - return + await runStdio(deps, flags['return-mode']); + return; } else if (args[0] === 'streamable_http') { const port = parseInt(flags.port) const host = flags.host @@ -39,11 +42,11 @@ export async function main() { runStreamableHttp(port, host, deps, flags['return-mode'], true) return } else if (args[0] === 'example') { - await example(deps) - return + await example(deps); + return; } else if (args[0] === 'noop') { - await installDeps(deps) - return + await installDeps(deps); + return; } } console.error( @@ -56,42 +59,43 @@ options: --port Port to run the HTTP server on (default: 3001) --host Host to run the HTTP server on (default: 127.0.0.1) --deps Comma separated list of dependencies to install ---return-mode Return mode for output data (default: xml)`, - ) - Deno.exit(1) +--return-mode Return mode for output data (default: xml)` + ); + Deno.exit(1); } /* * Create an MCP server with the `run_python_code` tool registered. */ function createServer(deps: string[], returnMode: string): McpServer { - const runCode = new RunCode() + const runCode = new RunCode(); const server = new McpServer( { name: 'MCP Run Python', version: VERSION, }, { - instructions: 'Call the "run_python_code" tool with the Python code to run.', + instructions: + 'Call the "run_python_code" tool with the Python code to run.', capabilities: { logging: {}, }, - }, - ) + } + ); const toolDescription = `Tool to execute Python code and return stdout, stderr, and return value. The code may be async, and the value on the last line will be returned as the return value. The code will be executed with Python 3.13. -` +`; - let setLogLevel: LoggingLevel = 'emergency' + let setLogLevel: LoggingLevel = 'emergency'; - server.server.setRequestHandler(SetLevelRequestSchema, (request) => { - setLogLevel = request.params.level - return {} - }) + server.server.setRequestHandler(SetLevelRequestSchema, request => { + setLogLevel = request.params.level; + return {}; + }); server.registerTool( 'run_python_code', @@ -100,75 +104,99 @@ The code will be executed with Python 3.13. description: toolDescription, inputSchema: { python_code: z.string().describe('Python code to run'), - global_variables: z.record(z.string(), z.any()).default({}).describe( - 'Map of global variables in context when the code is executed', - ), + global_variables: z + .record(z.string(), z.any()) + .default({}) + .describe( + 'Map of global variables in context when the code is executed' + ), }, }, - async ({ python_code, global_variables }: { python_code: string; global_variables: Record }) => { - const logPromises: Promise[] = [] + async ({ + python_code, + global_variables, + }: { + python_code: string; + global_variables: Record; + }) => { + const logPromises: Promise[] = []; const result = await runCode.run( deps, (level, data) => { if (LogLevels.indexOf(level) >= LogLevels.indexOf(setLogLevel)) { - logPromises.push(server.server.sendLoggingMessage({ level, data })) + logPromises.push(server.server.sendLoggingMessage({ level, data })); } }, { name: 'main.py', content: python_code }, global_variables, - returnMode !== 'xml', - ) - await Promise.all(logPromises) + returnMode !== 'xml' + ); + await Promise.all(logPromises); return { - content: [{ type: 'text', text: returnMode === 'xml' ? asXml(result) : asJson(result) }], - } - }, - ) - return server + content: [ + { + type: 'text', + text: returnMode === 'xml' ? asXml(result) : asJson(result), + }, + ], + }; + } + ); + return server; } /* * Define some QOL functions for both the Streamable HTTP server implementation */ function httpGetUrl(req: http.IncomingMessage): URL { - return new URL( - req.url ?? '', - `http://${req.headers.host ?? 'unknown'}`, - ) + return new URL(req.url ?? '', `http://${req.headers.host ?? 'unknown'}`); } function httpGetBody(req: http.IncomingMessage): Promise { // https://nodejs.org/en/learn/modules/anatomy-of-an-http-transaction#request-body - return new Promise((resolve) => { - const bodyParts: any[] = [] - let body - req.on('data', (chunk) => { - bodyParts.push(chunk) - }).on('end', () => { - body = Buffer.concat(bodyParts).toString() - resolve(JSON.parse(body)) - }) - }) + return new Promise(resolve => { + const bodyParts: any[] = []; + let body; + req + .on('data', chunk => { + bodyParts.push(chunk); + }) + .on('end', () => { + body = Buffer.concat(bodyParts).toString(); + resolve(JSON.parse(body)); + }); + }); } -function httpSetTextResponse(res: http.ServerResponse, status: number, text: string) { - res.setHeader('Content-Type', 'text/plain') - res.statusCode = status - res.end(`${text}\n`) +function httpSetTextResponse( + res: http.ServerResponse, + status: number, + text: string +) { + res.setHeader('Content-Type', 'text/plain'); + res.statusCode = status; + res.end(`${text}\n`); } -function httpSetJsonResponse(res: http.ServerResponse, status: number, text: string, code: number) { - res.setHeader('Content-Type', 'application/json') - res.statusCode = status - res.write(JSON.stringify({ - jsonrpc: '2.0', - error: { - code: code, - message: text, - }, - id: null, - })) - res.end() +function httpSetJsonResponse( + res: http.ServerResponse, + status: number, + text: string, + code: number +) { + res.setHeader('Content-Type', 'application/json'); + res.statusCode = status; + res.write( + JSON.stringify({ + jsonrpc: '2.0', + error: { + code: code, + message: text, + }, + id: null, + }) + ); + res.end(); } /* @@ -217,103 +245,107 @@ function createStatelessHttpServer(deps: string[], returnMode: string): http.Ser function createStatefulHttpServer(deps: string[], returnMode: string): http.Server { // Stateful mode with session management // https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#with-session-management - const mcpServer = createServer(deps, returnMode) - const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {} + const mcpServer = createServer(deps, returnMode); + const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; return http.createServer(async (req, res) => { - const url = httpGetUrl(req) - let pathMatch = false + const url = httpGetUrl(req); + let pathMatch = false; function match(method: string, path: string): boolean { if (url.pathname === path) { - pathMatch = true - return req.method === method + pathMatch = true; + return req.method === method; } - return false + return false; } // Reusable handler for GET and DELETE requests async function handleSessionRequest() { - const sessionId = req.headers['mcp-session-id'] as string | undefined + const sessionId = req.headers['mcp-session-id'] as string | undefined; if (!sessionId || !transports[sessionId]) { - httpSetTextResponse(res, 400, 'Invalid or missing session ID') - return + httpSetTextResponse(res, 400, 'Invalid or missing session ID'); + return; } - const transport = transports[sessionId] - await transport.handleRequest(req, res) + const transport = transports[sessionId]; + await transport.handleRequest(req, res); } // Handle different request methods and paths if (match('POST', '/mcp')) { // Check for existing session ID - const sessionId = req.headers['mcp-session-id'] as string | undefined - let transport: StreamableHTTPServerTransport + const sessionId = req.headers['mcp-session-id'] as string | undefined; + let transport: StreamableHTTPServerTransport; - const body = await httpGetBody(req) + const body = await httpGetBody(req); if (sessionId && transports[sessionId]) { // Reuse existing transport - transport = transports[sessionId] + transport = transports[sessionId]; } else if (!sessionId && isInitializeRequest(body)) { // New initialization request transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), - onsessioninitialized: (sessionId) => { + onsessioninitialized: sessionId => { // Store the transport by session ID - transports[sessionId] = transport + transports[sessionId] = transport; }, - }) + }); // Clean up transport when closed transport.onclose = () => { if (transport.sessionId) { - delete transports[transport.sessionId] + delete transports[transport.sessionId]; } - } + }; - await mcpServer.connect(transport) + await mcpServer.connect(transport); } else { - httpSetJsonResponse(res, 400, 'Bad Request: No valid session ID provided', -32000) - return + httpSetJsonResponse( + res, + 400, + 'Bad Request: No valid session ID provided', + -32000 + ); + return; } // Handle the request - await transport.handleRequest(req, res, body) + await transport.handleRequest(req, res, body); } else if (match('GET', '/mcp')) { // Handle server-to-client notifications - await handleSessionRequest() + await handleSessionRequest(); } else if (match('DELETE', '/mcp')) { // Handle requests for session termination - await handleSessionRequest() + await handleSessionRequest(); } else if (pathMatch) { - httpSetTextResponse(res, 405, 'Method not allowed') + httpSetTextResponse(res, 405, 'Method not allowed'); } else { - httpSetTextResponse(res, 404, 'Page not found') + httpSetTextResponse(res, 404, 'Page not found'); } - }) + }); } /* * Run the MCP server using the Stdio transport. */ async function runStdio(deps: string[], returnMode: string) { - const mcpServer = createServer(deps, returnMode) - const transport = new StdioServerTransport() - await mcpServer.connect(transport) + const mcpServer = createServer(deps, returnMode); + const transport = new StdioServerTransport(); + await mcpServer.connect(transport); } /* * Run pyodide to download and install dependencies. */ async function installDeps(deps: string[]) { - const runCode = new RunCode() - const result = await runCode.run( - deps, - (level, data) => console.error(`${level}|${data}`), - ) + const runCode = new RunCode(); + const result = await runCode.run(deps, (level, data) => + console.error(`${level}|${data}`) + ); if (result.status !== 'success') { - console.error('error|Failed to install dependencies') - Deno.exit(1) + console.error('error|Failed to install dependencies'); + Deno.exit(1); } } @@ -322,25 +354,25 @@ async function installDeps(deps: string[]) { */ async function example(deps: string[]) { console.error( - `Running example script for MCP Run Python version ${VERSION}...`, - ) + `Running example script for MCP Run Python version ${VERSION}...` + ); const code = ` import numpy a = numpy.array([1, 2, 3]) print('numpy array:', a) a -` - const runCode = new RunCode() +`; + const runCode = new RunCode(); const result = await runCode.run( deps, // use warn to avoid recursion since console.log is patched in runCode (level, data) => console.warn(`${level}: ${data}`), - { name: 'example.py', content: code }, - ) - console.log('Tool return value:') - console.log(asXml(result)) + { name: 'example.py', content: code } + ); + console.log('Tool return value:'); + console.log(asXml(result)); if (result.status !== 'success') { - Deno.exit(1) + Deno.exit(1); } } @@ -354,6 +386,6 @@ const LogLevels: LoggingLevel[] = [ 'critical', 'alert', 'emergency', -] +]; await main() \ No newline at end of file diff --git a/mcp_run_python/main.py b/mcp_run_python/main.py index b795a09..59abd24 100644 --- a/mcp_run_python/main.py +++ b/mcp_run_python/main.py @@ -83,7 +83,7 @@ def prepare_deno_env( http_port: int | None = None, http_host: str | None = None, dependencies: list[str] | None = None, - return_mode: Literal['json', 'xml'] = 'xml', + return_mode: Literal["json", "xml"] = "xml", deps_log_handler: LogHandler | None = None, allow_networking: bool = True, ) -> Iterator[DenoEnv]: @@ -126,7 +126,9 @@ def prepare_deno_env( stdout.append(line) p.wait() if p.returncode != 0: - raise RuntimeError(f'`deno run ...` returned a non-zero exit code {p.returncode}: {"".join(stdout)}') + raise RuntimeError( + f'`deno run ...` returned a non-zero exit code {p.returncode}: {"".join(stdout)}' + ) args = _deno_run_args( mode, @@ -149,7 +151,7 @@ async def async_prepare_deno_env( http_port: int | None = None, http_host: str | None = None, dependencies: list[str] | None = None, - return_mode: Literal['json', 'xml'] = 'xml', + return_mode: Literal["json", "xml"] = "xml", deps_log_handler: LogHandler | None = None, allow_networking: bool = True, ) -> AsyncIterator[DenoEnv]: @@ -191,7 +193,7 @@ def _deno_run_args( http_port: int | None = None, http_host: str | None = None, dependencies: list[str] | None = None, - return_mode: Literal['json', 'xml'] = 'xml', + return_mode: Literal["json", "xml"] = "xml", allow_networking: bool = True, ) -> list[str]: args = ['run'] @@ -221,4 +223,6 @@ def _deno_run_args( async def _asyncify(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: - return await asyncio.get_event_loop().run_in_executor(None, partial(func, *args, **kwargs)) + return await asyncio.get_event_loop().run_in_executor( + None, partial(func, *args, **kwargs) + ) From 9d1651fcde9b8e534c378178e19952ec50df3c78 Mon Sep 17 00:00:00 2001 From: luoshasha Date: Thu, 20 Nov 2025 11:20:23 +0800 Subject: [PATCH 04/12] fix: add host to cli error info. --- mcp_run_python/main.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mcp_run_python/main.py b/mcp_run_python/main.py index 59abd24..cf13f66 100644 --- a/mcp_run_python/main.py +++ b/mcp_run_python/main.py @@ -83,7 +83,7 @@ def prepare_deno_env( http_port: int | None = None, http_host: str | None = None, dependencies: list[str] | None = None, - return_mode: Literal["json", "xml"] = "xml", + return_mode: Literal['json', 'xml'] = 'xml', deps_log_handler: LogHandler | None = None, allow_networking: bool = True, ) -> Iterator[DenoEnv]: @@ -151,7 +151,7 @@ async def async_prepare_deno_env( http_port: int | None = None, http_host: str | None = None, dependencies: list[str] | None = None, - return_mode: Literal["json", "xml"] = "xml", + return_mode: Literal['json', 'xml'] = 'xml', deps_log_handler: LogHandler | None = None, allow_networking: bool = True, ) -> AsyncIterator[DenoEnv]: @@ -193,7 +193,7 @@ def _deno_run_args( http_port: int | None = None, http_host: str | None = None, dependencies: list[str] | None = None, - return_mode: Literal["json", "xml"] = "xml", + return_mode: Literal['json', 'xml'] = 'xml', allow_networking: bool = True, ) -> list[str]: args = ['run'] @@ -223,6 +223,4 @@ def _deno_run_args( async def _asyncify(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: - return await asyncio.get_event_loop().run_in_executor( - None, partial(func, *args, **kwargs) - ) + return await asyncio.get_event_loop().run_in_executor(None, partial(func, *args, **kwargs)) From 5c1d943cf6ff0d493293e73863f8d9f3e48cf755 Mon Sep 17 00:00:00 2001 From: luoshasha Date: Wed, 26 Nov 2025 10:40:51 +0800 Subject: [PATCH 05/12] fix: remove cli default value to make test happy. --- mcp_run_python/_cli.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/mcp_run_python/_cli.py b/mcp_run_python/_cli.py index 7adc87b..da1c40d 100644 --- a/mcp_run_python/_cli.py +++ b/mcp_run_python/_cli.py @@ -20,13 +20,10 @@ def cli_logic(args_list: Sequence[str] | None = None) -> int: description=f'mcp-run-python CLI v{__version__}\n\nMCP server for running untrusted Python code.\n', formatter_class=argparse.RawTextHelpFormatter, ) - parser.add_argument('--host', type=str, default='127.0.0.1', - help='Host to bind the HTTP server to (default: 127.0.0.1). Use 0.0.0.0 to bind to all interfaces, e.g. when using Docker.') + parser.add_argument('--host', type=str, help='Host to bind the HTTP server to (default: 127.0.0.1). Use 0.0.0.0 to bind to all interfaces, e.g. when using Docker.') parser.add_argument('--port', type=int, help='Port to run the server on, default 3001.') parser.add_argument('--deps', '--dependencies', help='Comma separated list of dependencies to install') - parser.add_argument( - '--disable-networking', action='store_true', help='Disable networking during execution of python code' - ) + parser.add_argument( '--disable-networking', action='store_true', help='Disable networking during execution of python code' ) parser.add_argument('--verbose', action='store_true', help='Enable verbose logging') parser.add_argument('--version', action='store_true', help='Show version and exit') parser.add_argument( From b92b7dc580b68fd940ac9185d1b295a4f7344602 Mon Sep 17 00:00:00 2001 From: luoshasha Date: Fri, 28 Nov 2025 11:16:21 +0800 Subject: [PATCH 06/12] style: modify code style to mark lint happy. --- mcp_run_python/_cli.py | 10 +- mcp_run_python/deno/src/main.ts | 296 ++++++++++++++------------------ mcp_run_python/main.py | 6 +- 3 files changed, 142 insertions(+), 170 deletions(-) diff --git a/mcp_run_python/_cli.py b/mcp_run_python/_cli.py index da1c40d..1c91624 100644 --- a/mcp_run_python/_cli.py +++ b/mcp_run_python/_cli.py @@ -20,10 +20,16 @@ def cli_logic(args_list: Sequence[str] | None = None) -> int: description=f'mcp-run-python CLI v{__version__}\n\nMCP server for running untrusted Python code.\n', formatter_class=argparse.RawTextHelpFormatter, ) - parser.add_argument('--host', type=str, help='Host to bind the HTTP server to (default: 127.0.0.1). Use 0.0.0.0 to bind to all interfaces, e.g. when using Docker.') + parser.add_argument( + '--host', + type=str, + help='Host to bind the HTTP server to (default: 127.0.0.1). Use 0.0.0.0 to bind to all interfaces, e.g. when using Docker.', + ) parser.add_argument('--port', type=int, help='Port to run the server on, default 3001.') parser.add_argument('--deps', '--dependencies', help='Comma separated list of dependencies to install') - parser.add_argument( '--disable-networking', action='store_true', help='Disable networking during execution of python code' ) + parser.add_argument( + '--disable-networking', action='store_true', help='Disable networking during execution of python code' + ) parser.add_argument('--verbose', action='store_true', help='Enable verbose logging') parser.add_argument('--version', action='store_true', help='Show version and exit') parser.add_argument( diff --git a/mcp_run_python/deno/src/main.ts b/mcp_run_python/deno/src/main.ts index e34a921..3f29c02 100644 --- a/mcp_run_python/deno/src/main.ts +++ b/mcp_run_python/deno/src/main.ts @@ -1,36 +1,33 @@ // deno-lint-ignore-file no-explicit-any /// -import './polyfill.ts'; -import http from 'node:http'; -import { randomUUID } from 'node:crypto'; -import { parseArgs } from '@std/cli/parse-args'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; -import { - type LoggingLevel, - SetLevelRequestSchema, -} from '@modelcontextprotocol/sdk/types.js'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { z } from 'zod'; - -import { asJson, asXml, RunCode } from './runCode.ts'; -import { Buffer } from 'node:buffer'; - -const VERSION = '0.0.13'; +import './polyfill.ts' +import http from 'node:http' +import { randomUUID } from 'node:crypto' +import { parseArgs } from '@std/cli/parse-args' +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' +import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js' +import { type LoggingLevel, SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js' +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { z } from 'zod' + +import { asJson, asXml, RunCode } from './runCode.ts' +import { Buffer } from 'node:buffer' + +const VERSION = '0.0.13' export async function main() { - const { args } = Deno; + const { args } = Deno const flags = parseArgs(Deno.args, { string: ['deps', 'return-mode', 'port', 'host'], default: { port: '3001', 'return-mode': 'xml', host: '127.0.0.1' }, - }); - const deps = flags.deps?.split(',') ?? []; + }) + const deps = flags.deps?.split(',') ?? [] if (args.length >= 1) { if (args[0] === 'stdio') { - await runStdio(deps, flags['return-mode']); - return; + await runStdio(deps, flags['return-mode']) + return } else if (args[0] === 'streamable_http') { const port = parseInt(flags.port) const host = flags.host @@ -42,11 +39,11 @@ export async function main() { runStreamableHttp(port, host, deps, flags['return-mode'], true) return } else if (args[0] === 'example') { - await example(deps); - return; + await example(deps) + return } else if (args[0] === 'noop') { - await installDeps(deps); - return; + await installDeps(deps) + return } } console.error( @@ -59,43 +56,42 @@ options: --port Port to run the HTTP server on (default: 3001) --host Host to run the HTTP server on (default: 127.0.0.1) --deps Comma separated list of dependencies to install ---return-mode Return mode for output data (default: xml)` - ); - Deno.exit(1); +--return-mode Return mode for output data (default: xml)`, + ) + Deno.exit(1) } /* * Create an MCP server with the `run_python_code` tool registered. */ function createServer(deps: string[], returnMode: string): McpServer { - const runCode = new RunCode(); + const runCode = new RunCode() const server = new McpServer( { name: 'MCP Run Python', version: VERSION, }, { - instructions: - 'Call the "run_python_code" tool with the Python code to run.', + instructions: 'Call the "run_python_code" tool with the Python code to run.', capabilities: { logging: {}, }, - } - ); + }, + ) const toolDescription = `Tool to execute Python code and return stdout, stderr, and return value. The code may be async, and the value on the last line will be returned as the return value. The code will be executed with Python 3.13. -`; +` - let setLogLevel: LoggingLevel = 'emergency'; + let setLogLevel: LoggingLevel = 'emergency' - server.server.setRequestHandler(SetLevelRequestSchema, request => { - setLogLevel = request.params.level; - return {}; - }); + server.server.setRequestHandler(SetLevelRequestSchema, (request) => { + setLogLevel = request.params.level + return {} + }) server.registerTool( 'run_python_code', @@ -104,99 +100,75 @@ The code will be executed with Python 3.13. description: toolDescription, inputSchema: { python_code: z.string().describe('Python code to run'), - global_variables: z - .record(z.string(), z.any()) - .default({}) - .describe( - 'Map of global variables in context when the code is executed' - ), + global_variables: z.record(z.string(), z.any()).default({}).describe( + 'Map of global variables in context when the code is executed', + ), }, }, - async ({ - python_code, - global_variables, - }: { - python_code: string; - global_variables: Record; - }) => { - const logPromises: Promise[] = []; + async ({ python_code, global_variables }: { python_code: string; global_variables: Record }) => { + const logPromises: Promise[] = [] const result = await runCode.run( deps, (level, data) => { if (LogLevels.indexOf(level) >= LogLevels.indexOf(setLogLevel)) { - logPromises.push(server.server.sendLoggingMessage({ level, data })); + logPromises.push(server.server.sendLoggingMessage({ level, data })) } }, { name: 'main.py', content: python_code }, global_variables, - returnMode !== 'xml' - ); - await Promise.all(logPromises); + returnMode !== 'xml', + ) + await Promise.all(logPromises) return { - content: [ - { - type: 'text', - text: returnMode === 'xml' ? asXml(result) : asJson(result), - }, - ], - }; - } - ); - return server; + content: [{ type: 'text', text: returnMode === 'xml' ? asXml(result) : asJson(result) }], + } + }, + ) + return server } /* * Define some QOL functions for both the Streamable HTTP server implementation */ function httpGetUrl(req: http.IncomingMessage): URL { - return new URL(req.url ?? '', `http://${req.headers.host ?? 'unknown'}`); + return new URL( + req.url ?? '', + `http://${req.headers.host ?? 'unknown'}`, + ) } function httpGetBody(req: http.IncomingMessage): Promise { // https://nodejs.org/en/learn/modules/anatomy-of-an-http-transaction#request-body - return new Promise(resolve => { - const bodyParts: any[] = []; - let body; - req - .on('data', chunk => { - bodyParts.push(chunk); - }) - .on('end', () => { - body = Buffer.concat(bodyParts).toString(); - resolve(JSON.parse(body)); - }); - }); + return new Promise((resolve) => { + const bodyParts: any[] = [] + let body + req.on('data', (chunk) => { + bodyParts.push(chunk) + }).on('end', () => { + body = Buffer.concat(bodyParts).toString() + resolve(JSON.parse(body)) + }) + }) } -function httpSetTextResponse( - res: http.ServerResponse, - status: number, - text: string -) { - res.setHeader('Content-Type', 'text/plain'); - res.statusCode = status; - res.end(`${text}\n`); +function httpSetTextResponse(res: http.ServerResponse, status: number, text: string) { + res.setHeader('Content-Type', 'text/plain') + res.statusCode = status + res.end(`${text}\n`) } -function httpSetJsonResponse( - res: http.ServerResponse, - status: number, - text: string, - code: number -) { - res.setHeader('Content-Type', 'application/json'); - res.statusCode = status; - res.write( - JSON.stringify({ - jsonrpc: '2.0', - error: { - code: code, - message: text, - }, - id: null, - }) - ); - res.end(); +function httpSetJsonResponse(res: http.ServerResponse, status: number, text: string, code: number) { + res.setHeader('Content-Type', 'application/json') + res.statusCode = status + res.write(JSON.stringify({ + jsonrpc: '2.0', + error: { + code: code, + message: text, + }, + id: null, + })) + res.end() } /* @@ -205,7 +177,7 @@ function httpSetJsonResponse( function runStreamableHttp(port: number, host:string, deps: string[], returnMode: string, stateless: boolean): void { const server = (stateless ? createStatelessHttpServer : createStatefulHttpServer)(deps, returnMode) server.listen(port, host, () => { - console.log(`Listening on host ${host} port ${port}`) + console.log(`Listening on port ${port}`) }) } @@ -245,107 +217,103 @@ function createStatelessHttpServer(deps: string[], returnMode: string): http.Ser function createStatefulHttpServer(deps: string[], returnMode: string): http.Server { // Stateful mode with session management // https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#with-session-management - const mcpServer = createServer(deps, returnMode); - const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; + const mcpServer = createServer(deps, returnMode) + const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {} return http.createServer(async (req, res) => { - const url = httpGetUrl(req); - let pathMatch = false; + const url = httpGetUrl(req) + let pathMatch = false function match(method: string, path: string): boolean { if (url.pathname === path) { - pathMatch = true; - return req.method === method; + pathMatch = true + return req.method === method } - return false; + return false } // Reusable handler for GET and DELETE requests async function handleSessionRequest() { - const sessionId = req.headers['mcp-session-id'] as string | undefined; + const sessionId = req.headers['mcp-session-id'] as string | undefined if (!sessionId || !transports[sessionId]) { - httpSetTextResponse(res, 400, 'Invalid or missing session ID'); - return; + httpSetTextResponse(res, 400, 'Invalid or missing session ID') + return } - const transport = transports[sessionId]; - await transport.handleRequest(req, res); + const transport = transports[sessionId] + await transport.handleRequest(req, res) } // Handle different request methods and paths if (match('POST', '/mcp')) { // Check for existing session ID - const sessionId = req.headers['mcp-session-id'] as string | undefined; - let transport: StreamableHTTPServerTransport; + const sessionId = req.headers['mcp-session-id'] as string | undefined + let transport: StreamableHTTPServerTransport - const body = await httpGetBody(req); + const body = await httpGetBody(req) if (sessionId && transports[sessionId]) { // Reuse existing transport - transport = transports[sessionId]; + transport = transports[sessionId] } else if (!sessionId && isInitializeRequest(body)) { // New initialization request transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), - onsessioninitialized: sessionId => { + onsessioninitialized: (sessionId) => { // Store the transport by session ID - transports[sessionId] = transport; + transports[sessionId] = transport }, - }); + }) // Clean up transport when closed transport.onclose = () => { if (transport.sessionId) { - delete transports[transport.sessionId]; + delete transports[transport.sessionId] } - }; + } - await mcpServer.connect(transport); + await mcpServer.connect(transport) } else { - httpSetJsonResponse( - res, - 400, - 'Bad Request: No valid session ID provided', - -32000 - ); - return; + httpSetJsonResponse(res, 400, 'Bad Request: No valid session ID provided', -32000) + return } // Handle the request - await transport.handleRequest(req, res, body); + await transport.handleRequest(req, res, body) } else if (match('GET', '/mcp')) { // Handle server-to-client notifications - await handleSessionRequest(); + await handleSessionRequest() } else if (match('DELETE', '/mcp')) { // Handle requests for session termination - await handleSessionRequest(); + await handleSessionRequest() } else if (pathMatch) { - httpSetTextResponse(res, 405, 'Method not allowed'); + httpSetTextResponse(res, 405, 'Method not allowed') } else { - httpSetTextResponse(res, 404, 'Page not found'); + httpSetTextResponse(res, 404, 'Page not found') } - }); + }) } /* * Run the MCP server using the Stdio transport. */ async function runStdio(deps: string[], returnMode: string) { - const mcpServer = createServer(deps, returnMode); - const transport = new StdioServerTransport(); - await mcpServer.connect(transport); + const mcpServer = createServer(deps, returnMode) + const transport = new StdioServerTransport() + await mcpServer.connect(transport) } /* * Run pyodide to download and install dependencies. */ async function installDeps(deps: string[]) { - const runCode = new RunCode(); - const result = await runCode.run(deps, (level, data) => - console.error(`${level}|${data}`) - ); + const runCode = new RunCode() + const result = await runCode.run( + deps, + (level, data) => console.error(`${level}|${data}`), + ) if (result.status !== 'success') { - console.error('error|Failed to install dependencies'); - Deno.exit(1); + console.error('error|Failed to install dependencies') + Deno.exit(1) } } @@ -354,25 +322,25 @@ async function installDeps(deps: string[]) { */ async function example(deps: string[]) { console.error( - `Running example script for MCP Run Python version ${VERSION}...` - ); + `Running example script for MCP Run Python version ${VERSION}...`, + ) const code = ` import numpy a = numpy.array([1, 2, 3]) print('numpy array:', a) a -`; - const runCode = new RunCode(); +` + const runCode = new RunCode() const result = await runCode.run( deps, // use warn to avoid recursion since console.log is patched in runCode (level, data) => console.warn(`${level}: ${data}`), - { name: 'example.py', content: code } - ); - console.log('Tool return value:'); - console.log(asXml(result)); + { name: 'example.py', content: code }, + ) + console.log('Tool return value:') + console.log(asXml(result)) if (result.status !== 'success') { - Deno.exit(1); + Deno.exit(1) } } @@ -386,6 +354,6 @@ const LogLevels: LoggingLevel[] = [ 'critical', 'alert', 'emergency', -]; +] await main() \ No newline at end of file diff --git a/mcp_run_python/main.py b/mcp_run_python/main.py index cf13f66..3e6f620 100644 --- a/mcp_run_python/main.py +++ b/mcp_run_python/main.py @@ -60,7 +60,7 @@ def run_mcp_server( logger.info('Running mcp-run-python via %s on port %d...', mode, http_port) else: logger.info('Running mcp-run-python via %s...', mode) - + try: p = subprocess.run(('deno', *env.args), cwd=env.cwd, stdout=stdout, stderr=stderr) except KeyboardInterrupt: # pragma: no cover @@ -126,9 +126,7 @@ def prepare_deno_env( stdout.append(line) p.wait() if p.returncode != 0: - raise RuntimeError( - f'`deno run ...` returned a non-zero exit code {p.returncode}: {"".join(stdout)}' - ) + raise RuntimeError(f'`deno run ...` returned a non-zero exit code {p.returncode}: {"".join(stdout)}') args = _deno_run_args( mode, From 12210472221a6f10a93edf29395a3900facc1121 Mon Sep 17 00:00:00 2001 From: luoshasha Date: Thu, 20 Nov 2025 11:00:40 +0800 Subject: [PATCH 07/12] feat: add [--host] parameter for streamable-http mode. --- mcp_run_python/deno/src/main.ts | 290 ++++++++++++++++++-------------- 1 file changed, 161 insertions(+), 129 deletions(-) diff --git a/mcp_run_python/deno/src/main.ts b/mcp_run_python/deno/src/main.ts index 3f29c02..bb70e22 100644 --- a/mcp_run_python/deno/src/main.ts +++ b/mcp_run_python/deno/src/main.ts @@ -1,24 +1,27 @@ // deno-lint-ignore-file no-explicit-any /// -import './polyfill.ts' -import http from 'node:http' -import { randomUUID } from 'node:crypto' -import { parseArgs } from '@std/cli/parse-args' -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' -import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js' -import { type LoggingLevel, SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js' -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' -import { z } from 'zod' - -import { asJson, asXml, RunCode } from './runCode.ts' -import { Buffer } from 'node:buffer' - -const VERSION = '0.0.13' +import './polyfill.ts'; +import http from 'node:http'; +import { randomUUID } from 'node:crypto'; +import { parseArgs } from '@std/cli/parse-args'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; +import { + type LoggingLevel, + SetLevelRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z } from 'zod'; + +import { asJson, asXml, RunCode } from './runCode.ts'; +import { Buffer } from 'node:buffer'; + +const VERSION = '0.0.13'; export async function main() { - const { args } = Deno + const { args } = Deno; const flags = parseArgs(Deno.args, { string: ['deps', 'return-mode', 'port', 'host'], default: { port: '3001', 'return-mode': 'xml', host: '127.0.0.1' }, @@ -26,8 +29,8 @@ export async function main() { const deps = flags.deps?.split(',') ?? [] if (args.length >= 1) { if (args[0] === 'stdio') { - await runStdio(deps, flags['return-mode']) - return + await runStdio(deps, flags['return-mode']); + return; } else if (args[0] === 'streamable_http') { const port = parseInt(flags.port) const host = flags.host @@ -39,11 +42,11 @@ export async function main() { runStreamableHttp(port, host, deps, flags['return-mode'], true) return } else if (args[0] === 'example') { - await example(deps) - return + await example(deps); + return; } else if (args[0] === 'noop') { - await installDeps(deps) - return + await installDeps(deps); + return; } } console.error( @@ -56,42 +59,43 @@ options: --port Port to run the HTTP server on (default: 3001) --host Host to run the HTTP server on (default: 127.0.0.1) --deps Comma separated list of dependencies to install ---return-mode Return mode for output data (default: xml)`, - ) - Deno.exit(1) +--return-mode Return mode for output data (default: xml)` + ); + Deno.exit(1); } /* * Create an MCP server with the `run_python_code` tool registered. */ function createServer(deps: string[], returnMode: string): McpServer { - const runCode = new RunCode() + const runCode = new RunCode(); const server = new McpServer( { name: 'MCP Run Python', version: VERSION, }, { - instructions: 'Call the "run_python_code" tool with the Python code to run.', + instructions: + 'Call the "run_python_code" tool with the Python code to run.', capabilities: { logging: {}, }, - }, - ) + } + ); const toolDescription = `Tool to execute Python code and return stdout, stderr, and return value. The code may be async, and the value on the last line will be returned as the return value. The code will be executed with Python 3.13. -` +`; - let setLogLevel: LoggingLevel = 'emergency' + let setLogLevel: LoggingLevel = 'emergency'; - server.server.setRequestHandler(SetLevelRequestSchema, (request) => { - setLogLevel = request.params.level - return {} - }) + server.server.setRequestHandler(SetLevelRequestSchema, request => { + setLogLevel = request.params.level; + return {}; + }); server.registerTool( 'run_python_code', @@ -100,75 +104,99 @@ The code will be executed with Python 3.13. description: toolDescription, inputSchema: { python_code: z.string().describe('Python code to run'), - global_variables: z.record(z.string(), z.any()).default({}).describe( - 'Map of global variables in context when the code is executed', - ), + global_variables: z + .record(z.string(), z.any()) + .default({}) + .describe( + 'Map of global variables in context when the code is executed' + ), }, }, - async ({ python_code, global_variables }: { python_code: string; global_variables: Record }) => { - const logPromises: Promise[] = [] + async ({ + python_code, + global_variables, + }: { + python_code: string; + global_variables: Record; + }) => { + const logPromises: Promise[] = []; const result = await runCode.run( deps, (level, data) => { if (LogLevels.indexOf(level) >= LogLevels.indexOf(setLogLevel)) { - logPromises.push(server.server.sendLoggingMessage({ level, data })) + logPromises.push(server.server.sendLoggingMessage({ level, data })); } }, { name: 'main.py', content: python_code }, global_variables, - returnMode !== 'xml', - ) - await Promise.all(logPromises) + returnMode !== 'xml' + ); + await Promise.all(logPromises); return { - content: [{ type: 'text', text: returnMode === 'xml' ? asXml(result) : asJson(result) }], - } - }, - ) - return server + content: [ + { + type: 'text', + text: returnMode === 'xml' ? asXml(result) : asJson(result), + }, + ], + }; + } + ); + return server; } /* * Define some QOL functions for both the Streamable HTTP server implementation */ function httpGetUrl(req: http.IncomingMessage): URL { - return new URL( - req.url ?? '', - `http://${req.headers.host ?? 'unknown'}`, - ) + return new URL(req.url ?? '', `http://${req.headers.host ?? 'unknown'}`); } function httpGetBody(req: http.IncomingMessage): Promise { // https://nodejs.org/en/learn/modules/anatomy-of-an-http-transaction#request-body - return new Promise((resolve) => { - const bodyParts: any[] = [] - let body - req.on('data', (chunk) => { - bodyParts.push(chunk) - }).on('end', () => { - body = Buffer.concat(bodyParts).toString() - resolve(JSON.parse(body)) - }) - }) + return new Promise(resolve => { + const bodyParts: any[] = []; + let body; + req + .on('data', chunk => { + bodyParts.push(chunk); + }) + .on('end', () => { + body = Buffer.concat(bodyParts).toString(); + resolve(JSON.parse(body)); + }); + }); } -function httpSetTextResponse(res: http.ServerResponse, status: number, text: string) { - res.setHeader('Content-Type', 'text/plain') - res.statusCode = status - res.end(`${text}\n`) +function httpSetTextResponse( + res: http.ServerResponse, + status: number, + text: string +) { + res.setHeader('Content-Type', 'text/plain'); + res.statusCode = status; + res.end(`${text}\n`); } -function httpSetJsonResponse(res: http.ServerResponse, status: number, text: string, code: number) { - res.setHeader('Content-Type', 'application/json') - res.statusCode = status - res.write(JSON.stringify({ - jsonrpc: '2.0', - error: { - code: code, - message: text, - }, - id: null, - })) - res.end() +function httpSetJsonResponse( + res: http.ServerResponse, + status: number, + text: string, + code: number +) { + res.setHeader('Content-Type', 'application/json'); + res.statusCode = status; + res.write( + JSON.stringify({ + jsonrpc: '2.0', + error: { + code: code, + message: text, + }, + id: null, + }) + ); + res.end(); } /* @@ -217,103 +245,107 @@ function createStatelessHttpServer(deps: string[], returnMode: string): http.Ser function createStatefulHttpServer(deps: string[], returnMode: string): http.Server { // Stateful mode with session management // https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#with-session-management - const mcpServer = createServer(deps, returnMode) - const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {} + const mcpServer = createServer(deps, returnMode); + const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; return http.createServer(async (req, res) => { - const url = httpGetUrl(req) - let pathMatch = false + const url = httpGetUrl(req); + let pathMatch = false; function match(method: string, path: string): boolean { if (url.pathname === path) { - pathMatch = true - return req.method === method + pathMatch = true; + return req.method === method; } - return false + return false; } // Reusable handler for GET and DELETE requests async function handleSessionRequest() { - const sessionId = req.headers['mcp-session-id'] as string | undefined + const sessionId = req.headers['mcp-session-id'] as string | undefined; if (!sessionId || !transports[sessionId]) { - httpSetTextResponse(res, 400, 'Invalid or missing session ID') - return + httpSetTextResponse(res, 400, 'Invalid or missing session ID'); + return; } - const transport = transports[sessionId] - await transport.handleRequest(req, res) + const transport = transports[sessionId]; + await transport.handleRequest(req, res); } // Handle different request methods and paths if (match('POST', '/mcp')) { // Check for existing session ID - const sessionId = req.headers['mcp-session-id'] as string | undefined - let transport: StreamableHTTPServerTransport + const sessionId = req.headers['mcp-session-id'] as string | undefined; + let transport: StreamableHTTPServerTransport; - const body = await httpGetBody(req) + const body = await httpGetBody(req); if (sessionId && transports[sessionId]) { // Reuse existing transport - transport = transports[sessionId] + transport = transports[sessionId]; } else if (!sessionId && isInitializeRequest(body)) { // New initialization request transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), - onsessioninitialized: (sessionId) => { + onsessioninitialized: sessionId => { // Store the transport by session ID - transports[sessionId] = transport + transports[sessionId] = transport; }, - }) + }); // Clean up transport when closed transport.onclose = () => { if (transport.sessionId) { - delete transports[transport.sessionId] + delete transports[transport.sessionId]; } - } + }; - await mcpServer.connect(transport) + await mcpServer.connect(transport); } else { - httpSetJsonResponse(res, 400, 'Bad Request: No valid session ID provided', -32000) - return + httpSetJsonResponse( + res, + 400, + 'Bad Request: No valid session ID provided', + -32000 + ); + return; } // Handle the request - await transport.handleRequest(req, res, body) + await transport.handleRequest(req, res, body); } else if (match('GET', '/mcp')) { // Handle server-to-client notifications - await handleSessionRequest() + await handleSessionRequest(); } else if (match('DELETE', '/mcp')) { // Handle requests for session termination - await handleSessionRequest() + await handleSessionRequest(); } else if (pathMatch) { - httpSetTextResponse(res, 405, 'Method not allowed') + httpSetTextResponse(res, 405, 'Method not allowed'); } else { - httpSetTextResponse(res, 404, 'Page not found') + httpSetTextResponse(res, 404, 'Page not found'); } - }) + }); } /* * Run the MCP server using the Stdio transport. */ async function runStdio(deps: string[], returnMode: string) { - const mcpServer = createServer(deps, returnMode) - const transport = new StdioServerTransport() - await mcpServer.connect(transport) + const mcpServer = createServer(deps, returnMode); + const transport = new StdioServerTransport(); + await mcpServer.connect(transport); } /* * Run pyodide to download and install dependencies. */ async function installDeps(deps: string[]) { - const runCode = new RunCode() - const result = await runCode.run( - deps, - (level, data) => console.error(`${level}|${data}`), - ) + const runCode = new RunCode(); + const result = await runCode.run(deps, (level, data) => + console.error(`${level}|${data}`) + ); if (result.status !== 'success') { - console.error('error|Failed to install dependencies') - Deno.exit(1) + console.error('error|Failed to install dependencies'); + Deno.exit(1); } } @@ -322,25 +354,25 @@ async function installDeps(deps: string[]) { */ async function example(deps: string[]) { console.error( - `Running example script for MCP Run Python version ${VERSION}...`, - ) + `Running example script for MCP Run Python version ${VERSION}...` + ); const code = ` import numpy a = numpy.array([1, 2, 3]) print('numpy array:', a) a -` - const runCode = new RunCode() +`; + const runCode = new RunCode(); const result = await runCode.run( deps, // use warn to avoid recursion since console.log is patched in runCode (level, data) => console.warn(`${level}: ${data}`), - { name: 'example.py', content: code }, - ) - console.log('Tool return value:') - console.log(asXml(result)) + { name: 'example.py', content: code } + ); + console.log('Tool return value:'); + console.log(asXml(result)); if (result.status !== 'success') { - Deno.exit(1) + Deno.exit(1); } } @@ -354,6 +386,6 @@ const LogLevels: LoggingLevel[] = [ 'critical', 'alert', 'emergency', -] +]; await main() \ No newline at end of file From c14a5efbaba800c821e24767a7c7fba8f1c53f01 Mon Sep 17 00:00:00 2001 From: luoshasha Date: Wed, 26 Nov 2025 10:40:51 +0800 Subject: [PATCH 08/12] fix: remove cli default value to make test happy. --- mcp_run_python/_cli.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mcp_run_python/_cli.py b/mcp_run_python/_cli.py index 1c91624..80b93bb 100644 --- a/mcp_run_python/_cli.py +++ b/mcp_run_python/_cli.py @@ -27,9 +27,7 @@ def cli_logic(args_list: Sequence[str] | None = None) -> int: ) parser.add_argument('--port', type=int, help='Port to run the server on, default 3001.') parser.add_argument('--deps', '--dependencies', help='Comma separated list of dependencies to install') - parser.add_argument( - '--disable-networking', action='store_true', help='Disable networking during execution of python code' - ) + parser.add_argument( '--disable-networking', action='store_true', help='Disable networking during execution of python code' ) parser.add_argument('--verbose', action='store_true', help='Enable verbose logging') parser.add_argument('--version', action='store_true', help='Show version and exit') parser.add_argument( From a46906e702a4bf752a4f0378fe95266313bd8560 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 28 Nov 2025 14:56:01 -0600 Subject: [PATCH 09/12] Update mcp_run_python/deno/src/main.ts --- mcp_run_python/deno/src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcp_run_python/deno/src/main.ts b/mcp_run_python/deno/src/main.ts index bb70e22..71facbe 100644 --- a/mcp_run_python/deno/src/main.ts +++ b/mcp_run_python/deno/src/main.ts @@ -388,4 +388,4 @@ const LogLevels: LoggingLevel[] = [ 'emergency', ]; -await main() \ No newline at end of file +await main() From 28df406f3bb989f570cb4aa2ab10e008897a948c Mon Sep 17 00:00:00 2001 From: luoshasha Date: Thu, 4 Dec 2025 11:32:47 +0800 Subject: [PATCH 10/12] style: fix code style. --- mcp_run_python/_cli.py | 4 +- mcp_run_python/deno/src/main.ts | 292 ++++++++++++++------------------ 2 files changed, 133 insertions(+), 163 deletions(-) diff --git a/mcp_run_python/_cli.py b/mcp_run_python/_cli.py index 80b93bb..1c91624 100644 --- a/mcp_run_python/_cli.py +++ b/mcp_run_python/_cli.py @@ -27,7 +27,9 @@ def cli_logic(args_list: Sequence[str] | None = None) -> int: ) parser.add_argument('--port', type=int, help='Port to run the server on, default 3001.') parser.add_argument('--deps', '--dependencies', help='Comma separated list of dependencies to install') - parser.add_argument( '--disable-networking', action='store_true', help='Disable networking during execution of python code' ) + parser.add_argument( + '--disable-networking', action='store_true', help='Disable networking during execution of python code' + ) parser.add_argument('--verbose', action='store_true', help='Enable verbose logging') parser.add_argument('--version', action='store_true', help='Show version and exit') parser.add_argument( diff --git a/mcp_run_python/deno/src/main.ts b/mcp_run_python/deno/src/main.ts index 71facbe..3f29c02 100644 --- a/mcp_run_python/deno/src/main.ts +++ b/mcp_run_python/deno/src/main.ts @@ -1,27 +1,24 @@ // deno-lint-ignore-file no-explicit-any /// -import './polyfill.ts'; -import http from 'node:http'; -import { randomUUID } from 'node:crypto'; -import { parseArgs } from '@std/cli/parse-args'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; -import { - type LoggingLevel, - SetLevelRequestSchema, -} from '@modelcontextprotocol/sdk/types.js'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { z } from 'zod'; - -import { asJson, asXml, RunCode } from './runCode.ts'; -import { Buffer } from 'node:buffer'; - -const VERSION = '0.0.13'; +import './polyfill.ts' +import http from 'node:http' +import { randomUUID } from 'node:crypto' +import { parseArgs } from '@std/cli/parse-args' +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' +import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js' +import { type LoggingLevel, SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js' +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { z } from 'zod' + +import { asJson, asXml, RunCode } from './runCode.ts' +import { Buffer } from 'node:buffer' + +const VERSION = '0.0.13' export async function main() { - const { args } = Deno; + const { args } = Deno const flags = parseArgs(Deno.args, { string: ['deps', 'return-mode', 'port', 'host'], default: { port: '3001', 'return-mode': 'xml', host: '127.0.0.1' }, @@ -29,8 +26,8 @@ export async function main() { const deps = flags.deps?.split(',') ?? [] if (args.length >= 1) { if (args[0] === 'stdio') { - await runStdio(deps, flags['return-mode']); - return; + await runStdio(deps, flags['return-mode']) + return } else if (args[0] === 'streamable_http') { const port = parseInt(flags.port) const host = flags.host @@ -42,11 +39,11 @@ export async function main() { runStreamableHttp(port, host, deps, flags['return-mode'], true) return } else if (args[0] === 'example') { - await example(deps); - return; + await example(deps) + return } else if (args[0] === 'noop') { - await installDeps(deps); - return; + await installDeps(deps) + return } } console.error( @@ -59,43 +56,42 @@ options: --port Port to run the HTTP server on (default: 3001) --host Host to run the HTTP server on (default: 127.0.0.1) --deps Comma separated list of dependencies to install ---return-mode Return mode for output data (default: xml)` - ); - Deno.exit(1); +--return-mode Return mode for output data (default: xml)`, + ) + Deno.exit(1) } /* * Create an MCP server with the `run_python_code` tool registered. */ function createServer(deps: string[], returnMode: string): McpServer { - const runCode = new RunCode(); + const runCode = new RunCode() const server = new McpServer( { name: 'MCP Run Python', version: VERSION, }, { - instructions: - 'Call the "run_python_code" tool with the Python code to run.', + instructions: 'Call the "run_python_code" tool with the Python code to run.', capabilities: { logging: {}, }, - } - ); + }, + ) const toolDescription = `Tool to execute Python code and return stdout, stderr, and return value. The code may be async, and the value on the last line will be returned as the return value. The code will be executed with Python 3.13. -`; +` - let setLogLevel: LoggingLevel = 'emergency'; + let setLogLevel: LoggingLevel = 'emergency' - server.server.setRequestHandler(SetLevelRequestSchema, request => { - setLogLevel = request.params.level; - return {}; - }); + server.server.setRequestHandler(SetLevelRequestSchema, (request) => { + setLogLevel = request.params.level + return {} + }) server.registerTool( 'run_python_code', @@ -104,99 +100,75 @@ The code will be executed with Python 3.13. description: toolDescription, inputSchema: { python_code: z.string().describe('Python code to run'), - global_variables: z - .record(z.string(), z.any()) - .default({}) - .describe( - 'Map of global variables in context when the code is executed' - ), + global_variables: z.record(z.string(), z.any()).default({}).describe( + 'Map of global variables in context when the code is executed', + ), }, }, - async ({ - python_code, - global_variables, - }: { - python_code: string; - global_variables: Record; - }) => { - const logPromises: Promise[] = []; + async ({ python_code, global_variables }: { python_code: string; global_variables: Record }) => { + const logPromises: Promise[] = [] const result = await runCode.run( deps, (level, data) => { if (LogLevels.indexOf(level) >= LogLevels.indexOf(setLogLevel)) { - logPromises.push(server.server.sendLoggingMessage({ level, data })); + logPromises.push(server.server.sendLoggingMessage({ level, data })) } }, { name: 'main.py', content: python_code }, global_variables, - returnMode !== 'xml' - ); - await Promise.all(logPromises); + returnMode !== 'xml', + ) + await Promise.all(logPromises) return { - content: [ - { - type: 'text', - text: returnMode === 'xml' ? asXml(result) : asJson(result), - }, - ], - }; - } - ); - return server; + content: [{ type: 'text', text: returnMode === 'xml' ? asXml(result) : asJson(result) }], + } + }, + ) + return server } /* * Define some QOL functions for both the Streamable HTTP server implementation */ function httpGetUrl(req: http.IncomingMessage): URL { - return new URL(req.url ?? '', `http://${req.headers.host ?? 'unknown'}`); + return new URL( + req.url ?? '', + `http://${req.headers.host ?? 'unknown'}`, + ) } function httpGetBody(req: http.IncomingMessage): Promise { // https://nodejs.org/en/learn/modules/anatomy-of-an-http-transaction#request-body - return new Promise(resolve => { - const bodyParts: any[] = []; - let body; - req - .on('data', chunk => { - bodyParts.push(chunk); - }) - .on('end', () => { - body = Buffer.concat(bodyParts).toString(); - resolve(JSON.parse(body)); - }); - }); + return new Promise((resolve) => { + const bodyParts: any[] = [] + let body + req.on('data', (chunk) => { + bodyParts.push(chunk) + }).on('end', () => { + body = Buffer.concat(bodyParts).toString() + resolve(JSON.parse(body)) + }) + }) } -function httpSetTextResponse( - res: http.ServerResponse, - status: number, - text: string -) { - res.setHeader('Content-Type', 'text/plain'); - res.statusCode = status; - res.end(`${text}\n`); +function httpSetTextResponse(res: http.ServerResponse, status: number, text: string) { + res.setHeader('Content-Type', 'text/plain') + res.statusCode = status + res.end(`${text}\n`) } -function httpSetJsonResponse( - res: http.ServerResponse, - status: number, - text: string, - code: number -) { - res.setHeader('Content-Type', 'application/json'); - res.statusCode = status; - res.write( - JSON.stringify({ - jsonrpc: '2.0', - error: { - code: code, - message: text, - }, - id: null, - }) - ); - res.end(); +function httpSetJsonResponse(res: http.ServerResponse, status: number, text: string, code: number) { + res.setHeader('Content-Type', 'application/json') + res.statusCode = status + res.write(JSON.stringify({ + jsonrpc: '2.0', + error: { + code: code, + message: text, + }, + id: null, + })) + res.end() } /* @@ -245,107 +217,103 @@ function createStatelessHttpServer(deps: string[], returnMode: string): http.Ser function createStatefulHttpServer(deps: string[], returnMode: string): http.Server { // Stateful mode with session management // https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#with-session-management - const mcpServer = createServer(deps, returnMode); - const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; + const mcpServer = createServer(deps, returnMode) + const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {} return http.createServer(async (req, res) => { - const url = httpGetUrl(req); - let pathMatch = false; + const url = httpGetUrl(req) + let pathMatch = false function match(method: string, path: string): boolean { if (url.pathname === path) { - pathMatch = true; - return req.method === method; + pathMatch = true + return req.method === method } - return false; + return false } // Reusable handler for GET and DELETE requests async function handleSessionRequest() { - const sessionId = req.headers['mcp-session-id'] as string | undefined; + const sessionId = req.headers['mcp-session-id'] as string | undefined if (!sessionId || !transports[sessionId]) { - httpSetTextResponse(res, 400, 'Invalid or missing session ID'); - return; + httpSetTextResponse(res, 400, 'Invalid or missing session ID') + return } - const transport = transports[sessionId]; - await transport.handleRequest(req, res); + const transport = transports[sessionId] + await transport.handleRequest(req, res) } // Handle different request methods and paths if (match('POST', '/mcp')) { // Check for existing session ID - const sessionId = req.headers['mcp-session-id'] as string | undefined; - let transport: StreamableHTTPServerTransport; + const sessionId = req.headers['mcp-session-id'] as string | undefined + let transport: StreamableHTTPServerTransport - const body = await httpGetBody(req); + const body = await httpGetBody(req) if (sessionId && transports[sessionId]) { // Reuse existing transport - transport = transports[sessionId]; + transport = transports[sessionId] } else if (!sessionId && isInitializeRequest(body)) { // New initialization request transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), - onsessioninitialized: sessionId => { + onsessioninitialized: (sessionId) => { // Store the transport by session ID - transports[sessionId] = transport; + transports[sessionId] = transport }, - }); + }) // Clean up transport when closed transport.onclose = () => { if (transport.sessionId) { - delete transports[transport.sessionId]; + delete transports[transport.sessionId] } - }; + } - await mcpServer.connect(transport); + await mcpServer.connect(transport) } else { - httpSetJsonResponse( - res, - 400, - 'Bad Request: No valid session ID provided', - -32000 - ); - return; + httpSetJsonResponse(res, 400, 'Bad Request: No valid session ID provided', -32000) + return } // Handle the request - await transport.handleRequest(req, res, body); + await transport.handleRequest(req, res, body) } else if (match('GET', '/mcp')) { // Handle server-to-client notifications - await handleSessionRequest(); + await handleSessionRequest() } else if (match('DELETE', '/mcp')) { // Handle requests for session termination - await handleSessionRequest(); + await handleSessionRequest() } else if (pathMatch) { - httpSetTextResponse(res, 405, 'Method not allowed'); + httpSetTextResponse(res, 405, 'Method not allowed') } else { - httpSetTextResponse(res, 404, 'Page not found'); + httpSetTextResponse(res, 404, 'Page not found') } - }); + }) } /* * Run the MCP server using the Stdio transport. */ async function runStdio(deps: string[], returnMode: string) { - const mcpServer = createServer(deps, returnMode); - const transport = new StdioServerTransport(); - await mcpServer.connect(transport); + const mcpServer = createServer(deps, returnMode) + const transport = new StdioServerTransport() + await mcpServer.connect(transport) } /* * Run pyodide to download and install dependencies. */ async function installDeps(deps: string[]) { - const runCode = new RunCode(); - const result = await runCode.run(deps, (level, data) => - console.error(`${level}|${data}`) - ); + const runCode = new RunCode() + const result = await runCode.run( + deps, + (level, data) => console.error(`${level}|${data}`), + ) if (result.status !== 'success') { - console.error('error|Failed to install dependencies'); - Deno.exit(1); + console.error('error|Failed to install dependencies') + Deno.exit(1) } } @@ -354,25 +322,25 @@ async function installDeps(deps: string[]) { */ async function example(deps: string[]) { console.error( - `Running example script for MCP Run Python version ${VERSION}...` - ); + `Running example script for MCP Run Python version ${VERSION}...`, + ) const code = ` import numpy a = numpy.array([1, 2, 3]) print('numpy array:', a) a -`; - const runCode = new RunCode(); +` + const runCode = new RunCode() const result = await runCode.run( deps, // use warn to avoid recursion since console.log is patched in runCode (level, data) => console.warn(`${level}: ${data}`), - { name: 'example.py', content: code } - ); - console.log('Tool return value:'); - console.log(asXml(result)); + { name: 'example.py', content: code }, + ) + console.log('Tool return value:') + console.log(asXml(result)) if (result.status !== 'success') { - Deno.exit(1); + Deno.exit(1) } } @@ -386,6 +354,6 @@ const LogLevels: LoggingLevel[] = [ 'critical', 'alert', 'emergency', -]; +] -await main() +await main() \ No newline at end of file From 6394f0db542ad4e5a622962500cbedbf50cf7b68 Mon Sep 17 00:00:00 2001 From: luoshasha Date: Wed, 24 Dec 2025 16:16:33 +0800 Subject: [PATCH 11/12] fix: fix lint end_of_file failed. --- mcp_run_python/deno/src/main.ts | 2 +- mcp_run_python/main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mcp_run_python/deno/src/main.ts b/mcp_run_python/deno/src/main.ts index 3f29c02..a58e59d 100644 --- a/mcp_run_python/deno/src/main.ts +++ b/mcp_run_python/deno/src/main.ts @@ -356,4 +356,4 @@ const LogLevels: LoggingLevel[] = [ 'emergency', ] -await main() \ No newline at end of file +await main() diff --git a/mcp_run_python/main.py b/mcp_run_python/main.py index 3e6f620..b795a09 100644 --- a/mcp_run_python/main.py +++ b/mcp_run_python/main.py @@ -60,7 +60,7 @@ def run_mcp_server( logger.info('Running mcp-run-python via %s on port %d...', mode, http_port) else: logger.info('Running mcp-run-python via %s...', mode) - + try: p = subprocess.run(('deno', *env.args), cwd=env.cwd, stdout=stdout, stderr=stderr) except KeyboardInterrupt: # pragma: no cover From ba8a7236ad1b244d5391ca57b1ebd74b0bc8b230 Mon Sep 17 00:00:00 2001 From: luoshasha Date: Wed, 24 Dec 2025 16:31:54 +0800 Subject: [PATCH 12/12] fix: fix format-ts error. --- mcp_run_python/deno/src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcp_run_python/deno/src/main.ts b/mcp_run_python/deno/src/main.ts index a58e59d..0c6cec0 100644 --- a/mcp_run_python/deno/src/main.ts +++ b/mcp_run_python/deno/src/main.ts @@ -174,7 +174,7 @@ function httpSetJsonResponse(res: http.ServerResponse, status: number, text: str /* * Run the MCP server using the Streamable HTTP transport */ -function runStreamableHttp(port: number, host:string, deps: string[], returnMode: string, stateless: boolean): void { +function runStreamableHttp(port: number, host: string, deps: string[], returnMode: string, stateless: boolean): void { const server = (stateless ? createStatelessHttpServer : createStatefulHttpServer)(deps, returnMode) server.listen(port, host, () => { console.log(`Listening on port ${port}`)