npm, the version-layer edition

npm-check package version, the three numbers the same install returns

Most write-ups on this topic treat the version of a package as a single string. Install npm-check, run it, pick the upgrade, move on. That is fine when the package is a pure-JavaScript library whose only version lives in package.json. For packages that ship their own runtime, the story splits.

This page uses whatsapp-mcp-macos, a Swift-built MCP server distributed over npm, to show why five different version-check commands will happily return three different numbers from the exact same install, and which one is the only one that tells you what the model is actually talking to.

M
Matthew Diakonov
10 min read
4.8from npm + GitHub signals
Covers the runtime-version layer every version-check tutorial skips
Real numbers from the registry, from the repo, and from main.swift line 1115
Shows exactly which command answers which question, and when they disagree

Five commands, three answers

Here are the commands a typical developer reaches for when someone asks, “what version of this package are we on?” Every one of these is in the common playbook. npm-check in particular is recommended heavily. For a package like this one, none of the first four will ever surface the fifth.

npm view <pkg> version

Asks the npm registry over HTTPS. Returns the latest dist-tag from registry.npmjs.org. For whatsapp-mcp-macos that is 1.0.1, the last thing published. It never touches your disk, so it has no idea whether you even have the package installed.

npm list -g <pkg>

Reads package.json under `npm config get prefix` / lib/node_modules. For a global install, returns the version field baked into the tarball you actually have on disk. This may be ahead of (or behind) the latest registry tag.

npm outdated -g

Compares every local global's package.json version against the registry's latest dist-tag. Reports a pair (current, wanted, latest). Fast, but it is only ever a two-way diff between two strings from package.json.

npm-check -g -u

Third-party CLI (`npm install -g npm-check`). Same underlying data as `npm outdated` plus a prompt-driven updater. Does not know about any version string that is not in package.json.

MCP initialize handshake

The only command that talks to the running process. The host sends `initialize`, the server returns `{ serverInfo: { name, version } }`. For whatsapp-mcp-macos that returns 3.0.0, hardcoded in main.swift. No npm command can see this number.

npm viewnpm list -gnpm ls -g --depth=0npm outdatednpm-checknpx npm-check -gnpm info <pkg> versioncat package.json | jq .versioninitialize (MCP)stat $(which whatsapp-mcp)

The common advice fleet. Every one of these reads package.json or the registry. Exactly one of them reaches the running process, and it is not in the list.

what each channel is actually reading

npm view
npm list / npm-check
MCP initialize
whatsapp-mcp-macos
1.0.1
1.1.0
3.0.0

Three channels, three storage locations, three version strings. A version check is really a triplet, not a scalar, and only one of the three lives inside the compiled binary.

0commands claiming to check the version
0distinct numbers returned
0version the model actually sees
0line of main.swift where it lives

Four numbers that describe this specific install, pulled from a live registry query, a local cat package.json, and a grep version Sources/WhatsAppMCP/main.swift.

anchor fact

The runtime version lives on line 0 of main.swift

The version string that actually reaches the model is a hardcoded constant at Sources/WhatsAppMCP/main.swift:1115. Today it reads version: "3.0.0". That string is handed to the MCP Server constructor from the Swift SDK, which echoes it back to every client as part of the initialize response, under serverInfo.version.

The last npm publish pushed 1.0.1 on 2026-03-18. The current repo has "version": "1.1.0" in package.json. Neither of those has any effect on the string on line 1115. Three numbers. Three clocks. One package.

The hardcoded line that no `npm-check` can see

This is the actual declaration that produces the runtime version. It is in the source tree, and it is compiled into every release binary. Nothing in the npm CLI ever reads it.

Sources/WhatsAppMCP/main.swift

The package.json the on-disk commands read

When npm list -g, npm outdated, or npm-check want a version string for this package, they load this file from your global prefix. The field they care about is on line 3.

lib/node_modules/whatsapp-mcp-macos/package.json

The registry document `npm view` reads

And this is the registry response that npm view whatsapp-mcp-macos version distills into a single string. Note the dist-tag. That is a pointer maintained by whoever last ran npm publish, nothing more.

GET https://registry.npmjs.org/whatsapp-mcp-macos

What the five commands actually print

Same machine, same install, five queries, in order. Read to the end and note that the last one is the only one that speaks to the live process.

version check, five ways

Where the number actually comes from, per command

If you only read one table on this page, read this one. “Returns” is the literal string each command prints for a reference install. “Notes” is what that string is useful for, and where it will mislead you.

