Skip to content

fix: version drift, bypassed output validation, and tests shipped in the wheel - #5

Open
PietjePuh wants to merge 3 commits into
CodeAgentBridge:mainfrom
PietjePuh:fix/version-drift-and-output-validation
Open

fix: version drift, bypassed output validation, and tests shipped in the wheel#5
PietjePuh wants to merge 3 commits into
CodeAgentBridge:mainfrom
PietjePuh:fix/version-drift-and-output-validation

Conversation

@PietjePuh

@PietjePuh PietjePuh commented Aug 29, 2026

Copy link
Copy Markdown

Three independent fixes, one commit each. Full CI gate passes locally (ruff check, ruff format --check, mypy, pytest), plus uvx twine check on the rebuilt artifacts and a read-only smoke test against the live Jules API.


1. The server advertises the wrong version

jules_mcp/jules_mcp.py hardcoded version: Final[str] = "0.1.3" while pyproject.toml declares 0.1.6. FastMCP("Jules MCP Server", version=version) passes that straight to clients, so every MCP client has been told the wrong version.

Now read from installed distribution metadata via importlib.metadata, with a fallback constant for source-checkout runs (fastmcp run jules_mcp/jules_mcp.py, used by the Dockerfile and MCP.json).

2. Output-schema validation was disabled for three tools

tests/test_jules_mcp/conftest.py monkeypatched FastMCP internals at module-load time:

if fn.__name__ in ("create_session", "get_session", "wait_for_session_completion"):
    if "return" in fn.__annotations__:
        fn.__annotations__["return"] = dict

Rewriting the return annotation to dict replaces the generated Session output schema with a permissive one, so a quarter of the tool surface ran with no meaningful output validation.

It was compensating for a malformed fixture, not a library bug. mock_session_dict had a top-level "source" key — not a field on models.Session — and "source_context": {"source_name": ...} where models.SourceContext requires source. The old fixture fails with Output validation error: 'source' is a required property once the patch is removed.

Fixture corrected, patch deleted. Verified all 12 tools still expose a non-null outputSchema without it.

3. The published wheel ships a top-level tests package

pyproject.toml had no [build-system] table, and [tool.setuptools.packages.find] had no include filter. Building 0.1.6 as-is produces:

top_level.txt -> "jules_mcp\ntests"

jules_mcp/__init__.py
jules_mcp/__main__.py
jules_mcp/jules_mcp.py
jules_mcp/py.typed
tests/test_jules_mcp/__init__.py      <-- shipped to PyPI
tests/test_jules_mcp/conftest.py      <-- shipped to PyPI
tests/test_jules_mcp/test_mcp_server.py

So pip install jules-mcp installs a top-level tests package into the user's site-packages, where it can shadow or collide with anything else of that name. This affects the released artifact, not just the repo.

The missing [build-system] had a second effect: uv classified the project as virtual (source = { virtual = "." } in uv.lock) and never installed it, so importlib.metadata could not resolve the distribution and the fix in (1) always took its fallback path.

Declaring the setuptools backend and constraining discovery to jules_mcp* fixes both. After the change:

top_level.txt -> "jules_mcp"
wheel contains only jules_mcp/
uv.lock: source = { virtual = "." }  ->  source = { editable = "." }

uv.lock is regenerated in the same commit because CI runs uv sync --locked, which would otherwise fail against the changed project source type. The lock diff is a single line.


Regression guards

Added TestVersion and TestPackaging. Both were confirmed to fail when the original defects are reintroduced, rather than passing vacuously:

  • restoring 0.1.3 → both version assertions fail
  • removing the include filter → test_tests_package_is_not_shipped fails

Verification

