Skip to content

fix(mcp): announce this package's version in serverInfo, not the SDK's - #9

Merged
bhyi4 merged 1 commit into
mainfrom
fix/mcp-serverinfo-own-version
Aug 26, 2026
Merged

fix(mcp): announce this package's version in serverInfo, not the SDK's#9
bhyi4 merged 1 commit into
mainfrom
fix/mcp-serverinfo-own-version

Conversation

@bhyi4

@bhyi4 bhyi4 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

What was wrong

FastMCP.__init__ takes no version, so Server.version stays None and the SDK fills serverInfo with its own release number:

server_version = self.version if self.version else pkg_version("mcp")   # mcp/server/lowlevel/server.py

Measured on this server before the fix — a real stdio handshake, not a read of the source:

serverInfo = {"name": "yeoul", "version": "1.27.2"}      ⊖ the installed `mcp` release

yeoul-mcp is 0.1.0. So the announced number tracked whatever SDK a machine happened to have and never tracked this package: the same yeoul build identifies itself differently on two machines, and a client using serverInfo to tell builds apart was reading the wrong package's version.

This is a FastMCP default rather than a local typo — the same shape was found on every unpatched FastMCP server on the machine where this was measured.

The fix

Set _mcp_server.version from the source __version__. Deliberately not importlib.metadata.version("yeoul-mcp"): under an editable install the dist-info can carry a stale number, which trades one wrong value for another that merely looks right.

The import is guarded because both entry paths are real — the console script and the tests import the package, while python yeoul_mcp/server.py runs the file with the package's parent off sys.path. Both arms were run; both now announce 0.1.0.

Test

A documented fix with no test is a promise. The existing mcp CI job imports the module and counts tools, which proves the module loads and never reads serverInfo at all. mcp/tests/test_serverinfo_version.py launches the server and reads the actual handshake, and is wired into CI.

It carries a negative control: a bare unpatched FastMCP must still show version is None. Without that arm the file could go green while asserting nothing.

Verified by reverting the fix: 4 of 6 checks turn red.

Two defects in that test, caught before pushing

Re-running it rather than trusting the first green turned up both:

  1. It depended on the working directory. sys.path.insert fixes the test process, not the child; python -c puts the caller's cwd on the child's path — so the handshake answered from mcp/ and died from the repo root, which is exactly where CI invokes it. The child's PYTHONPATH is now pinned to the package parent. Verified from four directories.
  2. A shrinking denominator hid the failure. When the handshake died, two checks vanished and the summary read 3/4 checks passed — which looks almost healthy. Checks that do not run now declare themselves, and the summary refuses any run whose checks are not all accounted for:
    SERVERINFO TESTS FAILED: 6 checks accounted for, 7 expected -- one vanished without declaring itself (verified by forcing the count).

Suites at this commit (from the repo root)

serverInfo contract 6/6
_run contract 8/8
gates 61/61
pre-publish guard 43 files, clean

`FastMCP.__init__` takes no `version`, so `Server.version` stays None and the SDK substitutes
its own release number:

    server_version = self.version if self.version else pkg_version("mcp")

Measured on this server before this change (2026-08-25, real stdio handshake, not a read of the
source): it announced `{"name": "yeoul", "version": "1.27.2"}` — the installed `mcp` release, not
yeoul-mcp's `0.1.0`. So the number moved with whatever SDK a machine happened to have and never
with this package: the same yeoul build identifies itself differently on two machines, and a
client using serverInfo to tell builds apart was reading the wrong package's version. This is a
FastMCP default, not a local typo — a separate measurement found the same shape on every
unpatched FastMCP server on this machine.

The fix sets `_mcp_server.version` from the source `__version__`. Deliberately NOT
`importlib.metadata.version("yeoul-mcp")`: under an editable install the dist-info can carry a
stale number, which would trade one wrong value for another that merely looks right.

The import is guarded because both entry paths are real: the console script and the tests import
the package, while `python yeoul_mcp/server.py` runs the file with the package's parent off
sys.path. Both arms were run and both now announce `0.1.0`.

Test, because a documented fix with no test is a promise. `mcp/tests/test_serverinfo_version.py`
launches the server and reads the actual handshake — the existing `mcp` CI job imports the module
and counts tools, which proves the module loads and never reads serverInfo at all. It carries a
negative control: a bare unpatched `FastMCP` must still show `version is None`, so the file cannot
go green while asserting nothing. Reverting the fix turns 4 of its 6 checks red (verified).

Two defects in that test were caught by re-running it before pushing rather than trusting the
first green:

  1. It depended on the working directory. `sys.path.insert` fixes the test process, not the
     child; `python -c` puts the CALLER's cwd on the child's path, so the handshake answered from
     `mcp/` and died from the repo root — which is exactly where CI invokes it. The child's
     PYTHONPATH is now pinned to the package parent. Verified from four directories.
  2. When the handshake died, two checks vanished and the summary read "3/4 checks passed" — a
     shrinking denominator that looks almost healthy. Checks that do not run now declare
     themselves (`skip`), and the summary refuses any run whose checks are not all accounted for:
     "6 checks accounted for, 7 expected — one vanished without declaring itself" (verified by
     forcing the count).

Suites at this commit, run from the repo root: serverInfo 6/6 · _run contract 8/8 · gates 61/61 ·
pre-publish 43 files clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bhyi4
bhyi4 merged commit 689ab81 into main Aug 26, 2026
4 checks passed
bhyi4 added a commit that referenced this pull request Aug 28, 2026
Until now no version number identified a build. `CHANGELOG.md` has said `[0.1.0] — unreleased`
since the first commit on 2026-07-21 while eighteen more went in; there are no tags and no releases.
That was nobody's decision — the number simply carried no weight, so nothing exposed its absence.

#9 gave it weight. Once the server announces its own version in `serverInfo`, that string is what a
client reads to tell builds apart — and it stayed `0.1.0` across the fix, so the server that reported
the SDK's version and the server that reports its own are indistinguishable by the very field the
fix repaired. Bumping is the other half of #9, not bookkeeping.

0.2.0, not 0.1.1: #10 changed behaviour for anyone using both tools — `arc-prereg` now refuses an
arc whose spec is blank, and every close writes a `Second key` line into its `_SUMMARY`.

Also closes the packaging door on the same defect. The version lives in two files: `pyproject.toml`
is what pip records and what a release is cut from, `yeoul_mcp/__init__.py` is what the server
speaks. Every existing check compared the server against `__init__.py`, so the two could drift with
the suite still green — measured: setting pyproject alone to 9.9.9 kept it at 6/6. A check now fails
when they disagree, verified by forcing the drift. That belongs in the suite rather than in a release
checklist nobody runs, because cutting a release is exactly when the two get touched.

The denominator guard added in #9 earned its place immediately: this commit added two checks and
bumped the expected count by one, and the run refused itself with "8 checks accounted for, 7
expected" rather than reporting a green 8/8.

Suites from the repo root: serverInfo 8/8 · _run contract 8/8 · gates 72/72 · pre-publish 43 files
clean.

Co-authored-by: Mother Seara <seara@bhyi4.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant