fix(apps): resolve bare Python MCP commands through app venvs - #1856
Closed
leonlaiyc wants to merge 1 commit into
Closed
fix(apps): resolve bare Python MCP commands through app venvs#1856leonlaiyc wants to merge 1 commit into
leonlaiyc wants to merge 1 commit into
Conversation
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
Contributor
Author
|
Upstream has since merged #1013, which adds 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. |
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.
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 theagent config verbatim, so kiro-cli resolves it through
PATHat spawn time. Thatneed 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.pyhardcodes.venv/bin/python3in both Python branches, so on Windows — where a venv exposesScripts/python.exe— the venv is never found and the launch falls through to thegateway 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 tosys.executable. Two thingsfrom it are adopted here and are better than what I had —
pybelongs in thebare set, and the match should be case- and whitespace-insensitive.
The difference is the resolution target:
sys.executable.venvfirst, elsesys.executablekiro_crewScripts/python.exeThe 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_runtimeresolves the app venv's interpreter for the runningplatform, falling back to
sys.executable. Bothbackend.pyPython branches andthe stdio registration use it — a net reduction in
backend.py. Its own modulebecause
apps.backendandapps.bridgesalready defer-import each other to breaka 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 explicitchoice and are written as declared. HTTP entries are untouched.
Only the value of
commandchanges; no key is added. kiro-cli parses thisfile, 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, requirementsinstallation, manifest validation.
Tests
test_app_python_runtime.py(new) — POSIX and Windows venv layouts; the otherplatform'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_serversand reading theconfig 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
nodecommand 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 restare 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 sameWinError 1314on pristinemainbecause this Windows account lacks thesymlink-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 thediff-scoped brand gate all clean.
app-kit-platform.mddocuments the rewrite inthe same commit.
What this does not fix
#1807also proposes an absolutecwdon the rewritten entry, so a relativeentry script resolves against the app.
cwdis not among kiro-cli's documentedlocal-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.