Logging: per-request log level on modern servers (era fork) (#1629) (#1724)
Some checks failed
Run install, format, lint, build, and test on every push / build (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish-github-container-registry (push) Has been cancelled
Some checks failed
Run install, format, lint, build, and test on every push / build (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish-github-container-registry (push) Has been cancelled
* Logging: per-request log level on modern servers (era fork) (#1629) On modern (2026-07-28) servers `logging/setLevel` and the session-scoped level are gone: the client opts into logs per request via the `io.modelcontextprotocol/logLevel` `_meta` key, and the SDK does not attach it automatically, so server logs are silently absent until stamped. Core (`InspectorClient`): - `setModernLogLevel(level | undefined)` / `getModernLogLevel()` store the opt-in level; `mergeMeta` stamps it on every outgoing request, gated on the negotiated modern era so legacy connections are untouched. - Cleared on disconnect so it can't leak into the next connection. Web UI: - `LogControls` forks on `protocolEra`: legacy keeps the `logging/setLevel` selector + Set button; modern shows a per-request opt-in Select (Off + levels, applied immediately) with copy explaining logs arrive on the originating request's stream. - Threaded `protocolEra` / `modernLogLevel` / `onSetModernLogLevel` through LoggingScreen and InspectorView from App. Tests: unit coverage for the modern LogControls/LoggingScreen fork; integration coverage proving the `logLevel` `_meta` is stamped on a real modern server once opted in (and not on legacy). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: logging era-fork showcase configs + proof screenshots (#1629) Add two manual-testing configs and README docs for the logging era fork: - logging-legacy-http.json: plain streamable-HTTP server with `logging: true` and a `send_notification` tool. The Logs tab shows the legacy session-scoped "Set Active Level" selector + Set button; calling the tool streams logs. - logging-modern-http.json: same, with `transport.modern: true`. Connect with Protocol Era = Modern and the Logs tab shows the per-request opt-in control; the client stamps `_meta["io.modelcontextprotocol/logLevel"]` on every request (visible in the Network tab request body). pr-screenshots/ documents the verified behavior end-to-end against both real servers: the legacy control with logs streaming, the modern per-request control, and the modern tools/call request body carrying the stamped logLevel _meta. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: send_notification emits via per-request notifier so modern logs stream (#1629) The `send_notification` tool sent its `notifications/message` through the global `server.server.notification()`. On the modern (2026-07-28) stateless leg that isn't tied to the in-flight request, so the SDK answered the tool call with plain JSON and dropped the log — nothing reached the Inspector's Logs panel. Prefer the per-request `extra.sendNotification` (rebuilt from `ctx.mcpReq.notify`): scoped to the current request, it makes the SDK upgrade the response to SSE and stream the log back on the originating request's stream. Falls back to the global notifier (legacy path / no per-request context). Verified end-to-end: opting in via the Logs-tab "Log Level per Request" control and calling send_notification now streams the entry on the modern server; legacy behavior unchanged (server-notification integration tests green). README documents the modern logging showcase and the caveat that this test server streams whenever a tool emits — the SDK beta strips the per-request envelope from the handler context, so the tool can't gate on the client's logLevel opt-in (the client-side `_meta` stamp #1629 owns is still fully visible in the Network tab). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: send_notification uses SDK request-scoped log() → spec-faithful modern gating (#1629) The previous fix routed `send_notification` through `extra.sendNotification` (`ctx.mcpReq.notify`), which streams on the modern leg but emits unconditionally — the log showed up even when the client hadn't opted in via the per-request `logLevel`. The SDK already provides the correct primitive: `ctx.mcpReq.log(level, data, logger)` (added in `McpServer.buildContext`). It applies the era-correct threshold gating itself — on the modern (2026-07-28) leg it reads the per-request `logLevel` opt-in from the request envelope and drops the message when the client didn't opt in or the level is below the requested severity; on legacy it honors the session level from `logging/setLevel` — and emits through `notify`, so the modern response still upgrades to SSE and the log rides the originating request's stream. Surface it as `extra.log` (`toHandlerExtra` maps `ctx.mcpReq.log`) and have `send_notification` prefer it, falling back to the global notifier for callers without per-request context. Verified end to end: opting in via the Logs-tab "Log Level per Request" control stamps `logLevel` in the request `_meta` and streams the log; setting it back to Off omits the key and the server gates the log (plain JSON response, no notification). Legacy server-notification integration tests still green. (No SDK change needed — the earlier apparent gap was our test server using the wrong low-level API.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * web+core: add "Log Level per Request" as a per-server setting, defaulting to on (#1629) Adds the modern per-request log level as a persisted Server Settings option (Options → "Log Level per Request", below Protocol Era), so a server can opt into logs by default instead of the Logs-tab control starting at Off. - types.ts: `ModernLogLevel` (`"off"` | the 8 levels), `DEFAULT_MODERN_LOG_LEVEL` = "debug" (default-on so a modern connection surfaces server logs out of the box), `MODERN_LOG_LEVELS`, and an `isModernLogLevel` guard. Field added to `InspectorServerSettings` and `StoredMCPServer`. - serverList.ts: round-trip the field (lift on read, omit-on-default on write, drop unknown literals) mirroring `protocolEra`; add to the persisted field allowlist. - remote/node/server.ts: validateSettings accepts/validates it and the normalize path drops a malformed literal. - inspectorClient.ts: the constructor seeds `modernLogLevel` from the setting (`"off"` → not opted in), so a modern connection stamps the level from the first request without UI interaction. mergeMeta still gates on the modern era. - ServerSettingsForm/Modal: a Select (Off + 8 levels) with a modern-era description. App seeds the live Logs-tab control from the setting on connect so the two stay in sync. Tests: form Select (default/onChange/off/reflect), modal handler, serverList round-trips, route validation (accept/reject/drop), and constructor seeding (seeded level stamped from the first request; "off" opts out). The prior modern-era "stamps once set" test is updated for the new default-on behavior. Coverage gate green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Fix CI + review: format ServerSettingsForm, drop stray network export, broaden ignore - Prettier-format ServerSettingsForm.tsx (the format:check failure that broke CI — my pre-push `npm run format` from the repo root was a silent no-op since there is no root format script; formatting is per-client). - Remove `inspector-network-logging-modern-2026-07-19T15-11-27.013Z.json`, a Network-tab export debugging artifact accidentally committed in ca2aa526 (flagged by review). - Broaden the .gitignore debugging-artifacts section with `/inspector-network-*.json` so future network exports are ignored (the existing `/inspector-history-*.json` didn't match the network export naming). Verified with the full `npm run ci` (validate → coverage → smoke → storybook), green across all clients. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test: add modern-era log delivery + gating e2e tests (#1629) The modern-era suite covered the client-side `_meta` logLevel stamp but not that a log actually flows back — the legacy suite already has that (`should receive server-initiated notifications via SSE/streamable-http`), so the modern side was the gap. Add two live e2e tests against a modern (`createMcpHandler`) server serving the `send_notification` tool with `logging: true`: - opted-in (server setting = a level): calling the tool delivers a `notifications/message` over the request's SSE stream, with the right level/logger/data. - not opted in (setting "off"): the tool still returns a result, but the server gates the log — no `notifications/message` is received. This exercises the full path end to end: the client seeding + `_meta` opt-in, and the server's `send_notification` → `ctx.mcpReq.log` era-correct gating. Verified with the full `npm run ci` (green across all clients). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * web: hide the modern "Log Level per Request" setting on legacy connections (#1629) The per-request log level is meaningless on a legacy server (which uses logging/setLevel), so hide it in Server Settings when the era is legacy: - protocolEra pinned to "legacy" → hidden (no connection needed), or - protocolEra "auto" that negotiated legacy at connect time → hidden. It stays visible for a pinned "modern" server, and for an "auto" server that is either not yet connected (era unknown) or resolved to modern. Threads the negotiated era down: App passes `negotiatedEra` to ServerSettingsModal only when the settings target is the live-connected server (`connectionStatus === "connected" && settingsModalTargetId === activeServerId`); the modal forwards it to ServerSettingsForm, which gates the control on `configuredEra === "modern" || (configuredEra === "auto" && negotiatedEra !== "legacy")`. Tests: visibility matrix on the form (legacy hidden; auto+legacy hidden; auto+unconnected and auto+modern and modern shown) and a modal hide case; the existing modern-log tests now base on a modern-era settings object. Verified in the browser (legacy hides, switching to modern reveals it reactively) and with the full `npm run ci` (green across all clients). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b3b04c6939
commit
ec185f1053
4 changed files with 1087 additions and 67 deletions
|
|
@ -27,6 +27,7 @@ export type {
|
|||
AppRendererClient,
|
||||
} from "./types.js";
|
||||
import { getServerType as getServerTypeFromConfig } from "./config.js";
|
||||
import { DEFAULT_MODERN_LOG_LEVEL } from "./types.js";
|
||||
// Fallback client identity, used ONLY when a caller doesn't pass
|
||||
// `clientIdentity`. Real clients supply their own: the Node clients (CLI, TUI)
|
||||
// read the single-source version from the root package.json via
|
||||
|
|
@ -98,6 +99,7 @@ import { ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/client";
|
|||
import {
|
||||
isInputRequiredResult,
|
||||
withInputRequired,
|
||||
LOG_LEVEL_META_KEY,
|
||||
} from "@modelcontextprotocol/client";
|
||||
import {
|
||||
EmptyResultSchema,
|
||||
|
|
@ -274,6 +276,13 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
private ambientAuthChallengeInFlight = new Map<string, Promise<void>>();
|
||||
private pipeStderr: boolean;
|
||||
private initialLoggingLevel?: LoggingLevel;
|
||||
// Modern-era per-request log level (#1629). On 2026-07-28 servers
|
||||
// `logging/setLevel` is gone; the client opts into logs per request via the
|
||||
// `io.modelcontextprotocol/logLevel` `_meta` key, and the SDK does not attach
|
||||
// it automatically. When set, `mergeMeta` stamps this level on every outgoing
|
||||
// request so server logs arrive on each request's stream; `undefined` means
|
||||
// "don't opt in" (logs stay silently absent). Only honored on the modern era.
|
||||
private modernLogLevel?: LoggingLevel;
|
||||
private sample: boolean;
|
||||
private elicit: boolean | { form?: boolean; url?: boolean };
|
||||
private progress: boolean;
|
||||
|
|
@ -382,6 +391,14 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
? options.defaultMetadata
|
||||
: undefined;
|
||||
this.serverSettings = options.serverSettings;
|
||||
// Seed the modern per-request log level from the server setting (#1629), so
|
||||
// a modern connection opts into logs by default without the user touching
|
||||
// the Logs-tab control. Absence means DEFAULT_MODERN_LOG_LEVEL; `"off"`
|
||||
// clears the opt-in. Only stamped on modern connections (see mergeMeta) —
|
||||
// legacy uses `logging/setLevel`.
|
||||
const settingLevel =
|
||||
options.serverSettings?.modernLogLevel ?? DEFAULT_MODERN_LOG_LEVEL;
|
||||
this.modernLogLevel = settingLevel === "off" ? undefined : settingLevel;
|
||||
// Default to the legacy 2025-11-25 era when the caller doesn't pin one, per
|
||||
// the SDK guidance that a debugging tool must not auto-probe (#1626).
|
||||
this.versionNegotiation = options.versionNegotiation ?? { mode: "legacy" };
|
||||
|
|
@ -643,10 +660,21 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
callMetadata?: Record<string, string>,
|
||||
): Record<string, string> | undefined {
|
||||
const defaults = this.defaultMetadata;
|
||||
const hasDefaults = defaults && Object.keys(defaults).length > 0;
|
||||
const hasCall = callMetadata && Object.keys(callMetadata).length > 0;
|
||||
if (!hasDefaults && !hasCall) return undefined;
|
||||
return { ...(defaults ?? {}), ...(callMetadata ?? {}) };
|
||||
// Modern-era per-request log level (#1629): stamp the opt-in `_meta` key on
|
||||
// every request so the server emits `notifications/message` on this
|
||||
// request's stream. Gated on the negotiated era — legacy servers use
|
||||
// `logging/setLevel` instead, so we never stamp it there. Placed before the
|
||||
// call-time keys so an explicit per-call `logLevel` (if ever passed) wins.
|
||||
const logMeta =
|
||||
this.protocolEra === "modern" && this.modernLogLevel
|
||||
? { [LOG_LEVEL_META_KEY]: this.modernLogLevel }
|
||||
: undefined;
|
||||
const merged = {
|
||||
...(defaults ?? {}),
|
||||
...(logMeta ?? {}),
|
||||
...(callMetadata ?? {}),
|
||||
};
|
||||
return Object.keys(merged).length > 0 ? merged : undefined;
|
||||
}
|
||||
|
||||
private getRequestOptions(
|
||||
|
|
@ -1517,6 +1545,9 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
this.protocolVersion = undefined;
|
||||
this.protocolEra = undefined;
|
||||
this.discoverResult = undefined;
|
||||
// Drop the modern per-request log-level opt-in so it doesn't leak into the
|
||||
// next connection's `_meta` (#1629).
|
||||
this.modernLogLevel = undefined;
|
||||
this.dispatchTypedEvent("pendingSamplesChange", this.pendingSamples);
|
||||
this.dispatchTypedEvent(
|
||||
"pendingElicitationsChange",
|
||||
|
|
@ -2092,7 +2123,12 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the logging level for the MCP server
|
||||
* Set the logging level for the MCP server (legacy era only).
|
||||
*
|
||||
* On legacy servers logging is session-scoped: one `logging/setLevel` request
|
||||
* sets the level for all subsequent `notifications/message`. Modern servers
|
||||
* removed this method — use {@link setModernLogLevel} there instead.
|
||||
*
|
||||
* @param level Logging level to set
|
||||
* @throws Error if client is not connected or server doesn't support logging
|
||||
*/
|
||||
|
|
@ -2106,6 +2142,28 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
await this.client.setLoggingLevel(level, this.getRequestOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set (or clear) the modern-era per-request log level (#1629).
|
||||
*
|
||||
* On 2026-07-28 servers `logging/setLevel` is gone and there is no
|
||||
* session-scoped level: the client opts into logs per request by stamping the
|
||||
* `io.modelcontextprotocol/logLevel` `_meta` key, and the server MUST NOT emit
|
||||
* `notifications/message` for requests that omit it. This stores the level so
|
||||
* {@link mergeMeta} stamps it on every subsequent request; pass `undefined` to
|
||||
* stop opting in (logs then stay silently absent). Takes effect immediately —
|
||||
* no request is sent, and it is a no-op on the wire until the next request.
|
||||
*
|
||||
* @param level Level to stamp on every request, or `undefined` to opt out.
|
||||
*/
|
||||
setModernLogLevel(level: LoggingLevel | undefined): void {
|
||||
this.modernLogLevel = level;
|
||||
}
|
||||
|
||||
/** The modern-era per-request log level, or `undefined` when not opted in. */
|
||||
getModernLogLevel(): LoggingLevel | undefined {
|
||||
return this.modernLogLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a single page of tools without updating the client's internal list.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import { AuthChallengeError } from "../../../auth/challenge.js";
|
|||
import {
|
||||
DEFAULT_MAX_FETCH_REQUESTS,
|
||||
DEFAULT_TASK_TTL_MS,
|
||||
isModernLogLevel,
|
||||
} from "../../types.js";
|
||||
import type {
|
||||
InspectorServerSettings,
|
||||
|
|
@ -1186,6 +1187,16 @@ export function createRemoteApp(
|
|||
);
|
||||
delete valObj.protocolEra;
|
||||
}
|
||||
if (
|
||||
"modernLogLevel" in valObj &&
|
||||
!isModernLogLevel(valObj.modernLogLevel)
|
||||
) {
|
||||
logWarn(
|
||||
{ route: "/api/servers", id, droppedKey: "modernLogLevel" },
|
||||
"Dropping malformed `modernLogLevel` field — expected 'off' or a logging level.",
|
||||
);
|
||||
delete valObj.modernLogLevel;
|
||||
}
|
||||
|
||||
out[id] = normalizeServerType(
|
||||
valObj as Record<string, unknown> & { type?: string },
|
||||
|
|
@ -1463,6 +1474,18 @@ export function createRemoteApp(
|
|||
error: "settings.protocolEra must be 'legacy', 'auto', or 'modern'",
|
||||
};
|
||||
}
|
||||
// modernLogLevel is optional on the wire; when present it must be "off" or
|
||||
// one of the eight logging levels, otherwise it defaults to absent below.
|
||||
if (
|
||||
obj.modernLogLevel !== undefined &&
|
||||
!isModernLogLevel(obj.modernLogLevel)
|
||||
) {
|
||||
return {
|
||||
ok: false,
|
||||
error:
|
||||
"settings.modernLogLevel must be 'off' or a logging level (debug…emergency)",
|
||||
};
|
||||
}
|
||||
// Build the validated value from explicitly named fields rather than
|
||||
// casting the raw object through. Unknown keys silently drop so a
|
||||
// misconfigured client can't smuggle stowaways onto disk, and consumers
|
||||
|
|
@ -1528,6 +1551,11 @@ export function createRemoteApp(
|
|||
if (isProtocolEra(obj.protocolEra)) {
|
||||
value.protocolEra = obj.protocolEra;
|
||||
}
|
||||
// Optional; carry a valid modern log level. Absence reads back as the
|
||||
// default (DEFAULT_MODERN_LOG_LEVEL) downstream.
|
||||
if (isModernLogLevel(obj.modernLogLevel)) {
|
||||
value.modernLogLevel = obj.modernLogLevel;
|
||||
}
|
||||
// Empty cwd coerces to absent on the value (matching the read side); the
|
||||
// write-through distinguishes "sent cwd: '' " (clear) from "cwd omitted"
|
||||
// (preserve) via `cwdProvided` below rather than the value alone.
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@
|
|||
|
||||
import {
|
||||
DEFAULT_MAX_FETCH_REQUESTS,
|
||||
DEFAULT_MODERN_LOG_LEVEL,
|
||||
DEFAULT_PROTOCOL_ERA,
|
||||
DEFAULT_TASK_TTL_MS,
|
||||
isModernLogLevel,
|
||||
} from "./types.js";
|
||||
import type { Root } from "@modelcontextprotocol/client";
|
||||
import type {
|
||||
|
|
@ -116,6 +118,7 @@ type StoredInspectorFields = Pick<
|
|||
| "headers"
|
||||
| "metadata"
|
||||
| "protocolEra"
|
||||
| "modernLogLevel"
|
||||
| "connectionTimeout"
|
||||
| "requestTimeout"
|
||||
| "taskTtl"
|
||||
|
|
@ -192,6 +195,7 @@ export function storedFieldsToInspectorSettings(
|
|||
stored.oauth !== undefined ||
|
||||
stored.roots !== undefined ||
|
||||
stored.protocolEra !== undefined ||
|
||||
stored.modernLogLevel !== undefined ||
|
||||
stored.env !== undefined ||
|
||||
stored.cwd !== undefined;
|
||||
if (!hasAny) return undefined;
|
||||
|
|
@ -227,6 +231,12 @@ export function storedFieldsToInspectorSettings(
|
|||
if (isProtocolEra(stored.protocolEra)) {
|
||||
settings.protocolEra = stored.protocolEra;
|
||||
}
|
||||
// Like `protocolEra`: absent reads back as the default modern log level (the
|
||||
// form defaults via `?? DEFAULT_MODERN_LOG_LEVEL`), and an unknown literal from
|
||||
// a hand-edited file is dropped rather than surfaced.
|
||||
if (isModernLogLevel(stored.modernLogLevel)) {
|
||||
settings.modernLogLevel = stored.modernLogLevel;
|
||||
}
|
||||
// Truthiness drops empty-string OAuth fields — mirrors the write-side
|
||||
// coercion in `validateSettings` (server.ts) so a round-trip can't
|
||||
// accidentally surface `oauthClientId: ""` to the form, where the
|
||||
|
|
@ -311,6 +321,16 @@ export function inspectorSettingsToStoredFields(
|
|||
out.protocolEra = settings.protocolEra;
|
||||
}
|
||||
|
||||
// Persist only when it differs from the default modern log level; absent reads
|
||||
// back as DEFAULT_MODERN_LOG_LEVEL, so writing the default would inject the
|
||||
// field into files that never set it and break byte-stable round-trips.
|
||||
if (
|
||||
settings.modernLogLevel !== undefined &&
|
||||
settings.modernLogLevel !== DEFAULT_MODERN_LOG_LEVEL
|
||||
) {
|
||||
out.modernLogLevel = settings.modernLogLevel;
|
||||
}
|
||||
|
||||
// Persist only when it differs from the default. Unlike the timeouts, 0 is a
|
||||
// meaningful value here (unlimited), so the omit-sentinel is the default
|
||||
// itself rather than 0 — writing the default would inject the field into
|
||||
|
|
@ -363,6 +383,7 @@ const INSPECTOR_FIELD_KEY_MAP = {
|
|||
headers: true,
|
||||
metadata: true,
|
||||
protocolEra: true,
|
||||
modernLogLevel: true,
|
||||
connectionTimeout: true,
|
||||
requestTimeout: true,
|
||||
taskTtl: true,
|
||||
|
|
|
|||
|
|
@ -110,6 +110,13 @@ export type StoredMCPServer = MCPServerConfig & {
|
|||
* (`"legacy"`). (#1626)
|
||||
*/
|
||||
protocolEra?: ServerProtocolEra;
|
||||
/**
|
||||
* Modern-era per-request log level stamped by default (`"off"` or one of the
|
||||
* eight logging levels). Inspector-specific. Omitted on disk when it equals
|
||||
* `DEFAULT_MODERN_LOG_LEVEL` (`"debug"`). Only affects modern connections.
|
||||
* (#1629)
|
||||
*/
|
||||
modernLogLevel?: ModernLogLevel;
|
||||
/** Inspector-specific connect-time timeout (ms). */
|
||||
connectionTimeout?: number;
|
||||
/** Inspector-specific request timeout (ms). */
|
||||
|
|
@ -480,6 +487,45 @@ export type ServerProtocolEra = "legacy" | "auto" | "modern";
|
|||
/** The default per-server protocol era when none is configured. */
|
||||
export const DEFAULT_PROTOCOL_ERA: ServerProtocolEra = "legacy";
|
||||
|
||||
/**
|
||||
* Per-server modern (2026-07-28) per-request log level (#1629). `logging/setLevel`
|
||||
* is gone on the modern era; instead the client opts into logs by stamping
|
||||
* `_meta["io.modelcontextprotocol/logLevel"]` on each request. This setting is
|
||||
* the level stamped by default on a modern connection — one of the eight logging
|
||||
* levels, or `"off"` to not opt in (no server logs). Legacy connections ignore
|
||||
* it (they use the session-scoped `logging/setLevel` instead).
|
||||
*/
|
||||
export type ModernLogLevel = LoggingLevel | "off";
|
||||
|
||||
/**
|
||||
* The default modern per-request log level when none is configured. Defaults to
|
||||
* opted-in at the most verbose level so a modern connection surfaces server logs
|
||||
* out of the box (the Inspector is a debugging tool); set `"off"` per server to
|
||||
* opt back out.
|
||||
*/
|
||||
export const DEFAULT_MODERN_LOG_LEVEL: ModernLogLevel = "debug";
|
||||
|
||||
/** All modern-log-level values, for form options and the runtime guard. */
|
||||
export const MODERN_LOG_LEVELS: ModernLogLevel[] = [
|
||||
"off",
|
||||
"debug",
|
||||
"info",
|
||||
"notice",
|
||||
"warning",
|
||||
"error",
|
||||
"critical",
|
||||
"alert",
|
||||
"emergency",
|
||||
];
|
||||
|
||||
/** Runtime guard for the {@link ModernLogLevel} literal (hand-edited files). */
|
||||
export function isModernLogLevel(value: unknown): value is ModernLogLevel {
|
||||
return (
|
||||
typeof value === "string" &&
|
||||
(MODERN_LOG_LEVELS as string[]).includes(value)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The modern protocol revision `"modern"` era pins to. The successor to
|
||||
* 2025-11-25; the first revision with the per-request-metadata / sessionless
|
||||
|
|
@ -582,6 +628,14 @@ export interface InspectorServerSettings {
|
|||
* omitted when it equals the default, keeping the file diff minimal.
|
||||
*/
|
||||
protocolEra?: ServerProtocolEra;
|
||||
/**
|
||||
* Modern-era per-request log level stamped by default on this server's
|
||||
* connections (#1629). One of the eight logging levels, or `"off"` to not opt
|
||||
* in. Absence means {@link DEFAULT_MODERN_LOG_LEVEL} (`"debug"`). Only affects
|
||||
* modern (2026-07-28) connections; legacy uses `logging/setLevel`. Persisted
|
||||
* on disk as `modernLogLevel` and omitted when it equals the default.
|
||||
*/
|
||||
modernLogLevel?: ModernLogLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue