Files
mcp-windbg/scripts/check-version-consistency.ps1
T
Sven Scharmentke 90457306fe feat: ship a Claude Code plugin and host a marketplace for it (#88)
Installing mcp-windbg into Claude Code meant a `pip install` plus a
hand-written server registration with an env var. It becomes two lines:

```
/plugin marketplace add svnscha/mcp-windbg
/plugin install mcp-windbg@mcp-windbg
```

This repo hosts the marketplace itself, so there is no second repository
to keep in step.

## Why there is no install step at all

The plugin launches the server with `uvx`, pinned to the matching
release, so uv fetches it from PyPI on first use. That is what makes the
two lines *complete* rather than the last two of five. `server.json`
already declared `runtimeHint: uvx`, so this is how the package
presented itself anyway.

## What ships

- The ten tools, through the plugin's `.mcp.json`.
- **`/mcp-windbg:analyze-dump`** - crash-dump triage that separates fact
from inference and calls out missing symbols rather than reading meaning
into `module+0x1234`.
- **`/mcp-windbg:debug-remote`** - attaching to a user-mode debug
server, and the part that is easy to get wrong by hand: saying plainly
whether the target was left running or frozen.
- **`/mcp-windbg:kernel-debug`** - driving a live kernel target,
including the `g` / `wait_for_break` / `send_ctrl_break` handling from
1.1.0, and closing with `resume: true` so nobody leaves a machine
halted.
- **`/mcp-windbg:windbg-doctor`** - the first thing to run when nothing
works: checks platform, CDB, uv, the `uvx` round trip, and symbols, then
reports what to fix rather than one failure at a time.
- **`crash-analyst`** agent - hand it a dump and a question and it
investigates on its own, returning a verdict, the evidence behind it,
what it ruled out, and what to do next. Its `tools:` list uses the
scoped `mcp__plugin_mcp-windbg_mcp-windbg__*` names.
- A [reference
page](https://github.com/svnscha/mcp-windbg/blob/feat/claude-plugin/docs/reference/plugin.md)
in the docs site, plus plugin sections in the README, getting-started,
and client configuration.

Always-on cost is about 400 tokens; a skill or the agent costs more only
when it fires.

## Symbols are configured, without overriding yours

The plugin sets `_NT_SYMBOL_PATH` as a **default**, not a value:

```json
"_NT_SYMBOL_PATH": "${_NT_SYMBOL_PATH:-SRV*C:\Symbols*https://msdl.microsoft.com/download/symbols}"
```

Claude Code's `.mcp.json` expansion supports `${VAR:-default}`, so an
existing value wins and a symbol path someone tuned themselves is never
overwritten. Without this the out-of-the-box experience is a stack of
`module+0x1234` offsets, which is exactly the case where triage is
guesswork.

## One thing that cannot be declared

**Windows-only.** There is no manifest field for a platform constraint,
so installing on macOS or Linux loads the plugin and the server then
fails to find a debugger. Stated in the plugin README, the marketplace
description, and the reference page; `windbg-doctor` catches it at the
first check and stops there.

## Version drift, closed

release-please now bumps the plugin manifest, the marketplace entry, and
the `uvx` pin along with `pyproject.toml` and `server.json`, and
`check-version-consistency.ps1` checks all three. That is the guard that
a silently-broken `extra-files` entry does not ship - the exact failure
caught by hand during the 1.1.0 release.

The `uvx` pin survives the bump because release-please's `generic-json`
updater does a substring replace against its version regex rather than
overwriting the whole value, so `mcp-windbg@1.1.0` becomes
`mcp-windbg@1.2.0` and not a bare `1.2.0`.

## A bug fixed on the way

`check-version-consistency.ps1` looped with `foreach ($error in
$errors)`. `$error` is a **read-only PowerShell automatic variable**, so
the first time it had a mismatch to report it threw `Cannot overwrite
variable Error` instead of naming the files. The build still failed, so
this was never dangerous - just useless exactly when you needed it.
Verified both ways now: it names the mismatching file, and still exits
1.

## Dependencies

Floors raised to current releases with the major caps unchanged: `mcp`
2.1.1, `starlette` 1.6.0, `uvicorn` 0.52.4, and `pymdown-extensions`
11.0.2 for the docs build. `uv.lock` is regenerated; it had been
stranded at 1.0.1 since the 1.1.0 release, because release-please has no
updater for it. A `uv lock --check` step in CI would catch that drift
and is worth a follow-up.

## Verification

- `claude plugin validate` passes for both the marketplace manifest and
the plugin manifest.
- Full suite green against the upgraded dependencies: 99 passed, 2
skipped (the kernel scenarios, no target attached). Live CDB scenarios
ran.
- `validate-server-schema.py` passes; `uv lock --check` reports the lock
in sync.
- The `.mcp.json` snippet on the reference page is asserted to parse and
to equal the real file, after it shipped with a single-backslash
`C:\Symbols` that did neither.
- The earlier end-to-end check (`claude plugin marketplace add` ->
`install` -> `details`, and a headless session seeing the tools through
`uvx` with nothing pre-installed) was run when the plugin had two skills
and no agent, so it predates the current surface.

## Note

You already have a `svnscha` marketplace pointing at
`svnscha/claude-plugins`. This adds a second, repo-local one. If you
would rather have the plugin listed there instead, say so - the plugin
directory moves as-is and only the marketplace manifest changes.
2026-08-27 20:08:07 +02:00

112 lines
4.5 KiB
PowerShell

#!/usr/bin/env pwsh
<#
.SYNOPSIS
Checks version consistency between pyproject.toml, server.json, and CHANGELOG.md
.DESCRIPTION
This script extracts version information from pyproject.toml, server.json, and CHANGELOG.md
and verifies that all versions are consistent across these files.
.EXAMPLE
.\check-version-consistency.ps1
#>
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
try {
Write-Host "Checking version consistency..." -ForegroundColor Cyan
# Extract version from pyproject.toml
if (-not (Test-Path "pyproject.toml")) {
throw "pyproject.toml not found in current directory"
}
$pyprojectMatch = Select-String -Path "pyproject.toml" -Pattern 'version = "([^"]+)"'
if (-not $pyprojectMatch) {
throw "Could not find version in pyproject.toml"
}
$PYPROJECT_VERSION = $pyprojectMatch.Matches[0].Groups[1].Value
Write-Host "INFO: pyproject.toml version: $PYPROJECT_VERSION" -ForegroundColor Green
# Extract version from server.json
if (-not (Test-Path "server.json")) {
throw "server.json not found in current directory"
}
$serverJsonContent = Get-Content "server.json" -Raw | ConvertFrom-Json
$SERVER_VERSION = $serverJsonContent.version
$PACKAGE_VERSION = $serverJsonContent.packages[0].version
Write-Host "INFO: server.json version: $SERVER_VERSION" -ForegroundColor Green
Write-Host "INFO: server.json package version: $PACKAGE_VERSION" -ForegroundColor Green
# Extract version from CHANGELOG.md
if (-not (Test-Path "CHANGELOG.md")) {
throw "CHANGELOG.md not found in current directory"
}
$changelogMatch = Select-String -Path "CHANGELOG.md" -Pattern '## \[([^\]]+)\]' | Select-Object -First 1
if (-not $changelogMatch) {
throw "Could not find version in CHANGELOG.md"
}
$CHANGELOG_VERSION = $changelogMatch.Matches[0].Groups[1].Value
Write-Host "INFO: CHANGELOG.md version: $CHANGELOG_VERSION" -ForegroundColor Green
# Extract versions from the Claude plugin + marketplace manifests. These are
# kept in step by release-please extra-files entries; checking them here is
# what catches an entry that silently stopped matching.
$PLUGIN_VERSION = (Get-Content "plugins/mcp-windbg/.claude-plugin/plugin.json" -Raw | ConvertFrom-Json).version
$MARKETPLACE_VERSION = (Get-Content ".claude-plugin/marketplace.json" -Raw | ConvertFrom-Json).plugins[0].version
$mcpArgs = (Get-Content "plugins/mcp-windbg/.mcp.json" -Raw | ConvertFrom-Json).mcpServers.'mcp-windbg'.args
$PINNED_VERSION = ($mcpArgs[0] -split '@')[-1]
Write-Host "INFO: plugin.json version: $PLUGIN_VERSION" -ForegroundColor Green
Write-Host "INFO: marketplace.json plugin version: $MARKETPLACE_VERSION" -ForegroundColor Green
Write-Host "INFO: plugin .mcp.json pinned server: $PINNED_VERSION" -ForegroundColor Green
# Check if all versions match
$errors = @()
if ($PYPROJECT_VERSION -ne $PLUGIN_VERSION) {
$errors += "Version mismatch: pyproject.toml ($PYPROJECT_VERSION) != plugin.json ($PLUGIN_VERSION)"
}
if ($PYPROJECT_VERSION -ne $MARKETPLACE_VERSION) {
$errors += "Version mismatch: pyproject.toml ($PYPROJECT_VERSION) != marketplace.json ($MARKETPLACE_VERSION)"
}
if ($PYPROJECT_VERSION -ne $PINNED_VERSION) {
$errors += "Version mismatch: pyproject.toml ($PYPROJECT_VERSION) != plugin .mcp.json pin ($PINNED_VERSION)"
}
if ($PYPROJECT_VERSION -ne $SERVER_VERSION) {
$errors += "Version mismatch: pyproject.toml ($PYPROJECT_VERSION) != server.json ($SERVER_VERSION)"
}
if ($PYPROJECT_VERSION -ne $PACKAGE_VERSION) {
$errors += "Version mismatch: pyproject.toml ($PYPROJECT_VERSION) != server.json package ($PACKAGE_VERSION)"
}
if ($PYPROJECT_VERSION -ne $CHANGELOG_VERSION) {
$errors += "Version mismatch: pyproject.toml ($PYPROJECT_VERSION) != CHANGELOG.md ($CHANGELOG_VERSION)"
}
if ($errors.Count -gt 0) {
# Not $error: that is a read-only PowerShell automatic variable, and
# assigning it threw before any mismatch could be reported.
foreach ($mismatch in $errors) {
Write-Host "ERROR: $mismatch" -ForegroundColor Red
}
exit 1
}
Write-Host "`nAll versions are consistent: $PYPROJECT_VERSION" -ForegroundColor Green
exit 0
}
catch {
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
exit 1
}