refactor(version): single-source the version from the root package.json

The Inspector ships as one package with one version, but four client
package.jsons each carried their own `version` and were read at runtime
(cli.ts, tui App.tsx), and core hardcoded a third, stale value (0.20.0) that
the TUI and web actually reported to servers. Consolidate onto the root
package.json as the single source of truth.

- Add core/node/version.ts: shared, Node-only readInspectorVersion() that walks
  up to the root package.json (always shipped in the tarball). 100% covered by a
  new integration test.
- CLI and TUI read the version via the shared reader instead of their own
  manifest; TUI now also passes a real clientIdentity (was falling through to
  the stale core default).
- Drop the `version` field from all four client package.jsons; the root keeps
  the only version. TUI header name/description become local display constants.
- core default client identity: replace the stale 0.20.0 hardcode with a neutral
  0.0.0 placeholder (real clients pass clientIdentity; web will source its
  version from GET /api/config, see #1639).
- Stop shipping clients/cli/package.json and clients/tui/package.json (no longer
  read at runtime) — supersedes the earlier "ship them" fix; update pack:verify
  and the root README packaging invariants accordingly.
- Add core/node/** to the web coverage include so the new module is gated.
- Add the shared-code architecture diagram to the root README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
This commit is contained in:
cliffhall 2026-07-10 17:31:21 -04:00
commit 7815548300

View file

@ -6,9 +6,6 @@ import React, {
useRef,
} from "react";
import { Box, Text, useInput, useApp, type Key } from "ink";
import { readFileSync } from "fs";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import type {
MessageEntry,
FetchRequestEntry,
@ -56,6 +53,7 @@ import {
isOAuthCapableServerConfig,
} from "@inspector/core/client/runner.js";
import { formatRunnerOAuthRedirectUrl } from "@inspector/core/auth/node/runner-oauth-callback.js";
import { readInspectorVersion } from "@inspector/core/node/version.js";
import {
createOAuthCallbackServer,
type OAuthCallbackServer,
@ -85,33 +83,17 @@ import { PromptTestModal } from "./components/PromptTestModal.js";
import { DetailsModal } from "./components/DetailsModal.js";
import type { TuiServer } from "./tui-servers.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Header branding. The version is the single source of truth — the root
// package.json — read via the shared core reader; the name/description are the
// TUI's own display strings (they live in code, not the npm manifest, which no
// longer carries them per-client).
const APP_NAME = "MCP Inspector TUI";
const APP_DESCRIPTION =
"Terminal User Interface for the Model Context Protocol Inspector";
const APP_VERSION = readInspectorVersion(import.meta.url);
// Read package.json to get project info
// Strategy: Try multiple paths to handle both local dev and global install
// - Local dev (tsx): __dirname = src/, package.json is one level up
// - Global install: __dirname = dist/src/, package.json is two levels up
let packagePath: string;
let packageJson: { name: string; description: string; version: string };
try {
// Try two levels up first (global install case)
packagePath = join(__dirname, "..", "..", "package.json");
packageJson = JSON.parse(readFileSync(packagePath, "utf-8")) as {
name: string;
description: string;
version: string;
};
} catch {
// Fall back to one level up (local dev case)
packagePath = join(__dirname, "..", "package.json");
packageJson = JSON.parse(readFileSync(packagePath, "utf-8")) as {
name: string;
description: string;
version: string;
};
}
/** Client identity name the TUI reports to servers. */
const TUI_CLIENT_NAME = "inspector-tui";
// Focus management types
type FocusArea =
@ -310,6 +292,7 @@ function App({
);
const opts: InspectorClientOptions = {
environment,
clientIdentity: { name: TUI_CLIENT_NAME, version: APP_VERSION },
pipeStderr: true,
...(savedSettings &&
savedSettings.requestTimeout > 0 && {
@ -1498,11 +1481,11 @@ function App({
>
<Box>
<Text bold color="cyan">
{packageJson.name}
{APP_NAME}
</Text>
<Text dimColor> - {packageJson.description}</Text>
<Text dimColor> - {APP_DESCRIPTION}</Text>
</Box>
<Text dimColor>v{packageJson.version}</Text>
<Text dimColor>v{APP_VERSION}</Text>
</Box>
{/* Main content area */}