Migrate to mcp 2.0 and raise test coverage to 99% - #1
Open
FWDavide wants to merge 4 commits into
Open
Conversation
mcp 2.0 removed the low-level Server decorator API (@server.list_tools / @server.call_tool), so the unbounded mcp>=1.2 range meant every fresh `uvx basecamp-cli-mcp` resolved mcp 2.0 and died with AttributeError before serving a single request. Pin to <2 and bump to 0.7.2 so the published package installs again; the 2.0 migration follows separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mcp 2.0 removed the @server.list_tools() / @server.call_tool() decorators and replaced them with on_list_tools / on_call_tool callbacks passed to Server(), so port the two handlers over and require mcp>=2,<3. Behaviour is preserved: tools still advertise the raw JSON Schema from tools.json (types.Tool now takes input_schema rather than inputSchema), and a failing call is still reported to the client as an error result carrying the CLI's message plus stderr. That last part is now explicit - 1.x's decorator wrapper converted a raised exception into isError:true for us, and a raw on_call_tool handler would surface it as a JSON-RPC error instead, so the handler returns CallToolResult(is_error=True) itself. Same convention the SDK's own MCPServer uses. Add end-to-end protocol tests over in-memory streams. The old suite only covered filter_specs, so it passed while the server could not be constructed at all - the new tests do a real handshake, tools/list and tools/call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The mcp 2.0 break exposed how thin the suite was: it stayed green while the
server could not be constructed, because three modules had no tests at all and
the two that did stubbed out their own boundaries.
New coverage, roughly in order of what it protects:
- Runner._invoke (tests/test_runner_invoke.py) - the seam neither existing
suite touched. The runner tests overrode _invoke and the server tests faked
the runner, so nothing exercised argv reaching a process or the
{ok, data} / {ok: false, error} envelope becoming a value or a BasecampError.
Driven by a real shell stub, so subprocess, stderr and exit codes are real.
- setup_cmd (tests/test_setup_cmd.py) - the only code that writes outside the
repo. Covers the config merge, the abort paths that must leave a malformed
file untouched, backup creation, both tool-set choices, and the installer
flow. The last three releases all edited this file untested.
- generator (tests/test_generator.py) - the tree walk that produces the shipped
contract, via a subclass replaying canned CLI output. Worth having before the
pending regeneration against basecamp CLI 0.9.0.
- tools.json (tests/test_tools_contract.py) - the committed contract now has to
load, satisfy the runner's expectations, pass SEP-986 tool naming, and build
a server across all 255 entries.
- cli (tests/test_cli.py) - flag plumbing through to server.run.
Two tests deliberately pin surprising behaviour rather than assert what it
ought to be, so a future change surfaces it: alias dedupe drops sibling actions
that share a short description, and the todos-update workaround cannot unassign
everyone. Both are documented in place.
Also adds pytest-cov and coverage config so ============================= test session starts ==============================
platform darwin -- Python 3.12.13, pytest-9.0.3, pluggy-1.6.0
rootdir: /Users/davide/Developer/basecamp-cli-mcp
configfile: pyproject.toml
testpaths: tests
plugins: cov-7.1.0, anyio-4.13.0
collected 151 items
tests/test_cli.py ......... [ 5%]
tests/test_generator.py .......................... [ 23%]
tests/test_help_parser.py .................. [ 35%]
tests/test_runner.py ............................ [ 53%]
tests/test_runner_invoke.py .......... [ 60%]
tests/test_server.py ..... [ 63%]
tests/test_server_protocol.py ......... [ 69%]
tests/test_setup_cmd.py .................................... [ 93%]
tests/test_tools_contract.py .......... [100%]
================================ tests coverage ================================
______________ coverage: platform darwin, python 3.12.13-final-0 _______________
Name Stmts Miss Branch BrPart Cover
-----------------------------------------------------------------------
src/basecamp_cli_mcp/__init__.py 5 2 0 0 60%
src/basecamp_cli_mcp/cli.py 35 0 6 0 100%
src/basecamp_cli_mcp/generator.py 90 0 36 0 100%
src/basecamp_cli_mcp/help_parser.py 115 0 54 1 99%
src/basecamp_cli_mcp/runner.py 120 0 60 0 100%
src/basecamp_cli_mcp/server.py 59 0 12 0 100%
src/basecamp_cli_mcp/setup_cmd.py 101 0 32 0 100%
-----------------------------------------------------------------------
TOTAL 525 2 200 1 99%
============================= 151 passed in 2.54s ============================== works.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
.coverage slipped into the previous commit; it's regenerated on every run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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
Unblocks
uvx basecamp-cli-mcp, which had been dead on arrival since the mcp 2.0 release, then migrates properly to the 2.0 API and backfills the test suite that failed to catch the break.Four commits, each self-contained:
mcp<2(v0.7.2) — the unboundedmcp>=1.2range meant every freshuvxinstall resolved mcp 2.0, which removed the@server.list_tools()/@server.call_tool()decorators, and died withAttributeErrorbefore serving a request. Pin so the published package installs again.on_list_tools/on_call_toolcallbacks passed toServer(), and requiremcp>=2,<3. Behaviour is preserved: tools still advertise the raw JSON Schema fromtools.json(types.Toolnow takesinput_schemarather thaninputSchema), and a failing call is still reported as an error result carrying the CLI message plus stderr. That last part is now explicit: 1.x's decorator wrapper turned a raised exception intoisError: truefor us, whereas a rawon_call_toolhandler would surface it as a JSON-RPC error, so the handler returnsCallToolResult(is_error=True)itself..coverageis regenerated on every run.Why the test work
The mcp 2.0 break exposed how thin the suite was: it stayed green while the server could not be constructed at all, because three modules had no tests and the two that did stubbed out their own boundaries.
Runner._invoke(tests/test_runner_invoke.py) — the seam neither existing suite touched. Runner tests overrode_invoke; server tests faked the runner. Nothing exercised argv reaching a process, or the{ok, data}/{ok: false, error}envelope becoming a value or aBasecampError. Driven by a real shell stub, so subprocess, stderr, and exit codes are real.setup_cmd(tests/test_setup_cmd.py) — the only code that writes outside the repo, and untested through the last three releases. Covers the config merge, the abort paths that must leave a malformed file untouched, backup creation, both tool-set choices, and the installer flow.generator(tests/test_generator.py) — the tree walk that produces the shipped contract, via a subclass replaying canned CLI output. Worth having before the pending regeneration against basecamp CLI 0.9.0.tools.json(tests/test_tools_contract.py) — the committed contract now has to load, satisfy the runner's expectations, pass SEP-986 tool naming, and build a server across all 255 entries.cli(tests/test_cli.py) — flag plumbing through toserver.run.tests/test_server_protocol.py) — real handshake,tools/list,tools/call.Two tests deliberately pin surprising behaviour rather than assert what it ought to be, so a future change surfaces it: alias dedupe drops sibling actions that share a short description, and the todos-update workaround cannot unassign everyone. Both are documented in place.
Also adds
pytest-covand coverage config.Test plan
uv run pytest— 151 passed.🤖 Generated with Claude Code