mcp-cmd-sandbox 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.3
2
+ Name: mcp-cmd-sandbox
3
+ Version: 0.1.0
4
+ Requires-Dist: fastmcp
5
+ Requires-Dist: python-on-whales
6
+ Requires-Python: >=3.10
@@ -0,0 +1,12 @@
1
+ [project]
2
+ name = "mcp-cmd-sandbox"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.10"
5
+ dependencies = ["fastmcp", "python-on-whales"]
6
+
7
+ [build-system]
8
+ requires = ["uv_build>=0.6.6"]
9
+ build-backend = "uv_build"
10
+
11
+ [project.scripts]
12
+ mcp-cmd-sandbox = "mcp_cmd_sandbox:main"
@@ -0,0 +1,5 @@
1
+ from mcp_cmd_sandbox.server import mcp
2
+
3
+
4
+ def main():
5
+ mcp.run()
@@ -0,0 +1,108 @@
1
+ """MCP server for isolated container execution via Docker/Podman."""
2
+
3
+ import argparse
4
+ import uuid
5
+ from pathlib import Path
6
+
7
+ from fastmcp import Context, FastMCP
8
+ from fastmcp.dependencies import CurrentContext
9
+ from fastmcp.server.lifespan import Lifespan
10
+ from python_on_whales import DockerClient
11
+
12
+ _parser = argparse.ArgumentParser(prog="mcp-cmd-sandbox")
13
+ _ = _parser.add_argument(
14
+ "container_binary",
15
+ nargs="*",
16
+ default=["podman"],
17
+ help="e.g. 'podman' or '-- docker --remote'",
18
+ )
19
+ _args = _parser.parse_args()
20
+
21
+ _SERVER_ID = uuid.uuid4()
22
+ _IS_PODMAN = "podman" in _args.container_binary[0]
23
+
24
+ volumes: dict[uuid.UUID, str] = {}
25
+ client = DockerClient(client_call=_args.container_binary)
26
+
27
+
28
+ # This cleans up the anonymous volumes created during the runtime of the mcp server
29
+ @Lifespan
30
+ async def remove_sessions(_):
31
+ # startup
32
+ yield
33
+
34
+ # shutdown
35
+ for vol in volumes.values():
36
+ try:
37
+ client.volume.remove(vol)
38
+ except Exception:
39
+ pass
40
+
41
+
42
+ def get_session_volume(ctx: Context):
43
+ id = uuid.uuid5(_SERVER_ID, ctx.session_id)
44
+ volume_name = f"mcp-cmd-sandbox-persistant-{id}"
45
+
46
+ if id not in volumes.keys():
47
+ _ = client.volume.create(volume_name)
48
+ volumes[id] = volume_name
49
+
50
+ return volume_name
51
+
52
+
53
+ mcp = FastMCP("mcp-cmd-sandbox", lifespan=remove_sessions)
54
+
55
+
56
+ def _run_cmd(command: str, image: str, writable: bool, ctx: Context) -> str:
57
+ volume = get_session_volume(ctx)
58
+ # TODO: this is sketchy af.
59
+ # this should be the pwd of the agent tool, not the mcp server
60
+ cwd = str(Path.cwd())
61
+
62
+ with client.run(
63
+ image,
64
+ ["sh", "-c", command],
65
+ volumes=[
66
+ (cwd, "/workspace", "rw" if writable else ("O" if _IS_PODMAN else "ro")),
67
+ (volume, "/persistent", "rw"),
68
+ ],
69
+ workdir="/workspace",
70
+ detach=True,
71
+ ) as container:
72
+ _ = client.wait(container)
73
+ return container.logs()
74
+
75
+
76
+ @mcp.tool(
77
+ description=f"""
78
+ Run a command in an isolated container.
79
+
80
+ - /workspace: your project directory ({"overlay mount, writes discarded on exit" if _IS_PODMAN else "read-only"})
81
+ - /persistent: writable volume that survives across calls within the same session_id
82
+
83
+ Pick an image appropriate for the task:
84
+ debian:latest (default), rust:latest, python:3.12-slim,
85
+ gcc:latest, node:22-alpine, golang:latest, maven:latest
86
+
87
+ Use /persistent for build caches or artifacts across multiple calls.
88
+ See the execute_writable call for a writable workspace.
89
+ """
90
+ )
91
+ def execute(
92
+ command: str, image: str = "debian:latest", ctx: Context = CurrentContext()
93
+ ) -> str:
94
+ return _run_cmd(command, image, writable=False, ctx=ctx)
95
+
96
+
97
+ @mcp.tool()
98
+ def execute_writable(
99
+ command: str, image: str = "debian:latest", ctx: Context = CurrentContext()
100
+ ) -> str:
101
+ """Run a command with write access to /workspace in an isolated container.
102
+
103
+ Only use this when you need to modify files on the host (sed -i, patch, writing build output, etc.).
104
+ Prefer the read-only 'execute' tool unless modification is required.
105
+
106
+ Same options as the execute mcp call.
107
+ """
108
+ return _run_cmd(command, image, writable=True, ctx=ctx)