Merge remote-tracking branch 'origin/v2/main' into v2/fix/2297-401-response-body
Signed-off-by: cliffhall <cliff@futurescale.com> # Conflicts: # core/mcp/node/transport.ts
This commit is contained in:
commit
860b8d27ad
4 changed files with 4402 additions and 100 deletions
|
|
@ -8,7 +8,8 @@ import type {
|
|||
SseServerConfig,
|
||||
StreamableHttpServerConfig,
|
||||
} from "../types.js";
|
||||
import { normalizeServerType } from "../serverList.js";
|
||||
import { isProtocolEra, normalizeServerType } from "../serverList.js";
|
||||
import type { ServerProtocolEra } from "../types.js";
|
||||
import { toRecord } from "../../json/jsonUtils.js";
|
||||
|
||||
/**
|
||||
|
|
@ -86,6 +87,20 @@ export function parseHeaderPair(
|
|||
return { ...previous, [key]: val };
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a `--protocol-era` value. Used as the Commander option coerce for
|
||||
* all three clients, so an ad-hoc launch can pick the era without writing an
|
||||
* `mcp.json` entry for it (#2208). Pure function; no Commander dependency.
|
||||
*/
|
||||
export function parseProtocolEra(value: string): ServerProtocolEra {
|
||||
if (!isProtocolEra(value)) {
|
||||
throw new Error(
|
||||
`Invalid protocol era: ${value}. Valid eras are: legacy, auto, modern`,
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** On-disk contents of a freshly seeded empty catalog (pretty-printed). */
|
||||
const EMPTY_CATALOG_CONTENT = `${JSON.stringify({ mcpServers: {} }, null, 2)}\n`;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export {
|
||||
parseKeyValuePair,
|
||||
parseHeaderPair,
|
||||
parseProtocolEra,
|
||||
withDefaultCatalogPath,
|
||||
resolveServerConfigs,
|
||||
getNamedServerConfigs,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import type { SecretStore } from "../../auth/node/secret-store.js";
|
||||
import { defaultSecretStore } from "../../auth/node/secret-store-selection.js";
|
||||
import type { InspectorServerSettings, MCPServerConfig } from "../types.js";
|
||||
import type {
|
||||
InspectorServerSettings,
|
||||
MCPServerConfig,
|
||||
ServerProtocolEra,
|
||||
} from "../types.js";
|
||||
import {
|
||||
DEFAULT_CONNECTION_TIMEOUT_MS,
|
||||
DEFAULT_MAX_FETCH_REQUESTS,
|
||||
|
|
@ -30,10 +34,14 @@ export type ResolvedServer = {
|
|||
settings?: InspectorServerSettings;
|
||||
};
|
||||
|
||||
/** Loader options: the shared source flags plus a single `--header` set that is
|
||||
* broadcast into every resolved server's settings. */
|
||||
/** Loader options: the shared source flags plus a single `--header` set and an
|
||||
* optional `--protocol-era`, both broadcast into every resolved server's
|
||||
* settings. */
|
||||
export type ServerLoadOptions = ServerConfigOptions & {
|
||||
headers?: Record<string, string>;
|
||||
/** `--protocol-era`: overrides the file's `protocolEra` (or the legacy
|
||||
* default) the way `--header` overrides its headers (#2208). */
|
||||
protocolEra?: ServerProtocolEra;
|
||||
/** Test injection; defaults to the selected store (see
|
||||
* `secret-store-selection.ts`) for catalog/config loads. */
|
||||
secretStore?: SecretStore;
|
||||
|
|
@ -51,7 +59,15 @@ export function headersToServerSettings(
|
|||
return undefined;
|
||||
}
|
||||
return {
|
||||
...defaultServerSettings(),
|
||||
headers: Object.entries(headers).map(([key, value]) => ({ key, value })),
|
||||
};
|
||||
}
|
||||
|
||||
/** A settings node with every field at its product default. */
|
||||
function defaultServerSettings(): InspectorServerSettings {
|
||||
return {
|
||||
headers: [],
|
||||
env: [],
|
||||
metadata: {},
|
||||
connectionTimeout: DEFAULT_CONNECTION_TIMEOUT_MS,
|
||||
|
|
@ -65,18 +81,29 @@ export function headersToServerSettings(
|
|||
}
|
||||
|
||||
/**
|
||||
* Overlay CLI `--header` values onto the settings lifted from the file. Only the
|
||||
* `headers` field is overridden — timeouts, OAuth, and the rest of the file's
|
||||
* settings are preserved.
|
||||
* Overlay `--header` / `--protocol-era` onto the settings lifted from the file
|
||||
* (or onto nothing, for an ad-hoc target). Only those two fields are overridden
|
||||
* — timeouts, OAuth, and the rest of the file's settings are preserved. Returns
|
||||
* `base` untouched when neither flag was given.
|
||||
*/
|
||||
function mergeSettings(
|
||||
base: InspectorServerSettings | undefined,
|
||||
headers?: Record<string, string>,
|
||||
overrides: Pick<ServerLoadOptions, "headers" | "protocolEra">,
|
||||
): InspectorServerSettings | undefined {
|
||||
const fromHeaders = headersToServerSettings(headers);
|
||||
if (!fromHeaders) return base;
|
||||
if (!base) return fromHeaders;
|
||||
return { ...base, headers: fromHeaders.headers };
|
||||
let settings = base;
|
||||
const fromHeaders = headersToServerSettings(overrides.headers);
|
||||
if (fromHeaders) {
|
||||
settings = settings
|
||||
? { ...settings, headers: fromHeaders.headers }
|
||||
: fromHeaders;
|
||||
}
|
||||
if (overrides.protocolEra) {
|
||||
settings = {
|
||||
...(settings ?? defaultServerSettings()),
|
||||
protocolEra: overrides.protocolEra,
|
||||
};
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -117,10 +144,11 @@ export async function loadServerEntries(
|
|||
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),
|
||||
// Deliberate broadcast: a single `--header` set (and `--protocol-era`)
|
||||
// is merged into EVERY server in the catalog/config (fine for the
|
||||
// common single-server case; for multi-server files, prefer per-server
|
||||
// settings in the file itself).
|
||||
settings: mergeSettings(entry.settings, serverOptions),
|
||||
};
|
||||
}
|
||||
return result;
|
||||
|
|
@ -135,7 +163,7 @@ export async function loadServerEntries(
|
|||
return {
|
||||
default: {
|
||||
config: configs[0]!,
|
||||
settings: headersToServerSettings(serverOptions.headers),
|
||||
settings: mergeSettings(undefined, serverOptions),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,11 +49,18 @@ export function createTransportNode(
|
|||
pipeStderr = false,
|
||||
onFetchRequest,
|
||||
onFetchResponseBody,
|
||||
onFetchStreamUpdate,
|
||||
authProvider,
|
||||
settings,
|
||||
interceptAuthChallenges = false,
|
||||
onAuthChallengeObserved,
|
||||
} = options;
|
||||
// Any of the three tracking callbacks earns a tracker: each is optional on
|
||||
// the tracker itself, and a caller that wants only stream updates (or only
|
||||
// bodies) must not be silently handed the bare fetch (#2318).
|
||||
const wantsFetchTracking = Boolean(
|
||||
onFetchRequest || onFetchResponseBody || onFetchStreamUpdate,
|
||||
);
|
||||
|
||||
// `optionsFetchFn` is the caller's whole fetch stack and already sits on top of
|
||||
// a proxy-aware base when one is needed — the Node clients install
|
||||
|
|
@ -68,10 +75,11 @@ export function createTransportNode(
|
|||
? createAuthChallengeObserverFetch(inner, onAuthChallengeObserved)
|
||||
: inner;
|
||||
const withTracking = (inner: typeof fetch): typeof fetch =>
|
||||
onFetchRequest
|
||||
wantsFetchTracking
|
||||
? createFetchTracker(inner, {
|
||||
trackRequest: onFetchRequest,
|
||||
updateResponseBody: onFetchResponseBody,
|
||||
updateStream: onFetchStreamUpdate,
|
||||
})
|
||||
: inner;
|
||||
// The observer sits *under* the interceptor so it still reports the
|
||||
|
|
|
|||
Loading…
Reference in a new issue