Skip to content

feat: add clipboard_version MCP tool for runtime version reporting#141

Merged
cmeans merged 4 commits into
mainfrom
feat/clipboard-version-tool
May 9, 2026
Merged

feat: add clipboard_version MCP tool for runtime version reporting#141
cmeans merged 4 commits into
mainfrom
feat/clipboard-version-tool

Conversation

@cmeans-claude-dev
Copy link
Copy Markdown
Contributor

@cmeans-claude-dev cmeans-claude-dev Bot commented May 9, 2026

Summary

  • Adds a new MCP tool clipboard_version that returns {"name": "mcp-clipboard", "version": "<x.y.z>"} from importlib.metadata via the existing __version__ symbol.
  • 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 build is serving the call. CLI flags --version / --check need shell access, which an in-host agent doesn't have. This closes that gap.
  • Concrete motivator: the in-progress mcp-clipboard Windows e2e suite couldn't populate mcp_clipboard_version in 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.tool with readOnlyHint: 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 ### Added in [Unreleased].

Test plan

  • uv run pytest — full local suite (620 passed locally).
  • Build the wheel and confirm instructions/clipboard_version.md is included.
  • In a Windows MCP host, call clipboard_version and confirm it returns the installed version string.
  • Update the e2e suite's pre-flight step 2 to call clipboard_version (separate change, lands after this merges).

🤖 Generated with Claude Code

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>
@github-actions github-actions Bot added the Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA label May 9, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot added Ready for QA Dev work complete — QA can begin review and removed Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA labels May 9, 2026
Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

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

Need updates to the README

@cmeans-claude-dev cmeans-claude-dev Bot added the QA Failed QA found issues — needs dev attention label May 9, 2026
@cmeans cmeans removed the Ready for QA Dev work complete — QA can begin review label May 9, 2026
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>
@cmeans-claude-dev cmeans-claude-dev Bot added Ready for QA Dev work complete — QA can begin review and removed QA Failed QA found issues — needs dev attention labels May 9, 2026
@cmeans-claude-dev
Copy link
Copy Markdown
Contributor Author

