Skip to content

Commit b2b0cb0

Browse files
author
luoshasha
committed
feat: add [--host] parameter for streamable-http mode.
1 parent ff3ebcf commit b2b0cb0

File tree

3 files changed

+59
-41
lines changed

3 files changed

+59
-41
lines changed

mcp_run_python/_cli.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ def cli_logic(args_list: Sequence[str] | None = None) -> int:
2020
description=f'mcp-run-python CLI v{__version__}\n\nMCP server for running untrusted Python code.\n',
2121
formatter_class=argparse.RawTextHelpFormatter,
2222
)
23-
23+
parser.add_argument('--host', type=str, default='127.0.0.1',
24+
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.')
2425
parser.add_argument('--port', type=int, help='Port to run the server on, default 3001.')
2526
parser.add_argument('--deps', '--dependencies', help='Comma separated list of dependencies to install')
2627
parser.add_argument(
@@ -51,6 +52,7 @@ def cli_logic(args_list: Sequence[str] | None = None) -> int:
5152
args.mode.replace('-', '_'),
5253
allow_networking=not args.disable_networking,
5354
http_port=args.port,
55+
http_host=args.host,
5456
dependencies=deps,
5557
deps_log_handler=deps_log_handler,
5658
verbose=bool(args.verbose),

mcp_run_python/deno/src/main.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ export async function main() {
3030
return
3131
} else if (args[0] === 'streamable_http') {
3232
const port = parseInt(flags.port)
33-
runStreamableHttp(port, deps, flags['return-mode'], false)
33+
const host = flags.host
34+
runStreamableHttp(port, host, deps, flags['return-mode'], false)
3435
return
3536
} else if (args[0] === 'streamable_http_stateless') {
3637
const port = parseInt(flags.port)
37-
runStreamableHttp(port, deps, flags['return-mode'], true)
38+
const host = flags.host
39+
runStreamableHttp(port, host, deps, flags['return-mode'], true)
3840
return
3941
} else if (args[0] === 'example') {
4042
await example(deps)
@@ -52,6 +54,7 @@ Usage: deno ... deno/main.ts [stdio|streamable_http|streamable_http_stateless|in
5254
5355
options:
5456
--port <port> Port to run the HTTP server on (default: 3001)
57+
--host <host> Host to run the HTTP server on (default: 127.0.0.1)
5558
--deps <deps> Comma separated list of dependencies to install
5659
--return-mode <xml/json> Return mode for output data (default: xml)`,
5760
)
@@ -171,9 +174,9 @@ function httpSetJsonResponse(res: http.ServerResponse, status: number, text: str
171174
/*
172175
* Run the MCP server using the Streamable HTTP transport
173176
*/
174-
function runStreamableHttp(port: number, deps: string[], returnMode: string, stateless: boolean): void {
177+
function runStreamableHttp(port: number, host:string, deps: string[], returnMode: string, stateless: boolean): void {
175178
const server = (stateless ? createStatelessHttpServer : createStatefulHttpServer)(deps, returnMode)
176-
server.listen(port, () => {
179+
server.listen(port, host, () => {
177180
console.log(`Listening on port ${port}`)
178181
})
179182
}
@@ -353,4 +356,4 @@ const LogLevels: LoggingLevel[] = [
353356
'emergency',
354357
]
355358

356-
await main()
359+
await main()

mcp_run_python/main.py

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from pathlib import Path
1212
from typing import Literal, ParamSpec, TypeVar, cast
1313

14-
__all__ = 'run_mcp_server', 'DenoEnv', 'prepare_deno_env', 'async_prepare_deno_env'
14+
__all__ = "run_mcp_server", "DenoEnv", "prepare_deno_env", "async_prepare_deno_env"
1515

1616
logger = logging.getLogger(__name__)
1717
LoggingLevel = Literal['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency']
@@ -23,8 +23,9 @@ def run_mcp_server(
2323
mode: Mode,
2424
*,
2525
http_port: int | None = None,
26+
http_host: str | None = None,
2627
dependencies: list[str] | None = None,
27-
return_mode: Literal['json', 'xml'] = 'xml',
28+
return_mode: Literal["json", "xml"] = "xml",
2829
deps_log_handler: LogHandler | None = None,
2930
allow_networking: bool = True,
3031
verbose: bool = False,
@@ -34,6 +35,7 @@ def run_mcp_server(
3435
Args:
3536
mode: The mode to run the server in.
3637
http_port: The port to run the server on if mode is `streamable_http`.
38+
http_host: The host to run the server on if mode is `streamable_http`.
3739
dependencies: The dependencies to install.
3840
return_mode: The mode to return tool results in.
3941
deps_log_handler: Optional function to receive logs emitted while installing dependencies.
@@ -49,19 +51,22 @@ def run_mcp_server(
4951
mode,
5052
dependencies=dependencies,
5153
http_port=http_port,
54+
http_host=http_host,
5255
return_mode=return_mode,
5356
deps_log_handler=deps_log_handler,
5457
allow_networking=allow_networking,
5558
) as env:
5659
if mode in ('streamable_http', 'streamable_http_stateless'):
5760
logger.info('Running mcp-run-python via %s on port %d...', mode, http_port)
5861
else:
59-
logger.info('Running mcp-run-python via %s...', mode)
62+
logger.info("Running mcp-run-python via %s...", mode)
6063

6164
try:
62-
p = subprocess.run(('deno', *env.args), cwd=env.cwd, stdout=stdout, stderr=stderr)
65+
p = subprocess.run(
66+
("deno", *env.args), cwd=env.cwd, stdout=stdout, stderr=stderr
67+
)
6368
except KeyboardInterrupt: # pragma: no cover
64-
logger.warning('Server stopped.')
69+
logger.warning("Server stopped.")
6570
return 0
6671
else:
6772
return p.returncode
@@ -78,8 +83,9 @@ def prepare_deno_env(
7883
mode: Mode,
7984
*,
8085
http_port: int | None = None,
86+
http_host: str | None = None,
8187
dependencies: list[str] | None = None,
82-
return_mode: Literal['json', 'xml'] = 'xml',
88+
return_mode: Literal["json", "xml"] = "xml",
8389
deps_log_handler: LogHandler | None = None,
8490
allow_networking: bool = True,
8591
) -> Iterator[DenoEnv]:
@@ -92,6 +98,7 @@ def prepare_deno_env(
9298
Args:
9399
mode: The mode to run the server in.
94100
http_port: The port to run the server on if mode is `streamable_http`.
101+
http_host: The host to run the server on if mode is `streamable_http`.
95102
dependencies: The dependencies to install.
96103
return_mode: The mode to return tool results in.
97104
deps_log_handler: Optional function to receive logs emitted while installing dependencies.
@@ -101,22 +108,22 @@ def prepare_deno_env(
101108
Returns:
102109
Yields the deno environment details.
103110
"""
104-
cwd = Path(tempfile.mkdtemp()) / 'mcp-run-python'
111+
cwd = Path(tempfile.mkdtemp()) / "mcp-run-python"
105112
try:
106-
src = Path(__file__).parent / 'deno'
107-
logger.debug('Copying from %s to %s...', src, cwd)
113+
src = Path(__file__).parent / "deno"
114+
logger.debug("Copying from %s to %s...", src, cwd)
108115
shutil.copytree(src, cwd)
109-
logger.info('Installing dependencies %s...', dependencies)
116+
logger.info("Installing dependencies %s...", dependencies)
110117

111-
args = 'deno', *_deno_install_args(dependencies)
118+
args = "deno", *_deno_install_args(dependencies)
112119
p = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
113120
stdout: list[str] = []
114121
if p.stdout is not None:
115122
for line in p.stdout:
116123
line = line.strip()
117124
if deps_log_handler:
118-
parts = line.split('|', 1)
119-
level, msg = parts if len(parts) == 2 else ('info', line)
125+
parts = line.split("|", 1)
126+
level, msg = parts if len(parts) == 2 else ("info", line)
120127
deps_log_handler(cast(LoggingLevel, level), msg)
121128
stdout.append(line)
122129
p.wait()
@@ -126,6 +133,7 @@ def prepare_deno_env(
126133
args = _deno_run_args(
127134
mode,
128135
http_port=http_port,
136+
http_host=http_host,
129137
dependencies=dependencies,
130138
return_mode=return_mode,
131139
allow_networking=allow_networking,
@@ -141,8 +149,9 @@ async def async_prepare_deno_env(
141149
mode: Mode,
142150
*,
143151
http_port: int | None = None,
152+
http_host: str | None = None,
144153
dependencies: list[str] | None = None,
145-
return_mode: Literal['json', 'xml'] = 'xml',
154+
return_mode: Literal["json", "xml"] = "xml",
146155
deps_log_handler: LogHandler | None = None,
147156
allow_networking: bool = True,
148157
) -> AsyncIterator[DenoEnv]:
@@ -151,6 +160,7 @@ async def async_prepare_deno_env(
151160
prepare_deno_env,
152161
mode,
153162
http_port=http_port,
163+
http_host=http_host,
154164
dependencies=dependencies,
155165
return_mode=return_mode,
156166
deps_log_handler=deps_log_handler,
@@ -164,13 +174,13 @@ async def async_prepare_deno_env(
164174

165175
def _deno_install_args(dependencies: list[str] | None = None) -> list[str]:
166176
args = [
167-
'run',
168-
'--allow-net',
169-
'--allow-read=./node_modules',
170-
'--allow-write=./node_modules',
171-
'--node-modules-dir=auto',
172-
'src/main.ts',
173-
'noop',
177+
"run",
178+
"--allow-net",
179+
"--allow-read=./node_modules",
180+
"--allow-write=./node_modules",
181+
"--node-modules-dir=auto",
182+
"src/main.ts",
183+
"noop",
174184
]
175185
if dependencies is not None:
176186
args.append(f'--deps={",".join(dependencies)}')
@@ -181,32 +191,35 @@ def _deno_run_args(
181191
mode: Mode,
182192
*,
183193
http_port: int | None = None,
194+
http_host: str | None = None,
184195
dependencies: list[str] | None = None,
185-
return_mode: Literal['json', 'xml'] = 'xml',
196+
return_mode: Literal["json", "xml"] = "xml",
186197
allow_networking: bool = True,
187198
) -> list[str]:
188-
args = ['run']
199+
args = ["run"]
189200
if allow_networking:
190-
args += ['--allow-net']
201+
args += ["--allow-net"]
191202
args += [
192-
'--allow-read=./node_modules',
193-
'--node-modules-dir=auto',
194-
'src/main.ts',
203+
"--allow-read=./node_modules",
204+
"--node-modules-dir=auto",
205+
"src/main.ts",
195206
mode,
196-
f'--return-mode={return_mode}',
207+
f"--return-mode={return_mode}",
197208
]
198209
if dependencies is not None:
199210
args.append(f'--deps={",".join(dependencies)}')
200-
if http_port is not None:
201-
if mode in ('streamable_http', 'streamable_http_stateless'):
202-
args.append(f'--port={http_port}')
203-
else:
204-
raise ValueError('Port is only supported for `streamable_http` modes')
211+
if mode in ('streamable_http', 'streamable_http_stateless'):
212+
if http_port is not None:
213+
args.append(f"--port={http_port}")
214+
if http_host is not None:
215+
args.append(f"--host={http_host}")
216+
elif http_port is not None or http_host is not None:
217+
raise ValueError("Port and host are only supported for `streamable_http` mode")
205218
return args
206219

207220

208-
P = ParamSpec('P')
209-
T = TypeVar('T')
221+
P = ParamSpec("P")
222+
T = TypeVar("T")
210223

211224

212225
async def _asyncify(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:

0 commit comments

Comments
 (0)