feat(cli): tripl CLI foundation with the HTTP client extracted from tripl-mcp - #76
Merged
Conversation
…ripl-mcp tripl-ey6j.1 — the groundwork every later stage of the CLI epic needs. New cli/ package: distribution `tripl` 0.1.0, Apache-2.0, console script `tripl`, `tripl --version`, and layered configuration (flag, then env, then a config file) with each field's provenance recorded so a later `doctor` can say where a value came from. The import package is `tripl_cli`, NOT `tripl`. The service's own source package is backend/src/tripl/, and a distribution installing an importable `tripl` would shadow it wherever both are present. Verified from the built wheel: it ships no top-level `tripl` module. The HTTP client MOVED rather than being copied — git mv, so history follows — and tripl-mcp now depends on `tripl` and imports it from there. One TriplClient exists in the repo. Copying it would have been the fourth drift defect here, after the query keys (four times), the event-name regex (three), the security headers and the significance gate. tripl-mcp resolves `tripl` from ../cli during development via [tool.uv.sources], which uv strips from published metadata — checked against the built wheel, whose METADATA carries a plain `Requires-Dist: tripl<0.2,>=0.1` with no path in it. Both packages now run in CI. Neither did before. FROM THE REVIEW --------------- - `--locked`, not `--frozen`. They are not synonyms: --frozen skips the lock-vs-pyproject check, so CI would pass on a stale lockfile — the opposite of what the comment claimed. It proved itself immediately: the httpx bound below moved pyproject ahead of uv.lock and --locked caught it. - The publish smoke test no longer passes `--find-links ../cli/dist`. That made `tripl` resolve from a directory no consumer has, turning the one gate that catches what a lockfile hides into a gate that hides it. It now installs exactly what a user gets, which means it FAILS until `tripl` is published — correct, because releasing tripl-mcp today would ship an unresolvable dependency. - `httpx>=0.27.0,<1` in both packages. Unbounded floors are what broke the last release (`mcp` 2.0 removed mcp.server.fastmcp); repeating it twenty lines from that explanation would have been careless. - CI now builds the tripl wheel and runs `tripl --version` from a clean venv. The console script is the deliverable; running it from the source tree proves nothing about packaging. - Config file types are validated up front. The layered pick short-circuits, so `base_url = 8000` stayed silent while TRIPL_BASE_URL was exported and would have surfaced days later from a file the user had not touched. - Corrected a documentation claim the workflow introduced: it said `uvx tripl-mcp` does not resolve. It does — the published 0.1.0 predates the client extraction and needs only mcp and httpx (checked against the PyPI JSON API). The git-subdirectory install is now described as untested rather than working. Gates: cli — ruff, mypy (7 modules), 51 tests; mcp-server — ruff, mypy (13 modules), 36 tests; wheel metadata verified for both. Deferred, filed: tripl-ey6j.7 (tripl-mcp's hardcoded __version__), plus notes on .2 and .6 for the docs page and the `tripl` distribution-name collision with backend/pyproject.toml.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a new cli/ Python distribution (tripl) that provides the foundational operator CLI (tripl --version, layered config with provenance) and centralizes the shared async HTTP client (tripl_cli.client) so tripl-mcp imports it instead of carrying a drifting copy. It also updates the MCP server packaging/build/test wiring (CI, Docker, docs) to reflect the new dependency relationship.
Changes:
- Add the
cli/package (tripl_cli) with typed config resolution, typed transport errors, and a sharedTriplClientused by both the CLI andtripl-mcp. - Refactor
tripl-mcpto depend ontripl(client extraction), update runtime error translation and User-Agent handling, and adjust tests accordingly. - Update CI, Docker build contexts, compose configs, and docs to build/test both packages and reflect the unpublished
tripldependency for the in-repotripl-mcp.
Reviewed changes
Copilot reviewed 35 out of 39 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| website/docs/integrate/mcp-server.md | Updates MCP server install-path documentation to distinguish PyPI vs in-repo state and the new tripl dependency. |
| mcp-server/uv.lock | Locks tripl as a local directory source for development/CI and adds it to tripl-mcp deps. |
| mcp-server/tests/test_tools_e2e.py | Updates e2e tool tests for User-Agent plumbing and MCP-specific error guidance restoration. |
| mcp-server/tests/test_tools_common.py | Adds unit tests for prompt/payload shaping (with_mutation_warnings) after refactor. |
| mcp-server/tests/test_runtime.py | Switches runtime tests to use tripl_cli.client.TriplClient and asserts streamable-http UA handling. |
| mcp-server/tests/test_contract.py | Points API prefix contract test at the shared client (tripl_cli.client). |
| mcp-server/src/tripl_mcp/tools/events.py | Rehomes with_mutation_warnings import to tools common module. |
| mcp-server/src/tripl_mcp/tools/_common.py | Adds with_mutation_warnings implementation as MCP prompt-layer behavior. |
| mcp-server/src/tripl_mcp/server.py | Builds the stdio lifespan HTTP pool via the shared client factory and passes MCP User-Agent. |
| mcp-server/src/tripl_mcp/runtime.py | Wraps shared-client typed errors into MCP ToolError once via _McpTriplClient. |
| mcp-server/src/tripl_mcp/errors.py | Implements MCP-specific guidance mapping from shared typed client errors to ToolError strings. |
| mcp-server/src/tripl_mcp/init.py | Defines USER_AGENT (currently derived from a hardcoded __version__). |
| mcp-server/README.md | Updates README to explain the shared client dependency and how development installs resolve tripl. |
| mcp-server/pyproject.toml | Adds tripl>=0.1,<0.2 dependency and bounds httpx; configures [tool.uv.sources] for dev/CI. |
| mcp-server/Dockerfile | Moves build context to repo root and copies cli/ into the image for local-path resolution. |
| compose.yaml | Updates MCP service build context to repo root + explicit Dockerfile path. |
| compose.dev.yaml | Same as compose.yaml for dev profile: builds MCP image from repo root. |
| cli/uv.lock | Adds lockfile for the new cli/ package (tripl distribution). |
| cli/tests/test_config.py | Adds extensive tests for precedence, provenance, normalization, and eager validation of config. |
| cli/tests/test_client.py | Moves/updates client tests to validate typed errors and User-Agent forwarding. |
| cli/tests/test_cli.py | Adds CLI entrypoint/parser/version/exit-code tests and global flag seam tests. |
| cli/tests/conftest.py | Adds autouse env/home isolation and config writer fixture to keep tests hermetic. |
| cli/tests/init.py | Initializes the CLI test package. |
| cli/src/tripl_cli/py.typed | Marks the CLI package as typed for consumers. |
| cli/src/tripl_cli/errors.py | Introduces shared typed exceptions and CLI exit codes without MCP dependency. |
| cli/src/tripl_cli/config.py | Implements layered config resolution with provenance and eager file validation. |
| cli/src/tripl_cli/commands/init.py | Adds subcommand registration scaffold (empty for now) aligned with MCP server structure. |
| cli/src/tripl_cli/client.py | Adds shared async client with typed error mapping and configurable User-Agent. |
| cli/src/tripl_cli/cli.py | Adds argparse wiring, config resolution, dispatch, and standardized error rendering. |
| cli/src/tripl_cli/main.py | Supports python -m tripl_cli module execution. |
| cli/src/tripl_cli/init.py | Defines distribution-aware version lookup (version("tripl")) with safe fallback. |
| cli/README.md | Documents installation, config precedence, file locations, and development commands for the CLI. |
| cli/pyproject.toml | Defines the tripl distribution (import package tripl_cli), dependencies, tooling config, and scripts. |
| cli/LICENSE | Adds Apache-2.0 license text for the CLI distribution. |
| cli/.gitignore | Ignores CLI-local virtualenv/build/test/tool artifacts. |
| .github/workflows/release.yml | Adjusts release docker build context for MCP image to repo root. |
| .github/workflows/publish-mcp.yml | Strengthens MCP release gates with --locked and clarifies wheel install behavior. |
| .github/workflows/ci.yml | Adds dedicated CI jobs for cli/ and mcp-server/ with lint/type/test + CLI wheel smoke test. |
| .dockerignore | Updates ignore rules/comments for root-context builds now covering both service and MCP images. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+21
to
23
| COPY mcp-server/pyproject.toml mcp-server/uv.lock mcp-server/README.md ./ | ||
| COPY mcp-server/src ./src | ||
| RUN uv sync --frozen --no-dev |
Comment on lines
1
to
+14
| """tripl-mcp — standalone MCP server that proxies a curated tripl REST toolset.""" | ||
|
|
||
| __version__ = "0.1.0" | ||
|
|
||
| # What this server calls itself on the wire. It lives HERE rather than in the | ||
| # client because the client now ships in the shared `tripl` distribution and | ||
| # must not claim to be tripl-mcp (tripl-ey6j.1). | ||
| # | ||
| # Both transports have to pass it: server.py builds the stdio lifespan pool, and | ||
| # TriplClient builds a per-request transport for streamable-http. Miss either | ||
| # and half this server's traffic reports the CLI's default UA in the operator's | ||
| # access logs — the one signal they have for telling agent traffic from CLI | ||
| # traffic apart. | ||
| USER_AGENT = f"tripl-mcp/{__version__}" |
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.
tripl-ey6j.1— the groundwork every later stage of the CLI epic needs.What lands
A new
cli/package: distributiontripl0.1.0, Apache-2.0, console scripttripl,tripl --version, and layered configuration (flag → env → config file) that records each field's provenance, so a laterdoctorcan say where a value came from.The import package is
tripl_cli, nottripl. The service's own source package isbackend/src/tripl/, and a distribution installing an importabletriplwould shadow it wherever both are present. Verified from the built wheel: it ships no top-leveltriplmodule.The HTTP client moved rather than being copied —
git mv, so history follows — andtripl-mcpnow depends ontripland imports from there. Exactly oneTriplClientexists in the repo. Copying it would have been the fourth drift defect here, after the query keys (four times), the event-name regex (three), the security headers and the significance gate.tripl-mcpresolvestriplfrom../cliduring development via[tool.uv.sources], which uv strips from published metadata — checked against the built wheel, whose METADATA carries a plainRequires-Dist: tripl<0.2,>=0.1with no path in it.Both packages now run in CI. Neither did before.
What the review changed
--locked, not--frozen--frozenskips the lock-vs-pyproject check, so CI would pass on a stale lockfile — the opposite of what the comment claimed. It proved itself immediately: thehttpxbound below moved pyproject ahead ofuv.lockand--lockedcaught it.--find-links ../cli/dist, makingtriplresolve from a directory no consumer has — turning the one gate that catches what a lockfile hides into a gate that hides it. It now installs exactly what a user gets, and so fails untiltriplis published. That is correct: releasingtripl-mcptoday would ship an unresolvable dependency.httpx>=0.27.0,<1mcp2.0 removedmcp.server.fastmcp). Repeating it twenty lines from that explanation would have been careless.tripl --versionfrom a clean venv. The console script is the deliverable; running it from the source tree proves nothing about packaging.base_url = 8000stayed silent whileTRIPL_BASE_URLwas exported — and would have surfaced days later from a file the user had not touched.uvx tripl-mcpdoes not resolve. It does — the published 0.1.0 predates the client extraction and needs onlymcpandhttpx(checked against the PyPI JSON API). The git-subdirectory install is now described as untested rather than as working.Test plan
cli: ruff, ruff format, mypy (7 modules), 51 testsmcp-server: ruff, ruff format, mypy (13 modules), 36 teststriplwheel:httpx<1, licence bundled,py.typedpresent, no importabletripltripl-mcpwheel: plainRequires-Dist: tripl<0.2,>=0.1, no path leakageDeferred, filed
tripl-ey6j.7—tripl_mcp.__version__is a hardcoded literal now load-bearing on the wire via the User-Agenttripl-ey6j.6— thetripldistribution-name collision withbackend/pyproject.tomlmust be settled before the name is claimed on PyPItripl-ey6j.2—website/docs/run/cli.md(the wheel'sDocumentationURL 404s until then) and the async seam fordoctor/status🤖 Generated with Claude Code
https://claude.ai/code/session_017Jwgd2ghK9bwvLhSHUZAm8