Skip to content

fix(apps): resolve bare Python MCP commands through app venvs - #1856

Closed
leonlaiyc wants to merge 1 commit into
kirodotdev:mainfrom
leonlaiyc:fix/app-python-interpreter-resolution
Closed

fix(apps): resolve bare Python MCP commands through app venvs#1856
leonlaiyc wants to merge 1 commit into
kirodotdev:mainfrom
leonlaiyc:fix/app-python-interpreter-resolution

Conversation

@leonlaiyc

Copy link
Copy Markdown
Contributor

Refs #1807 — the interpreter half. The working-directory half is deliberately not
addressed; see the end.

Problem

An app's stdio MCP server declaring command: "python3" is written into the
agent config verbatim, so kiro-cli resolves it through PATH at spawn time. That
need not name an interpreter at all, and where it does it need not name one new
enough for the dependencies installed into the app's own venv. Either way the
server never starts, and a stdio MCP server that never starts is
indistinguishable from an app that provides no tools — nothing on this path logs
the difference.

The same app's backend already refuses a bare name for exactly these reasons. It
just refuses it in a way that only works on POSIX: backend.py hardcodes
.venv/bin/python3 in both Python branches, so on Windows — where a venv exposes
Scripts/python.exe — the venv is never found and the launch falls through to the
gateway interpreter. The same silent substitution, one layer up.

Overlap with #1013

#1013 already fixes this defect, in the same function and the same branch:
resolve_stdio_command() maps a bare launcher to sys.executable. Two things
from it are adopted here and are better than what I had — py belongs in the
bare set, and the match should be case- and whitespace-insensitive.

The difference is the resolution target:

#1013 this
resolves to sys.executable app .venv first, else sys.executable
guarantees the server can import kiro_crew the server gets the app's own dependencies
backend unchanged both branches share the policy
Windows venv layout Scripts/python.exe
scope part of a 122-file feature PR this

The two agree whenever the app has no venv. They differ for a third-party app
that has one, which is the version-skew half of the report and the policy #1807
proposes. If #1013 lands first this should rebase onto it and replace that hunk
rather than leave two resolvers in one branch; I am happy to close this instead if
you would rather the venv-first policy were folded into #1013.

Change

apps/python_runtime resolves the app venv's interpreter for the running
platform, falling back to sys.executable. Both backend.py Python branches and
the stdio registration use it — a net reduction in backend.py. Its own module
because apps.backend and apps.bridges already defer-import each other to break
a cycle, so a helper owned by either would have to be imported lazily by the other.

The match is literal, like the host-CLI pin beside it: an absolute path, a
versioned name (python3.13) and any non-Python binary are the author's explicit
choice and are written as declared. HTTP entries are untouched.

Only the value of command changes; no key is added. kiro-cli parses this
file, and the documented local-server properties are command, args, env,
disabled, autoApprove, disabledTools
(docs/reference/kiro-cli/mcp/configuration.md). A test pins the key set.

Not touched: venv creation, python3 -m venv, bin/pip, requirements
installation, manifest validation.

Tests

test_app_python_runtime.py (new) — POSIX and Windows venv layouts; the other
platform's layout explicitly NOT accepted; no venv falls back; a directory at the
interpreter path is not a venv; the resolved value is never a bare name; every
bare launcher recognised; case/whitespace insensitivity; a versioned or absolute
name is not bare; both backend branches go through the resolver.

test_app_bridges.py — driving the real _register_mcp_servers and reading the
config back off disk: a bare command is resolved before it is written; the app
venv beats the gateway interpreter; the key set is unchanged; an absolute
interpreter, a node command and an HTTP entry are left alone.

Fail-before / pass-after: with the stdio resolution disabled and everything
else intact, 2 fail (a bare interpreter name reached the config). The rest
are preservation guards on what must NOT change — they pass either way by design
and are not evidence of the fix.

Validation

Targeted app suites: 302 passed, 19 skipped, 1 baseline Windows failure. That
failure is test_backend_entrypoint_escapes_app_root, which fails with the same
WinError 1314 on pristine main because this Windows account lacks the
symlink-creation privilege. The full repository suite was not run locally — the
environment is missing defusedxml — so CI is the authoritative matrix.

flake8, mypy, isort, git diff --check, docs-lint, scrub-lint and the
diff-scoped brand gate all clean. app-kit-platform.md documents the rewrite in
the same commit.

What this does not fix

#1807 also proposes an absolute cwd on the rewritten entry, so a relative
entry script resolves against the app. cwd is not among kiro-cli's documented
local-server properties and I could not verify locally whether an unknown key is
ignored or rejects the entry. If it rejects it, adding one would cause exactly the
failure this fixes — so it is left out, and the issue stays open for it.

An app's stdio MCP server declaring `python3` is written into the agent
config verbatim, so kiro-cli resolves it through PATH at spawn time. That
need not name an interpreter at all, and where it does it need not name
one new enough for the dependencies installed into the app's own venv.
Either way the server never starts, and a stdio MCP server that never
starts is indistinguishable from an app that provides no tools — nothing
on this path logs the difference.

The same app's backend already refuses a bare name for exactly these
reasons. It just refuses it in a way that only works on POSIX: both
Python branches hardcode `.venv/bin/python3`, so on Windows, where a venv
exposes `Scripts/python.exe`, the venv is never found and the launch
falls through to the gateway interpreter — the same silent substitution
one layer up.

Give the two spawn sites one policy. `apps/python_runtime` resolves the
app venv's interpreter for the running platform, falling back to
`sys.executable`, and both backend branches and the stdio registration
use it. Its own module because backend and bridges already defer-import
each other to break a cycle.

The match is literal, like the host-CLI pin beside it: an absolute path,
a versioned name and any non-Python binary are the author's explicit
choice and are written as declared. Only the value of `command` changes;
no key is added, because kiro-cli parses this file and its documented
local-server properties are command, args, env, disabled, autoApprove
and disabledTools.

The working-directory half of the report is not addressed here: `cwd` is
not among those properties and how kiro-cli treats an unknown key could
not be verified, so adding one risks causing the failure this fixes.

Refs kirodotdev#1807
@leonlaiyc
leonlaiyc requested a review from a team as a code owner August 6, 2026 15:32
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention labels Aug 6, 2026
@leonlaiyc

Copy link
Copy Markdown
Contributor Author

Upstream has since merged #1013, which adds resolve_stdio_command to apps/bridges.py and resolves bare python/python3/py MCP commands to sys.executable — documented there as the interpreter guaranteed to import kiro_crew.

That is a deliberate policy choice, and it is the opposite of the app-venv-first policy this PR proposes. The two resolvers also rebase cleanly onto each other rather than conflicting, so leaving this open would stack a second resolver ahead of the merged one and silently reduce it to a no-op.

Closing to avoid maintaining a competing implementation against the direction now adopted on main. Thanks to the reviewers who looked at it.

@leonlaiyc leonlaiyc closed this Aug 10, 2026
@github-actions github-actions Bot removed the readiness: action required A blocking check or review needs attention label Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant