From 64fd8cd20c62e0468f70546703eadee66ff153b2 Mon Sep 17 00:00:00 2001 From: KarlLeen Date: Wed, 29 Jul 2026 04:55:02 -0400 Subject: [PATCH 1/2] fix(sequentialthinking): read server version from package.json Replace the hardcoded 0.2.0 so initialize serverInfo tracks the package version (0.6.2 in-tree; CalVer after release), matching the approach used for memory server version sync. Skip the dist-layout assertion when dist/ is absent so CI's test-before-build job (and local runs without prepare) still pass. --- .../__tests__/server-version.test.ts | 35 +++++++++++++++++++ src/sequentialthinking/index.ts | 3 +- src/sequentialthinking/version.ts | 33 +++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/sequentialthinking/__tests__/server-version.test.ts create mode 100644 src/sequentialthinking/version.ts diff --git a/src/sequentialthinking/__tests__/server-version.test.ts b/src/sequentialthinking/__tests__/server-version.test.ts new file mode 100644 index 0000000000..fa3c26938b --- /dev/null +++ b/src/sequentialthinking/__tests__/server-version.test.ts @@ -0,0 +1,35 @@ +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 { resolvePackageVersion, SERVER_VERSION } from '../version.js'; + +const packageJson = createRequire(import.meta.url)('../package.json') as { version: string }; +const distVersionPath = path.join( + path.dirname(fileURLToPath(import.meta.url)), + '..', + 'dist', + 'version.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); + }, + ); +}); 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..d14838f8b9 --- /dev/null +++ b/src/sequentialthinking/version.ts @@ -0,0 +1,33 @@ +import { createRequire } from 'node:module'; +import path from 'path'; +import { fileURLToPath } from '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(); From 4a7808da7967a73b1003f1d58d5dc97bf4ab2fd8 Mon Sep 17 00:00:00 2001 From: KarlLeen Date: Wed, 29 Jul 2026 04:55:02 -0400 Subject: [PATCH 2/2] test(sequentialthinking): smoke stdio initialize serverInfo.version Spawn the built server over stdio and assert getServerVersion() matches package.json (and is not the old hardcoded 0.2.0). Skip when dist/ is absent so test-before-build CI still passes. --- .../__tests__/server-version.test.ts | 34 +++++++++++++++---- src/sequentialthinking/version.ts | 4 +-- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/sequentialthinking/__tests__/server-version.test.ts b/src/sequentialthinking/__tests__/server-version.test.ts index fa3c26938b..c7d7f5cb39 100644 --- a/src/sequentialthinking/__tests__/server-version.test.ts +++ b/src/sequentialthinking/__tests__/server-version.test.ts @@ -3,15 +3,14 @@ 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 distVersionPath = path.join( - path.dirname(fileURLToPath(import.meta.url)), - '..', - 'dist', - 'version.js', -); +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', () => { @@ -32,4 +31,27 @@ describe('server version', () => { 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/version.ts b/src/sequentialthinking/version.ts index d14838f8b9..1ab7d8f2fd 100644 --- a/src/sequentialthinking/version.ts +++ b/src/sequentialthinking/version.ts @@ -1,6 +1,6 @@ import { createRequire } from 'node:module'; -import path from 'path'; -import { fileURLToPath } from 'url'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; /** * Resolve this package's version from package.json.