FeatureReturns, on a reference installWhat the command actually tells you
npm view whatsapp-mcp-macos version1.0.1 (the latest tag on the npm registry)Tells you: what is published. Does not tell you: what you have installed.
npm list -g whatsapp-mcp-macos1.1.0 (the package.json shipped in your tarball)Tells you: the identifier inside the file tree on your disk. Does not tell you: what the binary reports.
npm outdated -g whatsapp-mcp-macosCurrent: 1.1.0, Latest: 1.0.1Will show a negative diff when your local is ahead of the registry. Harmless but confusing; the same happens to anyone running an unreleased build from source.
npm-check -g (filtered to this pkg)Reports the same numbers as npm outdated, with a promptWill suggest 'pinning' or 'upgrading' based on package.json diffs. Never surfaces 3.0.0.
MCP initialize handshake3.0.0 (the serverInfo.version the server returns)The only version the host and the model will ever see. If you care about runtime behavior, this is the only number that matters.

How the runtime version actually reaches a client

Since no npm command surfaces the 3.0.0, it is worth seeing exactly how the handshake that does surface it travels. This is the sequence between a host (Claude Code, Cursor, any MCP client) and a freshly spawned whatsapp-mcp-macos child.

MCP initialize, the only channel that returns serverInfo.version

HostMCP childmain.swiftspawn whatsapp-mcp (stdio)Server(name, version: "3.0.0", ...){"method":"initialize","params":{...}}{"result":{"serverInfo":{"name":"WhatsAppMCP","version":"3.0.0"},...}}tool calls proceed

The version string is handed to the SDK Server constructor exactly once at process start, captured in its internal state, and echoed back at every initialize. No npm query ever touches this path.

Manifest-level version vs. runtime-level version

Lining them up makes it obvious why a single-number check is the wrong shape of question.

Featuremanifest-level version (package.json)runtime-level version (serverInfo.version)
Data sourceA string in package.json on disk (or on the registry)A string hardcoded in Sources/WhatsAppMCP/main.swift, returned over JSON-RPC by the running process
What a bump in this number meansSomeone edited `"version"` and ran `npm publish` or `npm install`Someone edited main.swift, ran `swift build`, and the host spawned a new child process
How fast a version change propagatesInstantly on the next `npm install` or registry query. Files on disk are overwritten.Only when the host spawns a fresh child. Long-running Claude Code / Cursor sessions hold the old version until restart.
Can `npm-check` detect a mismatch?Yes. That is its entire purpose.No. `npm-check` cannot read the MCP handshake. It will happily show `up to date` while a stale 3.0.0 binary is still running.
Where the number is physically storedlib/node_modules/whatsapp-mcp-macos/package.json, plus the registryCompiled into the Mach-O at .build/release/whatsapp-mcp (constant string inside the binary)
Command that reveals it`npm view`, `npm list`, `npm outdated`, `npm-check`Any MCP client initialize response, or `strings $(which whatsapp-mcp) | grep 3.0.0` as a crude on-disk probe

The reliable version-check recipe

If you care about this for a real reason, say you are debugging why a tool is behaving oddly, or your release notes claim a new tool exists and it is not showing up, this is the order that gives you a coherent answer.

1

Start with the question, not the command

Are you asking 'what is on the npm registry?', 'what is installed on my machine?', or 'what is actually running right now?' The answer determines which command you reach for. Generic articles jump straight to `npm view`, which is correct only for the first question.

2

Run the on-disk check

`npm list -g whatsapp-mcp-macos` for a global install, or `npm ls -g --depth=0 | grep whatsapp` for the short form. This reads the package.json at `$(npm config get prefix)/lib/node_modules/whatsapp-mcp-macos/package.json`. Expect it to sometimes be ahead of the registry if you installed from a branch or a local tarball.

3

Run the registry check, separately

`npm view whatsapp-mcp-macos version` for the latest dist-tag, or `npm view whatsapp-mcp-macos versions --json` for the full history. This is a network call. Do not assume it matches step 2.

4

Ask the running process

For an MCP server the host is already holding a handle to it. The `initialize` response contains `serverInfo.version`. In Claude Code, this surfaces in the MCP panel. From raw JSON-RPC, it is in the first response. 3.0.0 for this package, because that string is hardcoded at main.swift:1115.

5

Only now decide whether to 'update'

If step 2 matches step 3 matches step 4, you are in sync. If any two disagree, you know exactly which layer is drifted and which command to run to fix it. `npm install -g whatsapp-mcp-macos@latest` then a host restart is the usual resolution.

Running into a version mismatch you cannot explain?

Show me the three numbers and we will work out which clock is stuck. Fifteen minutes, live over Zoom.

Frequently asked questions

Why does `npm view whatsapp-mcp-macos version` return 1.0.1 when the GitHub repo's package.json says 1.1.0?

Because those two files are not the same file. `npm view` queries the npm registry over HTTPS and reads the `dist-tags.latest` entry, which only changes when someone runs `npm publish`. The package.json in the Git repo moves every time a developer bumps the version locally. There is always a window (sometimes hours, sometimes months) where the repo is ahead of the registry. For whatsapp-mcp-macos, the last `npm publish` was 1.0.1 on 2026-03-18; the repo has been at 1.1.0 since. Both numbers are correct for the question they answer; they are answering different questions.