Updated. Added a clipboard_version row to the Tools table in README.md describing the new tool (returns {"name": "mcp-clipboard", "version": "<x.y.z>"}, framed as a diagnostic for hosts that don't expose serverInfo and for test-harness version recording), and added clipboard_version.md to the instructions tree under the project-layout section. Pushed as 2b0b469. Ready for re-QA.

@github-actions github-actions Bot added Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA and removed Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA labels May 9, 2026
@cmeans cmeans added QA Active QA is actively reviewing; Dev should not push changes and removed Ready for QA Dev work complete — QA can begin review labels May 9, 2026
Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

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

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.tool registration follows the established pattern (annotations dict with # type: ignore[arg-type], readOnlyHint: True / destructiveHint: False / idempotentHint: True / openWorldHint: False); function signature returns dict[str, str].
  • src/mcp_clipboard/instructions/clipboard_version.md — concise, names the fallback 0.0.0+dev behavior matching src/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) and README.md:397 (tree) both updated.
  • pyproject.toml:52 artifacts = ["src/mcp_clipboard/instructions/*.md", ...] glob captures the new file automatically — no packaging change needed.
  • Local verification on 2b0b469: uv run pytest -q620 passed, 19 deselected, 5 xfailed in 3.75s. The 19 deselected are the @pytest.mark.integration X11 suite (covered by CI's integration-x11 job, which is green). uv run ruff check src/ tests/ and uv run ruff format --check src/ tests/ clean. uv run mypy src/ clean.
  • uv build --wheel produces mcp_clipboard-2.6.1-py3-none-any.whl; unzip -l confirms mcp_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_version returned 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 2b0b469 Linux 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.

@cmeans
Copy link
Copy Markdown
Owner

cmeans commented May 9, 2026

Audit: applying QA Failed per the round 1 review above. One substantive (CLAUDE.md tool-count drift to 7) plus two observations (server.md mention + sync-vs-async on the new tool) await Dev.

@cmeans cmeans added QA Failed QA found issues — needs dev attention and removed QA Active QA is actively reviewing; Dev should not push changes labels May 9, 2026
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>
@github-actions github-actions Bot added Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA and removed QA Failed QA found issues — needs dev attention labels May 9, 2026
@cmeans cmeans added the Ready for QA Dev work complete — QA can begin review label May 9, 2026
@cmeans
Copy link
Copy Markdown
Owner

cmeans commented May 9, 2026

Round 2 — addressing QA round 1 findings

Pushed as e21cbcb.

F1 (substantive) — fixed. CLAUDE.md:49 bumped from "exposing 6 tools" to "exposing 7 tools" and a new bullet for clipboard_version() added underneath, matching the shape of the existing six. Describes the diagnostic role (host-serverInfo gap, test-harness version recording) so the doc tracks the actual surface area. Edit is local to that block.

F2 (observation) — fixed. Added a short paragraph to src/mcp_clipboard/instructions/server.md describing when to call clipboard_version. Wording: "Use clipboard_version to record or report which build of mcp-clipboard is serving the current MCP session. Diagnostic only; useful when the user asks which version is installed, or when a test harness needs to capture the running version into a result entry." Sits at the end of the file alongside the other tool-use notes; the per-tool instruction file in instructions/clipboard_version.md still carries the full description for tool-listing UIs.

F3 (observation) — fixed. Switched clipboard_version from def to async def in src/mcp_clipboard/server.py:634. Pattern now consistent with the other six @mcp.tool entries in the file. Body is still a synchronous dict return so behavior is unchanged; zero overhead. Test updated to await the coroutine and to be marked @pytest.mark.asyncio.

Verification. uv run pytest -q → 620 passed, 19 deselected, 5 xfailed. Lint and type-check still clean. README, CHANGELOG, and tree listing already covered in round 1's R2 commit (2b0b469); no further changes needed there.

Ready for re-QA on e21cbcb.

@github-actions github-actions Bot removed the Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA label May 9, 2026
@cmeans cmeans added QA Active QA is actively reviewing; Dev should not push changes and removed Ready for QA Dev work complete — QA can begin review labels May 9, 2026
Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

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

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 (67) 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 (defasync 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 -q620 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 e21cbcb after 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 Gate StatusContext is PENDING (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.

@cmeans
Copy link
Copy Markdown
Owner

cmeans commented May 9, 2026

Audit: applying QA Failed per Round 2 review above. R1's three findings all resolved; one trivial new nit on CLAUDE.md:56 (em-dash separator to match siblings).

@cmeans cmeans added QA Failed QA found issues — needs dev attention and removed QA Active QA is actively reviewing; Dev should not push changes labels May 9, 2026
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>
@cmeans cmeans added Ready for QA Dev work complete — QA can begin review and removed QA Failed QA found issues — needs dev attention labels May 9, 2026
@cmeans
Copy link
Copy Markdown
Owner

cmeans commented May 9, 2026

Round 3 — F1-R2 nit fixed

Pushed as the new head. Changed CLAUDE.md:56 from clipboard_version()`. Returns the running… to clipboard_version()` — Returns the running… so the new bullet uses the same separator as the other six tool bullets at lines 50-55. Three-character edit; nothing else touched.

Ready for re-QA.

@github-actions github-actions Bot added Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA Ready for QA Dev work complete — QA can begin review and removed Ready for QA Dev work complete — QA can begin review Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA labels May 9, 2026
@cmeans cmeans added QA Active QA is actively reviewing; Dev should not push changes and removed Ready for QA Dev work complete — QA can begin review labels May 9, 2026
Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

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

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 -q620 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_version returned {"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). Only QA Gate StatusContext is PENDING — 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 ### Added Unreleased 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).

@cmeans
Copy link
Copy Markdown
Owner

cmeans commented May 9, 2026

Audit: applying Ready for QA Signoff per Round 3 review above. Three rounds, zero open findings, full re-verification on 8e6da6f. Awaiting maintainer QA Approved gated on the Windows host verification per test plan checkbox 3.

@cmeans cmeans added Ready for QA Signoff QA passed — ready for maintainer final review and merge and removed QA Active QA is actively reviewing; Dev should not push changes labels May 9, 2026
Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

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

LGTM

@cmeans cmeans added QA Approved Manual QA testing completed and passed and removed Ready for QA Signoff QA passed — ready for maintainer final review and merge labels May 9, 2026
@cmeans cmeans merged commit eba45ed into main May 9, 2026
39 checks passed
@cmeans cmeans deleted the feat/clipboard-version-tool branch May 9, 2026 16:01
cmeans-claude-dev Bot added a commit that referenced this pull request May 9, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

QA Approved Manual QA testing completed and passed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant