Skip to content

Commit 9bfe865

Browse files
authored
Add ability to disable networking (#5)
1 parent 4edbbb9 commit 9bfe865

File tree

4 files changed

+51
-6
lines changed

4 files changed

+51
-6
lines changed

mcp_run_python/_cli.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ def cli_logic(args_list: Sequence[str] | None = None) -> int:
2323

2424
parser.add_argument('--port', type=int, help='Port to run the server on, default 3001.')
2525
parser.add_argument('--deps', '--dependencies', help='Comma separated list of dependencies to install')
26+
parser.add_argument(
27+
'--disable-networking', action='store_true', help='Disable networking during execution of python code'
28+
)
2629
parser.add_argument('--verbose', action='store_true', help='Enable verbose logging')
2730
parser.add_argument('--version', action='store_true', help='Show version and exit')
2831
parser.add_argument(
@@ -46,6 +49,7 @@ def cli_logic(args_list: Sequence[str] | None = None) -> int:
4649
deps: list[str] = args.deps.split(',') if args.deps else []
4750
return_code = run_mcp_server(
4851
args.mode.replace('-', '_'),
52+
allow_networking=not args.disable_networking,
4953
http_port=args.port,
5054
dependencies=deps,
5155
deps_log_handler=deps_log_handler,

mcp_run_python/code_sandbox.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ async def code_sandbox(
4343
dependencies: list[str] | None = None,
4444
log_handler: LogHandler | None = None,
4545
logging_level: mcp_types.LoggingLevel | None = None,
46+
allow_networking: bool = True,
4647
) -> AsyncIterator['CodeSandbox']:
4748
"""Run code in a secure sandbox.
4849
@@ -51,9 +52,14 @@ async def code_sandbox(
5152
log_handler: A callback function to handle print statements when code is running.
5253
logging_level: The logging level to use for the print handler, defaults to `info` if `log_handler` is provided.
5354
deps_log_handler: A callback function to run on log statements during initial install of dependencies.
55+
allow_networking: Whether to allow networking or not while executing python code.
5456
"""
5557
async with async_prepare_deno_env(
56-
'stdio', dependencies=dependencies, deps_log_handler=log_handler, return_mode='json'
58+
'stdio',
59+
dependencies=dependencies,
60+
deps_log_handler=log_handler,
61+
return_mode='json',
62+
allow_networking=allow_networking,
5763
) as deno_env:
5864
server_params = StdioServerParameters(command='deno', args=deno_env.args, cwd=deno_env.cwd)
5965

mcp_run_python/main.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def run_mcp_server(
2525
dependencies: list[str] | None = None,
2626
return_mode: Literal['json', 'xml'] = 'xml',
2727
deps_log_handler: LogHandler | None = None,
28+
allow_networking: bool = True,
2829
) -> int:
2930
"""Install dependencies then run the mcp-run-python server.
3031
@@ -34,9 +35,15 @@ def run_mcp_server(
3435
dependencies: The dependencies to install.
3536
return_mode: The mode to return tool results in.
3637
deps_log_handler: Optional function to receive logs emitted while installing dependencies.
38+
allow_networking: Whether to allow networking when running provided python code.
3739
"""
3840
with prepare_deno_env(
39-
mode, dependencies=dependencies, http_port=http_port, return_mode=return_mode, deps_log_handler=deps_log_handler
41+
mode,
42+
dependencies=dependencies,
43+
http_port=http_port,
44+
return_mode=return_mode,
45+
deps_log_handler=deps_log_handler,
46+
allow_networking=allow_networking,
4047
) as env:
4148
if mode == 'streamable_http':
4249
logger.info('Running mcp-run-python via %s on port %d...', mode, http_port)
@@ -66,6 +73,7 @@ def prepare_deno_env(
6673
dependencies: list[str] | None = None,
6774
return_mode: Literal['json', 'xml'] = 'xml',
6875
deps_log_handler: LogHandler | None = None,
76+
allow_networking: bool = True,
6977
) -> Iterator[DenoEnv]:
7078
"""Prepare the deno environment for running the mcp-run-python server with Deno.
7179
@@ -79,6 +87,8 @@ def prepare_deno_env(
7987
dependencies: The dependencies to install.
8088
return_mode: The mode to return tool results in.
8189
deps_log_handler: Optional function to receive logs emitted while installing dependencies.
90+
allow_networking: Whether the prepared DenoEnv should allow networking when running code.
91+
Note that we always allow networking during environment initialization to install dependencies.
8292
8393
Returns:
8494
Yields the deno environment details.
@@ -105,7 +115,13 @@ def prepare_deno_env(
105115
if p.returncode != 0:
106116
raise RuntimeError(f'`deno run ...` returned a non-zero exit code {p.returncode}: {"".join(stdout)}')
107117

108-
args = _deno_run_args(mode, http_port=http_port, dependencies=dependencies, return_mode=return_mode)
118+
args = _deno_run_args(
119+
mode,
120+
http_port=http_port,
121+
dependencies=dependencies,
122+
return_mode=return_mode,
123+
allow_networking=allow_networking,
124+
)
109125
yield DenoEnv(cwd, args)
110126

111127
finally:
@@ -120,6 +136,7 @@ async def async_prepare_deno_env(
120136
dependencies: list[str] | None = None,
121137
return_mode: Literal['json', 'xml'] = 'xml',
122138
deps_log_handler: LogHandler | None = None,
139+
allow_networking: bool = True,
123140
) -> AsyncIterator[DenoEnv]:
124141
"""Async variant of `prepare_deno_env`."""
125142
ct = await _asyncify(
@@ -129,6 +146,7 @@ async def async_prepare_deno_env(
129146
dependencies=dependencies,
130147
return_mode=return_mode,
131148
deps_log_handler=deps_log_handler,
149+
allow_networking=allow_networking,
132150
)
133151
try:
134152
yield await _asyncify(ct.__enter__)
@@ -157,10 +175,12 @@ def _deno_run_args(
157175
http_port: int | None = None,
158176
dependencies: list[str] | None = None,
159177
return_mode: Literal['json', 'xml'] = 'xml',
178+
allow_networking: bool = True,
160179
) -> list[str]:
161-
args = [
162-
'run',
163-
'--allow-net',
180+
args = ['run']
181+
if allow_networking:
182+
args += ['--allow-net']
183+
args += [
164184
'--allow-read=./node_modules',
165185
'--node-modules-dir=auto',
166186
'src/main.ts',

tests/test_sandbox.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,18 @@ def log_handler(level: str, message: str):
109109
),
110110
)
111111
assert [(level, msg) for level, msg in logs if level == 'info'][-1] == snapshot(('info', 'hello 123'))
112+
113+
114+
async def test_disallow_networking():
115+
code = """
116+
import httpx
117+
async with httpx.AsyncClient() as client:
118+
await client.get('http://localhost')
119+
"""
120+
async with code_sandbox(dependencies=['httpx'], allow_networking=False) as sandbox:
121+
result = await sandbox.eval(code)
122+
123+
assert 'error' in result
124+
assert result['error'].strip().splitlines()[-1] == snapshot(
125+
'httpx.ConnectError: Requires net access to "localhost:80", run again with the --allow-net flag'
126+
)

0 commit comments

Comments
 (0)