From 4369d8cec7813386e88fceb6d11608c0c3a80fc5 Mon Sep 17 00:00:00 2001 From: cyforkk <1139009771@qq.com> Date: Wed, 29 Jul 2026 20:49:38 +0800 Subject: [PATCH 1/2] fix(python): cap mcp dependency to <2 across all Python servers mcp 2.0.0 renamed McpError to MCPError, breaking imports in servers with no upper bound. Cap mcp to <2 in all three Python servers (time, fetch, git) and regenerate lockfiles with the default PyPI index. Closes #4560, #4570 Co-Authored-By: Claude Fable 5 --- src/fetch/pyproject.toml | 2 +- src/fetch/uv.lock | 2 +- src/git/pyproject.toml | 2 +- src/git/uv.lock | 2 +- src/time/pyproject.toml | 2 +- src/time/uv.lock | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fetch/pyproject.toml b/src/fetch/pyproject.toml index 84735f278a..4cbf1e0b2c 100644 --- a/src/fetch/pyproject.toml +++ b/src/fetch/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ dependencies = [ "httpx>=0.27", "markdownify>=0.13.1", - "mcp>=1.1.3", + "mcp>=1.1.3,<2", "protego>=0.3.1", "pydantic>=2.0.0", "readabilipy>=0.2.0", diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index ea9b0567e2..4f6f729518 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -582,7 +582,7 @@ dev = [ requires-dist = [ { name = "httpx", specifier = ">=0.27" }, { name = "markdownify", specifier = ">=0.13.1" }, - { name = "mcp", specifier = ">=1.1.3" }, + { name = "mcp", specifier = ">=1.1.3,<2" }, { name = "protego", specifier = ">=0.3.1" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "readabilipy", specifier = ">=0.2.0" }, diff --git a/src/git/pyproject.toml b/src/git/pyproject.toml index c5113ae7d4..a141397cac 100644 --- a/src/git/pyproject.toml +++ b/src/git/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ dependencies = [ "click>=8.1.7", "gitpython>=3.1.50", - "mcp>=1.0.0", + "mcp>=1.0.0,<2", "pydantic>=2.0.0", ] diff --git a/src/git/uv.lock b/src/git/uv.lock index a838e3d10d..15c16a95c6 100644 --- a/src/git/uv.lock +++ b/src/git/uv.lock @@ -376,7 +376,7 @@ dev = [ requires-dist = [ { name = "click", specifier = ">=8.1.7" }, { name = "gitpython", specifier = ">=3.1.50" }, - { name = "mcp", specifier = ">=1.0.0" }, + { name = "mcp", specifier = ">=1.0.0,<2" }, { name = "pydantic", specifier = ">=2.0.0" }, ] diff --git a/src/time/pyproject.toml b/src/time/pyproject.toml index f9be050452..84a34c4763 100644 --- a/src/time/pyproject.toml +++ b/src/time/pyproject.toml @@ -17,7 +17,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", ] dependencies = [ - "mcp>=1.23.0", + "mcp>=1.23.0,<2", "pydantic>=2.0.0", "tzdata>=2024.2", "tzlocal>=5.3.1", diff --git a/src/time/uv.lock b/src/time/uv.lock index e77b3c3820..a9585011d4 100644 --- a/src/time/uv.lock +++ b/src/time/uv.lock @@ -362,7 +362,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "mcp", specifier = ">=1.23.0" }, + { name = "mcp", specifier = ">=1.23.0,<2" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "tzdata", specifier = ">=2024.2" }, { name = "tzlocal", specifier = ">=5.3.1" }, From 0a4408f9c510ed4d9b1f334c204d138facca25f9 Mon Sep 17 00:00:00 2001 From: cyforkk <1139009771@qq.com> Date: Wed, 29 Jul 2026 21:13:01 +0800 Subject: [PATCH 2/2] fix(filesystem): read server version from package.json The serverInfo.version was hardcoded as "0.2.0" while package.json reports "0.6.3". Read the version at runtime so it stays in sync with the published package, matching the approach used for sequentialthinking (PR #4576). Related: #360 Co-Authored-By: Claude Fable 5 --- .../__tests__/server-version.test.ts | 57 +++++++++++++++++++ src/filesystem/index.ts | 3 +- src/filesystem/version.ts | 33 +++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 src/filesystem/__tests__/server-version.test.ts create mode 100644 src/filesystem/version.ts diff --git a/src/filesystem/__tests__/server-version.test.ts b/src/filesystem/__tests__/server-version.test.ts new file mode 100644 index 0000000000..b7adc645f0 --- /dev/null +++ b/src/filesystem/__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('secure-filesystem-server'); + expect(serverInfo?.version).toBe(packageJson.version); + expect(serverInfo?.version).not.toBe('0.2.0'); + } finally { + await client.close(); + } + }, + ); +}); diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index 234605bb13..9c7a3a847d 100644 --- a/src/filesystem/index.ts +++ b/src/filesystem/index.ts @@ -2,6 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { SERVER_VERSION } from './version.js'; import { RootsListChangedNotificationSchema, type Root, @@ -163,7 +164,7 @@ const GetFileInfoArgsSchema = z.object({ const server = new McpServer( { name: "secure-filesystem-server", - version: "0.2.0", + version: SERVER_VERSION, } ); diff --git a/src/filesystem/version.ts b/src/filesystem/version.ts new file mode 100644 index 0000000000..df7b7163ca --- /dev/null +++ b/src/filesystem/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/filesystem/`) 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();