feat(mcp): add run_python tool - #440
Merged
Merged
Conversation
This was referenced Aug 27, 2026
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
jahvon
force-pushed
the
feat/mcp-run-python
branch
from
August 27, 2026 04:28
c77faef to
312d54b
Compare
jahvon
force-pushed
the
feat/mcp-run-python
branch
from
August 27, 2026 05:48
312d54b to
d1225f7
Compare
jahvon
force-pushed
the
feat/mcp-run-python
branch
from
August 27, 2026 06:24
d1225f7 to
4ba4089
Compare
jahvon
force-pushed
the
feat/mcp-run-python
branch
from
August 27, 2026 06:44
4ba4089 to
80034f3
Compare
Exposes Python execution to agents as a first-class MCP tool alongside run_command, plus the `--interpreter` flag on `flow exec` that backs it. run_python is its own tool rather than a parameter on run_command because agents select tools by name, and because run_command's multi-command form builds serial/parallel steps, which carry no interpreter of their own. The CLI rejects `--interpreter` with multiple `--cmd` values for the same reason; a later change can relax that once step configs gain the field. Also allowlists the tool in this repo's own .claude config and adds it to the flow-context skill, so the agents working in this repo reach for it instead of shelling out to `python -c`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R328pa3FUUfga4gYah1iQi
jahvon
force-pushed
the
feat/mcp-run-python
branch
from
August 27, 2026 06:49
80034f3 to
14151e6
Compare
jahvon
added a commit
that referenced
this pull request
Aug 27, 2026
**Part 3/5.** Stacked on #440. # Summary Combines `interpreter: python` with the existing `container:` block, so a pinned Python toolchain needs no local install. ```yaml exec: interpreter: python cmd: | import sys print(sys.version) container: image: python:3.13-alpine ``` **Notable Changes** - **Inline code is bind-mounted read-only as a script and passed via `spec.Script`, never `spec.Cmd`.** `buildRunArgs` maps `Cmd` to `<entrypoint> -c <code>`, which would put user code in the process table and cost traceback line numbers — the same trade-off Part 1 avoided on the host. The existing "script lives outside every mount" branch already did exactly this for files, so the generated temp script reuses it and `internal/services/run/container.go` needed no change at all. - **The default entrypoint follows the interpreter** — `sh` for a shell command, `python3` for a Python one. Resolved in the runner rather than on `ExecContainer`, keeping the types package unaware of how flow launches containers. An explicit `container.entrypoint` still wins, including the empty form (which, with Python, only works if the image's own `ENTRYPOINT` is an interpreter — documented). - **Host interpreter discovery stops at the container boundary.** `VIRTUAL_ENV`, `PYTHONPATH`, `PYTHONHOME`, and `FLOW_PYTHON_BIN` are dropped from the container environment: those host paths either do not exist inside it or, worse, resolve to an unrelated mounted directory. `PYTHONUNBUFFERED` is set there too. - The temp script cleanup is registered as a context callback next to `ForceRemoveContainer`, so a run abandoned by the runner's timeout goroutine does not leak it. # Testing - Unit tests assert the built spec: `Cmd` empty, `Script` set to a `.py`, the script mount present and read-only, the `python3` entrypoint default, explicit-entrypoint override, empty-entrypoint passthrough, that a shell command still gets `sh`, and that host python env vars are dropped while unrelated ones survive. - An e2e case runs Python in a `python:3.13-alpine` image, behind the suite's existing runtime guard. - `flow validate` passes; `generate` produces no diff. **Verification note:** I have no container runtime on this machine, so the live container run was *not* executed locally — it is covered by the e2e case, which only executes where a runtime is available (CI). The spec and argv construction are verified by the unit tests above. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01R328pa3FUUfga4gYah1iQi Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
jahvon
added a commit
that referenced
this pull request
Aug 27, 2026
**Part 4/5.** Stacked on #441. # Summary Inline `cmd` steps in `serial`/`parallel` executables now take their own `interpreter`, so one workflow can mix shell and Python without splitting into separate executables. ```yaml serial: execs: - cmd: ./fetch-data.sh - cmd: | import json print(json.load(open("data.json"))["total"]) interpreter: python ``` **Notable Changes** - Both step configs `$ref` the same `ExecInterpreter` definition the `exec` type uses, so there is one enum rather than three parallel ones. (Part 1 hoisted it to a top-level definition for exactly this.) - A step that omits `interpreter` runs under the shell as before; a `ref` step ignores the field — the referenced executable brings its own. - **This lifts the interim restriction from #440**: `--interpreter` now applies to every `--cmd` in an invocation, in serial and parallel mode alike. - `ExecutableForCmd` takes the interpreter as a new parameter (5 call sites). Its unused `int` parameter is left alone to keep the diff to the one concern. # Testing - E2E: a serial executable mixing a shell step and a Python step, each running under its own interpreter; a step without an interpreter staying on the shell (asserted via `echo`, a shell builtin, so it only passes if the step really stayed there); and `--interpreter` applied across a multi-command batch. - The e2e case that previously asserted the multi-command rejection is replaced with one asserting it now works. - `flow validate` passes; `generate` produces no diff. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01R328pa3FUUfga4gYah1iQi Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 2/5. Stacked on #439 — review that first; this PR's diff is only its own change.
Summary
Exposes Python execution to agents as a first-class MCP tool, alongside
run_command. This is the point of the stack: flow already acts as an assistant's shell, and now it acts as their Python runtime too — same workspace env and secrets, same captured logs, same attributable history entry.Also adds the
--interpreterflag onflow execthat backs it.Notable Changes
run_pythonis its own tool rather than a parameter onrun_command. Agents select tools by name, so a tool called "run_command" is not what gets reached for when the task is Python. It also sidesteps a real constraint:run_command's multi-command form builds serial/parallel steps, which carry no interpreter of their own.--interpreterwith multiple--cmdvalues, with a usage error. Part 4 of this stack lifts that restriction once step configs gain the field — it is a deliberate interim guard, not an oversight.runTransientTool,ExecutionOutput, and the existing provenance/progress plumbing.docs/guides/ai-tools.mdso the tool ladder names it.mcp__flow__run_pythonin this repo's own.claude/settings.jsonand adds it to theflow-contextskill, so the agents working in this repo reach for it instead of shelling out topython -c.Testing
codeerror paths.run_pythonadded to the registered-tool and output-schema assertions.flow validatepasses;generateproduces no diff. (Completion scripts are unaffected — cobra generates them to query the binary at runtime rather than embedding flag lists.)🤖 Generated with Claude Code
https://claude.ai/code/session_01R328pa3FUUfga4gYah1iQi