Address PR #1641 review feedback: - Add readInspectorVersionSafe() (non-throwing) in core/node/version.ts and use it in the web backend, so a failure to resolve the root manifest hides the (cosmetic) version badge instead of crashing the backend at startup. Covered by tests for both the resolved and unresolvable paths. - Update MonitoringScreen's docstrings: it now wraps every supplied screen in a ScreenStage (only the active tab mounts, cross-fading) rather than rendering just the active tab's node. The third fetch-consolidation suggestion (one useInitialConfig instead of three GET /api/config hooks) is tracked as a follow-up in #1643. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
86 lines
3 KiB
TypeScript
86 lines
3 KiB
TypeScript
/**
|
|
* Single source of truth for the Inspector's version, read from the **root**
|
|
* `package.json` of the published `@modelcontextprotocol/inspector` package.
|
|
*
|
|
* The Inspector ships as one package with one version number; the per-client
|
|
* `package.json`s deliberately carry no `version` field, so every Node client
|
|
* (CLI, TUI, and the web backend) resolves the version here instead of reading
|
|
* its own manifest.
|
|
*
|
|
* **Node-only** — this uses `node:fs`; never import it from browser code. The
|
|
* web *browser* cannot read the filesystem and gets its version from the
|
|
* backend via `GET /api/config` instead.
|
|
*/
|
|
|
|
import { readFileSync } from "node:fs";
|
|
import { dirname, join, parse } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
/** The published root package's name — how we recognize the root manifest. */
|
|
export const ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
|
|
|
|
interface RootManifest {
|
|
name?: string;
|
|
version?: string;
|
|
}
|
|
|
|
/**
|
|
* Resolve the Inspector version by walking up from the calling module to the
|
|
* root `package.json` (the one named {@link ROOT_PACKAGE_NAME}, which alone
|
|
* carries a `version`). Pass the caller's `import.meta.url`.
|
|
*
|
|
* This works identically in the dev tree and the published tarball: in both,
|
|
* a client entry (`clients/<name>/{src,build}/…`) sits below the root manifest,
|
|
* and npm always ships the root `package.json`. Client `package.json`s are
|
|
* skipped because they have no matching `name`/`version`, so a stray manifest
|
|
* on the way up never shadows the real root.
|
|
*
|
|
* @param callerUrl the calling module's `import.meta.url`
|
|
* @returns the version string from the root `package.json`
|
|
* @throws if no root manifest is found above the caller
|
|
*/
|
|
export function readInspectorVersion(callerUrl: string): string {
|
|
const startDir = dirname(fileURLToPath(callerUrl));
|
|
const { root } = parse(startDir);
|
|
|
|
let dir = startDir;
|
|
for (;;) {
|
|
let manifest: RootManifest | undefined;
|
|
try {
|
|
manifest = JSON.parse(
|
|
readFileSync(join(dir, "package.json"), "utf-8"),
|
|
) as RootManifest;
|
|
} catch {
|
|
// No (readable) package.json at this level — keep walking up.
|
|
}
|
|
if (manifest?.name === ROOT_PACKAGE_NAME && manifest.version) {
|
|
return manifest.version;
|
|
}
|
|
if (dir === root) break;
|
|
dir = dirname(dir);
|
|
}
|
|
|
|
throw new Error(
|
|
`Could not locate the ${ROOT_PACKAGE_NAME} root package.json above ${startDir}`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Non-throwing variant of {@link readInspectorVersion}: returns `undefined`
|
|
* instead of throwing when the root manifest can't be resolved. For callers
|
|
* where the version is cosmetic (e.g. the web backend surfacing it to the
|
|
* browser) — a resolution failure should hide the version, not take the caller
|
|
* down at startup.
|
|
*
|
|
* @param callerUrl the calling module's `import.meta.url`
|
|
* @returns the root version, or `undefined` if it can't be resolved
|
|
*/
|
|
export function readInspectorVersionSafe(
|
|
callerUrl: string,
|
|
): string | undefined {
|
|
try {
|
|
return readInspectorVersion(callerUrl);
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|