* feat(cli): lift config-file headers/timeouts/OAuth into serverSettings (#1482) The CLI resolved file-sourced servers via resolveServerConfigs(), which returns bare MCPServerConfig and dropped disk-level headers, timeouts, and OAuth — CLI connections from a --catalog/--config file silently ignored them. The TUI already lifted these via mcpConfigToServerEntries(). Extract the TUI's catalog/config -> per-server-settings resolution into a shared core module (core/mcp/node/servers.ts: loadServerEntries + selectServerEntry) and route both CLI and TUI through it, so the two share one file->settings path. --header still overrides the file's headers for the run while preserving the file's timeouts/OAuth. - core: add loadServerEntries/selectServerEntry/headersToServerSettings - tui: tui-servers.ts re-exports the shared core helpers (no logic dup) - cli: resolve via loadServerEntries + selectServerEntry; drop the CLI-only conflict check and bare-config path - tests: core servers.test.ts (settings lifting, header merge/override, stdio overrides, seed/error/conflict, selection); CLI integration tests asserting a config header is sent over HTTP and --header overrides it - docs: CLI README note on file settings being applied Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(core): address #1500 review — dedup overrides, drop dead wrapper, clearer error, spec refresh - Remove the now-unused resolveLaunchServerConfigs() wrapper (both CLI and TUI route through loadServerEntries now); migrate its default-catalog launch tests to compose withDefaultCatalogPath + resolveServerConfigs. - Deduplicate applyStdioOverrides against config.ts applyOverrides: export the latter (now trims cwd) and reuse it in servers.ts. - selectServerEntry's unknown-name error is source-agnostic ("not found" not "not found in config file") since entries may be a single ad-hoc default, where "config file" would mislead. - Refresh specification/ docs (v2_cli_tui_launcher.md, v2_catalog_launch_config.md): CLI/TUI resolution now via loadServerEntries/selectServerEntry; mark gap G1 resolved (#1482); withDefaultConfigPath → withDefaultCatalogPath. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(cli): extend smoke:cli to cover #1482 lift→serverSettings Adds end-to-end smoke coverage for the config-file headers/timeouts/OAuth lift introduced in #1482: - unknown --server now errors source-agnostically ("not found") - multi-server catalog selection via --server (and the omit-it error) - --header merge does not break the stdio connect path - HTTP transport: a config-file `headers` object is lifted onto the wire, and a CLI --header overrides it (verified against an in-process test server's recorded requests via a non-blocking spawn) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
73b651ff21
commit
e72d4307f3
1 changed files with 1033 additions and 420 deletions
|
|
@ -1,112 +1,12 @@
|
|||
import type {
|
||||
InspectorServerSettings,
|
||||
MCPServerConfig,
|
||||
StdioServerConfig,
|
||||
} from "@inspector/core/mcp/types.js";
|
||||
import { DEFAULT_TASK_TTL_MS } from "@inspector/core/mcp/types.js";
|
||||
import { mcpConfigToServerEntries } from "@inspector/core/mcp/serverList.js";
|
||||
import {
|
||||
resolveServerConfigs,
|
||||
resolveServerSource,
|
||||
serverSourceConflict,
|
||||
readServerListFile,
|
||||
hasAdHocServerOptions,
|
||||
withDefaultCatalogPath,
|
||||
type ServerConfigOptions,
|
||||
} from "@inspector/core/mcp/node/config.js";
|
||||
// The catalog/config → per-server-settings resolution is shared with the CLI in
|
||||
// `@inspector/core/mcp/node` (see issue #1482). This module just re-exports it
|
||||
// under the TUI's historical names so the TUI keeps a single import surface.
|
||||
import type { ResolvedServer } from "@inspector/core/mcp/node/servers.js";
|
||||
|
||||
export type TuiServer = {
|
||||
config: MCPServerConfig;
|
||||
settings?: InspectorServerSettings;
|
||||
};
|
||||
export {
|
||||
headersToServerSettings,
|
||||
loadServerEntries as loadTuiServers,
|
||||
} from "@inspector/core/mcp/node/servers.js";
|
||||
|
||||
export function headersToServerSettings(
|
||||
headers?: Record<string, string>,
|
||||
): InspectorServerSettings | undefined {
|
||||
if (!headers || Object.keys(headers).length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
headers: Object.entries(headers).map(([key, value]) => ({ key, value })),
|
||||
metadata: [],
|
||||
connectionTimeout: 0,
|
||||
requestTimeout: 0,
|
||||
taskTtl: DEFAULT_TASK_TTL_MS,
|
||||
autoRefreshOnListChanged: false,
|
||||
roots: [],
|
||||
};
|
||||
}
|
||||
|
||||
function applyStdioOverrides(
|
||||
config: MCPServerConfig,
|
||||
overrides: { env?: Record<string, string>; cwd?: string },
|
||||
): MCPServerConfig {
|
||||
if (config.type !== "stdio") return config;
|
||||
const c = { ...config } as StdioServerConfig;
|
||||
if (overrides.env && Object.keys(overrides.env).length > 0) {
|
||||
c.env = { ...(c.env ?? {}), ...overrides.env };
|
||||
}
|
||||
if (overrides.cwd?.trim()) {
|
||||
c.cwd = overrides.cwd.trim();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
function mergeSettings(
|
||||
base: InspectorServerSettings | undefined,
|
||||
headers?: Record<string, string>,
|
||||
): InspectorServerSettings | undefined {
|
||||
const fromHeaders = headersToServerSettings(headers);
|
||||
if (!fromHeaders) return base;
|
||||
if (!base) return fromHeaders;
|
||||
return { ...base, headers: fromHeaders.headers };
|
||||
}
|
||||
|
||||
export function loadTuiServers(
|
||||
serverOptions: ServerConfigOptions & { headers?: Record<string, string> },
|
||||
): Record<string, TuiServer> {
|
||||
serverOptions = withDefaultCatalogPath(serverOptions);
|
||||
|
||||
const conflict = serverSourceConflict({
|
||||
hasCatalog: Boolean(serverOptions.catalogPath?.trim()),
|
||||
hasConfig: Boolean(serverOptions.configPath?.trim()),
|
||||
hasAdHoc: hasAdHocServerOptions(serverOptions),
|
||||
});
|
||||
if (conflict) {
|
||||
throw new Error(conflict);
|
||||
}
|
||||
|
||||
const source = resolveServerSource(serverOptions);
|
||||
|
||||
if (source) {
|
||||
const config = readServerListFile(source.path, source.writable);
|
||||
const entries = mcpConfigToServerEntries(config);
|
||||
const result: Record<string, TuiServer> = {};
|
||||
for (const entry of entries) {
|
||||
result[entry.name] = {
|
||||
config: applyStdioOverrides(entry.config, {
|
||||
env: serverOptions.env,
|
||||
cwd: serverOptions.cwd,
|
||||
}),
|
||||
// Deliberate broadcast: a single `--header` set is merged into EVERY
|
||||
// server in the catalog/config (fine for the common single-server case;
|
||||
// for multi-server files, prefer per-server headers in the file itself).
|
||||
settings: mergeSettings(entry.settings, serverOptions.headers),
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const configs = resolveServerConfigs(serverOptions, "multi");
|
||||
if (configs.length === 0) {
|
||||
throw new Error(
|
||||
"At least one server is required. Use --catalog/--config <path> or an ad-hoc target (command/URL).",
|
||||
);
|
||||
}
|
||||
return {
|
||||
default: {
|
||||
config: configs[0]!,
|
||||
settings: headersToServerSettings(serverOptions.headers),
|
||||
},
|
||||
};
|
||||
}
|
||||
/** A server resolved for the TUI: transport config plus per-server settings. */
|
||||
export type TuiServer = ResolvedServer;
|
||||
|
|
|
|||
Loading…
Reference in a new issue