Skip to content

Commit 35a1854

Browse files
committed
fix: report the real package version in the MCP handshake
createServer hardcoded version 0.1.0, so every client saw that in serverInfo while the package shipped 0.2.1. package.json is already the single source of truth for every manifest - the entry point now reads it too, through createRequire, which resolves the same from dist/index.js and src/index.ts. The new binary test runs the built artefact, so it also covers the one way the runtime read could break: package.json not being reachable from the packed layout.
1 parent fa358a3 commit 35a1854

2 files changed

Lines changed: 28 additions & 2 deletions

File tree

src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env node
22
import { realpathSync } from 'node:fs';
3+
import { createRequire } from 'node:module';
34
import { pathToFileURL } from 'node:url';
45
import { McpServer } from '@modelcontextprotocol/server';
56
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
@@ -17,8 +18,14 @@ import { registerRawTool } from './tools/raw.js';
1718
import type { ToolContext } from './tools/registry.js';
1819
import { registerSystemTools } from './tools/system.js';
1920

21+
// Read rather than repeated as a literal: package.json is the only place a
22+
// version is edited by hand, and a hardcoded one here went stale silently -
23+
// the handshake still reported 0.1.0 two releases later. `../package.json`
24+
// resolves from dist/index.js and src/index.ts alike, and npm always packs it.
25+
const VERSION = createRequire(import.meta.url)('../package.json').version as string;
26+
2027
export function createServer(ctx: ToolContext): McpServer {
21-
const server = new McpServer({ name: 'keenetic', version: '0.1.0' });
28+
const server = new McpServer({ name: 'keenetic', version: VERSION });
2229
registerSystemTools(server, ctx);
2330
registerDeviceTools(server, ctx);
2431
registerInterfaceTools(server, ctx);

tests/binary.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { spawn } from 'node:child_process';
2-
import { mkdtemp, symlink } from 'node:fs/promises';
2+
import { mkdtemp, readFile, symlink } from 'node:fs/promises';
33
import { tmpdir } from 'node:os';
44
import { join } from 'node:path';
55
import { fileURLToPath } from 'node:url';
@@ -59,6 +59,25 @@ describe('the built binary', () => {
5959
expect(result.stdout).toContain('keenetic');
6060
});
6161

62+
// The version in the handshake is what a client displays and what a bug
63+
// report quotes, so it has to be the published one. It was a literal in
64+
// src/index.ts and sat at 0.1.0 while the package shipped 0.2.1. Run against
65+
// dist because that also proves package.json is reachable from the built
66+
// layout, which is the only place the runtime read can go wrong.
67+
it('reports the package version in serverInfo', async () => {
68+
const pkg = JSON.parse(
69+
await readFile(new URL('../package.json', import.meta.url), 'utf8')
70+
) as { version: string };
71+
72+
const result = await run(DIST, `${INITIALIZE}\n`, CONFIGURED);
73+
const response = JSON.parse(result.stdout.split('\n')[0] ?? '{}') as {
74+
result?: { serverInfo?: { name?: string; version?: string } };
75+
};
76+
77+
expect(response.result?.serverInfo?.name).toBe('keenetic');
78+
expect(response.result?.serverInfo?.version).toBe(pkg.version);
79+
});
80+
6281
// Regression: npm installs a bin as a symlink in node_modules/.bin, so under
6382
// npx the entry path is the link and the module path is its target. Version
6483
// 0.1.0 compared them unresolved, matched nothing, and exited silently.

0 commit comments

Comments
 (0)