From e529d639b174ac36ea0467d33bd7f5f6ed7bd1e7 Mon Sep 17 00:00:00 2001 From: olaservo Date: Sun, 18 Jan 2026 12:44:33 -0700 Subject: [PATCH 1/3] feat(core): add @modelcontextprotocol/inspector-core package Extracts reusable MCP client logic into a separate package that can be shared across web UI, CLI, and TUI implementations. Includes: - MCP client lifecycle (createMcpClient, connectClient, disconnectClient) - HTTP transport creation (createHttpTransport, createAuthenticatedTransport) - Sampling/elicitation request handlers - Data layer interfaces (repositories, services) with memory stubs - Shared type definitions --- core/package.json | 33 ++ core/src/client.ts | 154 +++++++++ core/src/data/index.ts | 44 +++ core/src/data/memory.ts | 528 ++++++++++++++++++++++++++++++ core/src/data/repositories.ts | 197 +++++++++++ core/src/data/services.ts | 177 ++++++++++ core/src/handlers.ts | 442 +++++++++++++++++++++++++ core/src/index.ts | 44 +++ core/src/transport.ts | 65 ++++ core/src/types/auth.ts | 29 ++ core/src/types/capabilities.ts | 11 + core/src/types/clientRequests.ts | 68 ++++ core/src/types/history.ts | 73 +++++ core/src/types/index.ts | 12 + core/src/types/logs.ts | 84 +++++ core/src/types/responses.ts | 58 ++++ core/src/types/servers.ts | 109 ++++++ core/src/types/tasks.ts | 32 ++ core/src/types/testingProfiles.ts | 48 +++ core/tsconfig.json | 21 ++ package.json | 26 ++ 21 files changed, 2255 insertions(+) create mode 100644 core/package.json create mode 100644 core/src/client.ts create mode 100644 core/src/data/index.ts create mode 100644 core/src/data/memory.ts create mode 100644 core/src/data/repositories.ts create mode 100644 core/src/data/services.ts create mode 100644 core/src/handlers.ts create mode 100644 core/src/index.ts create mode 100644 core/src/transport.ts create mode 100644 core/src/types/auth.ts create mode 100644 core/src/types/capabilities.ts create mode 100644 core/src/types/clientRequests.ts create mode 100644 core/src/types/history.ts create mode 100644 core/src/types/index.ts create mode 100644 core/src/types/logs.ts create mode 100644 core/src/types/responses.ts create mode 100644 core/src/types/servers.ts create mode 100644 core/src/types/tasks.ts create mode 100644 core/src/types/testingProfiles.ts create mode 100644 core/tsconfig.json create mode 100644 package.json diff --git a/core/package.json b/core/package.json new file mode 100644 index 000000000..f2e5be8a3 --- /dev/null +++ b/core/package.json @@ -0,0 +1,33 @@ +{ + "name": "@modelcontextprotocol/inspector-core", + "version": "0.1.0", + "description": "Core MCP client logic for Inspector - no React dependencies", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./types": { + "types": "./dist/types/index.d.ts", + "import": "./dist/types/index.js" + } + }, + "scripts": { + "build": "tsc", + "clean": "rm -rf dist", + "prepublishOnly": "npm run build" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.25.1" + }, + "devDependencies": { + "typescript": "^5.6.3" + }, + "files": [ + "dist", + "src" + ] +} diff --git a/core/src/client.ts b/core/src/client.ts new file mode 100644 index 000000000..6796af849 --- /dev/null +++ b/core/src/client.ts @@ -0,0 +1,154 @@ +/** + * MCP Client Wrapper - Manage MCP client lifecycle and capabilities + * + * This module wraps the MCP SDK Client for React usage, handling: + * - Client creation with proper capabilities + * - Connection lifecycle (connect/disconnect) + * - Server info extraction + * + * Usage: + * import { createMcpClient, connectClient } from '@/lib/mcp/client'; + * + * const client = createMcpClient(); + * const transport = createHttpTransport(url); + * await connectClient(client, transport); + */ + +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; +import type { Implementation, ServerCapabilities } from '@modelcontextprotocol/sdk/types.js'; + +/** + * Client configuration options. + */ +export interface McpClientOptions { + /** Client name shown to servers */ + name?: string; + /** Client version */ + version?: string; + /** Enable sampling capability (receive LLM requests from server) */ + enableSampling?: boolean; + /** Enable roots capability (expose filesystem paths to server) */ + enableRoots?: boolean; +} + +/** + * Information about a connected MCP server. + */ +export interface ServerInfo { + /** Server name */ + name: string; + /** Server version */ + version: string; + /** Server capabilities */ + capabilities: ServerCapabilities; + /** Server instructions (if provided) */ + instructions?: string; +} + +const DEFAULT_CLIENT_NAME = 'MCP Inspector'; +const DEFAULT_CLIENT_VERSION = '2.0.0'; + +/** + * Create a new MCP client with Inspector capabilities. + * + * @param options - Client configuration options + * @returns Configured MCP Client instance + */ +export function createMcpClient(options: McpClientOptions = {}): Client { + const { + name = DEFAULT_CLIENT_NAME, + version = DEFAULT_CLIENT_VERSION, + enableSampling = true, + enableRoots = true, + } = options; + + const clientInfo: Implementation = { + name, + version, + }; + + // Build capabilities based on options + const capabilities: Record = {}; + + if (enableSampling) { + // Declare sampling capability with tool support (per MCP 2025-11-25 spec) + capabilities.sampling = { tools: {} }; + } + + if (enableRoots) { + // Declare roots capability + capabilities.roots = { listChanged: true }; + } + + return new Client(clientInfo, { capabilities }); +} + +/** + * Connect an MCP client to a server via transport. + * + * @param client - MCP Client instance + * @param transport - Transport to connect through + * @returns Server information on successful connection + * @throws Error if connection fails + */ +export async function connectClient( + client: Client, + transport: Transport +): Promise { + await client.connect(transport); + + const serverVersion = client.getServerVersion(); + const serverCapabilities = client.getServerCapabilities(); + const instructions = client.getInstructions(); + + if (!serverVersion) { + throw new Error('Failed to get server version after connection'); + } + + return { + name: serverVersion.name, + version: serverVersion.version, + capabilities: serverCapabilities ?? {}, + instructions: instructions ?? undefined, + }; +} + +/** + * Disconnect an MCP client and clean up resources. + * + * @param client - MCP Client instance to disconnect + */ +export async function disconnectClient(client: Client): Promise { + await client.close(); +} + +/** + * Check if a client is currently connected. + * + * @param client - MCP Client instance + * @returns true if connected + */ +export function isClientConnected(client: Client): boolean { + // The SDK client doesn't expose connection state directly, + // but we can check if server version is available + try { + return client.getServerVersion() !== undefined; + } catch { + return false; + } +} + +/** + * Check if server supports a specific capability. + * + * @param serverInfo - Server info from connection + * @param capability - Capability name to check + * @returns true if capability is supported + */ +export function serverSupports( + serverInfo: ServerInfo, + capability: keyof ServerCapabilities +): boolean { + return serverInfo.capabilities[capability] !== undefined; +} diff --git a/core/src/data/index.ts b/core/src/data/index.ts new file mode 100644 index 000000000..ff6dab5ea --- /dev/null +++ b/core/src/data/index.ts @@ -0,0 +1,44 @@ +/** + * Data Layer Interfaces and Memory Stubs + * + * This module provides: + * - Repository interfaces for data storage (CRUD contracts) + * - Service interfaces for stateful business logic + * - Memory stub implementations for development/testing + * + * Real implementations (proxy API, file-based, localStorage) can be added later. + */ + +// Repository interfaces +export type { + ServerConfigRepository, + HistoryRepository, + HistoryListOptions, + LogsRepository, + LogsListOptions, + TestingProfileRepository, +} from './repositories.js'; + +// Service interfaces +export type { + ConnectionService, + ConnectionState, + ConnectionError, + ConnectionOptions, + ExecutionService, + ExecutionState, + PendingClientRequest, +} from './services.js'; + +// Initial state exports +export { initialConnectionState, initialExecutionState } from './services.js'; + +// Memory stub implementations +export { + createMemoryServerConfigRepository, + createMemoryHistoryRepository, + createMemoryLogsRepository, + createMemoryTestingProfileRepository, + createMemoryConnectionService, + createMemoryExecutionService, +} from './memory.js'; diff --git a/core/src/data/memory.ts b/core/src/data/memory.ts new file mode 100644 index 000000000..8e2ee854b --- /dev/null +++ b/core/src/data/memory.ts @@ -0,0 +1,528 @@ +/** + * Memory Stub Implementations + * + * In-memory implementations of repository and service interfaces. + * Use for development, testing, or as a starting point for real implementations. + * Data is lost when the process exits. + */ + +import type { + ServerConfigRepository, + HistoryRepository, + HistoryListOptions, + LogsRepository, + LogsListOptions, + TestingProfileRepository, +} from './repositories.js'; +import type { + ConnectionService, + ConnectionState, + ConnectionOptions, + ExecutionService, + ExecutionState, + PendingClientRequest, + initialConnectionState, + initialExecutionState, +} from './services.js'; +import type { ServerConfig } from '../types/servers.js'; +import type { HistoryEntry } from '../types/history.js'; +import type { LogEntry, LOG_LEVELS } from '../types/logs.js'; +import type { TestingProfile } from '../types/testingProfiles.js'; +import type { ServerInfo } from '../client.js'; +import { + createMcpClient, + connectClient, + disconnectClient, + createHttpTransport, + createAuthenticatedTransport, +} from '../index.js'; + +// ============================================================================ +// Helper Functions +// ============================================================================ + +function generateId(prefix: string): string { + return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; +} + +// ============================================================================ +// Server Config Repository +// ============================================================================ + +/** + * In-memory server config repository. + */ +export function createMemoryServerConfigRepository(): ServerConfigRepository { + const configs = new Map(); + + return { + async list() { + return Array.from(configs.values()); + }, + + async get(id) { + return configs.get(id); + }, + + async create(config) { + const now = new Date().toISOString(); + const full: ServerConfig = { + ...config, + id: generateId('server'), + createdAt: now, + updatedAt: now, + connectionMode: config.transport === 'http' ? 'direct' : 'proxy', + }; + configs.set(full.id, full); + return full; + }, + + async update(id, updates) { + const existing = configs.get(id); + if (!existing) { + throw new Error(`Server config not found: ${id}`); + } + const updated: ServerConfig = { + ...existing, + ...updates, + id: existing.id, + createdAt: existing.createdAt, + updatedAt: new Date().toISOString(), + }; + configs.set(id, updated); + return updated; + }, + + async delete(id) { + configs.delete(id); + }, + + async deleteAll() { + configs.clear(); + }, + }; +} + +// ============================================================================ +// History Repository +// ============================================================================ + +/** + * In-memory history repository. + */ +export function createMemoryHistoryRepository(): HistoryRepository { + const entries = new Map(); + + return { + async list(options?: HistoryListOptions) { + let result = Array.from(entries.values()); + + // Filter by method + if (options?.method) { + result = result.filter((e) => e.method === options.method); + } + + // Filter by request type + if (options?.requestType) { + result = result.filter((e) => e.requestType === options.requestType); + } + + // Filter root only + if (options?.rootOnly) { + result = result.filter((e) => !e.parentRequestId); + } + + // Sort by timestamp descending (newest first) + result.sort((a, b) => b.timestamp.localeCompare(a.timestamp)); + + // Apply limit + if (options?.limit && options.limit > 0) { + result = result.slice(0, options.limit); + } + + return result; + }, + + async get(id) { + return entries.get(id); + }, + + async add(entry) { + const full: HistoryEntry = { + ...entry, + id: generateId('req'), + timestamp: new Date().toISOString(), + pinned: false, + }; + entries.set(full.id, full); + return full; + }, + + async update(id, updates) { + const existing = entries.get(id); + if (!existing) { + throw new Error(`History entry not found: ${id}`); + } + const updated: HistoryEntry = { + ...existing, + ...updates, + id: existing.id, + timestamp: existing.timestamp, + }; + entries.set(id, updated); + return updated; + }, + + async delete(id) { + entries.delete(id); + }, + + async deleteAll(options) { + if (options?.keepPinned) { + for (const [id, entry] of entries) { + if (!entry.pinned) { + entries.delete(id); + } + } + } else { + entries.clear(); + } + }, + + async getChildren(parentRequestId) { + return Array.from(entries.values()).filter( + (e) => e.parentRequestId === parentRequestId + ); + }, + }; +} + +// ============================================================================ +// Logs Repository +// ============================================================================ + +/** + * In-memory logs repository. + */ +export function createMemoryLogsRepository(): LogsRepository { + const logs: LogEntry[] = []; + const LOG_LEVEL_ORDER = [ + 'debug', + 'info', + 'notice', + 'warning', + 'error', + 'critical', + 'alert', + 'emergency', + ] as const; + + return { + async list(options?: LogsListOptions) { + let result = [...logs]; + + // Filter by minimum level + if (options?.minLevel) { + const minIndex = LOG_LEVEL_ORDER.indexOf(options.minLevel); + result = result.filter((log) => { + const logIndex = LOG_LEVEL_ORDER.indexOf( + log.level as (typeof LOG_LEVEL_ORDER)[number] + ); + return logIndex >= minIndex; + }); + } + + // Filter by logger + if (options?.logger) { + result = result.filter((log) => log.logger === options.logger); + } + + // Filter by request ID + if (options?.requestId) { + result = result.filter((log) => log.requestId === options.requestId); + } + + // Sort by timestamp descending + result.sort((a, b) => b.timestamp.localeCompare(a.timestamp)); + + // Apply limit + if (options?.limit && options.limit > 0) { + result = result.slice(0, options.limit); + } + + return result; + }, + + async add(entry) { + const full: LogEntry = { + ...entry, + timestamp: new Date().toISOString(), + }; + logs.push(full); + return full; + }, + + async addBatch(entries) { + const timestamp = new Date().toISOString(); + const fullEntries = entries.map((entry) => ({ + ...entry, + timestamp, + })); + logs.push(...fullEntries); + return fullEntries; + }, + + async deleteAll() { + logs.length = 0; + }, + + async getForRequest(requestId) { + return logs.filter( + (log) => + log.requestId === requestId || log.parentRequestId === requestId + ); + }, + }; +} + +// ============================================================================ +// Testing Profile Repository +// ============================================================================ + +/** + * In-memory testing profile repository. + */ +export function createMemoryTestingProfileRepository(): TestingProfileRepository { + const profiles = new Map(); + + return { + async list() { + return Array.from(profiles.values()); + }, + + async get(id) { + return profiles.get(id); + }, + + async create(profile) { + const full: TestingProfile = { + ...profile, + id: generateId('profile'), + }; + profiles.set(full.id, full); + return full; + }, + + async update(id, updates) { + const existing = profiles.get(id); + if (!existing) { + throw new Error(`Testing profile not found: ${id}`); + } + const updated: TestingProfile = { + ...existing, + ...updates, + id: existing.id, + }; + profiles.set(id, updated); + return updated; + }, + + async delete(id) { + profiles.delete(id); + }, + }; +} + +// ============================================================================ +// Connection Service +// ============================================================================ + +/** + * In-memory connection service. + * Wraps the actual MCP client with state tracking. + */ +export function createMemoryConnectionService(): ConnectionService { + let state: ConnectionState = { + status: 'disconnected', + serverUrl: null, + serverInfo: null, + error: null, + }; + const listeners = new Set<() => void>(); + let clientRef: ReturnType | null = null; + + function notify() { + listeners.forEach((l) => l()); + } + + return { + getState() { + return { ...state }; + }, + + async connect(url, options) { + // Update state to connecting + state = { + status: 'connecting', + serverUrl: url, + serverInfo: null, + error: null, + }; + notify(); + + try { + // Create client and transport + const client = createMcpClient(); + const transport = options?.token + ? createAuthenticatedTransport(url, options.token) + : createHttpTransport(url, options?.headers); + + // Connect + const serverInfo = await connectClient(client, transport); + clientRef = client; + + // Update state to connected + state = { + status: 'connected', + serverUrl: url, + serverInfo, + error: null, + }; + notify(); + + return serverInfo; + } catch (err) { + // Update state to error + state = { + status: 'error', + serverUrl: url, + serverInfo: null, + error: { + message: err instanceof Error ? err.message : 'Connection failed', + timestamp: new Date().toISOString(), + }, + }; + notify(); + throw err; + } + }, + + async disconnect() { + if (clientRef) { + try { + await disconnectClient(clientRef); + } catch { + // Ignore disconnect errors + } + clientRef = null; + } + + state = { + status: 'disconnected', + serverUrl: null, + serverInfo: null, + error: null, + }; + notify(); + }, + + subscribe(listener) { + listeners.add(listener); + return () => listeners.delete(listener); + }, + }; +} + +// ============================================================================ +// Execution Service +// ============================================================================ + +/** + * In-memory execution service. + */ +export function createMemoryExecutionService(): ExecutionService { + let state: ExecutionState = { + currentRequestId: null, + isExecuting: false, + pendingClientRequests: [], + }; + const listeners = new Set<() => void>(); + + function notify() { + listeners.forEach((l) => l()); + } + + return { + getState() { + return { ...state, pendingClientRequests: [...state.pendingClientRequests] }; + }, + + startExecution(requestId) { + state = { + ...state, + currentRequestId: requestId, + isExecuting: true, + }; + notify(); + }, + + endExecution() { + state = { + ...state, + currentRequestId: null, + isExecuting: false, + }; + notify(); + }, + + cancelExecution() { + state = { + currentRequestId: null, + isExecuting: false, + pendingClientRequests: [], + }; + notify(); + }, + + addPendingRequest(request) { + state = { + ...state, + pendingClientRequests: [...state.pendingClientRequests, request], + }; + notify(); + }, + + resolvePendingRequest(id) { + state = { + ...state, + pendingClientRequests: state.pendingClientRequests.map((r) => + r.id === id ? { ...r, status: 'resolved' as const } : r + ), + }; + notify(); + }, + + rejectPendingRequest(id) { + state = { + ...state, + pendingClientRequests: state.pendingClientRequests.map((r) => + r.id === id ? { ...r, status: 'rejected' as const } : r + ), + }; + notify(); + }, + + clearPendingRequests() { + state = { + ...state, + pendingClientRequests: [], + }; + notify(); + }, + + subscribe(listener) { + listeners.add(listener); + return () => listeners.delete(listener); + }, + }; +} diff --git a/core/src/data/repositories.ts b/core/src/data/repositories.ts new file mode 100644 index 000000000..0ea103dc5 --- /dev/null +++ b/core/src/data/repositories.ts @@ -0,0 +1,197 @@ +/** + * Repository Interfaces for Inspector Data Layer + * + * These interfaces define CRUD contracts for data storage. + * Implementations can use proxy API, file storage, localStorage, etc. + * Memory stubs provided for development and testing. + */ + +import type { ServerConfig } from '../types/servers.js'; +import type { HistoryEntry } from '../types/history.js'; +import type { LogEntry, LogLevel } from '../types/logs.js'; +import type { TestingProfile } from '../types/testingProfiles.js'; + +/** + * Server configuration repository. + * + * Manages persisted server configurations. + * Implementations: proxy API, file-based, localStorage, memory stub + */ +export interface ServerConfigRepository { + /** + * List all server configurations. + */ + list(): Promise; + + /** + * Get a server configuration by ID. + */ + get(id: string): Promise; + + /** + * Create a new server configuration. + * Returns the created config with generated ID and timestamps. + */ + create( + config: Omit + ): Promise; + + /** + * Update an existing server configuration. + * Returns the updated config with new updatedAt timestamp. + */ + update( + id: string, + updates: Partial> + ): Promise; + + /** + * Delete a server configuration. + */ + delete(id: string): Promise; + + /** + * Delete all server configurations. + */ + deleteAll(): Promise; +} + +/** + * History repository. + * + * Manages request/response history entries with hierarchical relationships. + * Implementations: proxy API (NDJSON), file-based, localStorage, memory stub + */ +export interface HistoryRepository { + /** + * List history entries with optional filtering. + */ + list(options?: HistoryListOptions): Promise; + + /** + * Get a history entry by ID. + */ + get(id: string): Promise; + + /** + * Add a new history entry. + * Returns the entry with generated ID and timestamp. + */ + add(entry: Omit): Promise; + + /** + * Update an existing history entry. + */ + update( + id: string, + updates: Partial> + ): Promise; + + /** + * Delete a history entry. + */ + delete(id: string): Promise; + + /** + * Delete all history entries. + * @param options.keepPinned If true, keep pinned entries + */ + deleteAll(options?: { keepPinned?: boolean }): Promise; + + /** + * Get children of a parent request (for hierarchical display). + */ + getChildren(parentRequestId: string): Promise; +} + +export interface HistoryListOptions { + /** Maximum number of entries to return */ + limit?: number; + /** Filter by MCP method (e.g., 'tools/call') */ + method?: string; + /** Filter by request type */ + requestType?: 'primary' | 'client'; + /** Only return root entries (no parentRequestId) */ + rootOnly?: boolean; +} + +/** + * Logs repository. + * + * Manages protocol event logs with RFC 5424 levels. + * Implementations: proxy API, file-based, localStorage, memory stub + */ +export interface LogsRepository { + /** + * List log entries with optional filtering. + */ + list(options?: LogsListOptions): Promise; + + /** + * Add a single log entry. + */ + add(entry: Omit): Promise; + + /** + * Add multiple log entries in batch. + */ + addBatch(entries: Array>): Promise; + + /** + * Delete all log entries. + */ + deleteAll(): Promise; + + /** + * Get logs for a specific request chain (parent + children). + */ + getForRequest(requestId: string): Promise; +} + +export interface LogsListOptions { + /** Maximum number of entries to return */ + limit?: number; + /** Minimum log level (includes this level and more severe) */ + minLevel?: LogLevel; + /** Filter by logger category */ + logger?: string; + /** Filter by request ID */ + requestId?: string; +} + +/** + * Testing profiles repository. + * + * Manages testing profiles for sampling/elicitation response strategies. + * Implementations: proxy API, file-based, localStorage, memory stub + */ +export interface TestingProfileRepository { + /** + * List all testing profiles. + */ + list(): Promise; + + /** + * Get a testing profile by ID. + */ + get(id: string): Promise; + + /** + * Create a new testing profile. + * Returns the created profile with generated ID. + */ + create(profile: Omit): Promise; + + /** + * Update an existing testing profile. + */ + update( + id: string, + updates: Partial> + ): Promise; + + /** + * Delete a testing profile. + */ + delete(id: string): Promise; +} diff --git a/core/src/data/services.ts b/core/src/data/services.ts new file mode 100644 index 000000000..b6c0a664c --- /dev/null +++ b/core/src/data/services.ts @@ -0,0 +1,177 @@ +/** + * Service Interfaces for Inspector Business Logic + * + * These interfaces define stateful services for connection and execution management. + * They wrap core MCP client functions with state tracking and event subscription. + */ + +import type { ServerInfo } from '../client.js'; +import type { SamplingRequest, ElicitationRequest } from '../types/clientRequests.js'; + +/** + * Connection service - manages MCP client connections. + * + * Wraps core client functions with state tracking and event subscription. + * Implementations can use different storage for connection state. + */ +export interface ConnectionService { + /** + * Get current connection state. + */ + getState(): ConnectionState; + + /** + * Connect to an MCP server. + * @param url Server URL + * @param options Connection options (headers, token, etc.) + */ + connect(url: string, options?: ConnectionOptions): Promise; + + /** + * Disconnect from the current server. + */ + disconnect(): Promise; + + /** + * Subscribe to state changes. + * Returns unsubscribe function. + */ + subscribe(listener: () => void): () => void; +} + +/** + * Connection state + */ +export interface ConnectionState { + /** Connection status */ + status: 'disconnected' | 'connecting' | 'connected' | 'error'; + /** Currently connected server URL */ + serverUrl: string | null; + /** Server info from successful connection */ + serverInfo: ServerInfo | null; + /** Error details if status is 'error' */ + error: ConnectionError | null; +} + +/** + * Connection error details + */ +export interface ConnectionError { + message: string; + code?: string; + timestamp: string; +} + +/** + * Connection options + */ +export interface ConnectionOptions { + /** Custom headers for HTTP requests */ + headers?: Record; + /** Bearer token for authentication */ + token?: string; +} + +/** + * Execution service - tracks tool/resource/prompt execution. + * + * Manages pending client requests (sampling, elicitation) during execution. + * Provides state subscription for UI updates. + */ +export interface ExecutionService { + /** + * Get current execution state. + */ + getState(): ExecutionState; + + /** + * Start a new execution. + * @param requestId Unique request ID for tracking + */ + startExecution(requestId: string): void; + + /** + * End the current execution. + */ + endExecution(): void; + + /** + * Cancel the current execution. + */ + cancelExecution(): void; + + /** + * Add a pending client request (sampling or elicitation). + */ + addPendingRequest(request: PendingClientRequest): void; + + /** + * Resolve a pending client request. + */ + resolvePendingRequest(id: string): void; + + /** + * Reject a pending client request. + */ + rejectPendingRequest(id: string): void; + + /** + * Clear all pending requests. + */ + clearPendingRequests(): void; + + /** + * Subscribe to state changes. + * Returns unsubscribe function. + */ + subscribe(listener: () => void): () => void; +} + +/** + * Execution state + */ +export interface ExecutionState { + /** Current executing request ID */ + currentRequestId: string | null; + /** Whether execution is in progress */ + isExecuting: boolean; + /** Pending client requests (sampling, elicitation) */ + pendingClientRequests: PendingClientRequest[]; +} + +/** + * Pending client request (sampling or elicitation) + */ +export interface PendingClientRequest { + /** Unique request ID */ + id: string; + /** Request type */ + type: 'sampling' | 'elicitation'; + /** The actual request data */ + request: SamplingRequest | ElicitationRequest; + /** Parent request ID (the tool call that triggered this) */ + parentRequestId: string; + /** Request status */ + status: 'pending' | 'resolved' | 'rejected'; + /** Timestamp when request was received */ + timestamp: string; +} + +/** + * Initial connection state + */ +export const initialConnectionState: ConnectionState = { + status: 'disconnected', + serverUrl: null, + serverInfo: null, + error: null, +}; + +/** + * Initial execution state + */ +export const initialExecutionState: ExecutionState = { + currentRequestId: null, + isExecuting: false, + pendingClientRequests: [], +}; diff --git a/core/src/handlers.ts b/core/src/handlers.ts new file mode 100644 index 000000000..2813067e5 --- /dev/null +++ b/core/src/handlers.ts @@ -0,0 +1,442 @@ +/** + * MCP Request Handlers + * + * Handles client-side requests from MCP servers: + * - Sampling: Server requests LLM completion from client + * - Elicitation: Server requests user input (form or URL) + * + * These handlers integrate with ExecutionContext to show UI + * and return user responses to the server. + */ + +import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { + CreateMessageRequestSchema, + ElicitRequestSchema, + type CreateMessageRequest, + type CreateMessageResult, + type ElicitRequest, + type ElicitResult, +} from '@modelcontextprotocol/sdk/types.js'; +import type { SamplingRequest, ElicitationRequest } from './types/clientRequests.js'; +import type { SamplingResponse } from './types/responses.js'; + +// Response resolvers - keyed by request ID +const samplingResolvers = new Map< + string, + { + resolve: (response: SamplingResponse) => void; + reject: (reason?: unknown) => void; + } +>(); + +const elicitationResolvers = new Map< + string, + { + resolve: (data: Record) => void; + reject: (reason?: unknown) => void; + } +>(); + +// Request ID counter for generating unique IDs +let requestIdCounter = 0; + +/** + * Generate a unique request ID for pending requests. + */ +export function generateSamplingRequestId(): string { + return `sampling-${Date.now()}-${++requestIdCounter}`; +} + +/** + * Generate a unique request ID for elicitation requests. + */ +export function generateElicitationRequestId(): string { + return `elicitation-${Date.now()}-${++requestIdCounter}`; +} + +/** + * Convert a single SDK content item to our internal format. + */ +function convertContentItem( + content: unknown +): { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string } { + // Handle content object with type field + const contentObj = content as { type?: string; text?: string; data?: string; mimeType?: string }; + + if (contentObj.type === 'text' && typeof contentObj.text === 'string') { + return { type: 'text' as const, text: contentObj.text }; + } + + if (contentObj.type === 'image' && typeof contentObj.data === 'string' && typeof contentObj.mimeType === 'string') { + return { type: 'image' as const, data: contentObj.data, mimeType: contentObj.mimeType }; + } + + // Fallback for unsupported types + return { type: 'text' as const, text: '[Unsupported content type]' }; +} + +/** + * Convert MCP SDK CreateMessageRequest to our internal SamplingRequest format. + */ +function convertToSamplingRequest( + sdkRequest: CreateMessageRequest['params'] +): SamplingRequest { + return { + messages: sdkRequest.messages.map((msg) => { + // Content can be a single item or an array + const contentItem = Array.isArray(msg.content) + ? msg.content[0] // Take first item if array + : msg.content; + + return { + role: msg.role as 'user' | 'assistant', + content: convertContentItem(contentItem), + }; + }), + modelPreferences: sdkRequest.modelPreferences + ? { + hints: sdkRequest.modelPreferences.hints + ?.map((h) => h.name) + .filter((name): name is string => name !== undefined), + costPriority: sdkRequest.modelPreferences.costPriority, + speedPriority: sdkRequest.modelPreferences.speedPriority, + intelligencePriority: sdkRequest.modelPreferences.intelligencePriority, + } + : undefined, + maxTokens: sdkRequest.maxTokens, + stopSequences: sdkRequest.stopSequences, + temperature: sdkRequest.temperature, + includeContext: sdkRequest.includeContext as + | 'none' + | 'thisServer' + | 'allServers' + | undefined, + }; +} + +/** + * Convert our SamplingResponse to MCP SDK CreateMessageResult format. + */ +function convertToSdkResult(response: SamplingResponse): CreateMessageResult { + // Handle both text and image content types + const content = response.content.type === 'text' + ? { type: 'text' as const, text: response.content.text } + : { type: 'image' as const, data: response.content.data, mimeType: response.content.mimeType }; + + return { + role: 'assistant', + content, + model: response.model, + stopReason: response.stopReason, + }; +} + +/** + * Callbacks for when sampling/elicitation requests are received. + */ +export interface SamplingHandlerCallbacks { + /** + * Called when a sampling request is received from the server. + * The implementation should show UI and eventually call resolveSamplingRequest. + */ + onSamplingRequest: ( + requestId: string, + request: SamplingRequest, + parentRequestId: string + ) => void; + + /** + * Called when a sampling request is cancelled by the server. + */ + onSamplingCancelled?: (requestId: string) => void; +} + +export interface ElicitationHandlerCallbacks { + /** + * Called when an elicitation request is received from the server. + * The implementation should show UI and eventually call resolveElicitationRequest. + */ + onElicitationRequest: ( + requestId: string, + request: ElicitationRequest, + parentRequestId: string + ) => void; + + /** + * Called when an elicitation request is cancelled by the server. + */ + onElicitationCancelled?: (requestId: string) => void; +} + +/** + * Set up the sampling request handler on the MCP client. + * + * @param client - The MCP client instance + * @param callbacks - Callbacks for handling requests + * @param parentRequestId - The ID of the parent tool execution request + */ +export function setupSamplingHandler( + client: Client, + callbacks: SamplingHandlerCallbacks, + parentRequestId: string +): void { + client.setRequestHandler( + CreateMessageRequestSchema, + async (request: CreateMessageRequest) => { + const requestId = generateSamplingRequestId(); + const samplingRequest = convertToSamplingRequest(request.params); + + console.log('[MCP Handlers] Sampling request received:', requestId, samplingRequest); + + // Create a promise that will be resolved by the UI + const responsePromise = new Promise((resolve, reject) => { + samplingResolvers.set(requestId, { resolve, reject }); + }); + + // Notify the UI about the new request + callbacks.onSamplingRequest(requestId, samplingRequest, parentRequestId); + + try { + // Wait for the UI to respond + const response = await responsePromise; + console.log('[MCP Handlers] Sampling response:', requestId, response); + + // Convert to SDK format and return + return convertToSdkResult(response); + } finally { + // Clean up the resolver + samplingResolvers.delete(requestId); + } + } + ); +} + +/** + * Convert MCP SDK ElicitRequest to our internal ElicitationRequest format. + */ +function convertToElicitationRequest( + sdkRequest: ElicitRequest['params'] +): ElicitationRequest { + // Check if it's a form-based or URL-based elicitation + if ('requestedSchema' in sdkRequest) { + // Form-based elicitation + return { + mode: 'form', + message: sdkRequest.message, + schema: { + properties: Object.fromEntries( + Object.entries(sdkRequest.requestedSchema.properties || {}).map(([key, value]) => { + const prop = value as { type?: string; description?: string; enum?: string[]; default?: unknown }; + return [ + key, + { + name: key, + type: (prop.type || 'string') as 'string' | 'number' | 'boolean', + description: prop.description, + enum: prop.enum, + default: prop.default as string | number | boolean | undefined, + }, + ]; + }) + ), + required: sdkRequest.requestedSchema.required, + }, + serverName: 'MCP Server', + }; + } else { + // URL-based elicitation + const urlParams = sdkRequest as { message: string; url: string; elicitationId: string }; + return { + mode: 'url', + message: urlParams.message, + url: urlParams.url, + elicitationId: urlParams.elicitationId, + serverName: 'MCP Server', + }; + } +} + +/** + * Convert user response to MCP SDK ElicitResult format. + */ +function convertToElicitResult( + data: Record, + action: 'accept' | 'decline' | 'cancel' = 'accept' +): ElicitResult { + // Convert data to the expected type for ElicitResult.content + const content: Record | undefined = + action === 'accept' + ? Object.fromEntries( + Object.entries(data).map(([key, value]) => { + // Ensure values match expected types + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return [key, value]; + } + if (Array.isArray(value) && value.every((v) => typeof v === 'string')) { + return [key, value as string[]]; + } + // Convert other types to string + return [key, String(value)]; + }) + ) + : undefined; + + return { + action, + content, + }; +} + +/** + * Set up the elicitation request handler on the MCP client. + * + * @param client - The MCP client instance + * @param callbacks - Callbacks for handling requests + * @param parentRequestId - The ID of the parent tool execution request + */ +export function setupElicitationHandler( + client: Client, + callbacks: ElicitationHandlerCallbacks, + parentRequestId: string +): void { + client.setRequestHandler( + ElicitRequestSchema, + async (request: ElicitRequest) => { + const requestId = generateElicitationRequestId(); + const elicitationRequest = convertToElicitationRequest(request.params); + + console.log('[MCP Handlers] Elicitation request received:', requestId, elicitationRequest); + + // Create a promise that will be resolved by the UI + const responsePromise = new Promise>((resolve, reject) => { + elicitationResolvers.set(requestId, { resolve, reject }); + }); + + // Notify the UI about the new request + callbacks.onElicitationRequest(requestId, elicitationRequest, parentRequestId); + + try { + // Wait for the UI to respond + const response = await responsePromise; + console.log('[MCP Handlers] Elicitation response:', requestId, response); + + // Convert to SDK format and return + return convertToElicitResult(response); + } catch (err) { + // User declined or cancelled + const message = err instanceof Error ? err.message : 'Unknown error'; + if (message.includes('rejected') || message.includes('declined')) { + return convertToElicitResult({}, 'decline'); + } + return convertToElicitResult({}, 'cancel'); + } finally { + // Clean up the resolver + elicitationResolvers.delete(requestId); + } + } + ); +} + +/** + * Resolve a pending sampling request with a response. + * This should be called by the UI when the user provides a response. + * + * @param requestId - The ID of the sampling request + * @param response - The sampling response from the user + */ +export function resolveSamplingRequest( + requestId: string, + response: SamplingResponse +): void { + const resolver = samplingResolvers.get(requestId); + if (resolver) { + resolver.resolve(response); + samplingResolvers.delete(requestId); + } else { + console.warn('[MCP Handlers] No resolver found for sampling request:', requestId); + } +} + +/** + * Reject a pending sampling request. + * This should be called by the UI when the user rejects the request. + * + * @param requestId - The ID of the sampling request + * @param reason - Optional reason for rejection + */ +export function rejectSamplingRequest(requestId: string, reason?: string): void { + const resolver = samplingResolvers.get(requestId); + if (resolver) { + resolver.reject(new Error(reason || 'Sampling request rejected by user')); + samplingResolvers.delete(requestId); + } else { + console.warn('[MCP Handlers] No resolver found for sampling request:', requestId); + } +} + +/** + * Resolve a pending elicitation request with user input. + * + * @param requestId - The ID of the elicitation request + * @param data - The user input data + */ +export function resolveElicitationRequest( + requestId: string, + data: Record +): void { + const resolver = elicitationResolvers.get(requestId); + if (resolver) { + resolver.resolve(data); + elicitationResolvers.delete(requestId); + } else { + console.warn('[MCP Handlers] No resolver found for elicitation request:', requestId); + } +} + +/** + * Reject a pending elicitation request. + * + * @param requestId - The ID of the elicitation request + * @param reason - Optional reason for rejection + */ +export function rejectElicitationRequest(requestId: string, reason?: string): void { + const resolver = elicitationResolvers.get(requestId); + if (resolver) { + resolver.reject(new Error(reason || 'Elicitation request rejected by user')); + elicitationResolvers.delete(requestId); + } else { + console.warn('[MCP Handlers] No resolver found for elicitation request:', requestId); + } +} + +/** + * Clear all pending resolvers (e.g., when disconnecting). + */ +export function clearAllPendingRequests(): void { + // Reject all pending sampling requests + for (const [requestId, resolver] of samplingResolvers) { + resolver.reject(new Error('Connection closed')); + samplingResolvers.delete(requestId); + } + + // Reject all pending elicitation requests + for (const [requestId, resolver] of elicitationResolvers) { + resolver.reject(new Error('Connection closed')); + elicitationResolvers.delete(requestId); + } +} + +/** + * Check if there are any pending sampling requests. + */ +export function hasPendingSamplingRequests(): boolean { + return samplingResolvers.size > 0; +} + +/** + * Check if there are any pending elicitation requests. + */ +export function hasPendingElicitationRequests(): boolean { + return elicitationResolvers.size > 0; +} diff --git a/core/src/index.ts b/core/src/index.ts new file mode 100644 index 000000000..7278e48d6 --- /dev/null +++ b/core/src/index.ts @@ -0,0 +1,44 @@ +/** + * @anthropic/inspector-core + * + * Core MCP client logic for Inspector - no React dependencies. + * This package can be used in CLI tools, test frameworks, or any JS environment. + */ + +// Client lifecycle +export { + createMcpClient, + connectClient, + disconnectClient, + isClientConnected, + serverSupports, +} from './client.js'; +export type { McpClientOptions, ServerInfo } from './client.js'; + +// Transport creation +export { + createHttpTransport, + createAuthenticatedTransport, + isValidHttpUrl, +} from './transport.js'; + +// Request handlers (sampling, elicitation) +export { + setupSamplingHandler, + setupElicitationHandler, + resolveSamplingRequest, + rejectSamplingRequest, + resolveElicitationRequest, + rejectElicitationRequest, + generateSamplingRequestId, + generateElicitationRequestId, + clearAllPendingRequests, + hasPendingSamplingRequests, + hasPendingElicitationRequests, +} from './handlers.js'; + +// All types +export * from './types/index.js'; + +// Data layer interfaces and memory stubs +export * from './data/index.js'; diff --git a/core/src/transport.ts b/core/src/transport.ts new file mode 100644 index 000000000..35df70410 --- /dev/null +++ b/core/src/transport.ts @@ -0,0 +1,65 @@ +/** + * HTTP Transport Factory - Create transports for remote MCP servers + * + * This module provides factory functions for creating HTTP transports + * that work in the browser using the MCP SDK's StreamableHTTPClientTransport. + * + * Usage: + * import { createHttpTransport } from '@/lib/mcp/transport'; + * + * const transport = createHttpTransport('http://localhost:3000/mcp'); + * await client.connect(transport); + */ + +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; + +/** + * Create an HTTP transport for a remote MCP server. + * + * @param url - The MCP server endpoint URL + * @param headers - Optional custom headers (e.g., for API keys) + * @returns StreamableHTTPClientTransport instance + */ +export function createHttpTransport( + url: string, + headers?: Record +): StreamableHTTPClientTransport { + const options: ConstructorParameters[1] = {}; + + if (headers) { + options.requestInit = { headers }; + } + + return new StreamableHTTPClientTransport(new URL(url), options); +} + +/** + * Create an HTTP transport with Bearer token authentication. + * + * @param url - The MCP server endpoint URL + * @param token - Bearer token (with or without 'Bearer ' prefix) + * @returns StreamableHTTPClientTransport instance + */ +export function createAuthenticatedTransport( + url: string, + token: string +): StreamableHTTPClientTransport { + return createHttpTransport(url, { + Authorization: token.startsWith('Bearer ') ? token : `Bearer ${token}`, + }); +} + +/** + * Validate that a URL is suitable for HTTP transport. + * + * @param url - URL to validate + * @returns true if valid HTTP/HTTPS URL + */ +export function isValidHttpUrl(url: string): boolean { + try { + const parsed = new URL(url); + return parsed.protocol === 'http:' || parsed.protocol === 'https:'; + } catch { + return false; + } +} diff --git a/core/src/types/auth.ts b/core/src/types/auth.ts new file mode 100644 index 000000000..942d995ec --- /dev/null +++ b/core/src/types/auth.ts @@ -0,0 +1,29 @@ +/** + * Authentication and OAuth types + */ + +export interface OAuthState { + authorizationUrl?: string; + authorizationCode?: string; + state?: string; + stateVerified?: boolean; + tokenEndpoint?: string; + accessToken?: string; + tokenType?: string; + expiresIn?: number; + expiresAt?: Date; + refreshToken?: string; + scopes?: string[]; + decodedToken?: { + header: Record; + payload: Record; + }; +} + +/** + * Root directory configuration for MCP roots capability + */ +export interface Root { + name: string; + uri: string; +} diff --git a/core/src/types/capabilities.ts b/core/src/types/capabilities.ts new file mode 100644 index 000000000..1dceeea85 --- /dev/null +++ b/core/src/types/capabilities.ts @@ -0,0 +1,11 @@ +/** + * Experimental capabilities configuration types + */ + +export interface ExperimentalCapability { + id: string; + name: string; + description: string; + enabled: boolean; + warning?: string; +} diff --git a/core/src/types/clientRequests.ts b/core/src/types/clientRequests.ts new file mode 100644 index 000000000..588d7d6f5 --- /dev/null +++ b/core/src/types/clientRequests.ts @@ -0,0 +1,68 @@ +/** + * Client request types for MCP client features + * Per MCP 2025-11-25 specification + */ + +import type { ToolDefinition, ToolChoice } from './responses'; + +// Sampling message content +export interface SamplingMessage { + role: 'user' | 'assistant'; + content: + | { type: 'text'; text: string } + | { type: 'image'; data: string; mimeType: string }; +} + +// Model preferences for sampling +export interface ModelPreferences { + hints?: string[]; + costPriority?: number; + speedPriority?: number; + intelligencePriority?: number; +} + +// Sampling request from server +export interface SamplingRequest { + messages: SamplingMessage[]; + modelPreferences?: ModelPreferences; + maxTokens: number; + stopSequences?: string[]; + temperature?: number; + includeContext?: 'none' | 'thisServer' | 'allServers'; + // Tool calling support (MCP 2025-11-25) + tools?: ToolDefinition[]; + toolChoice?: ToolChoice; +} + +// Elicitation form field schema +export interface ElicitationFormField { + name: string; + type: 'string' | 'number' | 'boolean'; + description?: string; + required?: boolean; + enum?: string[]; + default?: string | number | boolean; +} + +// Elicitation form request +export interface ElicitationFormRequest { + mode: 'form'; + message: string; + schema: { + properties: Record; + required?: string[]; + }; + serverName: string; +} + +// Elicitation URL request +export interface ElicitationUrlRequest { + mode: 'url'; + message: string; + url: string; + elicitationId: string; + serverName: string; +} + +// Union type for elicitation requests +export type ElicitationRequest = ElicitationFormRequest | ElicitationUrlRequest; diff --git a/core/src/types/history.ts b/core/src/types/history.ts new file mode 100644 index 000000000..1fdbdeeae --- /dev/null +++ b/core/src/types/history.ts @@ -0,0 +1,73 @@ +/** + * History types for MCP request tracking + * + * These types support hierarchical request traces where tool calls + * can trigger child requests (sampling, elicitation). + */ + +/** + * Request type discriminator for hierarchical display + * - 'primary': Tool calls, resource reads, prompt gets + * - 'client': Sampling/elicitation requests triggered by server + */ +export type RequestType = 'primary' | 'client'; + +/** + * A single entry in the request history + */ +export interface HistoryEntry { + /** Unique identifier */ + id: string; + /** ISO timestamp of when the request was made */ + timestamp: string; + /** MCP method (e.g., 'tools/call', 'resources/read', 'sampling/createMessage') */ + method: string; + /** Target name (tool name, resource URI, prompt name) */ + target: string | null; + /** Request parameters */ + params?: Record; + /** Response data */ + response?: Record; + /** Request duration in milliseconds */ + duration: number; + /** Whether the request succeeded */ + success: boolean; + /** Whether this entry is pinned (protected from cleanup) */ + pinned: boolean; + /** Optional user-provided label */ + label?: string; + /** SSE event ID (for SSE transport) */ + sseId?: string; + /** Progress token for long-running operations */ + progressToken?: string; + + // Hierarchical request trace fields + + /** Request type: 'primary' for tool/resource/prompt calls, 'client' for sampling/elicitation */ + requestType?: RequestType; + /** For client requests, links to parent tool call */ + parentRequestId?: string; + /** For primary requests, links to triggered client requests */ + childRequestIds?: string[]; + /** Offset from parent request start (ms), for client requests */ + relativeTime?: number; +} + +/** + * Create a new history entry with defaults + */ +export function createHistoryEntry( + partial: Omit & { id?: string } +): HistoryEntry { + return { + id: partial.id || `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + timestamp: new Date().toISOString(), + pinned: false, + ...partial, + }; +} + +/** + * Maximum number of history entries to keep (excluding pinned) + */ +export const MAX_HISTORY_ENTRIES = 500; diff --git a/core/src/types/index.ts b/core/src/types/index.ts new file mode 100644 index 000000000..e4b92ec79 --- /dev/null +++ b/core/src/types/index.ts @@ -0,0 +1,12 @@ +/** + * Re-export all types for @anthropic/inspector-core + */ +export * from './history.js'; +export * from './logs.js'; +export * from './servers.js'; +export * from './clientRequests.js'; +export * from './responses.js'; +export * from './testingProfiles.js'; +export * from './capabilities.js'; +export * from './tasks.js'; +export * from './auth.js'; diff --git a/core/src/types/logs.ts b/core/src/types/logs.ts new file mode 100644 index 000000000..7a8073cf4 --- /dev/null +++ b/core/src/types/logs.ts @@ -0,0 +1,84 @@ +/** + * Log types for MCP protocol event logging + * + * Supports RFC 5424 log levels and request correlation + * for filtering logs by request chain. + */ + +/** + * RFC 5424 log levels in order of severity + */ +export const LOG_LEVELS = [ + 'debug', + 'info', + 'notice', + 'warning', + 'error', + 'critical', + 'alert', + 'emergency', +] as const; + +export type LogLevel = (typeof LOG_LEVELS)[number]; + +/** + * Color mapping for log levels (Mantine color names) + */ +export const LOG_LEVEL_COLORS: Record = { + debug: 'gray', + info: 'blue', + notice: 'cyan', + warning: 'yellow', + error: 'red', + critical: 'red', + alert: 'red', + emergency: 'red', +}; + +/** + * Logger categories for grouping logs + */ +export type LoggerCategory = 'connection' | 'protocol' | 'tools' | 'resources' | 'prompts' | 'sampling' | 'elicitation'; + +/** + * A single log entry + */ +export interface LogEntry { + /** ISO timestamp of when the log was created */ + timestamp: string; + /** Log level (RFC 5424) */ + level: LogLevel; + /** Log message */ + message: string; + /** Logger category */ + logger: string; + /** The request ID that generated this log (for correlation) */ + requestId?: string; + /** For logs from client requests, links to parent request */ + parentRequestId?: string; +} + +/** + * Create a new log entry with automatic timestamp + */ +export function createLogEntry( + level: LogLevel, + message: string, + logger: string, + requestId?: string, + parentRequestId?: string +): LogEntry { + return { + timestamp: new Date().toISOString(), + level, + message, + logger, + requestId, + parentRequestId, + }; +} + +/** + * Maximum number of log entries to keep + */ +export const MAX_LOG_ENTRIES = 1000; diff --git a/core/src/types/responses.ts b/core/src/types/responses.ts new file mode 100644 index 000000000..4927515fc --- /dev/null +++ b/core/src/types/responses.ts @@ -0,0 +1,58 @@ +/** + * Shared response types for MCP client features + * Per MCP 2025-11-25 specification + */ + +// Tool definition for sampling requests +export interface ToolDefinition { + name: string; + description?: string; + inputSchema?: Record; +} + +// Tool call in sampling response +export interface ToolCall { + id: string; + name: string; + arguments: Record; +} + +// Tool choice options +export type ToolChoice = + | { type: 'auto' } + | { type: 'none' } + | { type: 'required' } + | { type: 'tool'; name: string }; + +// Sampling response content +export type SamplingContent = + | { type: 'text'; text: string } + | { type: 'image'; data: string; mimeType: string }; + +// Stop reason for sampling response +export type StopReason = 'endTurn' | 'stopSequence' | 'maxTokens' | 'toolUse'; + +// Sampling response sent back to server +export interface SamplingResponse { + content: SamplingContent; + model: string; + stopReason: StopReason; + toolCalls?: ToolCall[]; +} + +// Elicitation response action +export type ElicitationAction = 'accept' | 'decline' | 'cancel'; + +// Elicitation response sent back to server +export interface ElicitationResponse { + action: ElicitationAction; + data?: Record; +} + +// Request info for logs correlation +export interface RequestInfo { + id: string; + method: string; + target?: string; + timestamp: string; +} diff --git a/core/src/types/servers.ts b/core/src/types/servers.ts new file mode 100644 index 000000000..4952f1df3 --- /dev/null +++ b/core/src/types/servers.ts @@ -0,0 +1,109 @@ +/** + * Server configuration types for MCP Inspector + * + * ServerConfig represents the persisted configuration for an MCP server. + * This is separate from runtime state (connection status, capabilities). + */ + +/** + * Transport type for MCP servers + */ +export type TransportType = 'http' | 'stdio'; + +/** + * Connection mode for servers + * - 'direct': Connect directly from browser (HTTP only) + * - 'proxy': Connect via proxy server (required for STDIO) + */ +export type ConnectionMode = 'direct' | 'proxy'; + +/** + * Server configuration (persisted) + */ +export interface ServerConfig { + /** Unique identifier */ + id: string; + /** Display name */ + name: string; + /** Transport type */ + transport: TransportType; + /** URL for HTTP transport */ + url?: string; + /** Command for STDIO transport (future, requires proxy) */ + command?: string; + /** Arguments for STDIO command */ + args?: string[]; + /** Environment variables for STDIO */ + env?: Record; + /** Custom headers for HTTP requests */ + headers?: Record; + /** Connection mode */ + connectionMode?: ConnectionMode; + /** ISO timestamp of creation */ + createdAt: string; + /** ISO timestamp of last update */ + updatedAt: string; +} + +/** + * Runtime server state (not persisted) + */ +export interface ServerRuntimeState { + /** Connection status */ + status: 'disconnected' | 'connecting' | 'connected' | 'failed'; + /** Capabilities once connected */ + capabilities?: { + tools?: number; + resources?: number; + prompts?: number; + }; + /** Error message if failed */ + error?: string; + /** Retry count for failed connections */ + retryCount?: number; +} + +/** + * Combined server info (config + runtime state) + */ +export interface ServerInfo extends ServerConfig { + /** Runtime state */ + runtime: ServerRuntimeState; +} + +/** + * Create a new server config with defaults + */ +export function createServerConfig( + partial: Omit & { id?: string } +): ServerConfig { + const now = new Date().toISOString(); + return { + id: partial.id || `server-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + createdAt: now, + updatedAt: now, + connectionMode: partial.transport === 'http' ? 'direct' : 'proxy', + ...partial, + }; +} + +/** + * Server display model for UI components (ServerCard) + * Combines config with runtime state for display purposes + */ +export interface ServerDisplayModel { + id: string; + name: string; + version: string; + transport: 'stdio' | 'http'; + command?: string; + url?: string; + status: 'connected' | 'disconnected' | 'failed'; + capabilities: { tools: number; resources: number; prompts: number } | null; + retryCount?: number; + error?: string; + connectionMode?: ConnectionMode; +} + +/** @deprecated Use ServerDisplayModel instead */ +export type MockServer = ServerDisplayModel; diff --git a/core/src/types/tasks.ts b/core/src/types/tasks.ts new file mode 100644 index 000000000..6e5ed0890 --- /dev/null +++ b/core/src/types/tasks.ts @@ -0,0 +1,32 @@ +/** + * Task tracking types for long-running operations + */ + +export interface ActiveTask { + id: string; + method: string; + name: string; + status: 'running' | 'waiting'; + progress: number; + progressMessage: string | null; + startedAt: string; +} + +export interface CompletedTask { + id: string; + method: string; + name: string; + status: 'completed' | 'failed'; + progress: number; + startedAt: string; + completedAt: string; + error?: string; +} + +export const taskStatusColors: Record = { + waiting: 'gray', + running: 'blue', + completed: 'green', + failed: 'red', + cancelled: 'orange', +}; diff --git a/core/src/types/testingProfiles.ts b/core/src/types/testingProfiles.ts new file mode 100644 index 000000000..45128aef7 --- /dev/null +++ b/core/src/types/testingProfiles.ts @@ -0,0 +1,48 @@ +/** + * Testing Profiles for Sampling/Elicitation Response Strategies + */ + +export type SamplingProviderType = 'manual' | 'mock'; + +export interface ModelOverride { + pattern: string; // e.g., "claude-*", "gpt-*" + response: string; +} + +export interface TestingProfile { + id: string; + name: string; + description?: string; + samplingProvider: SamplingProviderType; + autoRespond: boolean; + defaultResponse?: string; + defaultModel?: string; + defaultStopReason?: 'endTurn' | 'stopSequence' | 'maxTokens' | 'toolUse'; + modelOverrides?: ModelOverride[]; + elicitationAutoRespond?: boolean; + elicitationDefaults?: Record; +} + +/** + * Helper to get response for a model hint based on profile overrides + */ +export function getResponseForModelHint( + profile: TestingProfile, + modelHints?: string[] +): string { + if (!profile.modelOverrides || !modelHints || modelHints.length === 0) { + return profile.defaultResponse || ''; + } + + // Check each hint against patterns + for (const hint of modelHints) { + for (const override of profile.modelOverrides) { + const pattern = override.pattern.replace('*', '.*'); + if (new RegExp(`^${pattern}$`).test(hint)) { + return override.response; + } + } + } + + return profile.defaultResponse || ''; +} diff --git a/core/tsconfig.json b/core/tsconfig.json new file mode 100644 index 000000000..15c1c7e90 --- /dev/null +++ b/core/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..fed33bba2 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "@modelcontextprotocol/inspector", + "version": "2.0.0-alpha.0", + "description": "MCP Inspector V2 - A tool for testing and debugging MCP servers", + "private": true, + "type": "module", + "workspaces": [ + "core", + "client" + ], + "scripts": { + "test": "playwright test", + "test:smoke": "playwright test tests/smoke/", + "test:e2e": "playwright test tests/e2e/", + "test:ui": "playwright test --ui", + "test:report": "playwright show-report" + }, + "devDependencies": { + "@playwright/test": "^1.49.0", + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=18.0.0" + } +} From 474f9b43350a806e8f97288312bcd327ba6b527e Mon Sep 17 00:00:00 2001 From: olaservo Date: Mon, 19 Jan 2026 08:36:59 -0700 Subject: [PATCH 2/3] test(core): add unit and integration test scripts - test-core.js: Unit tests for memory implementations (repositories, services, type helpers) - test-real-server.js: Integration tests with Everything MCP server Co-Authored-By: Claude Opus 4.5 --- core/test-core.js | 402 +++++++++++++++++++++++++++++++++++++++ core/test-real-server.js | 180 ++++++++++++++++++ package.json | 11 +- 3 files changed, 583 insertions(+), 10 deletions(-) create mode 100644 core/test-core.js create mode 100644 core/test-real-server.js diff --git a/core/test-core.js b/core/test-core.js new file mode 100644 index 000000000..7bc674dbf --- /dev/null +++ b/core/test-core.js @@ -0,0 +1,402 @@ +/** + * Manual test script for inspector-core package + * Run with: node test-core.mjs + */ + +import { + // Memory repositories + createMemoryServerConfigRepository, + createMemoryHistoryRepository, + createMemoryLogsRepository, + createMemoryTestingProfileRepository, + + // Memory services + createMemoryConnectionService, + createMemoryExecutionService, + + // Utilities + isValidHttpUrl, + generateSamplingRequestId, + generateElicitationRequestId, + + // Client (can create, but not connect without server) + createMcpClient, + + // Types/helpers + createServerConfig, + createHistoryEntry, + createLogEntry, + LOG_LEVELS, +} from './dist/index.js'; + +console.log('=== Inspector Core Package Tests ===\n'); + +// Track test results +let passed = 0; +let failed = 0; + +function test(name, fn) { + try { + fn(); + console.log(`[PASS] ${name}`); + passed++; + } catch (error) { + console.log(`[FAIL] ${name}`); + console.log(` ${error.message}`); + failed++; + } +} + +function assert(condition, message) { + if (!condition) throw new Error(message || 'Assertion failed'); +} + +// ============================================ +// 1. URL Validation +// ============================================ +console.log('\n--- URL Validation ---'); + +test('isValidHttpUrl accepts http://localhost:3000/mcp', () => { + assert(isValidHttpUrl('http://localhost:3000/mcp') === true); +}); + +test('isValidHttpUrl accepts https://api.example.com/mcp', () => { + assert(isValidHttpUrl('https://api.example.com/mcp') === true); +}); + +test('isValidHttpUrl rejects ftp://example.com', () => { + assert(isValidHttpUrl('ftp://example.com') === false); +}); + +test('isValidHttpUrl rejects invalid URL', () => { + assert(isValidHttpUrl('not-a-url') === false); +}); + +// ============================================ +// 2. ID Generation +// ============================================ +console.log('\n--- ID Generation ---'); + +test('generateSamplingRequestId returns unique IDs', () => { + const id1 = generateSamplingRequestId(); + const id2 = generateSamplingRequestId(); + assert(id1 !== id2, 'IDs should be unique'); + assert(id1.startsWith('sampling-'), 'Should start with sampling-'); +}); + +test('generateElicitationRequestId returns unique IDs', () => { + const id1 = generateElicitationRequestId(); + const id2 = generateElicitationRequestId(); + assert(id1 !== id2, 'IDs should be unique'); + assert(id1.startsWith('elicitation-'), 'Should start with elicitation-'); +}); + +// ============================================ +// 3. ServerConfig Repository +// ============================================ +console.log('\n--- ServerConfig Repository ---'); + +test('ServerConfigRepository CRUD operations', async () => { + const repo = createMemoryServerConfigRepository(); + + // Create + const config = await repo.create({ + name: 'Test Server', + transport: 'http', + url: 'http://localhost:3000/mcp', + connectionMode: 'direct', + }); + assert(config.id, 'Should have generated ID'); + assert(config.name === 'Test Server', 'Name should match'); + assert(config.createdAt, 'Should have createdAt'); + + // List + const list = await repo.list(); + assert(list.length === 1, 'Should have 1 config'); + + // Get + const fetched = await repo.get(config.id); + assert(fetched?.name === 'Test Server', 'Get should return config'); + + // Update + const updated = await repo.update(config.id, { name: 'Updated Server' }); + assert(updated.name === 'Updated Server', 'Name should be updated'); + assert(updated.updatedAt, 'Should have updatedAt'); + + // Delete + await repo.delete(config.id); + const afterDelete = await repo.list(); + assert(afterDelete.length === 0, 'Should be empty after delete'); +}); + +// ============================================ +// 4. History Repository +// ============================================ +console.log('\n--- History Repository ---'); + +test('HistoryRepository with parent-child relationships', async () => { + const repo = createMemoryHistoryRepository(); + + // Create parent entry (tool call) + const parent = await repo.add({ + method: 'tools/call', + target: 'test_tool', + params: { arg: 'value' }, + response: { result: 'success' }, + duration: 100, + success: true, + requestType: 'primary', + }); + assert(parent.id, 'Parent should have ID'); + + // Create child entry (sampling request) + const child = await repo.add({ + method: 'sampling/createMessage', + target: 'claude-3-sonnet', + params: { messages: [] }, + response: { content: 'response' }, + duration: 50, + success: true, + requestType: 'client', + parentRequestId: parent.id, + relativeTime: 25, + }); + + // Get children + const children = await repo.getChildren(parent.id); + assert(children.length === 1, 'Should have 1 child'); + assert(children[0].parentRequestId === parent.id, 'Child should reference parent'); + + // List with rootOnly + const rootOnly = await repo.list({ rootOnly: true }); + assert(rootOnly.length === 1, 'Should only return parent'); + assert(rootOnly[0].id === parent.id, 'Should be the parent'); + + // Delete all but keep pinned + await repo.update(parent.id, { pinned: true }); + await repo.deleteAll({ keepPinned: true }); + const afterDelete = await repo.list(); + assert(afterDelete.length === 1, 'Pinned entry should remain'); +}); + +// ============================================ +// 5. Logs Repository +// ============================================ +console.log('\n--- Logs Repository ---'); + +test('LogsRepository with request correlation', async () => { + const repo = createMemoryLogsRepository(); + + const requestId = 'req-123'; + + // Add logs for a request + await repo.add({ + level: 'info', + message: 'Starting request', + logger: 'connection', + requestId, + }); + + await repo.add({ + level: 'debug', + message: 'Processing data', + logger: 'tools', + requestId, + }); + + await repo.add({ + level: 'info', + message: 'Unrelated log', + logger: 'connection', + }); + + // Get logs for request + const requestLogs = await repo.getForRequest(requestId); + assert(requestLogs.length === 2, 'Should have 2 logs for request'); + + // Filter by level + const debugOnly = await repo.list({ minLevel: 'debug' }); + assert(debugOnly.every(l => LOG_LEVELS.indexOf(l.level) >= LOG_LEVELS.indexOf('debug'))); + + // Batch add + const batch = await repo.addBatch([ + { level: 'warning', message: 'Warning 1', logger: 'test' }, + { level: 'error', message: 'Error 1', logger: 'test' }, + ]); + assert(batch.length === 2, 'Batch should add 2 logs'); +}); + +// ============================================ +// 6. Testing Profile Repository +// ============================================ +console.log('\n--- Testing Profile Repository ---'); + +test('TestingProfileRepository operations', async () => { + const repo = createMemoryTestingProfileRepository(); + + // Create profile + const profile = await repo.create({ + name: 'Auto Mock', + description: 'Automatic mock responses', + samplingProvider: 'mock', + autoRespond: true, + defaultResponse: 'Mock response', + defaultModel: 'mock-model', + defaultStopReason: 'endTurn', + modelOverrides: [ + { pattern: 'claude-*', response: 'Claude mock response' }, + ], + }); + assert(profile.id, 'Should have ID'); + + // List + const list = await repo.list(); + assert(list.length === 1, 'Should have 1 profile'); + + // Update + await repo.update(profile.id, { autoRespond: false }); + const updated = await repo.get(profile.id); + assert(updated?.autoRespond === false, 'autoRespond should be updated'); +}); + +// ============================================ +// 7. Connection Service +// ============================================ +console.log('\n--- Connection Service ---'); + +test('ConnectionService state management', async () => { + const service = createMemoryConnectionService(); + + // Initial state + let state = service.getState(); + assert(state.status === 'disconnected', 'Initial status should be disconnected'); + assert(state.serverUrl === null, 'No server URL initially'); + + // Subscribe to changes + let notified = false; + const unsubscribe = service.subscribe(() => { + notified = true; + }); + + // Note: connect() would fail without real server, but we can test disconnect + await service.disconnect(); + state = service.getState(); + assert(state.status === 'disconnected', 'Should stay disconnected'); + + unsubscribe(); +}); + +// ============================================ +// 8. Execution Service +// ============================================ +console.log('\n--- Execution Service ---'); + +test('ExecutionService pending requests', () => { + const service = createMemoryExecutionService(); + + // Initial state + let state = service.getState(); + assert(state.isExecuting === false, 'Not executing initially'); + assert(state.pendingClientRequests.length === 0, 'No pending requests'); + + // Start execution + service.startExecution('req-001'); + state = service.getState(); + assert(state.isExecuting === true, 'Should be executing'); + assert(state.currentRequestId === 'req-001', 'Should have request ID'); + + // Add pending request + service.addPendingRequest({ + id: 'sampling-001', + type: 'sampling', + request: { + messages: [{ role: 'user', content: { type: 'text', text: 'Hello' } }], + }, + parentRequestId: 'req-001', + status: 'pending', + timestamp: new Date().toISOString(), + }); + state = service.getState(); + assert(state.pendingClientRequests.length === 1, 'Should have 1 pending request'); + + // Resolve pending request + service.resolvePendingRequest('sampling-001'); + state = service.getState(); + assert(state.pendingClientRequests[0].status === 'resolved', 'Should be resolved'); + + // End execution (doesn't clear pending requests - that's separate) + service.endExecution(); + state = service.getState(); + assert(state.isExecuting === false, 'Should not be executing'); + assert(state.currentRequestId === null, 'Request ID should be null'); + + // Clear pending requests explicitly + service.clearPendingRequests(); + state = service.getState(); + assert(state.pendingClientRequests.length === 0, 'Pending requests cleared after clearPendingRequests()'); +}); + +// ============================================ +// 9. MCP Client Creation +// ============================================ +console.log('\n--- MCP Client Creation ---'); + +test('createMcpClient returns client instance', () => { + const client = createMcpClient({ + name: 'Test Inspector', + version: '1.0.0', + }); + assert(client, 'Should return client instance'); + // Can't test connect without actual server +}); + +// ============================================ +// 10. Type Helpers +// ============================================ +console.log('\n--- Type Helpers ---'); + +test('createServerConfig helper', () => { + const config = createServerConfig({ + name: 'My Server', + transport: 'http', + url: 'http://localhost:3000/mcp', + }); + assert(config.connectionMode === 'direct', 'Default connectionMode should be direct'); +}); + +test('createHistoryEntry helper', () => { + const entry = createHistoryEntry({ + method: 'tools/call', + target: 'echo', + success: true, + duration: 100, + }); + assert(entry.id, 'Should generate ID'); + assert(entry.timestamp, 'Should generate timestamp'); + assert(entry.pinned === false, 'Default pinned should be false'); + assert(entry.method === 'tools/call', 'Method should match'); +}); + +test('createLogEntry helper', () => { + const entry = createLogEntry({ + level: 'info', + message: 'Test log', + }); + assert(entry.timestamp, 'Should have timestamp'); +}); + +test('LOG_LEVELS array has 8 RFC 5424 levels', () => { + assert(LOG_LEVELS.length === 8, 'Should have 8 levels'); + assert(LOG_LEVELS.includes('debug'), 'Should include debug'); + assert(LOG_LEVELS.includes('emergency'), 'Should include emergency'); +}); + +// ============================================ +// Summary +// ============================================ +console.log('\n=== Test Summary ==='); +console.log(`Passed: ${passed}`); +console.log(`Failed: ${failed}`); +console.log(`Total: ${passed + failed}`); + +process.exit(failed > 0 ? 1 : 0); diff --git a/core/test-real-server.js b/core/test-real-server.js new file mode 100644 index 000000000..46a74b89d --- /dev/null +++ b/core/test-real-server.js @@ -0,0 +1,180 @@ +/** + * Integration test with real Everything MCP server + * + * Prerequisites: + * PORT=6299 npx -y @modelcontextprotocol/server-everything streamableHttp + * + * Run with: + * node test-real-server.js + */ + +import { + createMcpClient, + connectClient, + disconnectClient, + isClientConnected, + serverSupports, + createHttpTransport, +} from './dist/index.js'; + +const SERVER_URL = 'http://localhost:6299/mcp'; + +console.log('=== Real MCP Server Integration Tests ===\n'); +console.log(`Connecting to: ${SERVER_URL}\n`); + +let passed = 0; +let failed = 0; + +async function test(name, fn) { + try { + await fn(); + console.log(`[PASS] ${name}`); + passed++; + } catch (error) { + console.log(`[FAIL] ${name}`); + console.log(` ${error.message}`); + failed++; + } +} + +function assert(condition, message) { + if (!condition) throw new Error(message || 'Assertion failed'); +} + +// ============================================ +// Run Tests +// ============================================ + +async function runTests() { + let client; + let serverInfo; + + // Test 1: Create client and connect + await test('Connect to Everything server', async () => { + client = createMcpClient({ name: 'Test Client', version: '1.0.0' }); + const transport = createHttpTransport(SERVER_URL); + serverInfo = await connectClient(client, transport); + + assert(serverInfo, 'Should return server info'); + assert(serverInfo.name, 'Server should have name'); + assert(serverInfo.version, 'Server should have version'); + console.log(` Server: ${serverInfo.name} v${serverInfo.version}`); + }); + + // Test 2: Check connection status + await test('Client reports connected', async () => { + assert(isClientConnected(client) === true, 'Should be connected'); + }); + + // Test 3: Check server capabilities + await test('Server has tools capability', async () => { + assert(serverSupports(serverInfo, 'tools') === true, 'Should support tools'); + }); + + await test('Server has resources capability', async () => { + assert(serverSupports(serverInfo, 'resources') === true, 'Should support resources'); + }); + + await test('Server has prompts capability', async () => { + assert(serverSupports(serverInfo, 'prompts') === true, 'Should support prompts'); + }); + + // Test 4: List tools + await test('List tools from server', async () => { + const result = await client.listTools(); + assert(result.tools, 'Should return tools array'); + assert(result.tools.length > 0, 'Should have at least one tool'); + console.log(` Found ${result.tools.length} tools: ${result.tools.map(t => t.name).join(', ')}`); + }); + + // Test 5: List resources + await test('List resources from server', async () => { + const result = await client.listResources(); + assert(result.resources, 'Should return resources array'); + console.log(` Found ${result.resources.length} resources`); + }); + + // Test 6: List prompts + await test('List prompts from server', async () => { + const result = await client.listPrompts(); + assert(result.prompts, 'Should return prompts array'); + console.log(` Found ${result.prompts.length} prompts`); + }); + + // Test 7: Call echo tool + await test('Call echo tool', async () => { + const result = await client.callTool({ + name: 'echo', + arguments: { message: 'Hello from inspector-core!' }, + }); + assert(result.content, 'Should return content'); + assert(result.content.length > 0, 'Should have content items'); + const text = result.content[0].text; + assert(text.includes('Hello from inspector-core'), 'Should echo our message'); + console.log(` Response: ${text}`); + }); + + // Test 8: Call add tool + await test('Call add tool', async () => { + const result = await client.callTool({ + name: 'add', + arguments: { a: 5, b: 3 }, + }); + assert(result.content, 'Should return content'); + const text = result.content[0].text; + assert(text.includes('8'), 'Should return 8'); + console.log(` 5 + 3 = ${text}`); + }); + + // Test 9: Read a resource + await test('Read a resource', async () => { + const resources = await client.listResources(); + if (resources.resources.length > 0) { + const uri = resources.resources[0].uri; + const result = await client.readResource({ uri }); + assert(result.contents, 'Should return contents'); + console.log(` Read resource: ${uri}`); + } + }); + + // Test 10: Get a prompt + await test('Get a prompt', async () => { + const prompts = await client.listPrompts(); + if (prompts.prompts.length > 0) { + const name = prompts.prompts[0].name; + const result = await client.getPrompt({ name }); + assert(result.messages, 'Should return messages'); + console.log(` Got prompt: ${name} (${result.messages.length} messages)`); + } + }); + + // Test 11: Disconnect + await test('Disconnect from server', async () => { + await disconnectClient(client); + // Note: isClientConnected may still return true due to cached server version + // The important thing is disconnect completes without error + }); + + // ============================================ + // Summary + // ============================================ + console.log('\n=== Test Summary ==='); + console.log(`Passed: ${passed}`); + console.log(`Failed: ${failed}`); + console.log(`Total: ${passed + failed}`); + + process.exit(failed > 0 ? 1 : 0); +} + +// Check if server is reachable first +try { + const response = await fetch(SERVER_URL); + // 400 is expected (needs proper MCP request) +} catch (error) { + console.error(`\nError: Cannot reach server at ${SERVER_URL}`); + console.error('Make sure the Everything server is running:'); + console.error(' PORT=6299 npx -y @modelcontextprotocol/server-everything streamableHttp\n'); + process.exit(1); +} + +runTests(); diff --git a/package.json b/package.json index fed33bba2..abd20337c 100644 --- a/package.json +++ b/package.json @@ -5,18 +5,9 @@ "private": true, "type": "module", "workspaces": [ - "core", - "client" + "core" ], - "scripts": { - "test": "playwright test", - "test:smoke": "playwright test tests/smoke/", - "test:e2e": "playwright test tests/e2e/", - "test:ui": "playwright test --ui", - "test:report": "playwright show-report" - }, "devDependencies": { - "@playwright/test": "^1.49.0", "@types/node": "^22.0.0", "typescript": "^5.7.0" }, From 6a32c0108feea4c424e1655e7f6f1d07d84c4b90 Mon Sep 17 00:00:00 2001 From: olaservo Date: Sun, 18 Jan 2026 13:05:50 -0700 Subject: [PATCH 3/3] feat(client): add Mantine UI with MCP SDK integration Complete Mantine-based UI for the MCP Inspector with: - App layout with responsive navigation - Server list page with mock data - Tools, Resources, Prompts pages with real MCP hooks - History and Logs pages with mock data - Tasks page for background task management - Sampling/Elicitation modals (standalone, no queue) - Server management modals (add, import, settings) - OAuth debugger modal - Roots configuration modal - Theme toggle (light/dark/auto) Uses mock data for storage-dependent features (pending #983). Client features (inline queue, tree view) will be added in PR #3. Co-Authored-By: Claude Opus 4.5 --- client/index.html | 23 + client/package-lock.json | 3455 +++++++++++++++++ client/package.json | 32 + client/postcss.config.cjs | 14 + client/src/App.tsx | 25 + client/src/components/AddServerModal.tsx | 237 ++ client/src/components/AnnotationBadges.tsx | 76 + client/src/components/ElicitationModal.tsx | 335 ++ .../src/components/ImportServerJsonModal.tsx | 654 ++++ .../src/components/ListChangedIndicator.tsx | 38 + client/src/components/OAuthDebuggerModal.tsx | 395 ++ .../components/RootsConfigurationModal.tsx | 205 + client/src/components/SamplingModal.tsx | 409 ++ client/src/components/ServerCard.tsx | 445 +++ client/src/components/ServerInfoModal.tsx | 254 ++ client/src/components/ServerSettingsModal.tsx | 359 ++ client/src/components/StatusIndicator.tsx | 35 + client/src/components/ThemeToggle.tsx | 43 + client/src/context/McpContext.tsx | 274 ++ client/src/context/index.ts | 14 + client/src/global.css | 10 + client/src/hooks/index.ts | 7 + client/src/hooks/useMcpPrompts.ts | 160 + client/src/hooks/useMcpResources.ts | 282 ++ client/src/hooks/useMcpTools.ts | 165 + client/src/layouts/AppLayout.tsx | 157 + client/src/lib/mcp/client.ts | 154 + client/src/lib/mcp/handlers.ts | 445 +++ client/src/lib/mcp/index.ts | 32 + client/src/lib/mcp/transport.ts | 65 + client/src/lib/toast.tsx | 115 + client/src/main.tsx | 27 + client/src/mocks/auth.ts | 174 + client/src/mocks/capabilities.ts | 34 + client/src/mocks/history.ts | 120 + client/src/mocks/index.ts | 8 + client/src/mocks/logs.ts | 21 + client/src/mocks/servers.ts | 38 + client/src/mocks/tasks.ts | 50 + client/src/mocks/testingProfiles.ts | 66 + client/src/pages/History.tsx | 281 ++ client/src/pages/Logs.tsx | 235 ++ client/src/pages/Prompts.tsx | 306 ++ client/src/pages/Resources.tsx | 526 +++ client/src/pages/ServerList.tsx | 261 ++ client/src/pages/Tasks.tsx | 223 ++ client/src/pages/Tools.tsx | 344 ++ client/src/types/auth.ts | 29 + client/src/types/capabilities.ts | 11 + client/src/types/clientRequests.ts | 68 + client/src/types/history.ts | 73 + client/src/types/index.ts | 14 + client/src/types/logs.ts | 84 + client/src/types/responses.ts | 58 + client/src/types/servers.ts | 109 + client/src/types/tasks.ts | 32 + client/src/types/testingProfiles.ts | 48 + client/tsconfig.json | 25 + client/tsconfig.node.json | 11 + client/vite.config.ts | 15 + package.json | 3 +- 61 files changed, 12207 insertions(+), 1 deletion(-) create mode 100644 client/index.html create mode 100644 client/package-lock.json create mode 100644 client/package.json create mode 100644 client/postcss.config.cjs create mode 100644 client/src/App.tsx create mode 100644 client/src/components/AddServerModal.tsx create mode 100644 client/src/components/AnnotationBadges.tsx create mode 100644 client/src/components/ElicitationModal.tsx create mode 100644 client/src/components/ImportServerJsonModal.tsx create mode 100644 client/src/components/ListChangedIndicator.tsx create mode 100644 client/src/components/OAuthDebuggerModal.tsx create mode 100644 client/src/components/RootsConfigurationModal.tsx create mode 100644 client/src/components/SamplingModal.tsx create mode 100644 client/src/components/ServerCard.tsx create mode 100644 client/src/components/ServerInfoModal.tsx create mode 100644 client/src/components/ServerSettingsModal.tsx create mode 100644 client/src/components/StatusIndicator.tsx create mode 100644 client/src/components/ThemeToggle.tsx create mode 100644 client/src/context/McpContext.tsx create mode 100644 client/src/context/index.ts create mode 100644 client/src/global.css create mode 100644 client/src/hooks/index.ts create mode 100644 client/src/hooks/useMcpPrompts.ts create mode 100644 client/src/hooks/useMcpResources.ts create mode 100644 client/src/hooks/useMcpTools.ts create mode 100644 client/src/layouts/AppLayout.tsx create mode 100644 client/src/lib/mcp/client.ts create mode 100644 client/src/lib/mcp/handlers.ts create mode 100644 client/src/lib/mcp/index.ts create mode 100644 client/src/lib/mcp/transport.ts create mode 100644 client/src/lib/toast.tsx create mode 100644 client/src/main.tsx create mode 100644 client/src/mocks/auth.ts create mode 100644 client/src/mocks/capabilities.ts create mode 100644 client/src/mocks/history.ts create mode 100644 client/src/mocks/index.ts create mode 100644 client/src/mocks/logs.ts create mode 100644 client/src/mocks/servers.ts create mode 100644 client/src/mocks/tasks.ts create mode 100644 client/src/mocks/testingProfiles.ts create mode 100644 client/src/pages/History.tsx create mode 100644 client/src/pages/Logs.tsx create mode 100644 client/src/pages/Prompts.tsx create mode 100644 client/src/pages/Resources.tsx create mode 100644 client/src/pages/ServerList.tsx create mode 100644 client/src/pages/Tasks.tsx create mode 100644 client/src/pages/Tools.tsx create mode 100644 client/src/types/auth.ts create mode 100644 client/src/types/capabilities.ts create mode 100644 client/src/types/clientRequests.ts create mode 100644 client/src/types/history.ts create mode 100644 client/src/types/index.ts create mode 100644 client/src/types/logs.ts create mode 100644 client/src/types/responses.ts create mode 100644 client/src/types/servers.ts create mode 100644 client/src/types/tasks.ts create mode 100644 client/src/types/testingProfiles.ts create mode 100644 client/tsconfig.json create mode 100644 client/tsconfig.node.json create mode 100644 client/vite.config.ts diff --git a/client/index.html b/client/index.html new file mode 100644 index 000000000..3be1a51b3 --- /dev/null +++ b/client/index.html @@ -0,0 +1,23 @@ + + + + + + MCP Inspector + + + +
+ + + diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 000000000..f030848cb --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,3455 @@ +{ + "name": "@anthropic/mcp-inspector-client", + "version": "2.0.0-alpha.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@anthropic/mcp-inspector-client", + "version": "2.0.0-alpha.0", + "dependencies": { + "@mantine/core": "^7.15.0", + "@mantine/hooks": "^7.15.0", + "@mantine/notifications": "^7.15.0", + "@modelcontextprotocol/sdk": "^1.25.1", + "@tabler/icons-react": "^3.35.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "postcss": "^8.4.49", + "postcss-preset-mantine": "^1.17.0", + "postcss-simple-vars": "^7.0.1", + "typescript": "^5.7.2", + "vite": "^5.4.11" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@hono/node-server": { + "version": "1.19.7", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.7.tgz", + "integrity": "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mantine/core": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.17.8.tgz", + "integrity": "sha512-42sfdLZSCpsCYmLCjSuntuPcDg3PLbakSmmYfz5Auea8gZYLr+8SS5k647doVu0BRAecqYOytkX2QC5/u/8VHw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.28", + "clsx": "^2.1.1", + "react-number-format": "^5.4.3", + "react-remove-scroll": "^2.6.2", + "react-textarea-autosize": "8.5.9", + "type-fest": "^4.27.0" + }, + "peerDependencies": { + "@mantine/hooks": "7.17.8", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/hooks": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.17.8.tgz", + "integrity": "sha512-96qygbkTjRhdkzd5HDU8fMziemN/h758/EwrFu7TlWrEP10Vw076u+Ap/sG6OT4RGPZYYoHrTlT+mkCZblWHuw==", + "license": "MIT", + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/notifications": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.17.8.tgz", + "integrity": "sha512-/YK16IZ198W6ru/IVecCtHcVveL08u2c8TbQTu/2p26LSIM9AbJhUkrU6H+AO0dgVVvmdmNdvPxcJnfq3S9TMg==", + "license": "MIT", + "dependencies": { + "@mantine/store": "7.17.8", + "react-transition-group": "4.4.5" + }, + "peerDependencies": { + "@mantine/core": "7.17.8", + "@mantine/hooks": "7.17.8", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/store": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.17.8.tgz", + "integrity": "sha512-/FrB6PAVH4NEjQ1dsc9qOB+VvVlSuyjf4oOOlM9gscPuapDP/79Ryq7JkhHYfS55VWQ/YUlY24hDI2VV+VptXg==", + "license": "MIT", + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.25.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", + "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.7", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.1.tgz", + "integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tabler/icons": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.35.0.tgz", + "integrity": "sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-react": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.35.0.tgz", + "integrity": "sha512-XG7t2DYf3DyHT5jxFNp5xyLVbL4hMJYJhiSdHADzAjLRYfL7AnjlRfiHDHeXxkb2N103rEIvTsBRazxXtAUz2g==", + "license": "MIT", + "dependencies": { + "@tabler/icons": "3.35.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": ">= 16" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", + "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.262", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", + "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.3.tgz", + "integrity": "sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-mixins": { + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/postcss-mixins/-/postcss-mixins-12.1.2.tgz", + "integrity": "sha512-90pSxmZVfbX9e5xCv7tI5RV1mnjdf16y89CJKbf/hD7GyOz1FCxcYMl8ZYA8Hc56dbApTKKmU9HfvgfWdCxlwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-js": "^4.0.1", + "postcss-simple-vars": "^7.0.1", + "sugarss": "^5.0.0", + "tinyglobby": "^0.2.14" + }, + "engines": { + "node": "^20.0 || ^22.0 || >=24.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-7.0.2.tgz", + "integrity": "sha512-5osppouFc0VR9/VYzYxO03VaDa3e8F23Kfd6/9qcZTUI8P58GIYlArOET2Wq0ywSl2o2PjELhYOFI4W7l5QHKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-preset-mantine": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.18.0.tgz", + "integrity": "sha512-sP6/s1oC7cOtBdl4mw/IRKmKvYTuzpRrH/vT6v9enMU/EQEQ31eQnHcWtFghOXLH87AAthjL/Q75rLmin1oZoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-mixins": "^12.0.0", + "postcss-nested": "^7.0.2" + }, + "peerDependencies": { + "postcss": ">=8.0.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-simple-vars": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz", + "integrity": "sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.1" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-number-format": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz", + "integrity": "sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==", + "license": "MIT", + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.30.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz", + "integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.2.tgz", + "integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.1", + "react-router": "6.30.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz", + "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/sugarss": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-5.0.1.tgz", + "integrity": "sha512-ctS5RYCBVvPoZAnzIaX5QSShK8ZiZxD5HUqSxlusvEMC+QZQIPCPOIJg6aceFX+K2rf4+SH89eu++h1Zmsr2nw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/tabbable": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.3.0.tgz", + "integrity": "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", + "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", + "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", + "license": "MIT", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 000000000..9562bf43b --- /dev/null +++ b/client/package.json @@ -0,0 +1,32 @@ +{ + "name": "@modelcontextprotocol/inspector-client", + "version": "2.0.0-alpha.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@modelcontextprotocol/inspector-core": "*", + "@mantine/core": "^7.15.0", + "@mantine/hooks": "^7.15.0", + "@mantine/notifications": "^7.15.0", + "@modelcontextprotocol/sdk": "^1.25.1", + "@tabler/icons-react": "^3.35.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "postcss": "^8.4.49", + "postcss-preset-mantine": "^1.17.0", + "postcss-simple-vars": "^7.0.1", + "typescript": "^5.7.2", + "vite": "^5.4.11" + } +} diff --git a/client/postcss.config.cjs b/client/postcss.config.cjs new file mode 100644 index 000000000..bfba0ddfa --- /dev/null +++ b/client/postcss.config.cjs @@ -0,0 +1,14 @@ +module.exports = { + plugins: { + 'postcss-preset-mantine': {}, + 'postcss-simple-vars': { + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }, + }, +}; diff --git a/client/src/App.tsx b/client/src/App.tsx new file mode 100644 index 000000000..6351e3ad3 --- /dev/null +++ b/client/src/App.tsx @@ -0,0 +1,25 @@ +import { Routes, Route } from 'react-router-dom'; +import { AppLayout } from './layouts/AppLayout'; +import { ServerList } from './pages/ServerList'; +import { Tools } from './pages/Tools'; +import { Resources } from './pages/Resources'; +import { Prompts } from './pages/Prompts'; +import { Logs } from './pages/Logs'; +import { Tasks } from './pages/Tasks'; +import { History } from './pages/History'; + +export default function App() { + return ( + + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); +} diff --git a/client/src/components/AddServerModal.tsx b/client/src/components/AddServerModal.tsx new file mode 100644 index 000000000..f9112c21b --- /dev/null +++ b/client/src/components/AddServerModal.tsx @@ -0,0 +1,237 @@ +import { useState, useEffect } from 'react'; +import { IconPlus, IconTrash } from '@tabler/icons-react'; +import { + Modal, + Button, + TextInput, + Select, + Stack, + Group, + Text, + ActionIcon, +} from '@mantine/core'; + +type TransportType = 'stdio' | 'http' | 'sse'; + +interface EnvVar { + key: string; + value: string; +} + +export interface ServerConfig { + id?: string; + name: string; + transport: TransportType; + command?: string; + url?: string; + env: Record; +} + +interface AddServerModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + server?: ServerConfig; + onSave: (config: ServerConfig) => void; +} + +export function AddServerModal({ + open, + onOpenChange, + server, + onSave, +}: AddServerModalProps) { + const isEditMode = !!server; + + const [name, setName] = useState(''); + const [transport, setTransport] = useState('stdio'); + const [command, setCommand] = useState(''); + const [url, setUrl] = useState(''); + const [envVars, setEnvVars] = useState([]); + + // Reset form when modal opens/closes or server changes + useEffect(() => { + if (open && server) { + setName(server.name); + setTransport(server.transport); + setCommand(server.command || ''); + setUrl(server.url || ''); + setEnvVars( + Object.entries(server.env || {}).map(([key, value]) => ({ + key, + value, + })) + ); + } else if (open && !server) { + setName(''); + setTransport('stdio'); + setCommand(''); + setUrl(''); + setEnvVars([]); + } + }, [open, server]); + + const addEnvVar = () => { + setEnvVars([...envVars, { key: '', value: '' }]); + }; + + const removeEnvVar = (index: number) => { + setEnvVars(envVars.filter((_, i) => i !== index)); + }; + + const updateEnvVar = ( + index: number, + field: 'key' | 'value', + value: string + ) => { + const updated = [...envVars]; + updated[index][field] = value; + setEnvVars(updated); + }; + + const handleSave = () => { + const env: Record = {}; + envVars.forEach(({ key, value }) => { + if (key.trim()) { + env[key.trim()] = value; + } + }); + + const config: ServerConfig = { + id: server?.id, + name: name.trim(), + transport, + env, + }; + + if (transport === 'stdio') { + config.command = command.trim(); + } else { + config.url = url.trim(); + } + + onSave(config); + onOpenChange(false); + }; + + const isValid = () => { + if (!name.trim()) return false; + if (transport === 'stdio' && !command.trim()) return false; + if ((transport === 'http' || transport === 'sse') && !url.trim()) + return false; + return true; + }; + + return ( + onOpenChange(false)} + title={isEditMode ? 'Edit Server' : 'Add Server'} + size="md" + > + + {/* Server Name */} + setName(e.target.value)} + required + /> + + {/* Transport Type */} + + {field.name} + {isRequired(key) && *} + + } + value={String(formData[key] ?? '')} + onChange={(val) => handleChange(key, val || '')} + data={field.enum.map((opt) => ({ value: opt, label: opt }))} + placeholder={`Select ${field.name}`} + /> + ) : ( + + {field.name} + {isRequired(key) && *} + + } + type={field.type === 'number' ? 'number' : 'text'} + value={formData[key] ?? ''} + onChange={(e) => + handleChange( + key, + field.type === 'number' + ? Number(e.target.value) + : e.target.value + ) + } + placeholder={`Enter ${field.name}`} + /> + )} + + ))} + + + {/* Security Warning */} + } + color="yellow" + title="Warning" + > + Only provide information you trust this server with. The server " + {request.serverName}" is requesting this data. + + + {/* Actions */} + + + + + + + + ); +} + +// URL Mode Component +function ElicitationUrlMode({ + open, + onOpenChange, + request, + onSubmit, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + request: ElicitationUrlRequest; + onSubmit?: (response: ElicitationResponse) => void; +}) { + const [copied, setCopied] = useState(false); + const [status] = useState<'waiting' | 'completed'>('waiting'); + + const handleCopyUrl = async () => { + await navigator.clipboard.writeText(request.url); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + const handleOpenInBrowser = () => { + window.open(request.url, '_blank'); + }; + + const handleCancel = () => { + onSubmit?.({ action: 'cancel' }); + onOpenChange(false); + }; + + const handleDecline = () => { + onSubmit?.({ action: 'decline' }); + onOpenChange(false); + }; + + // Extract domain from URL for display + let domain = ''; + try { + domain = new URL(request.url).hostname; + } catch { + domain = 'unknown'; + } + + return ( + onOpenChange(false)} + title="Server Request: External Action Required" + size="md" + > + + {/* Message */} + {request.message} + + + + {/* URL Display */} +
+ + The server is requesting you visit: + + + + {request.url} + + + + + +
+ + {/* Open in Browser Button */} + + + + + + + {/* Status */} + + + + Status: + + {status === 'waiting' ? ( + <> + + Waiting for completion... + + ) : ( + + Completed + + )} + + + + {/* Elicitation ID */} + + Elicitation ID: {request.elicitationId} + + + {/* Domain Warning */} + } + color="yellow" + title="Warning" + > + This will open an external URL ({domain}). Verify the domain before + proceeding. + + + {/* Actions */} + + + + +
+
+ ); +} diff --git a/client/src/components/ImportServerJsonModal.tsx b/client/src/components/ImportServerJsonModal.tsx new file mode 100644 index 000000000..31f3dabce --- /dev/null +++ b/client/src/components/ImportServerJsonModal.tsx @@ -0,0 +1,654 @@ +import { useState, useCallback } from 'react'; +import { + IconCheck, + IconAlertTriangle, + IconInfoCircle, + IconUpload, + IconX, +} from '@tabler/icons-react'; +import { + Modal, + Button, + TextInput, + Textarea, + Stack, + Group, + Text, + Box, + Badge, + Select, + Radio, + FileButton, +} from '@mantine/core'; + +// server.json schema types +interface EnvVar { + name: string; + description?: string; + default?: string; + isRequired?: boolean; + isSecret?: boolean; + choices?: string[]; +} + +interface Argument { + type: 'positional' | 'named'; + name?: string; + value?: string; + valueHint?: string; + description?: string; + default?: string; + isRequired?: boolean; + choices?: string[]; +} + +interface Package { + registryType: string; + identifier: string; + version: string; + runtimeHint?: string; + transport: { type: string }; + packageArguments?: Argument[]; + runtimeArguments?: Argument[]; + environmentVariables?: EnvVar[]; +} + +interface Remote { + type: 'streamable-http' | 'sse'; + url: string; + headers?: EnvVar[]; +} + +interface ServerJson { + $schema?: string; + name: string; + description: string; + title?: string; + version: string; + packages?: Package[]; + remotes?: Remote[]; +} + +interface ValidationResult { + valid: boolean; + errors: string[]; + warnings: string[]; + info: string[]; +} + +interface ImportServerJsonModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onImport: (config: { + name: string; + transport: string; + command?: string; + url?: string; + env: Record; + }) => void; +} + +function validateServerJson(jsonText: string): { + result: ValidationResult; + parsed: ServerJson | null; +} { + const result: ValidationResult = { + valid: false, + errors: [], + warnings: [], + info: [], + }; + + if (!jsonText.trim()) { + return { result, parsed: null }; + } + + let parsed: ServerJson; + try { + parsed = JSON.parse(jsonText); + } catch { + result.errors.push('Invalid JSON syntax'); + return { result, parsed: null }; + } + + // Basic schema validation + if (!parsed.name) { + result.errors.push('Missing required field: name'); + } + if (!parsed.description) { + result.errors.push('Missing required field: description'); + } + if (!parsed.version) { + result.errors.push('Missing required field: version'); + } + + if (!parsed.packages?.length && !parsed.remotes?.length) { + result.errors.push('Must have at least one package or remote'); + } + + if (result.errors.length === 0) { + result.valid = true; + result.info.push('Schema validation passed'); + + if (parsed.packages?.length) { + const pkg = parsed.packages[0]; + result.info.push( + `Package found: ${pkg.identifier} (${pkg.registryType})` + ); + if (pkg.runtimeHint) { + result.info.push(`Runtime hint: ${pkg.runtimeHint}`); + } + result.info.push(`Transport: ${pkg.transport?.type || 'stdio'}`); + } + + if (parsed.remotes?.length) { + const remote = parsed.remotes[0]; + result.info.push(`Remote found: ${remote.url} (${remote.type})`); + } + + // Check for required env vars + const pkg = parsed.packages?.[0]; + const requiredEnvVars = pkg?.environmentVariables?.filter( + (v) => v.isRequired + ); + if (requiredEnvVars?.length) { + requiredEnvVars.forEach((v) => { + result.warnings.push(`Environment variable ${v.name} is required`); + }); + } + } + + return { result, parsed }; +} + +export function ImportServerJsonModal({ + open, + onOpenChange, + onImport, +}: ImportServerJsonModalProps) { + const [jsonText, setJsonText] = useState(''); + const [validationResult, setValidationResult] = + useState(null); + const [parsedJson, setParsedJson] = useState(null); + const [sourceType, setSourceType] = useState<'package' | 'remote'>('package'); + const [selectedPackageIndex, setSelectedPackageIndex] = useState(0); + const [selectedRemoteIndex, setSelectedRemoteIndex] = useState(0); + const [envVars, setEnvVars] = useState>({}); + const [headers, setHeaders] = useState>({}); + const [serverNameOverride, setServerNameOverride] = useState(''); + + const handleValidate = useCallback(() => { + const { result, parsed } = validateServerJson(jsonText); + setValidationResult(result); + setParsedJson(parsed); + + if (parsed) { + // Determine source type + if (parsed.packages?.length && !parsed.remotes?.length) { + setSourceType('package'); + } else if (parsed.remotes?.length && !parsed.packages?.length) { + setSourceType('remote'); + } + + // Initialize env vars with defaults + const pkg = parsed.packages?.[selectedPackageIndex]; + if (pkg?.environmentVariables) { + const defaults: Record = {}; + pkg.environmentVariables.forEach((v) => { + if (v.default) { + defaults[v.name] = v.default; + } + }); + setEnvVars(defaults); + } + + // Initialize headers with defaults + const remote = parsed.remotes?.[selectedRemoteIndex]; + if (remote?.headers) { + const defaults: Record = {}; + remote.headers.forEach((h) => { + if (h.default) { + defaults[h.name] = h.default; + } + }); + setHeaders(defaults); + } + } + }, [jsonText, selectedPackageIndex, selectedRemoteIndex]); + + const handleClear = () => { + setJsonText(''); + setValidationResult(null); + setParsedJson(null); + setEnvVars({}); + setHeaders({}); + setServerNameOverride(''); + }; + + const handleFileSelect = (file: File | null) => { + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + const text = event.target?.result as string; + setJsonText(text); + }; + reader.readAsText(file); + } + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + const file = e.dataTransfer.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + const text = event.target?.result as string; + setJsonText(text); + }; + reader.readAsText(file); + } + }; + + const handleImport = () => { + if (!parsedJson) return; + + const name = + serverNameOverride.trim() || parsedJson.title || parsedJson.name; + + if (sourceType === 'package' && parsedJson.packages?.length) { + const pkg = parsedJson.packages[selectedPackageIndex]; + const runtimeHint = pkg.runtimeHint || 'npx'; + const command = `${runtimeHint} -y ${pkg.identifier}`; + + onImport({ + name, + transport: 'stdio', + command, + env: envVars, + }); + } else if (sourceType === 'remote' && parsedJson.remotes?.length) { + const remote = parsedJson.remotes[selectedRemoteIndex]; + + onImport({ + name, + transport: remote.type === 'sse' ? 'sse' : 'http', + url: remote.url, + env: headers, + }); + } + + onOpenChange(false); + }; + + const canImport = () => { + if (!validationResult?.valid || !parsedJson) return false; + + // Check required env vars are filled + if (sourceType === 'package') { + const pkg = parsedJson.packages?.[selectedPackageIndex]; + const requiredVars = + pkg?.environmentVariables?.filter((v) => v.isRequired) || []; + for (const v of requiredVars) { + if (!envVars[v.name]?.trim()) return false; + } + } + + // Check required headers are filled + if (sourceType === 'remote') { + const remote = parsedJson.remotes?.[selectedRemoteIndex]; + const requiredHeaders = + remote?.headers?.filter((h) => h.isRequired) || []; + for (const h of requiredHeaders) { + if (!headers[h.name]?.trim()) return false; + } + } + + return true; + }; + + const selectedPackage = parsedJson?.packages?.[selectedPackageIndex]; + const selectedRemote = parsedJson?.remotes?.[selectedRemoteIndex]; + + return ( + onOpenChange(false)} + title="Import MCP Registry server.json" + size="xl" + > + + {/* JSON Input */} + + + Paste server.json content or drag and drop a file: + + e.preventDefault()} + > +