-
Notifications
You must be signed in to change notification settings - Fork 0
feat(local-mcp): report the real plugin version #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
steve-calvert-glean
merged 2 commits into
steve/rename-local-mcp
from
steve/local-mcp-plugin-version
Aug 10, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| #!/usr/bin/env node | ||
| // Propagate canonical root release metadata into each source bucket so every | ||
| // built plugin ships the same values. Runs automatically via the `prebuild` | ||
| // npm script. The root CHANGELOG.md and package.json are the single sources of | ||
| // truth (managed by release-it); the copies below are derived and should not be | ||
| // hand-edited. | ||
| import { copyFileSync, readFileSync, writeFileSync } from "node:fs"; | ||
| import { fileURLToPath } from "node:url"; | ||
| import { dirname, join } from "node:path"; | ||
|
|
||
| const root = join(dirname(fileURLToPath(import.meta.url)), ".."); | ||
| const { version } = JSON.parse( | ||
| readFileSync(join(root, "package.json"), "utf8"), | ||
| ); | ||
|
|
||
| const changelog = join(root, "CHANGELOG.md"); | ||
| const buckets = [ | ||
| "sources/claude", | ||
| "sources/codex", | ||
| "sources/cursor", | ||
| "sources/dev-docs", | ||
| ]; | ||
|
|
||
| for (const bucket of buckets) { | ||
| copyFileSync(changelog, join(root, bucket, "CHANGELOG.md")); | ||
| console.log(`synced CHANGELOG.md -> ${bucket}`); | ||
| } | ||
|
|
||
| // Textual replace, not a JSON round-trip, to keep the release diff to one line. | ||
| const manifestPath = join(root, "sources/local-mcp/package.json"); | ||
| const before = readFileSync(manifestPath, "utf8"); | ||
| if (!/"version":\s*"[^"]*"/.test(before)) { | ||
| throw new Error( | ||
| `No "version" field in ${manifestPath} to sync. Add one — the local MCP ` + | ||
| `server reads it at runtime to report its own version.`, | ||
| ); | ||
| } | ||
| writeFileSync( | ||
| manifestPath, | ||
| before.replace(/("version":\s*")[^"]*"/, `$1${version}"`), | ||
| ); | ||
| console.log(`synced version ${version} -> sources/local-mcp`); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| { | ||
| "name": "glean", | ||
| "version": "3.2.0", | ||
| "type": "module", | ||
| "private": true, | ||
| "engines": { | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // Read at runtime rather than injected at build time: `../package.json` hits the | ||
| // shipped manifest from both src/ and dist/, and that file has to exist anyway | ||
| // for Node to load the bundle as ESM. Kept in step with the root manifest by | ||
| // scripts/sync-release-metadata.mjs. | ||
| import { readFileSync } from "node:fs"; | ||
|
|
||
| export const PLUGIN_VERSION: string = JSON.parse( | ||
| readFileSync(new URL("../package.json", import.meta.url), "utf8"), | ||
| ).version; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import { describe, it, expect } from "vitest"; | ||
| import { | ||
| readFileSync, | ||
| mkdtempSync, | ||
| mkdirSync, | ||
| copyFileSync, | ||
| rmSync, | ||
| } from "node:fs"; | ||
| import { execFileSync, spawn } from "node:child_process"; | ||
| import type { ChildProcessWithoutNullStreams } from "node:child_process"; | ||
| import { createInterface } from "node:readline"; | ||
| import { tmpdir } from "node:os"; | ||
| import path from "node:path"; | ||
| import { fileURLToPath } from "node:url"; | ||
| import { PLUGIN_VERSION } from "../src/version.js"; | ||
|
|
||
| // Anchor to the test file, not cwd — vitest runs with --root sources/local-mcp | ||
| // while the process cwd stays at the repo root. | ||
| const here = path.dirname(fileURLToPath(import.meta.url)); | ||
| const serverDir = path.resolve(here, ".."); | ||
| const repoRoot = path.resolve(here, "../../.."); | ||
|
|
||
| const readVersion = (manifest: string): string => | ||
| JSON.parse(readFileSync(manifest, "utf8")).version; | ||
|
|
||
| describe("plugin version", () => { | ||
| it("reports the shipped manifest's version when run from source", () => { | ||
| expect(PLUGIN_VERSION).toBe( | ||
| readVersion(path.join(serverDir, "package.json")), | ||
| ); | ||
| }); | ||
|
|
||
| it("stays in step with the repo-root version that release-it bumps", () => { | ||
| // prebuild syncs these; drift means a release would misreport its version. | ||
| expect(readVersion(path.join(serverDir, "package.json"))).toBe( | ||
| readVersion(path.join(repoRoot, "package.json")), | ||
| ); | ||
| }); | ||
|
|
||
| it("advertises the version over MCP from the built bundle", async () => { | ||
| // Guards esbuild's handling of import.meta.url: unbundled the read resolves | ||
| // from src/, shipped it resolves from dist/. | ||
| execFileSync("node", ["sources/local-mcp/build.mjs"], { | ||
| cwd: repoRoot, | ||
| stdio: "pipe", | ||
| }); | ||
|
|
||
| const staged = mkdtempSync(path.join(tmpdir(), "glean-plugin-layout-")); | ||
| let child: ChildProcessWithoutNullStreams | undefined; | ||
| let timer: ReturnType<typeof setTimeout> | undefined; | ||
|
|
||
| try { | ||
| mkdirSync(path.join(staged, "dist")); | ||
| copyFileSync( | ||
| path.join(serverDir, "dist/index.js"), | ||
| path.join(staged, "dist/index.js"), | ||
| ); | ||
| copyFileSync( | ||
| path.join(serverDir, "package.json"), | ||
| path.join(staged, "package.json"), | ||
| ); | ||
|
|
||
| const server = spawn("node", [path.join(staged, "dist/index.js")], { | ||
| // Keep all server state inside the temp dir — never touch ~/.glean. | ||
| env: { | ||
| ...process.env, | ||
| CLAUDE_PLUGIN_DATA: path.join(staged, "plugin-data"), | ||
| SKILLS_BASE_DIR: path.join(staged, "skills"), | ||
| }, | ||
| stdio: "pipe", | ||
| }); | ||
| child = server; | ||
|
|
||
| const version = await new Promise<string>((resolve, reject) => { | ||
| timer = setTimeout( | ||
| () => reject(new Error("no initialize response within 15s")), | ||
| 15_000, | ||
| ); | ||
| createInterface({ input: server.stdout }).on("line", (line) => { | ||
| try { | ||
| const msg = JSON.parse(line); | ||
| if (msg.id === 1 && msg.result?.serverInfo) { | ||
| resolve(msg.result.serverInfo.version); | ||
| } | ||
| } catch { | ||
| // Not a JSON-RPC frame. | ||
| } | ||
| }); | ||
| // Fail fast with the server's own diagnostics instead of waiting out | ||
| // the timeout when it dies during startup. | ||
| let stderr = ""; | ||
| server.stderr.on("data", (chunk) => (stderr += chunk.toString())); | ||
| server.on("exit", (code) => | ||
| reject( | ||
| new Error(`server exited (${code}) before replying: ${stderr}`), | ||
| ), | ||
| ); | ||
| server.on("error", reject); | ||
| server.stdin.write( | ||
| JSON.stringify({ | ||
| jsonrpc: "2.0", | ||
| id: 1, | ||
| method: "initialize", | ||
| params: { | ||
| protocolVersion: "2025-06-18", | ||
| capabilities: {}, | ||
| clientInfo: { name: "version-test", version: "0.0.0" }, | ||
| }, | ||
| }) + "\n", | ||
| ); | ||
| }); | ||
|
|
||
| expect(version).toBe(readVersion(path.join(repoRoot, "package.json"))); | ||
| } finally { | ||
| clearTimeout(timer); | ||
| child?.kill(); | ||
| rmSync(staged, { recursive: true, force: true }); | ||
| } | ||
| }, 30_000); | ||
| }); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.