diff --git a/index.ts b/index.ts index 98ea120c..51753d8b 100644 --- a/index.ts +++ b/index.ts @@ -188,9 +188,9 @@ import fs from "node:fs"; import { pipeline as streamPipeline } from "node:stream/promises"; import os from "node:os"; import nodeFetch from "node-fetch"; -import path, { dirname } from "node:path"; +import path from "node:path"; import { CookieJar, parse as parseCookie } from "tough-cookie"; -import { fileURLToPath, URL } from "node:url"; +import { URL } from "node:url"; import { z } from "zod"; import { initializeOAuthClient, GitLabOAuth } from "./oauth.js"; @@ -199,6 +199,7 @@ import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js"; import { ipKeyGenerator } from "express-rate-limit"; import { normalizeProxyClientIpForRateLimit } from "./utils/proxy-client-ip.js"; import { getForwardedPublicBaseUrl } from "./utils/forwarded-public-base-url.js"; +import { SERVER_VERSION } from "./server/version.js"; import { normalizeGitLabApiUrl } from "./utils/url.js"; import { estimateMergeCommitCount, @@ -623,22 +624,6 @@ enum TransportMode { STREAMABLE_HTTP = "streamable-http", } -/** - * Read version from package.json - */ -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const packageJsonPath = path.resolve(__dirname, "../package.json"); -let SERVER_VERSION = "unknown"; -try { - if (fs.existsSync(packageJsonPath)) { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); - SERVER_VERSION = packageJson.version || SERVER_VERSION; - } -} catch { - // Intentionally ignored: version read failure is non-critical -} - /** * Create a new MCP Server instance with request handlers registered. * Each transport connection gets its own Server instance to prevent diff --git a/package.json b/package.json index 080d5d8b..82014425 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "changelog": "auto-changelog -p", "test": "npm run test:all", "test:all": "npm run build && npm run test:mock && npm run test:live", - "test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && node --import tsx/esm --test test/test-oauth-proxy-rate-limit.ts && node --import tsx/esm --test test/streamable-http-static-token-auth.test.ts && node --import tsx/esm --test test/streamable-http-concurrent-session.test.ts && node --import tsx/esm --test test/streamable-http-unauthenticated-discovery.test.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && tsx test/test-list-issues.ts && node --import tsx/esm --test test/test-create-repository.ts && node --import tsx/esm --test test/test-update-project.ts && node --import tsx/esm --test test/test-merge-request-pipelines.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-upload-markdown.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-remote-downloads.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-protected-branches.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-ci-catalog.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/test-issue-description-patch.ts && node --import tsx/esm --test test/test-geteffectiveprojectid.ts && node --import tsx/esm --test test/test-get-file-blame.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts && node --import tsx/esm --test test/utils/tool-args.test.ts && node --import tsx/esm --test test/utils/proxy-client-ip.test.ts && node --import tsx/esm --test test/utils/forwarded-public-base-url.test.ts && node --import tsx/esm --test test/utils/graphql-query.test.ts && node --import tsx/esm --test test/utils/wiki-title.test.ts && node --import tsx/esm --test test/utils/merge-request-position.test.ts && node --import tsx/esm --test test/nullish-tool-arguments-schema.test.ts && node --import tsx/esm --test test/test-ci-variables.ts && node --import tsx/esm --test test/test-dependency-proxy.ts", + "test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && node --import tsx/esm --test test/test-oauth-proxy-rate-limit.ts && node --import tsx/esm --test test/streamable-http-static-token-auth.test.ts && node --import tsx/esm --test test/streamable-http-concurrent-session.test.ts && node --import tsx/esm --test test/streamable-http-unauthenticated-discovery.test.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && tsx test/test-list-issues.ts && node --import tsx/esm --test test/test-create-repository.ts && node --import tsx/esm --test test/test-update-project.ts && node --import tsx/esm --test test/test-merge-request-pipelines.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-upload-markdown.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-remote-downloads.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-protected-branches.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-ci-catalog.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/test-issue-description-patch.ts && node --import tsx/esm --test test/test-geteffectiveprojectid.ts && node --import tsx/esm --test test/test-get-file-blame.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts && node --import tsx/esm --test test/utils/tool-args.test.ts && node --import tsx/esm --test test/utils/proxy-client-ip.test.ts && node --import tsx/esm --test test/utils/forwarded-public-base-url.test.ts && node --import tsx/esm --test test/utils/graphql-query.test.ts && node --import tsx/esm --test test/utils/wiki-title.test.ts && node --import tsx/esm --test test/utils/merge-request-position.test.ts && node --import tsx/esm --test test/nullish-tool-arguments-schema.test.ts && node --import tsx/esm --test test/test-ci-variables.ts && node --import tsx/esm --test test/test-dependency-proxy.ts && node --import tsx/esm --test test/server/version.test.ts", "test:stateless": "npm run build && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts", "test:mcp-oauth": "npm run build && node --import tsx/esm --test test/mcp-oauth-tests.ts", "test:live": "node test/validate-api.js", diff --git a/server/version.ts b/server/version.ts new file mode 100644 index 00000000..86a45c7d --- /dev/null +++ b/server/version.ts @@ -0,0 +1,27 @@ +import fs from "node:fs"; +import path, { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const packageJsonPaths = [ + // Built file: build/server/version.js -> ../../package.json + path.resolve(__dirname, "../../package.json"), + // Source file under tsx/node:test: server/version.ts -> ../package.json + path.resolve(__dirname, "../package.json"), +]; + +export const SERVER_VERSION: string = (() => { + for (const packageJsonPath of packageJsonPaths) { + try { + if (fs.existsSync(packageJsonPath)) { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); + return packageJson.version || "unknown"; + } + } catch { + // Intentionally ignored: version read failure is non-critical. + } + } + return "unknown"; +})(); diff --git a/test/server/version.test.ts b/test/server/version.test.ts new file mode 100644 index 00000000..45a0e15a --- /dev/null +++ b/test/server/version.test.ts @@ -0,0 +1,10 @@ +import { test } from "node:test"; +import assert from "node:assert"; +import fs from "node:fs"; +import { SERVER_VERSION } from "../../server/version.js"; + +const packageVersion = JSON.parse(fs.readFileSync("package.json", "utf8")).version; + +test("SERVER_VERSION matches package.json", () => { + assert.strictEqual(SERVER_VERSION, packageVersion); +});