Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ server/
coverage/
*.tsbuildinfo

# Editor and IDE state
.idea/
.vscode/
.DS_Store

# Working design docs and plans, not part of the published project
docs/superpowers/

Expand Down
9 changes: 8 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env node
import { realpathSync } from 'node:fs';
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
Expand All @@ -17,8 +18,14 @@ import { registerRawTool } from './tools/raw.js';
import type { ToolContext } from './tools/registry.js';
import { registerSystemTools } from './tools/system.js';

// Read rather than repeated as a literal: package.json is the only place a
// version is edited by hand, and a hardcoded one here went stale silently -
// the handshake still reported 0.1.0 two releases later. `../package.json`
// resolves from dist/index.js and src/index.ts alike, and npm always packs it.
const VERSION = createRequire(import.meta.url)('../package.json').version as string;

export function createServer(ctx: ToolContext): McpServer {
const server = new McpServer({ name: 'keenetic', version: '0.1.0' });
const server = new McpServer({ name: 'keenetic', version: VERSION });
registerSystemTools(server, ctx);
registerDeviceTools(server, ctx);
registerInterfaceTools(server, ctx);
Expand Down
21 changes: 20 additions & 1 deletion tests/binary.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { spawn } from 'node:child_process';
import { mkdtemp, symlink } from 'node:fs/promises';
import { mkdtemp, readFile, symlink } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
Expand Down Expand Up @@ -59,6 +59,25 @@ describe('the built binary', () => {
expect(result.stdout).toContain('keenetic');
});

// The version in the handshake is what a client displays and what a bug
// report quotes, so it has to be the published one. It was a literal in
// src/index.ts and sat at 0.1.0 while the package shipped 0.2.1. Run against
// dist because that also proves package.json is reachable from the built
// layout, which is the only place the runtime read can go wrong.
it('reports the package version in serverInfo', async () => {
const pkg = JSON.parse(
await readFile(new URL('../package.json', import.meta.url), 'utf8')
) as { version: string };

const result = await run(DIST, `${INITIALIZE}\n`, CONFIGURED);
const response = JSON.parse(result.stdout.split('\n')[0] ?? '{}') as {
result?: { serverInfo?: { name?: string; version?: string } };
};

expect(response.result?.serverInfo?.name).toBe('keenetic');
expect(response.result?.serverInfo?.version).toBe(pkg.version);
});

// Regression: npm installs a bin as a symlink in node_modules/.bin, so under
// npx the entry path is the link and the module path is its target. Version
// 0.1.0 compared them unresolved, matched nothing, and exited silently.
Expand Down
Loading