feat: add clipboard_version MCP tool for runtime version reporting#141
Conversation
Adds a 7th MCP tool, clipboard_version, that returns the running
package version as a dict {"name": "mcp-clipboard", "version": "..."}
sourced from importlib.metadata via the existing mcp_clipboard
__version__ symbol.
Why: hosts that don't expose the standard MCP serverInfo block to
the model (notably Claude Desktop on Windows) leave the agent with
no reliable way to determine which mcp-clipboard build is serving
the call. The CLI flags --version / --check require shell access,
which an in-host agent does not have. Test harnesses driven from
inside an MCP session also need the version recorded in per-test
result entries.
Adds the instruction file at instructions/clipboard_version.md so
the description is part of the shipped wheel and visible in tool
listings. Two unit tests: one verifies the tool returns the live
__version__, one verifies the instruction file ships and loads.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Documents the new clipboard_version MCP tool added in this PR. Adds a row to the Tools table describing it as a diagnostic that returns the running package version, with the same use-case framing as the PR body (host serverInfo gap, test harness version recording). Adds clipboard_version.md to the instructions tree in the project layout section. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Updated. Added a |
cmeans
left a comment
There was a problem hiding this comment.
QA Round 1 — QA Failed
Reviewed at head 2b0b469. Code itself is small and clean; verification end-to-end is green. One substantive doc-drift finding plus two observations.
What's clean
src/mcp_clipboard/server.py:622-634—@mcp.toolregistration follows the established pattern (annotations dict with# type: ignore[arg-type],readOnlyHint: True / destructiveHint: False / idempotentHint: True / openWorldHint: False); function signature returnsdict[str, str].src/mcp_clipboard/instructions/clipboard_version.md— concise, names the fallback0.0.0+devbehavior matchingsrc/mcp_clipboard/__init__.py:6-8.tests/test_server.py:4313-4326— two tests cover (a) live__version__round-trip and (b) instruction file ships and loads; not tautological since they verify shape + name + the version sourcing.CHANGELOG.md:7-13— entry under## [Unreleased]/### Added, per project convention.README.md:90(tools table) andREADME.md:397(tree) both updated.pyproject.toml:52artifacts = ["src/mcp_clipboard/instructions/*.md", ...]glob captures the new file automatically — no packaging change needed.- Local verification on
2b0b469:uv run pytest -q→ 620 passed, 19 deselected, 5 xfailed in 3.75s. The 19 deselected are the@pytest.mark.integrationX11 suite (covered by CI'sintegration-x11job, which is green).uv run ruff check src/ tests/anduv run ruff format --check src/ tests/clean.uv run mypy src/clean. uv build --wheelproducesmcp_clipboard-2.6.1-py3-none-any.whl;unzip -lconfirmsmcp_clipboard/instructions/clipboard_version.md(624 bytes) ships in the wheel — checkbox 2 verified.- End-to-end MCP call via
claude -p --mcp-config ... --allowedTools mcp__clipboard-qa__clipboard_versionreturned exactly{"name":"mcp-clipboard","version":"2.6.1"}— round-trip matches the documented contract. - All 11 actual CI checks green (lint, typecheck, test 3.11/3.12/3.13, integration-x11, version-sync, validate-server-json, codecov/patch).
Findings
F1 — substantive — CLAUDE.md tool count drift. CLAUDE.md:49 reads exposing 6 tools: and lines 50–55 enumerate the six existing tools. Adding clipboard_version makes both stale. Per project history this line is religiously updated each time a tool is added (3 → 4 → 5 → 6 across prior PRs). Bump to 7 tools and add a bullet for clipboard_version() describing the diagnostic role. Doc drift is fixed in the same PR cycle on this project.
F2 — observation — instructions/server.md doesn't mention clipboard_version. That file is the model-facing server instruction loaded by FastMCP and walks through when to use each of the six prior tools. The new diagnostic doesn't fit naturally in any existing paragraph, but a brief sentence — e.g. "Use clipboard_version to record or report which build is serving the current MCP session — diagnostic only" — would aid model-side discoverability. The tool's own instruction file already covers this for tool-listing UIs, so this is optional. Confirm intent or add a one-liner.
F3 — observation — clipboard_version is def, while the other six @mcp.tool functions in the file are async def. Functionally equivalent here (FastMCP accepts both, and the body is a synchronous dict return — confirmed working via the live MCP call above). Worth flagging because it's the only sync @mcp.tool in src/mcp_clipboard/server.py and pattern consistency in a 600+ line file has real reader value. Either is defensible; if def is intentional (no I/O), confirm and I'll close this. Otherwise, async def clipboard_version() aligns with the rest of the file at zero cost.
Outside scope (not findings)
- Test plan checkbox 3 (Windows MCP host call) is not exercisable from this Linux QA session; the
2b0b469Linux MCP call passed but doesn't substitute for the Windows-host motivation behind the PR. Leaving unchecked for Dev/maintainer. - Test plan checkbox 4 (e2e suite pre-flight wiring) is explicitly a separate PR.
Verdict
QA Failed — fix F1 (substantive). F2 and F3 need either a fix or a "won't-fix" with rationale before Ready for QA Signoff is on the table.
After Dev addresses, no new commit is needed for F1 if CLAUDE.md is the only change touched (small text edit) — but a fresh commit is cleaner since we're already mid-PR. Re-QA on the new head.
|
Audit: applying |
F1 (substantive): CLAUDE.md "exposing 6 tools" was stale. Bump to 7
and add a clipboard_version bullet describing the diagnostic role
and the host-serverInfo gap that motivates it. Same shape as the
existing six bullets.
F2 (observation): instructions/server.md walks the model through
when to use each tool. Adds a brief paragraph for clipboard_version
("Use to record or report which build is serving the current MCP
session") so model-side discoverability does not depend solely on
the per-tool instruction file shown in tool-listing UIs.
F3 (observation): clipboard_version was the only sync def among
seven @mcp.tool entries in server.py. Switched to async def for
pattern consistency. The body is still a synchronous dict return;
zero behavioral change. Test updated to await the now-coroutine.
Full local suite: 620 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 2 — addressing QA round 1 findingsPushed as F1 (substantive) — fixed. F2 (observation) — fixed. Added a short paragraph to F3 (observation) — fixed. Switched Verification. Ready for re-QA on |
cmeans
left a comment
There was a problem hiding this comment.
QA Round 2 — QA Failed
Reviewed at head e21cbcb. All three Round 1 findings addressed cleanly. One small new nit on the CLAUDE.md bullet introduced in this round.
Round 1 findings — status
| R1 finding | Resolution |
|---|---|
| F1 substantive — CLAUDE.md tool count drift | Fixed at CLAUDE.md:49 (6 → 7) and a new bullet for clipboard_version() at line 56. |
F2 observation — instructions/server.md mention |
Fixed at instructions/server.md:38-41 with a clear "Diagnostic only; useful when..." paragraph. |
F3 observation — sync def vs async def |
Fixed at src/mcp_clipboard/server.py:633 (def → async def); test at tests/test_server.py:4313-4315 correctly gained @pytest.mark.asyncio + await. Confirmed against project convention: every async test in tests/test_server.py uses the explicit marker (271/271), so the marker addition is correct even with asyncio_mode = "auto". |
Round 2 verification
uv run pytest -q→ 620 passed, 19 deselected, 5 xfailed in 3.85s (same shape as R1; Dev intentionally didn't add tests since the existing two cover both the contract and the instruction-file load path).uv run ruff check src/ tests/clean.uv run ruff format --check src/ tests/clean.uv run mypy src/clean.- Live MCP call (re-run on
e21cbcbafter the async conversion) returned{"name":"mcp-clipboard","version":"2.6.1"}— async dispatch works end-to-end. - All 11 actual CI checks green; only
QA GateStatusContext isPENDING(expected — that's the post-approval gate).
Findings
F1-R2 — nit — CLAUDE.md:56 bullet separator inconsistent with siblings. The new bullet reads:
clipboard_version(). Returns the running mcp-clipboard package version…
while lines 50–55 all use — (em-dash + spaces) between signature and description:
clipboard_copy(content)— Writes text to the system clipboard.
Three-character fix: change (). Returns the running… → () — Returns the running… on CLAUDE.md:56. Trivial but the new bullet is the odd one out in a list of seven.
Verdict
QA Failed — single-line nit on CLAUDE.md:56. Re-QA on the new head; expect a quick Round 3.
|
Audit: applying |
QA round 2 nit: the new clipboard_version bullet at CLAUDE.md:56 used a period separator while siblings 50-55 all use " — " between signature and description. Three-character fix to match the existing in-file pattern. No content change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 3 — F1-R2 nit fixedPushed as the new head. Changed Ready for re-QA. |
cmeans
left a comment
There was a problem hiding this comment.
QA Round 3 — Ready for QA Signoff
Reviewed at head 8e6da6f. Three rounds total — clean PR.
Round 2 finding — status
| R2 finding | Resolution |
|---|---|
F1-R2 nit — CLAUDE.md:56 separator |
Fixed at CLAUDE.md:56 ((). → () —). New bullet now matches the — pattern of all six sibling bullets at lines 50–55. Pure punctuation diff, no other changes. |
Round 3 verification (re-run on 8e6da6f in this session)
uv run pytest -q→ 620 passed, 19 deselected, 5 xfailed in 3.88s.uv run ruff check src/ tests/clean.uv run ruff format --check src/ tests/clean.uv run mypy src/clean.- Live MCP call:
claude -p ... --allowedTools mcp__clipboard-qa__clipboard_versionreturned{"name":"mcp-clipboard","version":"2.6.1"}— async dispatch and instruction-loading both working end-to-end. - All 11 actual CI checks
SUCCESS(lint, typecheck, test 3.11/3.12/3.13, integration-x11, version-sync, validate-server-json, on-push label automation, codecov/patch). OnlyQA GateStatusContext isPENDING— expected, that's the post-approval gate.
Round-by-round history
| Round | Head | Outcome |
|---|---|---|
| R1 | 2b0b469 |
QA Failed — F1 substantive (CLAUDE.md tool count drift), F2 observation (server.md mention), F3 observation (sync def vs async def). |
| R2 | e21cbcb |
QA Failed — all R1 findings resolved; one new nit F1-R2 on CLAUDE.md bullet separator (. vs —). |
| R3 | 8e6da6f |
Ready for QA Signoff — F1-R2 fixed; zero open findings. |
Cross-link reminders for post-merge
- Test plan checkbox 3 (Windows MCP host call) is the maintainer-side gate before
QA Approved— Linux QA can't substitute for the Windows host that motivated this PR. The QEMU + Claude Desktop on Windows lever still applies, same as #138's R6. - Test plan checkbox 4 — e2e suite pre-flight wiring is explicitly a separate PR; track separately.
- This is a
### AddedUnreleased entry — when the next release-PR aggregates Unreleased, the Added category will require a minor bump (per semver §6 and v2.6.0 precedent), not a patch.
Awaiting maintainer QA Approved (gated on the Windows host verification per checkbox 3).
|
Audit: applying |
F1 (substantive) -- doc drift across CLAUDE.md / README.md / SECURITY.md described the Windows backend as "PowerShell" in three places. Updated each to reflect the Phase 1 state: pywin32 for text formats (text/plain, text/html, text/rtf, image/svg+xml) via the new clipboard_win32 module; PowerShell still in the path for image read/write until Phase 2 (#147). - CLAUDE.md: extended the clipboard.py architecture bullet to name pywin32-via-clipboard_win32 alongside the Linux/macOS backends, and added a clipboard_win32.py architecture bullet documenting the wrapper module's role. - README.md "How It Works" step 3: split the Linux/macOS pipe- through-subprocess phrasing from the Windows direct-Win32-API phrasing, and named CF_UNICODETEXT / HTML Format / Rich Text Format / image/svg+xml as the formats the Windows path uses. - README.md "How It Works" step 4: noted the image write path is still on PowerShell, with a "transitioning to pywin32 in a follow-up PR" parenthetical. - SECURITY.md scope bullet: kept PowerShell in scope for the image paths, added pywin32-based code paths to scope explicitly. F2 (substantive) -- duplicate `### Added` blocks under `## [Unreleased]` in CHANGELOG.md (one from #141 for clipboard_version, one from this PR for the CI matrix + integration-windows job). Per Keep-a-Changelog each category appears at most once per release. Merged into a single `### Added` block keeping all three bullets in the order they were added; `### Changed` and `### Fixed` sections unchanged in their canonical Keep-a-Changelog order. F3 (observation) -- silent `except Exception: pass` in `clipboard_win32._format_name` swallowed `GetClipboardFormatName` errors without a trace. Added `logger.debug(...)` in the except branch citing the format ID and the exception so a future debugging session can locate the unresolved-format-name path. F4 (observation) -- comment at `clipboard_win32.py:213-215` promised NUL stripping but the code was a bare `return str(data)`. Updated the comment to describe what the code actually does: defensive str-coercion for unexpected return shapes from pywin32 (memoryview, int) on unusual custom formats. Added `logger.debug(...)` so the unexpected pywin32 shape is observable when it happens, instead of being masked by the str() rendering. Phase 2 (image paths) tracked at #147. Verification: pytest 650 passed, 1 skipped, 19 deselected, 5 xfailed locally. clipboard_win32.py 100% coverage maintained. Lint, format, mypy clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
clipboard_versionthat returns{"name": "mcp-clipboard", "version": "<x.y.z>"}fromimportlib.metadatavia the existing__version__symbol.serverInfoblock to the model (notably Claude Desktop on Windows) leave the agent with no reliable way to determine which build is serving the call. CLI flags--version/--checkneed shell access, which an in-host agent doesn't have. This closes that gap.mcp_clipboard_versionin per-test result entries because no in-session probe was available. With this tool, the suite's pre-flight (§0 step 2) becomes a single tool call on every platform.What's in
src/mcp_clipboard/server.py— registers the 7th@mcp.toolwithreadOnlyHint: True.src/mcp_clipboard/instructions/clipboard_version.md— the description shipped in the wheel and shown in tool listings.tests/test_server.py— two tests: tool returns the live__version__, and the instruction file ships and loads.CHANGELOG.md— entry under### Addedin[Unreleased].Test plan
uv run pytest— full local suite (620 passed locally).instructions/clipboard_version.mdis included.clipboard_versionand confirm it returns the installed version string.clipboard_version(separate change, lands after this merges).🤖 Generated with Claude Code