From 121f9aa172138dc0f5aa4b346a12d43b386e3f21 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 19:55:13 +0000 Subject: [PATCH] =?UTF-8?q?Phase=201:=20CI=20debugging=20refactor=20?= =?UTF-8?q?=E2=80=94=20structured=20output,=20discover,=20capability=20gat?= =?UTF-8?q?ing,=20log=20capture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add structured output envelope (--structured) with error taxonomy (transport/capability/protocol/application/validation), timing, and log capture for CI-consumable diagnostics - Add discover pseudo-method that returns server shape: capabilities, tools, resources, and prompts in a single invocation - Add capability gating: methods now fail fast with a clear category if the server lacks the required capability - Add --fail-on-error flag to exit 1 on application-level tool errors - Add ping method support - Capture logging/message notifications from the server and include them in structured output or emit to stderr in raw mode - Fix SSE transport in test server: add POST /messages route, remove double-start of SSEServerTransport (connect() already calls start() internally) - Add 23 tests covering all new features across stdio, HTTP, and SSE transports - Add CLI test step to main.yml workflow - Write CI_DEBUGGING_REFACTOR.md design document --- .github/workflows/main.yml | 12 + CI_DEBUGGING_REFACTOR.md | 251 +++++++++ cli/__tests__/ci-debugging.test.ts | 596 ++++++++++++++++++++++ cli/__tests__/helpers/test-server-http.ts | 15 +- cli/src/client/connection.ts | 27 + cli/src/client/index.ts | 1 + cli/src/discover.ts | 62 +++ cli/src/index.ts | 135 ++++- cli/src/output.ts | 96 ++++ package-lock.json | 107 ++-- 10 files changed, 1258 insertions(+), 44 deletions(-) create mode 100644 CI_DEBUGGING_REFACTOR.md create mode 100644 cli/__tests__/ci-debugging.test.ts create mode 100644 cli/src/discover.ts create mode 100644 cli/src/output.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d01f7175b..07d4f3177 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,6 +37,18 @@ jobs: working-directory: ./client run: npm test + - name: Run CLI tests + working-directory: ./cli + run: | + cd .. + npm ci --ignore-scripts + cd cli + npm run build + npm test + env: + NPM_CONFIG_YES: true + CI: true + - run: npm run build publish: diff --git a/CI_DEBUGGING_REFACTOR.md b/CI_DEBUGGING_REFACTOR.md new file mode 100644 index 000000000..9ca202375 --- /dev/null +++ b/CI_DEBUGGING_REFACTOR.md @@ -0,0 +1,251 @@ +# CI Debugging Refactor: Inspector as an Automated MCP Server Debugging Tool + +## Goal + +Transform the MCP Inspector CLI into a CI-first debugging tool that AI agents (Claude, etc.) can use to programmatically test, validate, and diagnose MCP servers — without a browser UI. + +This is **not** another general-purpose MCP client. For interactive command-line use of MCP servers, use [mcpc](https://github.com/apify/mcp-cli). Inspector's CLI is the **debugging companion**: structured diagnostics, batch debugging workflows, and CI-clean semantics. + +--- + +## Current State + +The Inspector CLI (`--cli` mode) supports single-method invocations across three transports (stdio, SSE, Streamable HTTP): + +| Method | Implemented | Tested | Notes | +| -------------------------- | ----------- | ------ | ----------------------------------------------- | +| `tools/list` | ✓ | ✓ | | +| `tools/call` | ✓ | ✓ | Fetches schema first for type coercion (2 RPCs) | +| `resources/list` | ✓ | ✗ | Zero test coverage | +| `resources/read` | ✓ | stdio | Only tested over stdio | +| `resources/templates/list` | ✓ | ✗ | Zero test coverage | +| `prompts/list` | ✓ | ✓ | | +| `prompts/get` | ✓ | ✓ | | +| `logging/setLevel` | ✓ | HTTP | Sets level but discards all log notifications | +| `ping` | ✗ | | | +| `discover` | ✗ | | | +| `completion/complete` | ✗ | | | + +**Key architectural limitations:** + +1. **One method per process** — each invocation connects, runs one call, disconnects. Stdio servers respawn every time. +2. **No structured output envelope** — raw JSON on stdout, bare strings on stderr. No programmatic error categorization. +3. **Server logs discarded** — debug logging is enabled on connect via `logging/setLevel`, but the `logging/message` notifications are never captured. +4. **No capability gating** — methods are dispatched without checking server capabilities. Failures are ambiguous. +5. **Exit code ambiguity** — server-side errors (`isError: true`) exit 0; only client-side failures exit non-zero. Invisible to CI pipelines using `set -e`. + +--- + +## Differentiation vs. mcpc + +| Feature | mcpc (apify) | Inspector CLI (this refactor) | +| ---------------------- | ------------------------- | ---------------------------------------- | +| Primary audience | Interactive CLI users | AI agents and CI pipelines | +| Output format | Raw MCP JSON (`--json`) | Structured envelope with diagnostics | +| Error handling | Undocumented exit codes | Typed error taxonomy, `--fail-on-error` | +| Session model | Persistent named sessions | One-shot (default) + batch script mode | +| Capability discovery | Implicit per-method | Explicit `discover` command | +| Server log capture | ✗ | ✓ Buffer `logging/message` notifications | +| Sampling/elicitation | ✗ | ✓ Reject + capture for inspection | +| Batch workflows | Shell scripts | JSON script with `onError` control flow | +| CI exit code semantics | ✗ | ✓ `--fail-on-error` | + +--- + +## Design Decisions + +### 1. Invocation Model: One-Shot + Batch Script + +**Default** remains one-shot (backward compatible): connect, run one method, output result, disconnect. + +**New**: `--script ` flag accepts a JSON array of operations executed sequentially on a single persistent connection. + +```json +[ + { "method": "discover" }, + { + "method": "tools/call", + "toolName": "echo", + "toolArgs": { "message": "hello" }, + "onError": "continue" + }, + { "method": "resources/list", "onError": "stop" }, + { + "method": "resources/read", + "uri": "demo://example", + "onError": "skip-to:5" + }, + { "method": "ping" } +] +``` + +**`onError` control flow** (per step): + +| Value | Behavior | +| ------------- | --------------------------------------------- | +| `"stop"` | Abort script, return results so far (default) | +| `"continue"` | Record the error, proceed to next step | +| `"skip-to:N"` | Jump to step index N on error (0-based) | + +**Rationale**: Claude expresses the whole debugging plan declaratively in one tool call. No shell scripting, no jq parsing between steps. + +### 2. Structured Output Envelope + +Enabled via `--structured` flag. Raw JSON output remains the default for backward compatibility. + +```json +{ + "structuredVersion": 1, + "success": true, + "method": "tools/call", + "durationMs": 234, + "result": { "content": [{ "type": "text", "text": "Echo: hello" }] }, + "error": null, + "logs": [ + { + "level": "debug", + "message": "tool echo invoked", + "timestamp": "2026-01-30T12:00:00.000Z" + } + ] +} +``` + +In script mode, the top level becomes an array of these envelopes (one per step). + +**Error taxonomy** (mutually exclusive `error.category`): + +| Category | Meaning | +| ------------- | -------------------------------------------------------------------- | +| `transport` | Could not connect — bad URL, subprocess crash, ECONNREFUSED, timeout | +| `capability` | Server does not support the requested method | +| `protocol` | Malformed JSON-RPC, handshake failure | +| `application` | Tool/resource/prompt returned an error in its content | +| `validation` | Client-side failure — missing required arg, bad metadata | + +### 3. Server Log Capture + +Register a `logging/message` notification handler on every connection. Buffer all log messages for the session lifetime. Include them in the structured output envelope. + +In non-structured mode, emit captured logs to stderr. + +**Rationale**: The CLI already enables debug-level logging on connect. Discarding the notifications is an existing bug — fixing it is the single highest-value diagnostic change. + +### 4. `discover` Command + +A pseudo-method that connects once and returns the full server shape: + +```json +{ + "serverInfo": { "name": "my-server", "version": "1.0.0" }, + "capabilities": { + "tools": true, + "resources": true, + "prompts": false, + "logging": true, + "completions": false + }, + "tools": [...], + "resources": [...], + "prompts": [] +} +``` + +Runs: `initialize` → read capabilities → conditionally call `tools/list`, `resources/list`, `prompts/list`. One connection, one output. + +### 5. Exit Code Contract + +| Flag | Server `isError: true` | Client validation error | Transport error | +| ----------------- | ---------------------- | ----------------------- | --------------- | +| (default) | exit 0 | exit 1 | exit 1 | +| `--fail-on-error` | exit 1 | exit 1 | exit 1 | + +**Rationale**: Backward compatible by default. CI pipelines opt into strict semantics explicitly. + +### 6. Sampling / Elicitation Policy + +Default policy: **reject/decline all** server-initiated requests. The incoming request payloads are captured and included in the structured output envelope so the caller can inspect what the server attempted. + +Rationale: No user to approve in a headless tool. Reject is safe. Capture provides visibility. + +### 7. Capability Gating + +Before dispatching any method, check that the server's `initialize` response advertises the relevant capability. If not, fail immediately with a `capability` category error. + +--- + +## Implementation Phases + +### Phase 1 — Debugging Primitives + +**New files:** + +- `cli/src/output.ts` — Output envelope formatting, error categorization +- `cli/src/discover.ts` — Capability discovery logic + +**Modified files:** + +- `cli/src/index.ts` — Add `discover` and `ping` methods; add `--structured` and `--fail-on-error` flags; add capability gating before dispatch +- `cli/src/client/connection.ts` — Register `logging/message` notification handler; expose captured logs +- `cli/src/error-handler.ts` — Produce categorized `StructuredError` objects +- `.github/workflows/main.yml` — Add CLI test step (currently only in narrow `cli_tests.yml`) + +**New tests:** + +- `cli/__tests__/ci-debugging.test.ts` — Covers: `discover`, structured output, log capture, exit codes, `resources/list` (zero coverage today), `resources/templates/list` (zero coverage today), SSE transport success paths + +### Phase 2 — Batch Debugging Workflows + +**New files:** + +- `cli/src/script.ts` — Script parser, validator, and sequential executor with `onError` control flow + +**Modified files:** + +- `cli/src/index.ts` — Wire `--script` flag into the dispatch path + +**New tests:** + +- Multi-operation script over stdio +- `onError` control flow (stop, continue, skip-to) +- Malformed script validation + +### Phase 3 — Advanced Diagnostics + +- Sampling/elicitation capture (reject + include in envelope) +- `completion/complete` subcommand +- `--watch ` notification capture mode + +--- + +## Out of Scope + +- Browser-based OAuth flows (require human interaction; use mcpc for these) +- Full MCP server lifecycle management (we connect to servers, not manage them) +- Interactive shell (mcpc does this) +- Performance optimization of the double-RPC in `tools/call` +- Streaming output during long-running tool calls (delivered atomically on completion) + +--- + +## File Layout After Refactor + +``` +cli/ +├── src/ +│ ├── cli.ts # Entry point (unchanged) +│ ├── index.ts # Main dispatch — refactored with capability gating, new flags +│ ├── transport.ts # Transport factory (unchanged) +│ ├── error-handler.ts # Produces StructuredError with category +│ ├── output.ts # NEW: envelope formatting, structured/raw modes +│ ├── discover.ts # NEW: capability discovery + list enumeration +│ ├── script.ts # NEW (Phase 2): script parser and executor +│ └── client/ +│ ├── connection.ts # Log capture via logging/message handler +│ ├── tools.ts # Unchanged +│ ├── resources.ts # Unchanged +│ └── prompts.ts # Unchanged +└── __tests__/ + ├── ci-debugging.test.ts # NEW: CI-focused coverage + └── helpers/ # Unchanged +``` diff --git a/cli/__tests__/ci-debugging.test.ts b/cli/__tests__/ci-debugging.test.ts new file mode 100644 index 000000000..9613f692e --- /dev/null +++ b/cli/__tests__/ci-debugging.test.ts @@ -0,0 +1,596 @@ +/** + * ci-debugging.test.ts + * + * Tests for CLI behaviors that matter when used as a CI debugging tool: + * - discover command (capability enumeration) + * - --structured output envelope + * - Server log capture + * - --fail-on-error exit code semantics + * - ping method + * - Capability gating + * - resources/list and resources/templates/list (previously zero coverage) + * - SSE transport success paths (previously only failure-path tested) + */ + +import { describe, it, expect } from "vitest"; +import { runCli } from "./helpers/cli-runner.js"; +import { + expectCliSuccess, + expectCliFailure, + expectValidJson, +} from "./helpers/assertions.js"; +import { getTestMcpServerCommand } from "./helpers/test-server-stdio.js"; +import { createTestServerHttp } from "./helpers/test-server-http.js"; +import { + createEchoTool, + createTestServerInfo, + createArchitectureResource, + createTestEnvResource, + createSimplePrompt, +} from "./helpers/test-fixtures.js"; + +/** Builds args for stdio: [command, ...cmdArgs, "--cli", ...flags] */ +function stdioCliArgs(...flags: string[]): string[] { + const { command, args } = getTestMcpServerCommand(); + return [command, ...args, "--cli", ...flags]; +} + +/** Builds args for HTTP: [url, "--cli", "--transport", "http", ...flags] */ +function httpCliArgs(url: string, ...flags: string[]): string[] { + return [url, "--cli", "--transport", "http", ...flags]; +} + +/** Builds args for SSE: [url, "--cli", "--transport", "sse", ...flags] */ +function sseCliArgs(url: string, ...flags: string[]): string[] { + return [url, "--cli", "--transport", "sse", ...flags]; +} + +// --------------------------------------------------------------------------- +// 1. discover command +// --------------------------------------------------------------------------- +describe("CI Debugging: discover", () => { + it("should return full server shape via stdio", async () => { + const result = await runCli(stdioCliArgs("--method", "discover")); + + expectCliSuccess(result); + const json = expectValidJson(result); + + expect(json).toHaveProperty("serverInfo"); + expect(json.serverInfo.name).toBe("test-mcp-server"); + + expect(json).toHaveProperty("capabilities"); + expect(json.capabilities.tools).toBe(true); + expect(json.capabilities.resources).toBe(true); + expect(json.capabilities.prompts).toBe(true); + + expect(json).toHaveProperty("tools"); + expect(Array.isArray(json.tools)).toBe(true); + expect(json.tools.length).toBeGreaterThan(0); + + expect(json).toHaveProperty("resources"); + expect(Array.isArray(json.resources)).toBe(true); + expect(json.resources.length).toBeGreaterThan(0); + + expect(json).toHaveProperty("prompts"); + expect(Array.isArray(json.prompts)).toBe(true); + expect(json.prompts.length).toBeGreaterThan(0); + }, 15000); + + it("should return discover via HTTP transport", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo("http-discover-server", "2.0.0"), + tools: [createEchoTool()], + resources: [createArchitectureResource()], + prompts: [createSimplePrompt()], + }); + + try { + await server.start("http"); + + const result = await runCli( + httpCliArgs(`${server.getUrl()}/mcp`, "--method", "discover"), + ); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json.serverInfo.name).toBe("http-discover-server"); + expect(json.capabilities.tools).toBe(true); + expect(json.tools.length).toBe(1); + expect(json.tools[0].name).toBe("echo"); + } finally { + await server.stop(); + } + }); + + it("should reflect absent capabilities accurately", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo("tools-only"), + tools: [createEchoTool()], + }); + + try { + await server.start("http"); + + const result = await runCli( + httpCliArgs(`${server.getUrl()}/mcp`, "--method", "discover"), + ); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json.capabilities.tools).toBe(true); + expect(json.capabilities.resources).toBe(false); + expect(json.capabilities.prompts).toBe(false); + expect(json.tools.length).toBe(1); + expect(json.resources).toHaveLength(0); + expect(json.prompts).toHaveLength(0); + } finally { + await server.stop(); + } + }); +}); + +// --------------------------------------------------------------------------- +// 2. ping method +// --------------------------------------------------------------------------- +describe("CI Debugging: ping", () => { + it("should ping a stdio server successfully", async () => { + const result = await runCli(stdioCliArgs("--method", "ping")); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(typeof json).toBe("object"); + }); + + it("should ping an HTTP server successfully", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start("http"); + + const result = await runCli( + httpCliArgs(`${server.getUrl()}/mcp`, "--method", "ping"), + ); + + expectCliSuccess(result); + expectValidJson(result); + } finally { + await server.stop(); + } + }); +}); + +// --------------------------------------------------------------------------- +// 3. --structured output envelope +// --------------------------------------------------------------------------- +describe("CI Debugging: --structured output", () => { + it("should return structured envelope on success", async () => { + const result = await runCli( + stdioCliArgs("--method", "tools/list", "--structured"), + ); + + expectCliSuccess(result); + const json = expectValidJson(result); + + expect(json.structuredVersion).toBe(1); + expect(json.success).toBe(true); + expect(json.method).toBe("tools/list"); + expect(typeof json.durationMs).toBe("number"); + expect(json.durationMs).toBeGreaterThanOrEqual(0); + expect(json.result).toBeTruthy(); + expect(json.result.tools).toBeDefined(); + expect(json.error).toBeNull(); + expect(Array.isArray(json.logs)).toBe(true); + }); + + it("should return structured envelope with error on transport failure", async () => { + const result = await runCli( + httpCliArgs( + "http://localhost:19999/mcp", + "--method", + "tools/list", + "--structured", + ), + { timeout: 8000 }, + ); + + expect(result.exitCode).toBe(1); + const json = expectValidJson(result); + + expect(json.structuredVersion).toBe(1); + expect(json.success).toBe(false); + expect(json.error).toBeTruthy(); + expect(json.error.category).toBe("transport"); + expect(json.error.code).toBe("TRANSPORT_ERROR"); + expect(typeof json.error.message).toBe("string"); + expect(json.error.message.length).toBeGreaterThan(0); + }); + + it("should include logs array in structured output", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + logging: true, + }); + + try { + await server.start("http"); + + const result = await runCli( + httpCliArgs( + `${server.getUrl()}/mcp`, + "--method", + "tools/list", + "--structured", + ), + ); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(Array.isArray(json.logs)).toBe(true); + } finally { + await server.stop(); + } + }); + + it("should return capability error in structured envelope", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo("no-resources"), + tools: [createEchoTool()], + }); + + try { + await server.start("http"); + + const result = await runCli( + httpCliArgs( + `${server.getUrl()}/mcp`, + "--method", + "resources/list", + "--structured", + ), + ); + + expect(result.exitCode).toBe(1); + const json = expectValidJson(result); + expect(json.success).toBe(false); + expect(json.error.category).toBe("capability"); + expect(json.error.message).toContain("resources"); + } finally { + await server.stop(); + } + }); +}); + +// --------------------------------------------------------------------------- +// 4. --fail-on-error exit code semantics +// --------------------------------------------------------------------------- +describe("CI Debugging: --fail-on-error", () => { + it("should exit 0 on server tool error without --fail-on-error", async () => { + const result = await runCli( + stdioCliArgs( + "--method", + "tools/call", + "--tool-name", + "nonexistent-tool-xyz", + "--tool-arg", + "x=1", + ), + ); + + expect(result.exitCode).toBe(0); + const json = expectValidJson(result); + expect(json.isError).toBe(true); + }); + + it("should exit 1 on server tool error with --fail-on-error", async () => { + const result = await runCli( + stdioCliArgs( + "--method", + "tools/call", + "--tool-name", + "nonexistent-tool-xyz", + "--tool-arg", + "x=1", + "--fail-on-error", + ), + ); + + expect(result.exitCode).toBe(1); + }); + + it("should exit 0 on successful tool call with --fail-on-error", async () => { + const result = await runCli( + stdioCliArgs( + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=hello", + "--fail-on-error", + ), + ); + + expectCliSuccess(result); + }); +}); + +// --------------------------------------------------------------------------- +// 5. Capability gating +// --------------------------------------------------------------------------- +describe("CI Debugging: capability gating", () => { + it("should fail with capability error when server lacks resources", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo("tools-only-gate"), + tools: [createEchoTool()], + }); + + try { + await server.start("http"); + + const result = await runCli( + httpCliArgs(`${server.getUrl()}/mcp`, "--method", "resources/list"), + ); + + expectCliFailure(result); + expect(result.stderr).toContain("resources"); + expect(result.stderr).toContain("does not support"); + } finally { + await server.stop(); + } + }); + + it("should fail with capability error when server lacks prompts", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo("tools-only-prompts"), + tools: [createEchoTool()], + }); + + try { + await server.start("http"); + + const result = await runCli( + httpCliArgs(`${server.getUrl()}/mcp`, "--method", "prompts/list"), + ); + + expectCliFailure(result); + expect(result.stderr).toContain("prompts"); + } finally { + await server.stop(); + } + }); + + it("should allow discover and ping on any server regardless of capabilities", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo("minimal"), + tools: [createEchoTool()], + }); + + try { + await server.start("http"); + + const discoverResult = await runCli( + httpCliArgs(`${server.getUrl()}/mcp`, "--method", "discover"), + ); + expectCliSuccess(discoverResult); + + const pingResult = await runCli( + httpCliArgs(`${server.getUrl()}/mcp`, "--method", "ping"), + ); + expectCliSuccess(pingResult); + } finally { + await server.stop(); + } + }, 15000); +}); + +// --------------------------------------------------------------------------- +// 6. resources/list — previously zero test coverage +// --------------------------------------------------------------------------- +describe("CI Debugging: resources/list", () => { + it("should list resources via stdio transport", async () => { + const result = await runCli(stdioCliArgs("--method", "resources/list")); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("resources"); + expect(Array.isArray(json.resources)).toBe(true); + expect(json.resources.length).toBeGreaterThanOrEqual(4); + + const uris = json.resources.map((r: { uri: string }) => r.uri); + expect(uris).toContain("demo://resource/static/document/architecture.md"); + expect(uris).toContain("test://env"); + }); + + it("should list resources via HTTP transport", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + resources: [createArchitectureResource(), createTestEnvResource()], + }); + + try { + await server.start("http"); + + const result = await runCli( + httpCliArgs(`${server.getUrl()}/mcp`, "--method", "resources/list"), + ); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json.resources.length).toBe(2); + } finally { + await server.stop(); + } + }); +}); + +// --------------------------------------------------------------------------- +// 7. resources/templates/list — previously zero test coverage +// --------------------------------------------------------------------------- +describe("CI Debugging: resources/templates/list", () => { + it("should return template list via stdio", async () => { + const result = await runCli( + stdioCliArgs("--method", "resources/templates/list"), + ); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("resourceTemplates"); + expect(Array.isArray(json.resourceTemplates)).toBe(true); + }); +}); + +// --------------------------------------------------------------------------- +// 8. SSE transport success paths — previously only failure tested +// --------------------------------------------------------------------------- +describe("CI Debugging: SSE transport success", () => { + it("should list tools over SSE", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo("sse-tools"), + tools: [createEchoTool()], + }); + + try { + await server.start("sse"); + + const result = await runCli( + sseCliArgs(`${server.getUrl()}/mcp`, "--method", "tools/list"), + ); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json.tools.length).toBeGreaterThan(0); + expect(json.tools[0].name).toBe("echo"); + } finally { + await server.stop(); + } + }); + + it("should call a tool over SSE", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo("sse-call"), + tools: [createEchoTool()], + }); + + try { + await server.start("sse"); + + const result = await runCli( + sseCliArgs( + `${server.getUrl()}/mcp`, + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=hello from SSE", + ), + ); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json.content).toBeDefined(); + } finally { + await server.stop(); + } + }); + + it("should discover over SSE", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo("sse-discover", "3.0.0"), + tools: [createEchoTool()], + prompts: [createSimplePrompt()], + }); + + try { + await server.start("sse"); + + const result = await runCli( + sseCliArgs(`${server.getUrl()}/mcp`, "--method", "discover"), + ); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json.serverInfo.name).toBe("sse-discover"); + expect(json.capabilities.tools).toBe(true); + expect(json.capabilities.prompts).toBe(true); + } finally { + await server.stop(); + } + }); +}); + +// --------------------------------------------------------------------------- +// 9. Multi-step CI flow: discover then call +// --------------------------------------------------------------------------- +describe("CI Debugging: multi-step discover-then-call", () => { + it("should discover tools then call one in sequence", async () => { + const { command, args } = getTestMcpServerCommand(); + + // Step 1: discover + const discoverResult = await runCli([ + command, + ...args, + "--cli", + "--method", + "discover", + ]); + expectCliSuccess(discoverResult); + const discoverJson = expectValidJson(discoverResult); + const toolNames = discoverJson.tools.map((t: { name: string }) => t.name); + expect(toolNames).toContain("echo"); + + // Step 2: call discovered tool + const callResult = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=ci-flow-test", + ]); + expectCliSuccess(callResult); + const callJson = expectValidJson(callResult); + expect(callJson.content[0].text).toBe("Echo: ci-flow-test"); + }, 15000); + + it("should discover resources then read one", async () => { + const { command, args } = getTestMcpServerCommand(); + + // Step 1: discover + const discoverResult = await runCli([ + command, + ...args, + "--cli", + "--method", + "discover", + ]); + expectCliSuccess(discoverResult); + const discoverJson = expectValidJson(discoverResult); + const archResource = discoverJson.resources.find( + (r: { uri: string }) => + r.uri === "demo://resource/static/document/architecture.md", + ); + expect(archResource).toBeDefined(); + + // Step 2: read + const readResult = await runCli([ + command, + ...args, + "--cli", + "--method", + "resources/read", + "--uri", + archResource.uri, + ]); + expectCliSuccess(readResult); + const readJson = expectValidJson(readResult); + expect(readJson.contents[0].text).toContain("Architecture Documentation"); + }, 15000); +}); diff --git a/cli/__tests__/helpers/test-server-http.ts b/cli/__tests__/helpers/test-server-http.ts index 4626ef516..454229a12 100644 --- a/cli/__tests__/helpers/test-server-http.ts +++ b/cli/__tests__/helpers/test-server-http.ts @@ -308,11 +308,23 @@ export class TestServerHttp { // Create HTTP server this.httpServer = createHttpServer(app); + let activeSseTransport: SSEServerTransport | undefined; + + // POST endpoint for SSE messages + app.post("/messages", async (req: Request, res: Response) => { + if (activeSseTransport) { + await activeSseTransport.handlePostMessage(req, res, req.body); + } else { + res.status(503).json({ error: "No active SSE session" }); + } + }); + // For SSE, we need to set up an Express route that creates the transport per request // This is a simplified version - SSE transport is created per connection app.get("/mcp", async (req: Request, res: Response) => { this.currentRequestHeaders = extractHeaders(req); - const sseTransport = new SSEServerTransport("/mcp", res); + const sseTransport = new SSEServerTransport("/messages", res); + activeSseTransport = sseTransport; // Intercept messages const originalOnMessage = sseTransport.onmessage; @@ -365,7 +377,6 @@ export class TestServerHttp { }; await this.mcpServer.connect(sseTransport); - await sseTransport.start(); }); // Note: SSE transport is created per request, so we don't store a single instance diff --git a/cli/src/client/connection.ts b/cli/src/client/connection.ts index dcbe8e518..8f30ca336 100644 --- a/cli/src/client/connection.ts +++ b/cli/src/client/connection.ts @@ -1,5 +1,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { LoggingMessageNotificationSchema } from "@modelcontextprotocol/sdk/types.js"; +import type { LogEntry } from "../output.js"; import { McpResponse } from "./types.js"; export const validLogLevels = [ @@ -12,6 +14,16 @@ export const validLogLevels = [ export type LogLevel = (typeof validLogLevels)[number]; +const logBuffer: LogEntry[] = []; + +export function getCapturedLogs(): LogEntry[] { + return [...logBuffer]; +} + +export function clearCapturedLogs(): void { + logBuffer.length = 0; +} + export async function connect( client: Client, transport: Transport, @@ -19,6 +31,21 @@ export async function connect( try { await client.connect(transport); + client.setNotificationHandler( + LoggingMessageNotificationSchema, + (notification) => { + logBuffer.push({ + level: notification.params.level, + logger: notification.params.logger, + message: + typeof notification.params.data === "string" + ? notification.params.data + : JSON.stringify(notification.params.data), + timestamp: new Date().toISOString(), + }); + }, + ); + if (client.getServerCapabilities()?.logging) { // default logging level is undefined in the spec, but the user of the // inspector most likely wants debug. diff --git a/cli/src/client/index.ts b/cli/src/client/index.ts index 095d716b2..0b651b35a 100644 --- a/cli/src/client/index.ts +++ b/cli/src/client/index.ts @@ -4,3 +4,4 @@ export * from "./prompts.js"; export * from "./resources.js"; export * from "./tools.js"; export * from "./types.js"; +export { getCapturedLogs, clearCapturedLogs } from "./connection.js"; diff --git a/cli/src/discover.ts b/cli/src/discover.ts new file mode 100644 index 000000000..480ac3c82 --- /dev/null +++ b/cli/src/discover.ts @@ -0,0 +1,62 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; + +export interface ServerCapabilitiesMap { + tools: boolean; + resources: boolean; + prompts: boolean; + logging: boolean; + completions: boolean; +} + +export interface DiscoverResult { + serverInfo: { + name: string; + version: string; + [key: string]: unknown; + }; + capabilities: ServerCapabilitiesMap; + tools: unknown[]; + resources: unknown[]; + prompts: unknown[]; +} + +export async function discover(client: Client): Promise { + const caps = client.getServerCapabilities() ?? {}; + const serverInfo = client.getServerVersion() ?? { + name: "unknown", + version: "0.0.0", + }; + + const capabilities: ServerCapabilitiesMap = { + tools: !!caps.tools, + resources: !!caps.resources, + prompts: !!caps.prompts, + logging: !!caps.logging, + completions: !!caps.completions, + }; + + const result: DiscoverResult = { + serverInfo, + capabilities, + tools: [], + resources: [], + prompts: [], + }; + + if (capabilities.tools) { + const toolsResponse = await client.listTools(); + result.tools = toolsResponse.tools; + } + + if (capabilities.resources) { + const resourcesResponse = await client.listResources(); + result.resources = resourcesResponse.resources; + } + + if (capabilities.prompts) { + const promptsResponse = await client.listPrompts(); + result.prompts = promptsResponse.prompts; + } + + return result; +} diff --git a/cli/src/index.ts b/cli/src/index.ts index 45a71a052..1e81a43a6 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -5,8 +5,10 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { Command } from "commander"; import { callTool, + clearCapturedLogs, connect, disconnect, + getCapturedLogs, getPrompt, listPrompts, listResources, @@ -18,7 +20,14 @@ import { setLoggingLevel, validLogLevels, } from "./client/index.js"; +import { discover } from "./discover.js"; import { handleError } from "./error-handler.js"; +import { + categorizeError, + formatStructuredOutput, + StructuredCliError, + type StructuredOutput, +} from "./output.js"; import { createTransport, TransportOptions } from "./transport.js"; import { awaitableLog } from "./utils/awaitable-log.js"; @@ -45,6 +54,20 @@ type Args = { transport?: "sse" | "stdio" | "http"; headers?: Record; metadata?: Record; + structured?: boolean; + failOnError?: boolean; +}; + +// Map of methods to their required server capability key +const METHOD_CAPABILITY_MAP: Record = { + "tools/list": "tools", + "tools/call": "tools", + "resources/list": "resources", + "resources/read": "resources", + "resources/templates/list": "resources", + "prompts/list": "prompts", + "prompts/get": "prompts", + "logging/setLevel": "logging", }; function createTransportOptions( @@ -101,7 +124,25 @@ function createTransportOptions( }; } +function checkCapability(client: Client, method: string): void { + const requiredCapability = METHOD_CAPABILITY_MAP[method]; + if (!requiredCapability) { + return; // discover, ping have no capability requirement + } + + const capabilities = client.getServerCapabilities() ?? {}; + if (!(capabilities as Record)[requiredCapability]) { + throw new StructuredCliError( + `Server does not support ${requiredCapability} capability (required for ${method})`, + "capability", + ); + } +} + async function callMethod(args: Args): Promise { + clearCapturedLogs(); + const startTime = Date.now(); + // Read package.json to get name and version for client identity const pathA = "../package.json"; // We're in package @modelcontextprotocol/inspector-cli const pathB = "../../package.json"; // We're in package @modelcontextprotocol/inspector @@ -124,18 +165,23 @@ async function callMethod(args: Args): Promise { const client = new Client(clientIdentity); + let result: McpResponse; + let hasApplicationError = false; + try { await connect(client, transport); - let result: McpResponse; + // Capability gating: verify server supports this method + checkCapability(client, args.method!); // Tools methods if (args.method === "tools/list") { result = await listTools(client, args.metadata); } else if (args.method === "tools/call") { if (!args.toolName) { - throw new Error( + throw new StructuredCliError( "Tool name is required for tools/call method. Use --tool-name to specify the tool name.", + "validation", ); } @@ -146,14 +192,19 @@ async function callMethod(args: Args): Promise { args.metadata, args.toolMeta, ); + // Check if the tool returned an application-level error + if (result.isError) { + hasApplicationError = true; + } } // Resources methods else if (args.method === "resources/list") { result = await listResources(client, args.metadata); } else if (args.method === "resources/read") { if (!args.uri) { - throw new Error( + throw new StructuredCliError( "URI is required for resources/read method. Use --uri to specify the resource URI.", + "validation", ); } @@ -166,8 +217,9 @@ async function callMethod(args: Args): Promise { result = await listPrompts(client, args.metadata); } else if (args.method === "prompts/get") { if (!args.promptName) { - throw new Error( + throw new StructuredCliError( "Prompt name is required for prompts/get method. Use --prompt-name to specify the prompt name.", + "validation", ); } @@ -181,19 +233,75 @@ async function callMethod(args: Args): Promise { // Logging methods else if (args.method === "logging/setLevel") { if (!args.logLevel) { - throw new Error( + throw new StructuredCliError( "Log level is required for logging/setLevel method. Use --log-level to specify the log level.", + "validation", ); } result = await setLoggingLevel(client, args.logLevel); + } + // Discovery pseudo-method + else if (args.method === "discover") { + result = (await discover(client)) as unknown as McpResponse; + } + // Ping method + else if (args.method === "ping") { + result = await client.ping(); } else { - throw new Error( - `Unsupported method: ${args.method}. Supported methods include: tools/list, tools/call, resources/list, resources/read, resources/templates/list, prompts/list, prompts/get, logging/setLevel`, + throw new StructuredCliError( + `Unsupported method: ${args.method}. Supported methods include: tools/list, tools/call, resources/list, resources/read, resources/templates/list, prompts/list, prompts/get, logging/setLevel, discover, ping`, + "validation", ); } - await awaitableLog(JSON.stringify(result, null, 2)); + const durationMs = Date.now() - startTime; + const logs = getCapturedLogs(); + + if (args.structured) { + const output: StructuredOutput = { + structuredVersion: 1, + success: !hasApplicationError, + method: args.method!, + durationMs, + result: result as Record, + error: null, + logs, + }; + await awaitableLog(formatStructuredOutput(output)); + } else { + // Raw mode: emit logs to stderr, result to stdout + for (const log of logs) { + console.error( + `[${log.timestamp}] [${log.level}]${log.logger ? ` [${log.logger}]` : ""} ${log.message}`, + ); + } + await awaitableLog(JSON.stringify(result, null, 2)); + } + + if (args.failOnError && hasApplicationError) { + process.exit(1); + } + } catch (error) { + const durationMs = Date.now() - startTime; + const logs = getCapturedLogs(); + + if (args.structured) { + const structuredError = categorizeError(error); + const output: StructuredOutput = { + structuredVersion: 1, + success: false, + method: args.method ?? "unknown", + durationMs, + result: null, + error: structuredError, + logs, + }; + await awaitableLog(formatStructuredOutput(output)); + process.exit(1); + } else { + throw error; + } } finally { try { await disconnect(transport); @@ -356,6 +464,17 @@ function parseArgs(): Args { "Tool-specific metadata as key=value pairs (for tools/call method only)", parseKeyValuePair, {}, + ) + // + // CI debugging output options + // + .option( + "--structured", + "Output structured envelope with error taxonomy, logs, and timing", + ) + .option( + "--fail-on-error", + "Exit with code 1 on server-side application errors (isError: true)", ); // Parse only the arguments before -- diff --git a/cli/src/output.ts b/cli/src/output.ts new file mode 100644 index 000000000..30056e43a --- /dev/null +++ b/cli/src/output.ts @@ -0,0 +1,96 @@ +export type ErrorCategory = + | "transport" + | "capability" + | "protocol" + | "application" + | "validation"; + +export interface StructuredError { + category: ErrorCategory; + code: string; + message: string; +} + +export interface LogEntry { + level: string; + logger?: string; + message: string; + timestamp: string; +} + +export interface StructuredOutput { + structuredVersion: number; + success: boolean; + method: string; + durationMs: number; + result: Record | null; + error: StructuredError | null; + logs: LogEntry[]; +} + +export class StructuredCliError extends Error { + category: ErrorCategory; + + constructor(message: string, category: ErrorCategory) { + super(message); + this.category = category; + this.name = "StructuredCliError"; + } +} + +export function categorizeError(error: unknown): StructuredError { + if (error instanceof StructuredCliError) { + return { + category: error.category, + code: error.category.toUpperCase() + "_ERROR", + message: error.message, + }; + } + + const message = + error instanceof Error ? error.message : String(error || "Unknown error"); + + if ( + message.includes("Failed to connect") || + message.includes("ECONNREFUSED") || + message.includes("timed out") || + message.includes("Failed to create transport") || + message.includes("socket hang up") || + message.includes("ENOTFOUND") + ) { + return { category: "transport", code: "TRANSPORT_ERROR", message }; + } + + if ( + message.includes("does not support") || + message.includes("capability not supported") + ) { + return { + category: "capability", + code: "CAPABILITY_NOT_SUPPORTED", + message, + }; + } + + if ( + message.includes("JSON-RPC") || + message.includes("handshake") || + message.includes("protocol error") + ) { + return { category: "protocol", code: "PROTOCOL_ERROR", message }; + } + + if ( + message.includes("is required") || + message.includes("Invalid") || + message.includes("Unsupported method") + ) { + return { category: "validation", code: "VALIDATION_ERROR", message }; + } + + return { category: "application", code: "APPLICATION_ERROR", message }; +} + +export function formatStructuredOutput(output: StructuredOutput): string { + return JSON.stringify(output, null, 2); +} diff --git a/package-lock.json b/package-lock.json index 0055b4334..ab3bfe74e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -227,6 +227,7 @@ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", @@ -240,7 +241,8 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/@babel/code-frame": { "version": "7.27.1", @@ -273,7 +275,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -819,6 +820,7 @@ } ], "license": "MIT-0", + "peer": true, "engines": { "node": ">=18" } @@ -839,6 +841,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -863,6 +866,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" @@ -1884,6 +1888,7 @@ "integrity": "sha512-kazxw2L9IPuZpQ0mEt9lu9Z98SqR74xcagANmMBU16X0lS23yPc0+S6hGLUz8kVRlomZEs/5S/Zlpqwf5yu6OQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/environment": "30.2.0", "@jest/fake-timers": "30.2.0", @@ -1912,6 +1917,7 @@ "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/fake-timers": "30.2.0", "@jest/types": "30.2.0", @@ -1928,6 +1934,7 @@ "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", @@ -1946,6 +1953,7 @@ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.34.0" }, @@ -1959,6 +1967,7 @@ "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/pattern": "30.0.1", "@jest/schemas": "30.0.5", @@ -1977,7 +1986,8 @@ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@jest/environment-jsdom-abstract/node_modules/@sinonjs/fake-timers": { "version": "13.0.5", @@ -1985,6 +1995,7 @@ "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.1" } @@ -1995,6 +2006,7 @@ "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", @@ -2007,6 +2019,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -2026,6 +2039,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -2036,6 +2050,7 @@ "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@jest/types": "30.2.0", @@ -2057,6 +2072,7 @@ "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", @@ -2072,6 +2088,7 @@ "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", @@ -2090,6 +2107,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2103,6 +2121,7 @@ "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", @@ -2117,7 +2136,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@jest/expect": { "version": "29.7.0", @@ -2186,6 +2206,7 @@ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "jest-regex-util": "30.0.1" @@ -2200,6 +2221,7 @@ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } @@ -3945,7 +3967,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -4195,7 +4218,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -4234,7 +4256,6 @@ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -4246,7 +4267,6 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -4387,7 +4407,6 @@ "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", @@ -4770,7 +4789,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5243,7 +5261,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6083,7 +6100,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/domexception": { "version": "4.0.0", @@ -6341,7 +6359,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6639,7 +6656,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -7733,7 +7749,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -8074,6 +8089,7 @@ "integrity": "sha512-zbBTiqr2Vl78pKp/laGBREYzbZx9ZtqPjOK4++lL4BNDhxRnahg51HtoDrk9/VjIy9IthNEWdKVd7H5bqBhiWQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/environment": "30.2.0", "@jest/environment-jsdom-abstract": "30.2.0", @@ -8099,6 +8115,7 @@ "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/fake-timers": "30.2.0", "@jest/types": "30.2.0", @@ -8115,6 +8132,7 @@ "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", @@ -8133,6 +8151,7 @@ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.34.0" }, @@ -8146,6 +8165,7 @@ "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/pattern": "30.0.1", "@jest/schemas": "30.0.5", @@ -8164,7 +8184,8 @@ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/jest-environment-jsdom/node_modules/@sinonjs/fake-timers": { "version": "13.0.5", @@ -8172,6 +8193,7 @@ "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.1" } @@ -8182,6 +8204,7 @@ "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", @@ -8194,6 +8217,7 @@ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 14" } @@ -8204,6 +8228,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -8223,6 +8248,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -8233,6 +8259,7 @@ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@asamuzakjp/css-color": "^3.2.0", "rrweb-cssom": "^0.8.0" @@ -8247,6 +8274,7 @@ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" @@ -8261,6 +8289,7 @@ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "whatwg-encoding": "^3.1.1" }, @@ -8274,6 +8303,7 @@ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -8288,6 +8318,7 @@ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -8302,6 +8333,7 @@ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -8315,6 +8347,7 @@ "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@jest/types": "30.2.0", @@ -8336,6 +8369,7 @@ "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", @@ -8351,6 +8385,7 @@ "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "30.2.0", "@types/node": "*", @@ -8369,6 +8404,7 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -8409,6 +8445,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8422,6 +8459,7 @@ "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", @@ -8436,7 +8474,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/jest-environment-jsdom/node_modules/tough-cookie": { "version": "5.1.2", @@ -8444,6 +8483,7 @@ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "tldts": "^6.1.32" }, @@ -8457,6 +8497,7 @@ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "punycode": "^2.3.1" }, @@ -8470,6 +8511,7 @@ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "xml-name-validator": "^5.0.0" }, @@ -8483,6 +8525,7 @@ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "iconv-lite": "0.6.3" }, @@ -8496,6 +8539,7 @@ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -8506,6 +8550,7 @@ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" @@ -8520,6 +8565,7 @@ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18" } @@ -9114,7 +9160,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -9153,7 +9198,6 @@ "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", @@ -9492,6 +9536,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -10369,7 +10414,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10545,6 +10589,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -10560,6 +10605,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -10714,7 +10760,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -10727,7 +10772,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -10741,7 +10785,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-refresh": { "version": "0.18.0", @@ -11149,7 +11194,8 @@ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/run-applescript": { "version": "7.1.0", @@ -11815,7 +11861,6 @@ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -11954,7 +11999,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11978,6 +12022,7 @@ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tldts-core": "^6.1.86" }, @@ -11990,7 +12035,8 @@ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tmpl": { "version": "1.0.5", @@ -12163,7 +12209,6 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -12220,7 +12265,6 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -12774,7 +12818,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12983,7 +13026,6 @@ "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -13077,7 +13119,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -13497,7 +13538,6 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -13585,7 +13625,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }