diff --git a/src/sequentialthinking/__tests__/server-version.test.ts b/src/sequentialthinking/__tests__/server-version.test.ts new file mode 100644 index 0000000000..c7d7f5cb39 --- /dev/null +++ b/src/sequentialthinking/__tests__/server-version.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect } from 'vitest'; +import { createRequire } from 'node:module'; +import { existsSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import { resolvePackageVersion, SERVER_VERSION } from '../version.js'; + +const packageJson = createRequire(import.meta.url)('../package.json') as { version: string }; +const packageRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); +const distVersionPath = path.join(packageRoot, 'dist', 'version.js'); +const distIndexPath = path.join(packageRoot, 'dist', 'index.js'); + +describe('server version', () => { + it('uses package.json version instead of a hardcoded string', () => { + expect(SERVER_VERSION).toBe(packageJson.version); + expect(resolvePackageVersion()).toBe(packageJson.version); + expect(SERVER_VERSION).not.toBe('0.2.0'); + }); + + // CI runs `npm test` before the dedicated build job. `npm ci` usually + // materializes dist/ via prepare, but that is not guaranteed (e.g. local + // `rm -rf dist && npm test`, or install with --ignore-scripts). + it.skipIf(!existsSync(distVersionPath))( + 'resolves package.json from the dist layout after build', + async () => { + const distModule = (await import(pathToFileURL(distVersionPath).href)) as { + SERVER_VERSION: string; + }; + expect(distModule.SERVER_VERSION).toBe(packageJson.version); + }, + ); + + it.skipIf(!existsSync(distIndexPath))( + 'stdio initialize reports package.json version in serverInfo', + async () => { + const transport = new StdioClientTransport({ + command: process.execPath, + args: [distIndexPath], + cwd: packageRoot, + stderr: 'pipe', + }); + const client = new Client({ name: 'version-smoke', version: '0.0.0' }); + + try { + await client.connect(transport); + const serverInfo = client.getServerVersion(); + expect(serverInfo?.name).toBe('sequential-thinking-server'); + expect(serverInfo?.version).toBe(packageJson.version); + expect(serverInfo?.version).not.toBe('0.2.0'); + } finally { + await client.close(); + } + }, + ); +}); diff --git a/src/sequentialthinking/index.ts b/src/sequentialthinking/index.ts index 217845bb3d..54c42df199 100644 --- a/src/sequentialthinking/index.ts +++ b/src/sequentialthinking/index.ts @@ -4,6 +4,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { SequentialThinkingServer } from './lib.js'; +import { SERVER_VERSION } from './version.js'; /** Safe boolean coercion that correctly handles string "false" */ const coercedBoolean = z.preprocess((val) => { @@ -17,7 +18,7 @@ const coercedBoolean = z.preprocess((val) => { const server = new McpServer({ name: "sequential-thinking-server", - version: "0.2.0", + version: SERVER_VERSION, }); const thinkingServer = new SequentialThinkingServer(); diff --git a/src/sequentialthinking/version.ts b/src/sequentialthinking/version.ts new file mode 100644 index 0000000000..1ab7d8f2fd --- /dev/null +++ b/src/sequentialthinking/version.ts @@ -0,0 +1,33 @@ +import { createRequire } from 'node:module'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +/** + * Resolve this package's version from package.json. + * + * Works both from source (`src/sequentialthinking/`) and from the published + * layout (`dist/`), where package.json lives one directory up. + */ +export function resolvePackageVersion(): string { + const require = createRequire(import.meta.url); + const moduleDir = path.dirname(fileURLToPath(import.meta.url)); + const candidates = [ + path.join(moduleDir, 'package.json'), + path.join(moduleDir, '..', 'package.json'), + ]; + + for (const candidate of candidates) { + try { + const pkg = require(candidate) as { version?: string }; + if (pkg.version) { + return pkg.version; + } + } catch { + // Try the next candidate when running from dist/ or source. + } + } + + throw new Error('Could not locate package.json for server version'); +} + +export const SERVER_VERSION = resolvePackageVersion();