ruff check .            All checks passed!
ruff format --check .   6 files already formatted
mypy .                  Success: no issues found in 3 source files
pytest                  16 passed   (was 12)
uvx twine check dist/*  PASSED (wheel + sdist)

Also exercised read-only against a live Jules API account — list_sources, get_all_sources, list_sessions, get_session, list_activities all pass, confirming real Session payloads validate against the schema this PR re-enables. Details in the comment below.

Not included

The Dockerfile looks like it has a separate issue: RUN uv sync --no-dev builds /app/.venv, but the ENTRYPOINT is uv run --with fastmcp --with jules-agent-sdk --with requests fastmcp run .... Those unpinned --with flags re-resolve dependencies at container start, bypassing uv.lock and requiring network at runtime. I had no Docker daemon available to build-test a change, so I left it out rather than ship something unverified. Happy to open a separate PR.

The server hardcoded version = "0.1.3" while pyproject.toml declared
0.1.6, so every MCP client was told the wrong server version. Read the
version from installed distribution metadata instead, with a fallback
for source-checkout runs (`fastmcp run jules_mcp/jules_mcp.py`) where no
distribution metadata exists.
conftest monkeypatched ParsedFunction.from_function to rewrite the return
annotation of create_session, get_session and wait_for_session_completion
to `dict`, which disabled MCP output-schema validation for those three
tools. It was there to accommodate a malformed fixture: mock_session_dict
carried a top-level "source" key (not a field on models.Session) and
"source_context": {"source_name": ...}, but SourceContext requires
"source".

Correct the fixture and drop the patch, so the three session tools are
now covered by real output-schema validation. Also add TestVersion to
guard against the version drift fixed in the previous commit.
Copilot AI lite review requested due to automatic review settings August 29, 2026 09:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@PietjePuh

Copy link
Copy Markdown
Author

Follow-up: I've now validated this against a live Jules API account, not just mocks. That was the one real risk in this PR — the existing fixtures return plain dicts, while the SDK returns dataclasses, so removing the output-schema bypass needed proof that genuine API payloads validate.

Read-only calls only (list_sources, get_all_sources, list_sessions, get_session, list_activities). No session was created, approved, or messaged.

server version advertised: 0.1.6
tools registered: 12
[PASS] list_sources        -> 5 source(s), nextPageToken=yes
[PASS] get_all_sources     -> 32 source(s)
[PASS] list_sessions       -> 5 session(s)   <-- real Session payloads
[PASS] get_session         -> state=AWAITING_USER_FEEDBACK
[PASS] list_activities     -> 1 activity(ies)

RESULT: ALL READ-ONLY CALLS PASSED

Two things this confirms:

  1. get_session and list_sessions validate against the real Session output schema. These are two of the three tools whose validation the conftest.py patch was suppressing. Live responses pass the schema unmodified, so the patch was never protecting against a genuine incompatibility — only against the malformed fixture.
  2. The version fix works end-to-end. The server reports 0.1.6 from installed distribution metadata, matching pyproject.toml, where it previously reported 0.1.3.

Account-identifying details (source names, session titles) omitted deliberately.

pyproject.toml had no [build-system] table, and
[tool.setuptools.packages.find] had no include filter. Two consequences:

1. setuptools discovered `tests` alongside `jules_mcp`, so the published
   wheel exported BOTH as top-level packages. top_level.txt read
   "jules_mcp\ntests", meaning `pip install jules-mcp` dropped a
   top-level `tests` package into the user's site-packages, where it can
   collide with anything else of that name.

2. Without [build-system], uv classified the project as virtual
   (`source = { virtual = "." }` in uv.lock) and never installed it, so
   importlib.metadata could not resolve the distribution and the version
   lookup always took the source-checkout fallback path.

Declare the setuptools backend and constrain discovery to jules_mcp*.
The wheel now contains only jules_mcp/ and top_level.txt reads
"jules_mcp"; uv.lock flips to `source = { editable = "." }` and the
project installs, so the version is read from real metadata.

uv.lock is regenerated because `uv sync --locked` in CI would otherwise
fail against the changed project source type.

Adds TestPackaging to guard both regressions. Verified `uvx twine check`
passes on the rebuilt wheel and sdist.
@PietjePuh PietjePuh changed the title fix: version drift and re-enable output validation for session tools fix: version drift, bypassed output validation, and tests shipped in the wheel Aug 29, 2026
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