feat(sdk): standalone local MCP server over stdio (#540) - #551
Open
abhayKashyap03 wants to merge 5 commits into
Open
abhayKashyap03 wants to merge 5 commits into
abhayKashyap03 wants to merge 5 commits into
Conversation
- Skip the SDK's input validation so string booleans reach the tool layer. - Add serve_stdio(), which redirects stray stdout to stderr so it can't corrupt the JSON-RPC stream. - Add tests for both bugs and use serve_stdio() in the docs launcher.
abhayKashyap03
marked this pull request as ready for review
October 3, 2026 08:08
Author
|
@rejojer @zmtomorrow would appreciate a review, thanks. |
This branch has not been deployed
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.
Closes #540.
What changes
A local library can now be used from MCP clients outside the Python process, such as Claude Desktop, Cursor and Cline. Until now, local mode reached agent tools only in-process, through
as_openai_tools(),as_claude_mcp()andas_anthropic_tools().pageindex/local_mcp_server.pywithLocalMcpServer, a low-level MCP server built on_tool_specs()for aPageIndexLocalClient.agent_instructions()is served atinitialize.isErroron tool results passes through unchanged.asyncio.to_thread, so they don't block the event loop.--managementaddsremove_document. When management is off, a direct call toremove_documentgets an MCP tool error and deletes nothing.serve_stdio()points stdout at stderr while it serves, and the protocol writes to a private copy of the original stdout. Stray output from a tool, C extension or child process can't corrupt the JSON-RPC stream this way. MCP 2.x'sstdio_serveralready does this; 1.x does not.pageindex-mcpconsole entry point ([tool.poetry.scripts]). It exits with a usage error when--storage-pathis missing, isn't a directory or isn't readable. It warns on stderr when the path has nomanifest.json, because the store would otherwise serve an empty library without saying so. Ctrl+C exits with code 130 and prints no traceback.mcp>=1.19.0,<3. 1.x registers handlers with decorators and 2.x passes them to the constructor, so the server picks the right path at construction time. On 1.x it registerscall_toolwithvalidate_input=False. Otherwise the SDK's schema check rejects string booleans such as"false"before the tool layer can convert them, which cloud does.local_mcp_serveris added topageindex._SUBMODULES.mcpServersconfig. The "MCP server" row in the Local column now links to that section.No new dependencies.
CI
pip install --no-deps -e .was added after the requirements install, so the console script exists for the executable tests.mcp==1.19.0. It is the only leg that runs the 1.x code path, because the other legs install the latest mcp. Job names now include the mcp version.gateis unchanged.Tests
tests/test_local_mcp.py, 13 tests. No LLM calls: fixtures seed a temporary store withseed_doc.initializereturns the agent instructions.tools/listreturns the right names, schemas andreadOnlyHintannotations.NOT_FOUND. An unknown tool, and a call with missing arguments, both return errors.serve_stdio().pageindex-mcp, run from an unrelated working directory:--management, including whether deletion is allowed.--helpoutput and invalid arguments.CIis set.Local runs:
CI=true(the new leg):test_local_mcp.py13 passed. Before the latest two executable tests were added, the full suite on this leg was 614 passed, 218 skipped.pageindex-mcpdriven over raw JSON-RPC pipes with a minimalPATHandcwd=/, the way desktop hosts launch it: every stdout line is valid JSON-RPC, and the process exits 0 when stdin closes.Not in this PR
cited_answerprompt.--management, whenremove_documentruns alongside reads.manifest.jsonstill loads as an empty library without error. That behaviour is inlocal_storeand is unchanged here.serve_stdio()usesos.dup/os.dup2, which exist on Windows, but CI runs only on Ubuntu, so Windows is untested.