What does the `npm-check` tool actually do that plain `npm outdated` does not?

The two tools read the same data. `npm outdated` loads every installed package's package.json, compares the version field against the registry's latest dist-tag, and prints a table. `npm-check` does the same read, then wraps an interactive prompt (`npm install -g npm-check` first). Where they differ is ergonomics: `npm-check` filters out unused packages using `require-package-name` heuristics, and it offers an `-u` flag to pick updates interactively. Neither tool reads anything beyond package.json strings, so neither can see a runtime version like `serverInfo.version`.

The MCP handshake reports version 3.0.0 but npm says 1.0.1. Which number is the real version?

They are both real and they are tracking different things. The npm tag tracks the publish cadence (what was uploaded to registry.npmjs.org, and in what order). The `serverInfo.version` string hardcoded at Sources/WhatsAppMCP/main.swift line 1115 tracks the MCP protocol surface the server exposes to clients. Those two clocks move independently. A routine `npm publish` bumps the first. Editing main.swift and rebuilding bumps the second. If you want to know 'what is the model talking to right now', 3.0.0 is the answer. If you want 'what can I pin in my install script', 1.0.1 is the answer.

I ran `npm-check -g` and it said everything is up to date, but the tools in Claude Code behave like an old version. What is going on?

`npm-check` is reading file-on-disk state. Claude Code (or any MCP host) keeps a running child process that was spawned from a specific binary at a specific moment. Updating the files on disk does not migrate the running child. The `serverInfo.version` the host sees is whatever was baked into the binary the running process is executing. Two places to look: (1) the host's MCP panel for the live `serverInfo.version`, (2) `ps -p <pid>` for the start time of the child. If start time predates your most recent `npm install`, the host is still running the old child. Restart the host, the host respawns the child, the new version takes effect.

Can I bypass npm and just check the version string embedded in the binary?

Yes, as a crude probe. `strings $(which whatsapp-mcp) | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$'` will scan the Mach-O for version-like constants. For this binary it reports both 3.0.0 (the serverInfo version) and the versions of every bundled Swift dependency (swift-sdk, MacosUseSDK). That is noisy but occasionally useful when you are debugging a binary that was installed by a package manager you do not control, or a binary someone else handed you.

Is 3.0.0 the MCP protocol version or the server's own version?

It is the server's own version. The MCP protocol version is negotiated separately and lives in the `protocolVersion` field of the initialize response, not in `serverInfo.version`. The MCP Swift SDK fills protocolVersion automatically based on which SDK release is linked. whatsapp-mcp-macos pins modelcontextprotocol/swift-sdk at 0.11.0, which speaks the corresponding MCP spec revision. `serverInfo.version` is a free-form string the server author picks. Here, the author bumped it to 3.0.0 when the tool surface stabilized around 11 tools, independent of npm tags.

Why does `npm outdated` sometimes report a 'current' that is higher than 'latest' for this package?

That happens when your locally installed package.json version is ahead of what was published. For whatsapp-mcp-macos, anyone running a freshly checked-out clone has `"version": "1.1.0"` on disk while the registry still shows 1.0.1 as latest. `npm outdated` prints Current: 1.1.0, Wanted: 1.0.1, Latest: 1.0.1. It is not a bug; you are just ahead of the published tag. The same output appears for anyone who installed from a GitHub tarball, a local tgz, or `npm link`.

Does `npm-check` respect my `.npmrc` registry settings, or will it always hit registry.npmjs.org?

It respects the registry the underlying `npm` client is configured with. Under the hood it shells out to `npm outdated` logic, which reads `registry=` from `.npmrc` (project, user, or global, in that precedence order). If you use a private registry or a proxy, `npm-check` will query the same endpoint. For whatsapp-mcp-macos, which is only published to registry.npmjs.org, point `npm-check` at a proxy and it will 404 on the package lookup.

What's the minimum reliable way to answer 'am I running the latest?' for this specific package?

Three commands in sequence, and no fewer. (1) `npm view whatsapp-mcp-macos version` → latest published. (2) `npm list -g whatsapp-mcp-macos` → what is on disk. (3) Have the MCP host re-issue `initialize` (usually by restarting the host) → what the binary actually reports. Matching all three is the only signal that 'latest' is a coherent claim. Matching only (1) and (2) leaves you open to the stale-child-process case. Matching only (2) and (3) leaves you open to a newer publish you have not pulled yet.

Will `npm-check` ever be able to read the MCP handshake version?

Not without a change in scope. `npm-check` is a package-manifest tool; its domain is package.json fields and the registry. Reading an MCP handshake requires spawning the child and speaking JSON-RPC to it, which is outside what a version checker signs up for. The closest generic tool would be an MCP-specific health check that runs `initialize` against every configured server and records `serverInfo.version`. That does not exist as a single command today; hosts like Claude Code surface the data in their MCP panel instead.