Skip to content

feat(cli): tripl CLI foundation with the HTTP client extracted from tripl-mcp - #76

Merged
vladenisov merged 1 commit into
mainfrom
feat/cli-foundation
Aug 1, 2026
Merged

feat(cli): tripl CLI foundation with the HTTP client extracted from tripl-mcp#76
vladenisov merged 1 commit into
mainfrom
feat/cli-foundation

Conversation

@vladenisov

Copy link
Copy Markdown
Owner

tripl-ey6j.1 — the groundwork every later stage of the CLI epic needs.

What lands

A new cli/ package: distribution tripl 0.1.0, Apache-2.0, console script tripl, tripl --version, and layered configuration (flag → env → config file) that records each field's provenance, 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 from there. Exactly 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.

What the review changed

--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.
Publish gate un-defanged The smoke test had gained --find-links ../cli/dist, making 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, and so fails until tripl is published. That is correct: releasing tripl-mcp today would ship an unresolvable dependency.
httpx>=0.27.0,<1 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.
CLI wheel smoke test CI now builds the 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.
Eager config validation 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.
A false doc claim The workflow introduced a note saying 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 as working.

Test plan

  • cli: ruff, ruff format, mypy (7 modules), 51 tests
  • mcp-server: ruff, ruff format, mypy (13 modules), 36 tests
  • tripl wheel: httpx<1, licence bundled, py.typed present, no importable tripl
  • tripl-mcp wheel: plain Requires-Dist: tripl<0.2,>=0.1, no path leakage

Deferred, filed

  • tripl-ey6j.7tripl_mcp.__version__ is a hardcoded literal now load-bearing on the wire via the User-Agent
  • tripl-ey6j.6 — the tripl distribution-name collision with backend/pyproject.toml must be settled before the name is claimed on PyPI
  • tripl-ey6j.2website/docs/run/cli.md (the wheel's Documentation URL 404s until then) and the async seam for doctor/status

🤖 Generated with Claude Code

https://claude.ai/code/session_017Jwgd2ghK9bwvLhSHUZAm8

…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.
Copilot AI review requested due to automatic review settings July 31, 2026 18:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 shared TriplClient used by both the CLI and tripl-mcp.
  • Refactor tripl-mcp to depend on tripl (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 tripl dependency for the in-repo tripl-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 thread mcp-server/Dockerfile
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__}"
@vladenisov
vladenisov merged commit f0efa5c into main Aug 1, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants