Repository navigation
feat(mcp): add pi-mcp-py, an extension that connects the MCP servers a client passes - #36
Merged
Merged
Conversation
…a client passes
An editor such as Zed passes its MCP servers in session/new. With this package
installed the agent connects them (stdio, Streamable HTTP, SSE) and gives their
tools to the model. Core, harness and CLI still have no MCP client and read no
mcp.json: the extension takes the servers from pi.client_mcp_servers (the hooks
of the previous commit) and claims them. Without the package nothing changes.
Package packages/pi-mcp (pi-mcp-py, import name pi_mcp, entry point pi-mcp):
- One task per server. The MCP SDK uses anyio cancel scopes, which must end in
the task that began them. session_start only starts the tasks; a turn barrier
waits at most 10 s for servers that still start, and says so once.
- Tools are named mcp__<server>__<tool> (64 characters at most, a hash when cut,
_2/_3 for repeats). The prefix matters: an extension tool with the name of a
built-in tool replaces it. The ask mode asks about every MCP tool call unless
the tool says it is harmless (readOnlyHint, as for all tools).
- Results: text over 50 KB is cut with a note, a result with only
structuredContent becomes JSON text, an MCP error result becomes an error.
- Limits: PI_MCP_STARTUP_TIMEOUT (60 s), PI_MCP_CALL_TIMEOUT (120 s); a call
also ends on abort.
- A server that stops: the in-flight call fails with the reason and is not sent
again (the server may have run it). The next call connects the server again,
3 times in a row at most (a call that gets an answer starts the count again).
A request that never left is sent again once. A call stops waiting when the
connection ends: the SDK does not tell a request when its transport ends by
cancellation, so every start has a future that the call races. /mcp and
/mcp reconnect <name> show and repair the servers.
- Secrets: the notices, /mcp, errors and logs show only the server name, the
executable name or the host. Token values of env, headers and url are masked
in error text.
- stdio is opened with the MCP SDK and not with create_session of
langchain-mcp-adapters, which would expand ${VAR} from the environment of the
agent (the LLM key). The child gets the SDK default list, proxy, certificate
and locale variables, and the env of the client. stderr goes to
<home>/logs/mcp/<name>.log, not to the pipe the client reads.
- A server at localhost, 127.0.0.1 or ::1 is reached without the system proxy:
httpx reads the Windows proxy settings and does not skip loopback.
- Closing the session ends every server (stdin first, then the process tree).
Wiring: uv workspace member and testpaths, the install line of ci.yml,
test_extension_packaging (the floor of pi-agent-core-lc is the checkout
version; the package says so and does nothing on an older core), AGENTS.md,
README.md, CHANGELOG.md and the design (docs/specs/2026-10-23-mcp-extension-
design.md, brought in line with the code). The tests of the package are not
collected when mcp, langchain_mcp_adapters or pi_mcp is missing (the pre-release
job of CI installs none of them; release.yml is not changed). tui-ci.yml is not
changed either: zypi passes no servers.
Tests (192, real child processes and a real local HTTP server, no mock of the
SDK): units, stdio, HTTP and SSE, the extension in a real AgentHarness, and a
real PiAcpAgent over ACP (ask mode, session/close ends the process). Windows
(Python 3.11 and 3.12) and WSL.
The Release workflow now installs pi-mcp-py for its pre-release tests and builds it with the other packages. `publish-pypi` uploads every file of `dist/` and `github-release` attaches `dist/*`, so the build step is the whole publish list. - release.yml: add `-e "./packages/pi-mcp"` to the install line of the test job, and the step "Build pi-mcp". With the package installed, the pre-release `pytest -v` runs `packages/pi-mcp/tests` too. - README.md: the status table and the install lines say that pi-mcp-py is on PyPI from 0.6.0. - packages/pi-mcp/README.md and pi_mcp/__init__.py: install with `pip install pi-mcp-py`. The README is the long description on PyPI, so it must not say "not on PyPI yet". - CHANGELOG.md: the entry of pi-mcp-py says that the Release workflow builds it. - docs/specs/2026-10-23-mcp-extension-design.md: section 9 lists what changed for the release. Checked: actionlint finds nothing in release.yml; the sdist and the wheel build with the command of the workflow; `twine check --strict` passes for both; the wheel has the eight modules and the `pi-mcp` entry point.
zy1233
added a commit
that referenced
this pull request
Oct 25, 2026
…erge of #33 to #36 (plan r20) (#38) * docs(plan): record the MCP direction, the blank-session fix and the merge of #33 to #36 (plan r20) The owner came back on 2026-10-23 with two problems in Zed: the MCP notice at the start of each new thread, and a thread without a message that cannot be opened after Zed restarts. The owner chose an optional extension package for MCP (core, harness and CLI stay without an MCP client) and asked for the blank-session fix as a PR at once. On 2026-10-25 the owner asked for the release content first, then for the merge of #33 to #36. The merge is done. #33 with `gh pr merge --squash` (ca2ab85). #34 to #36 with `gh stack link 34 35 36` (stack #37) and `gh stack merge 37 --yes --squash` (fa4887b, 34c11d9, e0f2a36). The tree of each commit is the tree of its PR head plus the two plan files of #33. CI and TUI CI pass on e0f2a36 (pytest 2,908 passed on Python 3.11, 3.12 and 3.13; nextest 8,490 passed, 0 failed, 22 skipped). PLAN-RUST-AGENT-RUNTIME-REMOVAL.md: - Decision 17 gets a correction (#34: session/load and session/resume open an empty session for a canonical UUID that has no file). - New decisions: 19 (the MCP direction, decided), 20 (the versions of the extension packages and their core floors), 21 (the 11 defaults of the MCP design). - V0.6: 14 commits, one more package. Conditions 1, 3, 5 and 6 and the known issues are rewritten. - r19 said the three extension packages stay as they are. That is wrong. The packaging test requires that each floor equals the workspace version, so a core of 0.6.0 needs `>=0.6.0` in all four extension packages (checked: 4 tests fail). This is decision 20. - The status, section 7, appendix A.1 and A.2, the index and the revision log carry the same facts. The execution log gets section 10.23: the cause of both problems, the three PRs, the release content, the merge, the check of the trees, CI on main, the floors, and what is not verified. docs/TUI-AND-CODE-AGENT.md: the paragraph about MCP servers now says that `pi-mcp-py` connects them and what the agent says without it. No condition of V0.6 is met yet. Only documents change. * docs(plan): record the answer of the owner to decisions 20 and 21 (plan r20) The owner answered "follow your lean" for the two open decisions of r20. Decision 20 (versions and core floors of the extension packages): option (a). At the version bump (V0.6 condition 5) the four extension packages get the core floor `>=0.6.0`. pi-web-access-py, pi-goal-x-py and pi-dynamic-workflows-py go from 0.2.0 to 0.3.0, as V0.5 did in 7e7d4d4 (0.1.0 to 0.2.0). pi-mcp-py stays at 0.1.0 (first upload). The lean did not say which digit to raise. 0.3.0 is my choice from the V0.5 precedent; the plan and the execution log say so. Decision 21 (the 11 defaults of the MCP design): all defaults stay, including Q3 (annotations are trusted) and Q5 (no way to configure servers in zypi). No code change. Neither decision changes code. The change of decision 20 belongs to the PR of condition 5, which is not open yet. Files: the plan (status, V0.6 section, next steps, decisions 20 and 21, the lists of the decision section, index, revision log, one link definition), the execution log (section 10.23 and its revision-log entry), the MCP design (section 10: one sentence). Only documents change.
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.
Summary
Stacked on #35 (base:
feat/client-mcp-hooks, which is on #34). Core and CLI still have no MCP client and do not import this package. This PR adds the optional packagepi-mcp-py(modulepi_mcp, entry pointpi-mcp). It connects the MCP servers that the ACP client passes insession/new|load|resume, and gives their tools to the model.Wiring: workspace member and
testpathsinpyproject.toml; the CItestjob installs the package (ci.yml); module-map row inAGENTS.md; README status row; CHANGELOG; the design (docs/specs/2026-10-23-mcp-extension-design.md) is synced with the code.Release (last commit,
ci(release)):release.ymlinstalls the package for the pre-release tests and builds it (Build pi-mcp).publish-pypiuploads every file ofdist/andgithub-releaseattachesdist/*, so the next tag publishespi-mcp-py0.1.0 with the other packages. The README table and install lines, the package README (it is the PyPI description), the CHANGELOG and spec 9 say "on PyPI from 0.6.0".Design points that are easy to miss:
mcp__<server>__<tool>. The harness lets an extension tool replace a tool of the same name, so a server with a tool namedbashwould take the place of the realbashwithout the prefix.gonefuture (ConnectionEnded) instead of waiting for its time limit.httpxreads the proxy of the system (on Windows, the registry) and does not skip loopback addresses. A server atlocalhost,127.0.0.1or::1uses a client withtrust_env=False.envof the editor. It does not get the LLM keys of the agent.Evidence
Before (base of this PR, Zed passes its context7 server): the notice says the server is not connected. After: the model calls it.
Real server. The context7 server of Zed, started as Zed passes it (Zed has no system Node: its own
node.exeruns the script of its extension): ready in 3.1 s, 2 tools (both withreadOnlyHint),resolve-library-id("react")returned 1683 characters, no process left after close.Real model.
PiAcpAgent(entry-point discovery, real stream function, config of the maintainer:deepseek/deepseek-flash) and that server insession/new:session/newanswered in 0.45 s. The model calledmcp__mcp-server-context7__resolve-library-id(argument nameslibraryName,query), got the 1683 characters, and answered with the library id/reactjs/react.dev. 10.1 s for the turn,stop_reason=end_turn, 0 permission requests (automode), 0nodeprocesses afterclose_session.Tests. The 192 tests of
packages/pi-mcp/testspass on Windows with Python 3.11, 3.12 and 3.13, and on Linux (WSL2) with Python 3.12. Whole suite, run before the last test-only edits: Windows 2905 passed / 42 skipped; Linux 2931 passed / 16 skipped.28 one-line mutations of
pi_mcp(no prefix, whole environment to the child, no redaction, no second try for an unsent request, no time limit, no direct client for loopback, a name given twice, no cut of a long result, and others): every one makes at least one test fail. One survived at first (a tool registered again after a reconnect); the assertion is now exact and the mutation is caught.Release content (last commit).
actionlintfinds nothing inrelease.yml. A dry run ofReleasefrom this branch (run 40162795623; a manual run publishes nothing):Pre-release testspass (the job now installs the package and runs its tests),Build Python packagespass with the new stepBuild pi-mcp,Publish to PyPIis skipped. Thedistartifact has 14 files, among thempi_mcp_py-0.1.0-py3-none-any.whlandpi_mcp_py-0.1.0.tar.gz, andtwine check --strictpasses for all 14. The wheel has the eight modules and thepi-mcpentry point. I cancelled the two TUI build jobs of the run: this change does not touch them.ruff check .andruff format --check .: clean, also with the CI-pinned ruff 0.16.0.Not done: OAuth; MCP resources, prompts, sampling, roots, elicitation,
tools/list_changed; websocket servers. The legacy SSE transport of the SDK swallows the errors of its POST requests, so a call to a dead SSE server ends by its time limit. Asession/newthat Zed sends before its first worktree loads has an emptymcpServers(zed#64611); the agent cannot tell that from "no servers". Whether Zed gives OAuth tokens to an agent asheadersis not verified.Merge Danger
Door: two-way. It is a new package; nothing in core or CLI imports it. Uninstall
pi-mcp-pyand the agent is as after #35.Blast Radius: the CI
testjob, and everyone who installs the package.commandandargsof a stdio server). The editor has already asked the user (Zed keeps the servers of a project off in Restricted Mode until the worktree is trusted), so the agent does not ask a second time (spec 6.3). Inaskmode, each MCP tool call is still asked about unless the server marks the toolreadOnlyHint(or neither destructive nor open-world).testjob installs one more package. Measured on the runs of feat(extensions): give extensions the MCP servers a client passes, a way to tell the user, and turn barriers #35 and this PR: Python 3.11 2m31s to 4m10s, 3.12 1m48s to 3m58s, 3.13 2m36s to 3m17s. All checks of this PR pass.tui-ci.ymlis not changed.release.yml(two edits: the install line of the pre-release tests, and the stepBuild pi-mcp). The next tag publishespi-mcp-py0.1.0 with the other packages. Three things that I cannot check from here:PYPI_API_TOKENthat may create projects (an account-wide token, or a pending publisher for the name). The namepi-mcp-pyis free (/pypi/pi-mcp-py/jsongives 404). Withskip-existing: true, a re-run after a token fix is safe.pi-agent-core-lc>=0.5.0inpackages/pi-mcp/pyproject.tomlmust become>=0.6.0with the version bump, because the extension needs the hooks of feat(extensions): give extensions the MCP servers a client passes, a way to tell the user, and turn barriers #35.test_extension_packaging.pyfails until it does, so CI says so.