Skip to content

Commit 7339b46

Browse files
fix: pass MCP config directly via --mcp-config flag
The Modal sandbox was writing MCP config to a file that Claude Code wasn't picking up. Now passes config directly via --mcp-config flag, matching the local streaming implementation.
1 parent 535dd5c commit 7339b46

1 file changed

Lines changed: 24 additions & 30 deletions

File tree

src/policyengine_api/agent_sandbox.py

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
to the PolicyEngine API via MCP. Outputs are streamed back in real-time.
55
"""
66

7+
import json
8+
79
import modal
810

911
# Sandbox image with Bun and Claude Code CLI (v2 - with ToS pre-accept)
@@ -57,14 +59,10 @@ def run_claude_code_in_sandbox(
5759
)
5860

5961
# MCP config for Claude Code (type: sse for HTTP SSE transport)
60-
mcp_config = f"""{{
61-
"mcpServers": {{
62-
"policyengine": {{
63-
"type": "sse",
64-
"url": "{api_base_url}/mcp"
65-
}}
66-
}}
67-
}}"""
62+
mcp_config = {
63+
"mcpServers": {"policyengine": {"type": "sse", "url": f"{api_base_url}/mcp"}}
64+
}
65+
mcp_config_json = json.dumps(mcp_config)
6866

6967
# Get reference to deployed app (required when calling from outside Modal)
7068
print("[SANDBOX] looking up Modal app", flush=True)
@@ -85,29 +83,23 @@ def run_claude_code_in_sandbox(
8583
print("[SANDBOX] sandbox created", flush=True)
8684
logfire.info("run_claude_code_in_sandbox: sandbox created")
8785

88-
# Write MCP config
89-
logfire.info("run_claude_code_in_sandbox: writing MCP config")
90-
sb.exec("mkdir", "-p", "/root/.claude")
91-
config_process = sb.exec(
92-
"sh", "-c", f"cat > /root/.claude/mcp_servers.json << 'EOF'\n{mcp_config}\nEOF"
93-
)
94-
config_process.wait()
95-
logfire.info(
96-
"run_claude_code_in_sandbox: MCP config written",
97-
returncode=config_process.returncode,
98-
)
99-
10086
# Run Claude Code with the question
10187
# Note: Can't use --dangerously-skip-permissions as root (Modal runs as root)
10288
# Use shell wrapper with </dev/null to properly close stdin (prevents hanging)
10389
# --max-turns: limit execution to prevent runaway
90+
# Use --mcp-config to pass MCP config directly (more reliable than config file)
10491
print("[SANDBOX] Starting claude CLI with question", flush=True)
105-
logfire.info("run_claude_code_in_sandbox: starting claude CLI")
92+
logfire.info(
93+
"run_claude_code_in_sandbox: starting claude CLI",
94+
mcp_url=f"{api_base_url}/mcp",
95+
)
10696

107-
# Escape the question for shell
97+
# Escape the question and config for shell
10898
escaped_question = question.replace("'", "'\"'\"'")
99+
escaped_mcp_config = mcp_config_json.replace("'", "'\"'\"'")
109100
cmd = (
110101
f"claude -p '{escaped_question}' "
102+
f"--mcp-config '{escaped_mcp_config}' "
111103
"--output-format stream-json --verbose --max-turns 10 "
112104
"--allowedTools 'mcp__policyengine__*,Bash,Read,Grep,Glob,Write,Edit' "
113105
"< /dev/null 2>&1"
@@ -129,8 +121,6 @@ def run_policy_analysis(
129121
130122
This is the non-streaming version that returns the full result.
131123
"""
132-
import json
133-
import os
134124
import subprocess
135125

136126
import logfire
@@ -140,24 +130,28 @@ def run_policy_analysis(
140130
with logfire.span(
141131
"run_policy_analysis", question=question[:100], api_base_url=api_base_url
142132
):
143-
# Write MCP config
144-
os.makedirs("/root/.claude", exist_ok=True)
133+
# MCP config for Claude Code (type: sse for HTTP SSE transport)
145134
mcp_config = {
146135
"mcpServers": {
147136
"policyengine": {"type": "sse", "url": f"{api_base_url}/mcp"}
148137
}
149138
}
150-
with open("/root/.claude/mcp_servers.json", "w") as f:
151-
json.dump(mcp_config, f)
139+
mcp_config_json = json.dumps(mcp_config)
152140

153-
logfire.info("Starting Claude Code", question=question[:100])
141+
logfire.info(
142+
"Starting Claude Code",
143+
question=question[:100],
144+
mcp_url=f"{api_base_url}/mcp",
145+
)
154146

155-
# Run Claude Code (no --dangerously-skip-permissions since we run as root)
147+
# Run Claude Code with --mcp-config (no --dangerously-skip-permissions as root)
156148
result = subprocess.run(
157149
[
158150
"claude",
159151
"-p",
160152
question,
153+
"--mcp-config",
154+
mcp_config_json,
161155
"--max-turns",
162156
"10",
163157
"--allowedTools",

0 commit comments

Comments
 (0)