From 112997d3522026452ede2dc61ffbac2930553c2e Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Thu, 2 Oct 2025 18:21:12 -0400 Subject: [PATCH 01/12] refactor, improve docs --- .env.integrated | 4 - .env.separate | 6 - README.md | 429 +- auth-server/README.md | 94 - docs/endpoints.md | 154 + docs/oauth-flow.md | 217 + docs/session-ownership.md | 145 + docs/streamable-http-design.md | 468 -- docs/user-id-system.md | 404 - embedded-oauth/README.md | 135 + embedded-oauth/eslint.config.mjs | 19 + embedded-oauth/jest.config.js | 19 + embedded-oauth/package-lock.json | 7141 +++++++++++++++++ embedded-oauth/package.json | 43 + .../src/auth}/auth-core.ts | 0 embedded-oauth/src/auth/provider.test.ts | 412 + {src => embedded-oauth/src}/auth/provider.ts | 12 +- embedded-oauth/src/config.ts | 24 + {src => embedded-oauth/src}/context.ts | 8 +- .../src}/handlers/common.ts | 2 +- .../src/handlers/mock-upstream-idp.ts | 353 + .../src}/handlers/shttp.integration.test.ts | 0 .../src}/handlers/shttp.test.ts | 0 {src => embedded-oauth/src}/handlers/shttp.ts | 0 {src => embedded-oauth/src}/handlers/sse.ts | 0 {src => embedded-oauth/src}/index.ts | 107 +- {src => embedded-oauth/src}/redis.ts | 0 .../src}/services/auth.test.ts | 12 +- {src => embedded-oauth/src}/services/auth.ts | 6 +- {src => embedded-oauth/src}/services/mcp.ts | 2 +- .../src/services}/redis-auth.ts | 8 +- .../redisTransport.integration.test.ts | 0 .../src}/services/redisTransport.test.ts | 0 .../src}/services/redisTransport.ts | 0 {src => embedded-oauth/src}/static/index.html | 0 {src => embedded-oauth/src}/static/mcp.png | Bin {src => embedded-oauth/src}/static/styles.css | 0 {shared => embedded-oauth/src}/types.ts | 14 +- {src => embedded-oauth/src}/utils/logger.ts | 0 embedded-oauth/tsconfig.json | 17 + external-oauth/README.md | 109 + external-oauth/auth-server/README.md | 123 + external-oauth/auth-server/eslint.config.mjs | 19 + external-oauth/auth-server/jest.config.js | 19 + external-oauth/auth-server/package-lock.json | 7141 +++++++++++++++++ external-oauth/auth-server/package.json | 43 + .../auth-server/src/auth/auth-core.ts | 87 + .../auth-server/src}/auth/provider.test.ts | 46 +- .../auth-server/src/auth/provider.ts | 378 + .../auth-server/src}/config.ts | 21 +- .../src/handlers/mock-upstream-idp.ts | 27 +- .../auth-server/src}/index.ts | 25 +- external-oauth/auth-server/src/redis.ts | 289 + .../auth-server/src/services/auth.test.ts | 292 + .../auth-server/src/services/auth.ts | 86 + .../auth-server/src/services/redis-auth.ts | 285 + .../auth-server/src/static/index.html | 97 + external-oauth/auth-server/src/static/mcp.png | Bin 0 -> 40652 bytes .../auth-server/src/static/styles.css | 188 + external-oauth/auth-server/src/types.ts | 92 + .../auth-server/src/utils/logger.ts | 176 + external-oauth/auth-server/tsconfig.json | 17 + external-oauth/mcp-server/README.md | 150 + external-oauth/mcp-server/eslint.config.mjs | 19 + external-oauth/mcp-server/jest.config.js | 19 + external-oauth/mcp-server/package-lock.json | 7141 +++++++++++++++++ external-oauth/mcp-server/package.json | 43 + .../mcp-server/src}/auth/external-verifier.ts | 2 +- external-oauth/mcp-server/src/config.ts | 28 + .../src/handlers/shttp.integration.test.ts | 694 ++ .../mcp-server/src/handlers/shttp.test.ts | 229 + .../mcp-server/src/handlers/shttp.ts | 177 + external-oauth/mcp-server/src/handlers/sse.ts | 100 + external-oauth/mcp-server/src/index.ts | 239 + external-oauth/mcp-server/src/redis.ts | 289 + external-oauth/mcp-server/src/services/mcp.ts | 686 ++ .../redisTransport.integration.test.ts | 291 + .../src/services/redisTransport.test.ts | 536 ++ .../mcp-server/src/services/redisTransport.ts | 351 + .../mcp-server/src/static/index.html | 97 + external-oauth/mcp-server/src/static/mcp.png | Bin 0 -> 40652 bytes .../mcp-server/src/static/styles.css | 188 + external-oauth/mcp-server/src/types.ts | 33 + external-oauth/mcp-server/src/utils/logger.ts | 205 + external-oauth/mcp-server/tsconfig.json | 17 + package-lock.json | 81 +- package.json | 82 +- ...integrated-e2e.sh => test-embedded-e2e.sh} | 22 +- ...t-separate-e2e.sh => test-external-e2e.sh} | 31 +- src/context.test.ts | 114 - src/types.ts | 31 - tsconfig.json | 9 +- 92 files changed, 30079 insertions(+), 1640 deletions(-) delete mode 100644 .env.integrated delete mode 100644 .env.separate delete mode 100644 auth-server/README.md create mode 100644 docs/endpoints.md create mode 100644 docs/oauth-flow.md create mode 100644 docs/session-ownership.md delete mode 100644 docs/streamable-http-design.md delete mode 100644 docs/user-id-system.md create mode 100644 embedded-oauth/README.md create mode 100644 embedded-oauth/eslint.config.mjs create mode 100644 embedded-oauth/jest.config.js create mode 100644 embedded-oauth/package-lock.json create mode 100644 embedded-oauth/package.json rename {shared => embedded-oauth/src/auth}/auth-core.ts (100%) create mode 100644 embedded-oauth/src/auth/provider.test.ts rename {src => embedded-oauth/src}/auth/provider.ts (95%) create mode 100644 embedded-oauth/src/config.ts rename {src => embedded-oauth/src}/context.ts (78%) rename {src => embedded-oauth/src}/handlers/common.ts (93%) create mode 100644 embedded-oauth/src/handlers/mock-upstream-idp.ts rename {src => embedded-oauth/src}/handlers/shttp.integration.test.ts (100%) rename {src => embedded-oauth/src}/handlers/shttp.test.ts (100%) rename {src => embedded-oauth/src}/handlers/shttp.ts (100%) rename {src => embedded-oauth/src}/handlers/sse.ts (100%) rename {src => embedded-oauth/src}/index.ts (63%) rename {src => embedded-oauth/src}/redis.ts (100%) rename {src => embedded-oauth/src}/services/auth.test.ts (96%) rename {src => embedded-oauth/src}/services/auth.ts (94%) rename {src => embedded-oauth/src}/services/mcp.ts (99%) rename {shared => embedded-oauth/src/services}/redis-auth.ts (97%) rename {src => embedded-oauth/src}/services/redisTransport.integration.test.ts (100%) rename {src => embedded-oauth/src}/services/redisTransport.test.ts (100%) rename {src => embedded-oauth/src}/services/redisTransport.ts (100%) rename {src => embedded-oauth/src}/static/index.html (100%) rename {src => embedded-oauth/src}/static/mcp.png (100%) rename {src => embedded-oauth/src}/static/styles.css (100%) rename {shared => embedded-oauth/src}/types.ts (88%) rename {src => embedded-oauth/src}/utils/logger.ts (100%) create mode 100644 embedded-oauth/tsconfig.json create mode 100644 external-oauth/README.md create mode 100644 external-oauth/auth-server/README.md create mode 100644 external-oauth/auth-server/eslint.config.mjs create mode 100644 external-oauth/auth-server/jest.config.js create mode 100644 external-oauth/auth-server/package-lock.json create mode 100644 external-oauth/auth-server/package.json create mode 100644 external-oauth/auth-server/src/auth/auth-core.ts rename {src => external-oauth/auth-server/src}/auth/provider.test.ts (90%) create mode 100644 external-oauth/auth-server/src/auth/provider.ts rename {src => external-oauth/auth-server/src}/config.ts (50%) rename src/handlers/fakeauth.ts => external-oauth/auth-server/src/handlers/mock-upstream-idp.ts (92%) rename {auth-server => external-oauth/auth-server/src}/index.ts (85%) create mode 100644 external-oauth/auth-server/src/redis.ts create mode 100644 external-oauth/auth-server/src/services/auth.test.ts create mode 100644 external-oauth/auth-server/src/services/auth.ts create mode 100644 external-oauth/auth-server/src/services/redis-auth.ts create mode 100644 external-oauth/auth-server/src/static/index.html create mode 100644 external-oauth/auth-server/src/static/mcp.png create mode 100644 external-oauth/auth-server/src/static/styles.css create mode 100644 external-oauth/auth-server/src/types.ts create mode 100644 external-oauth/auth-server/src/utils/logger.ts create mode 100644 external-oauth/auth-server/tsconfig.json create mode 100644 external-oauth/mcp-server/README.md create mode 100644 external-oauth/mcp-server/eslint.config.mjs create mode 100644 external-oauth/mcp-server/jest.config.js create mode 100644 external-oauth/mcp-server/package-lock.json create mode 100644 external-oauth/mcp-server/package.json rename {src => external-oauth/mcp-server/src}/auth/external-verifier.ts (99%) create mode 100644 external-oauth/mcp-server/src/config.ts create mode 100644 external-oauth/mcp-server/src/handlers/shttp.integration.test.ts create mode 100644 external-oauth/mcp-server/src/handlers/shttp.test.ts create mode 100644 external-oauth/mcp-server/src/handlers/shttp.ts create mode 100644 external-oauth/mcp-server/src/handlers/sse.ts create mode 100644 external-oauth/mcp-server/src/index.ts create mode 100644 external-oauth/mcp-server/src/redis.ts create mode 100644 external-oauth/mcp-server/src/services/mcp.ts create mode 100644 external-oauth/mcp-server/src/services/redisTransport.integration.test.ts create mode 100644 external-oauth/mcp-server/src/services/redisTransport.test.ts create mode 100644 external-oauth/mcp-server/src/services/redisTransport.ts create mode 100644 external-oauth/mcp-server/src/static/index.html create mode 100644 external-oauth/mcp-server/src/static/mcp.png create mode 100644 external-oauth/mcp-server/src/static/styles.css create mode 100644 external-oauth/mcp-server/src/types.ts create mode 100644 external-oauth/mcp-server/src/utils/logger.ts create mode 100644 external-oauth/mcp-server/tsconfig.json rename scripts/{test-integrated-e2e.sh => test-embedded-e2e.sh} (93%) rename scripts/{test-separate-e2e.sh => test-external-e2e.sh} (93%) delete mode 100644 src/context.test.ts delete mode 100644 src/types.ts diff --git a/.env.integrated b/.env.integrated deleted file mode 100644 index aca7862..0000000 --- a/.env.integrated +++ /dev/null @@ -1,4 +0,0 @@ -AUTH_MODE=integrated -BASE_URI=http://localhost:3232 -PORT=3232 -REDIS_URL=redis://localhost:6379 \ No newline at end of file diff --git a/.env.separate b/.env.separate deleted file mode 100644 index e0c6565..0000000 --- a/.env.separate +++ /dev/null @@ -1,6 +0,0 @@ -AUTH_MODE=separate -BASE_URI=http://localhost:3232 -PORT=3232 -REDIS_URL=redis://localhost:6379 -AUTH_SERVER_URL=http://localhost:3001 -AUTH_SERVER_PORT=3001 \ No newline at end of file diff --git a/README.md b/README.md index a57b81d..acc5f6b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# MCP Everything Server +# MCP Feature Reference Server _Note: these docs were AI generated based on a claude code transcript, and then edited manually for accuracy_ @@ -6,7 +6,7 @@ A comprehensive example implementation of a scalable Model Context Protocol (MCP ## Overview -The Everything Server is an open-source reference implementation that showcases: +The Feature Reference Server is an open-source reference implementation that showcases: - **Complete [MCP Protocol](https://modelcontextprotocol.io/specification) Support**: All MCP features including tools, resources, prompts, sampling, completions, and logging - **Multiple [Transport Methods](https://modelcontextprotocol.io/docs/concepts/transports)**: Streamable HTTP (SHTTP) and Server-Sent Events (SSE) - **Dual Authentication Modes**: Integrated and separate authorization server support @@ -14,6 +14,23 @@ The Everything Server is an open-source reference implementation that showcases: This server serves as both primarily as a learning resource, and an example implementation of a scalable remote MCP server. +## Repository Structure + +This repository contains **two complete, standalone implementations** to help you learn different MCP deployment patterns: + +``` +embedded-oauth/ # Complete example: MCP server with integrated OAuth + └── src/ # All code in one place, ready to run + +external-oauth/ + ├── auth-server/ # Standalone OAuth authorization server + │ └── src/ + └── mcp-server/ # MCP server that delegates to external auth + └── src/ +``` + +Both modes are complete, independently buildable implementations with full test coverage. For more information on the difference between embedded and external OAuth, see [Authentication Modes](#authentication-modes). + ## Quick Start Get the server running in 5 minutes: @@ -27,12 +44,13 @@ orbctl start # macOS: Start OrbStack daemon # 2. Setup git clone https://github.com/modelcontextprotocol/example-remote-server.git cd example-remote-server -npm install -cp .env.integrated .env # Configure for integrated mode (see Authentication Modes for details) +npm install # Install dependencies for all workspaces # 3. Start services docker compose up -d # Start Redis -npm run dev # Start server +npm run dev:embedded # Start embedded-oauth server +# OR +npm run dev:external # Start both auth-server and mcp-server # 4. Test with Inspector npx -y @modelcontextprotocol/inspector @@ -120,16 +138,22 @@ sudo apt-get install redis-server && sudo systemctl start redis ```bash git clone https://github.com/modelcontextprotocol/example-remote-server.git cd example-remote-server -npm install +npm install # Installs dependencies for all workspaces (embedded-oauth, auth-server, mcp-server) ``` -### Step 3: Configure Environment +### Step 3: Choose Your Mode +The repository includes two complete, standalone examples: + +**Option A: Embedded OAuth (Simpler)** ```bash -# Use integrated mode (default, simpler setup) -cp .env.integrated .env +cd embedded-oauth +# Everything is configured by default in .env +``` -# OR use separate mode (for testing external auth) -cp .env.separate .env +**Option B: External OAuth (Production-like)** +```bash +cd external-oauth/auth-server # Auth server configuration in .env +cd external-oauth/mcp-server # MCP server configuration in .env ``` ### Step 4: Start Redis @@ -141,133 +165,103 @@ docker compose up -d docker compose ps ``` -### Step 5: Verify Installation +### Step 5: Start and Verify + +**Embedded OAuth:** ```bash -# Run the development server -npm run dev +npm run dev:embedded +# Server starts on http://localhost:3232 +``` -# Server should start on http://localhost:3232 +**External OAuth:** +```bash +npm run dev:external +# Auth server starts on http://localhost:3001 +# MCP server starts on http://localhost:3232 ``` ## Configuration -Environment variables (`.env` file): +Each mode has its own `.env` file pre-configured: + +**Embedded OAuth** (`embedded-oauth/.env`): ```bash -# Server Configuration +BASE_URI=http://localhost:3232 # MCP server URL PORT=3232 # MCP server port -BASE_URI=http://localhost:3232 # Base URI for OAuth redirects - -# Redis Configuration -REDIS_URL=redis://localhost:6379 # Redis connection URL - -# Authentication Mode (integrated | separate) -AUTH_MODE=integrated # Default: integrated mode - -# Separate Mode Configuration (only used when AUTH_MODE=separate) -AUTH_SERVER_URL=http://localhost:3001 # External auth server URL -AUTH_SERVER_PORT=3001 # Auth server port (for standalone server) +REDIS_URL=redis://localhost:6379 # Redis connection ``` -**Pre-configured environment files:** -- `.env.integrated` - Configuration for integrated mode -- `.env.separate` - Configuration for separate mode - +**External OAuth Auth Server** (`external-oauth/auth-server/.env`): ```bash -# Use integrated mode -cp .env.integrated .env +AUTH_SERVER_URL=http://localhost:3001 # Auth server URL +AUTH_SERVER_PORT=3001 # Auth server port +BASE_URI=http://localhost:3232 # MCP server URL (for redirects) +REDIS_URL=redis://localhost:6379 # Redis connection +``` -# Use separate mode -cp .env.separate .env +**External OAuth MCP Server** (`external-oauth/mcp-server/.env`): +```bash +BASE_URI=http://localhost:3232 # MCP server URL +PORT=3232 # MCP server port +AUTH_SERVER_URL=http://localhost:3001 # External auth server URL +REDIS_URL=redis://localhost:6379 # Redis connection ``` ## Authentication Modes -The Everything Server supports two authentication modes to demonstrate different MCP deployment patterns: +Per the [MCP Authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), the authorization server may be hosted with the resource server or as a separate entity. This repository demonstrates both patterns. -### Integrated Mode (Default) -The MCP server acts as its own OAuth 2.0 authorization server. This configuration is simpler to deploy but requires the MCP server to host its own authorization logic. The implementation provided here is for demonstration purposes only. +### Embedded OAuth +**Location**: `embedded-oauth/` -```bash -npm run dev:integrated -``` - -### Separate Mode -The MCP server delegates authentication to a standalone authorization server. This demonstrates how MCP servers can integrate with existing OAuth infrastructure. See [auth-server/README.md](auth-server/README.md) for more details about the standalone auth server. +OAuth authorization server hosted with the MCP resource server. Demonstrates self-hosted OAuth with upstream identity provider delegation (enterprise pattern where you control OAuth but delegate user authentication to corporate SSO/LDAP). ```bash -# Start both the auth server and MCP server -npm run dev:with-separate-auth - -# Or run them separately: -# Terminal 1: Start the authorization server -npm run dev:auth-server - -# Terminal 2: Start the MCP server in separate mode -npm run dev:separate +npm run dev:embedded # Single server on :3232 ``` -In production, the separate authorization server would typically be replaced with: -- Corporate SSO (Auth0, Okta) -- Cloud providers (AWS Cognito, Azure AD) -- Social providers (Google, GitHub) +See [embedded-oauth/README.md](embedded-oauth/README.md) for details. -## Development +### External OAuth +**Location**: `external-oauth/` -### Quick Start -If you've completed installation, you're ready to develop: +MCP resource server using a completely separate OAuth authorization server. Demonstrates the OAuth-as-a-Service pattern (modern SaaS pattern using Auth0, Okta, or similar providers). ```bash -# Integrated mode (MCP server handles auth) -npm run dev:integrated - -# Separate mode (external auth server) -npm run dev:with-separate-auth +npm run dev:external # Auth server on :3001, MCP server on :3232 ``` -### Development Commands -```bash -# Start development server with hot reload -npm run dev - -# Start in integrated mode (MCP server as OAuth server) -npm run dev:integrated - -# Start in separate mode (external auth server) -npm run dev:separate +See [external-oauth/README.md](external-oauth/README.md) for architecture and implementation details. -# Start standalone authorization server -npm run dev:auth-server - -# Start both auth server and MCP server in separate mode -npm run dev:with-separate-auth +## Development -# Start development server with debugging -npm run dev:break +```bash +# Start servers +npm run dev:embedded # Embedded OAuth +npm run dev:external # External OAuth (both servers) +npm run dev:auth-server # External OAuth - auth server only +npm run dev:mcp-server # External OAuth - MCP server only ``` -#### Build & Production +### Build & Production ```bash -# Build TypeScript to JavaScript (builds both servers) -npm run build - -# Run production server -npm start +npm run build # Build all workspaces +npm run build:embedded # Build embedded OAuth only +npm run build:external # Build external OAuth only -# Run production auth server -npm run start:auth-server +npm run start:embedded # Run embedded OAuth +npm run start:external # Run external OAuth (both servers) ``` -#### Testing & Quality +### Testing & Quality ```bash -# Run linting -npm run lint - -# Run unit tests -npm test +npm test # All unit tests (189 total) +npm run lint # Lint all code +npm run typecheck # Typecheck all code -# Run end-to-end tests (automated server management) -npm run test:e2e:integrated # Test integrated mode OAuth + features -npm run test:e2e:separate # Test separate mode OAuth + features +npm run test:e2e # Run all e2e tests +npm run test:e2e:embedded # E2E test for embedded OAuth +npm run test:e2e:external # E2E test for external OAuth ``` ### Testing with MCP Inspector @@ -277,12 +271,11 @@ The MCP Inspector is a web-based tool for testing MCP servers. #### Prerequisites 1. Ensure Docker/OrbStack is running 2. Ensure Redis is running: `docker compose ps` -3. Ensure environment is configured: Check `.env` file exists -#### Test Integrated Mode +#### Test Embedded OAuth ```bash # 1. Start the server (Redis must already be running) -npm run dev:integrated +npm run dev:embedded # 2. Launch MCP Inspector in a new terminal npx -y @modelcontextprotocol/inspector @@ -291,10 +284,10 @@ npx -y @modelcontextprotocol/inspector # 4. Navigate to Auth tab and complete OAuth flow ``` -#### Test Separate Mode +#### Test External OAuth ```bash # 1. Start both servers (Redis must already be running) -npm run dev:with-separate-auth +npm run dev:external # 2. Launch MCP Inspector in a new terminal npx -y @modelcontextprotocol/inspector @@ -303,54 +296,22 @@ npx -y @modelcontextprotocol/inspector # 4. Auth flow will redirect to :3001 for authentication ``` -### Running Tests -```bash -# Run all tests -npm test - -# Run specific test suites -npm test -- --testNamePattern="User Session Isolation" -npm test -- --testNamePattern="session ownership" - -# Run with coverage -npm test -- --coverage -``` - ### Test Categories -- **Unit Tests**: Individual component testing +- **Unit Tests**: Component testing (189 tests across all workspaces) - **Integration Tests**: Transport and Redis integration -- **Auth Tests**: OAuth flow and session ownership -- **Multi-user Tests**: User isolation and access control - -### Automated End-to-End Testing +- **E2E Tests**: Complete OAuth + MCP feature verification -The `scripts/` directory contains automated test scripts that verify the complete OAuth flow and all MCP features: +### End-to-End Testing -#### Scripts -- **`test-integrated-e2e.sh`** - Tests integrated mode (MCP server as OAuth server) -- **`test-separate-e2e.sh`** - Tests separate mode (external auth server) +E2E scripts in `scripts/` verify complete OAuth flows and MCP features: -#### What the scripts test: -- Complete OAuth 2.0 + PKCE flow from client registration to token usage -- All MCP features: tools (7), resources (100 with pagination), prompts (3) -- Session management and proper error handling -- README claim verification - -#### Usage ```bash -# Recommended: Automated testing (handles server lifecycle) -npm run test:e2e:integrated # Tests integrated mode -npm run test:e2e:separate # Tests separate mode - -# Advanced: Manual script execution (requires manual server setup) -./scripts/test-integrated-e2e.sh -./scripts/test-separate-e2e.sh +npm run test:e2e:embedded # Test embedded OAuth +npm run test:e2e:external # Test external OAuth +npm run test:e2e # Run both ``` -The npm scripts automatically start required servers, run tests, and clean up. Manual scripts require you to start Redis and servers first. - -### Interactive Testing -Use the MCP Inspector for interactive testing and debugging of OAuth flows, tool execution, and resource access. +Scripts automatically manage server lifecycle, test OAuth 2.0 + PKCE flow, and verify all MCP features. ## Troubleshooting @@ -367,8 +328,8 @@ Use the MCP Inspector for interactive testing and debugging of OAuth flows, tool - Ensure Docker/OrbStack is started first **"Missing .env file"** -- Run `cp .env.integrated .env` for default setup -- Or `cp .env.separate .env` for separate auth mode +- Each mode directory has a pre-configured `.env` file +- embedded-oauth/.env, external-oauth/auth-server/.env, external-oauth/mcp-server/.env **"Port already in use"** - Check for existing processes: `lsof -i :3232` or `lsof -i :3001` @@ -380,15 +341,15 @@ Use the MCP Inspector for interactive testing and debugging of OAuth flows, tool - Delete node_modules and package-lock.json, then retry **"Authentication flow fails"** -- Check the server logs for error messages -- Ensure Redis is running and accessible -- Verify .env configuration matches your setup mode +- Check server logs for error messages +- Ensure Redis is running: `docker compose ps` +- Verify .env configuration in the mode directory you're running ## Architecture & Technical Details ### Authentication Architecture -#### Integrated Mode +#### Embedded OAuth ```mermaid graph TD Client["MCP Client
(Inspector)"] @@ -397,7 +358,7 @@ graph TD Client <-->|"OAuth flow & MCP resources"| MCP ``` -#### Separate Mode +#### External OAuth ```mermaid graph TD Client["MCP Client
(Inspector)"] @@ -410,97 +371,63 @@ graph TD MCP <-->|"Token validation
(introspect)"| Auth ``` -### OAuth 2.0 + PKCE Flow Analysis - -The server implements a complete OAuth 2.0 authorization code flow with PKCE. Here's how each step maps to data storage and expiry: +### OAuth Flow -**1. Client Registration** (app setup - happens once) -``` -App → Auth Server: "I want to use OAuth, here's my info" -Auth Server → App: "OK, your client_id is XYZ, client_secret is ABC" -``` -- **Storage**: Client credentials for future OAuth flows -- **Expiry**: 30 days (long-lived app credentials) - -**2. Authorization Request** (starts each OAuth flow) -``` -User → App: "I want to connect to MCP server" -App → Auth Server: "User wants access, here's my PKCE challenge" -Auth Server: Stores pending authorization, shows auth page -``` -- **Storage**: `PENDING_AUTHORIZATION` - temporary state during flow -- **Expiry**: 10 minutes (short-lived temporary state) - -**3. Authorization Code Exchange** (completes OAuth flow) -``` -User → Auth Server: "I approve this app" -Auth Server → App: "Here's your authorization code" -App → Auth Server: "Exchange code + PKCE verifier for tokens" -Auth Server → App: "Here are your access/refresh tokens" -``` -- **Storage**: `TOKEN_EXCHANGE` - prevents replay attacks -- **Expiry**: 10 minutes (single-use, consumed immediately) - -**4. Token Storage** (long-term user session) -``` -Auth Server: Issues access_token + refresh_token -Server: Stores user installation with tokens -``` -- **Storage**: `UPSTREAM_INSTALLATION` - the actual user session -- **Expiry**: 7 days (balances security vs usability) - -**5. Token Refresh** (extends user session) -``` -App → Auth Server: "My access token expired, here's my refresh token" -Auth Server → App: "Here's a new access token" -``` -- **Storage**: `REFRESH_TOKEN` - mapping for token rotation -- **Expiry**: 7 days (matches installation lifetime) - -#### Data Lifecycle Hierarchy - -**Timeline (shortest to longest expiry):** -1. **OAuth flow state** (10 minutes) - very temporary -2. **User sessions** (7 days) - medium-term -3. **Client credentials** (30 days) - long-term - -This creates a logical hierarchy where each layer outlives the layers it supports. +Both modes implement OAuth 2.1 with PKCE. For detailed flow analysis including data storage, TTLs, and mode-specific differences, see [docs/oauth-flow.md](docs/oauth-flow.md). ### Project Structure ``` -├── src/ # MCP server code -│ ├── index.ts # Express app setup and routes -│ ├── config.ts # Configuration management -│ ├── redis.ts # Redis client setup -│ ├── auth/ -│ │ ├── provider.ts # OAuth auth provider implementation -│ │ └── external-verifier.ts # External token verification -│ ├── handlers/ -│ │ ├── shttp.ts # Streamable HTTP handler -│ │ ├── sse.ts # SSE transport handler -│ │ ├── fakeauth.ts # Fake upstream auth handler -│ │ └── common.ts # Shared middleware -│ ├── services/ -│ │ ├── mcp.ts # MCP server implementation -│ │ ├── auth.ts # Auth service wrappers -│ │ └── redisTransport.ts # Redis-backed transport -│ └── utils/ -│ └── logger.ts # Structured logging -├── auth-server/ # Standalone authorization server -│ ├── index.ts # Auth server main entry point -│ ├── README.md # Auth server documentation -│ └── tsconfig.json # TypeScript configuration -├── shared/ # Shared between both servers -│ ├── auth-core.ts # Core auth logic -│ ├── redis-auth.ts # Redis auth operations -│ └── types.ts # Shared type definitions +├── embedded-oauth/ # Complete integrated auth example +│ ├── src/ +│ │ ├── index.ts # Main entry point (MCP + OAuth) +│ │ ├── auth/ +│ │ │ ├── provider.ts # FeatureReferenceAuthProvider +│ │ │ └── auth-core.ts # Token generation, PKCE +│ │ ├── services/ +│ │ │ ├── mcp.ts # MCP server implementation +│ │ │ ├── auth.ts # Auth service wrappers +│ │ │ ├── redis-auth.ts # Redis auth operations +│ │ │ └── redisTransport.ts # Redis-backed transport +│ │ ├── handlers/ # SHTTP, SSE, mock-upstream-idp handlers +│ │ ├── utils/logger.ts # Structured logging +│ │ └── ... +│ ├── package.json +│ ├── tsconfig.json +│ └── .env +│ +├── external-oauth/ +│ ├── auth-server/ # Standalone OAuth server +│ │ ├── src/ +│ │ │ ├── index.ts # Auth server entry point +│ │ │ ├── auth/ +│ │ │ │ ├── provider.ts # FeatureReferenceAuthProvider +│ │ │ │ └── auth-core.ts +│ │ │ ├── services/ # Auth services, redis-auth +│ │ │ ├── handlers/ # Mock upstream auth +│ │ │ └── ... +│ │ ├── package.json +│ │ └── .env +│ │ +│ └── mcp-server/ # MCP server with external auth +│ ├── src/ +│ │ ├── index.ts # MCP server entry point +│ │ ├── auth/ +│ │ │ ├── external-verifier.ts # Token introspection +│ │ │ └── auth-core.ts +│ │ ├── services/ # MCP server, redisTransport +│ │ ├── handlers/ # SHTTP, SSE handlers +│ │ └── ... +│ ├── package.json +│ └── .env +│ ├── scripts/ # End-to-end testing scripts │ ├── test-integrated-e2e.sh # OAuth + feature verification (integrated) │ └── test-separate-e2e.sh # OAuth + feature verification (separate) ├── docs/ │ ├── streamable-http-design.md # SHTTP implementation details │ └── user-id-system.md # Authentication flow documentation -└── dist/ # Compiled JavaScript output +├── package.json # Root workspace configuration +└── docker-compose.yml # Redis service ``` ### Scalability Architecture @@ -560,23 +487,7 @@ Backwards-compatible [transport](https://modelcontextprotocol.io/specification/2 ## API Reference -### MCP Endpoints -- `GET/POST/DELETE /mcp` - Streamable HTTP transport endpoint - - `POST`: Initialize sessions or send messages - - `GET`: Establish SSE streams - - `DELETE`: Terminate sessions -- `GET /sse` - Legacy SSE transport endpoint -- `POST /message` - Legacy message endpoint for SSE transport - -### Authentication Endpoints (Integrated Mode Only) -- `GET /fakeupstreamauth/authorize` - Fake OAuth authorization page -- `GET /fakeupstreamauth/callback` - OAuth redirect handler -- OAuth 2.0 endpoints provided by MCP SDK auth router - -### Headers -- `Mcp-Session-Id`: Session identifier for Streamable HTTP -- `Authorization: Bearer `: OAuth access token -- Standard MCP headers as per protocol specification +For a complete listing of all endpoints provided by each server configuration, including OAuth authorization endpoints, MCP resource endpoints, and demo identity provider endpoints, see [docs/endpoints.md](docs/endpoints.md). ## Security @@ -660,9 +571,11 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file Built by the Model Context Protocol team as a reference implementation for the MCP ecosystem. -## Links +## References +### MCP Documentation - [Model Context Protocol Documentation](https://modelcontextprotocol.io) +- [MCP Authorization Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) - [MCP Specification](https://modelcontextprotocol.io/specification) - [MCP Concepts](https://modelcontextprotocol.io/docs/concepts) - [Tools](https://modelcontextprotocol.io/docs/concepts/tools) @@ -671,4 +584,16 @@ Built by the Model Context Protocol team as a reference implementation for the M - [Sampling](https://modelcontextprotocol.io/docs/concepts/sampling) - [Transports](https://modelcontextprotocol.io/docs/concepts/transports) - [TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) -- [Example Servers](https://github.com/modelcontextprotocol/servers) \ No newline at end of file + +### OAuth 2.0 / 2.1 Resources +- [RFC 6749: OAuth 2.0 Framework](https://datatracker.ietf.org/doc/html/rfc6749) +- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) (used in separate mode) +- [OAuth 2.1 Draft](https://oauth.net/2.1/) - Modern security best practices +- [OAuth.net: End User Authentication](https://oauth.net/articles/authentication/) +- [The Resource Server - OAuth 2.0 Simplified](https://www.oauth.com/oauth2-servers/the-resource-server/) + +### Architecture Patterns +This implementation demonstrates patterns discussed in: +- [Stack Overflow: Separating Resource and Authorization Servers](https://stackoverflow.com/questions/16228193/oauth-2-separating-resource-server-and-authorization-server) +- [OAuth 2.0 Overview - Curity](https://curity.io/resources/learn/oauth-overview/) +- Microsoft identity platform documentation on [OAuth 2.0 flows](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow) \ No newline at end of file diff --git a/auth-server/README.md b/auth-server/README.md deleted file mode 100644 index 0323731..0000000 --- a/auth-server/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# MCP Standalone Authorization Server - -This is a demonstration OAuth 2.0 authorization server for MCP's separate authentication mode. - -## Purpose - -This server demonstrates how MCP servers can delegate authentication to a separate authorization server. See the main [README Authentication Modes](../README.md#authentication-modes) section for a complete overview of integrated vs separate modes. - -In production environments, you would typically use established OAuth providers like: -- Auth0, Okta -- Google OAuth, GitHub OAuth -- Microsoft Azure AD, AWS Cognito - -## Architecture - -For detailed architecture information and OAuth flow analysis, see: -- [Authentication Modes](../README.md#authentication-modes) - Overview and comparison -- [OAuth 2.0 + PKCE Flow Analysis](../README.md#oauth-20--pkce-flow-analysis) - Step-by-step flow breakdown -- [Authentication Architecture](../README.md#authentication-architecture) - Visual diagrams - -This auth server specifically implements the "Auth Server" component in the separate mode architecture diagram. - -## Endpoints - -- `/.well-known/oauth-authorization-server` - OAuth 2.0 server metadata -- `/authorize` - Authorization endpoint -- `/token` - Token endpoint -- `/register` - Dynamic client registration -- `/introspect` - Token introspection (for MCP server validation) -- `/fakeupstreamauth/authorize` - Fake upstream auth page (demo only) -- `/fakeupstreamauth/callback` - Fake upstream callback (demo only) -- `/health` - Health check endpoint - -## Development - -This server shares Redis with the MCP server for development convenience. -In production, these would typically be separate. - -## Running the Auth Server - -### Standalone -```bash -# From the repository root -npm run dev:auth-server -``` - -### With MCP Server (Separate Mode) -```bash -# Start both servers together -npm run dev:with-separate-auth -``` - -## Testing - -### Health Check -```bash -curl http://localhost:3001/health -``` - -### OAuth Metadata -```bash -curl http://localhost:3001/.well-known/oauth-authorization-server -``` - -### With MCP Inspector -See the main [Testing with MCP Inspector](../README.md#testing-with-mcp-inspector) section for complete testing instructions for both modes. - -**Quick test for this auth server:** -1. Start this auth server: `npm run dev:auth-server` -2. Start MCP server in separate mode: `AUTH_MODE=separate npm run dev` -3. Follow the separate mode testing steps in the main README - -## Configuration - -The auth server uses the same configuration system as the main server. See [Configuration](../README.md#configuration) in the main README for complete environment variable documentation. - -**Auth server specific variables:** -- `AUTH_SERVER_PORT` - Port to run on (default: 3001) -- `AUTH_SERVER_URL` - Base URL (default: http://localhost:3001) -- `REDIS_URL` - Redis connection (shared with MCP server) - -## Production Considerations - -**This server is for demonstration only.** In production, use established OAuth providers. - -For comprehensive security and deployment guidance, see: -- [Security](../README.md#security) - Security measures and best practices -- [Configuration](../README.md#configuration) - Environment setup -- [Monitoring & Debugging](../README.md#monitoring--debugging) - Operational guidance - -**Production replacement options:** -- Corporate SSO (Auth0, Okta) -- Cloud providers (AWS Cognito, Azure AD) -- Social providers (Google OAuth, GitHub OAuth) \ No newline at end of file diff --git a/docs/endpoints.md b/docs/endpoints.md new file mode 100644 index 0000000..00a6de1 --- /dev/null +++ b/docs/endpoints.md @@ -0,0 +1,154 @@ +# Endpoint Reference + +Complete listing of all endpoints provided by each server configuration. + +## Embedded OAuth Server (Port 3232) + +Single server hosting OAuth authorization, mock identity provider, and MCP resources. + +### OAuth Authorization Endpoints +Provided by `mcpAuthRouter` from MCP SDK: + +- `GET /.well-known/oauth-authorization-server` - OAuth metadata discovery +- `POST /register` - Dynamic client registration +- `GET /authorize` - Authorization request (starts OAuth flow) +- `POST /token` - Token exchange (authorization code → tokens) and token refresh +- `POST /revoke` - Token revocation + +### Mock Upstream Identity Provider Endpoints +Local simulation of upstream IDP (would be external in production): + +- `GET /mock-upstream-idp/authorize` - Mock user authentication page +- `GET /mock-upstream-idp/callback` - IDP callback handler (returns userId) + +**Note**: In production, the OAuth server would redirect to external URLs like `https://accounts.google.com` or `https://login.okta.com` instead of these local endpoints. + +### MCP Resource Endpoints + +#### Streamable HTTP Transport (Recommended) +- `GET /mcp` - Establish SSE stream for session +- `POST /mcp` - Initialize session or send messages +- `DELETE /mcp` - Terminate session + +#### SSE Transport (Legacy) +- `GET /sse` - Establish SSE connection +- `POST /message` - Send messages to session + +All MCP endpoints require `Authorization: Bearer ` header. + +### Static Assets +- `GET /` - Splash page (HTML) +- `GET /mcp-logo.png` - MCP logo +- `GET /styles.css` - Stylesheet + +--- + +## External OAuth - Auth Server (Port 3001) + +Standalone OAuth authorization server (represents Auth0, Okta, etc. in production). + +### OAuth Authorization Endpoints +Provided by `mcpAuthRouter` from MCP SDK: + +- `GET /.well-known/oauth-authorization-server` - OAuth metadata discovery +- `POST /register` - Dynamic client registration +- `GET /authorize` - Authorization request +- `POST /token` - Token exchange and refresh +- `POST /revoke` - Token revocation + +### Token Introspection +Custom implementation for resource server token validation: + +- `POST /introspect` - Token introspection (RFC 7662) + - Called by MCP server to validate tokens + - Returns token status, scopes, expiry, user info + - Protected endpoint (not public) + +### Mock Upstream Identity Provider Endpoints +Local simulation of upstream IDP: + +- `GET /mock-upstream-idp/authorize` - Mock user authentication page +- `GET /mock-upstream-idp/callback` - IDP callback handler + +**Note**: Commercial OAuth providers (Auth0, Okta) have their own user authentication systems. These endpoints simulate that functionality. + +### Utility Endpoints +- `GET /health` - Health check (returns server status) +- `GET /mcp-logo.png` - Logo asset for auth pages + +--- + +## External OAuth - MCP Server (Port 3232) + +Pure MCP resource server with no OAuth authorization functionality. + +### OAuth Metadata (Read-Only) +Provided by `mcpAuthMetadataRouter`: + +- `GET /.well-known/oauth-authorization-server` - Returns metadata pointing to external auth server + - Tells clients to use auth server at :3001 + - Read-only - no token issuance happens here + +### MCP Resource Endpoints + +#### Streamable HTTP Transport (Recommended) +- `GET /mcp` - Establish SSE stream for session +- `POST /mcp` - Initialize session or send messages +- `DELETE /mcp` - Terminate session + +#### SSE Transport (Legacy) +- `GET /sse` - Establish SSE connection +- `POST /message` - Send messages to session + +All MCP endpoints require `Authorization: Bearer ` header. Tokens are validated by calling the auth server's `/introspect` endpoint. + +### Static Assets +- `GET /` - Splash page +- `GET /mcp-logo.png` - MCP logo +- `GET /styles.css` - Stylesheet + +--- + +## Key Differences + +| Endpoint Type | Embedded OAuth | External Auth Server | External MCP Server | +|---------------|----------------|---------------------|---------------------| +| OAuth authorization (`/authorize`, `/token`) | ✅ Full | ✅ Full | ❌ None | +| Token introspection (`/introspect`) | ❌ Not needed | ✅ Yes | ❌ Not needed | +| OAuth metadata discovery | ✅ Yes | ✅ Yes | ✅ Read-only redirect | +| Mock IDP (`/mock-upstream-idp`) | ✅ Yes | ✅ Yes | ❌ No | +| MCP resources (`/mcp`, `/sse`) | ✅ Yes | ❌ No | ✅ Yes | + +--- + +## Headers + +### Required Headers + +**For MCP endpoints**: +- `Authorization: Bearer ` - OAuth access token (required) +- `Mcp-Session-Id: ` - Session identifier (for Streamable HTTP, after initialization) + +**For OAuth endpoints**: +- `Content-Type: application/json` (for POST requests) +- `Content-Type: application/x-www-form-urlencoded` (for `/token` endpoint) + +### Response Headers + +**Streamable HTTP**: +- `Mcp-Session-Id` - Session ID (returned on initialization) +- `Content-Type: text/event-stream` - For SSE responses + +**SSE Transport**: +- `Content-Type: text/event-stream` +- `Cache-Control: no-store, max-age=0` +- `Connection: keep-alive` + +--- + +## References + +- [MCP Transport Specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports) +- [MCP Authorization Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) +- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) +- [OAuth 2.0 Endpoints](https://www.oauth.com/oauth2-servers/definitions/) diff --git a/docs/oauth-flow.md b/docs/oauth-flow.md new file mode 100644 index 0000000..8457b2e --- /dev/null +++ b/docs/oauth-flow.md @@ -0,0 +1,217 @@ +# OAuth 2.0 + PKCE Flow Analysis + +This document details the complete OAuth 2.0 authorization code flow with PKCE as implemented in this reference server, including how it differs between integrated and separate modes. + +## Flow Overview + +The server implements OAuth 2.1 with PKCE (Proof Key for Code Exchange) for secure authorization. Here's how each step maps to data storage, expiry, and mode-specific behavior: + +## 1. Client Registration + +**Purpose**: Register the application with the OAuth server (happens once during app setup). + +**Flow**: +``` +App → Auth Server: POST /register {"client_name": "...", "redirect_uris": [...]} +Auth Server → App: {"client_id": "XYZ", "client_secret": "ABC", ...} +``` + +**Storage**: +- Redis key: `auth:client:{clientId}` +- Expiry: 30 days (long-lived app credentials) + +**Mode differences**: None - identical in both modes. + +--- + +## 2. Authorization Request + +**Purpose**: User initiates connection to MCP server. + +**Flow**: +``` +App → Auth Server: GET /authorize? + client_id=XYZ + &redirect_uri=http://localhost:3000/callback + &code_challenge= + &code_challenge_method=S256 + &state= + +Auth Server: Saves pending authorization, shows authorization page +``` + +**Storage**: +- Redis key: `auth:pending:{authCode}` +- Expiry: 10 minutes (short-lived temporary state) +- Data: redirect_uri, code_challenge, code_challenge_method, client_id, state + +**Mode differences**: +- **Embedded OAuth**: OAuth server runs on MCP server (port 3232) +- **External OAuth**: OAuth server runs independently (port 3001) + +--- + +## 3. User Authentication & Authorization + +**Purpose**: Authenticate the user and obtain consent. + +**Flow** (identical in both modes): +``` +OAuth Server → User: Shows auth page with "Continue to Authentication" button +User → OAuth Server: Clicks button +OAuth Server → Upstream IDP: Redirects to /mock-upstream-idp/authorize +Upstream IDP → User: Shows user selection UI +User → Upstream IDP: Selects/creates user ID +Upstream IDP → OAuth Server: Redirects to /mock-upstream-idp/callback?userId=X +OAuth Server: Validates user, issues authorization code +OAuth Server → App: Redirects to app's redirect_uri with code +``` + +**Mode differences**: +- **Embedded OAuth**: OAuth Server runs on MCP server (port 3232) +- **External OAuth**: OAuth Server runs independently (port 3001) + +The flow itself is identical. Both modes delegate user authentication to an upstream IDP (simulated by `/mock-upstream-idp/*` endpoints, which represent corporate SSO or social login in production). + +--- + +## 4. Authorization Code Exchange + +**Purpose**: Exchange authorization code for access and refresh tokens. + +**Flow**: +``` +App → Auth Server: POST /token + grant_type=authorization_code + &client_id=XYZ + &client_secret=ABC + &code= + &redirect_uri=http://localhost:3000/callback + &code_verifier= + +Auth Server: + 1. Validates code_verifier matches code_challenge (PKCE) + 2. Validates redirect_uri matches original + 3. Validates client credentials + 4. Issues tokens + +Auth Server → App: + { + "access_token": "...", + "refresh_token": "...", + "expires_in": 604800, + "token_type": "Bearer" + } +``` + +**Storage**: +- `auth:exch:{authCode}` - Token exchange record (prevents replay attacks) +- `auth:installation:{accessToken}` - Active MCP installation +- `auth:refresh:{refreshToken}` - Refresh token mapping +- Expiry: 10 minutes for exchange record, 7 days for installation + +**Mode differences**: None in the exchange itself. The endpoint location differs: +- **Embedded OAuth**: `http://localhost:3232/token` +- **External OAuth**: `http://localhost:3001/token` + +--- + +## 5. Using Access Tokens + +**Purpose**: Access MCP resources with the token. + +**Flow**: +``` +App → MCP Server: POST /mcp + Authorization: Bearer + Mcp-Session-Id: + {MCP request} + +MCP Server: Validates token → Serves MCP resource +``` + +**Token Validation - Embedded OAuth**: +``` +MCP Server (same process as OAuth server): + - Reads auth:installation:{accessToken} from Redis + - Validates expiry, scopes + - Returns user info directly +``` + +**Token Validation - External OAuth**: +``` +MCP Server: + - Calls POST {AUTH_SERVER_URL}/introspect with token + - Auth server validates and returns token info + - MCP server validates audience matches BASE_URI + - Proceeds with request +``` + +**Mode differences**: This is the key architectural difference: +- **Embedded OAuth**: In-process token validation (fast, direct Redis access) +- **External OAuth**: Remote token validation via HTTP introspection (adds network hop, follows RFC 7662) + +--- + +## 6. Token Refresh + +**Purpose**: Obtain new access token when current one expires. + +**Flow**: +``` +App → Auth Server: POST /token + grant_type=refresh_token + &refresh_token= + &client_id=XYZ + &client_secret=ABC + +Auth Server: + 1. Validates refresh_token + 2. Issues new access_token (and optionally new refresh_token) + 3. Updates installation record + +Auth Server → App: {"access_token": "...", ...} +``` + +**Storage**: +- Reads: `auth:refresh:{refreshToken}` → gets access token +- Reads: `auth:installation:{accessToken}` → gets installation +- Writes: New `auth:installation:{newAccessToken}` +- Writes: New `auth:refresh:{newRefreshToken}` +- Expiry: 7 days + +**Mode differences**: None - identical flow, just different endpoint locations. + +--- + +## Data Lifecycle Hierarchy + +**Timeline (shortest to longest expiry):** +1. **OAuth flow state** (10 minutes) - `auth:pending`, `auth:exch` +2. **User sessions** (7 days) - `auth:installation`, `auth:refresh` +3. **Client credentials** (30 days) - `auth:client` + +This hierarchy ensures each layer outlives the layers it supports. + +--- + +## Security: PKCE (Proof Key for Code Exchange) + +PKCE prevents authorization code interception attacks: + +1. **Authorization request**: Client sends `code_challenge` (SHA256 hash of random string) +2. **Token exchange**: Client must provide original `code_verifier` +3. **Server validates**: SHA256(code_verifier) must equal stored code_challenge + +This ensures only the client that initiated the flow can exchange the code, even if the code is intercepted. + +**Mode differences**: None - PKCE works identically in both modes. + +--- + +## References + +- [RFC 6749: OAuth 2.0 Framework](https://datatracker.ietf.org/doc/html/rfc6749) +- [RFC 7636: PKCE](https://datatracker.ietf.org/doc/html/rfc7636) +- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) +- [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) diff --git a/docs/session-ownership.md b/docs/session-ownership.md new file mode 100644 index 0000000..0f0b8d8 --- /dev/null +++ b/docs/session-ownership.md @@ -0,0 +1,145 @@ +# Session Ownership System + +## Overview + +The MCP server implements multi-user session isolation to ensure users can only access their own MCP sessions. This is accomplished using user identifiers and Redis-backed ownership tracking. + +## How It Works + +Each MCP session is owned by a specific user. Session ownership is stored in Redis and validated on each request. + +### User ID Assignment + +**Production**: OAuth providers assign unique user identifiers via the `sub` (subject) claim - a standard OAuth/OpenID Connect field. + +User ID sources: +- **Provider's user database**: Users created in Auth0/Okta (example: `auth0|507f1f77bcf86cd799439011`) +- **Social/federated identities**: Provider links to Google, Facebook (example: `google-oauth2|112233445566778899`) +- **Enterprise connections**: SAML, LDAP, Active Directory (example: `samlp|ad|john.doe@company.com`) + +**This implementation**: Mock upstream IDP returns HTML with JavaScript that generates a random UUID in the browser, stores it in `localStorage.mcpUserId`, and sends it to the server as a query parameter. This simulates user authentication for testing - different browsers/tabs get different UUIDs, allowing multi-user testing. + +### Token Validation + +When the MCP server validates an access token: + +**Embedded OAuth**: Reads `McpInstallation` from Redis, extracts `installation.userId` + +**External OAuth**: Calls auth server's `/introspect` endpoint, receives `sub` field + +Both result in `AuthInfo.extra.userId` being populated. + +### Session Creation + +```typescript +const userId = req.auth?.extra?.userId; +const sessionId = randomUUID(); +await redisClient.set(`session:${sessionId}:owner`, userId); +``` + +### Session Access Validation + +```typescript +const owner = await redisClient.get(`session:${sessionId}:owner`); +if (owner !== userId) { + throw new Error('Session not owned by user'); +} +``` + +## Redis Key Structure + +### Session Ownership +``` +session:{sessionId}:owner → userId +``` + +Example: `session:550e8400-...:owner` → `auth0|507f1f77bcf86cd799439011` + +### Session Liveness + +Sessions are considered "live" when an MCP server process is actively handling them. This is tracked via Redis pub/sub subscription counts: + +``` +mcp:shttp:toserver:{sessionId} → pub/sub channel +``` + +Check if live: `PUBSUB NUMSUB mcp:shttp:toserver:{sessionId}` returns > 0 + +When an MCP server starts handling a session, it subscribes to the channel. When it shuts down (gracefully or via crash), Redis automatically removes the subscription. + +## Security + +- **Session isolation**: Users can only access sessions they own +- **Ownership persistence**: Survives across requests and server restarts +- **Token-based validation**: User ID extracted from validated OAuth token +- **Access control**: All operations (GET, POST, DELETE) validate ownership + +## Implementation + +### Core Functions (src/services/redisTransport.ts) + +```typescript +export async function setSessionOwner(sessionId: string, userId: string): Promise +export async function isSessionOwnedBy(sessionId: string, userId: string): Promise +export async function isLive(sessionId: string): Promise +export async function shutdownSession(sessionId: string): Promise +``` + +### Request Flow + +```mermaid +sequenceDiagram + participant Client + participant MCP as MCP Server + participant Redis + + Client->>MCP: POST /mcp (initialize) + MCP->>MCP: Extract userId from OAuth token + MCP->>MCP: Generate sessionId + MCP->>Redis: SET session:{sessionId}:owner = userId + MCP->>Client: Return sessionId + + Client->>MCP: POST /mcp (with sessionId) + MCP->>MCP: Extract userId from OAuth token + MCP->>Redis: GET session:{sessionId}:owner + Redis-->>MCP: userId + MCP->>MCP: Verify userId matches token + MCP->>Client: Process request +``` + + +## Configuration + +### Environment Variables +```bash +REDIS_URL=redis://localhost:6379 +BASE_URI=http://localhost:3232 +``` + +### Redis Monitoring + +```bash +# List all session owners +redis-cli KEYS "session:*:owner" + +# Check specific session ownership +redis-cli GET "session:{sessionId}:owner" + +# Check if session is live (actively being handled) +redis-cli PUBSUB NUMSUB "mcp:shttp:toserver:{sessionId}" + +# Monitor session operations +redis-cli MONITOR | grep "session:" +``` + +## Testing + +```bash +npm test -- --testNamePattern="User Session Isolation" +npm test -- --testNamePattern="session ownership" +``` + +## References + +- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) - Standard for `sub` claim +- [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) diff --git a/docs/streamable-http-design.md b/docs/streamable-http-design.md deleted file mode 100644 index 28a3a9d..0000000 --- a/docs/streamable-http-design.md +++ /dev/null @@ -1,468 +0,0 @@ -# Design Document: Transport Implementation for Example Remote Server - -## Current Implementation - -The example remote server implements both MCP transport methods to support different client needs. - -### Shared Infrastructure - -All transports share the same foundational components: - -1. **Authentication**: Uses `requireBearerAuth` middleware with mode-dependent auth providers -2. **Redis Integration**: Messages published/subscribed through Redis channels using session IDs -3. **Session Management**: Session ownership tracked via Redis for multi-user isolation -4. **MCP Server**: Common `createMcpServer()` provides tools, resources, and prompts - -### SSE Transport Architecture - -Legacy transport for backwards compatibility: - -1. **SSE Endpoint**: `/sse` - Creates SSE connection using `SSEServerTransport` -2. **Message Endpoint**: `/message` - Receives POST requests and forwards them via Redis -3. **Session ID**: Generated by `SSEServerTransport`, used for Redis channel keys -4. **Message Flow**: POST to `/message` → Redis pub/sub → SSE stream to client - -**Key Files:** -- `/src/index.ts:236` - SSE endpoint registration -- `/src/handlers/sse.ts` - SSE connection handler with Redis integration -- `/src/handlers/sse.ts:75-95` - Message POST handler - -### Streamable HTTP Transport Architecture - -Modern unified transport: - -1. **Unified Endpoint**: `/mcp` - Handles GET, POST, DELETE with `StreamableHTTPServerTransport` -2. **Stateful Sessions**: Requires initialization with session ID tracking -3. **Response Format**: Returns results via Server-Sent Events streams -4. **Session Management**: Custom session ownership integration via Redis - -**Key Files:** -- `/src/index.ts:240-242` - Streamable HTTP endpoint registration -- `/src/handlers/shttp.ts` - Complete Streamable HTTP handler -- `/src/services/redisTransport.ts` - Redis-backed transport integration - -### Streamable HTTP Transport Specification (2025-03-26) - -The new Streamable HTTP transport replaces the old HTTP+SSE approach with a single endpoint that supports: - -1. **Single Endpoint**: One URL that handles GET, POST, and DELETE methods -2. **POST Requests**: Send JSON-RPC messages, can return either JSON responses or SSE streams -3. **GET Requests**: Open SSE streams for server-to-client messages -4. **Session Management**: Optional session IDs in `Mcp-Session-Id` headers -5. **Resumability**: Optional event storage with `Last-Event-ID` support -6. **Auth Integration**: Same authentication patterns as SSE - -**Key Specification Requirements:** -- Accept header must include both `application/json` and `text/event-stream` -- Session ID management via `Mcp-Session-Id` headers -- 202 Accepted for notifications/responses only -- SSE streams or JSON responses for requests -- Security: Origin validation, localhost binding, proper auth - -### TypeScript SDK Implementation - -The SDK provides `StreamableHTTPServerTransport` with: - -1. **Two Modes**: - - **Stateful**: Session ID generator provided, maintains sessions in memory - - **Stateless**: Session ID generator undefined, no session state - -2. **Key Features**: - - Built-in session validation - - Event store support for resumability - - Automatic response correlation - - Auth info threading via `req.auth` - -3. **Integration Patterns**: - - **Stateful**: Store transports by session ID, reuse across requests - - **Stateless**: New transport per request, immediate cleanup - - **Auth**: Same bearer auth middleware as SSE - -## Implementation Plan - -### 1. New Streamable HTTP Endpoint - -Add `/mcp` endpoint that handles GET, POST, DELETE methods: - -```typescript -// In src/index.ts -app.get("/mcp", cors(corsOptions), bearerAuth, authContext, handleStreamableHTTP); -app.post("/mcp", cors(corsOptions), bearerAuth, authContext, handleStreamableHTTP); -app.delete("/mcp", cors(corsOptions), bearerAuth, authContext, handleStreamableHTTP); -``` - -### 2. Handler Implementation - -Create new handler in `/src/handlers/mcp.ts`: - -```typescript -export async function handleStreamableHTTP(req: Request, res: Response) { - // Use same Redis-based architecture as SSE transport - // but with StreamableHTTPServerTransport instead of SSEServerTransport -} -``` - -### 3. Transport Integration Strategy (Horizontally Scalable) - -**Redis-based Session Management (Required for Horizontal Scaling)** -- Store session state in Redis, not in-memory -- Any server instance can handle any request for any session -- Session lifecycle independent of SSE connection lifecycle -- Message buffering in Redis when SSE connection is down -- Session TTL of 5 minutes to prevent Redis bloat - -### 4. Redis Integration - -Maintain current Redis architecture: -- Use session ID as Redis channel key -- Same message publishing/subscribing pattern -- Same MCP server creation logic -- Transport acts as bridge to Redis like current SSE implementation - -### 5. Auth Integration - -Use identical auth setup as SSE: -- Same `bearerAuth` middleware -- Same `authContext` middleware -- Same `EverythingAuthProvider` -- Auth info flows through `req.auth` to transport - -### 6. Backwards Compatibility - -Keep existing `/sse` and `/message` endpoints: -- Maintain current SSE transport for existing clients -- Add new `/mcp` endpoint alongside -- Both transports share same Redis infrastructure -- Same auth provider serves both - -## Key Differences from Current SSE Implementation - -1. **Single Endpoint**: `/mcp` handles all HTTP methods vs separate `/sse` + `/message` -2. **Transport Class**: `StreamableHTTPServerTransport` vs `SSEServerTransport` -3. **Session Headers**: `Mcp-Session-Id` headers vs URL session ID -4. **Request Handling**: Transport handles HTTP details vs manual SSE headers -5. **Response Correlation**: Built into transport vs manual request tracking - -## Benefits of This Approach - -1. **Spec Compliance**: Follows 2025-03-26 MCP specification exactly -2. **Minimal Changes**: Reuses existing Redis infrastructure and auth -3. **Feature Parity**: Same functionality as current SSE transport -4. **Future Proof**: Can add resumability with event store later -5. **Clean Integration**: Same auth patterns and middleware stack - -## Implementation Steps - -1. **Add Dependencies**: `StreamableHTTPServerTransport` from SDK -2. **Create Redis Session Management**: Implement `SessionManager` and `MessageDelivery` classes -3. **Create Handler**: New streamable HTTP handler function with Redis integration -4. **Add Routes**: New `/mcp` endpoint with all HTTP methods -5. **Session Management**: Redis-based session storage with TTL -6. **Message Buffering**: Redis-based message buffering for disconnected clients -7. **Testing**: Verify auth, Redis integration, horizontal scaling, and MCP protocol compliance -8. **Documentation**: Update README with new endpoint usage - -### Additional Implementation Considerations - -- **Redis Connection Management**: Ensure Redis connections are properly pooled and cleaned up -- **Error Handling**: Robust error handling for Redis operations and session timeouts -- **Monitoring**: Add logging for session creation, cleanup, and message buffering metrics -- **Performance**: Consider Redis memory usage and implement appropriate limits on message buffer size -- **Security**: Ensure session IDs are cryptographically secure and validate all session operations - -## Technical Details - -### Session Management Architecture (Redis-based) - -**Redis Data Structures for Horizontal Scaling:** - -```typescript -// Redis keys for session management -const SESSION_METADATA_KEY = (sessionId: string) => `session:${sessionId}:metadata`; -const SESSION_MESSAGES_KEY = (sessionId: string) => `session:${sessionId}:messages`; -const SESSION_CONNECTION_KEY = (sessionId: string) => `session:${sessionId}:connection`; - -// Session metadata structure -interface SessionMetadata { - sessionId: string; - clientId: string; - createdAt: number; - lastActivity: number; -} - -// Session lifecycle management -class SessionManager { - private static SESSION_TTL = 5 * 60; // 5 minutes in seconds - - static async createSession(sessionId: string, clientId: string): Promise { - const metadata: SessionMetadata = { - sessionId, - clientId, - createdAt: Date.now(), - lastActivity: Date.now() - }; - - // Store session metadata with TTL - await redisClient.set( - SESSION_METADATA_KEY(sessionId), - JSON.stringify(metadata), - { EX: this.SESSION_TTL } - ); - - // Initialize empty message buffer - await redisClient.del(SESSION_MESSAGES_KEY(sessionId)); - - // Mark connection as disconnected initially - await redisClient.set(SESSION_CONNECTION_KEY(sessionId), 'disconnected', { EX: this.SESSION_TTL }); - } - - static async refreshSession(sessionId: string): Promise { - const metadata = await this.getSessionMetadata(sessionId); - if (!metadata) return false; - - // Update last activity and refresh TTL - metadata.lastActivity = Date.now(); - await redisClient.set( - SESSION_METADATA_KEY(sessionId), - JSON.stringify(metadata), - { EX: this.SESSION_TTL } - ); - - // Refresh other keys too - await redisClient.expire(SESSION_MESSAGES_KEY(sessionId), this.SESSION_TTL); - await redisClient.expire(SESSION_CONNECTION_KEY(sessionId), this.SESSION_TTL); - - return true; - } - - static async deleteSession(sessionId: string): Promise { - await redisClient.del(SESSION_METADATA_KEY(sessionId)); - await redisClient.del(SESSION_MESSAGES_KEY(sessionId)); - await redisClient.del(SESSION_CONNECTION_KEY(sessionId)); - } - - static async getSessionMetadata(sessionId: string): Promise { - const data = await redisClient.get(SESSION_METADATA_KEY(sessionId)); - return data ? JSON.parse(data) : null; - } - - // Mark SSE connection as connected/disconnected - static async setConnectionState(sessionId: string, connected: boolean): Promise { - await redisClient.set( - SESSION_CONNECTION_KEY(sessionId), - connected ? 'connected' : 'disconnected', - { EX: this.SESSION_TTL } - ); - } - - static async isConnected(sessionId: string): Promise { - const state = await redisClient.get(SESSION_CONNECTION_KEY(sessionId)); - return state === 'connected'; - } -} -``` - -### Redis Integration Pattern with Message Buffering - -The implementation extends the current Redis pattern to support message buffering: - -```typescript -// Message delivery with buffering support -class MessageDelivery { - static async deliverMessage(sessionId: string, message: JSONRPCMessage): Promise { - const isConnected = await SessionManager.isConnected(sessionId); - - if (isConnected) { - // Direct delivery via existing Redis pub/sub - const redisChannel = `mcp:${sessionId}`; - await redisClient.publish(redisChannel, JSON.stringify(message)); - } else { - // Buffer the message for later delivery - await redisClient.lpush( - SESSION_MESSAGES_KEY(sessionId), - JSON.stringify(message) - ); - // Set TTL on the messages list - await redisClient.expire(SESSION_MESSAGES_KEY(sessionId), SessionManager.SESSION_TTL); - } - } - - static async deliverBufferedMessages(sessionId: string, transport: StreamableHTTPServerTransport): Promise { - // Get all buffered messages - const bufferedMessages = await redisClient.lrange(SESSION_MESSAGES_KEY(sessionId), 0, -1); - - // Deliver buffered messages in order (reverse because lpush) - for (let i = bufferedMessages.length - 1; i >= 0; i--) { - const message = JSON.parse(bufferedMessages[i]); - await transport.send(message); - } - - // Clear the buffer after delivery - await redisClient.del(SESSION_MESSAGES_KEY(sessionId)); - } -} - -// Enhanced Redis subscription for SSE connections -const setupRedisSubscription = async (sessionId: string, transport: StreamableHTTPServerTransport) => { - const redisChannel = `mcp:${sessionId}`; - - const redisCleanup = await redisClient.createSubscription( - redisChannel, - async (message) => { - const jsonMessage = JSON.parse(message); - try { - await transport.send(jsonMessage); - } catch (error) { - console.error(`Failed to send message on transport for session ${sessionId}:`, error); - // Mark connection as disconnected so future messages get buffered - await SessionManager.setConnectionState(sessionId, false); - } - }, - async (error) => { - console.error('Redis subscription error:', error); - await SessionManager.setConnectionState(sessionId, false); - } - ); - - return redisCleanup; -}; -``` - -### Handler Implementation Flow - -The new streamable HTTP handler integrates with the Redis-based session management: - -```typescript -export async function handleStreamableHTTP(req: Request, res: Response) { - const method = req.method; - const sessionId = req.headers['mcp-session-id'] as string | undefined; - - if (method === 'POST') { - // Handle POST requests (initialization or message sending) - - if (isInitializeRequest(req.body) && !sessionId) { - // New session initialization - const newSessionId = randomUUID(); - const authInfo = req.auth; - - // Create session in Redis - await SessionManager.createSession(newSessionId, authInfo?.clientId || 'unknown'); - - // Create transport with Redis-based session management - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => newSessionId, - // Custom implementation - don't store transport in memory - }); - - const { server: mcpServer, cleanup: mcpCleanup } = createMcpServer(); - - // Set up Redis subscription for this session but don't store transport globally - // Instead, rely on Redis for all message routing - - await mcpServer.connect(transport); - await transport.handleRequest(req, res, req.body); - - } else if (sessionId) { - // Existing session - validate and handle request - const sessionValid = await SessionManager.refreshSession(sessionId); - if (!sessionValid) { - res.writeHead(404).end(JSON.stringify({ - jsonrpc: "2.0", - error: { code: -32001, message: "Session not found" }, - id: null - })); - return; - } - - // Create ephemeral transport for this request - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => sessionId, // Use existing session ID - }); - - const { server: mcpServer, cleanup: mcpCleanup } = createMcpServer(); - await mcpServer.connect(transport); - await transport.handleRequest(req, res, req.body); - - // Clean up after request completes - res.on('close', mcpCleanup); - } - - } else if (method === 'GET') { - // Handle SSE stream requests - - if (!sessionId) { - res.writeHead(400).end('Session ID required'); - return; - } - - const sessionValid = await SessionManager.refreshSession(sessionId); - if (!sessionValid) { - res.writeHead(404).end('Session not found'); - return; - } - - // Create transport for SSE stream - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => sessionId, - }); - - // Mark connection as active - await SessionManager.setConnectionState(sessionId, true); - - // Deliver any buffered messages first - await MessageDelivery.deliverBufferedMessages(sessionId, transport); - - // Set up Redis subscription for live messages - const redisCleanup = await setupRedisSubscription(sessionId, transport); - - // Handle connection cleanup - res.on('close', async () => { - await SessionManager.setConnectionState(sessionId, false); - redisCleanup(); - }); - - await transport.handleRequest(req, res); - - } else if (method === 'DELETE') { - // Handle session deletion - - if (!sessionId) { - res.writeHead(400).end('Session ID required'); - return; - } - - // Delete session from Redis - await SessionManager.deleteSession(sessionId); - res.writeHead(200).end(); - } -} -``` - -### Auth Information Flow - -Auth information flows through the middleware stack: - -```typescript -// Auth middleware adds req.auth -const authInfo: AuthInfo = req.auth; - -// Transport receives auth info -await transport.handleRequest(req, res); - -// Auth info is available in MCP server handlers -server.tool('example', 'description', schema, async (params, { authInfo }) => { - // authInfo contains token, clientId, scopes, etc. -}); -``` - -## Conclusion - -This design provides horizontally scalable streamable HTTP support by using Redis for all session state management and message buffering. Key advantages: - -1. **Horizontal Scalability**: Any server instance can handle any request for any session -2. **Resilient Connection Handling**: SSE disconnects don't end sessions; messages are buffered -3. **Automatic Cleanup**: 5-minute session TTL prevents Redis bloat -4. **Backwards Compatibility**: Existing `/sse` and `/message` endpoints remain unchanged -5. **Spec Compliance**: Follows 2025-03-26 MCP specification exactly - -The implementation is more complex than a single-instance approach but essential for production deployment in a horizontally scaled environment. The Redis-based architecture ensures sessions persist across server instances and SSE connection interruptions. \ No newline at end of file diff --git a/docs/user-id-system.md b/docs/user-id-system.md deleted file mode 100644 index 917b41c..0000000 --- a/docs/user-id-system.md +++ /dev/null @@ -1,404 +0,0 @@ -# User ID System Documentation - -## Overview - -The MCP server implements a comprehensive user identification and session ownership system that ensures secure multi-user access to MCP resources. This system integrates localStorage-based user management, OAuth authentication flows, and Redis-backed session isolation. - -## Architecture Components - -### 1. User ID Management (localStorage) -### 2. OAuth Authorization Flow -### 3. Redis Session Ownership -### 4. Session Access Validation - ---- - -## 1. User ID Management (localStorage) - -The fake upstream authentication system uses browser localStorage to manage user identities for testing and development purposes. - -### localStorage Schema - -```typescript -// Stored in browser localStorage -{ - "mcpUserId": "550e8400-e29b-41d4-a716-446655440000" // UUID v4 -} -``` - -### User ID Generation Flow - -```mermaid -sequenceDiagram - participant Browser - participant LocalStorage - participant AuthPage as Fake Auth Page - - Browser->>AuthPage: Load /fakeupstreamauth/authorize - AuthPage->>LocalStorage: Check mcpUserId - - alt User ID exists - LocalStorage-->>AuthPage: Return existing UUID - AuthPage->>Browser: Display existing ID - else User ID missing - AuthPage->>AuthPage: Generate new UUID v4 - AuthPage->>LocalStorage: Store new mcpUserId - AuthPage->>Browser: Display new ID - end - - Note over AuthPage: User can edit or regenerate ID - AuthPage->>LocalStorage: Update mcpUserId (if changed) -``` - -### User ID Operations - -| Operation | Description | Implementation | -|-----------|-------------|----------------| -| **Generate** | Create new UUID v4 | `generateUUID()` function | -| **Retrieve** | Get existing or create new | `getUserId()` function | -| **Update** | Edit existing ID | `editUserId()` function | -| **Persist** | Store in localStorage | `localStorage.setItem('mcpUserId', userId)` | - ---- - -## 2. OAuth Authorization Flow - -The OAuth flow integrates user IDs from localStorage into the MCP authorization process. - -### Complete OAuth Flow with User ID - -```mermaid -sequenceDiagram - participant Client - participant MCPServer as MCP Server - participant AuthPage as Auth Page - participant FakeAuth as Fake Upstream Auth - participant LocalStorage - participant Redis - - Client->>MCPServer: Request authorization - MCPServer->>AuthPage: Redirect to auth page - AuthPage->>Client: Show MCP authorization page - Client->>FakeAuth: Click "Continue to Authentication" - - FakeAuth->>LocalStorage: Get/Create userId - LocalStorage-->>FakeAuth: Return userId - FakeAuth->>Client: Show userId management UI - - Client->>FakeAuth: Complete authentication - FakeAuth->>MCPServer: Redirect with code + userId - MCPServer->>Redis: Store userId in McpInstallation - MCPServer->>Client: Return access token - - Note over Redis: McpInstallation.userId = userId -``` - -### OAuth Data Flow - -```mermaid -graph TD - A[Browser localStorage] -->|userId| B[Fake Auth Page] - B -->|userId in query params| C[Authorization Callback] - C -->|userId| D[McpInstallation Object] - D -->|access_token| E[Redis Storage] - E -->|AuthInfo.extra.userId| F[Session Ownership] -``` - -### Authorization Code Exchange - -The userId is embedded in the authorization flow: - -```javascript -// In fake auth page -function authorize() { - const userId = getUserId(); // From localStorage - const url = new URL(redirectUri); - url.searchParams.set('userId', userId); - url.searchParams.set('code', 'fakecode'); - window.location.href = url.toString(); -} -``` - ---- - -## 3. Redis Session Ownership - -Redis stores session ownership information using a structured key system. - -### Redis Key Structure - -#### MCP Session Keys (MCP Server) -``` -session:{sessionId}:owner → userId # Session ownership -mcp:shttp:toserver:{sessionId} → [pub/sub channel] # Client→Server messages (also indicates liveness) -mcp:shttp:toclient:{sessionId}:{requestId} → [pub/sub channel] # Server→Client responses -mcp:control:{sessionId} → [pub/sub channel] # Control messages -``` - -#### Auth Keys (Auth Server) -``` -auth:client:{clientId} → client registration # OAuth client registrations -auth:pending:{authCode} → pending authorization # Pending auth (10 min TTL) -auth:installation:{accessToken} → MCP installation # Active sessions (7 days TTL) -auth:exch:{authCode} → token exchange # Token exchange (10 min TTL) -auth:refresh:{refreshToken} → access token # Refresh tokens (7 days TTL) -``` - -Note: The `auth:` prefix ensures complete isolation from MCP session keys, allowing both integrated and separate modes to work consistently. - -### Redis Operations - -| Operation | Key Pattern | Value | Purpose | -|-----------|-------------|--------|---------| -| **Set Owner** | `session:{sessionId}:owner` | `userId` | Store session owner | -| **Get Owner** | `session:{sessionId}:owner` | `userId` | Retrieve session owner | -| **Check Live** | `mcp:shttp:toserver:{sessionId}` | `numsub > 0` | Check if session active via pub/sub subscribers | - -### Session Liveness Mechanism - -Session liveness is determined by **pub/sub subscription count** rather than explicit keys: - -```mermaid -graph TD - A[MCP Server Starts] --> B[Subscribe to mcp:shttp:toserver:sessionId] - B --> C[numsub = 1 → Session is LIVE] - C --> D[Session Processing] - D --> E[MCP Server Shutdown] - E --> F[Unsubscribe from channel] - F --> G[numsub = 0 → Session is DEAD] - - H[isLive() function] --> I[Check numsub count] - I --> J{numsub > 0?} - J -->|Yes| K[Session is Live] - J -->|No| L[Session is Dead] -``` - -**Why this works:** -- When an MCP server starts, it subscribes to `mcp:shttp:toserver:{sessionId}` -- When it shuts down (gracefully or crashes), Redis automatically removes the subscription -- `numsub` reflects the actual state without requiring explicit cleanup - -### Session Ownership Functions - -```typescript -// Core ownership functions -export async function setSessionOwner(sessionId: string, userId: string): Promise -export async function getSessionOwner(sessionId: string): Promise -export async function validateSessionOwnership(sessionId: string, userId: string): Promise -export async function isSessionOwnedBy(sessionId: string, userId: string): Promise -export async function isLive(sessionId: string): Promise // Uses numsub count -``` - ---- - -## 4. Session Access Validation - -Session access is validated at multiple points in the request lifecycle. - -### Session Validation Flow - -```mermaid -sequenceDiagram - participant Client - participant Handler as shttp Handler - participant Auth as Auth Middleware - participant Redis - - Client->>Handler: MCP Request with session-id - Handler->>Auth: Extract userId from token - Auth-->>Handler: Return userId - - alt New Session (Initialize) - Handler->>Handler: Generate new sessionId - Handler->>Redis: setSessionOwner(sessionId, userId) - Handler->>Handler: Start MCP server (subscribes to channel) - Note over Handler: Session becomes "live" via pub/sub subscription - Handler->>Client: Return with new session - else Existing Session - Handler->>Redis: isSessionOwnedBy(sessionId, userId) - Redis-->>Handler: Return ownership status - - alt Session Owned by User - Handler->>Client: Process request - else Session Not Owned - Handler->>Client: 400 Bad Request - end - end -``` - -### DELETE Request Validation - -```mermaid -sequenceDiagram - participant Client - participant Handler as shttp Handler - participant Redis - - Client->>Handler: DELETE /mcp (session-id: xyz) - Handler->>Handler: Extract userId from auth - Handler->>Redis: isSessionOwnedBy(sessionId, userId) - - alt Session Owned by User - Redis-->>Handler: true - Handler->>Redis: shutdownSession(sessionId) - Handler->>Client: 200 OK (Session terminated) - else Session Not Owned - Redis-->>Handler: false - Handler->>Client: 404 Not Found (Session not found or access denied) - end -``` - -### Request Authorization Matrix - -| Request Type | Session ID | User ID | Authorization Check | -|-------------|-----------|---------|-------------------| -| **Initialize** | None | Required | Create new session | -| **Existing Session** | Required | Required | `isSessionOwnedBy()` | -| **DELETE Session** | Required | Required | `isSessionOwnedBy()` | - ---- - -## 5. Security Model - -### Multi-User Isolation - -```mermaid -graph TB - subgraph "User A" - A1[localStorage: userA-id] - A2[Session: session-A] - A3[Redis: session:A:owner → userA] - end - - subgraph "User B" - B1[localStorage: userB-id] - B2[Session: session-B] - B3[Redis: session:B:owner → userB] - end - - subgraph "Redis Isolation" - R1[session:A:owner → userA-id] - R2[session:B:owner → userB-id] - R3[Ownership Validation] - end - - A3 --> R1 - B3 --> R2 - R1 --> R3 - R2 --> R3 -``` - -### Security Guarantees - -1. **Session Isolation**: Users can only access sessions they own -2. **Identity Verification**: User ID is validated from authenticated token -3. **Ownership Persistence**: Session ownership is stored in Redis -4. **Access Control**: All session operations validate ownership -5. **Secure Cleanup**: DELETE operations verify ownership before termination - -### Attack Prevention - -| Attack Vector | Prevention | Implementation | -|---------------|------------|----------------| -| **Session Hijacking** | Ownership validation | `isSessionOwnedBy()` check | -| **Cross-User Access** | User ID verification | Extract userId from AuthInfo | -| **Session Spoofing** | Token validation | Bearer token middleware | -| **Unauthorized DELETE** | Ownership check | Validate before shutdown | - ---- - -## 6. Implementation Details - -### Error Handling - -```typescript -// Session access errors -if (!userId) { - return 401; // Unauthorized: User ID required -} - -if (!await isSessionOwnedBy(sessionId, userId)) { - return 400; // Bad Request: Session access denied -} -``` - -### Testing Strategy - -The system includes comprehensive tests for: - -- **User session isolation**: Users cannot access other users' sessions -- **DELETE request validation**: Only owners can delete sessions -- **Redis cleanup**: Proper cleanup of ownership data -- **Auth flow integration**: User ID propagation through OAuth - -### Performance Considerations - -1. **Redis Efficiency**: O(1) lookups for session ownership -2. **Session Reuse**: Existing sessions are reused when ownership matches -3. **Cleanup**: Automatic cleanup prevents resource leaks -4. **Caching**: Session ownership is cached in Redis - ---- - -## 7. Configuration - -### Environment Variables - -```bash -# Redis configuration for session storage -REDIS_HOST=localhost -REDIS_PORT=6379 -REDIS_PASSWORD=optional - -# Base URI for OAuth redirects -BASE_URI=http://localhost:3000 -``` - -### Development Testing - -```bash -# Run multi-user tests -npm test -- --testNamePattern="User Session Isolation" - -# Test session ownership -npm test -- --testNamePattern="session ownership" - -# Full integration test -npm test -``` - ---- - -## 8. Monitoring and Debugging - -### Redis Key Monitoring - -```bash -# Monitor session ownership keys -redis-cli KEYS "session:*:owner" - -# Watch session ownership operations -redis-cli MONITOR | grep "session:" - -# Check active (live) sessions via pub/sub -redis-cli PUBSUB CHANNELS "mcp:shttp:toserver:*" -redis-cli PUBSUB NUMSUB "mcp:shttp:toserver:*" -``` - -### Debugging Commands - -```bash -# Check session ownership -redis-cli GET "session:550e8400-e29b-41d4-a716-446655440000:owner" - -# List all session owners -redis-cli KEYS "session:*:owner" - -# Check if specific session is live -redis-cli PUBSUB NUMSUB "mcp:shttp:toserver:550e8400-e29b-41d4-a716-446655440000" - -# Monitor pub/sub activity -redis-cli MONITOR -``` - -This system provides robust multi-user session management with strong security guarantees and comprehensive testing coverage. \ No newline at end of file diff --git a/embedded-oauth/README.md b/embedded-oauth/README.md new file mode 100644 index 0000000..f56289c --- /dev/null +++ b/embedded-oauth/README.md @@ -0,0 +1,135 @@ +# Embedded OAuth - MCP Feature Reference Server + +This is a **complete, standalone implementation** of an MCP server with self-hosted OAuth authentication. Everything you need is in this directory. + +## Overview + +Per the [MCP specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), "the authorization server may be hosted with the resource server." This mode demonstrates a self-hosted OAuth 2.1 authorization server running in the same process as the MCP server. + +**Pattern demonstrated**: OAuth server that delegates user authentication to an upstream identity provider (corporate SSO, LDAP, Active Directory). + +**Real-world use case**: Enterprise deployments that need control over OAuth token issuance while leveraging existing corporate identity infrastructure. + +For the OAuth-as-a-Service pattern (using Auth0/Okta directly), see [external-oauth](../external-oauth/README.md). + +## Quick Start + +```bash +# 1. Make sure Redis is running (from repo root) +docker compose up -d + +# 2. Install dependencies (if not already done) +npm install + +# 3. Start the server +npm run dev + +# 4. Test with MCP Inspector +npx -y @modelcontextprotocol/inspector +# Connect to: http://localhost:3232/mcp +``` + +## Available Commands + +```bash +npm run dev # Start with hot reload +npm run dev:break # Start with debugger +npm run build # Build to dist/ +npm start # Run built version +npm test # Run 85 unit tests +npm run lint # Lint code +npm run typecheck # Check types +``` + +## Configuration + +Environment variables are in `.env`: +```bash +BASE_URI=http://localhost:3232 +PORT=3232 +REDIS_URL=redis://localhost:6379 +``` + +## What This Demonstrates + +**OAuth + MCP integration**: +- Self-hosted OAuth 2.1 server with PKCE +- Upstream identity provider delegation (simulated via `/mock-upstream-idp/*`) +- Complete authorization flow in one codebase + +**MCP features**: +- Tools (7), resources (100), prompts (3), sampling, logging, completions +- Streamable HTTP and SSE transports +- Redis-backed horizontal scaling +- Session isolation and user ownership + +## Understanding the Upstream Delegation Pattern + +This implementation demonstrates a common enterprise OAuth pattern: + +``` +MCP Client This Server Simulated Upstream + │ │ │ + │──1. /authorize───────────────>│ │ + │<───(show auth page)───────────│ │ + │ │ │ + │──2. Click "Continue"──────────>│ │ + │<──redirect /mock-upstream-idp─│ │ + │ │ │ + │──3. /mock-upstream-idp/authorize──────────────────────────────>│ + │<──(show user selection)───────────────────────────────────────│ + │──4. Select user───────────────────────────────────────────────>│ + │<──redirect with userId────────────────────────────────────────│ + │ │ │ + │──5. /mock-upstream-idp/callback──>│ │ + │ │──(validate userId) │ + │ │──(issue MCP tokens) │ + │<──redirect with auth code─────│ │ + │ │ │ + │──6. /token (exchange code)────>│ │ + │<──MCP access token────────────│ │ +``` + +**In production**, `/mock-upstream-idp/*` would be replaced by corporate SSO (Okta, Azure AD, etc.). + +## Code Organization + +``` +src/ +├── index.ts # Main entry point (MCP + OAuth + routing) +├── auth/ +│ ├── provider.ts # FeatureReferenceAuthProvider (OAuth server logic) +│ └── auth-core.ts # Token generation, PKCE utilities +├── services/ +│ ├── mcp.ts # MCP server with all features +│ ├── auth.ts # Auth service wrappers +│ ├── redis-auth.ts # Redis auth operations +│ └── redisTransport.ts # Redis-backed transport +├── handlers/ +│ ├── shttp.ts # Streamable HTTP handler +│ ├── sse.ts # SSE handler +│ ├── mock-upstream-idp.ts # Mock upstream IDP simulation +│ └── common.ts # Shared middleware +└── utils/ + └── logger.ts # Structured logging +``` + +## Next Steps + +- Modify `src/services/mcp.ts` to add custom tools and resources +- See [external-oauth](../external-oauth/README.md) for external OAuth pattern +- See [docs/oauth-flow.md](../docs/oauth-flow.md) for detailed OAuth flow analysis + +## Testing + +```bash +npm test # 85 unit tests +npm run test:e2e:integrated # E2E test (from repo root) +``` + +## References + +- [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) +- [OAuth 2.1 Draft](https://oauth.net/2.1/) +- [RFC 6749: OAuth 2.0 Framework](https://datatracker.ietf.org/doc/html/rfc6749) +- [docs/oauth-flow.md](../docs/oauth-flow.md) - Detailed flow analysis with mode differences diff --git a/embedded-oauth/eslint.config.mjs b/embedded-oauth/eslint.config.mjs new file mode 100644 index 0000000..515114c --- /dev/null +++ b/embedded-oauth/eslint.config.mjs @@ -0,0 +1,19 @@ +// @ts-check + +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + linterOptions: { + reportUnusedDisableDirectives: false, + }, + rules: { + "@typescript-eslint/no-unused-vars": ["error", + { "argsIgnorePattern": "^_" } + ] + } + } +); diff --git a/embedded-oauth/jest.config.js b/embedded-oauth/jest.config.js new file mode 100644 index 0000000..87825b9 --- /dev/null +++ b/embedded-oauth/jest.config.js @@ -0,0 +1,19 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +export default { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + extensionsToTreatAsEsm: ['.ts'], + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + useESM: true, + }, + ], + }, + testPathIgnorePatterns: ['/node_modules/', '/dist/', '/scratch/'], + injectGlobals: true, +}; diff --git a/embedded-oauth/package-lock.json b/embedded-oauth/package-lock.json new file mode 100644 index 0000000..47cc325 --- /dev/null +++ b/embedded-oauth/package-lock.json @@ -0,0 +1,7141 @@ +{ + "name": "mcp-server-feature-reference-integrated", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-server-feature-reference-integrated", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@redis/client": "^1.6.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-rate-limit": "^8.0.1", + "raw-body": "^3.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/content-type": "^1.1.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + } + }, + "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.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@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.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@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.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "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.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "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-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "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-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "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/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.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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/@modelcontextprotocol/sdk": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.18.2.tgz", + "integrity": "sha512-beedclIvFcCnPrYgHsylqiYJVJ/CI47Vyc4tY8no1/Li/O8U4BTlJfy6ZwxkYwx+Mx10nrgwSVrA7VBbhh4slg==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "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", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "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": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@redis/client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", + "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "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/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/content-type": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.9.tgz", + "integrity": "sha512-Hq9IMnfekuOCsEmYl4QX2HBrT+XsfXiupfrLLY8Dcf3Puf4BkBOxSbWYTITSOQAhJoYPBez+b4MJRpIYL65z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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", + "peer": true + }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.18.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz", + "integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz", + "integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/type-utils": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.45.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz", + "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz", + "integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.45.0", + "@typescript-eslint/types": "^8.45.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz", + "integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz", + "integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz", + "integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz", + "integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz", + "integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.45.0", + "@typescript-eslint/tsconfig-utils": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz", + "integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz", + "integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0", + "peer": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.9.tgz", + "integrity": "sha512-hY/u2lxLrbecMEWSB0IpGzGyDyeoMFQhCvZd2jGFSE5I17Fh01sYUBPCJtkWERw7zrac9+cIghxm/ytJa2X8iA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "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.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001746", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz", + "integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "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/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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/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/dedent": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "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.228", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz", + "integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "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/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "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.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "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/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.1.0.tgz", + "integrity": "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "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-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC", + "peer": true + }, + "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": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "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/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "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/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "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/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "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-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "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/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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/lru-cache/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/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "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": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "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": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "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/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "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.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "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/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "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/router/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/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "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/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": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.4.tgz", + "integrity": "sha512-ccVcRABct5ZELCT5U0+DZwkXMCcOCLi2doHRrKy1nK/s7J7bch6TzJMsrY09WxgUUIP/ITfmcDS8D2yl63rnXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "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/typescript-eslint": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz", + "integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.45.0", + "@typescript-eslint/parser": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "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.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "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/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "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/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/embedded-oauth/package.json b/embedded-oauth/package.json new file mode 100644 index 0000000..feaf367 --- /dev/null +++ b/embedded-oauth/package.json @@ -0,0 +1,43 @@ +{ + "name": "mcp-server-feature-reference-embedded-oauth", + "version": "0.1.0", + "description": "MCP Feature Reference Server - Embedded OAuth", + "type": "module", + "main": "dist/index.js", + "scripts": { + "start": "node dist/index.js", + "dev": "tsx watch --inspect src/index.ts", + "dev:break": "tsx --inspect-brk watch src/index.ts", + "build": "tsc && npm run copy-static", + "copy-static": "mkdir -p dist/static && cp -r src/static/* dist/static/", + "lint": "eslint src/", + "typecheck": "tsc --noEmit", + "test": "NODE_OPTIONS=--experimental-vm-modules jest" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@redis/client": "^1.6.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-rate-limit": "^8.0.1", + "raw-body": "^3.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/content-type": "^1.1.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + }, + "overrides": { + "@types/express": "^5.0.0", + "@types/express-serve-static-core": "^5.0.2" + } +} diff --git a/shared/auth-core.ts b/embedded-oauth/src/auth/auth-core.ts similarity index 100% rename from shared/auth-core.ts rename to embedded-oauth/src/auth/auth-core.ts diff --git a/embedded-oauth/src/auth/provider.test.ts b/embedded-oauth/src/auth/provider.test.ts new file mode 100644 index 0000000..03baa83 --- /dev/null +++ b/embedded-oauth/src/auth/provider.test.ts @@ -0,0 +1,412 @@ +import { jest, describe, beforeEach, it, expect } from '@jest/globals'; +import { Response } from "express"; +import { AuthorizationParams } from "@modelcontextprotocol/sdk/server/auth/provider.js"; +import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; +import { InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors.js"; +import { MockRedisClient, setRedisClient } from "../redis.js"; +import { McpInstallation, PendingAuthorization, TokenExchange } from "../types.js"; +import { FeatureReferenceAuthProvider, FeatureReferenceOAuthClientsStore } from "./provider.js"; +import * as authService from "../services/auth.js"; + +// Helper function to create sample client +function createTestClient(): OAuthClientInformationFull { + return { + client_id: "test-client-id", + client_name: "Test Client", + client_uri: "https://example.com", + redirect_uris: ["https://example.com/callback"] + }; +} + +// Helper function to create a mock Response object +function createMockResponse() { + const res = { + redirect: jest.fn().mockReturnThis(), + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + send: jest.fn().mockReturnThis(), + setHeader: jest.fn().mockReturnThis() + }; + return res as unknown as jest.Mocked; +} + + +function getMockAuthValues() { + const client = createTestClient(); + // Use properly generated tokens for encryption + const accessToken = authService.generateToken(); + const newTokens: OAuthTokens = { + access_token: authService.generateToken(), + refresh_token: authService.generateToken(), + token_type: "bearer", + expires_in: 3600, + }; + const mcpInstallation: McpInstallation = { + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", + }, + mcpTokens: { + access_token: accessToken, + token_type: "Bearer", + refresh_token: authService.generateToken(), + expires_in: 3600, + }, + clientId: client.client_id, + issuedAt: Date.now() / 1000, + userId: "test-user-id", + }; + + return { + client, + accessToken, + newTokens, + mcpInstallation, + } +} + +describe("FeatureReferenceOAuthClientsStore", () => { + let clientsStore: FeatureReferenceOAuthClientsStore; + + beforeEach(() => { + const mockRedis = new MockRedisClient(); + setRedisClient(mockRedis); + jest.resetAllMocks(); + clientsStore = new FeatureReferenceOAuthClientsStore(); + }); + + describe("getClient", () => { + it("returns undefined for non-existent client", async () => { + const result = await clientsStore.getClient("non-existent"); + expect(result).toBeUndefined(); + }); + + it("returns client information for existing client", async () => { + const client = createTestClient(); + // First save the client + await clientsStore.registerClient(client); + + // Then retrieve it + const result = await clientsStore.getClient(client.client_id); + + expect(result).toEqual(client); + }); + }); + + describe("registerClient", () => { + it("saves and returns client information", async () => { + const client = createTestClient(); + + const result = await clientsStore.registerClient(client); + + expect(result).toEqual(client); + + // Verify it was saved + const retrieved = await clientsStore.getClient(client.client_id); + expect(retrieved).toEqual(client); + }); + }); +}); + +describe("FeatureReferenceAuthProvider", () => { + let provider: FeatureReferenceAuthProvider; + let mockRedis: MockRedisClient; + + beforeEach(() => { + jest.resetAllMocks(); + + mockRedis = new MockRedisClient(); + setRedisClient(mockRedis); + + provider = new FeatureReferenceAuthProvider(); + }); + + describe("authorize", () => { + it("saves pending authorization and sends HTML response", async () => { + const client = createTestClient(); + // Use a type assertion to make TypeScript ignore the mismatch + const params = { + redirectUri: "https://example.com/callback", + codeChallenge: "test-challenge", + codeChallengeMethod: "S256", + } as unknown as AuthorizationParams; + const res = createMockResponse(); + + await provider.authorize(client, params, res); + + // Verify HTML sent with redirect + expect(res.send).toHaveBeenCalled(); + const sentHtml = (res.send as jest.Mock).mock.calls[0][0]; + expect(sentHtml).toContain('MCP Server Authorization'); + expect(sentHtml).toContain('Authorization Required'); + expect(sentHtml).toContain('mock-upstream-idp/authorize?redirect_uri=/mock-upstream-idp/callback&state='); + }); + }); + + describe("challengeForAuthorizationCode", () => { + it("returns code challenge for valid authorization code", async () => { + const client = createTestClient(); + const pendingAuth: PendingAuthorization = { + redirectUri: "https://example.com/callback", + codeChallenge: "test-challenge", + codeChallengeMethod: "S256", + clientId: client.client_id, + }; + + // First save the pending authorization + const authCode = authService.generateToken(); + await authService.savePendingAuthorization(authCode, pendingAuth); + + const result = await provider.challengeForAuthorizationCode(client, authCode); + + expect(result).toBe("test-challenge"); + }); + + it("throws error for non-existent authorization code", async () => { + const client = createTestClient(); + + await expect(provider.challengeForAuthorizationCode(client, "test-code")) + .rejects + .toThrow("Authorization code not found"); + }); + + it("throws error when client ID doesn't match", async () => { + const client = createTestClient(); + const pendingAuth: PendingAuthorization = { + redirectUri: "https://example.com/callback", + codeChallenge: "test-challenge", + codeChallengeMethod: "S256", + clientId: "different-client-id", + }; + + // Save pending auth with different client ID + const authCode = authService.generateToken(); + await authService.savePendingAuthorization(authCode, pendingAuth); + + await expect(provider.challengeForAuthorizationCode(client, authCode)) + .rejects + .toThrow("Authorization code does not match client"); + }); + }); + + describe("exchangeAuthorizationCode", () => { + it("returns tokens when exchange is successful", async () => { + const client = createTestClient(); + const { mcpInstallation } = getMockAuthValues(); + + // Setup: save token exchange and installation + const authCode = authService.generateToken(); + const tokenExchange: TokenExchange = { + mcpAccessToken: mcpInstallation.mcpTokens.access_token, + alreadyUsed: false, + }; + await authService.saveTokenExchange(authCode, tokenExchange); + await authService.saveMcpInstallation(mcpInstallation.mcpTokens.access_token, mcpInstallation); + + const result = await provider.exchangeAuthorizationCode(client, authCode); + + expect(result).toEqual({ + access_token: mcpInstallation.mcpTokens.access_token, + expires_in: mcpInstallation.mcpTokens.expires_in, + refresh_token: mcpInstallation.mcpTokens.refresh_token, + token_type: "Bearer", + }); + }); + + it("throws error for invalid authorization code", async () => { + const client = createTestClient(); + + await expect(provider.exchangeAuthorizationCode(client, "test-code")) + .rejects + .toThrow("Invalid authorization code"); + }); + }); + + describe("exchangeRefreshToken", () => { + it("returns new tokens when refresh token is valid", async () => { + const { + client, + accessToken, + mcpInstallation, + } = getMockAuthValues(); + + // Setup: save refresh token and installation + const refreshToken = authService.generateToken(); + await authService.saveRefreshToken(refreshToken, accessToken); + await authService.saveMcpInstallation(accessToken, mcpInstallation); + + const result = await provider.exchangeRefreshToken(client, refreshToken); + + // Should return new tokens + expect(result).toHaveProperty('access_token'); + expect(result).toHaveProperty('refresh_token'); + expect(result).toHaveProperty('expires_in', 3600); + expect(result).toHaveProperty('token_type', 'Bearer'); + }); + + it("throws error for invalid refresh token", async () => { + const client = createTestClient(); + + await expect(provider.exchangeRefreshToken(client, "test-refresh-token")) + .rejects + .toThrow("Invalid refresh token"); + }); + + it("throws error for refresh token with no installation", async () => { + const client = createTestClient(); + const refreshToken = authService.generateToken(); + const accessToken = authService.generateToken(); + + // Only save refresh token, not the installation + await authService.saveRefreshToken(refreshToken, accessToken); + + await expect(provider.exchangeRefreshToken(client, refreshToken)) + .rejects + .toThrow("Invalid refresh token"); + }); + + it("throws error when client ID doesn't match", async () => { + const client = createTestClient(); + const accessToken = authService.generateToken(); + const refreshToken = authService.generateToken(); + + const mcpInstallation: McpInstallation = { + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", + }, + mcpTokens: { + access_token: accessToken, + token_type: "Bearer", + expires_in: 3600, + }, + clientId: "different-client-id", + issuedAt: Date.now() / 1000, + userId: "test-user-id", + }; + + await authService.saveRefreshToken(refreshToken, accessToken); + await authService.saveMcpInstallation(accessToken, mcpInstallation); + + await expect(provider.exchangeRefreshToken(client, refreshToken)) + .rejects + .toThrow("Invalid client"); + }); + + it("works correctly even when the refresh token has expired from Redis", async () => { + const client = createTestClient(); + + // Simulate the refresh token not being found in Redis (expired) + await expect(provider.exchangeRefreshToken(client, "expired-refresh-token")) + .rejects + .toThrow("Invalid refresh token"); + }); + }); + + describe("verifyAccessToken", () => { + it("returns auth info for valid token", async () => { + const accessToken = authService.generateToken(); + const mcpInstallation: McpInstallation = { + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", + }, + mcpTokens: { + access_token: accessToken, + token_type: "Bearer", + expires_in: 3600, + }, + clientId: "client-id", + issuedAt: Date.now() / 1000, + userId: "test-user-id", + }; + + await authService.saveMcpInstallation(accessToken, mcpInstallation); + + const result = await provider.verifyAccessToken(accessToken); + + expect(result).toEqual({ + token: accessToken, + clientId: mcpInstallation.clientId, + scopes: ['mcp'], + expiresAt: mcpInstallation.mcpTokens.expires_in! + mcpInstallation.issuedAt, + extra: { + userId: "test-user-id" + } + }); + }); + + it("throws error for invalid token", async () => { + await expect(provider.verifyAccessToken("invalid-token")) + .rejects + .toThrow("Invalid access token"); + }); + + it("throws InvalidTokenError for expired token", async () => { + const accessToken = authService.generateToken(); + const oneDayInSeconds = 24 * 60 * 60; + const twoDaysAgoInSeconds = Math.floor(Date.now() / 1000) - (2 * oneDayInSeconds); + + const mcpInstallation: McpInstallation = { + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", + }, + mcpTokens: { + access_token: accessToken, + token_type: "Bearer", + expires_in: oneDayInSeconds, + }, + clientId: "client-id", + issuedAt: twoDaysAgoInSeconds, // 2 days ago, with 1-day expiry + userId: "test-user-id", + }; + + await authService.saveMcpInstallation(accessToken, mcpInstallation); + + await expect(provider.verifyAccessToken(accessToken)) + .rejects + .toThrow(InvalidTokenError); + }); + }); + + describe("revokeToken", () => { + it("revokes the installation when given an access token", async () => { + const client = createTestClient(); + const accessToken = authService.generateToken(); + const mcpInstallation: McpInstallation = { + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", + }, + mcpTokens: { + access_token: accessToken, + token_type: "Bearer", + expires_in: 3600, + }, + clientId: client.client_id, + issuedAt: Date.now() / 1000, + userId: "test-user-id", + }; + + // Save the installation + await authService.saveMcpInstallation(accessToken, mcpInstallation); + + // Verify it exists + const saved = await authService.readMcpInstallation(accessToken); + expect(saved).toBeTruthy(); + + // Revoke it + const request: OAuthTokenRevocationRequest = { + token: accessToken, + token_type_hint: "access_token" + }; + + await provider.revokeToken(client, request); + + // Verify it was revoked + const revoked = await authService.readMcpInstallation(accessToken); + expect(revoked).toBeUndefined(); + }); + }); +}); \ No newline at end of file diff --git a/src/auth/provider.ts b/embedded-oauth/src/auth/provider.ts similarity index 95% rename from src/auth/provider.ts rename to embedded-oauth/src/auth/provider.ts index d522f2f..22aed80 100644 --- a/src/auth/provider.ts +++ b/embedded-oauth/src/auth/provider.ts @@ -23,7 +23,7 @@ import { logger } from '../utils/logger.js'; /** * Implementation of the OAuthRegisteredClientsStore interface using the existing client registration system */ -export class EverythingOAuthClientsStore implements OAuthRegisteredClientsStore { +export class FeatureReferenceOAuthClientsStore implements OAuthRegisteredClientsStore { async getClient(clientId: string): Promise { const registration = await getClientRegistration(clientId); if (!registration) { @@ -41,11 +41,11 @@ export class EverythingOAuthClientsStore implements OAuthRegisteredClientsStore /** * Implementation of the OAuthServerProvider interface for upstream authentication */ -export class EverythingAuthProvider implements OAuthServerProvider { - private _clientsStore: EverythingOAuthClientsStore; +export class FeatureReferenceAuthProvider implements OAuthServerProvider { + private _clientsStore: FeatureReferenceOAuthClientsStore; constructor() { - this._clientsStore = new EverythingOAuthClientsStore(); + this._clientsStore = new FeatureReferenceOAuthClientsStore(); } get clientsStore(): OAuthRegisteredClientsStore { @@ -77,7 +77,7 @@ export class EverythingAuthProvider implements OAuthServerProvider { // TODO: should we use a different key, other than the authorization code, to store the pending authorization? // You can redirect to another page, or you can send an html response directly - // res.redirect(new URL(`fakeupstreamauth/authorize?metadata=${authorizationCode}`, BASE_URI).href); + // res.redirect(new URL(`mock-upstream-idp/authorize?metadata=${authorizationCode}`, BASE_URI).href); // Set permissive CSP for styling res.setHeader('Content-Security-Policy', [ @@ -261,7 +261,7 @@ export class EverythingAuthProvider implements OAuthServerProvider {

You'll be redirected to authenticate with the upstream provider. Once verified, you'll be granted access to this MCP server's resources.

- + Continue to Authentication diff --git a/embedded-oauth/src/config.ts b/embedded-oauth/src/config.ts new file mode 100644 index 0000000..cfa2992 --- /dev/null +++ b/embedded-oauth/src/config.ts @@ -0,0 +1,24 @@ +import "dotenv/config"; + +/** + * Port for the MCP server to listen on + */ +export const PORT = Number(process.env.PORT) || 3232; + +/** + * Base URI for the MCP server. Used for OAuth callbacks and metadata. + * Should match the port if specified separately. + */ +export const BASE_URI = process.env.BASE_URI || `http://localhost:${PORT}`; + +// Validate PORT and BASE_URI consistency +const baseUrl = new URL(BASE_URI); +if (baseUrl.port && parseInt(baseUrl.port) !== PORT) { + console.warn(`Warning: BASE_URI port (${baseUrl.port}) doesn't match PORT (${PORT})`); +} + +/** + * Redis connection URL + */ +export const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379'; + diff --git a/src/context.ts b/embedded-oauth/src/context.ts similarity index 78% rename from src/context.ts rename to embedded-oauth/src/context.ts index 61190b8..4785ab2 100644 --- a/src/context.ts +++ b/embedded-oauth/src/context.ts @@ -1,21 +1,21 @@ import { AsyncLocalStorage } from "async_hooks"; -import { FakeUpstreamInstallation } from "./types.js"; +import { MockUpstreamInstallation } from "./types.js"; interface RequestContext { mcpAccessToken: string; - fakeUpstreamInstallation: FakeUpstreamInstallation; + mockUpstreamInstallation: MockUpstreamInstallation; } export const asyncLocalStorage = new AsyncLocalStorage(); -export function getFakeUpstreamInstallation(): FakeUpstreamInstallation { +export function getMockUpstreamInstallation(): MockUpstreamInstallation { const context = asyncLocalStorage.getStore(); if (!context) { throw new Error( "No request context found - are you calling this from within a request handler?", ); } - return context.fakeUpstreamInstallation; + return context.mockUpstreamInstallation; } export function getMcpAccessToken(): string { diff --git a/src/handlers/common.ts b/embedded-oauth/src/handlers/common.ts similarity index 93% rename from src/handlers/common.ts rename to embedded-oauth/src/handlers/common.ts index 3a95c18..48c5be1 100644 --- a/src/handlers/common.ts +++ b/embedded-oauth/src/handlers/common.ts @@ -57,7 +57,7 @@ export async function authContext( } // Wrap the rest of the request handling in the context - withContext({ mcpAccessToken: token, fakeUpstreamInstallation: mcpInstallation.fakeUpstreamInstallation }, () => + withContext({ mcpAccessToken: token, mockUpstreamInstallation: mcpInstallation.mockUpstreamInstallation }, () => next(), ); } diff --git a/embedded-oauth/src/handlers/mock-upstream-idp.ts b/embedded-oauth/src/handlers/mock-upstream-idp.ts new file mode 100644 index 0000000..2eb4d8f --- /dev/null +++ b/embedded-oauth/src/handlers/mock-upstream-idp.ts @@ -0,0 +1,353 @@ +import { Request, Response } from "express"; +import { generateMcpTokens, readPendingAuthorization, saveMcpInstallation, saveRefreshToken, saveTokenExchange } from "../services/auth.js"; +import { McpInstallation } from "../types.js"; +import { logger } from "../utils/logger.js"; + +// Mock upstream identity provider - simulates Google OAuth, corporate SAML, or other external identity providers +// that OAuth servers can delegate user authentication to (configurable in Auth0/Okta). +// In production, the OAuth server would redirect to actual external IDPs. + +export async function handleMockUpstreamAuthorize(req: Request, res: Response) { + // get the redirect_uri and state from the query params + const { redirect_uri, state } = req.query; + + // Set a more permissive CSP for auth pages to allow inline styles and scripts + res.setHeader('Content-Security-Policy', [ + "default-src 'self'", + "style-src 'self' 'unsafe-inline'", // Allow inline styles for auth page styling + "script-src 'self' 'unsafe-inline'", // Allow inline scripts for auth page functionality + "object-src 'none'", + "frame-ancestors 'none'", + "form-action 'self'", + "base-uri 'self'" + ].join('; ')); + + res.send(` + + + + + + Upstream Provider Authentication + + + +
+ +

Upstream Authentication

+

Please verify your identity with the upstream provider

+ +
+

Your User Identity

+
Loading...
+ +
+ + + +
+ Testing Multiple Users: Open this page in different browser windows or incognito tabs to simulate different users. Each will have their own unique User ID and separate MCP sessions. +
+
+ + + + + `); +} + + +// Callback from the mock upstream identity provider after user authentication +export async function handleMockUpstreamCallback(req: Request, res: Response) { + const { + // The state returned from the upstream auth server is actually the authorization code + state: mcpAuthorizationCode, + code: upstreamAuthorizationCode, + userId, // User ID from the authorization flow + } = req.query; + + logger.debug('Mock upstream IDP callback received', { + mcpAuthorizationCode: typeof mcpAuthorizationCode === 'string' ? mcpAuthorizationCode.substring(0, 8) + '...' : mcpAuthorizationCode, + upstreamAuthorizationCode: typeof upstreamAuthorizationCode === 'string' ? upstreamAuthorizationCode.substring(0, 8) + '...' : upstreamAuthorizationCode, + userId + }); + + // Exchange the upstream authorization code for tokens + const upstreamTokens = await mockUpstreamTokenExchange(upstreamAuthorizationCode as string); + + // Validate that it's a string + if (typeof mcpAuthorizationCode !== "string") { + throw new Error("Invalid authorization code"); + } + + const pendingAuth = await readPendingAuthorization(mcpAuthorizationCode); + logger.debug('Reading pending authorization', { + mcpAuthorizationCode: mcpAuthorizationCode.substring(0, 8) + '...', + found: !!pendingAuth + }); + + if (!pendingAuth) { + throw new Error("No matching authorization found"); + } + + logger.debug('Generating MCP tokens'); + const mcpTokens = generateMcpTokens(); + logger.debug('MCP tokens generated', { + hasAccessToken: !!mcpTokens.access_token, + hasRefreshToken: !!mcpTokens.refresh_token + }); + + const mcpInstallation: McpInstallation = { + mockUpstreamInstallation: { + mockUpstreamAccessToken: upstreamTokens.access_token, + mockUpstreamRefreshToken: upstreamTokens.refresh_token, + }, + mcpTokens, + clientId: pendingAuth.clientId, + issuedAt: Date.now() / 1000, + userId: (userId as string) || 'anonymous-user', // Include user ID from auth flow + } + + logger.debug('Saving MCP installation'); + // Store the upstream authorization data + await saveMcpInstallation(mcpTokens.access_token, mcpInstallation); + logger.debug('MCP installation saved'); + + // Store the refresh token -> access token mapping + if (mcpTokens.refresh_token) { + logger.debug('Saving refresh token mapping'); + await saveRefreshToken(mcpTokens.refresh_token, mcpTokens.access_token); + logger.debug('Refresh token mapping saved'); + } + + logger.debug('Saving token exchange data'); + // Store the token exchange data + await saveTokenExchange(mcpAuthorizationCode, { + mcpAccessToken: mcpTokens.access_token, + alreadyUsed: false, + }); + logger.debug('Token exchange data saved'); + + // Redirect back to the original application with the authorization code and state + const redirectUrl = pendingAuth.state ? + `${pendingAuth.redirectUri}?code=${mcpAuthorizationCode}&state=${pendingAuth.state}` : + `${pendingAuth.redirectUri}?code=${mcpAuthorizationCode}`; + + logger.debug('Redirecting to callback', { + redirectUrl, + hasState: !!pendingAuth.state + }); + res.redirect(redirectUrl); + logger.debug('Redirect completed'); +}; + +function mockUpstreamTokenExchange( + authorizationCode: string, +): Promise<{ access_token: string; refresh_token: string }> { + // just return the authorization code with a suffix + return new Promise((resolve) => { + resolve({ + access_token: `${authorizationCode}-exchanged-for-access-token`, + refresh_token: `${authorizationCode}-exchanged-for-refresh-token`, + }); + }); +} \ No newline at end of file diff --git a/src/handlers/shttp.integration.test.ts b/embedded-oauth/src/handlers/shttp.integration.test.ts similarity index 100% rename from src/handlers/shttp.integration.test.ts rename to embedded-oauth/src/handlers/shttp.integration.test.ts diff --git a/src/handlers/shttp.test.ts b/embedded-oauth/src/handlers/shttp.test.ts similarity index 100% rename from src/handlers/shttp.test.ts rename to embedded-oauth/src/handlers/shttp.test.ts diff --git a/src/handlers/shttp.ts b/embedded-oauth/src/handlers/shttp.ts similarity index 100% rename from src/handlers/shttp.ts rename to embedded-oauth/src/handlers/shttp.ts diff --git a/src/handlers/sse.ts b/embedded-oauth/src/handlers/sse.ts similarity index 100% rename from src/handlers/sse.ts rename to embedded-oauth/src/handlers/sse.ts diff --git a/src/index.ts b/embedded-oauth/src/index.ts similarity index 63% rename from src/index.ts rename to embedded-oauth/src/index.ts index 28166b8..5125d43 100644 --- a/src/index.ts +++ b/embedded-oauth/src/index.ts @@ -1,15 +1,14 @@ import { BearerAuthMiddlewareOptions, requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"; -import { AuthRouterOptions, getOAuthProtectedResourceMetadataUrl, mcpAuthRouter, mcpAuthMetadataRouter } from "@modelcontextprotocol/sdk/server/auth/router.js"; +import { AuthRouterOptions, getOAuthProtectedResourceMetadataUrl, mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js"; import cors from "cors"; import rateLimit from "express-rate-limit"; import express from "express"; import path from "path"; import { fileURLToPath } from "url"; -import { EverythingAuthProvider } from "./auth/provider.js"; -import { ExternalAuthVerifier } from "./auth/external-verifier.js"; -import { BASE_URI, PORT, AUTH_MODE, AUTH_SERVER_URL } from "./config.js"; +import { FeatureReferenceAuthProvider } from "./auth/provider.js"; +import { handleMockUpstreamAuthorize, handleMockUpstreamCallback } from "./handlers/mock-upstream-idp.js"; +import { BASE_URI, PORT } from "./config.js"; import { authContext } from "./handlers/common.js"; -import { handleFakeAuthorize, handleFakeAuthorizeRedirect } from "./handlers/fakeauth.js"; import { handleStreamableHTTP } from "./handlers/shttp.js"; import { handleMessage, handleSSEConnection } from "./handlers/sse.js"; import { redisClient } from "./redis.js"; @@ -124,7 +123,7 @@ app.use(baseSecurityHeaders); app.options('*', cors(corsOptions)); // Rate limiting for custom endpoints -const fakeAuthRateLimit = rateLimit({ +const mockUpstreamIdpRateLimit = rateLimit({ windowMs: 60 * 1000, // 1 minute limit: 20, // 20 auth attempts per minute message: { error: 'too_many_requests', error_description: 'Authentication rate limit exceeded' } @@ -136,18 +135,13 @@ const staticFileRateLimit = rateLimit({ message: { error: 'too_many_requests', error_description: 'Static file rate limit exceeded' } }); -// Mode-dependent auth configuration -let bearerAuth: express.RequestHandler; +// Embedded OAuth: MCP server acts as its own OAuth authorization server +logger.info('Starting MCP server with embedded OAuth', { + baseUri: BASE_URI, + port: PORT +}); -if (AUTH_MODE === 'integrated') { - // Integrated mode: MCP server acts as its own OAuth server - logger.info('Starting MCP server in INTEGRATED mode', { - mode: AUTH_MODE, - baseUri: BASE_URI, - port: PORT - }); - - const authProvider = new EverythingAuthProvider(); +const authProvider = new FeatureReferenceAuthProvider(); const authRouterOptions: AuthRouterOptions = { provider: authProvider, @@ -177,76 +171,7 @@ if (AUTH_MODE === 'integrated') { resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(new URL(BASE_URI)), }; - bearerAuth = requireBearerAuth(bearerAuthOptions); - -} else { - // Separate mode: MCP server uses external auth server - logger.info('Starting MCP server in SEPARATE mode', { - mode: AUTH_MODE, - baseUri: BASE_URI, - port: PORT, - authServerUrl: AUTH_SERVER_URL - }); - - // Fetch metadata from external auth server with retry logic - let authMetadata; - const maxRetries = 5; - const retryDelay = 3000; // 3 seconds - - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - logger.info(`Attempting to connect to auth server (attempt ${attempt}/${maxRetries})`, { - authServerUrl: AUTH_SERVER_URL - }); - - const authMetadataResponse = await fetch(`${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`); - if (!authMetadataResponse.ok) { - throw new Error(`Failed to fetch auth server metadata: ${authMetadataResponse.status} ${authMetadataResponse.statusText}`); - } - authMetadata = await authMetadataResponse.json(); - logger.info('Successfully fetched auth server metadata', { - issuer: authMetadata.issuer, - authorizationEndpoint: authMetadata.authorization_endpoint, - tokenEndpoint: authMetadata.token_endpoint - }); - break; // Success, exit retry loop - - } catch (error) { - if (attempt < maxRetries) { - logger.info(`Failed to connect to auth server, retrying in ${retryDelay/1000} seconds...`, { - attempt, - maxRetries, - error: (error as Error).message - }); - await new Promise(resolve => setTimeout(resolve, retryDelay)); - } else { - logger.error('Failed to fetch auth server metadata after all retries', error as Error); - logger.error('Make sure the auth server is running at', undefined, { authServerUrl: AUTH_SERVER_URL }); - process.exit(1); - } - } - } - - // BACKWARDS COMPATIBILITY: We serve OAuth metadata from the MCP server even in separate mode - // This is technically redundant since the auth server handles all OAuth operations, - // but some clients may expect to find .well-known/oauth-authorization-server on the - // resource server itself. The metadata points to the external auth server endpoints. - app.use(mcpAuthMetadataRouter({ - oauthMetadata: authMetadata, - resourceServerUrl: new URL(BASE_URI), - resourceName: "MCP Everything Server" - })); - - // Configure bearer auth with external verifier - const externalVerifier = new ExternalAuthVerifier(AUTH_SERVER_URL); - - const bearerAuthOptions: BearerAuthMiddlewareOptions = { - verifier: externalVerifier, - resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(new URL(BASE_URI)), - }; - - bearerAuth = requireBearerAuth(bearerAuthOptions); -} +const bearerAuth = requireBearerAuth(bearerAuthOptions); // MCP routes (legacy SSE transport) app.get("/sse", cors(corsOptions), bearerAuth, authContext, sseHeaders, handleSSEConnection); @@ -275,11 +200,9 @@ app.get("/", (req, res) => { res.sendFile(splashPath); }); -// Upstream auth routes (only in integrated mode) -if (AUTH_MODE === 'integrated') { - app.get("/fakeupstreamauth/authorize", fakeAuthRateLimit, cors(corsOptions), handleFakeAuthorize); - app.get("/fakeupstreamauth/callback", fakeAuthRateLimit, cors(corsOptions), handleFakeAuthorizeRedirect); -} +// Mock upstream identity provider routes +app.get("/mock-upstream-idp/authorize", mockUpstreamIdpRateLimit, cors(corsOptions), handleMockUpstreamAuthorize); +app.get("/mock-upstream-idp/callback", mockUpstreamIdpRateLimit, cors(corsOptions), handleMockUpstreamCallback); try { await redisClient.connect(); diff --git a/src/redis.ts b/embedded-oauth/src/redis.ts similarity index 100% rename from src/redis.ts rename to embedded-oauth/src/redis.ts diff --git a/src/services/auth.test.ts b/embedded-oauth/src/services/auth.test.ts similarity index 96% rename from src/services/auth.test.ts rename to embedded-oauth/src/services/auth.test.ts index 4964031..1695cf1 100644 --- a/src/services/auth.test.ts +++ b/embedded-oauth/src/services/auth.test.ts @@ -182,9 +182,9 @@ describe("auth utils", () => { const accessToken = generateToken(); const mcpInstallation: McpInstallation = { - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", }, mcpTokens: { access_token: accessToken, @@ -229,9 +229,9 @@ describe("auth utils", () => { // Save it to Redis with actual function await saveMcpInstallation(accessToken, { - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", }, mcpTokens: { access_token: accessToken, diff --git a/src/services/auth.ts b/embedded-oauth/src/services/auth.ts similarity index 94% rename from src/services/auth.ts rename to embedded-oauth/src/services/auth.ts index 2067de0..5d9b4e3 100644 --- a/src/services/auth.ts +++ b/embedded-oauth/src/services/auth.ts @@ -2,15 +2,15 @@ import { redisClient } from "../redis.js"; import { McpInstallation, PendingAuthorization, TokenExchange } from "../types.js"; import { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js"; -// Re-export from shared modules for backward compatibility +// Re-export from auth-core module export { generatePKCEChallenge, generateToken, decryptString, generateMcpTokens -} from "../../shared/auth-core.js"; +} from "../auth/auth-core.js"; -import * as sharedRedisAuth from "../../shared/redis-auth.js"; +import * as sharedRedisAuth from "./redis-auth.js"; // Wrapper functions that pass redisClient to shared module functions diff --git a/src/services/mcp.ts b/embedded-oauth/src/services/mcp.ts similarity index 99% rename from src/services/mcp.ts rename to embedded-oauth/src/services/mcp.ts index 04a5e65..39192b5 100644 --- a/src/services/mcp.ts +++ b/embedded-oauth/src/services/mcp.ts @@ -103,7 +103,7 @@ interface McpServerWrapper { export const createMcpServer = (): McpServerWrapper => { const server = new Server( { - name: "example-servers/everything", + name: "example-servers/feature-reference", version: "1.0.0", }, { diff --git a/shared/redis-auth.ts b/embedded-oauth/src/services/redis-auth.ts similarity index 97% rename from shared/redis-auth.ts rename to embedded-oauth/src/services/redis-auth.ts index fc4d636..7886bbc 100644 --- a/shared/redis-auth.ts +++ b/embedded-oauth/src/services/redis-auth.ts @@ -1,9 +1,9 @@ import { SetOptions } from "@redis/client"; import { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js"; -import { RedisClient } from "../src/redis.js"; -import { McpInstallation, PendingAuthorization, TokenExchange } from "./types.js"; -import { sha256, encryptString, decryptString } from "./auth-core.js"; -import { logger } from "../src/utils/logger.js"; +import { RedisClient } from "../redis.js"; +import { McpInstallation, PendingAuthorization, TokenExchange } from "../types.js"; +import { sha256, encryptString, decryptString } from "../auth/auth-core.js"; +import { logger } from "../utils/logger.js"; /** * Redis key prefixes for different data types diff --git a/src/services/redisTransport.integration.test.ts b/embedded-oauth/src/services/redisTransport.integration.test.ts similarity index 100% rename from src/services/redisTransport.integration.test.ts rename to embedded-oauth/src/services/redisTransport.integration.test.ts diff --git a/src/services/redisTransport.test.ts b/embedded-oauth/src/services/redisTransport.test.ts similarity index 100% rename from src/services/redisTransport.test.ts rename to embedded-oauth/src/services/redisTransport.test.ts diff --git a/src/services/redisTransport.ts b/embedded-oauth/src/services/redisTransport.ts similarity index 100% rename from src/services/redisTransport.ts rename to embedded-oauth/src/services/redisTransport.ts diff --git a/src/static/index.html b/embedded-oauth/src/static/index.html similarity index 100% rename from src/static/index.html rename to embedded-oauth/src/static/index.html diff --git a/src/static/mcp.png b/embedded-oauth/src/static/mcp.png similarity index 100% rename from src/static/mcp.png rename to embedded-oauth/src/static/mcp.png diff --git a/src/static/styles.css b/embedded-oauth/src/static/styles.css similarity index 100% rename from src/static/styles.css rename to embedded-oauth/src/static/styles.css diff --git a/shared/types.ts b/embedded-oauth/src/types.ts similarity index 88% rename from shared/types.ts rename to embedded-oauth/src/types.ts index d87ce13..877b5dc 100644 --- a/shared/types.ts +++ b/embedded-oauth/src/types.ts @@ -29,14 +29,14 @@ export interface TokenExchange { } /** - * Represents fake upstream tokens for demonstration purposes. + * Represents mock upstream identity provider tokens for demonstration purposes. * In production, this would contain real upstream provider tokens. */ -export interface FakeUpstreamInstallation { - /** Simulated access token from the fake upstream provider */ - fakeAccessTokenForDemonstration: string; - /** Simulated refresh token from the fake upstream provider */ - fakeRefreshTokenForDemonstration: string; +export interface MockUpstreamInstallation { + /** Access token from the mock upstream identity provider */ + mockUpstreamAccessToken: string; + /** Refresh token from the mock upstream identity provider */ + mockUpstreamRefreshToken: string; } /** @@ -46,7 +46,7 @@ export interface FakeUpstreamInstallation { */ export interface McpInstallation { /** Information from the upstream authentication provider */ - fakeUpstreamInstallation: FakeUpstreamInstallation; + mockUpstreamInstallation: MockUpstreamInstallation; /** MCP OAuth tokens issued to the client */ mcpTokens: OAuthTokens; /** The OAuth client ID associated with this installation */ diff --git a/src/utils/logger.ts b/embedded-oauth/src/utils/logger.ts similarity index 100% rename from src/utils/logger.ts rename to embedded-oauth/src/utils/logger.ts diff --git a/embedded-oauth/tsconfig.json b/embedded-oauth/tsconfig.json new file mode 100644 index 0000000..fee103a --- /dev/null +++ b/embedded-oauth/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "Node16", + "moduleResolution": "Node16", + "sourceMap": true, + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/external-oauth/README.md b/external-oauth/README.md new file mode 100644 index 0000000..b01a2e0 --- /dev/null +++ b/external-oauth/README.md @@ -0,0 +1,109 @@ +# External OAuth - External OAuth Provider Pattern + +Two standalone servers demonstrating how MCP resource servers integrate with external OAuth providers. + +## Overview + +Per the [MCP specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), "the authorization server may be a separate entity." This mode demonstrates the OAuth-as-a-Service pattern. + +**Pattern**: MCP resource server delegating all OAuth operations to a separate authorization server. + +**Real-world use cases**: Applications using Auth0, Okta, Google OAuth, AWS Cognito, or similar providers for authentication. + +## Architecture + +``` +┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ │ OAuth │ │ Token │ │ +│ MCP Client │────────>│ Auth Server │<────────│ MCP Server │ +│ │ │ (port 3001) │validate │ (port 3232) │ +│ │<────────│ │ │ │ +│ │ token │ Issues tokens │ │ Serves MCP │ +│ │ │ │ │ resources │ +│ │────────────────────────────────────> │ │ +│ │ MCP requests with token │ │ +└─────────────┘ └─────────────────┘ +``` + +**Servers**: +1. **Auth Server** - Demo OAuth provider (represents Auth0/Okta) - [Details](auth-server/README.md) +2. **MCP Server** - Resource server with token validation only - [Details](mcp-server/README.md) + +The MCP server contains no OAuth authorization code - it only validates tokens via introspection. + +## Quick Start + +```bash +# From repo root: + +# 1. Make sure Redis is running +docker compose up -d + +# 2. Install dependencies (if not already done) +npm install + +# 3. Start both servers +npm run dev:separate + +# 4. Test with MCP Inspector +npx -y @modelcontextprotocol/inspector +# Connect to: http://localhost:3232/mcp +# OAuth will redirect to auth server at :3001 +``` + +## Individual Server Commands + +```bash +# From repo root: +npm run dev:auth-server # Start just auth server (:3001) +npm run dev:mcp-server # Start just MCP server (:3232) + +# Or from individual directories: +cd auth-server && npm run dev +cd mcp-server && npm run dev +``` + +## Server Roles + +**Auth Server** (port 3001): Demo OAuth provider implementing client registration, authorization, token issuance, and introspection. In production, replace with Auth0, Okta, or similar. [Implementation details](auth-server/README.md) + +**MCP Server** (port 3232): Resource server that validates tokens via introspection and serves MCP resources. Production-ready - just configure `AUTH_SERVER_URL`. [Implementation details](mcp-server/README.md) + +## Testing + +```bash +# From repo root: +npm run test:separate # Run all tests (104 total) +npm run test:e2e:separate # Full e2e test + +# Individual workspace tests: +npm test --workspace=external-oauth/auth-server # 37 tests +npm test --workspace=external-oauth/mcp-server # 67 tests +``` + +## Key Differences from Embedded OAuth + +| Aspect | Embedded OAuth | External OAuth | +|--------|----------------|---------------| +| Servers | 1 server | 2 servers | +| OAuth endpoints | On MCP server | On auth server | +| Token validation | Direct (in-process) | Remote (introspection API) | +| Deployment | Simpler | Production-like | +| Code sharing | All in one place | Separated by concern | + +## Production Adaptation + +Replace `auth-server/` with a commercial OAuth provider (Auth0, Okta, AWS Cognito, Azure AD). The `mcp-server/` code integrates with any RFC 7662-compliant introspection endpoint. + +## References + +- [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) +- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) +- [OAuth 2.0 Resource Servers](https://www.oauth.com/oauth2-servers/the-resource-server/) +- [docs/oauth-flow.md](../docs/oauth-flow.md) - Detailed flow analysis + +## Next Steps + +- See [auth-server/README.md](auth-server/README.md) for OAuth provider implementation +- See [mcp-server/README.md](mcp-server/README.md) for token validation implementation +- Compare with [embedded-oauth](../embedded-oauth/README.md) for self-hosted OAuth alternative diff --git a/external-oauth/auth-server/README.md b/external-oauth/auth-server/README.md new file mode 100644 index 0000000..b0f24f4 --- /dev/null +++ b/external-oauth/auth-server/README.md @@ -0,0 +1,123 @@ +# Auth Server - Demo OAuth 2.0 Provider + +Demonstration OAuth 2.0 authorization server representing commercial providers (Auth0, Okta, Google OAuth). + +## Purpose + +Complete OAuth 2.1 server implementing: +- Client registration (Dynamic Client Registration) +- Authorization flow with PKCE +- Token issuance and refresh +- Token introspection (RFC 7662) +- User authentication (via demo upstream IDP) + +In production, replace this server with a commercial OAuth provider. + +## Quick Start + +```bash +# From this directory: +npm run dev + +# Server starts on http://localhost:3001 +``` + +## Flow + +``` +MCP Client ──OAuth──> Auth Server ──tokens──> MCP Client ──MCP requests──> MCP Server + │ + └──validate──> Auth Server /introspect +``` + +1. MCP Client initiates OAuth flow with this server +2. Server handles authorization and user authentication +3. Server issues access tokens +4. Client uses tokens to access MCP server +5. MCP server validates tokens by calling this server's `/introspect` endpoint + +## Key Components + +### `src/index.ts` +Main entry point. Sets up: +- OAuth endpoints via SDK's `mcpAuthRouter` +- Token introspection endpoint (`POST /introspect`) +- Mock upstream auth UI for demo +- Rate limiting and CORS + +### `src/auth/provider.ts` +**FeatureReferenceAuthProvider** - Implements OAuth server logic: +- Client registration storage +- Authorization code generation +- PKCE challenge validation +- Token issuance and refresh +- Token verification + +### `src/services/` +- **auth.ts**: Auth service wrappers +- **redis-auth.ts**: Redis operations for OAuth data + +### `src/handlers/mock-upstream-idp.ts` +Mock upstream identity provider. Simulates user authentication that would be handled by corporate SSO or social login in production OAuth providers. + +## OAuth Endpoints + +```bash +# Discovery +GET /.well-known/oauth-authorization-server + +# Client registration +POST /register + +# Authorization +GET /authorize?client_id=...&redirect_uri=...&code_challenge=... + +# Token exchange +POST /token + +# Token introspection (called by MCP server) +POST /introspect +``` + +## Configuration + +Environment variables in `.env`: +```bash +AUTH_SERVER_URL=http://localhost:3001 +AUTH_SERVER_PORT=3001 +BASE_URI=http://localhost:3232 # MCP server URL +REDIS_URL=redis://localhost:6379 +``` + +## Testing + +```bash +npm test # 37 unit tests +npm run lint # Lint code +npm run typecheck # Type checking +npm run build # Build to dist/ +``` + +## Production Replacement + +Commercial OAuth providers supporting RFC 7662 introspection: +- Auth0, Okta, Azure AD/Microsoft Entra +- AWS Cognito, Google Identity Platform +- GitHub OAuth + +The MCP server integrates with any RFC 7662-compliant provider. + +## Redis Data + +This server stores in Redis: +- `auth:client:{clientId}` - OAuth client registrations (30 day TTL) +- `auth:pending:{code}` - Pending authorizations (10 min TTL) +- `auth:installation:{token}` - Active installations (7 day TTL) +- `auth:refresh:{refreshToken}` - Refresh token mappings (7 day TTL) +- `auth:exch:{code}` - Token exchanges (10 min TTL) + +## Related Documentation + +- [Main README](../../README.md) - Complete project documentation +- [MCP Server README](../mcp-server/README.md) - How MCP server uses these tokens +- [External OAuth Overview](../README.md) - Architecture explanation diff --git a/external-oauth/auth-server/eslint.config.mjs b/external-oauth/auth-server/eslint.config.mjs new file mode 100644 index 0000000..515114c --- /dev/null +++ b/external-oauth/auth-server/eslint.config.mjs @@ -0,0 +1,19 @@ +// @ts-check + +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + linterOptions: { + reportUnusedDisableDirectives: false, + }, + rules: { + "@typescript-eslint/no-unused-vars": ["error", + { "argsIgnorePattern": "^_" } + ] + } + } +); diff --git a/external-oauth/auth-server/jest.config.js b/external-oauth/auth-server/jest.config.js new file mode 100644 index 0000000..87825b9 --- /dev/null +++ b/external-oauth/auth-server/jest.config.js @@ -0,0 +1,19 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +export default { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + extensionsToTreatAsEsm: ['.ts'], + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + useESM: true, + }, + ], + }, + testPathIgnorePatterns: ['/node_modules/', '/dist/', '/scratch/'], + injectGlobals: true, +}; diff --git a/external-oauth/auth-server/package-lock.json b/external-oauth/auth-server/package-lock.json new file mode 100644 index 0000000..19c6322 --- /dev/null +++ b/external-oauth/auth-server/package-lock.json @@ -0,0 +1,7141 @@ +{ + "name": "mcp-server-feature-reference-auth-server", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-server-feature-reference-auth-server", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@redis/client": "^1.6.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-rate-limit": "^8.0.1", + "raw-body": "^3.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/content-type": "^1.1.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + } + }, + "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.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@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.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@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.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "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.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "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-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "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-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "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/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.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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/@modelcontextprotocol/sdk": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.18.2.tgz", + "integrity": "sha512-beedclIvFcCnPrYgHsylqiYJVJ/CI47Vyc4tY8no1/Li/O8U4BTlJfy6ZwxkYwx+Mx10nrgwSVrA7VBbhh4slg==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "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", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "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": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@redis/client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", + "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "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/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/content-type": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.9.tgz", + "integrity": "sha512-Hq9IMnfekuOCsEmYl4QX2HBrT+XsfXiupfrLLY8Dcf3Puf4BkBOxSbWYTITSOQAhJoYPBez+b4MJRpIYL65z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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", + "peer": true + }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.18.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz", + "integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz", + "integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/type-utils": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.45.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz", + "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz", + "integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.45.0", + "@typescript-eslint/types": "^8.45.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz", + "integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz", + "integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz", + "integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz", + "integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz", + "integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.45.0", + "@typescript-eslint/tsconfig-utils": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz", + "integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz", + "integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0", + "peer": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz", + "integrity": "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "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.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001746", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz", + "integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "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/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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/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/dedent": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "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.228", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz", + "integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "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/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "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.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "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/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.1.0.tgz", + "integrity": "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "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-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC", + "peer": true + }, + "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": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "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/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "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/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "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/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "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-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "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/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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/lru-cache/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/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "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": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "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": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "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/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "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.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "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/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "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/router/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/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "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/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": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.4.tgz", + "integrity": "sha512-ccVcRABct5ZELCT5U0+DZwkXMCcOCLi2doHRrKy1nK/s7J7bch6TzJMsrY09WxgUUIP/ITfmcDS8D2yl63rnXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "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/typescript-eslint": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz", + "integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.45.0", + "@typescript-eslint/parser": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "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.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "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/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "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/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/external-oauth/auth-server/package.json b/external-oauth/auth-server/package.json new file mode 100644 index 0000000..94d08b9 --- /dev/null +++ b/external-oauth/auth-server/package.json @@ -0,0 +1,43 @@ +{ + "name": "mcp-server-feature-reference-external-auth-server", + "version": "0.1.0", + "description": "MCP Feature Reference Server - Standalone Authorization Server", + "type": "module", + "main": "dist/index.js", + "scripts": { + "start": "node dist/index.js", + "dev": "tsx watch --inspect src/index.ts", + "dev:break": "tsx --inspect-brk watch src/index.ts", + "build": "tsc && npm run copy-static", + "copy-static": "mkdir -p dist/static && cp -r src/static/* dist/static/", + "lint": "eslint src/", + "typecheck": "tsc --noEmit", + "test": "NODE_OPTIONS=--experimental-vm-modules jest" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@redis/client": "^1.6.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-rate-limit": "^8.0.1", + "raw-body": "^3.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/content-type": "^1.1.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + }, + "overrides": { + "@types/express": "^5.0.0", + "@types/express-serve-static-core": "^5.0.2" + } +} diff --git a/external-oauth/auth-server/src/auth/auth-core.ts b/external-oauth/auth-server/src/auth/auth-core.ts new file mode 100644 index 0000000..5feb735 --- /dev/null +++ b/external-oauth/auth-server/src/auth/auth-core.ts @@ -0,0 +1,87 @@ +import crypto from "crypto"; +import { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; + +/** + * Generates a PKCE code challenge from a verifier string. + * Uses S256 method as specified in RFC 7636. + * @param verifier The code verifier string + * @returns Base64url-encoded SHA256 hash of the verifier + */ +export function generatePKCEChallenge(verifier: string): string { + const buffer = Buffer.from(verifier); + const hash = crypto.createHash("sha256").update(buffer); + return hash.digest("base64url"); +} + +/** + * Generates a cryptographically secure random token. + * @returns 64-character hexadecimal string + */ +export function generateToken(): string { + return crypto.randomBytes(32).toString("hex"); +} + +/** + * Computes SHA256 hash of input data. + * @param data The string to hash + * @returns Hexadecimal representation of the hash + */ +export function sha256(data: string): string { + return crypto.createHash("sha256").update(data).digest("hex"); +} + +/** + * Encrypts a string using AES-256-CBC encryption. + * @param text The plaintext to encrypt + * @param key The encryption key (64 hex characters) + * @returns Encrypted string in format "iv:ciphertext" + */ +export function encryptString({ text, key }: { text: string; key: string }): string { + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(key, "hex"), iv); + let encrypted = cipher.update(text, "utf-8", "hex"); + encrypted += cipher.final("hex"); + return `${iv.toString("hex")}:${encrypted}`; +} + +/** + * Decrypts a string encrypted with encryptString. + * @param encryptedText The encrypted string in format "iv:ciphertext" + * @param key The encryption key (64 hex characters) + * @returns Decrypted plaintext + */ +export function decryptString({ + encryptedText, + key, +}: { + encryptedText: string; + key: string; +}): string { + const [ivHex, encrypted] = encryptedText.split(":"); + const iv = Buffer.from(ivHex, "hex"); + const decipher = crypto.createDecipheriv("aes-256-cbc", Buffer.from(key, "hex"), iv); + let decrypted = decipher.update(encrypted, "hex", "utf-8"); + decrypted += decipher.final("utf-8"); + return decrypted; +} + +/** + * Access token expiry time in seconds (1 hour) + */ +export const ACCESS_TOKEN_EXPIRY_SEC = 60 * 60; + +/** + * Generates a complete set of MCP OAuth tokens. + * @returns OAuth tokens with access token, refresh token, and expiry + */ +export function generateMcpTokens(): OAuthTokens { + const mcpAccessToken = generateToken(); + const mcpRefreshToken = generateToken(); + + return { + access_token: mcpAccessToken, + refresh_token: mcpRefreshToken, + expires_in: ACCESS_TOKEN_EXPIRY_SEC, + token_type: "Bearer", + }; +} \ No newline at end of file diff --git a/src/auth/provider.test.ts b/external-oauth/auth-server/src/auth/provider.test.ts similarity index 90% rename from src/auth/provider.test.ts rename to external-oauth/auth-server/src/auth/provider.test.ts index 261159b..67b6957 100644 --- a/src/auth/provider.test.ts +++ b/external-oauth/auth-server/src/auth/provider.test.ts @@ -5,7 +5,7 @@ import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } import { InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors.js"; import { MockRedisClient, setRedisClient } from "../redis.js"; import { McpInstallation, PendingAuthorization, TokenExchange } from "../types.js"; -import { EverythingAuthProvider, EverythingOAuthClientsStore } from "./provider.js"; +import { FeatureReferenceAuthProvider, FeatureReferenceOAuthClientsStore } from "./provider.js"; import * as authService from "../services/auth.js"; // Helper function to create sample client @@ -42,9 +42,9 @@ function getMockAuthValues() { expires_in: 3600, }; const mcpInstallation: McpInstallation = { - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", }, mcpTokens: { access_token: accessToken, @@ -65,14 +65,14 @@ function getMockAuthValues() { } } -describe("EverythingOAuthClientsStore", () => { - let clientsStore: EverythingOAuthClientsStore; +describe("FeatureReferenceOAuthClientsStore", () => { + let clientsStore: FeatureReferenceOAuthClientsStore; beforeEach(() => { const mockRedis = new MockRedisClient(); setRedisClient(mockRedis); jest.resetAllMocks(); - clientsStore = new EverythingOAuthClientsStore(); + clientsStore = new FeatureReferenceOAuthClientsStore(); }); describe("getClient", () => { @@ -108,8 +108,8 @@ describe("EverythingOAuthClientsStore", () => { }); }); -describe("EverythingAuthProvider", () => { - let provider: EverythingAuthProvider; +describe("FeatureReferenceAuthProvider", () => { + let provider: FeatureReferenceAuthProvider; let mockRedis: MockRedisClient; beforeEach(() => { @@ -118,7 +118,7 @@ describe("EverythingAuthProvider", () => { mockRedis = new MockRedisClient(); setRedisClient(mockRedis); - provider = new EverythingAuthProvider(); + provider = new FeatureReferenceAuthProvider(); }); describe("authorize", () => { @@ -139,7 +139,7 @@ describe("EverythingAuthProvider", () => { const sentHtml = (res.send as jest.Mock).mock.calls[0][0]; expect(sentHtml).toContain('MCP Server Authorization'); expect(sentHtml).toContain('Authorization Required'); - expect(sentHtml).toContain('fakeupstreamauth/authorize?redirect_uri=/fakeupstreamauth/callback&state='); + expect(sentHtml).toContain('mock-upstream-idp/authorize?redirect_uri=/mock-upstream-idp/callback&state='); }); }); @@ -271,9 +271,9 @@ describe("EverythingAuthProvider", () => { const refreshToken = authService.generateToken(); const mcpInstallation: McpInstallation = { - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", }, mcpTokens: { access_token: accessToken, @@ -307,9 +307,9 @@ describe("EverythingAuthProvider", () => { it("returns auth info for valid token", async () => { const accessToken = authService.generateToken(); const mcpInstallation: McpInstallation = { - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", }, mcpTokens: { access_token: accessToken, @@ -348,9 +348,9 @@ describe("EverythingAuthProvider", () => { const twoDaysAgoInSeconds = Math.floor(Date.now() / 1000) - (2 * oneDayInSeconds); const mcpInstallation: McpInstallation = { - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", }, mcpTokens: { access_token: accessToken, @@ -375,9 +375,9 @@ describe("EverythingAuthProvider", () => { const client = createTestClient(); const accessToken = authService.generateToken(); const mcpInstallation: McpInstallation = { - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", }, mcpTokens: { access_token: accessToken, diff --git a/external-oauth/auth-server/src/auth/provider.ts b/external-oauth/auth-server/src/auth/provider.ts new file mode 100644 index 0000000..22aed80 --- /dev/null +++ b/external-oauth/auth-server/src/auth/provider.ts @@ -0,0 +1,378 @@ +import { Response } from 'express'; +import { OAuthServerProvider, AuthorizationParams } from '@modelcontextprotocol/sdk/server/auth/provider.js'; +import { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js'; +import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js'; +import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; +import { + exchangeToken, + generateToken, + getClientRegistration, + readPendingAuthorization, + readMcpInstallation, + revokeMcpInstallation, + saveClientRegistration, + savePendingAuthorization, + readRefreshToken, + generateMcpTokens, + saveMcpInstallation, + saveRefreshToken, +} from '../services/auth.js'; +import { InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js'; +import { logger } from '../utils/logger.js'; + +/** + * Implementation of the OAuthRegisteredClientsStore interface using the existing client registration system + */ +export class FeatureReferenceOAuthClientsStore implements OAuthRegisteredClientsStore { + async getClient(clientId: string): Promise { + const registration = await getClientRegistration(clientId); + if (!registration) { + return undefined; + } + return registration; + } + + async registerClient(client: OAuthClientInformationFull): Promise { + await saveClientRegistration(client.client_id, client); + return client; + } +} + +/** + * Implementation of the OAuthServerProvider interface for upstream authentication + */ +export class FeatureReferenceAuthProvider implements OAuthServerProvider { + private _clientsStore: FeatureReferenceOAuthClientsStore; + + constructor() { + this._clientsStore = new FeatureReferenceOAuthClientsStore(); + } + + get clientsStore(): OAuthRegisteredClientsStore { + return this._clientsStore; + } + + async authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise { + + // Client is validated by the MCP sdk. + + // Generate authorization code + const authorizationCode = generateToken(); + + // Save the pending authorization with code challenge and state + await savePendingAuthorization(authorizationCode, { + redirectUri: params.redirectUri, + codeChallenge: params.codeChallenge, + codeChallengeMethod: 'S256', // Currently only support S256 + clientId: client.client_id, + state: params.state, + }); + + logger.debug('Saved pending authorization', { + authorizationCode: authorizationCode.substring(0, 8) + '...', + clientId: client.client_id, + state: params.state?.substring(0, 8) + '...' + }); + + // TODO: should we use a different key, other than the authorization code, to store the pending authorization? + + // You can redirect to another page, or you can send an html response directly + // res.redirect(new URL(`mock-upstream-idp/authorize?metadata=${authorizationCode}`, BASE_URI).href); + + // Set permissive CSP for styling + res.setHeader('Content-Security-Policy', [ + "default-src 'self'", + "style-src 'self' 'unsafe-inline'", + "script-src 'self' 'unsafe-inline'", + "img-src 'self' data:", + "object-src 'none'", + "frame-ancestors 'none'", + "form-action 'self'", + "base-uri 'self'" + ].join('; ')); + + res.send(` + + + + + + MCP Server Authorization + + + +
+
+ +
MCP
+
+ +

Authorization Required

+

This client wants to connect to your MCP server

+ +
+

Client Application

+
${client.client_id}
+
+ +
+

What happens next?

+

You'll be redirected to authenticate with the upstream provider. Once verified, you'll be granted access to this MCP server's resources.

+
+ + + Continue to Authentication + + +
+ Model Context Protocol (MCP) Server +
+
+ + + `); + } + + async challengeForAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string): Promise { + const pendingAuth = await readPendingAuthorization(authorizationCode); + if (!pendingAuth) { + throw new Error('Authorization code not found'); + } + + if (pendingAuth.clientId !== client.client_id) { + throw new Error('Authorization code does not match client'); + } + + return pendingAuth.codeChallenge; + } + + async exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string): Promise { + const tokenData = await exchangeToken(authorizationCode); + if (!tokenData) { + throw new Error('Invalid authorization code'); + } + + // Get the MCP installation to retrieve the full token data including refresh token + const mcpInstallation = await readMcpInstallation(tokenData.mcpAccessToken); + if (!mcpInstallation) { + throw new Error('Failed to retrieve MCP installation'); + } + + // Return the full token data including refresh token + return { + access_token: mcpInstallation.mcpTokens.access_token, + refresh_token: mcpInstallation.mcpTokens.refresh_token, + expires_in: mcpInstallation.mcpTokens.expires_in, + token_type: 'Bearer', + }; + } + + async exchangeRefreshToken(client: OAuthClientInformationFull, refreshToken: string, _scopes?: string[]): Promise { + const accessToken = await readRefreshToken(refreshToken); + + if (!accessToken) { + throw new Error('Invalid refresh token'); + } + + const mcpInstallation = await readMcpInstallation(accessToken); + + if (!mcpInstallation) { + throw new Error('Invalid refresh token'); + } + + // Check the client_id + if (mcpInstallation.clientId !== client.client_id) { + throw new Error('Invalid client'); + } + + const newTokens = generateMcpTokens(); + + if (newTokens.refresh_token) { + await saveRefreshToken(newTokens.refresh_token, newTokens.access_token); + } + + // Update the installation with the new tokens + await saveMcpInstallation(newTokens.access_token, { + ...mcpInstallation, + mcpTokens: newTokens, + issuedAt: Date.now() / 1000, + userId: mcpInstallation.userId, // Preserve the user ID + }); + + return newTokens; + } + + async verifyAccessToken(token: string): Promise { + const installation = await readMcpInstallation(token); + if (!installation) { + throw new InvalidTokenError("Invalid access token"); + } + + const expiresAt = ( + installation.mcpTokens.expires_in + ? installation.mcpTokens.expires_in + installation.issuedAt + : undefined + ); + + // This can be removed once in the SDK + // Check if the token is expired + if (!!expiresAt && expiresAt < Date.now() / 1000) { + throw new InvalidTokenError("Token has expired"); + } + + return { + token, + clientId: installation.clientId, + scopes: ['mcp'], + expiresAt, + extra: { + userId: installation.userId + } + }; + } + + async revokeToken(client: OAuthClientInformationFull, request: OAuthTokenRevocationRequest): Promise { + await revokeMcpInstallation(request.token); + } +} \ No newline at end of file diff --git a/src/config.ts b/external-oauth/auth-server/src/config.ts similarity index 50% rename from src/config.ts rename to external-oauth/auth-server/src/config.ts index cffc9c9..96ddfe7 100644 --- a/src/config.ts +++ b/external-oauth/auth-server/src/config.ts @@ -23,28 +23,11 @@ if (baseUrl.port && parseInt(baseUrl.port) !== PORT) { export const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379'; /** - * Authentication mode: - * - 'integrated': MCP server acts as its own OAuth server (default) - * - 'separate': MCP server delegates to external auth server - */ -export const AUTH_MODE = (process.env.AUTH_MODE as 'integrated' | 'separate') || 'integrated'; - -/** - * Port for the standalone auth server (only used in separate mode) - * Used when running the auth-server component + * Port for this authorization server */ export const AUTH_SERVER_PORT = parseInt(process.env.AUTH_SERVER_PORT || '3001'); /** - * URL of the external authorization server (only used when AUTH_MODE='separate') - * This is where the MCP server will redirect clients for authentication + * URL where this authorization server is hosted */ export const AUTH_SERVER_URL = process.env.AUTH_SERVER_URL || `http://localhost:${AUTH_SERVER_PORT}`; - -// Validate AUTH_SERVER configuration -if (AUTH_MODE === 'separate') { - const authUrl = new URL(AUTH_SERVER_URL); - if (authUrl.port && parseInt(authUrl.port) !== AUTH_SERVER_PORT) { - throw new Error(`Configuration error: AUTH_SERVER_URL port (${authUrl.port}) doesn't match AUTH_SERVER_PORT (${AUTH_SERVER_PORT})`); - } -} diff --git a/src/handlers/fakeauth.ts b/external-oauth/auth-server/src/handlers/mock-upstream-idp.ts similarity index 92% rename from src/handlers/fakeauth.ts rename to external-oauth/auth-server/src/handlers/mock-upstream-idp.ts index 0ccc5d3..8bf8c25 100644 --- a/src/handlers/fakeauth.ts +++ b/external-oauth/auth-server/src/handlers/mock-upstream-idp.ts @@ -3,13 +3,10 @@ import { generateMcpTokens, readPendingAuthorization, saveMcpInstallation, saveR import { McpInstallation } from "../types.js"; import { logger } from "../utils/logger.js"; -// this module has a fake upstream auth server that returns a fake auth code, it also allows you to authorize or fail -// authorization, to test the different flows - -// TODO: make an implementation of this using the ProxyAuthProvider - -// This is mocking an upstream auth server. This wouldn't normally be in the same server as the MCP auth server -export async function handleFakeAuthorize(req: Request, res: Response) { +// Mock upstream identity provider - simulates Google OAuth, corporate SAML, or other external identity providers +// that OAuth servers can delegate user authentication to (configurable in Auth0/Okta). +// In production, the OAuth server would redirect to actual external IDPs. +export async function handleMockUpstreamAuthorize(req: Request, res: Response) { // get the redirect_uri and state from the query params const { redirect_uri, state } = req.query; @@ -244,7 +241,7 @@ export async function handleFakeAuthorize(req: Request, res: Response) { const baseUrl = redirectUri.startsWith('http') ? redirectUri : window.location.origin + redirectUri; const url = new URL(baseUrl); url.searchParams.set('state', '${state}'); - url.searchParams.set('code', 'fakecode'); + url.searchParams.set('code', 'mock-auth-code'); url.searchParams.set('userId', userId); window.location.href = url.toString(); } @@ -259,7 +256,7 @@ export async function handleFakeAuthorize(req: Request, res: Response) { // This is the callback URL that the upstream auth server will redirect to after authorization -export async function handleFakeAuthorizeRedirect(req: Request, res: Response) { +export async function handleMockUpstreamCallback(req: Request, res: Response) { const { // The state returned from the upstream auth server is actually the authorization code state: mcpAuthorizationCode, @@ -267,7 +264,7 @@ export async function handleFakeAuthorizeRedirect(req: Request, res: Response) { userId, // User ID from the authorization flow } = req.query; - logger.debug('Fake auth redirect received', { + logger.debug('Mock upstream IDP callback received', { mcpAuthorizationCode: typeof mcpAuthorizationCode === 'string' ? mcpAuthorizationCode.substring(0, 8) + '...' : mcpAuthorizationCode, upstreamAuthorizationCode: typeof upstreamAuthorizationCode === 'string' ? upstreamAuthorizationCode.substring(0, 8) + '...' : upstreamAuthorizationCode, userId @@ -275,7 +272,7 @@ export async function handleFakeAuthorizeRedirect(req: Request, res: Response) { // This is where you'd exchange the upstreamAuthorizationCode for access/refresh tokens // In this case, we're just going to fake it - const upstreamTokens = await fakeUpstreamTokenExchange(upstreamAuthorizationCode as string); + const upstreamTokens = await mockUpstreamTokenExchange(upstreamAuthorizationCode as string); // Validate that it's a string if (typeof mcpAuthorizationCode !== "string") { @@ -300,9 +297,9 @@ export async function handleFakeAuthorizeRedirect(req: Request, res: Response) { }); const mcpInstallation: McpInstallation = { - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: upstreamTokens.access_token, - fakeRefreshTokenForDemonstration: upstreamTokens.refresh_token, + mockUpstreamInstallation: { + mockUpstreamAccessToken: upstreamTokens.access_token, + mockUpstreamRefreshToken: upstreamTokens.refresh_token, }, mcpTokens, clientId: pendingAuth.clientId, @@ -343,7 +340,7 @@ export async function handleFakeAuthorizeRedirect(req: Request, res: Response) { logger.debug('Redirect completed'); }; -function fakeUpstreamTokenExchange( +function mockUpstreamTokenExchange( authorizationCode: string, ): Promise<{ access_token: string; refresh_token: string }> { // just return the authorization code with a suffix diff --git a/auth-server/index.ts b/external-oauth/auth-server/src/index.ts similarity index 85% rename from auth-server/index.ts rename to external-oauth/auth-server/src/index.ts index 8db58a0..25a841e 100644 --- a/auth-server/index.ts +++ b/external-oauth/auth-server/src/index.ts @@ -2,11 +2,11 @@ import express from 'express'; import cors from 'cors'; import rateLimit from 'express-rate-limit'; import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js'; -import { EverythingAuthProvider } from '../src/auth/provider.js'; -import { handleFakeAuthorize, handleFakeAuthorizeRedirect } from '../src/handlers/fakeauth.js'; -import { redisClient } from '../src/redis.js'; -import { logger } from '../src/utils/logger.js'; -import { AUTH_SERVER_PORT, AUTH_SERVER_URL, BASE_URI } from '../src/config.js'; +import { FeatureReferenceAuthProvider } from './auth/provider.js'; +import { handleMockUpstreamAuthorize, handleMockUpstreamCallback } from './handlers/mock-upstream-idp.js'; +import { redisClient } from './redis.js'; +import { logger } from './utils/logger.js'; +import { AUTH_SERVER_PORT, AUTH_SERVER_URL, BASE_URI } from './config.js'; const app = express(); @@ -46,7 +46,7 @@ app.get('/health', (req, res) => { }); // Create auth provider instance for reuse -const authProvider = new EverythingAuthProvider(); +const authProvider = new FeatureReferenceAuthProvider(); // OAuth endpoints via SDK's mcpAuthRouter app.use(mcpAuthRouter({ @@ -67,7 +67,7 @@ const introspectRateLimit = rateLimit({ message: { error: 'too_many_requests', error_description: 'Token introspection rate limit exceeded' } }); -const fakeAuthRateLimit = rateLimit({ +const mockUpstreamIdpRateLimit = rateLimit({ windowMs: 60 * 1000, // 1 minute limit: 20, // 20 auth attempts per minute message: { error: 'too_many_requests', error_description: 'Authentication rate limit exceeded' } @@ -116,8 +116,8 @@ app.post('/introspect', introspectRateLimit, express.urlencoded({ extended: fals }); // Fake upstream auth endpoints (for user authentication simulation) -app.get('/fakeupstreamauth/authorize', fakeAuthRateLimit, cors(), handleFakeAuthorize); -app.get('/fakeupstreamauth/callback', fakeAuthRateLimit, cors(), handleFakeAuthorizeRedirect); +app.get('/mock-upstream-idp/authorize', mockUpstreamIdpRateLimit, cors(), handleMockUpstreamAuthorize); +app.get('/mock-upstream-idp/callback', mockUpstreamIdpRateLimit, cors(), handleMockUpstreamCallback); // Static assets (for auth page styling) import path from 'path'; @@ -127,8 +127,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); app.get('/mcp-logo.png', staticFileRateLimit, (req, res) => { - // Serve from the main server's static directory - const logoPath = path.join(__dirname, '../src/static/mcp.png'); + const logoPath = path.join(__dirname, 'static/mcp.png'); res.sendFile(logoPath); }); @@ -159,8 +158,8 @@ app.listen(AUTH_SERVER_PORT, () => { console.log(` curl ${AUTH_SERVER_URL}/health`); console.log(` curl ${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`); console.log(''); - console.log('💡 To test separate mode:'); + console.log('💡 To test with MCP server:'); console.log(' 1. Keep this server running'); - console.log(' 2. In another terminal: AUTH_MODE=separate npm run dev'); + console.log(' 2. In another terminal: cd ../mcp-server && npm run dev'); console.log(' 3. Connect Inspector to http://localhost:3232/mcp'); }); \ No newline at end of file diff --git a/external-oauth/auth-server/src/redis.ts b/external-oauth/auth-server/src/redis.ts new file mode 100644 index 0000000..3fc1a47 --- /dev/null +++ b/external-oauth/auth-server/src/redis.ts @@ -0,0 +1,289 @@ +import { createClient, SetOptions } from "@redis/client"; +import { logger } from "./utils/logger.js"; +import { REDIS_URL } from "./config.js"; + +/** + * Describes the Redis primitives we use in this application, to be able to mock + * them in tests (so we don't need to actually hit Redis). + */ +export interface RedisClient { + get(key: string): Promise; + set(key: string, value: string, options?: SetOptions): Promise; + getDel(key: string): Promise; + del(key: string): Promise; + expire(key: string, seconds: number): Promise; + lpush(key: string, ...values: string[]): Promise; + lrange(key: string, start: number, stop: number): Promise; + connect(): Promise; + on(event: string, callback: (error: Error) => void): void; + options?: { url: string }; + exists(key: string): Promise; + numsub(key: string): Promise; + + /** + * Creates a pub/sub subscription. Returns a cleanup function to unsubscribe. + * Handles Redis client duplication and error handling internally. + */ + createSubscription( + channel: string, + onMessage: (message: string) => void, + onError: (error: Error) => void, + ): Promise<() => Promise>; + + /** + * Publishes a message to a channel. + */ + publish(channel: string, message: string): Promise; +} + +export class RedisClientImpl implements RedisClient { + private redis = createClient({ + url: REDIS_URL, + password: process.env.REDIS_PASSWORD, + socket: { + tls: process.env.REDIS_TLS === "1", + ca: process.env.REDIS_TLS_CA, + } + }); + + constructor() { + this.redis.on("error", (error) => + logger.error("Redis client error", error as Error), + ); + } + + async numsub(key: string): Promise { + const subs = await this.redis.pubSubNumSub(key); + return subs[key] || 0; + } + + async get(key: string): Promise { + return await this.redis.get(key); + } + + async getDel(key: string): Promise { + return await this.redis.getDel(key); + } + + async set(key: string, value: string, options?: SetOptions): Promise { + return await this.redis.set( + key, + value, + options, + ); + } + + async del(key: string): Promise { + return await this.redis.del(key); + } + + async expire(key: string, seconds: number): Promise { + return await this.redis.expire(key, seconds); + } + + async lpush(key: string, ...values: string[]): Promise { + return await this.redis.lPush(key, values); + } + + async lrange(key: string, start: number, stop: number): Promise { + return await this.redis.lRange(key, start, stop); + } + + async connect(): Promise { + await this.redis.connect(); + } + + on(event: string, callback: (error: Error) => void): void { + this.redis.on(event, callback); + } + + get options() { + return { url: REDIS_URL }; + } + + async createSubscription( + channel: string, + onMessage: (message: string) => void, + onError: (error: Error) => void, + ): Promise<() => Promise> { + const subscriber = this.redis.duplicate(); + subscriber.on("error", (error) => { + onError(error); + }); + + await subscriber.connect(); + + await subscriber.subscribe(channel, (message) => { + onMessage(message); + }); + + return async () => { + await subscriber.disconnect(); + }; + } + + async publish(channel: string, message: string): Promise { + await this.redis.publish(channel, message); + } + + async exists(key: string): Promise { + const result = await this.redis.exists(key); + return result > 0; + } +} + +// Export a mutable reference that can be swapped in tests +export let redisClient: RedisClient = new RedisClientImpl(); + +// Function to replace the Redis client (used in tests) +export function setRedisClient(client: RedisClient) { + redisClient = client; +} + +export class MockRedisClient implements RedisClient { + options = { url: "redis://localhost:6379" }; + private store = new Map(); + private lists = new Map(); + public subscribers = new Map void)[]>(); // Public for testing access + private errorCallbacks = new Map void)[]>(); + + async get(key: string): Promise { + return this.store.get(key) ?? null; + } + + async getDel(key: string): Promise { + const value = this.store.get(key) ?? null; + this.store.delete(key); + return value; + } + + async set(key: string, value: string, options?: SetOptions): Promise { + let oldValue: string | null = null; + if (options?.GET) { + oldValue = this.store.get(key) ?? null; + } + this.store.set(key, value); + return oldValue; + } + + async del(key: string): Promise { + let deleted = 0; + if (this.store.has(key)) { + this.store.delete(key); + deleted++; + } + if (this.lists.has(key)) { + this.lists.delete(key); + deleted++; + } + return deleted; + } + + async expire(key: string, _seconds: number): Promise { + // Mock implementation - just return true if key exists + return this.store.has(key) || this.lists.has(key); + } + + async lpush(key: string, ...values: string[]): Promise { + const list = this.lists.get(key) || []; + list.unshift(...values); + this.lists.set(key, list); + return list.length; + } + + async lrange(key: string, start: number, stop: number): Promise { + const list = this.lists.get(key) || []; + if (stop === -1) { + return list.slice(start); + } + return list.slice(start, stop + 1); + } + + async connect(): Promise { + // No-op in mock + } + + on(event: string, callback: (error: Error) => void): void { + if (event === "error") { + const callbacks = this.errorCallbacks.get("global") ?? []; + callbacks.push(callback); + this.errorCallbacks.set("global", callbacks); + } + } + + async createSubscription( + channel: string, + onMessage: (message: string) => void, + onError: (error: Error) => void, + ): Promise<() => Promise> { + const callbacks = this.subscribers.get(channel) ?? []; + callbacks.push(onMessage); + this.subscribers.set(channel, callbacks); + + const errorCallbacks = this.errorCallbacks.get(channel) ?? []; + errorCallbacks.push(onError); + this.errorCallbacks.set(channel, errorCallbacks); + + return async () => { + const callbacks = this.subscribers.get(channel) ?? []; + const index = callbacks.indexOf(onMessage); + if (index !== -1) { + callbacks.splice(index, 1); + } + this.subscribers.set(channel, callbacks); + + if (onError) { + const errorCallbacks = this.errorCallbacks.get(channel) ?? []; + const errorIndex = errorCallbacks.indexOf(onError); + if (errorIndex !== -1) { + errorCallbacks.splice(errorIndex, 1); + } + this.errorCallbacks.set(channel, errorCallbacks); + } + }; + } + + async publish(channel: string, message: string): Promise { + const callbacks = this.subscribers.get(channel) ?? []; + for (const callback of callbacks) { + try { + callback(message); + } catch (error) { + const errorCallbacks = this.errorCallbacks.get(channel) ?? []; + for (const errorCallback of errorCallbacks) { + errorCallback(error as Error); + } + } + } + } + + async exists(key: string): Promise { + return this.store.has(key) || this.lists.has(key); + } + + async numsub(key: string): Promise { + return (this.subscribers.get(key) || []).length; + } + + clear() { + this.store.clear(); + this.lists.clear(); + this.subscribers.clear(); + this.errorCallbacks.clear(); + } + + // Helper method for tests to simulate Redis errors + simulateError(error: Error, channel?: string): void { + if (channel) { + const callbacks = this.errorCallbacks.get(channel) ?? []; + for (const callback of callbacks) { + callback(error); + } + } else { + const callbacks = this.errorCallbacks.get("global") ?? []; + for (const callback of callbacks) { + callback(error); + } + } + } +} diff --git a/external-oauth/auth-server/src/services/auth.test.ts b/external-oauth/auth-server/src/services/auth.test.ts new file mode 100644 index 0000000..1695cf1 --- /dev/null +++ b/external-oauth/auth-server/src/services/auth.test.ts @@ -0,0 +1,292 @@ +import { jest } from '@jest/globals'; +import crypto from "crypto"; +import { + decryptString, + exchangeToken, + generateMcpTokens, + generatePKCEChallenge, + generateToken, + getClientRegistration, + readMcpInstallation, + readPendingAuthorization, + readRefreshToken, + revokeMcpInstallation, + saveClientRegistration, + saveMcpInstallation, + savePendingAuthorization, + saveRefreshToken, + saveTokenExchange, +} from "./auth.js"; +import { MockRedisClient, setRedisClient } from "../redis.js"; +import { McpInstallation, PendingAuthorization, TokenExchange } from "../types.js"; +import { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js"; + + +describe("auth utils", () => { + let mockRedis: MockRedisClient; + + beforeEach(() => { + mockRedis = new MockRedisClient(); + setRedisClient(mockRedis); + + jest.resetAllMocks(); + }); + + describe("generateToken", () => { + it("generates a 64-character hex string", () => { + const token = generateToken(); + expect(token).toMatch(/^[0-9a-f]{64}$/); + }); + + it("generates unique tokens", () => { + const token1 = generateToken(); + const token2 = generateToken(); + expect(token1).not.toBe(token2); + }); + }); + + describe("generateMcpTokens", () => { + it("generates valid token structure", () => { + const tokens = generateMcpTokens(); + + expect(tokens.access_token).toBeDefined(); + expect(tokens.access_token).toMatch(/^[0-9a-f]{64}$/); + + expect(tokens.refresh_token).toBeDefined(); + expect(tokens.refresh_token).toMatch(/^[0-9a-f]{64}$/); + + expect(tokens.token_type).toBe("Bearer"); + expect(tokens.expires_in).toBeDefined(); + }); + + it("generates unique tokens", () => { + const tokens1 = generateMcpTokens(); + const tokens2 = generateMcpTokens(); + + expect(tokens1.access_token).not.toBe(tokens2.access_token); + expect(tokens1.refresh_token).not.toBe(tokens2.refresh_token); + }); + }); + + describe("client registration", () => { + it("saves and retrieves client registration", async () => { + const clientId = "test-client-id"; + const registration: OAuthClientInformationFull = { + client_id: clientId, + client_name: "Test Client", + client_uri: "https://example.com", + redirect_uris: ["https://example.com/callback"] + }; + + await saveClientRegistration(clientId, registration); + const retrieved = await getClientRegistration(clientId); + + expect(retrieved).toEqual(registration); + }); + + it("returns undefined for non-existent client", async () => { + const result = await getClientRegistration("non-existent"); + expect(result).toBeUndefined(); + }); + }); + + describe("pending authorization", () => { + it("saves and retrieves pending authorization", async () => { + const authCode = generateToken(); + const pendingAuth: PendingAuthorization = { + redirectUri: "https://example.com/callback", + codeChallenge: "test-challenge", + codeChallengeMethod: "S256", + clientId: "test-client-id" + }; + + await savePendingAuthorization(authCode, pendingAuth); + const retrieved = await readPendingAuthorization(authCode); + + expect(retrieved).toEqual(pendingAuth); + }); + + it("returns undefined for non-existent code", async () => { + const result = await readPendingAuthorization("non-existent"); + expect(result).toBeUndefined(); + }); + }); + + describe("token exchange", () => { + it("saves and retrieves token exchange data", async () => { + const authCode = generateToken(); + const tokenExchange: TokenExchange = { + mcpAccessToken: generateToken(), + alreadyUsed: false + }; + + // For this test, we'll directly manipulate Redis to check the token + // instead of using exchangeToken which changes the value + await saveTokenExchange(authCode, tokenExchange); + + // Get the key used by saveTokenExchange (now with auth: prefix) + const key = "auth:exch:" + crypto.createHash("sha256").update(authCode).digest("hex"); + + // Get the encrypted data + const encryptedData = await mockRedis.get(key); + expect(encryptedData).not.toBeNull(); + + // Decrypt it manually to verify + const decoded = JSON.parse(decryptString({ + encryptedText: encryptedData!, + key: authCode + })); + + expect(decoded).toEqual(tokenExchange); + }); + + it("prevents duplicate token exchange", async () => { + const authCode = generateToken(); + const tokenExchange: TokenExchange = { + mcpAccessToken: generateToken(), + alreadyUsed: false + }; + + await saveTokenExchange(authCode, tokenExchange); + + // First exchange succeeds + const first = await exchangeToken(authCode); + expect(first).toBeDefined(); + + // Mock console.error to suppress expected error message + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + // Second exchange throws + await expect(exchangeToken(authCode)).rejects.toThrow( + "Duplicate use of authorization code detected" + ); + + // Restore console.error + consoleErrorSpy.mockRestore(); + }); + + it("returns undefined for non-existent code", async () => { + const result = await exchangeToken("non-existent"); + expect(result).toBeUndefined(); + }); + }); + + describe("MCP installation", () => { + it("returns undefined for missing tokens", async () => { + const accessToken = generateToken(); + const result = await readMcpInstallation(accessToken); + expect(result).toBeUndefined(); + }); + + it("saves and retrieves installation data", async () => { + const accessToken = generateToken(); + + const mcpInstallation: McpInstallation = { + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", + }, + mcpTokens: { + access_token: accessToken, + token_type: "Bearer", + expires_in: 3600, + }, + clientId: "client-id", + issuedAt: Date.now() / 1000, + userId: "test-user-id", + } + + await saveMcpInstallation(accessToken, mcpInstallation); + + const result = await readMcpInstallation(accessToken); + expect(result).toEqual(mcpInstallation); + }); + }); + + describe("refresh token", () => { + it("saves and retrieves refresh token mapping", async () => { + const refreshToken = generateToken(); + const accessToken = generateToken(); + + await saveRefreshToken(refreshToken, accessToken); + const retrieved = await readRefreshToken(refreshToken); + + expect(retrieved).toBe(accessToken); + }); + + it("returns undefined for non-existent refresh token", async () => { + const result = await readRefreshToken("non-existent"); + expect(result).toBeUndefined(); + }); + }); + + describe("revokeMcpInstallation", () => { + it("revokes token for valid installation", async () => { + // For this test, we'll simply test if the WebClient is called with our mocked token + + // Create mock installation + const accessToken = generateToken(); + + // Save it to Redis with actual function + await saveMcpInstallation(accessToken, { + mockUpstreamInstallation: { + mockUpstreamAccessToken: "fake-upstream-access-token", + mockUpstreamRefreshToken: "fake-upstream-refresh-token", + }, + mcpTokens: { + access_token: accessToken, + token_type: "Bearer", + expires_in: 3600, + }, + clientId: "client-id", + issuedAt: Date.now() / 1000, + userId: "test-user-id", + }); + + const getDel = jest.spyOn(mockRedis, 'getDel').mockImplementationOnce(() => { + // Need to return encrypted data for successful decryption in the revoke function + // Create encrypted data using our access token + const mcpInstallation = { + mcpTokens: { + access_token: accessToken, + token_type: "Bearer", + expires_in: 3600, + }, + clientId: "client-id", + issuedAt: Date.now() / 1000, + userId: "test-user-id", + }; + const value = JSON.stringify(mcpInstallation); + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(accessToken, "hex"), iv); + let encrypted = cipher.update(value, "utf-8", "hex"); + encrypted += cipher.final("hex"); + return Promise.resolve(`${iv.toString("hex")}:${encrypted}`); + }); + + await revokeMcpInstallation(accessToken); + + // Should have called getDel with the correct key (now auth:installation:) + expect(getDel).toHaveBeenCalledWith(expect.stringContaining("auth:installation:")); + + }); + + it("handles non-existent installation without error", async () => { + await expect(revokeMcpInstallation("non-existent")).resolves.not.toThrow(); + }); + }); + + describe("generatePKCEChallenge", () => { + it("generates base64url-encoded SHA256 hash", () => { + const verifier = "test_verifier"; + const challenge = generatePKCEChallenge(verifier); + expect(challenge).toBe("0Ku4rR8EgR1w3HyHLBCxVLtPsAAks5HOlpmTEt0XhVA"); + }); + + it("generates different challenges for different verifiers", () => { + const challenge1 = generatePKCEChallenge("verifier1"); + const challenge2 = generatePKCEChallenge("verifier2"); + expect(challenge1).not.toBe(challenge2); + }); + }); +}); \ No newline at end of file diff --git a/external-oauth/auth-server/src/services/auth.ts b/external-oauth/auth-server/src/services/auth.ts new file mode 100644 index 0000000..5d9b4e3 --- /dev/null +++ b/external-oauth/auth-server/src/services/auth.ts @@ -0,0 +1,86 @@ +import { redisClient } from "../redis.js"; +import { McpInstallation, PendingAuthorization, TokenExchange } from "../types.js"; +import { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js"; + +// Re-export from auth-core module +export { + generatePKCEChallenge, + generateToken, + decryptString, + generateMcpTokens +} from "../auth/auth-core.js"; + +import * as sharedRedisAuth from "./redis-auth.js"; + +// Wrapper functions that pass redisClient to shared module functions + +export async function saveClientRegistration( + clientId: string, + registration: OAuthClientInformationFull, +) { + return sharedRedisAuth.saveClientRegistration(redisClient, clientId, registration); +} + +export async function getClientRegistration( + clientId: string, +): Promise { + return sharedRedisAuth.getClientRegistration(redisClient, clientId); +} + +export async function savePendingAuthorization( + authorizationCode: string, + pendingAuthorization: PendingAuthorization, +) { + return sharedRedisAuth.savePendingAuthorization(redisClient, authorizationCode, pendingAuthorization); +} + +export async function readPendingAuthorization( + authorizationCode: string, +): Promise { + return sharedRedisAuth.readPendingAuthorization(redisClient, authorizationCode); +} + +export async function saveMcpInstallation( + mcpAccessToken: string, + installation: McpInstallation, +) { + return sharedRedisAuth.saveMcpInstallation(redisClient, mcpAccessToken, installation); +} + +export async function readMcpInstallation( + mcpAccessToken: string, +): Promise { + return sharedRedisAuth.readMcpInstallation(redisClient, mcpAccessToken); +} + +export async function saveRefreshToken( + refreshToken: string, + mcpAccessToken: string, +) { + return sharedRedisAuth.saveRefreshToken(redisClient, refreshToken, mcpAccessToken); +} + +export async function readRefreshToken( + refreshToken: string, +): Promise { + return sharedRedisAuth.readRefreshToken(redisClient, refreshToken); +} + +export async function revokeMcpInstallation( + mcpAccessToken: string, +): Promise { + return sharedRedisAuth.revokeMcpInstallation(redisClient, mcpAccessToken); +} + +export async function saveTokenExchange( + authorizationCode: string, + tokenExchange: TokenExchange, +) { + return sharedRedisAuth.saveTokenExchange(redisClient, authorizationCode, tokenExchange); +} + +export async function exchangeToken( + authorizationCode: string, +): Promise { + return sharedRedisAuth.exchangeToken(redisClient, authorizationCode); +} diff --git a/external-oauth/auth-server/src/services/redis-auth.ts b/external-oauth/auth-server/src/services/redis-auth.ts new file mode 100644 index 0000000..7886bbc --- /dev/null +++ b/external-oauth/auth-server/src/services/redis-auth.ts @@ -0,0 +1,285 @@ +import { SetOptions } from "@redis/client"; +import { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js"; +import { RedisClient } from "../redis.js"; +import { McpInstallation, PendingAuthorization, TokenExchange } from "../types.js"; +import { sha256, encryptString, decryptString } from "../auth/auth-core.js"; +import { logger } from "../utils/logger.js"; + +/** + * Redis key prefixes for different data types + * All auth-related keys use "auth:" prefix to avoid collision with MCP session keys + */ +export const REDIS_KEY_PREFIXES = { + CLIENT_REGISTRATION: "auth:client:", + PENDING_AUTHORIZATION: "auth:pending:", + MCP_AUTHORIZATION: "auth:installation:", // Changed from "mcp:" to avoid collision + TOKEN_EXCHANGE: "auth:exch:", + REFRESH_TOKEN: "auth:refresh:", +} as const; + +/** + * Redis key expiry times in seconds + */ +export const REDIS_EXPIRY_TIMES = { + CLIENT_REGISTRATION: 30 * 24 * 60 * 60, // 30 days - client app credentials + PENDING_AUTHORIZATION: 10 * 60, // 10 minutes - authorization code -> PendingAuthorization + TOKEN_EXCHANGE: 10 * 60, // 10 minutes - authorization code -> MCP access token + UPSTREAM_INSTALLATION: 7 * 24 * 60 * 60, // 7 days - MCP access token -> UpstreamInstallation + REFRESH_TOKEN: 7 * 24 * 60 * 60, // 7 days - MCP refresh token -> access token +} as const; + +/** + * Saves encrypted data to Redis with optional expiry. + */ +async function saveEncrypted( + redisClient: RedisClient, + { + prefix, + key, + data, + options, + }: { + prefix: string; + key: string; + data: T; + options?: SetOptions; + } +): Promise { + const value = encryptString({ + text: JSON.stringify(data), + key: key, + }); + + return await redisClient.set(prefix + sha256(key), value, options); +} + +/** + * Reads and decrypts data from Redis. + */ +async function readEncrypted( + redisClient: RedisClient, + { + prefix, + key, + del = false, + }: { + prefix: string; + key: string; + del?: boolean; + } +): Promise { + const data = del + ? await redisClient.getDel(prefix + sha256(key)) + : await redisClient.get(prefix + sha256(key)); + + if (!data) { + return undefined; + } + + const decoded = decryptString({ + encryptedText: data, + key: key, + }); + + return JSON.parse(decoded); +} + +/** + * Saves a client registration to Redis. + */ +export async function saveClientRegistration( + redisClient: RedisClient, + clientId: string, + registration: OAuthClientInformationFull +): Promise { + await redisClient.set( + REDIS_KEY_PREFIXES.CLIENT_REGISTRATION + clientId, + JSON.stringify(registration), + { EX: REDIS_EXPIRY_TIMES.CLIENT_REGISTRATION } + ); +} + +/** + * Retrieves a client registration from Redis. + */ +export async function getClientRegistration( + redisClient: RedisClient, + clientId: string +): Promise { + const data = await redisClient.get(REDIS_KEY_PREFIXES.CLIENT_REGISTRATION + clientId); + if (!data) { + return undefined; + } + return JSON.parse(data); +} + +/** + * Saves a pending authorization to Redis. + */ +export async function savePendingAuthorization( + redisClient: RedisClient, + authorizationCode: string, + pendingAuthorization: PendingAuthorization +): Promise { + await saveEncrypted(redisClient, { + prefix: REDIS_KEY_PREFIXES.PENDING_AUTHORIZATION, + key: authorizationCode, + data: pendingAuthorization, + options: { EX: REDIS_EXPIRY_TIMES.PENDING_AUTHORIZATION }, + }); +} + +/** + * Reads a pending authorization from Redis. + */ +export async function readPendingAuthorization( + redisClient: RedisClient, + authorizationCode: string +): Promise { + return readEncrypted(redisClient, { + prefix: REDIS_KEY_PREFIXES.PENDING_AUTHORIZATION, + key: authorizationCode, + }); +} + +/** + * Saves an MCP installation to Redis. + */ +export async function saveMcpInstallation( + redisClient: RedisClient, + mcpAccessToken: string, + installation: McpInstallation +): Promise { + await saveEncrypted(redisClient, { + prefix: REDIS_KEY_PREFIXES.MCP_AUTHORIZATION, + key: mcpAccessToken, + data: installation, + options: { EX: REDIS_EXPIRY_TIMES.UPSTREAM_INSTALLATION }, + }); +} + +/** + * Reads an MCP installation from Redis. + */ +export async function readMcpInstallation( + redisClient: RedisClient, + mcpAccessToken: string +): Promise { + return readEncrypted(redisClient, { + prefix: REDIS_KEY_PREFIXES.MCP_AUTHORIZATION, + key: mcpAccessToken, + }); +} + +/** + * Links a refresh token to an MCP access token. + */ +export async function saveRefreshToken( + redisClient: RedisClient, + refreshToken: string, + mcpAccessToken: string +): Promise { + await saveEncrypted(redisClient, { + prefix: REDIS_KEY_PREFIXES.REFRESH_TOKEN, + key: refreshToken, + data: mcpAccessToken, + options: { EX: REDIS_EXPIRY_TIMES.REFRESH_TOKEN }, + }); +} + +/** + * Reads the access token associated with a refresh token. + */ +export async function readRefreshToken( + redisClient: RedisClient, + refreshToken: string +): Promise { + return readEncrypted(redisClient, { + prefix: REDIS_KEY_PREFIXES.REFRESH_TOKEN, + key: refreshToken, + }); +} + +/** + * Revokes an MCP installation. + */ +export async function revokeMcpInstallation( + redisClient: RedisClient, + mcpAccessToken: string +): Promise { + const installation = await readEncrypted(redisClient, { + prefix: REDIS_KEY_PREFIXES.MCP_AUTHORIZATION, + key: mcpAccessToken, + del: true, + }); + + if (!installation) { + return; + } + // In production, would revoke upstream tokens here +} + +/** + * Saves a token exchange record. + */ +export async function saveTokenExchange( + redisClient: RedisClient, + authorizationCode: string, + tokenExchange: TokenExchange +): Promise { + await saveEncrypted(redisClient, { + prefix: REDIS_KEY_PREFIXES.TOKEN_EXCHANGE, + key: authorizationCode, + data: tokenExchange, + options: { EX: REDIS_EXPIRY_TIMES.TOKEN_EXCHANGE }, + }); +} + +/** + * Exchanges a temporary authorization code for an MCP access token. + * Will only succeed the first time to prevent replay attacks. + */ +export async function exchangeToken( + redisClient: RedisClient, + authorizationCode: string +): Promise { + const data = await redisClient.get( + REDIS_KEY_PREFIXES.TOKEN_EXCHANGE + sha256(authorizationCode) + ); + + if (!data) { + return undefined; + } + + const decoded = decryptString({ + encryptedText: data, + key: authorizationCode, + }); + + const tokenExchange: TokenExchange = JSON.parse(decoded); + if (tokenExchange.alreadyUsed) { + logger.error('Duplicate use of authorization code detected; revoking tokens', undefined, { + authorizationCode: authorizationCode.substring(0, 8) + '...' + }); + await revokeMcpInstallation(redisClient, tokenExchange.mcpAccessToken); + throw new Error("Duplicate use of authorization code detected; tokens revoked"); + } + + const rereadData = await saveEncrypted(redisClient, { + prefix: REDIS_KEY_PREFIXES.TOKEN_EXCHANGE, + key: authorizationCode, + data: { ...tokenExchange, alreadyUsed: true }, + options: { KEEPTTL: true, GET: true }, + }); + + if (rereadData !== data) { + // Data concurrently changed while we were updating it. This necessarily means a duplicate use. + logger.error('Duplicate use of authorization code detected (concurrent update); revoking tokens', undefined, { + authorizationCode: authorizationCode.substring(0, 8) + '...' + }); + await revokeMcpInstallation(redisClient, tokenExchange.mcpAccessToken); + throw new Error("Duplicate use of authorization code detected; tokens revoked"); + } + + return tokenExchange; +} \ No newline at end of file diff --git a/external-oauth/auth-server/src/static/index.html b/external-oauth/auth-server/src/static/index.html new file mode 100644 index 0000000..2e3eeb9 --- /dev/null +++ b/external-oauth/auth-server/src/static/index.html @@ -0,0 +1,97 @@ + + + + + + MCP Example Server + + + +
+
+ +

MCP Example Server

+
+ +

+ A comprehensive reference implementation of the Model Context Protocol (MCP) server + demonstrating all protocol features with full authentication support and horizontal scalability. +

+ +
+
+

Complete MCP Support

+

All MCP features including tools, resources, prompts, sampling, completions, and logging with full protocol compliance.

+
+
+

Multiple Transports

+

Streamable HTTP (SHTTP) and Server-Sent Events (SSE) transports for flexible client integration.

+
+
+

OAuth 2.0 Authentication

+

Complete OAuth flow with PKCE support and a built-in fake provider for testing and development.

+
+
+

Horizontal Scalability

+

Redis-backed session management enables multi-instance deployments with automatic load distribution.

+
+
+

7 Demo Tools

+

Echo, add, long-running operations, LLM sampling, image handling, annotations, and resource references.

+
+
+

100+ Resources

+

Example resources with pagination, templates, subscriptions, and real-time update notifications.

+
+
+ +
+

API Endpoints

+
+
+ POST + /mcp - Initialize sessions or send messages (Streamable HTTP) +
+
+ GET + /mcp - Establish SSE streams (Streamable HTTP) +
+
+ DELETE + /mcp - Terminate sessions (Streamable HTTP) +
+
+ GET + /sse - Legacy SSE transport endpoint +
+
+ POST + /message - Legacy message endpoint for SSE transport +
+
+
+ + +
+ + + + \ No newline at end of file diff --git a/external-oauth/auth-server/src/static/mcp.png b/external-oauth/auth-server/src/static/mcp.png new file mode 100644 index 0000000000000000000000000000000000000000..86c52662d890dd8ebf115d6c228f36748c77176e GIT binary patch literal 40652 zcmZs@cU+TK8#kT-iEOP3%2Yv6K~Nd80$P+I0t!S>8L=pcvV^cfLTRN>6;Y-#LRBCV z6i~_v>u#Mz0Zl+qR@4FwTLBpY`CTXWdEWQ)etv)S)Ar7N&biJtzt_0=&k;u(8A(M+ z91bUAXM4aIhZBSU6vM3|!M_qzw3Fap5+Sz7!*DpMwdfx_E+=mT4z~$scVNG3RNh!$ zO$y#N4}7hX7uG&J#*eeqZ(f+7#15@Vy{2Jz zPPt-Lh0N3;{zoh|(SD=l9KM;@gl`~6;iqoU<2CHkH0*NpFhy(v+mHQ)^$FVQFVqm8 z;mHvRHJLoa4D>?Dmxu&1zYp7u%?g5I#LT7h$Z-I@Y7N58<%xA%vVACI)7w1)F=JZb~y6)oM5>Kxxki3xrV%(Xq|>3p_H;E~slhIa;Huaapox~@{cot}eSWHFGGU>;*qD9lI+-qZdysP8v(=T|} znEkz7uyhC=G<4eoPgcfxFefw(&-z8UN$N7&enLbksRrk?7MsQ9eK@1(D=m+(xwzGv(C#jkox(ACwwA+({reUSXh}0S zuEWcP`|;IyN88A7t~sZIx-stEX+qyEsyE5%^q6csFT;@(Q5P#Q@?@8rlO)xZ;7t$G9d0{__9*UVO)b8w? zwOv$eCcUw1mqFxMud1+|I>PC>-VsKuu!^|~7Mo@eg5Ad63uJA$%6(D|1{80k5Q^Jq zTltUae(L_tsw7#0Z_%(UqMb+x5X&o=IIGcG860i=8#>1h&lK-XoA^PawWiWs`3NFX z8DCCRJshc*Cf}WjedLAxirypP$%SU}b}l2n{Oy9egYcfSI^$tMN1La)j0;d&T&{fYh$yVUfM+OS?-N8}(hc&AC7> z8dgKp>5I8b7*qOww*7U%Jw_ECq#3j_sEkTpUvgosQ}Hm`-;O8dzTQ}?FWmOrB{@7I z>?rXbX-T&)(DvNbe;?rQNx&udXc&&F3!+65ClKC{bZsLQ(sJh7X5vJ;VSyjTDHPXB z+9iD&nrRR{QpJxF6j=2Vc1fRiO|{G{i5{jgWD>Qx`9BrzkHK~Cq@*Gma-?WJ{1!TS zv;V6J5;Kq4jxOnd>lUls(Pv_-D6to(R5Wae-b@gW(g_LY_*^q^)&-x_9*k~fie>fn znA+u>W8p5dhE3o-|FQ5^VTwR@KiBpvsp3dXQ#!o%RKCJ6#D$}O%|L#|kTQ(6^O1;> z3^~bBzh+>hO8Vm19uLpB?)V6?Ag4%T+S4`_tXN=zWT?m*YS#I4>-P6fSg4>*^u!J@o!y~GaaU|mx`2_XJ zWF{Foww;X70i&sXF=xvC2fmN@o%^zM6Z??5VSgV>EEb;}Q6PJErok{@A94H|Bb(lO z%ep-ut4UNKzOej38m~kMCOV3%Iw~A+xjp9kPBVWedhrM&yZ%hf-@USN5>cuZ2TXl- z;@{xBbJU>W2V}$h4Wg@4M8RZ>wrY!n1F65V#gj@y-*L*nn0F}&|-VDD!!A&}qKR0x`I;|u;g=0$O zrORH4={jYxJzoZK>}hz$umLS01AdwP(8kg>i#NwLwZQs!&1K02kMmUsZQ}1z-53$PxwHC3q91Gewg9g~Xi%!PRQY{(WR$5CViny;n9OqP%_$Ae8vh_@ zIJ=nnNI7w!khph+e0P(KK>tc+%wm@vJ1E9 zvgoK1^PiVW1=Q-He>R{orC9i#6AgOQ=;p}a&5e& zZE~ozr?d{UtQj3Mz&MyVS?KmA=VO-8uQ2SkX0V`pfJpG`&E-{P!wW-F6&pfaV-DVz zP?5Wr)ApZt;$y2k(q%_B)ZyKYJR{K|7juN_yi$LMe=0uvTbpW~Q>)Mn@indR`FmNA zV-MF&C+XmqG#Y-vgyQ@k0;6Vw_)yQANM zM`AAt7CEw?ZWtST2BS0f1WP0^y-Z3+cF=gRLmYO#K3^lpB9fSTs0&5J9A2()Ew9wq zA*!*PB)D>xj*G?36(&(WSX?iwgllvVb;x-+UOP)i<}gM0eFkSntYHx?{%j%2N8&-n zOH?pPY@;CaA={CZ<>YtZj#Rg7?RJ%!HD4akQ^4aEHV3RYIh9|dQrmuTH_cy@VnGX2 zBOK0s_~_5%C?KGPa14Jj*Gr{z#Lq~WV~1s7tpa}P-I~HtZCJdUbWbub-D`d6hz){` zX9FzBQ->KPk=wzV;$e4#OD(rtu{y!}2kfgPd3(PDAEmnSwc3=`*01^n?;uwAzr>Xi z6Ku+S9JY9gbDaN#T{==CTH>x*D9$3%D%a80~mmIYyb=UHA zLoaNjsq=rGVY^Xnx|d4B2R$VDzt7M%ZdP;Xa)pbluY&ku+W3yzc?zt^y6`XD8$y|b zT+W&cm%HK+ZHL}2?Hxaon;;`IwT|C3!{4|@&!Ni&tt3PdLgHh7E@daZi0lL~Jx%P^ zqcE%=xAUqX6mVDJs&PTK!IX)#M=ia$o9fKkO>N z#Tip-tucLGI69JnGb2P6x;xbN|_0WKNJM#+an#>}#r$%%W5r#H7|gkoo6Udixpy4ZudsEYu#?v%$M7a?>3>}$SWJMAHHp{8w)ZL zwi*fb;97@v1V7{vFJLQ1o|forv8cr4KtsWP{ux1>tv~h-^AzAsjwBb3YN4GotZp;N zTy0lst}q)Yt0K(PVvfjmP_gd?i?DjT^n7ft&^K480CZIqdiaw)vQ+bkRe_v~z-dW> z*Xwxivg%&)*v+=&jxBtu0B?Fk&9=)CetdiI_tu2mR-q~!d!HarYIBn7z?*7$n!&c> z8KZv(tmL`|(V~J()tpBe@!X{F-`?1Z7V59+R*N!jl%%%zVFQKJn`O=_U~0Ui2*wwu zZ@-I0osXF`96K^}VZ zIr7`)yj8}V8b@3^RWL`O%SwF7C1$nUEBSAWb8zHhx7?g5hu#SEBS}Z1_nPWUVx>1H z2riUE8tfmQIpjjE!8siswo(*t3!5G@6Xc*{f5odgmG7a3t5=)>4Y-RtMxL;j8^dom z)098dg)#}3-*m9&ewX>?}LVB^>E~#1r$}95Uqu8OUI|uKz*byb zn=eeQ&>PW-5@$wtPxZ}=9HkBu&ll<6HX&vaRwRslNP8~GO;KnHlimeh*d31N#+-V! z!cpXH>u~bKtB&Q$)Y)fTMxp-g|KLlBYJ?!kGk@m=xiN;xhu89lZ@rUkH58mcvXNyx zNmMIxOJOfkDo&WH=X@-?`H->{@h${k;kG!r=hr1^8uMOS8475c7I|Y@g3jCQwusGr zy0rEQk4y5)8@jhG?^WE2Jr&%ReeNiTfjvw5m(&WpwzEfcXoGa?4zdnW`Ow!1-QfGY zgz}>Vj||Wnwle0|bpOVAUcP(Ra-`}O+9drG>BFRmQbjXJ zP$zlX`>D$~ck}!;;AEWi7uzCpzcdiv98w6{)So3H(H+ofn?pH)8osW=-Bg`zBrG@+ z7q@eHui6&=XZm{*R*D0;`$|?AO|fPqk5FxTmTJg1#Fg9Tqp6F*mIl~v!EG7ry1*0| zo+3_D;r}@^;zS(~lehCbPzgx0>3P(SWJ1~XvOlg}>26)k8|?b_syWS*e|x6IiRw*O zz1j8)xL>yo!8V1XyEHJ%LMbC$7i$!0D3@VFRA9NqvlrQ|39&6f()^Qx5Q7f_L5#Hc z)I9wGiGNDY1XvBV7i%n2u)cuZbfifSSPf^3H|5IOSUK8toumW=#Hw*W(s%FB~|h6OOo zPL4A2c?T64PK6>Kx?^9kK*9QacCS(2CYlMqgKkXvCq?j1=5J~F*0-lb@{swif*2AT zW5{Ah7ERx>phfe4hqp$a)0a<>@7>hp;^KCSZtw>rj}x}#f4k0JMQ7dK$?vhGj+G3JqfqYMRo8m^u& zSLhZ3YWKAmolE8C(f3(D=J93&R6?j6+mx?oIM#?KwCMr*v~Usja_e^|4i^*meD9TA z=0D1?=N>%QD-bAxEDYtkWoq{}hPt#{VJ~`Ww zVs?QZe{$F`JuKWyM4JQQ%Y%hr=eH@JeiU6*&Ld0q`6{z5o*&<2T1_DG>b{Xjjyf|J zU-vFlt-{Y#ZG>neyoyJ7Bk?1Rp)&&r9{IA*Z%oNE>d-%?W}Z;|DAdFS|Gp5+^#l}! zm|+qUZ@S6nZ$m*&jclzkFe6$9N;=<7=?SPe*%M++W?S+iV<$t2i{Gg4WZ%^oe(~2m zZmeQ-;5zY!osC;uGFubwFzz#yc=(N0kRc41@+aum6(i$BY}k$A1)Vq8LE_9jw9z~`sjAKijniXk~ zukuvl%#OqV#`}0EZuY>TK3|LUcU`mEZs?c%%{}-)Nh!gp9CHoVuW&F`A0b9ew4FZE z<||N^)?npSIu(lyAiY)ihoCc?T}EVHL(bJNy5dA{LCaER+xMf4>_RC++@zhzmWy+Q z?S*b$Y>s?XoM<=vgdO=ngLeMUo3|yf=(kAx0v@_W&VPo4l>#d4h&OUTZoLcuXw-(N z!=`dpxFtW1J8|lR=o-Z`6}zSqc-p#DPuYMQ=xj>jS1$f_!Sc5Jx^LJzr@jWSMrtB0N!Dh)H{WDNHs<8{;Fh$fw(z%K^wqTE{z5!9 z*1zE{MT=n1(!XtyD=b#Z)nZNWCe;6-dRW2vXjf#jWin1EJ@${T0mQO>bB(9o{(AR| z^5pw7fdq>T*<{UtmH6pq27|);*)_dIMoS^0V0QyCiqm{FO+&`E&Zen{aQ92b001wrK{bj1-18ulaBG4wgpyArTehO-HG*ErOX;JHOMecos`a zXE`&Okq(`$7lqG$(Xg{JZmo;cD*dATVeTqqlLP3QXB2obf-6_ppJ`9Vh39%bu>`zX z;j_yccCC0-DUbY>s$-qQ0V_Q&Wh}zgL-0O;c%Zw5Ay<4^r!^@Inj=MAADgto+E9jU zh}B}(ajGzAdO($C$JY{soc;6X6{xse`QxU-AkN7DY|yx@EIsm?hQB%fBw=rH!gcEG z1GQsrJx5?I~&M06%M{yn=0!0Y1Ow%TfMM zh%53Hp{D8|ar0iBZtgluW1VD@ZjS0nNVSysfAt+Rt{TQr(9)5~Lz1~jyqD4T+<2Rx zHg1k3rR$LN`S^>`7Yn+A3!vy31XL9ea@ivkQ3RGXHdK+N=1bhW(e}I+XOW{@YtULo zl-*VkTl6d!vbB32$;4jfN;tK%o{M6hdB_Q%-jwLRIsf{FqZ8D*%H1i~N8_QbrD(wS z$5aH~uCp2SzYp91@(+bcMmdZe(bbv@CtKL7!FQHhHvTXfM_u{08 z!$p_%42O(ql{51h8iE9z-V~=;ld@{b2SJHr!xl~$6YeR>P6DW z8RJZi6cbvo2vNj~m?9gp-*gk(^y!c#(Os<8SOr&dA(BoOH+2G_uR8Ui?6qrbu%s7a z2_ro5G9vdiR6DL*DV=zwZD-k;f!rIGS1GDof`VYVYotpX}F|DvA*RPQ;?eI5Qs!D?G#gmy)O z$pL*z)>bH9MOC#XRJIDkM95`#2o(rMDS&)M)86N{E+4W7=QJtB@M2-_)&Wm#R&~ac z=?TO(RP;h3(b}aA67LE;m1ST!L6QyG3lt~fgR8&cArTuycnIYHmU7*ODq{Wz@;xjl z_Nf=_xcl(I4#G75KZg>AMd%9Oi?}O`mp=ruz}rV}D~5K}L>nmt8ZQoBHJOLU>()la zpj%ZFf)0}pPzyqK_mcQL!7f{$56`eWTpzDA100qp@ZRYV^`9 zErRHMX&oE*NdoWf)V?342Q+D9{ulbb*Q!fD{dX3?_006ZYJNIeG$GC3EvSPPjL(7h zE>{*0ECr{d_@i7R_9Jb?4u~kRbZ|G=oa1%J$-fctqbenwv;Lmw|#YMhz=zv;S4yIejZTMdFzrg{h*M z@~fIlyZbkxU|El!D<CnIs6Y%_*&CH1fhOY52$F3iVhCf&aSZ5$$A6 z{2WX4IwTJUW{`GXq8n?pPS@e2iiYLvxE*@|cCBO{?JUB;+1{0l+B~(-bkoT8;B4XU zd^t$w2*&yu``+rmvhi+F1at83dH%L>p zYF@D1vuuBx9hPW_*oX1}&CIY3%mQSK@x1rf42puppo4f41_yBN(#EFCQn%Tbgj9$z z@z{C5P4s`QD`M_(g1<)J@v>%z_TA96#bt?IbrOc&{Z=(Gcz^O`sN%zS=T{1H(awI5 zgb}y+pNRBoG^TU`9XNN%dH+QPD{3?Tm}u{l2mm!=K_U}VtyNc9ZO1D!-!Py@fp(Q< z(2CPxq4Qm~M@rQX2sigNC-IR8c@-ad#0RuZZ(v+A<@t<{398cmVvtm z<>Qt0M>;PI8eKv)q_}MmRQd?!+mT4j=3AQ!n?K*0>4kcWIO-l#zLt(W6ZHoougiA{F;cz2n3&zLuEopOm!Kq(+X&>`?}RJ)<}GK+VbW?R~Y zeQKNW0GB1>t}<^GQ|;Ed8+lbwg#;rw-jfW?IWR>(Vk8S>vAUnhtkm$=z@r9&UoQEpc5d4H+?_8Tq{>=ZRnyL;wv8C$3` zkPZ0sE&eiLzySd(+^C3>$wV?2f8Aiu1fkl#JuuB+8>Wd-1sgr6$!z;HENR)b;+TZqEapvW_z@HzvPh0#@ziG0zh;F@y1FIlnL_dKe!E|ZOvzp* zGm|LMMkM50bmNY~ul3er!&vr< z;UKJ;?q)S1F(wuuV?Ly}a{NJV@(auJD^G+?CKW1E%KRd11^dc5AC`by>d#hL)VBP5 zWFOc_w|Fhs2pfFbW^_g=uGs-%U{NCK$IWK;*n{uzs5>_A=c&I!q79ct7p#B&QL-=4 zMi0LtWBjmQtABLcGhVWvX;Ruc{1~Y>ml@vaBEPBJVc04jJBOKoyJQ>48wpeJ@`M$N zifn7G8E5yx8vX)yB6UMsimq8ivg<{q+AYh;9akFZ3-$CB4gV8(=rb1ic|h6I4g7Lb zXlh(dylY_4dyg%mT4;OWqgxxzj*M$zJ)H4H^~xenPtg$zt@$k>XBOq|`?~~TF^5fq z(+qYp6zBuJB|GH^$h}+80{Kor&PX!$;hW{^s>nK&N^zXbB2-zsSDDLq%Nff?ZSoXz zC3s z$0ITF{d?&JS#9YWJ9qI1S2h`z|1aY8vZyWpzKo54Z$U-YXQ{!RGuhYNt32Gr5%O3~del}*0W0|C>*OuF&! zSj(^@xqIob3cm!~+m~o!JYeT%1Fh+)#fg};RfDQD=+;4TO4=h6;M3I&MZ70A2N%}e z(aGYye-)>#x9TEpjPUZ%u)iyQl&D4yvQzL#|H7sRY4|hgtd4g}HPN2jN&o~uvCO>l zVF!!gFs>@?DE~Qx3vbmiRd!%b+iPQdmY~eOL01LW+L=EXhQUOGzcVC;eh$<=adVMz zUs45mw%nhfb<=NM?ec#? zKs6_%zG>J6NVS+5F@e|Ht`5bd(2hUqW6EBRX{nO|546RO(no1L*M1*^rWasW5_Gmf zI0lH<_C!njWUKTq=lL52y=Olkx+9@HbRXZkU~Q_DW}w7h7Ti3#*23_jv*<|3k-*}} z5E3N@jxZtaX)g1l6M|bels)tzLdvAk>f27&)ey8ZWKzGF5+AQ&3KIvEaW}N)-nCS$Fm{m=iJEIVciEw7lfEK&1>IOcgfWl<yK%{Q1}yqm=u+c2disCXXaX95;+m)|NlhF8 z12QpHb~nmje+H)~kd1YHD#hJ+&4AS6-voml`b@Si*+^8_3PJ%BK{aq|4)cuajW^K@P**5BQ+a8is~^mJ^l)4&hn+tgGjC@-UF?# z>X^IlG>hqw;&!OK+&x=HU)~Bm{5AYs!Er~b0%24Xk$%RAaOSY6Weh8oYN590@Q*#_ zEXcd-fLTU8YQ0jw)?ME-)=r(|-tovQrz!NW`m7$$Rd1JS-YzvG)yX8^>?1lcEKaVFBN%K?qX__}mPTg(Qoo%%KCa^1W0KX^g%uDNuzyZDwv4Y3|B z@E&)Ja4}1E;zqt-b_-V*Yu6MPZ!+UL7 z9-uRxJwFN~CW^+;OYfcuC}t02nMH|^@`?YuuFYq|vRQl7#xn@Odg}LfeE{~Jx3w}OeojxIe;eBFKwWZ7z}>JFW^6cSCX{qfB;&x+6Q^k=jP znlLAx{tfm;&!$pQu~P(DJy9F`mIeO-)`|Rf{oq;*PCLOE2E_Y_LSkl>RTaKB6lxXw z>3*v!0H&%bdUQ)77p88I!xG6?92DX6_ZS(-Y6Y z?Y!&cwILcr>3!yjJUtBK{hPF!{+UBc_awX_+hRK~SAW1_>g_A<@m%#}_|& zG+3PplQM<`fWPrEzRv$I{Ehk^P_lN{(Qv;#cN?GbQ=qpm^j@I9aQdnE$t{?mIftu| z54jj)&HX7-PcNqp^#T2OkZOcuB5wi6!VI{PE8Gji2o6)&RR0WaO=7`cO!{^Bd&X>{d@Kz z38s9kAtgM;zlrv;_-Ww>7`9lz>kyZAQ+~02UMNAhnwtFXe|S1G*ZvK{k%Qw8 zTNBb-SR2|QK;`_XaNJAI*iaTGf4Bw&RkBAq)894Rw(gj&sF5jcyaPzhC*K2ISY)`x z{1BNwLHuG13nj5>j+IUoZDL#f4(@D*fecJ*EAc9?uq{Mwq^*esfGyY$m#2?{*DqJz zF?hSdZgBC5Cyb30e+SSQQ(RMixXYQlj#v!<c#m;;=e-2)FLp4C1?;{dkt)TZ$ z|E^L>^Hj`jBm5-b091Pi{Wyg4%2Q*%NHFs;(tr7y<#u#4qLoNV+Ji z^KF6vrGB}Ifxz%IhB}%`3fi_;yYT!r|Fo9nCQ-#A7zBwB_9UwPJosONtAz}3mib}l zay0YAvy*uF=L2ZjcZ@zpEKmPZj4HuPB;(G+0R*em@RuQEKslxaxI=Asj9n+hp9wI<^Pi6_>oMlW@N7qNS==6FuNF+PH19Z1E!&jbS z9k^T!XA-b}h=(`7?;H{qArah9RDjYC`%dB$ks%2KP>xNC@-S6*zRc+35$RZjU6+%y zc!g8fS@zB|d!KrVq=5Ri;8WL%Qs}Z?37NR<$O1_L&>re|IAOlA)hw8J?F6ym_SYtl zz&g2Z5o}B)={f?c@}BE>4^vp`r~&U zyB>krsu+rsG!w3{QEr39`5ly(i*t+0mKL@XG$qu$Uk zdJaBlIj@znl|Mec%iS<+0vB-@sYweeN)Bo+16Yq)~II6dOmU5 z*w#=#2+ss4vd9Qzc}mL{G*r-R8b9D7}-VSMJ!Io zQ{O)W#%)+Owa{`l5`mFQMm-OIwEYp5E4mXQBnI&%+9Vy#qa6;SY_B&pG-06=!I0kC z$$sB@?>u}3f~J=2BW>I)rx)wlkNKFRUo z^1(^oU)qMI-Dbmsk54$kc5+!(nId|dBoJaoEsBI)qg9@XdQKmym2#2feNA}xt;+-E zx-G{Id9Lk8>8D|*ZA&+ludDvW=7_oLA=>mju$rAYO+yIk ziHyJ$`xzO55(cULYvr1SW|i9QdFjcVN-?9uLI>;PHPhdxSB3&jM{+Cz+dcUm+?(<|}?(jGUFF9?RF0e_=s7)4AjvK}w*Andc22(ji= z5HGlMj&ZN$2tV@Fk5T;=C@#5-y%AC>Rf9n=A&H#b>3t3_Yqv*YJxhC6MNRHYIPVxX z{GkJHaZUH1eM>t$wf(ngJ!f81z0Y1WtZ)_uXB2cNLcjXwGM*9KL^(O*XYk~+M#b&B z0|xU_9k>ZEj^Y9ebcgo!3LYJX`N5*ZwLEny^-O8kuOcScm@4N?*$g~NP!tRJ)|Roj z4%thb$+VUAqZG)an+31J<$mJUSV%uvgn>Z^LY7|7X|B2}Rg3t4ip}3}592I@Ju2Eu3?d60yAGWrRqUARv6EJAZ4iz5K_lQja%q=gs24uXs6mWFiMP#_ zuYNb5Mg8+PA)k08qS3z%ApD_o-`D*rKHuj-y0Ql_Jf!NrZt~ku(faJ4dxbqH$NV3L zcc|5$rayhrTqvA?XAy$I)52f)o-IW56DN(?g?6S>W(O0+Dpxy`-`9k=R*Z-M&FP_qM8 znTR}L)xv-tN~(_!nOrGPUs^}&$JZB0?OmXqoLR9@xkhR*_q==y|HeTfa8or*`5kgttx#kG(}1$(dqf4l zas)^JTiaIV_tW=vOs}5$6&V8h{7cqWz3hv0;ljHjciBHW^TspFG7avud|!7GP?-0Q z6knKK&2LTMeH-MB$D}yH(Vbc3MChj?3cRk#+WPbs?MY>%^I)(AJ}%H`-2l`1Ei-pr zIu#hlp$ZW(=pqfnH~?>*X^qH1j|Ga#Xk4IR*ze}r9eVfV9lHE%8CGaCCc(bTE@utq zQ$Ox{u3gmQmsdfbW*niKG|=y(VV(MaSU2`J-$;J+ZUV&q7!Jzd@f!-{xjF075)>!}3=R8%#y z$ziv6e)bCE-3VRY65@(V+zRfZiIws(*=(1S-;#lN?CHfUlkJ1IK8y%rWW=X_LUTpK zFjolQWPVq>nbP8lz7&9PDWQz)rzO9;RMhr?>N_M_?V>twF?__!kbZtqkRt=Oh9;G_5J_yF*++i?-kDMHZk3`QqQ^gu22bTS|9E659b7uf3QR?O?iz80N|evih)? zP* Wt$UO#QT4xF{!Cg`V=wfbRA8HV?}G@QPh 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 literal 0 HcmV?d00001 diff --git a/external-oauth/auth-server/src/static/styles.css b/external-oauth/auth-server/src/static/styles.css new file mode 100644 index 0000000..c3dcd08 --- /dev/null +++ b/external-oauth/auth-server/src/static/styles.css @@ -0,0 +1,188 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + background: #ffffff; + color: #000000; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; + flex: 1; +} + +header { + display: flex; + align-items: center; + gap: 2rem; + margin-bottom: 3rem; + padding-bottom: 2rem; + border-bottom: 2px solid #000000; +} + +.logo { + width: 80px; + height: 80px; + background: #000000; + padding: 10px; + border-radius: 8px; +} + +h1 { + font-size: 2.5rem; + font-weight: 700; + letter-spacing: -0.02em; +} + +.tagline { + font-size: 1.25rem; + color: #666666; + margin-bottom: 3rem; + line-height: 1.6; +} + +.features { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; + margin-bottom: 3rem; +} + +.feature-card { + border: 2px solid #000000; + padding: 1.5rem; + background: #ffffff; + transition: all 0.2s ease; +} + +.feature-card:hover { + background: #000000; + color: #ffffff; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.feature-card h3 { + font-size: 1.25rem; + margin-bottom: 0.75rem; + font-weight: 600; +} + +.feature-card p { + line-height: 1.6; + opacity: 0.9; +} + +.endpoints { + background: #f5f5f5; + border: 2px solid #000000; + padding: 2rem; + margin-bottom: 3rem; +} + +.endpoints h2 { + font-size: 1.75rem; + margin-bottom: 1.5rem; + font-weight: 600; +} + +.endpoint-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.endpoint { + font-family: 'Courier New', monospace; + background: #ffffff; + padding: 0.75rem 1rem; + border: 1px solid #000000; + display: flex; + align-items: center; + gap: 1rem; +} + +.method { + font-weight: bold; + min-width: 80px; +} + +.method.get { color: #0066cc; } +.method.post { color: #009900; } +.method.delete { color: #cc0000; } + +.links { + display: flex; + gap: 2rem; + flex-wrap: wrap; + margin-bottom: 3rem; +} + +.link-button { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 1rem 2rem; + background: #000000; + color: #ffffff; + text-decoration: none; + font-weight: 600; + transition: all 0.2s ease; + border: 2px solid #000000; +} + +.link-button:hover { + background: #ffffff; + color: #000000; +} + +.link-button.secondary { + background: #ffffff; + color: #000000; +} + +.link-button.secondary:hover { + background: #000000; + color: #ffffff; +} + +footer { + background: #000000; + color: #ffffff; + padding: 2rem; + text-align: center; +} + +footer a { + color: #ffffff; + text-decoration: underline; +} + +@media (max-width: 768px) { + h1 { + font-size: 2rem; + } + + header { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .logo { + width: 60px; + height: 60px; + background: #000000; + padding: 8px; + border-radius: 6px; + } +} \ No newline at end of file diff --git a/external-oauth/auth-server/src/types.ts b/external-oauth/auth-server/src/types.ts new file mode 100644 index 0000000..877b5dc --- /dev/null +++ b/external-oauth/auth-server/src/types.ts @@ -0,0 +1,92 @@ +import { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; + +/** + * Represents a pending OAuth authorization that hasn't been exchanged for tokens yet. + * Stored in Redis with the authorization code as the key. + */ +export interface PendingAuthorization { + /** The redirect URI where the client expects to receive the authorization code */ + redirectUri: string; + /** PKCE code challenge - a derived value from the code verifier */ + codeChallenge: string; + /** Method used to derive the code challenge (currently only S256 supported) */ + codeChallengeMethod: string; + /** The OAuth client ID that initiated the authorization */ + clientId: string; + /** Optional state parameter for CSRF protection */ + state?: string; +} + +/** + * Represents the exchange of an authorization code for an MCP access token. + * Used to prevent replay attacks by tracking if a code has been used. + */ +export interface TokenExchange { + /** The MCP access token that was issued for this authorization code */ + mcpAccessToken: string; + /** Whether this authorization code has already been exchanged for tokens */ + alreadyUsed: boolean; +} + +/** + * Represents mock upstream identity provider tokens for demonstration purposes. + * In production, this would contain real upstream provider tokens. + */ +export interface MockUpstreamInstallation { + /** Access token from the mock upstream identity provider */ + mockUpstreamAccessToken: string; + /** Refresh token from the mock upstream identity provider */ + mockUpstreamRefreshToken: string; +} + +/** + * The complete installation object stored in Redis, containing both + * upstream provider information and MCP-specific tokens. + * This object is encrypted using the MCP access token as the key. + */ +export interface McpInstallation { + /** Information from the upstream authentication provider */ + mockUpstreamInstallation: MockUpstreamInstallation; + /** MCP OAuth tokens issued to the client */ + mcpTokens: OAuthTokens; + /** The OAuth client ID associated with this installation */ + clientId: string; + /** Unix timestamp (seconds) when the tokens were issued */ + issuedAt: number; + /** Unique identifier for the user (not the OAuth client) */ + userId: string; +} + +/** + * OAuth 2.0 Token Introspection Response + * Based on RFC 7662: https://tools.ietf.org/html/rfc7662 + * Used when validating tokens with an external authorization server. + */ +export interface TokenIntrospectionResponse { + /** Whether the token is currently active */ + active: boolean; + /** Space-separated list of scopes associated with the token */ + scope?: string; + /** Client identifier for the OAuth client that requested the token */ + client_id?: string; + /** Human-readable identifier for the resource owner */ + username?: string; + /** Type of the token (e.g., "Bearer") */ + token_type?: string; + /** Expiration time as seconds since Unix epoch */ + exp?: number; + /** Time at which the token was issued as seconds since Unix epoch */ + iat?: number; + /** Time before which the token is not valid as seconds since Unix epoch */ + nbf?: number; + /** Subject identifier for the resource owner */ + sub?: string; + /** Intended audience for the token */ + aud?: string | string[]; + /** Issuer of the token */ + iss?: string; + /** Unique identifier for the token */ + jti?: string; + /** Custom field for our implementation to store user ID */ + userId?: string; +} \ No newline at end of file diff --git a/external-oauth/auth-server/src/utils/logger.ts b/external-oauth/auth-server/src/utils/logger.ts new file mode 100644 index 0000000..f9879db --- /dev/null +++ b/external-oauth/auth-server/src/utils/logger.ts @@ -0,0 +1,176 @@ +import { AsyncLocalStorage } from 'async_hooks'; +import { Request, Response, NextFunction } from 'express'; + +// Severity levels as per Google Cloud Logging +export enum LogSeverity { + DEFAULT = 'DEFAULT', + DEBUG = 'DEBUG', + INFO = 'INFO', + NOTICE = 'NOTICE', + WARNING = 'WARNING', + ERROR = 'ERROR', + CRITICAL = 'CRITICAL', + ALERT = 'ALERT', + EMERGENCY = 'EMERGENCY' +} + +interface LogContext { + trace?: string; + spanId?: string; + requestId?: string; + userAgent?: string; + method?: string; + path?: string; + [key: string]: string | undefined; +} + +interface StructuredLogEntry { + severity: LogSeverity; + message: string; + timestamp: string; + 'logging.googleapis.com/trace'?: string; + 'logging.googleapis.com/spanId'?: string; + [key: string]: unknown; +} + +class StructuredLogger { + private asyncLocalStorage = new AsyncLocalStorage(); + private projectId: string | undefined; + + constructor() { + // Get project ID from environment or metadata server + this.projectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCP_PROJECT; + } + + /** + * Run a function with a specific logging context + */ + runWithContext(context: LogContext, fn: () => T): T { + return this.asyncLocalStorage.run(context, fn); + } + + /** + * Extract trace context from Cloud Run request + */ + extractTraceContext(req: Request): LogContext { + const context: LogContext = {}; + + const traceHeader = req.header('X-Cloud-Trace-Context'); + if (traceHeader && this.projectId) { + const [trace, spanId] = traceHeader.split('/'); + context.trace = `projects/${this.projectId}/traces/${trace}`; + if (spanId) { + context.spanId = spanId.split(';')[0]; // Remove any trace flags + } + } + + // Add other useful request context + context.requestId = req.header('X-Request-Id'); + context.userAgent = req.header('User-Agent'); + context.method = req.method; + context.path = req.path; + + return context; + } + + /** + * Create Express middleware for request context + */ + middleware() { + return (req: Request, res: Response, next: NextFunction) => { + const context = this.extractTraceContext(req); + this.runWithContext(context, () => { + next(); + }); + }; + } + + /** + * Log a structured message + */ + private log(severity: LogSeverity, message: string, metadata?: Record) { + const context = this.asyncLocalStorage.getStore() || {}; + + const entry: StructuredLogEntry = { + severity, + message, + timestamp: new Date().toISOString(), + ...metadata + }; + + // Add trace context if available + if (context.trace) { + entry['logging.googleapis.com/trace'] = context.trace; + } + if (context.spanId) { + entry['logging.googleapis.com/spanId'] = context.spanId; + } + + // Add any other context fields + Object.keys(context).forEach(key => { + if (key !== 'trace' && key !== 'spanId') { + entry[`context.${key}`] = context[key]; + } + }); + + // Output as JSON for Cloud Logging + console.log(JSON.stringify(entry)); + } + + // Convenience methods for different severity levels + debug(message: string, metadata?: Record) { + this.log(LogSeverity.DEBUG, message, metadata); + } + + info(message: string, metadata?: Record) { + this.log(LogSeverity.INFO, message, metadata); + } + + notice(message: string, metadata?: Record) { + this.log(LogSeverity.NOTICE, message, metadata); + } + + warning(message: string, metadata?: Record) { + this.log(LogSeverity.WARNING, message, metadata); + } + + error(message: string, error?: Error, metadata?: Record) { + const errorMetadata = { + ...metadata, + error: error ? { + name: error.name, + message: error.message, + stack: error.stack + } : undefined + }; + this.log(LogSeverity.ERROR, message, errorMetadata); + } + + critical(message: string, metadata?: Record) { + this.log(LogSeverity.CRITICAL, message, metadata); + } + + alert(message: string, metadata?: Record) { + this.log(LogSeverity.ALERT, message, metadata); + } + + emergency(message: string, metadata?: Record) { + this.log(LogSeverity.EMERGENCY, message, metadata); + } + + /** + * Add additional context to the current async context + */ + addContext(context: LogContext) { + const currentContext = this.asyncLocalStorage.getStore(); + if (currentContext) { + Object.assign(currentContext, context); + } + } +} + +// Export singleton instance +export const logger = new StructuredLogger(); + +// Re-export for convenience +export type { LogContext, StructuredLogEntry }; \ No newline at end of file diff --git a/external-oauth/auth-server/tsconfig.json b/external-oauth/auth-server/tsconfig.json new file mode 100644 index 0000000..fee103a --- /dev/null +++ b/external-oauth/auth-server/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "Node16", + "moduleResolution": "Node16", + "sourceMap": true, + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/external-oauth/mcp-server/README.md b/external-oauth/mcp-server/README.md new file mode 100644 index 0000000..3c83dd1 --- /dev/null +++ b/external-oauth/mcp-server/README.md @@ -0,0 +1,150 @@ +# MCP Server - Resource Server with External OAuth + +MCP resource server demonstrating token validation with external OAuth providers. + +## Purpose + +This server shows how to build an MCP server that delegates all OAuth operations to external providers: +- No OAuth authorization code in the MCP server +- Token validation via introspection API (RFC 7662) +- Focus on serving MCP resources +- Authentication handled by external provider (Auth0, Okta, etc.) + +## Quick Start + +```bash +# This server requires the auth server to be running! + +# Option 1: Start both servers from repo root +cd ../.. && npm run dev:separate + +# Option 2: Start servers individually +# Terminal 1: +cd ../auth-server && npm run dev + +# Terminal 2: +cd ../mcp-server && npm run dev + +# Server starts on http://localhost:3232 +``` + +## Architecture + +This server: +1. Advertises the external OAuth server in its metadata +2. Receives MCP requests with Bearer tokens +3. Validates tokens via the OAuth server's `/introspect` endpoint +4. Serves MCP resources after token validation +5. Manages MCP sessions tied to validated users + +The server does not issue tokens or handle user authentication. + +## Key Components + +### `src/index.ts` +Main entry point. Notable aspects for external auth: +- **No OAuth endpoints** (handled by auth server) +- Uses `ExternalAuthVerifier` to validate tokens +- Fetches auth metadata from external server on startup +- Serves MCP endpoints only + +### `src/auth/external-verifier.ts` +**ExternalAuthVerifier** - Validates tokens with external auth server: +```typescript +async verifyAccessToken(token: string): Promise { + // Calls POST /introspect on auth server + // Validates token is active and intended for this MCP server + // Returns user info and scopes +} +``` + +### `src/services/mcp.ts` +MCP server implementation with all features: +- 7 demonstration tools +- 100 paginated resources +- 3 prompts with argument support +- Sampling, completions, logging + +### `src/services/redisTransport.ts` +Redis-backed transport enabling: +- Horizontal scaling +- Session state management +- Message routing across instances + +## MCP Endpoints + +```bash +# Streamable HTTP (recommended) +GET/POST/DELETE /mcp + +# SSE (legacy) +GET /sse +POST /message +``` + +All endpoints require `Authorization: Bearer ` header. + +## Configuration + +Environment variables in `.env`: +```bash +BASE_URI=http://localhost:3232 # This MCP server's URL +PORT=3232 # MCP server port +AUTH_SERVER_URL=http://localhost:3001 # External auth server URL +REDIS_URL=redis://localhost:6379 +``` + +## Token Validation Flow + +1. Client sends request with `Authorization: Bearer ` +2. MCP server extracts token from header +3. **MCP server calls** `POST AUTH_SERVER_URL/introspect` with token +4. Auth server returns token info (active, user_id, scopes, expiry) +5. MCP server validates: + - Token is active + - Token audience matches this server (BASE_URI) + - Token hasn't expired +6. Request proceeds with user context + +## Testing + +```bash +npm test # 67 unit tests +npm run lint # Lint code +npm run typecheck # Type checking +npm run build # Build to dist/ +``` + +## Production Adaptation + +To use a commercial OAuth provider: + +1. Update `.env` with provider URL: +```bash +AUTH_SERVER_URL=https://your-tenant.auth0.com +``` + +2. Modify `src/auth/external-verifier.ts` for provider-specific introspection: +```typescript +const response = await fetch(`${this.authServerUrl}/oauth/introspect`, { + // Add provider-specific authentication +}) +``` + +3. Adjust response parsing if the introspection format differs from RFC 7662 standard + +The MCP server code otherwise remains unchanged. + +## References + +- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) +- [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) +- [OAuth 2.0 Resource Servers](https://www.oauth.com/oauth2-servers/the-resource-server/) +- [docs/oauth-flow.md](../../docs/oauth-flow.md) - Detailed flow with mode differences + +## Related Documentation + +- [Main README](../../README.md) - Complete project documentation +- [Auth Server README](../auth-server/README.md) - The demo OAuth provider +- [External OAuth Overview](../README.md) - Architecture explanation +- [Embedded OAuth](../../embedded-oauth/README.md) - Self-hosted OAuth alternative diff --git a/external-oauth/mcp-server/eslint.config.mjs b/external-oauth/mcp-server/eslint.config.mjs new file mode 100644 index 0000000..515114c --- /dev/null +++ b/external-oauth/mcp-server/eslint.config.mjs @@ -0,0 +1,19 @@ +// @ts-check + +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + linterOptions: { + reportUnusedDisableDirectives: false, + }, + rules: { + "@typescript-eslint/no-unused-vars": ["error", + { "argsIgnorePattern": "^_" } + ] + } + } +); diff --git a/external-oauth/mcp-server/jest.config.js b/external-oauth/mcp-server/jest.config.js new file mode 100644 index 0000000..87825b9 --- /dev/null +++ b/external-oauth/mcp-server/jest.config.js @@ -0,0 +1,19 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +export default { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + extensionsToTreatAsEsm: ['.ts'], + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + useESM: true, + }, + ], + }, + testPathIgnorePatterns: ['/node_modules/', '/dist/', '/scratch/'], + injectGlobals: true, +}; diff --git a/external-oauth/mcp-server/package-lock.json b/external-oauth/mcp-server/package-lock.json new file mode 100644 index 0000000..d28fb04 --- /dev/null +++ b/external-oauth/mcp-server/package-lock.json @@ -0,0 +1,7141 @@ +{ + "name": "mcp-server-feature-reference-mcp-server", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-server-feature-reference-mcp-server", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@redis/client": "^1.6.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-rate-limit": "^8.0.1", + "raw-body": "^3.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/content-type": "^1.1.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + } + }, + "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.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@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.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@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.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "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.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "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-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "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-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "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/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.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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/@modelcontextprotocol/sdk": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.18.2.tgz", + "integrity": "sha512-beedclIvFcCnPrYgHsylqiYJVJ/CI47Vyc4tY8no1/Li/O8U4BTlJfy6ZwxkYwx+Mx10nrgwSVrA7VBbhh4slg==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "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", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "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": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@redis/client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", + "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "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/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/content-type": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.9.tgz", + "integrity": "sha512-Hq9IMnfekuOCsEmYl4QX2HBrT+XsfXiupfrLLY8Dcf3Puf4BkBOxSbWYTITSOQAhJoYPBez+b4MJRpIYL65z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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", + "peer": true + }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.18.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz", + "integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz", + "integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/type-utils": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.45.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz", + "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz", + "integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.45.0", + "@typescript-eslint/types": "^8.45.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz", + "integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz", + "integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz", + "integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz", + "integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz", + "integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.45.0", + "@typescript-eslint/tsconfig-utils": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz", + "integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz", + "integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0", + "peer": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz", + "integrity": "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "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.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001746", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz", + "integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "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/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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/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/dedent": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "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.228", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz", + "integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "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/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "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.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "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/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.1.0.tgz", + "integrity": "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "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-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC", + "peer": true + }, + "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": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "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/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "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/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "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/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "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-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "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/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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/lru-cache/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/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "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": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "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": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "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/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "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.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "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/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "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/router/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/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "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/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": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.4.tgz", + "integrity": "sha512-ccVcRABct5ZELCT5U0+DZwkXMCcOCLi2doHRrKy1nK/s7J7bch6TzJMsrY09WxgUUIP/ITfmcDS8D2yl63rnXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "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/typescript-eslint": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz", + "integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.45.0", + "@typescript-eslint/parser": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "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.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "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/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "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/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/external-oauth/mcp-server/package.json b/external-oauth/mcp-server/package.json new file mode 100644 index 0000000..c64e659 --- /dev/null +++ b/external-oauth/mcp-server/package.json @@ -0,0 +1,43 @@ +{ + "name": "mcp-server-feature-reference-external-mcp-server", + "version": "0.1.0", + "description": "MCP Feature Reference Server - MCP Server (External OAuth)", + "type": "module", + "main": "dist/index.js", + "scripts": { + "start": "node dist/index.js", + "dev": "tsx watch --inspect src/index.ts", + "dev:break": "tsx --inspect-brk watch src/index.ts", + "build": "tsc && npm run copy-static", + "copy-static": "mkdir -p dist/static && cp -r src/static/* dist/static/", + "lint": "eslint src/", + "typecheck": "tsc --noEmit", + "test": "NODE_OPTIONS=--experimental-vm-modules jest" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@redis/client": "^1.6.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-rate-limit": "^8.0.1", + "raw-body": "^3.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/content-type": "^1.1.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + }, + "overrides": { + "@types/express": "^5.0.0", + "@types/express-serve-static-core": "^5.0.2" + } +} diff --git a/src/auth/external-verifier.ts b/external-oauth/mcp-server/src/auth/external-verifier.ts similarity index 99% rename from src/auth/external-verifier.ts rename to external-oauth/mcp-server/src/auth/external-verifier.ts index 5fbb1bb..c1d73ce 100644 --- a/src/auth/external-verifier.ts +++ b/external-oauth/mcp-server/src/auth/external-verifier.ts @@ -1,7 +1,7 @@ import { OAuthTokenVerifier } from '@modelcontextprotocol/sdk/server/auth/provider.js'; import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; import { InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js'; -import { TokenIntrospectionResponse } from '../../shared/types.js'; +import { TokenIntrospectionResponse } from '../types.js'; import { logger } from '../utils/logger.js'; import { BASE_URI } from '../config.js'; diff --git a/external-oauth/mcp-server/src/config.ts b/external-oauth/mcp-server/src/config.ts new file mode 100644 index 0000000..f30869d --- /dev/null +++ b/external-oauth/mcp-server/src/config.ts @@ -0,0 +1,28 @@ +import "dotenv/config"; + +/** + * Port for the MCP server to listen on + */ +export const PORT = Number(process.env.PORT) || 3232; + +/** + * Base URI for the MCP server. Used for OAuth callbacks and metadata. + * Should match the port if specified separately. + */ +export const BASE_URI = process.env.BASE_URI || `http://localhost:${PORT}`; + +// Validate PORT and BASE_URI consistency +const baseUrl = new URL(BASE_URI); +if (baseUrl.port && parseInt(baseUrl.port) !== PORT) { + console.warn(`Warning: BASE_URI port (${baseUrl.port}) doesn't match PORT (${PORT})`); +} + +/** + * Redis connection URL + */ +export const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379'; + +/** + * URL of the external authorization server + */ +export const AUTH_SERVER_URL = process.env.AUTH_SERVER_URL || 'http://localhost:3001'; diff --git a/external-oauth/mcp-server/src/handlers/shttp.integration.test.ts b/external-oauth/mcp-server/src/handlers/shttp.integration.test.ts new file mode 100644 index 0000000..b345d28 --- /dev/null +++ b/external-oauth/mcp-server/src/handlers/shttp.integration.test.ts @@ -0,0 +1,694 @@ +import { jest } from '@jest/globals'; +import { Request, Response } from 'express'; +import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; +import { MockRedisClient, setRedisClient } from '../redis.js'; +import { handleStreamableHTTP } from './shttp.js'; +import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; +// import { randomUUID } from 'crypto'; // Currently unused but may be needed for future tests +import { shutdownSession } from '../services/redisTransport.js'; + +// Type for MCP initialization response +interface MCPInitResponse { + jsonrpc: string; + id: string | number; + result?: { + _meta?: { + sessionId?: string; + }; + [key: string]: unknown; + }; +} + +describe('Streamable HTTP Handler Integration Tests', () => { + let mockRedis: MockRedisClient; + let mockReq: Partial; + let mockRes: Partial; + + beforeEach(() => { + mockRedis = new MockRedisClient(); + setRedisClient(mockRedis); + jest.resetAllMocks(); + + // Create mock response with chainable methods + mockRes = { + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + on: jest.fn().mockReturnThis(), + once: jest.fn().mockReturnThis(), + emit: jest.fn().mockReturnThis(), + headersSent: false, + setHeader: jest.fn().mockReturnThis(), + writeHead: jest.fn().mockReturnThis(), + write: jest.fn().mockReturnThis(), + end: jest.fn().mockReturnThis(), + getHeader: jest.fn(), + removeHeader: jest.fn().mockReturnThis(), + socket: { + setTimeout: jest.fn(), + }, + } as unknown as Partial; + + // Create mock request + mockReq = { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'accept': 'application/json, text/event-stream', + 'mcp-protocol-version': '2024-11-05', + }, + body: {}, + }; + }); + + // Helper function to trigger cleanup after handleStreamableHTTP calls + const triggerResponseCleanup = async () => { + // Find all finish handlers registered during the test + const finishHandlers = (mockRes.on as jest.Mock).mock.calls + .filter(([event]) => event === 'finish') + .map(([, handler]) => handler); + + // Trigger all finish handlers + for (const handler of finishHandlers) { + if (typeof handler === 'function') { + await handler(); + } + } + }; + + // Helper to extract session ID from test context + const getSessionIdFromTest = (): string | undefined => { + // Try to get from response headers first + const setHeaderCalls = (mockRes.setHeader as jest.Mock).mock.calls; + const sessionIdHeader = setHeaderCalls.find(([name]) => name === 'mcp-session-id'); + if (sessionIdHeader?.[1]) { + return sessionIdHeader[1] as string; + } + + // Fall back to extracting from Redis channels + const allChannels = Array.from(mockRedis.subscribers.keys()); + const serverChannel = allChannels.find(channel => channel.includes('mcp:shttp:toserver:')); + return serverChannel?.split(':')[3]; + }; + + afterEach(async () => { + // Always trigger cleanup for any MCP servers created during tests + await triggerResponseCleanup(); + mockRedis.clear(); + jest.clearAllMocks(); + }); + + describe('Redis Subscription Cleanup', () => { + it('should clean up Redis subscriptions after shttp response completes', async () => { + // Set up initialization request (no session ID for new initialization) + const initRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'init-1', + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'test-client', version: '1.0.0' } + } + }; + + mockReq.body = initRequest; + mockReq.auth = { + clientId: 'test-client-123', + token: 'test-token', + scopes: ['mcp'], + extra: { userId: 'test-user-123' } + } as AuthInfo; + + // Call the handler + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Wait longer for async initialization to complete + await new Promise(resolve => setTimeout(resolve, 200)); + + // get the sessionId from the response + const sessionId = getSessionIdFromTest(); + expect(sessionId).toBeDefined(); + + // Check if any subscriptions were created on any channels + // Since we don't know the exact sessionId generated, check all channels + const allChannels = Array.from(mockRedis.subscribers.keys()); + const totalSubscriptions = allChannels.reduce((sum, channel) => sum + (mockRedis.subscribers.get(channel)?.length || 0), 0); + + // Should have created at least one subscription (server channel) + expect(totalSubscriptions).toBeGreaterThan(0); + expect(allChannels.some(channel => channel.includes('mcp:shttp:toserver:'))).toBe(true); + + // Find the finish handler that was registered + const finishHandler = (mockRes.on as jest.Mock).mock.calls.find( + ([event]) => event === 'finish' + )?.[1] as (() => Promise) | undefined; + + expect(finishHandler).toBeDefined(); + + // Simulate response completion to trigger cleanup + if (finishHandler) { + await finishHandler(); + } + + // Verify cleanup handler was registered + expect(mockRes.on).toHaveBeenCalledWith('finish', expect.any(Function)); + + if (sessionId) { + await shutdownSession(sessionId) + } + }); + + it('should handle cleanup errors gracefully', async () => { + const initRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'init-1', + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'test-client', version: '1.0.0' } + } + }; + + mockReq.body = initRequest; + mockReq.auth = { + clientId: 'test-client-123', + token: 'test-token', + scopes: ['mcp'], + extra: { userId: 'test-user-123' } + } as AuthInfo; + + // Call the handler + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Use Redis error simulation to test error handling + const finishHandler = (mockRes.on as jest.Mock).mock.calls.find( + ([event]) => event === 'finish' + )?.[1] as (() => Promise) | undefined; + + // Simulate error during cleanup + + // Cleanup should not throw error even if Redis operations fail + if (finishHandler) { + await expect(finishHandler()).resolves.not.toThrow(); + } + + // Clean up the MCP server by sending DELETE request + const cleanupSessionId = getSessionIdFromTest(); + + if (cleanupSessionId) { + // Send DELETE request to clean up MCP server + jest.clearAllMocks(); + mockReq.method = 'DELETE'; + if (mockReq.headers) { + mockReq.headers['mcp-session-id'] = cleanupSessionId; + } + mockReq.body = {}; + + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Wait a bit for cleanup to complete + await new Promise(resolve => setTimeout(resolve, 50)); + } + }); + }); + + describe('DELETE Request Session Cleanup', () => { + it('should trigger onsessionclosed callback which sends shutdown control message', async () => { + // First, create a session with an initialization request + const initRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'init-1', + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'test-client', version: '1.0.0' } + } + }; + + mockReq.body = initRequest; + mockReq.auth = { + clientId: 'test-client-123', + token: 'test-token', + scopes: ['mcp'], + extra: { userId: 'test-user-123' } + } as AuthInfo; + + // Initialize session + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Wait for async initialization + await new Promise(resolve => setTimeout(resolve, 100)); + + // For initialization requests with StreamableHTTPServerTransport, + // the handler might not immediately return a response if using SSE mode + // Let's check different possible locations for the session ID + + // Check JSON responses + const jsonCalls = (mockRes.json as jest.Mock).mock.calls; + let sessionId: string | undefined; + + if (jsonCalls.length > 0) { + const response = jsonCalls[0][0] as MCPInitResponse; + if (response?.result?._meta?.sessionId) { + sessionId = response.result._meta.sessionId; + } + } + + // Check write calls (for SSE responses) + if (!sessionId) { + const writeCalls = (mockRes.write as jest.Mock).mock.calls; + for (const [data] of writeCalls) { + if (typeof data === 'string' && data.includes('sessionId')) { + try { + // SSE data format: "data: {...}\n\n" + const jsonStr = data.replace(/^data: /, '').trim(); + const parsed = JSON.parse(jsonStr) as MCPInitResponse; + if (parsed?.result?._meta?.sessionId) { + sessionId = parsed.result._meta.sessionId; + } + } catch { + // Not valid JSON, continue + } + } + } + } + + // Fallback to getting from Redis channels + if (!sessionId) { + sessionId = getSessionIdFromTest(); + } + + expect(sessionId).toBeDefined(); + + // Reset mocks but keep the session + jest.clearAllMocks(); + + // Now test DELETE request + mockReq.method = 'DELETE'; + mockReq.headers = { + ...mockReq.headers, + 'mcp-session-id': sessionId + }; + mockReq.body = {}; + + // Track control messages sent to Redis + const publishSpy = jest.spyOn(mockRedis, 'publish'); + + // Call DELETE handler - StreamableHTTPServerTransport should handle it + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Wait for async processing and onsessionclosed callback + await new Promise(resolve => setTimeout(resolve, 100)); + + // The StreamableHTTPServerTransport should handle the DELETE and trigger onsessionclosed + // which calls shutdownSession, sending the control message + const controlCalls = publishSpy.mock.calls.filter(call => + call[0] === `mcp:control:${sessionId}` + ); + + expect(controlCalls.length).toBeGreaterThan(0); + + // Verify the control message content + const controlCall = publishSpy.mock.calls.find(call => + call[0] === `mcp:control:${sessionId}` + ); + if (controlCall) { + const message = JSON.parse(controlCall[1]); + expect(message.type).toBe('control'); + expect(message.action).toBe('SHUTDOWN'); + } + }); + + it('should return 401 for DELETE request with wrong user', async () => { + // First, create a session as user1 + const initRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'init-1', + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'test-client', version: '1.0.0' } + } + }; + + mockReq.body = initRequest; + mockReq.auth = { + clientId: 'test-client-123', + token: 'test-token', + scopes: ['mcp'], + extra: { userId: 'user1' } + } as AuthInfo; + + // Initialize session as user1 + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Wait for async initialization + await new Promise(resolve => setTimeout(resolve, 100)); + + // Get the session ID from response + let sessionId: string | undefined; + + // Check JSON responses + const jsonCalls = (mockRes.json as jest.Mock).mock.calls; + if (jsonCalls.length > 0) { + const response = jsonCalls[0][0] as MCPInitResponse; + if (response?.result?._meta?.sessionId) { + sessionId = response.result._meta.sessionId; + } + } + + // Check write calls (for SSE responses) + if (!sessionId) { + const writeCalls = (mockRes.write as jest.Mock).mock.calls; + for (const [data] of writeCalls) { + if (typeof data === 'string' && data.includes('sessionId')) { + try { + const jsonStr = data.replace(/^data: /, '').trim(); + const parsed = JSON.parse(jsonStr) as MCPInitResponse; + if (parsed?.result?._meta?.sessionId) { + sessionId = parsed.result._meta.sessionId; + } + } catch { + // Ignore JSON parse errors + } + } + } + } + + if (!sessionId) { + sessionId = getSessionIdFromTest(); + } + + // Reset mocks + jest.clearAllMocks(); + + // Now test DELETE request as user2 + mockReq.method = 'DELETE'; + mockReq.headers = { + ...mockReq.headers, + 'mcp-session-id': sessionId + }; + mockReq.body = {}; + mockReq.auth = { + clientId: 'test-client-456', + token: 'test-token-2', + scopes: ['mcp'], + extra: { userId: 'user2' } + } as AuthInfo; + + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Should return 401 for unauthorized access to another user's session + expect(mockRes.status).toHaveBeenCalledWith(401); + + // shutdown the session + if (sessionId) { + await shutdownSession(sessionId) + } + }); + }); + + describe('User Session Isolation', () => { + it('should prevent users from accessing sessions created by other users', async () => { + // Create session for user 1 + const user1Auth: AuthInfo = { + clientId: 'user1-client', + token: 'user1-token', + scopes: ['mcp'], + extra: { userId: 'user1' } + }; + + const user2Auth: AuthInfo = { + clientId: 'user2-client', + token: 'user2-token', + scopes: ['mcp'], + extra: { userId: 'user2' } + }; + + const initRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'init-1', + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'user1-client', version: '1.0.0' } + } + }; + + // User 1 creates session + mockReq.body = initRequest; + mockReq.auth = user1Auth; + + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Wait for async initialization to complete + await new Promise(resolve => setTimeout(resolve, 100)); + + // Get the actual session ID from response + let actualSessionId: string | undefined; + + // Check JSON responses + const jsonCalls = (mockRes.json as jest.Mock).mock.calls; + if (jsonCalls.length > 0) { + const response = jsonCalls[0][0] as MCPInitResponse; + if (response?.result?._meta?.sessionId) { + actualSessionId = response.result._meta.sessionId; + } + } + + // Check write calls (for SSE responses) + if (!actualSessionId) { + const writeCalls = (mockRes.write as jest.Mock).mock.calls; + for (const [data] of writeCalls) { + if (typeof data === 'string' && data.includes('sessionId')) { + try { + const jsonStr = data.replace(/^data: /, '').trim(); + const parsed = JSON.parse(jsonStr) as MCPInitResponse; + if (parsed?.result?._meta?.sessionId) { + actualSessionId = parsed.result._meta.sessionId; + } + } catch { + // Ignore JSON parse errors + } + } + } + } + + if (!actualSessionId) { + actualSessionId = getSessionIdFromTest(); + } + + expect(actualSessionId).toBeDefined(); + + // Store finish handler before clearing mocks + const finishHandler1 = (mockRes.on as jest.Mock).mock.calls.find( + ([event]) => event === 'finish' + )?.[1] as (() => Promise) | undefined; + + // Reset mocks + jest.clearAllMocks(); + + // Trigger cleanup for the MCP server created in this step + if (finishHandler1) { + await finishHandler1(); + } + + // User 2 tries to access user 1's session + mockReq.headers = { + ...mockReq.headers, + 'mcp-session-id': actualSessionId + }; + mockReq.body = { + jsonrpc: '2.0', + id: 'user2-request', + method: 'tools/list', + params: {} + }; + mockReq.auth = user2Auth; + + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Should return 401 for unauthorized access to another user's session + expect(mockRes.status).toHaveBeenCalledWith(401); + + // Clean up the MCP server by sending DELETE request + if (actualSessionId) { + jest.clearAllMocks(); + mockReq.method = 'DELETE'; + mockReq.headers['mcp-session-id'] = actualSessionId; + mockReq.body = {}; + mockReq.auth = user1Auth; // Use user1's auth to delete their session + + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Wait a bit for cleanup to complete + await new Promise(resolve => setTimeout(resolve, 50)); + } + }); + + it('should allow users to create separate sessions with same session ID pattern', async () => { + // This test shows that different users should be able to use sessions + // without interfering with each other, even if session IDs might collide + + const user1Auth: AuthInfo = { + clientId: 'user1-client', + token: 'user1-token', + scopes: ['mcp'] + }; + + const user2Auth: AuthInfo = { + clientId: 'user2-client', + token: 'user2-token', + scopes: ['mcp'] + }; + + const initRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'init-1', + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'test-client', version: '1.0.0' } + } + }; + + // User 1 creates session + mockReq.body = initRequest; + mockReq.auth = user1Auth; + + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Store finish handler before clearing mocks + const finishHandler1 = (mockRes.on as jest.Mock).mock.calls.find( + ([event]) => event === 'finish' + )?.[1] as (() => Promise) | undefined; + + // Reset for user 2 + jest.clearAllMocks(); + + // Trigger cleanup for User 1's MCP server + if (finishHandler1) { + await finishHandler1(); + } + + // User 2 creates their own session + mockReq.body = { + ...initRequest, + id: 'init-2', + params: { + ...initRequest.params, + clientInfo: { name: 'user2-client', version: '1.0.0' } + } + }; + mockReq.auth = user2Auth; + delete mockReq.headers!['mcp-session-id']; // New initialization + + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Trigger cleanup for User 2's MCP server + const finishHandler2 = (mockRes.on as jest.Mock).mock.calls.find( + ([event]) => event === 'finish' + )?.[1] as (() => Promise) | undefined; + + if (finishHandler2) { + await finishHandler2(); + } + + // Both users should be able to create sessions successfully + // Sessions should be isolated in Redis using user-scoped keys + expect(mockRes.status).not.toHaveBeenCalledWith(400); + expect(mockRes.status).not.toHaveBeenCalledWith(403); + }); + + it('should clean up only the requesting user\'s session on DELETE', async () => { + // Create sessions for both users + const user1Auth: AuthInfo = { + clientId: 'user1-client', + token: 'user1-token', + scopes: ['mcp'] + }; + + const user2Auth: AuthInfo = { + clientId: 'user2-client', + token: 'user2-token', + scopes: ['mcp'] + }; + + // Create session for user 1 + const initRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'init-1', + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'user1-client', version: '1.0.0' } + } + }; + + mockReq.body = initRequest; + mockReq.auth = user1Auth; + + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Trigger cleanup for User 1's MCP server + const finishHandler1 = (mockRes.on as jest.Mock).mock.calls.find( + ([event]) => event === 'finish' + )?.[1] as (() => Promise) | undefined; + + if (finishHandler1) { + await finishHandler1(); + } + + // Track session 1 ID (would be returned in response headers) + const session1Id = 'user1-session-id'; // In real implementation, extract from response + + // Create session for user 2 + jest.clearAllMocks(); + mockReq.body = { + ...initRequest, + id: 'init-2', + params: { + ...initRequest.params, + clientInfo: { name: 'user2-client', version: '1.0.0' } + } + }; + mockReq.auth = user2Auth; + delete mockReq.headers!['mcp-session-id']; + + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Trigger cleanup for User 2's MCP server + const finishHandler2 = (mockRes.on as jest.Mock).mock.calls.find( + ([event]) => event === 'finish' + )?.[1] as (() => Promise) | undefined; + + if (finishHandler2) { + await finishHandler2(); + } + + // Track session 2 ID (placeholder for actual implementation) + + // User 1 deletes their session + jest.clearAllMocks(); + mockReq.method = 'DELETE'; + mockReq.headers = { + ...mockReq.headers, + 'mcp-session-id': session1Id + }; + mockReq.body = {}; + mockReq.auth = user1Auth; + + await handleStreamableHTTP(mockReq as Request, mockRes as Response); + + // Only user 1's session should be cleaned up + // User 2's session should remain active + // This test documents expected behavior for proper user isolation + }); + }); +}); \ No newline at end of file diff --git a/external-oauth/mcp-server/src/handlers/shttp.test.ts b/external-oauth/mcp-server/src/handlers/shttp.test.ts new file mode 100644 index 0000000..a068209 --- /dev/null +++ b/external-oauth/mcp-server/src/handlers/shttp.test.ts @@ -0,0 +1,229 @@ +import { jest } from '@jest/globals'; +import { Request, Response } from 'express'; +import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; +import { MockRedisClient, setRedisClient } from '../redis.js'; + +describe('Streamable HTTP Handler', () => { + let mockRedis: MockRedisClient; + + beforeEach(() => { + mockRedis = new MockRedisClient(); + setRedisClient(mockRedis); + jest.resetAllMocks(); + }); + + afterEach(() => { + mockRedis.clear(); + }); + + describe('Helper function tests', () => { + it('should verify Redis mock is working', async () => { + await mockRedis.set('test-key', 'test-value'); + const value = await mockRedis.get('test-key'); + expect(value).toBe('test-value'); + }); + + it('should handle Redis pub/sub', async () => { + const messageHandler = jest.fn(); + const cleanup = await mockRedis.createSubscription( + 'test-channel', + messageHandler, + jest.fn() + ); + + await mockRedis.publish('test-channel', 'test-message'); + + expect(messageHandler).toHaveBeenCalledWith('test-message'); + + await cleanup(); + }); + }); + + describe('Request validation', () => { + it('should identify initialize requests correctly', async () => { + const { isInitializeRequest } = await import('@modelcontextprotocol/sdk/types.js'); + + const initRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'test', version: '1.0' } + } + }; + + const nonInitRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 2, + method: 'tools/list', + params: {} + }; + + expect(isInitializeRequest(initRequest)).toBe(true); + expect(isInitializeRequest(nonInitRequest)).toBe(false); + }); + }); + + describe('HTTP response mock behavior', () => { + it('should create proper response mock with chainable methods', () => { + const mockRes = { + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + on: jest.fn().mockReturnThis(), + headersSent: false, + } as Partial; + + // Test chaining + const result = mockRes.status!(400).json!({ + jsonrpc: '2.0', + error: { code: -32000, message: 'Bad Request' }, + id: null + }); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(mockRes.json).toHaveBeenCalledWith({ + jsonrpc: '2.0', + error: { code: -32000, message: 'Bad Request' }, + id: null + }); + expect(result).toBe(mockRes); + }); + }); + + describe('Session ID generation', () => { + it('should generate valid UUIDs', async () => { + const { randomUUID } = await import('crypto'); + + const sessionId = randomUUID(); + + // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + expect(sessionId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); + }); + }); + + describe('Redis channel naming', () => { + it('should create correct channel names for server communication', () => { + const sessionId = 'test-session-123'; + const requestId = 'req-456'; + + const toServerChannel = `mcp:shttp:toserver:${sessionId}`; + const toClientChannel = `mcp:shttp:toclient:${sessionId}:${requestId}`; + const notificationChannel = `mcp:shttp:toclient:${sessionId}:__GET_stream`; + + expect(toServerChannel).toBe('mcp:shttp:toserver:test-session-123'); + expect(toClientChannel).toBe('mcp:shttp:toclient:test-session-123:req-456'); + expect(notificationChannel).toBe('mcp:shttp:toclient:test-session-123:__GET_stream'); + }); + }); + + describe('Error response formatting', () => { + it('should format JSON-RPC error responses correctly', () => { + const errorResponse = { + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Bad Request: No valid session ID provided', + }, + id: null, + }; + + expect(errorResponse.jsonrpc).toBe('2.0'); + expect(errorResponse.error.code).toBe(-32000); + expect(errorResponse.error.message).toContain('Bad Request'); + expect(errorResponse.id).toBe(null); + }); + + it('should format internal error responses correctly', () => { + const internalErrorResponse = { + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal error', + }, + id: null, + }; + + expect(internalErrorResponse.jsonrpc).toBe('2.0'); + expect(internalErrorResponse.error.code).toBe(-32603); + expect(internalErrorResponse.error.message).toBe('Internal error'); + expect(internalErrorResponse.id).toBe(null); + }); + }); + + describe('Request/Response patterns', () => { + it('should handle typical MCP message structures', () => { + const initializeRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'test-client', version: '1.0.0' } + } + }; + + const toolsListRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 2, + method: 'tools/list', + params: {} + }; + + const toolsListResponse: JSONRPCMessage = { + jsonrpc: '2.0', + id: 2, + result: { + tools: [ + { + name: 'echo', + description: 'Echo the input', + inputSchema: { + type: 'object', + properties: { + text: { type: 'string' } + } + } + } + ] + } + }; + + expect(initializeRequest.method).toBe('initialize'); + expect(toolsListRequest.method).toBe('tools/list'); + expect(toolsListResponse.result).toBeDefined(); + expect(Array.isArray(toolsListResponse.result?.tools)).toBe(true); + }); + }); + + describe('HTTP header handling', () => { + it('should extract session ID from headers', () => { + const mockReq = { + headers: { + 'mcp-session-id': 'test-session-123', + 'content-type': 'application/json' + }, + body: {} + } as Partial; + + const sessionId = mockReq.headers!['mcp-session-id'] as string; + + expect(sessionId).toBe('test-session-123'); + }); + + it('should handle missing session ID in headers', () => { + const mockReq = { + headers: { + 'content-type': 'application/json' + }, + body: {} + } as Partial; + + const sessionId = mockReq.headers!['mcp-session-id'] as string | undefined; + + expect(sessionId).toBeUndefined(); + }); + }); +}); \ No newline at end of file diff --git a/external-oauth/mcp-server/src/handlers/shttp.ts b/external-oauth/mcp-server/src/handlers/shttp.ts new file mode 100644 index 0000000..1b89498 --- /dev/null +++ b/external-oauth/mcp-server/src/handlers/shttp.ts @@ -0,0 +1,177 @@ +import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { Request, Response } from "express"; +import { getShttpTransport, isSessionOwnedBy, redisRelayToMcpServer, ServerRedisTransport, setSessionOwner, shutdownSession } from "../services/redisTransport.js"; +import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; +import { randomUUID } from "crypto"; +import { createMcpServer } from "../services/mcp.js"; +import { logger } from "../utils/logger.js"; + + +declare module "express-serve-static-core" { + interface Request { + /** + * Information about the validated access token, if the `requireBearerAuth` middleware was used. + */ + auth?: AuthInfo; + } +} + +function getUserIdFromAuth(auth?: AuthInfo): string | null { + return auth?.extra?.userId as string || null; +} + +// TODO: Document Streamable HTTP implementation choices: +// 1. STATEFUL: Requires clients to initialize sessions and track session IDs +// - First request must be 'initialize' without Mcp-Session-Id header +// - Server returns session ID, client must include it in subsequent requests +// - Alternative: Could implement STATELESS mode (each request independent) +// 2. SSE RESPONSES: Returns results via Server-Sent Events stream, not JSON responses +// - Requires Accept: application/json, text/event-stream header +// - Responses formatted as: event: message\ndata: {...} +// - Alternative: Could use JSON response mode (check StreamableHTTPServerTransport options) + +export async function handleStreamableHTTP(req: Request, res: Response) { + let shttpTransport: StreamableHTTPServerTransport | undefined = undefined; + + res.on('finish', async () => { + await shttpTransport?.close(); + }); + + const onsessionclosed = async (sessionId: string) => { + logger.info('Session closed callback triggered', { + sessionId, + userId: getUserIdFromAuth(req.auth) + }); + await shutdownSession(sessionId); + } + + try { + // Check for existing session ID + const sessionId = req.headers['mcp-session-id'] as string | undefined; + const userId = getUserIdFromAuth(req.auth); + + logger.debug('SHTTP request received', { + method: req.method, + sessionId, + userId, + hasAuth: !!req.auth, + authExtra: req.auth?.extra + }); + + // if no userid, return 401, we shouldn't get here ideally + if (!userId) { + logger.warning('Request without user ID', { + sessionId, + hasAuth: !!req.auth + }); + res.status(401).json({ + "jsonrpc": "2.0", + "error": { + "code": -32002, + "message": "User ID required" + } + }); + return; + } + + const isGetRequest = req.method === 'GET'; + + // incorrect session for the authed user, return 401 + if (sessionId) { + if (!(await isSessionOwnedBy(sessionId, userId))) { + logger.warning('Session ownership mismatch', { + sessionId, + userId, + requestMethod: req.method + }); + res.status(401).json({ + "jsonrpc": "2.0", + "error": { + "code": -32001, + "message": "Session not found or access denied" + } + }); + return; + } + // Reuse existing transport for owned session + logger.info('Reusing existing session', { + sessionId, + userId, + isGetRequest + }); + shttpTransport = await getShttpTransport(sessionId, onsessionclosed, isGetRequest); + } else if (isInitializeRequest(req.body)) { + // New initialization request - use JSON response mode + logger.debug('Processing initialize request', { + body: req.body, + userId, + headerSessionId: sessionId, // This is the sessionId from header (should be undefined for init) + isInitializeRequest: true + }); + + const onsessioninitialized = async (sessionId: string) => { + logger.info('Initializing new session', { + sessionId, + userId + }); + + const { server, cleanup: mcpCleanup } = createMcpServer(); + + const serverRedisTransport = new ServerRedisTransport(sessionId); + serverRedisTransport.onclose = mcpCleanup; + await server.connect(serverRedisTransport) + + // Set session ownership + await setSessionOwner(sessionId, userId); + + logger.info('Session initialized successfully', { + sessionId, + userId + }); + } + + const newSessionId = randomUUID(); + shttpTransport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => newSessionId, + onsessionclosed, + onsessioninitialized, + }); + shttpTransport.onclose = await redisRelayToMcpServer(newSessionId, shttpTransport); + } else { + // Invalid request - no session ID and not initialization request + logger.warning('Invalid request: no session ID and not initialization', { + hasSessionId: !!sessionId, + isInitRequest: false, + userId, + method: req.method + }); + res.status(400).json({ + "jsonrpc": "2.0", + "error": { + "code": -32600, + "message": "Invalid request method for existing session" + } + }); + return; + } + // Handle the request with existing transport - no need to reconnect + await shttpTransport.handleRequest(req, res, req.body); + } catch (error) { + logger.error('Error handling MCP request', error as Error, { + sessionId: req.headers['mcp-session-id'] as string | undefined, + method: req.method, + userId: getUserIdFromAuth(req.auth) + }); + + if (!res.headersSent) { + res.status(500).json({ + "jsonrpc": "2.0", + "error": { + "code": -32603, + "message": "Internal error during request processing" + } + }); + } + } +} diff --git a/external-oauth/mcp-server/src/handlers/sse.ts b/external-oauth/mcp-server/src/handlers/sse.ts new file mode 100644 index 0000000..409d792 --- /dev/null +++ b/external-oauth/mcp-server/src/handlers/sse.ts @@ -0,0 +1,100 @@ +import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import contentType from "content-type"; +import { Request, Response } from "express"; +import { redisClient } from "../redis.js"; +import { createMcpServer } from "../services/mcp.js"; +import { logger, logMcpMessage } from "../utils/logger.js"; + +declare module "express-serve-static-core" { + interface Request { + /** + * Information about the validated access token, if the `requireBearerAuth` middleware was used. + */ + auth?: AuthInfo; + } +} + +function redisChannelForSession(sessionId: string): string { + return `mcp:${sessionId}`; +} + +export async function handleSSEConnection(req: Request, res: Response) { + const { server: mcpServer, cleanup: mcpCleanup } = createMcpServer(); + const transport = new SSEServerTransport("/message", res); + logger.info('Received MCP SSE connection', { + sessionId: transport.sessionId + }); + + const redisCleanup = await redisClient.createSubscription( + redisChannelForSession(transport.sessionId), + (json) => { + // TODO handle DELETE messages + // TODO set timeout to kill the session + + const message = JSON.parse(json); + logMcpMessage(message, transport.sessionId); + transport.handleMessage(message).catch((error) => { + logger.error('Error handling message', error as Error, { + sessionId: transport.sessionId + }); + }); + }, + (error) => { + logger.error('Disconnecting due to error in Redis subscriber', error as Error, { + sessionId: transport.sessionId + }); + transport + .close() + .catch((error) => + logger.error('Error closing transport', error as Error, { + sessionId: transport.sessionId + }), + ); + }, + ); + + const cleanup = () => { + void mcpCleanup(); + redisCleanup().catch((error) => + logger.error('Error disconnecting Redis subscriber', error as Error, { + sessionId: transport.sessionId + }), + ); + } + + // Clean up Redis subscription when the connection closes + mcpServer.onclose = cleanup + + logger.info('Listening on Redis channel', { + sessionId: transport.sessionId, + channel: redisChannelForSession(transport.sessionId) + }); + await mcpServer.connect(transport); +} + +export async function handleMessage(req: Request, res: Response) { + const sessionId = req.query.sessionId; + let body: string; + try { + if (typeof sessionId !== "string") { + throw new Error("Only one sessionId allowed"); + } + + const ct = contentType.parse(req.headers["content-type"] ?? ""); + if (ct.type !== "application/json") { + throw new Error(`Unsupported content-type: ${ct}`); + } + + body = JSON.stringify(req.body); + } catch (error) { + res.status(400).json(error); + logger.error('Bad POST request', error as Error, { + sessionId, + contentType: req.headers['content-type'] + }); + return; + } + await redisClient.publish(redisChannelForSession(sessionId), body); + res.status(202).end(); +} diff --git a/external-oauth/mcp-server/src/index.ts b/external-oauth/mcp-server/src/index.ts new file mode 100644 index 0000000..5f634de --- /dev/null +++ b/external-oauth/mcp-server/src/index.ts @@ -0,0 +1,239 @@ +import { BearerAuthMiddlewareOptions, requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"; +import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from "@modelcontextprotocol/sdk/server/auth/router.js"; +import cors from "cors"; +import rateLimit from "express-rate-limit"; +import express from "express"; +import path from "path"; +import { fileURLToPath } from "url"; +import { ExternalAuthVerifier } from "./auth/external-verifier.js"; +import { BASE_URI, PORT, AUTH_SERVER_URL } from "./config.js"; +import { handleStreamableHTTP } from "./handlers/shttp.js"; +import { handleMessage, handleSSEConnection } from "./handlers/sse.js"; +import { redisClient } from "./redis.js"; +import { logger } from "./utils/logger.js"; + +const app = express(); + +// Get the directory of the current module +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Base security middleware - applied to all routes +const baseSecurityHeaders = (req: express.Request, res: express.Response, next: express.NextFunction) => { + // Basic security headers + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.setHeader('X-Frame-Options', 'SAMEORIGIN'); + res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); + + // Content Security Policy + const csp = [ + "default-src 'self'", + "object-src 'none'", // Disable plugins + "frame-ancestors 'none'", // No embedding + "form-action 'self'", // Only allow forms to submit to our domain + "base-uri 'self'", // Restrict base tag + "upgrade-insecure-requests", + "block-all-mixed-content" + ].join('; '); + + res.setHeader('Content-Security-Policy', csp); + next(); +}; + +// Structured logging middleware +const loggingMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => { + const startTime = Date.now(); + + // Sanitize headers to remove sensitive information + const sanitizedHeaders = { ...req.headers }; + delete sanitizedHeaders.authorization; + delete sanitizedHeaders.cookie; + delete sanitizedHeaders['x-api-key']; + + // Log request (without sensitive data) + logger.info('Request received', { + method: req.method, + url: req.url, + // Only log specific safe headers + headers: { + 'content-type': sanitizedHeaders['content-type'], + 'user-agent': sanitizedHeaders['user-agent'], + 'mcp-protocol-version': sanitizedHeaders['mcp-protocol-version'], + 'mcp-session-id': sanitizedHeaders['mcp-session-id'], + 'accept': sanitizedHeaders['accept'], + 'x-cloud-trace-context': sanitizedHeaders['x-cloud-trace-context'] + }, + // Don't log request body as it may contain sensitive data + bodySize: req.headers['content-length'] + }); + + // Log response when finished + res.on('finish', () => { + const duration = Date.now() - startTime; + logger.info('Request completed', { + method: req.method, + url: req.url, + statusCode: res.statusCode, + duration: `${duration}ms` + }); + }); + + next(); +}; + + +// Sensitive data middleware - for routes with sensitive data +const sensitiveDataHeaders = (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.setHeader('Cache-Control', 'no-store, max-age=0'); + next(); +}; + +// SSE middleware - specific for SSE endpoint +const sseHeaders = (req: express.Request, res: express.Response, next: express.NextFunction) => { + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-store, max-age=0'); + res.setHeader('Connection', 'keep-alive'); + next(); +}; + +// Configure CORS to allow any origin since this is a public API service +const corsOptions = { + origin: true, // Allow any origin + methods: ['GET', 'POST'], + allowedHeaders: ['Content-Type', 'Authorization', "Mcp-Protocol-Version", "Mcp-Protocol-Id"], + exposedHeaders: ["Mcp-Protocol-Version", "Mcp-Protocol-Id"], + credentials: true +}; + + +app.use(express.json()); + +// Add structured logging context middleware first +app.use(logger.middleware()); + +// Then add the logging middleware +app.use(loggingMiddleware); + +// Apply base security headers to all routes +app.use(baseSecurityHeaders); + +// Enable CORS pre-flight requests +app.options('*', cors(corsOptions)); + +// Rate limiting for custom endpoints +const staticFileRateLimit = rateLimit({ + windowMs: 10 * 60 * 1000, // 10 minutes + limit: 25, // 25 requests per 10 minutes for static files + message: { error: 'too_many_requests', error_description: 'Static file rate limit exceeded' } +}); + +// Separate mode: MCP server uses external auth server +logger.info('Starting MCP server in SEPARATE mode', { + baseUri: BASE_URI, + port: PORT, + authServerUrl: AUTH_SERVER_URL +}); + +// Fetch metadata from external auth server with retry logic +let authMetadata; +const maxRetries = 5; +const retryDelay = 3000; // 3 seconds + +for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + logger.info(`Attempting to connect to auth server (attempt ${attempt}/${maxRetries})`, { + authServerUrl: AUTH_SERVER_URL + }); + + const authMetadataResponse = await fetch(`${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`); + if (!authMetadataResponse.ok) { + throw new Error(`Failed to fetch auth server metadata: ${authMetadataResponse.status} ${authMetadataResponse.statusText}`); + } + authMetadata = await authMetadataResponse.json(); + logger.info('Successfully fetched auth server metadata', { + issuer: authMetadata.issuer, + authorizationEndpoint: authMetadata.authorization_endpoint, + tokenEndpoint: authMetadata.token_endpoint + }); + break; // Success, exit retry loop + + } catch (error) { + if (attempt < maxRetries) { + logger.info(`Failed to connect to auth server, retrying in ${retryDelay/1000} seconds...`, { + attempt, + maxRetries, + error: (error as Error).message + }); + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } else { + logger.error('Failed to fetch auth server metadata after all retries', error as Error); + logger.error('Make sure the auth server is running at', undefined, { authServerUrl: AUTH_SERVER_URL }); + process.exit(1); + } + } +} + +// BACKWARDS COMPATIBILITY: We serve OAuth metadata from the MCP server even in separate mode +// This is technically redundant since the auth server handles all OAuth operations, +// but some clients may expect to find .well-known/oauth-authorization-server on the +// resource server itself. The metadata points to the external auth server endpoints. +app.use(mcpAuthMetadataRouter({ + oauthMetadata: authMetadata, + resourceServerUrl: new URL(BASE_URI), + resourceName: "MCP Feature Reference Server" +})); + +// Configure bearer auth with external verifier +const externalVerifier = new ExternalAuthVerifier(AUTH_SERVER_URL); + +const bearerAuthOptions: BearerAuthMiddlewareOptions = { + verifier: externalVerifier, + resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(new URL(BASE_URI)), +}; + +const bearerAuth = requireBearerAuth(bearerAuthOptions); + +// MCP routes (legacy SSE transport) +app.get("/sse", cors(corsOptions), bearerAuth, sseHeaders, handleSSEConnection); +app.post("/message", cors(corsOptions), bearerAuth, sensitiveDataHeaders, handleMessage); + +// MCP routes (new streamable HTTP transport) +app.get("/mcp", cors(corsOptions), bearerAuth, handleStreamableHTTP); +app.post("/mcp", cors(corsOptions), bearerAuth, handleStreamableHTTP); +app.delete("/mcp", cors(corsOptions), bearerAuth, handleStreamableHTTP); + +// Static assets +app.get("/mcp-logo.png", staticFileRateLimit, (req, res) => { + const logoPath = path.join(__dirname, "static", "mcp.png"); + res.sendFile(logoPath); +}); + +app.get("/styles.css", staticFileRateLimit, (req, res) => { + const cssPath = path.join(__dirname, "static", "styles.css"); + res.setHeader('Content-Type', 'text/css'); + res.sendFile(cssPath); +}); + +// Splash page +app.get("/", (req, res) => { + const splashPath = path.join(__dirname, "static", "index.html"); + res.sendFile(splashPath); +}); + +// Note: Fake upstream auth routes are not needed in separate mode +// The auth server handles all authentication + +try { + await redisClient.connect(); +} catch (error) { + logger.error("Could not connect to Redis", error as Error); + process.exit(1); +} + +app.listen(PORT, () => { + logger.info('Server started', { + port: PORT, + url: `http://localhost:${PORT}`, + environment: process.env.NODE_ENV || 'development' + }); +}); diff --git a/external-oauth/mcp-server/src/redis.ts b/external-oauth/mcp-server/src/redis.ts new file mode 100644 index 0000000..3fc1a47 --- /dev/null +++ b/external-oauth/mcp-server/src/redis.ts @@ -0,0 +1,289 @@ +import { createClient, SetOptions } from "@redis/client"; +import { logger } from "./utils/logger.js"; +import { REDIS_URL } from "./config.js"; + +/** + * Describes the Redis primitives we use in this application, to be able to mock + * them in tests (so we don't need to actually hit Redis). + */ +export interface RedisClient { + get(key: string): Promise; + set(key: string, value: string, options?: SetOptions): Promise; + getDel(key: string): Promise; + del(key: string): Promise; + expire(key: string, seconds: number): Promise; + lpush(key: string, ...values: string[]): Promise; + lrange(key: string, start: number, stop: number): Promise; + connect(): Promise; + on(event: string, callback: (error: Error) => void): void; + options?: { url: string }; + exists(key: string): Promise; + numsub(key: string): Promise; + + /** + * Creates a pub/sub subscription. Returns a cleanup function to unsubscribe. + * Handles Redis client duplication and error handling internally. + */ + createSubscription( + channel: string, + onMessage: (message: string) => void, + onError: (error: Error) => void, + ): Promise<() => Promise>; + + /** + * Publishes a message to a channel. + */ + publish(channel: string, message: string): Promise; +} + +export class RedisClientImpl implements RedisClient { + private redis = createClient({ + url: REDIS_URL, + password: process.env.REDIS_PASSWORD, + socket: { + tls: process.env.REDIS_TLS === "1", + ca: process.env.REDIS_TLS_CA, + } + }); + + constructor() { + this.redis.on("error", (error) => + logger.error("Redis client error", error as Error), + ); + } + + async numsub(key: string): Promise { + const subs = await this.redis.pubSubNumSub(key); + return subs[key] || 0; + } + + async get(key: string): Promise { + return await this.redis.get(key); + } + + async getDel(key: string): Promise { + return await this.redis.getDel(key); + } + + async set(key: string, value: string, options?: SetOptions): Promise { + return await this.redis.set( + key, + value, + options, + ); + } + + async del(key: string): Promise { + return await this.redis.del(key); + } + + async expire(key: string, seconds: number): Promise { + return await this.redis.expire(key, seconds); + } + + async lpush(key: string, ...values: string[]): Promise { + return await this.redis.lPush(key, values); + } + + async lrange(key: string, start: number, stop: number): Promise { + return await this.redis.lRange(key, start, stop); + } + + async connect(): Promise { + await this.redis.connect(); + } + + on(event: string, callback: (error: Error) => void): void { + this.redis.on(event, callback); + } + + get options() { + return { url: REDIS_URL }; + } + + async createSubscription( + channel: string, + onMessage: (message: string) => void, + onError: (error: Error) => void, + ): Promise<() => Promise> { + const subscriber = this.redis.duplicate(); + subscriber.on("error", (error) => { + onError(error); + }); + + await subscriber.connect(); + + await subscriber.subscribe(channel, (message) => { + onMessage(message); + }); + + return async () => { + await subscriber.disconnect(); + }; + } + + async publish(channel: string, message: string): Promise { + await this.redis.publish(channel, message); + } + + async exists(key: string): Promise { + const result = await this.redis.exists(key); + return result > 0; + } +} + +// Export a mutable reference that can be swapped in tests +export let redisClient: RedisClient = new RedisClientImpl(); + +// Function to replace the Redis client (used in tests) +export function setRedisClient(client: RedisClient) { + redisClient = client; +} + +export class MockRedisClient implements RedisClient { + options = { url: "redis://localhost:6379" }; + private store = new Map(); + private lists = new Map(); + public subscribers = new Map void)[]>(); // Public for testing access + private errorCallbacks = new Map void)[]>(); + + async get(key: string): Promise { + return this.store.get(key) ?? null; + } + + async getDel(key: string): Promise { + const value = this.store.get(key) ?? null; + this.store.delete(key); + return value; + } + + async set(key: string, value: string, options?: SetOptions): Promise { + let oldValue: string | null = null; + if (options?.GET) { + oldValue = this.store.get(key) ?? null; + } + this.store.set(key, value); + return oldValue; + } + + async del(key: string): Promise { + let deleted = 0; + if (this.store.has(key)) { + this.store.delete(key); + deleted++; + } + if (this.lists.has(key)) { + this.lists.delete(key); + deleted++; + } + return deleted; + } + + async expire(key: string, _seconds: number): Promise { + // Mock implementation - just return true if key exists + return this.store.has(key) || this.lists.has(key); + } + + async lpush(key: string, ...values: string[]): Promise { + const list = this.lists.get(key) || []; + list.unshift(...values); + this.lists.set(key, list); + return list.length; + } + + async lrange(key: string, start: number, stop: number): Promise { + const list = this.lists.get(key) || []; + if (stop === -1) { + return list.slice(start); + } + return list.slice(start, stop + 1); + } + + async connect(): Promise { + // No-op in mock + } + + on(event: string, callback: (error: Error) => void): void { + if (event === "error") { + const callbacks = this.errorCallbacks.get("global") ?? []; + callbacks.push(callback); + this.errorCallbacks.set("global", callbacks); + } + } + + async createSubscription( + channel: string, + onMessage: (message: string) => void, + onError: (error: Error) => void, + ): Promise<() => Promise> { + const callbacks = this.subscribers.get(channel) ?? []; + callbacks.push(onMessage); + this.subscribers.set(channel, callbacks); + + const errorCallbacks = this.errorCallbacks.get(channel) ?? []; + errorCallbacks.push(onError); + this.errorCallbacks.set(channel, errorCallbacks); + + return async () => { + const callbacks = this.subscribers.get(channel) ?? []; + const index = callbacks.indexOf(onMessage); + if (index !== -1) { + callbacks.splice(index, 1); + } + this.subscribers.set(channel, callbacks); + + if (onError) { + const errorCallbacks = this.errorCallbacks.get(channel) ?? []; + const errorIndex = errorCallbacks.indexOf(onError); + if (errorIndex !== -1) { + errorCallbacks.splice(errorIndex, 1); + } + this.errorCallbacks.set(channel, errorCallbacks); + } + }; + } + + async publish(channel: string, message: string): Promise { + const callbacks = this.subscribers.get(channel) ?? []; + for (const callback of callbacks) { + try { + callback(message); + } catch (error) { + const errorCallbacks = this.errorCallbacks.get(channel) ?? []; + for (const errorCallback of errorCallbacks) { + errorCallback(error as Error); + } + } + } + } + + async exists(key: string): Promise { + return this.store.has(key) || this.lists.has(key); + } + + async numsub(key: string): Promise { + return (this.subscribers.get(key) || []).length; + } + + clear() { + this.store.clear(); + this.lists.clear(); + this.subscribers.clear(); + this.errorCallbacks.clear(); + } + + // Helper method for tests to simulate Redis errors + simulateError(error: Error, channel?: string): void { + if (channel) { + const callbacks = this.errorCallbacks.get(channel) ?? []; + for (const callback of callbacks) { + callback(error); + } + } else { + const callbacks = this.errorCallbacks.get("global") ?? []; + for (const callback of callbacks) { + callback(error); + } + } + } +} diff --git a/external-oauth/mcp-server/src/services/mcp.ts b/external-oauth/mcp-server/src/services/mcp.ts new file mode 100644 index 0000000..39192b5 --- /dev/null +++ b/external-oauth/mcp-server/src/services/mcp.ts @@ -0,0 +1,686 @@ +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { + CallToolRequestSchema, + CompleteRequestSchema, + CreateMessageRequest, + CreateMessageResultSchema, + GetPromptRequestSchema, + ListPromptsRequestSchema, + ListResourcesRequestSchema, + ListResourceTemplatesRequestSchema, + ListToolsRequestSchema, + LoggingLevel, + ReadResourceRequestSchema, + Resource, + SetLevelRequestSchema, + SubscribeRequestSchema, + Tool, + UnsubscribeRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; +import { zodToJsonSchema } from "zod-to-json-schema"; + +type ToolInput = { + type: "object"; + properties?: Record; + required?: string[]; +}; + +/* Input schemas for tools implemented in this server */ +const EchoSchema = z.object({ + message: z.string().describe("Message to echo"), +}); + +const AddSchema = z.object({ + a: z.number().describe("First number"), + b: z.number().describe("Second number"), +}); + +const LongRunningOperationSchema = z.object({ + duration: z + .number() + .default(10) + .describe("Duration of the operation in seconds"), + steps: z.number().default(5).describe("Number of steps in the operation"), +}); + +const SampleLLMSchema = z.object({ + prompt: z.string().describe("The prompt to send to the LLM"), + maxTokens: z + .number() + .default(100) + .describe("Maximum number of tokens to generate"), +}); + +// Example completion values +const EXAMPLE_COMPLETIONS = { + style: ["casual", "formal", "technical", "friendly"], + temperature: ["0", "0.5", "0.7", "1.0"], + resourceId: ["1", "2", "3", "4", "5"], +}; + +const GetTinyImageSchema = z.object({}); + +const AnnotatedMessageSchema = z.object({ + messageType: z + .enum(["error", "success", "debug"]) + .describe("Type of message to demonstrate different annotation patterns"), + includeImage: z + .boolean() + .default(false) + .describe("Whether to include an example image"), +}); + +const GetResourceReferenceSchema = z.object({ + resourceId: z + .number() + .min(1) + .max(100) + .describe("ID of the resource to reference (1-100)"), +}); + +enum ToolName { + ECHO = "echo", + ADD = "add", + LONG_RUNNING_OPERATION = "longRunningOperation", + SAMPLE_LLM = "sampleLLM", + GET_TINY_IMAGE = "getTinyImage", + ANNOTATED_MESSAGE = "annotatedMessage", + GET_RESOURCE_REFERENCE = "getResourceReference", +} + +enum PromptName { + SIMPLE = "simple_prompt", + COMPLEX = "complex_prompt", + RESOURCE = "resource_prompt", +} + +interface McpServerWrapper { + server: Server; + cleanup: () => void; +} + +export const createMcpServer = (): McpServerWrapper => { + const server = new Server( + { + name: "example-servers/feature-reference", + version: "1.0.0", + }, + { + capabilities: { + prompts: {}, + resources: { subscribe: true }, + tools: {}, + logging: {}, + completions: {}, + }, + } + ); + + const subscriptions: Set = new Set(); + + // Set up update interval for subscribed resources + const subsUpdateInterval = setInterval(() => { + for (const uri of subscriptions) { + server.notification({ + method: "notifications/resources/updated", + params: { uri }, + }); + } + }, 10000); + + let logLevel: LoggingLevel = "debug"; + const messages = [ + { level: "debug", data: "Debug-level message" }, + { level: "info", data: "Info-level message" }, + { level: "notice", data: "Notice-level message" }, + { level: "warning", data: "Warning-level message" }, + { level: "error", data: "Error-level message" }, + { level: "critical", data: "Critical-level message" }, + { level: "alert", data: "Alert level-message" }, + { level: "emergency", data: "Emergency-level message" }, + ]; + + const isMessageIgnored = (level: LoggingLevel): boolean => { + const currentLevel = messages.findIndex((msg) => logLevel === msg.level); + const messageLevel = messages.findIndex((msg) => level === msg.level); + return messageLevel < currentLevel; + }; + + // Set up update interval for random log messages + const logsUpdateInterval = setInterval(() => { + const message = { + method: "notifications/message", + params: messages[Math.floor(Math.random() * messages.length)], + }; + if (!isMessageIgnored(message.params.level as LoggingLevel)) + server.notification(message); + }, 20000); + + + // Set up update interval for stderr messages + const stdErrUpdateInterval = setInterval(() => { + const shortTimestamp = new Date().toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }); + server.notification({ + method: "notifications/stderr", + params: { content: `${shortTimestamp}: A stderr message` }, + }); + }, 30000); + + // Helper method to request sampling from client + const requestSampling = async ( + context: string, + uri: string, + maxTokens: number = 100 + ) => { + const request: CreateMessageRequest = { + method: "sampling/createMessage", + params: { + messages: [ + { + role: "user", + content: { + type: "text", + text: `Resource ${uri} context: ${context}`, + }, + }, + ], + systemPrompt: "You are a helpful test server.", + maxTokens, + temperature: 0.7, + includeContext: "thisServer", + }, + }; + + return await server.request(request, CreateMessageResultSchema); + }; + + const ALL_RESOURCES: Resource[] = Array.from({ length: 100 }, (_, i) => { + const uri = `test://static/resource/${i + 1}`; + if (i % 2 === 0) { + return { + uri, + name: `Resource ${i + 1}`, + mimeType: "text/plain", + text: `Resource ${i + 1}: This is a plaintext resource`, + }; + } else { + const buffer = Buffer.from(`Resource ${i + 1}: This is a base64 blob`); + return { + uri, + name: `Resource ${i + 1}`, + mimeType: "application/octet-stream", + blob: buffer.toString("base64"), + }; + } + }); + + const PAGE_SIZE = 10; + + server.setRequestHandler(ListResourcesRequestSchema, async (request) => { + const cursor = request.params?.cursor; + let startIndex = 0; + + if (cursor) { + const decodedCursor = parseInt(atob(cursor), 10); + if (!isNaN(decodedCursor)) { + startIndex = decodedCursor; + } + } + + const endIndex = Math.min(startIndex + PAGE_SIZE, ALL_RESOURCES.length); + const resources = ALL_RESOURCES.slice(startIndex, endIndex); + + let nextCursor: string | undefined; + if (endIndex < ALL_RESOURCES.length) { + nextCursor = btoa(endIndex.toString()); + } + + return { + resources, + nextCursor, + }; + }); + + server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => { + return { + resourceTemplates: [ + { + uriTemplate: "test://static/resource/{id}", + name: "Static Resource", + description: "A static resource with a numeric ID", + }, + ], + }; + }); + + server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const uri = request.params.uri; + + if (uri.startsWith("test://static/resource/")) { + const index = parseInt(uri.split("/").pop() ?? "", 10) - 1; + if (index >= 0 && index < ALL_RESOURCES.length) { + const resource = ALL_RESOURCES[index]; + return { + contents: [resource], + }; + } + } + + throw new Error(`Unknown resource: ${uri}`); + }); + + server.setRequestHandler(SubscribeRequestSchema, async (request) => { + const { uri } = request.params; + subscriptions.add(uri); + + // Request sampling from client when someone subscribes + await requestSampling("A new subscription was started", uri); + return {}; + }); + + server.setRequestHandler(UnsubscribeRequestSchema, async (request) => { + subscriptions.delete(request.params.uri); + return {}; + }); + + server.setRequestHandler(ListPromptsRequestSchema, async () => { + return { + prompts: [ + { + name: PromptName.SIMPLE, + description: "A prompt without arguments", + }, + { + name: PromptName.COMPLEX, + description: "A prompt with arguments", + arguments: [ + { + name: "temperature", + description: "Temperature setting", + required: true, + }, + { + name: "style", + description: "Output style", + required: false, + }, + ], + }, + { + name: PromptName.RESOURCE, + description: "A prompt that includes an embedded resource reference", + arguments: [ + { + name: "resourceId", + description: "Resource ID to include (1-100)", + required: true, + }, + ], + }, + ], + }; + }); + + server.setRequestHandler(GetPromptRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + if (name === PromptName.SIMPLE) { + return { + messages: [ + { + role: "user", + content: { + type: "text", + text: "This is a simple prompt without arguments.", + }, + }, + ], + }; + } + + if (name === PromptName.COMPLEX) { + return { + messages: [ + { + role: "user", + content: { + type: "text", + text: `This is a complex prompt with arguments: temperature=${args?.temperature}, style=${args?.style}`, + }, + }, + { + role: "assistant", + content: { + type: "text", + text: "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?", + }, + }, + { + role: "user", + content: { + type: "image", + data: MCP_TINY_IMAGE, + mimeType: "image/png", + }, + }, + ], + }; + } + + if (name === PromptName.RESOURCE) { + const resourceId = parseInt(args?.resourceId as string, 10); + if (isNaN(resourceId) || resourceId < 1 || resourceId > 100) { + throw new Error( + `Invalid resourceId: ${args?.resourceId}. Must be a number between 1 and 100.` + ); + } + + const resourceIndex = resourceId - 1; + const resource = ALL_RESOURCES[resourceIndex]; + + return { + messages: [ + { + role: "user", + content: { + type: "text", + text: `This prompt includes Resource ${resourceId}. Please analyze the following resource:`, + }, + }, + { + role: "user", + content: { + type: "resource", + resource: resource, + }, + }, + ], + }; + } + + throw new Error(`Unknown prompt: ${name}`); + }); + + server.setRequestHandler(ListToolsRequestSchema, async () => { + const tools: Tool[] = [ + { + name: ToolName.ECHO, + description: "Echoes back the input", + inputSchema: zodToJsonSchema(EchoSchema) as ToolInput, + }, + { + name: ToolName.ADD, + description: "Adds two numbers", + inputSchema: zodToJsonSchema(AddSchema) as ToolInput, + }, + { + name: ToolName.LONG_RUNNING_OPERATION, + description: + "Demonstrates a long running operation with progress updates", + inputSchema: zodToJsonSchema(LongRunningOperationSchema) as ToolInput, + }, + { + name: ToolName.SAMPLE_LLM, + description: "Samples from an LLM using MCP's sampling feature", + inputSchema: zodToJsonSchema(SampleLLMSchema) as ToolInput, + }, + { + name: ToolName.GET_TINY_IMAGE, + description: "Returns the MCP_TINY_IMAGE", + inputSchema: zodToJsonSchema(GetTinyImageSchema) as ToolInput, + }, + { + name: ToolName.ANNOTATED_MESSAGE, + description: + "Demonstrates how annotations can be used to provide metadata about content", + inputSchema: zodToJsonSchema(AnnotatedMessageSchema) as ToolInput, + }, + { + name: ToolName.GET_RESOURCE_REFERENCE, + description: + "Returns a resource reference that can be used by MCP clients", + inputSchema: zodToJsonSchema(GetResourceReferenceSchema) as ToolInput, + }, + ]; + + return { tools }; + }); + + server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + if (name === ToolName.ECHO) { + const validatedArgs = EchoSchema.parse(args); + return { + content: [{ type: "text", text: `Echo: ${validatedArgs.message}` }], + }; + } + + if (name === ToolName.ADD) { + const validatedArgs = AddSchema.parse(args); + const sum = validatedArgs.a + validatedArgs.b; + return { + content: [ + { + type: "text", + text: `The sum of ${validatedArgs.a} and ${validatedArgs.b} is ${sum}.`, + }, + ], + }; + } + + if (name === ToolName.LONG_RUNNING_OPERATION) { + const validatedArgs = LongRunningOperationSchema.parse(args); + const { duration, steps } = validatedArgs; + const stepDuration = duration / steps; + const progressToken = request.params._meta?.progressToken; + + for (let i = 1; i < steps + 1; i++) { + await new Promise((resolve) => + setTimeout(resolve, stepDuration * 1000) + ); + + if (progressToken !== undefined) { + await server.notification({ + method: "notifications/progress", + params: { + progress: i, + total: steps, + progressToken, + }, + }); + } + } + + return { + content: [ + { + type: "text", + text: `Long running operation completed. Duration: ${duration} seconds, Steps: ${steps}.`, + }, + ], + }; + } + + if (name === ToolName.SAMPLE_LLM) { + const validatedArgs = SampleLLMSchema.parse(args); + const { prompt, maxTokens } = validatedArgs; + + const result = await requestSampling( + prompt, + ToolName.SAMPLE_LLM, + maxTokens + ); + return { + content: [ + { type: "text", text: `LLM sampling result: ${result.content.text}` }, + ], + }; + } + + if (name === ToolName.GET_TINY_IMAGE) { + GetTinyImageSchema.parse(args); + return { + content: [ + { + type: "text", + text: "This is a tiny image:", + }, + { + type: "image", + data: MCP_TINY_IMAGE, + mimeType: "image/png", + }, + { + type: "text", + text: "The image above is the MCP tiny image.", + }, + ], + }; + } + + if (name === ToolName.GET_RESOURCE_REFERENCE) { + const validatedArgs = GetResourceReferenceSchema.parse(args); + const resourceId = validatedArgs.resourceId; + + const resourceIndex = resourceId - 1; + if (resourceIndex < 0 || resourceIndex >= ALL_RESOURCES.length) { + throw new Error(`Resource with ID ${resourceId} does not exist`); + } + + const resource = ALL_RESOURCES[resourceIndex]; + + return { + content: [ + { + type: "text", + text: `Returning resource reference for Resource ${resourceId}:`, + }, + { + type: "resource", + resource: resource, + }, + { + type: "text", + text: `You can access this resource using the URI: ${resource.uri}`, + }, + ], + }; + } + + if (name === ToolName.ANNOTATED_MESSAGE) { + const { messageType, includeImage } = AnnotatedMessageSchema.parse(args); + + const content = []; + + // Main message with different priorities/audiences based on type + if (messageType === "error") { + content.push({ + type: "text", + text: "Error: Operation failed", + annotations: { + priority: 1.0, // Errors are highest priority + audience: ["user", "assistant"], // Both need to know about errors + }, + }); + } else if (messageType === "success") { + content.push({ + type: "text", + text: "Operation completed successfully", + annotations: { + priority: 0.7, // Success messages are important but not critical + audience: ["user"], // Success mainly for user consumption + }, + }); + } else if (messageType === "debug") { + content.push({ + type: "text", + text: "Debug: Cache hit ratio 0.95, latency 150ms", + annotations: { + priority: 0.3, // Debug info is low priority + audience: ["assistant"], // Technical details for assistant + }, + }); + } + + // Optional image with its own annotations + if (includeImage) { + content.push({ + type: "image", + data: MCP_TINY_IMAGE, + mimeType: "image/png", + annotations: { + priority: 0.5, + audience: ["user"], // Images primarily for user visualization + }, + }); + } + + return { content }; + } + + throw new Error(`Unknown tool: ${name}`); + }); + + server.setRequestHandler(CompleteRequestSchema, async (request) => { + const { ref, argument } = request.params; + + if (ref.type === "ref/resource") { + const resourceId = ref.uri.split("/").pop(); + if (!resourceId) return { completion: { values: [] } }; + + // Filter resource IDs that start with the input value + const values = EXAMPLE_COMPLETIONS.resourceId.filter((id) => + id.startsWith(argument.value) + ); + return { completion: { values, hasMore: false, total: values.length } }; + } + + if (ref.type === "ref/prompt") { + // Handle completion for prompt arguments + const completions = + EXAMPLE_COMPLETIONS[argument.name as keyof typeof EXAMPLE_COMPLETIONS]; + if (!completions) return { completion: { values: [] } }; + + const values = completions.filter((value) => + value.startsWith(argument.value) + ); + return { completion: { values, hasMore: false, total: values.length } }; + } + + throw new Error(`Unknown reference type`); + }); + + server.setRequestHandler(SetLevelRequestSchema, async (request) => { + const { level } = request.params; + logLevel = level; + + // Demonstrate different log levels + await server.notification({ + method: "notifications/message", + params: { + level: "debug", + logger: "test-server", + data: `Logging level set to: ${logLevel}`, + }, + }); + + return {}; + }); + + const cleanup = async () => { + if (subsUpdateInterval) clearInterval(subsUpdateInterval); + if (logsUpdateInterval) clearInterval(logsUpdateInterval); + if (stdErrUpdateInterval) clearInterval(stdErrUpdateInterval); + }; + + return { server, cleanup }; +}; + +const MCP_TINY_IMAGE = + "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"; diff --git a/external-oauth/mcp-server/src/services/redisTransport.integration.test.ts b/external-oauth/mcp-server/src/services/redisTransport.integration.test.ts new file mode 100644 index 0000000..3fcc778 --- /dev/null +++ b/external-oauth/mcp-server/src/services/redisTransport.integration.test.ts @@ -0,0 +1,291 @@ +import { jest } from '@jest/globals'; +import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; +import { MockRedisClient, setRedisClient } from '../redis.js'; +import { + ServerRedisTransport, + redisRelayToMcpServer, + shutdownSession +} from './redisTransport.js'; +import { createMcpServer } from './mcp.js'; +import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; + +describe('Redis Transport Integration', () => { + let mockRedis: MockRedisClient; + + beforeEach(() => { + mockRedis = new MockRedisClient(); + setRedisClient(mockRedis); + jest.resetAllMocks(); + }); + + afterEach(() => { + mockRedis.clear(); + }); + + describe('MCP Initialization Flow', () => { + const sessionId = 'test-init-session'; + + it('should relay initialization request from client to server through Redis', async () => { + // 1. Start the server listening to Redis + const { server, cleanup: serverCleanup } = createMcpServer(); + const serverTransport = new ServerRedisTransport(sessionId); + serverTransport.onclose = serverCleanup; + await server.connect(serverTransport); + + // 2. Create a mock client transport (simulating the streamable HTTP client side) + const mockClientTransport: Transport = { + onmessage: undefined, + onclose: undefined, + onerror: undefined, + send: jest.fn(() => Promise.resolve()), + close: jest.fn(() => Promise.resolve()), + start: jest.fn(() => Promise.resolve()) + }; + + // 3. Set up the Redis relay (this is what happens in the HTTP handler) + const cleanup = await redisRelayToMcpServer(sessionId, mockClientTransport); + + // Track messages received by server + const serverReceivedMessages: JSONRPCMessage[] = []; + const originalServerOnMessage = serverTransport.onmessage; + serverTransport.onmessage = (message, extra) => { + serverReceivedMessages.push(message); + originalServerOnMessage?.(message, extra); + }; + + // 4. Simulate client sending initialization request + const initMessage: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'init-1', + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { + name: 'test-client', + version: '1.0.0' + } + } + }; + + // Trigger the client transport onmessage (simulates HTTP request) + mockClientTransport.onmessage?.(initMessage); + + // Wait for message to be relayed through Redis + await new Promise(resolve => setTimeout(resolve, 50)); + + // 5. Verify server received the init message + expect(serverReceivedMessages).toHaveLength(1); + expect(serverReceivedMessages[0]).toMatchObject({ + jsonrpc: '2.0', + id: 'init-1', + method: 'initialize' + }); + + // 6. Simulate server responding (this should get relayed back to client) + const initResponse: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'init-1', + result: { + protocolVersion: '2024-11-05', + capabilities: { + tools: {}, + prompts: {}, + resources: {} + }, + serverInfo: { + name: 'example-server', + version: '1.0.0' + } + } + }; + + await serverTransport.send(initResponse, { relatedRequestId: 'init-1' }); + + // Wait for response to be relayed back + await new Promise(resolve => setTimeout(resolve, 50)); + + // 7. Verify client transport received the response + expect(mockClientTransport.send).toHaveBeenCalledWith( + expect.objectContaining({ + jsonrpc: '2.0', + id: 'init-1', + result: expect.objectContaining({ + protocolVersion: '2024-11-05', + serverInfo: expect.objectContaining({ + name: 'example-server' + }) + }) + }), + { relatedRequestId: 'init-1' } + ); + + // Cleanup + await cleanup(); + await shutdownSession(sessionId); + serverCleanup(); // Clean up MCP server intervals + + // Ensure server transport is closed + await serverTransport.close(); + + await new Promise(resolve => setTimeout(resolve, 10)); + }); + + it('should handle tools/list request through Redis relay', async () => { + // Set up server and mock client + const { server, cleanup: serverCleanup } = createMcpServer(); + const serverTransport = new ServerRedisTransport(sessionId); + serverTransport.onclose = serverCleanup; + await server.connect(serverTransport); + + const mockClientTransport: Transport = { + onmessage: undefined, + onclose: undefined, + onerror: undefined, + send: jest.fn(() => Promise.resolve()), + close: jest.fn(() => Promise.resolve()), + start: jest.fn(() => Promise.resolve()) + }; + + const cleanup = await redisRelayToMcpServer(sessionId, mockClientTransport); + + // Send tools/list request + const toolsListMessage: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'tools-1', + method: 'tools/list', + params: {} + }; + + mockClientTransport.onmessage?.(toolsListMessage); + + // Wait for processing and response + await new Promise(resolve => setTimeout(resolve, 100)); + + // Verify client received a response with tools + expect(mockClientTransport.send).toHaveBeenCalledWith( + expect.objectContaining({ + jsonrpc: '2.0', + id: 'tools-1', + result: expect.objectContaining({ + tools: expect.any(Array) + }) + }), + undefined + ); + + // Cleanup + await cleanup(); + await shutdownSession(sessionId); + serverCleanup(); // Clean up MCP server intervals + + // Ensure server transport is closed + await serverTransport.close(); + + await new Promise(resolve => setTimeout(resolve, 10)); + }); + + it('should handle notifications through Redis relay', async () => { + // Set up server and mock client + const { server, cleanup: serverCleanup } = createMcpServer(); + const serverTransport = new ServerRedisTransport(sessionId); + serverTransport.onclose = serverCleanup; + await server.connect(serverTransport); + + const mockClientTransport: Transport = { + onmessage: undefined, + onclose: undefined, + onerror: undefined, + send: jest.fn(() => Promise.resolve()), + close: jest.fn(() => Promise.resolve()), + start: jest.fn(() => Promise.resolve()) + }; + + const cleanup = await redisRelayToMcpServer(sessionId, mockClientTransport); + + // Set up notification subscription manually since notifications don't have an id + const notificationChannel = `mcp:shttp:toclient:${sessionId}:__GET_stream`; + const notificationCleanup = await mockRedis.createSubscription(notificationChannel, async (redisMessageJson) => { + const redisMessage = JSON.parse(redisMessageJson); + if (redisMessage.type === 'mcp') { + await mockClientTransport.send(redisMessage.message, redisMessage.options); + } + }, (error) => { + mockClientTransport.onerror?.(error); + }); + + // Send a notification from server (notifications don't have an id) + const notification: JSONRPCMessage = { + jsonrpc: '2.0', + method: 'notifications/message', + params: { + level: 'info', + logger: 'test', + data: 'Test notification' + } + }; + + await serverTransport.send(notification); + + // Wait for notification to be delivered + await new Promise(resolve => setTimeout(resolve, 50)); + + // Verify client received the notification + expect(mockClientTransport.send).toHaveBeenCalledWith( + expect.objectContaining({ + jsonrpc: '2.0', + method: 'notifications/message', + params: expect.objectContaining({ + level: 'info', + data: 'Test notification' + }) + }), + undefined + ); + + // Cleanup notification subscription + await notificationCleanup(); + + // Cleanup + await cleanup(); + await shutdownSession(sessionId); + serverCleanup(); // Clean up MCP server intervals + + // Ensure server transport is closed + await serverTransport.close(); + + await new Promise(resolve => setTimeout(resolve, 10)); + }); + + it('should not create response subscriptions for notifications', async () => { + const mockClientTransport: Transport = { + onmessage: undefined, + onclose: undefined, + onerror: undefined, + send: jest.fn(() => Promise.resolve()), + close: jest.fn(() => Promise.resolve()), + start: jest.fn(() => Promise.resolve()) + }; + + const cleanup = await redisRelayToMcpServer(sessionId, mockClientTransport); + + // Send a notification (no id field) + const notification: JSONRPCMessage = { + jsonrpc: '2.0', + method: 'notifications/initialized', + params: {} + }; + + mockClientTransport.onmessage?.(notification); + + // Wait for processing + await new Promise(resolve => setTimeout(resolve, 50)); + + // Should not create any response channel subscriptions for notifications + // (we can't easily test this directly, but we can ensure no errors occur) + expect(mockClientTransport.send).not.toHaveBeenCalled(); + + await cleanup(); + }); + }); +}); \ No newline at end of file diff --git a/external-oauth/mcp-server/src/services/redisTransport.test.ts b/external-oauth/mcp-server/src/services/redisTransport.test.ts new file mode 100644 index 0000000..60e7e33 --- /dev/null +++ b/external-oauth/mcp-server/src/services/redisTransport.test.ts @@ -0,0 +1,536 @@ +import { jest } from '@jest/globals'; +import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; +import { MockRedisClient, setRedisClient } from '../redis.js'; +import { + ServerRedisTransport, + redisRelayToMcpServer, + isLive, + shutdownSession, + setSessionOwner, + getSessionOwner, + validateSessionOwnership, + isSessionOwnedBy +} from './redisTransport.js'; +import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; + +describe('Redis Transport', () => { + let mockRedis: MockRedisClient; + + beforeEach(() => { + mockRedis = new MockRedisClient(); + setRedisClient(mockRedis); + jest.resetAllMocks(); + }); + + afterEach(() => { + // Clear all Redis data and subscriptions + mockRedis.clear(); + }); + + describe('ServerRedisTransport', () => { + let transport: ServerRedisTransport; + const sessionId = 'test-session-123'; + + beforeEach(() => { + transport = new ServerRedisTransport(sessionId); + }); + + afterEach(async () => { + if (transport) { + await transport.close(); + } + }); + + it('should create transport with session ID', () => { + expect(transport).toBeInstanceOf(ServerRedisTransport); + }); + + it('should send response messages to request-specific channels', async () => { + const responseMessage: JSONRPCMessage = { + jsonrpc: '2.0', + id: 123, + result: { data: 'test response' } + }; + + const mockSubscriber = jest.fn(); + await mockRedis.createSubscription( + `mcp:shttp:toclient:${sessionId}:123`, + mockSubscriber, + jest.fn() + ); + + await transport.send(responseMessage, { relatedRequestId: 123 }); + + expect(mockSubscriber).toHaveBeenCalledWith( + JSON.stringify({ + type: 'mcp', + message: responseMessage, + options: { relatedRequestId: 123 } + }) + ); + }); + + it('should send notification messages to notification channel', async () => { + const notificationMessage: JSONRPCMessage = { + jsonrpc: '2.0', + method: 'notifications/message', + params: { message: 'test notification' } + }; + + const mockSubscriber = jest.fn(); + await mockRedis.createSubscription( + `mcp:shttp:toclient:${sessionId}:__GET_stream`, + mockSubscriber, + jest.fn() + ); + + await transport.send(notificationMessage); + + expect(mockSubscriber).toHaveBeenCalledWith( + JSON.stringify({ + type: 'mcp', + message: notificationMessage, + options: undefined + }) + ); + }); + + it('should handle close gracefully', async () => { + const onCloseMock = jest.fn(); + transport.onclose = onCloseMock; + + await transport.close(); + + expect(onCloseMock).toHaveBeenCalled(); + }); + + it('should respond to shutdown control messages', async () => { + await transport.start(); + + const onCloseMock = jest.fn(); + transport.onclose = onCloseMock; + + // Send a shutdown control message + await shutdownSession(sessionId); + + // Wait for async processing + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(onCloseMock).toHaveBeenCalled(); + }); + + it('should receive MCP messages from clients and call onmessage', async () => { + const onMessageMock = jest.fn(); + transport.onmessage = onMessageMock; + + await transport.start(); + + // Simulate client sending a message to server + const clientMessage: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'test-req', + method: 'tools/list', + params: {} + }; + + await mockRedis.publish( + `mcp:shttp:toserver:${sessionId}`, + JSON.stringify({ + type: 'mcp', + message: clientMessage, + extra: { authInfo: { token: 'test-token', clientId: 'test-client', scopes: [] } } + }) + ); + + // Wait for async processing + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(onMessageMock).toHaveBeenCalledWith( + clientMessage, + { authInfo: { token: 'test-token', clientId: 'test-client', scopes: [] } } + ); + + await transport.close(); + }); + }); + + + describe('redisRelayToMcpServer', () => { + let mockTransport: Transport; + const sessionId = 'test-session-456'; + + beforeEach(() => { + mockTransport = { + onmessage: undefined, + onclose: undefined, + onerror: undefined, + send: jest.fn(() => Promise.resolve()), + close: jest.fn(() => Promise.resolve()), + start: jest.fn(() => Promise.resolve()) + }; + }); + + it('should set up message relay from transport to server', async () => { + const cleanup = await redisRelayToMcpServer(sessionId, mockTransport); + + // Simulate a message from the transport + const requestMessage: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'req-123', + method: 'tools/list', + params: {} + }; + + // Trigger the onmessage handler + mockTransport.onmessage?.(requestMessage, { authInfo: { token: 'test-token', clientId: 'test-client', scopes: [] } }); + + // Wait a bit for async processing + await new Promise(resolve => setTimeout(resolve, 10)); + + // Check that message was published to server channel + const serverSubscriber = jest.fn(); + await mockRedis.createSubscription( + `mcp:shttp:toserver:${sessionId}`, + serverSubscriber, + jest.fn() + ); + + // The message should have been published + expect(mockRedis.numsub(`mcp:shttp:toserver:${sessionId}`)).resolves.toBe(1); + + await cleanup(); + }); + + it('should subscribe to response channel for request messages', async () => { + const cleanup = await redisRelayToMcpServer(sessionId, mockTransport); + + const requestMessage: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'req-456', + method: 'tools/call', + params: { name: 'echo', arguments: { text: 'hello' } } + }; + + // Trigger the onmessage handler + mockTransport.onmessage?.(requestMessage, { authInfo: { token: 'test-token', clientId: 'test-client', scopes: [] } }); + + // Wait for subscription setup + await new Promise(resolve => setTimeout(resolve, 10)); + + // Now simulate a response from the server + const responseMessage: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'req-456', + result: { content: [{ type: 'text', text: 'hello' }] } + }; + + await mockRedis.publish( + `mcp:shttp:toclient:${sessionId}:req-456`, + JSON.stringify({ + type: 'mcp', + message: responseMessage, + options: undefined + }) + ); + + // Check that the response was sent back to the transport + expect(mockTransport.send).toHaveBeenCalledWith(responseMessage, undefined); + + await cleanup(); + }); + + it('should not subscribe for notification messages (no id)', async () => { + const cleanup = await redisRelayToMcpServer(sessionId, mockTransport); + + const notificationMessage: JSONRPCMessage = { + jsonrpc: '2.0', + method: 'notifications/message', + params: { message: 'test' } + }; + + // Trigger the onmessage handler + mockTransport.onmessage?.(notificationMessage); + + // Wait a bit + await new Promise(resolve => setTimeout(resolve, 10)); + + // Should not create any response channel subscriptions + expect(await mockRedis.numsub(`mcp:shttp:toclient:${sessionId}:undefined`)).toBe(0); + + await cleanup(); + }); + }); + + describe('isLive', () => { + const sessionId = 'test-session-789'; + + it('should return true when session has active subscribers', async () => { + // Create a subscription to the server channel + await mockRedis.createSubscription( + `mcp:shttp:toserver:${sessionId}`, + jest.fn(), + jest.fn() + ); + + expect(await isLive(sessionId)).toBe(true); + }); + + it('should return false when session has no subscribers', async () => { + expect(await isLive(sessionId)).toBe(false); + }); + }); + + describe('Session Ownership', () => { + const sessionId = 'test-session-ownership'; + const userId = 'test-user-123'; + + it('should set and get session owner', async () => { + await setSessionOwner(sessionId, userId); + const owner = await getSessionOwner(sessionId); + expect(owner).toBe(userId); + }); + + it('should validate session ownership correctly', async () => { + await setSessionOwner(sessionId, userId); + + expect(await validateSessionOwnership(sessionId, userId)).toBe(true); + expect(await validateSessionOwnership(sessionId, 'different-user')).toBe(false); + }); + + it('should check if session is owned by user including liveness', async () => { + // Session not live yet + expect(await isSessionOwnedBy(sessionId, userId)).toBe(false); + + // Make session live + await mockRedis.createSubscription( + `mcp:shttp:toserver:${sessionId}`, + jest.fn(), + jest.fn() + ); + + // Still false because no owner set + expect(await isSessionOwnedBy(sessionId, userId)).toBe(false); + + // Set owner + await setSessionOwner(sessionId, userId); + + // Now should be true + expect(await isSessionOwnedBy(sessionId, userId)).toBe(true); + + // False for different user + expect(await isSessionOwnedBy(sessionId, 'different-user')).toBe(false); + }); + }); + + describe('Integration: Redis message flow', () => { + const sessionId = 'integration-test-session'; + + it('should relay messages between client and server through Redis', async () => { + // Set up client-side transport simulation + const clientTransport: Transport = { + onmessage: undefined, + onclose: undefined, + onerror: undefined, + send: jest.fn(() => Promise.resolve()), + close: jest.fn(() => Promise.resolve()), + start: jest.fn(() => Promise.resolve()) + }; + + const cleanup = await redisRelayToMcpServer(sessionId, clientTransport); + + // Client sends a request + const listToolsRequest: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'integration-req-1', + method: 'tools/list', + params: {} + }; + + // Set up subscription to simulate server receiving the message + const serverSubscriber = jest.fn(); + await mockRedis.createSubscription( + `mcp:shttp:toserver:${sessionId}`, + serverSubscriber, + jest.fn() + ); + + // Simulate client sending request + clientTransport.onmessage?.(listToolsRequest); + + // Wait for async processing + await new Promise(resolve => setTimeout(resolve, 10)); + + // Verify the message was published to server channel + expect(serverSubscriber).toHaveBeenCalledWith( + JSON.stringify({ + type: 'mcp', + message: listToolsRequest, + extra: undefined, + options: undefined + }) + ); + + // Simulate server sending response + const serverResponse: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'integration-req-1', + result: { tools: [{ name: 'echo', description: 'Echo tool' }] } + }; + + await mockRedis.publish( + `mcp:shttp:toclient:${sessionId}:integration-req-1`, + JSON.stringify({ + type: 'mcp', + message: serverResponse, + options: undefined + }) + ); + + // Wait for response processing + await new Promise(resolve => setTimeout(resolve, 10)); + + // Verify the response was sent back to client + expect(clientTransport.send).toHaveBeenCalledWith(serverResponse, undefined); + + await cleanup(); + }); + }); + + describe('Control Messages', () => { + const sessionId = 'test-control-session'; + + it('should send shutdown control messages', async () => { + const controlSubscriber = jest.fn(); + await mockRedis.createSubscription( + `mcp:control:${sessionId}`, + controlSubscriber, + jest.fn() + ); + + await shutdownSession(sessionId); + + const callArgs = controlSubscriber.mock.calls[0][0] as string; + const message = JSON.parse(callArgs); + + expect(message.type).toBe('control'); + expect(message.action).toBe('SHUTDOWN'); + expect(typeof message.timestamp).toBe('number'); + }); + + it('should properly shutdown server transport via control message', async () => { + const transport = new ServerRedisTransport(sessionId); + const onCloseMock = jest.fn(); + transport.onclose = onCloseMock; + + await transport.start(); + + // Send shutdown signal + await shutdownSession(sessionId); + + // Wait for async processing + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(onCloseMock).toHaveBeenCalled(); + }); + }); + + describe('Inactivity Timeout', () => { + const sessionId = 'test-inactivity-session'; + + beforeEach(() => { + jest.useFakeTimers({ doNotFake: ['setImmediate', 'nextTick'] }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should shutdown session after 5 minutes of inactivity', async () => { + const transport = new ServerRedisTransport(sessionId); + const shutdownSpy = jest.spyOn(mockRedis, 'publish'); + + await transport.start(); + + // Fast-forward time by 5 minutes + jest.advanceTimersByTime(5 * 60 * 1000); + + // Should have published shutdown control message + expect(shutdownSpy).toHaveBeenCalledWith( + `mcp:control:${sessionId}`, + expect.stringContaining('"action":"SHUTDOWN"') + ); + + await transport.close(); + }); + + it('should reset timeout when message is received', async () => { + const transport = new ServerRedisTransport(sessionId); + const onMessageMock = jest.fn(); + transport.onmessage = onMessageMock; + + await transport.start(); + + // Fast-forward 4 minutes + jest.advanceTimersByTime(4 * 60 * 1000); + + // Manually publish a message to trigger the subscription handler + const testMessage = { jsonrpc: '2.0', method: 'ping' }; + await mockRedis.publish( + `mcp:shttp:toserver:${sessionId}`, + JSON.stringify({ + type: 'mcp', + message: testMessage + }) + ); + + // Wait for message to be processed + await new Promise(resolve => setImmediate(resolve)); + + // Verify message was received + expect(onMessageMock).toHaveBeenCalledWith(testMessage, undefined); + + // Clear the publish spy to check only future calls + const shutdownSpy = jest.spyOn(mockRedis, 'publish'); + shutdownSpy.mockClear(); + + // Fast-forward 4 more minutes (total 8, but only 4 since last message) + jest.advanceTimersByTime(4 * 60 * 1000); + + // Should not have shutdown yet + expect(shutdownSpy).not.toHaveBeenCalledWith( + `mcp:control:${sessionId}`, + expect.stringContaining('"action":"SHUTDOWN"') + ); + + // Fast-forward 2 more minutes to exceed timeout + jest.advanceTimersByTime(2 * 60 * 1000); + + // Now should have shutdown + expect(shutdownSpy).toHaveBeenCalledWith( + `mcp:control:${sessionId}`, + expect.stringContaining('"action":"SHUTDOWN"') + ); + + await transport.close(); + }, 10000); + + it('should clear timeout on close', async () => { + const transport = new ServerRedisTransport(sessionId); + const shutdownSpy = jest.spyOn(mockRedis, 'publish'); + + await transport.start(); + + // Close transport before timeout + await transport.close(); + + // Fast-forward past timeout + jest.advanceTimersByTime(10 * 60 * 1000); + + // Should not have triggered shutdown + expect(shutdownSpy).not.toHaveBeenCalledWith( + `mcp:control:${sessionId}`, + expect.stringContaining('"action":"SHUTDOWN"') + ); + }); + }); +}); \ No newline at end of file diff --git a/external-oauth/mcp-server/src/services/redisTransport.ts b/external-oauth/mcp-server/src/services/redisTransport.ts new file mode 100644 index 0000000..e35eb9e --- /dev/null +++ b/external-oauth/mcp-server/src/services/redisTransport.ts @@ -0,0 +1,351 @@ +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { redisClient } from "../redis.js"; +import { Transport, TransportSendOptions } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js"; +import { JSONRPCMessage, MessageExtraInfo } from "@modelcontextprotocol/sdk/types.js"; +import { logger } from "../utils/logger.js"; + +let redisTransportCounter = 0; +const notificationStreamId = "__GET_stream"; + +// Message types for Redis transport +type RedisMessage = + | { + type: 'mcp'; + message: JSONRPCMessage; + extra?: MessageExtraInfo; + options?: TransportSendOptions; + } + | { + type: 'control'; + action: 'SHUTDOWN' | 'PING' | 'STATUS'; + timestamp?: number; + }; + +function sendToMcpServer(sessionId: string, message: JSONRPCMessage, extra?: { authInfo?: AuthInfo; }, options?: TransportSendOptions): Promise { + const toServerChannel = getToServerChannel(sessionId); + + logger.debug('Sending message to MCP server via Redis', { + sessionId, + channel: toServerChannel, + method: ('method' in message ? message.method : undefined), + id: ('id' in message ? message.id : undefined) + }); + + const redisMessage: RedisMessage = { type: 'mcp', message, extra, options }; + return redisClient.publish(toServerChannel, JSON.stringify(redisMessage)); +} + +function getToServerChannel(sessionId: string): string { + return `mcp:shttp:toserver:${sessionId}`; +} + +function getToClientChannel(sessionId: string, relatedRequestId: string): string { + return `mcp:shttp:toclient:${sessionId}:${relatedRequestId}`; +} + +function getControlChannel(sessionId: string): string { + return `mcp:control:${sessionId}`; +} + +function sendControlMessage(sessionId: string, action: 'SHUTDOWN' | 'PING' | 'STATUS'): Promise { + const controlChannel = getControlChannel(sessionId); + const redisMessage: RedisMessage = { + type: 'control', + action, + timestamp: Date.now() + }; + return redisClient.publish(controlChannel, JSON.stringify(redisMessage)); +} + +export async function shutdownSession(sessionId: string): Promise { + logger.info('Sending shutdown control message', { sessionId }); + return sendControlMessage(sessionId, 'SHUTDOWN'); +} + +export async function isLive(sessionId: string): Promise { + // Check if the session is live by checking if the key exists in Redis + const numSubs = await redisClient.numsub(getToServerChannel(sessionId)); + return numSubs > 0; +} + +export async function setSessionOwner(sessionId: string, userId: string): Promise { + logger.debug('Setting session owner', { sessionId, userId }); + await redisClient.set(`session:${sessionId}:owner`, userId); +} + +export async function getSessionOwner(sessionId: string): Promise { + return await redisClient.get(`session:${sessionId}:owner`); +} + +export async function validateSessionOwnership(sessionId: string, userId: string): Promise { + const owner = await getSessionOwner(sessionId); + return owner === userId; +} + +export async function isSessionOwnedBy(sessionId: string, userId: string): Promise { + const isLiveSession = await isLive(sessionId); + if (!isLiveSession) { + logger.debug('Session not live', { sessionId }); + return false; + } + const isOwned = await validateSessionOwnership(sessionId, userId); + logger.debug('Session ownership check', { sessionId, userId, isOwned }); + return isOwned; +} + + +export async function redisRelayToMcpServer(sessionId: string, transport: Transport, isGetRequest: boolean = false): Promise<() => Promise> { + logger.debug('Setting up Redis relay to MCP server', { + sessionId, + isGetRequest + }); + + let redisCleanup: (() => Promise) | undefined = undefined; + const cleanup = async () => { + // TODO: solve race conditions where we call cleanup while the subscription is being created / before it is created + if (redisCleanup) { + logger.debug('Cleaning up Redis relay', { sessionId }); + await redisCleanup(); + } + } + + const subscribe = async (requestId: string) => { + const toClientChannel = getToClientChannel(sessionId, requestId); + + logger.debug('Subscribing to client channel', { + sessionId, + requestId, + channel: toClientChannel + }); + + redisCleanup = await redisClient.createSubscription(toClientChannel, async (redisMessageJson) => { + const redisMessage = JSON.parse(redisMessageJson) as RedisMessage; + if (redisMessage.type === 'mcp') { + logger.debug('Relaying message from Redis to client', { + sessionId, + requestId, + method: ('method' in redisMessage.message ? redisMessage.message.method : undefined) + }); + await transport.send(redisMessage.message, redisMessage.options); + } + }, (error) => { + logger.error('Error in Redis relay subscription', error, { + sessionId, + channel: toClientChannel + }); + transport.onerror?.(error); + }); + } + + if (isGetRequest) { + await subscribe(notificationStreamId); + } else { + const messagePromise = new Promise((resolve) => { + transport.onmessage = async (message, extra) => { + // First, set up response subscription if needed + if ("id" in message) { + logger.debug('Setting up response subscription', { + sessionId, + messageId: message.id, + method: ('method' in message ? message.method : undefined) + }); + await subscribe(message.id.toString()); + } + // Now send the message to the MCP server + await sendToMcpServer(sessionId, message, extra); + resolve(message); + } + }); + + messagePromise.catch((error) => { + transport.onerror?.(error); + cleanup(); + }); + } + return cleanup; +} + + +// New Redis transport for server->client messages using request-id based channels +export class ServerRedisTransport implements Transport { + private counter: number; + private _sessionId: string; + private controlCleanup?: (() => Promise); + private serverCleanup?: (() => Promise); + private shouldShutdown = false; + private inactivityTimeout?: NodeJS.Timeout; + private readonly INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes + + onclose?: (() => void) | undefined; + onerror?: ((error: Error) => void) | undefined; + onmessage?: ((message: JSONRPCMessage, extra?: { authInfo?: AuthInfo; }) => void) | undefined; + + constructor(sessionId: string) { + this.counter = redisTransportCounter++; + this._sessionId = sessionId; + } + + private resetInactivityTimer(): void { + // Clear existing timeout if any + if (this.inactivityTimeout) { + clearTimeout(this.inactivityTimeout); + } + + // Set new timeout + this.inactivityTimeout = setTimeout(() => { + logger.info('Session timed out due to inactivity', { + sessionId: this._sessionId, + timeoutMs: this.INACTIVITY_TIMEOUT_MS + }); + void shutdownSession(this._sessionId); + }, this.INACTIVITY_TIMEOUT_MS); + } + + private clearInactivityTimer(): void { + if (this.inactivityTimeout) { + clearTimeout(this.inactivityTimeout); + this.inactivityTimeout = undefined; + } + } + + async start(): Promise { + logger.info('Starting ServerRedisTransport', { + sessionId: this._sessionId, + inactivityTimeoutMs: this.INACTIVITY_TIMEOUT_MS + }); + + // Start inactivity timer + this.resetInactivityTimer(); + + // Subscribe to MCP messages from clients + const serverChannel = getToServerChannel(this._sessionId); + logger.debug('Subscribing to server channel', { + sessionId: this._sessionId, + channel: serverChannel + }); + + this.serverCleanup = await redisClient.createSubscription( + serverChannel, + (messageJson) => { + const redisMessage = JSON.parse(messageJson) as RedisMessage; + if (redisMessage.type === 'mcp') { + // Reset inactivity timer on each message from client + this.resetInactivityTimer(); + + logger.debug('Received MCP message from client', { + sessionId: this._sessionId, + method: ('method' in redisMessage.message ? redisMessage.message.method : undefined), + id: ('id' in redisMessage.message ? redisMessage.message.id : undefined) + }); + + this.onmessage?.(redisMessage.message, redisMessage.extra); + } + }, + (error) => { + logger.error('Error in server channel subscription', error, { + sessionId: this._sessionId, + channel: serverChannel + }); + this.onerror?.(error); + } + ); + + // Subscribe to control messages for shutdown + const controlChannel = getControlChannel(this._sessionId); + logger.debug('Subscribing to control channel', { + sessionId: this._sessionId, + channel: controlChannel + }); + + this.controlCleanup = await redisClient.createSubscription( + controlChannel, + (messageJson) => { + const redisMessage = JSON.parse(messageJson) as RedisMessage; + if (redisMessage.type === 'control') { + logger.info('Received control message', { + sessionId: this._sessionId, + action: redisMessage.action + }); + + if (redisMessage.action === 'SHUTDOWN') { + logger.info('Shutting down transport due to control message', { + sessionId: this._sessionId + }); + this.shouldShutdown = true; + this.close(); + } + } + }, + (error) => { + logger.error('Error in control channel subscription', error, { + sessionId: this._sessionId, + channel: controlChannel + }); + this.onerror?.(error); + } + ); + + } + + async send(message: JSONRPCMessage, options?: TransportSendOptions): Promise { + const relatedRequestId = options?.relatedRequestId?.toString() ?? ("id" in message ? message.id?.toString() : notificationStreamId); + const channel = getToClientChannel(this._sessionId, relatedRequestId) + + logger.debug('Sending message to client', { + sessionId: this._sessionId, + channel, + method: ('method' in message ? message.method : undefined), + id: ('id' in message ? message.id : undefined), + relatedRequestId + }); + + const redisMessage: RedisMessage = { type: 'mcp', message, options }; + const messageStr = JSON.stringify(redisMessage); + await redisClient.publish(channel, messageStr); + } + + async close(): Promise { + logger.info('Closing ServerRedisTransport', { + sessionId: this._sessionId, + wasShutdown: this.shouldShutdown + }); + + // Clear inactivity timer + this.clearInactivityTimer(); + + // Clean up server message subscription + if (this.serverCleanup) { + await this.serverCleanup(); + this.serverCleanup = undefined; + } + + // Clean up control message subscription + if (this.controlCleanup) { + await this.controlCleanup(); + this.controlCleanup = undefined; + } + + this.onclose?.(); + } +} + +export async function getShttpTransport(sessionId: string, onsessionclosed: (sessionId: string) => void | Promise, isGetRequest: boolean = false): Promise { + logger.debug('Getting StreamableHTTPServerTransport for existing session', { + sessionId, + isGetRequest + }); + + // Giving undefined here and setting the sessionId means the + // transport wont try to create a new session. + const shttpTransport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + onsessionclosed, + }) + shttpTransport.sessionId = sessionId; + + // Use the new request-id based relay approach + const cleanup = await redisRelayToMcpServer(sessionId, shttpTransport, isGetRequest); + shttpTransport.onclose = cleanup; + return shttpTransport; +} \ No newline at end of file diff --git a/external-oauth/mcp-server/src/static/index.html b/external-oauth/mcp-server/src/static/index.html new file mode 100644 index 0000000..2e3eeb9 --- /dev/null +++ b/external-oauth/mcp-server/src/static/index.html @@ -0,0 +1,97 @@ + + + + + + MCP Example Server + + + +
+
+ +

MCP Example Server

+
+ +

+ A comprehensive reference implementation of the Model Context Protocol (MCP) server + demonstrating all protocol features with full authentication support and horizontal scalability. +

+ +
+
+

Complete MCP Support

+

All MCP features including tools, resources, prompts, sampling, completions, and logging with full protocol compliance.

+
+
+

Multiple Transports

+

Streamable HTTP (SHTTP) and Server-Sent Events (SSE) transports for flexible client integration.

+
+
+

OAuth 2.0 Authentication

+

Complete OAuth flow with PKCE support and a built-in fake provider for testing and development.

+
+
+

Horizontal Scalability

+

Redis-backed session management enables multi-instance deployments with automatic load distribution.

+
+
+

7 Demo Tools

+

Echo, add, long-running operations, LLM sampling, image handling, annotations, and resource references.

+
+
+

100+ Resources

+

Example resources with pagination, templates, subscriptions, and real-time update notifications.

+
+
+ +
+

API Endpoints

+
+
+ POST + /mcp - Initialize sessions or send messages (Streamable HTTP) +
+
+ GET + /mcp - Establish SSE streams (Streamable HTTP) +
+
+ DELETE + /mcp - Terminate sessions (Streamable HTTP) +
+
+ GET + /sse - Legacy SSE transport endpoint +
+
+ POST + /message - Legacy message endpoint for SSE transport +
+
+
+ + +
+ + + + \ No newline at end of file diff --git a/external-oauth/mcp-server/src/static/mcp.png b/external-oauth/mcp-server/src/static/mcp.png new file mode 100644 index 0000000000000000000000000000000000000000..86c52662d890dd8ebf115d6c228f36748c77176e GIT binary patch literal 40652 zcmZs@cU+TK8#kT-iEOP3%2Yv6K~Nd80$P+I0t!S>8L=pcvV^cfLTRN>6;Y-#LRBCV z6i~_v>u#Mz0Zl+qR@4FwTLBpY`CTXWdEWQ)etv)S)Ar7N&biJtzt_0=&k;u(8A(M+ z91bUAXM4aIhZBSU6vM3|!M_qzw3Fap5+Sz7!*DpMwdfx_E+=mT4z~$scVNG3RNh!$ zO$y#N4}7hX7uG&J#*eeqZ(f+7#15@Vy{2Jz zPPt-Lh0N3;{zoh|(SD=l9KM;@gl`~6;iqoU<2CHkH0*NpFhy(v+mHQ)^$FVQFVqm8 z;mHvRHJLoa4D>?Dmxu&1zYp7u%?g5I#LT7h$Z-I@Y7N58<%xA%vVACI)7w1)F=JZb~y6)oM5>Kxxki3xrV%(Xq|>3p_H;E~slhIa;Huaapox~@{cot}eSWHFGGU>;*qD9lI+-qZdysP8v(=T|} znEkz7uyhC=G<4eoPgcfxFefw(&-z8UN$N7&enLbksRrk?7MsQ9eK@1(D=m+(xwzGv(C#jkox(ACwwA+({reUSXh}0S zuEWcP`|;IyN88A7t~sZIx-stEX+qyEsyE5%^q6csFT;@(Q5P#Q@?@8rlO)xZ;7t$G9d0{__9*UVO)b8w? zwOv$eCcUw1mqFxMud1+|I>PC>-VsKuu!^|~7Mo@eg5Ad63uJA$%6(D|1{80k5Q^Jq zTltUae(L_tsw7#0Z_%(UqMb+x5X&o=IIGcG860i=8#>1h&lK-XoA^PawWiWs`3NFX z8DCCRJshc*Cf}WjedLAxirypP$%SU}b}l2n{Oy9egYcfSI^$tMN1La)j0;d&T&{fYh$yVUfM+OS?-N8}(hc&AC7> z8dgKp>5I8b7*qOww*7U%Jw_ECq#3j_sEkTpUvgosQ}Hm`-;O8dzTQ}?FWmOrB{@7I z>?rXbX-T&)(DvNbe;?rQNx&udXc&&F3!+65ClKC{bZsLQ(sJh7X5vJ;VSyjTDHPXB z+9iD&nrRR{QpJxF6j=2Vc1fRiO|{G{i5{jgWD>Qx`9BrzkHK~Cq@*Gma-?WJ{1!TS zv;V6J5;Kq4jxOnd>lUls(Pv_-D6to(R5Wae-b@gW(g_LY_*^q^)&-x_9*k~fie>fn znA+u>W8p5dhE3o-|FQ5^VTwR@KiBpvsp3dXQ#!o%RKCJ6#D$}O%|L#|kTQ(6^O1;> z3^~bBzh+>hO8Vm19uLpB?)V6?Ag4%T+S4`_tXN=zWT?m*YS#I4>-P6fSg4>*^u!J@o!y~GaaU|mx`2_XJ zWF{Foww;X70i&sXF=xvC2fmN@o%^zM6Z??5VSgV>EEb;}Q6PJErok{@A94H|Bb(lO z%ep-ut4UNKzOej38m~kMCOV3%Iw~A+xjp9kPBVWedhrM&yZ%hf-@USN5>cuZ2TXl- z;@{xBbJU>W2V}$h4Wg@4M8RZ>wrY!n1F65V#gj@y-*L*nn0F}&|-VDD!!A&}qKR0x`I;|u;g=0$O zrORH4={jYxJzoZK>}hz$umLS01AdwP(8kg>i#NwLwZQs!&1K02kMmUsZQ}1z-53$PxwHC3q91Gewg9g~Xi%!PRQY{(WR$5CViny;n9OqP%_$Ae8vh_@ zIJ=nnNI7w!khph+e0P(KK>tc+%wm@vJ1E9 zvgoK1^PiVW1=Q-He>R{orC9i#6AgOQ=;p}a&5e& zZE~ozr?d{UtQj3Mz&MyVS?KmA=VO-8uQ2SkX0V`pfJpG`&E-{P!wW-F6&pfaV-DVz zP?5Wr)ApZt;$y2k(q%_B)ZyKYJR{K|7juN_yi$LMe=0uvTbpW~Q>)Mn@indR`FmNA zV-MF&C+XmqG#Y-vgyQ@k0;6Vw_)yQANM zM`AAt7CEw?ZWtST2BS0f1WP0^y-Z3+cF=gRLmYO#K3^lpB9fSTs0&5J9A2()Ew9wq zA*!*PB)D>xj*G?36(&(WSX?iwgllvVb;x-+UOP)i<}gM0eFkSntYHx?{%j%2N8&-n zOH?pPY@;CaA={CZ<>YtZj#Rg7?RJ%!HD4akQ^4aEHV3RYIh9|dQrmuTH_cy@VnGX2 zBOK0s_~_5%C?KGPa14Jj*Gr{z#Lq~WV~1s7tpa}P-I~HtZCJdUbWbub-D`d6hz){` zX9FzBQ->KPk=wzV;$e4#OD(rtu{y!}2kfgPd3(PDAEmnSwc3=`*01^n?;uwAzr>Xi z6Ku+S9JY9gbDaN#T{==CTH>x*D9$3%D%a80~mmIYyb=UHA zLoaNjsq=rGVY^Xnx|d4B2R$VDzt7M%ZdP;Xa)pbluY&ku+W3yzc?zt^y6`XD8$y|b zT+W&cm%HK+ZHL}2?Hxaon;;`IwT|C3!{4|@&!Ni&tt3PdLgHh7E@daZi0lL~Jx%P^ zqcE%=xAUqX6mVDJs&PTK!IX)#M=ia$o9fKkO>N z#Tip-tucLGI69JnGb2P6x;xbN|_0WKNJM#+an#>}#r$%%W5r#H7|gkoo6Udixpy4ZudsEYu#?v%$M7a?>3>}$SWJMAHHp{8w)ZL zwi*fb;97@v1V7{vFJLQ1o|forv8cr4KtsWP{ux1>tv~h-^AzAsjwBb3YN4GotZp;N zTy0lst}q)Yt0K(PVvfjmP_gd?i?DjT^n7ft&^K480CZIqdiaw)vQ+bkRe_v~z-dW> z*Xwxivg%&)*v+=&jxBtu0B?Fk&9=)CetdiI_tu2mR-q~!d!HarYIBn7z?*7$n!&c> z8KZv(tmL`|(V~J()tpBe@!X{F-`?1Z7V59+R*N!jl%%%zVFQKJn`O=_U~0Ui2*wwu zZ@-I0osXF`96K^}VZ zIr7`)yj8}V8b@3^RWL`O%SwF7C1$nUEBSAWb8zHhx7?g5hu#SEBS}Z1_nPWUVx>1H z2riUE8tfmQIpjjE!8siswo(*t3!5G@6Xc*{f5odgmG7a3t5=)>4Y-RtMxL;j8^dom z)098dg)#}3-*m9&ewX>?}LVB^>E~#1r$}95Uqu8OUI|uKz*byb zn=eeQ&>PW-5@$wtPxZ}=9HkBu&ll<6HX&vaRwRslNP8~GO;KnHlimeh*d31N#+-V! z!cpXH>u~bKtB&Q$)Y)fTMxp-g|KLlBYJ?!kGk@m=xiN;xhu89lZ@rUkH58mcvXNyx zNmMIxOJOfkDo&WH=X@-?`H->{@h${k;kG!r=hr1^8uMOS8475c7I|Y@g3jCQwusGr zy0rEQk4y5)8@jhG?^WE2Jr&%ReeNiTfjvw5m(&WpwzEfcXoGa?4zdnW`Ow!1-QfGY zgz}>Vj||Wnwle0|bpOVAUcP(Ra-`}O+9drG>BFRmQbjXJ zP$zlX`>D$~ck}!;;AEWi7uzCpzcdiv98w6{)So3H(H+ofn?pH)8osW=-Bg`zBrG@+ z7q@eHui6&=XZm{*R*D0;`$|?AO|fPqk5FxTmTJg1#Fg9Tqp6F*mIl~v!EG7ry1*0| zo+3_D;r}@^;zS(~lehCbPzgx0>3P(SWJ1~XvOlg}>26)k8|?b_syWS*e|x6IiRw*O zz1j8)xL>yo!8V1XyEHJ%LMbC$7i$!0D3@VFRA9NqvlrQ|39&6f()^Qx5Q7f_L5#Hc z)I9wGiGNDY1XvBV7i%n2u)cuZbfifSSPf^3H|5IOSUK8toumW=#Hw*W(s%FB~|h6OOo zPL4A2c?T64PK6>Kx?^9kK*9QacCS(2CYlMqgKkXvCq?j1=5J~F*0-lb@{swif*2AT zW5{Ah7ERx>phfe4hqp$a)0a<>@7>hp;^KCSZtw>rj}x}#f4k0JMQ7dK$?vhGj+G3JqfqYMRo8m^u& zSLhZ3YWKAmolE8C(f3(D=J93&R6?j6+mx?oIM#?KwCMr*v~Usja_e^|4i^*meD9TA z=0D1?=N>%QD-bAxEDYtkWoq{}hPt#{VJ~`Ww zVs?QZe{$F`JuKWyM4JQQ%Y%hr=eH@JeiU6*&Ld0q`6{z5o*&<2T1_DG>b{Xjjyf|J zU-vFlt-{Y#ZG>neyoyJ7Bk?1Rp)&&r9{IA*Z%oNE>d-%?W}Z;|DAdFS|Gp5+^#l}! zm|+qUZ@S6nZ$m*&jclzkFe6$9N;=<7=?SPe*%M++W?S+iV<$t2i{Gg4WZ%^oe(~2m zZmeQ-;5zY!osC;uGFubwFzz#yc=(N0kRc41@+aum6(i$BY}k$A1)Vq8LE_9jw9z~`sjAKijniXk~ zukuvl%#OqV#`}0EZuY>TK3|LUcU`mEZs?c%%{}-)Nh!gp9CHoVuW&F`A0b9ew4FZE z<||N^)?npSIu(lyAiY)ihoCc?T}EVHL(bJNy5dA{LCaER+xMf4>_RC++@zhzmWy+Q z?S*b$Y>s?XoM<=vgdO=ngLeMUo3|yf=(kAx0v@_W&VPo4l>#d4h&OUTZoLcuXw-(N z!=`dpxFtW1J8|lR=o-Z`6}zSqc-p#DPuYMQ=xj>jS1$f_!Sc5Jx^LJzr@jWSMrtB0N!Dh)H{WDNHs<8{;Fh$fw(z%K^wqTE{z5!9 z*1zE{MT=n1(!XtyD=b#Z)nZNWCe;6-dRW2vXjf#jWin1EJ@${T0mQO>bB(9o{(AR| z^5pw7fdq>T*<{UtmH6pq27|);*)_dIMoS^0V0QyCiqm{FO+&`E&Zen{aQ92b001wrK{bj1-18ulaBG4wgpyArTehO-HG*ErOX;JHOMecos`a zXE`&Okq(`$7lqG$(Xg{JZmo;cD*dATVeTqqlLP3QXB2obf-6_ppJ`9Vh39%bu>`zX z;j_yccCC0-DUbY>s$-qQ0V_Q&Wh}zgL-0O;c%Zw5Ay<4^r!^@Inj=MAADgto+E9jU zh}B}(ajGzAdO($C$JY{soc;6X6{xse`QxU-AkN7DY|yx@EIsm?hQB%fBw=rH!gcEG z1GQsrJx5?I~&M06%M{yn=0!0Y1Ow%TfMM zh%53Hp{D8|ar0iBZtgluW1VD@ZjS0nNVSysfAt+Rt{TQr(9)5~Lz1~jyqD4T+<2Rx zHg1k3rR$LN`S^>`7Yn+A3!vy31XL9ea@ivkQ3RGXHdK+N=1bhW(e}I+XOW{@YtULo zl-*VkTl6d!vbB32$;4jfN;tK%o{M6hdB_Q%-jwLRIsf{FqZ8D*%H1i~N8_QbrD(wS z$5aH~uCp2SzYp91@(+bcMmdZe(bbv@CtKL7!FQHhHvTXfM_u{08 z!$p_%42O(ql{51h8iE9z-V~=;ld@{b2SJHr!xl~$6YeR>P6DW z8RJZi6cbvo2vNj~m?9gp-*gk(^y!c#(Os<8SOr&dA(BoOH+2G_uR8Ui?6qrbu%s7a z2_ro5G9vdiR6DL*DV=zwZD-k;f!rIGS1GDof`VYVYotpX}F|DvA*RPQ;?eI5Qs!D?G#gmy)O z$pL*z)>bH9MOC#XRJIDkM95`#2o(rMDS&)M)86N{E+4W7=QJtB@M2-_)&Wm#R&~ac z=?TO(RP;h3(b}aA67LE;m1ST!L6QyG3lt~fgR8&cArTuycnIYHmU7*ODq{Wz@;xjl z_Nf=_xcl(I4#G75KZg>AMd%9Oi?}O`mp=ruz}rV}D~5K}L>nmt8ZQoBHJOLU>()la zpj%ZFf)0}pPzyqK_mcQL!7f{$56`eWTpzDA100qp@ZRYV^`9 zErRHMX&oE*NdoWf)V?342Q+D9{ulbb*Q!fD{dX3?_006ZYJNIeG$GC3EvSPPjL(7h zE>{*0ECr{d_@i7R_9Jb?4u~kRbZ|G=oa1%J$-fctqbenwv;Lmw|#YMhz=zv;S4yIejZTMdFzrg{h*M z@~fIlyZbkxU|El!D<CnIs6Y%_*&CH1fhOY52$F3iVhCf&aSZ5$$A6 z{2WX4IwTJUW{`GXq8n?pPS@e2iiYLvxE*@|cCBO{?JUB;+1{0l+B~(-bkoT8;B4XU zd^t$w2*&yu``+rmvhi+F1at83dH%L>p zYF@D1vuuBx9hPW_*oX1}&CIY3%mQSK@x1rf42puppo4f41_yBN(#EFCQn%Tbgj9$z z@z{C5P4s`QD`M_(g1<)J@v>%z_TA96#bt?IbrOc&{Z=(Gcz^O`sN%zS=T{1H(awI5 zgb}y+pNRBoG^TU`9XNN%dH+QPD{3?Tm}u{l2mm!=K_U}VtyNc9ZO1D!-!Py@fp(Q< z(2CPxq4Qm~M@rQX2sigNC-IR8c@-ad#0RuZZ(v+A<@t<{398cmVvtm z<>Qt0M>;PI8eKv)q_}MmRQd?!+mT4j=3AQ!n?K*0>4kcWIO-l#zLt(W6ZHoougiA{F;cz2n3&zLuEopOm!Kq(+X&>`?}RJ)<}GK+VbW?R~Y zeQKNW0GB1>t}<^GQ|;Ed8+lbwg#;rw-jfW?IWR>(Vk8S>vAUnhtkm$=z@r9&UoQEpc5d4H+?_8Tq{>=ZRnyL;wv8C$3` zkPZ0sE&eiLzySd(+^C3>$wV?2f8Aiu1fkl#JuuB+8>Wd-1sgr6$!z;HENR)b;+TZqEapvW_z@HzvPh0#@ziG0zh;F@y1FIlnL_dKe!E|ZOvzp* zGm|LMMkM50bmNY~ul3er!&vr< z;UKJ;?q)S1F(wuuV?Ly}a{NJV@(auJD^G+?CKW1E%KRd11^dc5AC`by>d#hL)VBP5 zWFOc_w|Fhs2pfFbW^_g=uGs-%U{NCK$IWK;*n{uzs5>_A=c&I!q79ct7p#B&QL-=4 zMi0LtWBjmQtABLcGhVWvX;Ruc{1~Y>ml@vaBEPBJVc04jJBOKoyJQ>48wpeJ@`M$N zifn7G8E5yx8vX)yB6UMsimq8ivg<{q+AYh;9akFZ3-$CB4gV8(=rb1ic|h6I4g7Lb zXlh(dylY_4dyg%mT4;OWqgxxzj*M$zJ)H4H^~xenPtg$zt@$k>XBOq|`?~~TF^5fq z(+qYp6zBuJB|GH^$h}+80{Kor&PX!$;hW{^s>nK&N^zXbB2-zsSDDLq%Nff?ZSoXz zC3s z$0ITF{d?&JS#9YWJ9qI1S2h`z|1aY8vZyWpzKo54Z$U-YXQ{!RGuhYNt32Gr5%O3~del}*0W0|C>*OuF&! zSj(^@xqIob3cm!~+m~o!JYeT%1Fh+)#fg};RfDQD=+;4TO4=h6;M3I&MZ70A2N%}e z(aGYye-)>#x9TEpjPUZ%u)iyQl&D4yvQzL#|H7sRY4|hgtd4g}HPN2jN&o~uvCO>l zVF!!gFs>@?DE~Qx3vbmiRd!%b+iPQdmY~eOL01LW+L=EXhQUOGzcVC;eh$<=adVMz zUs45mw%nhfb<=NM?ec#? zKs6_%zG>J6NVS+5F@e|Ht`5bd(2hUqW6EBRX{nO|546RO(no1L*M1*^rWasW5_Gmf zI0lH<_C!njWUKTq=lL52y=Olkx+9@HbRXZkU~Q_DW}w7h7Ti3#*23_jv*<|3k-*}} z5E3N@jxZtaX)g1l6M|bels)tzLdvAk>f27&)ey8ZWKzGF5+AQ&3KIvEaW}N)-nCS$Fm{m=iJEIVciEw7lfEK&1>IOcgfWl<yK%{Q1}yqm=u+c2disCXXaX95;+m)|NlhF8 z12QpHb~nmje+H)~kd1YHD#hJ+&4AS6-voml`b@Si*+^8_3PJ%BK{aq|4)cuajW^K@P**5BQ+a8is~^mJ^l)4&hn+tgGjC@-UF?# z>X^IlG>hqw;&!OK+&x=HU)~Bm{5AYs!Er~b0%24Xk$%RAaOSY6Weh8oYN590@Q*#_ zEXcd-fLTU8YQ0jw)?ME-)=r(|-tovQrz!NW`m7$$Rd1JS-YzvG)yX8^>?1lcEKaVFBN%K?qX__}mPTg(Qoo%%KCa^1W0KX^g%uDNuzyZDwv4Y3|B z@E&)Ja4}1E;zqt-b_-V*Yu6MPZ!+UL7 z9-uRxJwFN~CW^+;OYfcuC}t02nMH|^@`?YuuFYq|vRQl7#xn@Odg}LfeE{~Jx3w}OeojxIe;eBFKwWZ7z}>JFW^6cSCX{qfB;&x+6Q^k=jP znlLAx{tfm;&!$pQu~P(DJy9F`mIeO-)`|Rf{oq;*PCLOE2E_Y_LSkl>RTaKB6lxXw z>3*v!0H&%bdUQ)77p88I!xG6?92DX6_ZS(-Y6Y z?Y!&cwILcr>3!yjJUtBK{hPF!{+UBc_awX_+hRK~SAW1_>g_A<@m%#}_|& zG+3PplQM<`fWPrEzRv$I{Ehk^P_lN{(Qv;#cN?GbQ=qpm^j@I9aQdnE$t{?mIftu| z54jj)&HX7-PcNqp^#T2OkZOcuB5wi6!VI{PE8Gji2o6)&RR0WaO=7`cO!{^Bd&X>{d@Kz z38s9kAtgM;zlrv;_-Ww>7`9lz>kyZAQ+~02UMNAhnwtFXe|S1G*ZvK{k%Qw8 zTNBb-SR2|QK;`_XaNJAI*iaTGf4Bw&RkBAq)894Rw(gj&sF5jcyaPzhC*K2ISY)`x z{1BNwLHuG13nj5>j+IUoZDL#f4(@D*fecJ*EAc9?uq{Mwq^*esfGyY$m#2?{*DqJz zF?hSdZgBC5Cyb30e+SSQQ(RMixXYQlj#v!<c#m;;=e-2)FLp4C1?;{dkt)TZ$ z|E^L>^Hj`jBm5-b091Pi{Wyg4%2Q*%NHFs;(tr7y<#u#4qLoNV+Ji z^KF6vrGB}Ifxz%IhB}%`3fi_;yYT!r|Fo9nCQ-#A7zBwB_9UwPJosONtAz}3mib}l zay0YAvy*uF=L2ZjcZ@zpEKmPZj4HuPB;(G+0R*em@RuQEKslxaxI=Asj9n+hp9wI<^Pi6_>oMlW@N7qNS==6FuNF+PH19Z1E!&jbS z9k^T!XA-b}h=(`7?;H{qArah9RDjYC`%dB$ks%2KP>xNC@-S6*zRc+35$RZjU6+%y zc!g8fS@zB|d!KrVq=5Ri;8WL%Qs}Z?37NR<$O1_L&>re|IAOlA)hw8J?F6ym_SYtl zz&g2Z5o}B)={f?c@}BE>4^vp`r~&U zyB>krsu+rsG!w3{QEr39`5ly(i*t+0mKL@XG$qu$Uk zdJaBlIj@znl|Mec%iS<+0vB-@sYweeN)Bo+16Yq)~II6dOmU5 z*w#=#2+ss4vd9Qzc}mL{G*r-R8b9D7}-VSMJ!Io zQ{O)W#%)+Owa{`l5`mFQMm-OIwEYp5E4mXQBnI&%+9Vy#qa6;SY_B&pG-06=!I0kC z$$sB@?>u}3f~J=2BW>I)rx)wlkNKFRUo z^1(^oU)qMI-Dbmsk54$kc5+!(nId|dBoJaoEsBI)qg9@XdQKmym2#2feNA}xt;+-E zx-G{Id9Lk8>8D|*ZA&+ludDvW=7_oLA=>mju$rAYO+yIk ziHyJ$`xzO55(cULYvr1SW|i9QdFjcVN-?9uLI>;PHPhdxSB3&jM{+Cz+dcUm+?(<|}?(jGUFF9?RF0e_=s7)4AjvK}w*Andc22(ji= z5HGlMj&ZN$2tV@Fk5T;=C@#5-y%AC>Rf9n=A&H#b>3t3_Yqv*YJxhC6MNRHYIPVxX z{GkJHaZUH1eM>t$wf(ngJ!f81z0Y1WtZ)_uXB2cNLcjXwGM*9KL^(O*XYk~+M#b&B z0|xU_9k>ZEj^Y9ebcgo!3LYJX`N5*ZwLEny^-O8kuOcScm@4N?*$g~NP!tRJ)|Roj z4%thb$+VUAqZG)an+31J<$mJUSV%uvgn>Z^LY7|7X|B2}Rg3t4ip}3}592I@Ju2Eu3?d60yAGWrRqUARv6EJAZ4iz5K_lQja%q=gs24uXs6mWFiMP#_ zuYNb5Mg8+PA)k08qS3z%ApD_o-`D*rKHuj-y0Ql_Jf!NrZt~ku(faJ4dxbqH$NV3L zcc|5$rayhrTqvA?XAy$I)52f)o-IW56DN(?g?6S>W(O0+Dpxy`-`9k=R*Z-M&FP_qM8 znTR}L)xv-tN~(_!nOrGPUs^}&$JZB0?OmXqoLR9@xkhR*_q==y|HeTfa8or*`5kgttx#kG(}1$(dqf4l zas)^JTiaIV_tW=vOs}5$6&V8h{7cqWz3hv0;ljHjciBHW^TspFG7avud|!7GP?-0Q z6knKK&2LTMeH-MB$D}yH(Vbc3MChj?3cRk#+WPbs?MY>%^I)(AJ}%H`-2l`1Ei-pr zIu#hlp$ZW(=pqfnH~?>*X^qH1j|Ga#Xk4IR*ze}r9eVfV9lHE%8CGaCCc(bTE@utq zQ$Ox{u3gmQmsdfbW*niKG|=y(VV(MaSU2`J-$;J+ZUV&q7!Jzd@f!-{xjF075)>!}3=R8%#y z$ziv6e)bCE-3VRY65@(V+zRfZiIws(*=(1S-;#lN?CHfUlkJ1IK8y%rWW=X_LUTpK zFjolQWPVq>nbP8lz7&9PDWQz)rzO9;RMhr?>N_M_?V>twF?__!kbZtqkRt=Oh9;G_5J_yF*++i?-kDMHZk3`QqQ^gu22bTS|9E659b7uf3QR?O?iz80N|evih)? zP* Wt$UO#QT4xF{!Cg`V=wfbRA8HV?}G@QPh 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 literal 0 HcmV?d00001 diff --git a/external-oauth/mcp-server/src/static/styles.css b/external-oauth/mcp-server/src/static/styles.css new file mode 100644 index 0000000..c3dcd08 --- /dev/null +++ b/external-oauth/mcp-server/src/static/styles.css @@ -0,0 +1,188 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + background: #ffffff; + color: #000000; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; + flex: 1; +} + +header { + display: flex; + align-items: center; + gap: 2rem; + margin-bottom: 3rem; + padding-bottom: 2rem; + border-bottom: 2px solid #000000; +} + +.logo { + width: 80px; + height: 80px; + background: #000000; + padding: 10px; + border-radius: 8px; +} + +h1 { + font-size: 2.5rem; + font-weight: 700; + letter-spacing: -0.02em; +} + +.tagline { + font-size: 1.25rem; + color: #666666; + margin-bottom: 3rem; + line-height: 1.6; +} + +.features { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; + margin-bottom: 3rem; +} + +.feature-card { + border: 2px solid #000000; + padding: 1.5rem; + background: #ffffff; + transition: all 0.2s ease; +} + +.feature-card:hover { + background: #000000; + color: #ffffff; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.feature-card h3 { + font-size: 1.25rem; + margin-bottom: 0.75rem; + font-weight: 600; +} + +.feature-card p { + line-height: 1.6; + opacity: 0.9; +} + +.endpoints { + background: #f5f5f5; + border: 2px solid #000000; + padding: 2rem; + margin-bottom: 3rem; +} + +.endpoints h2 { + font-size: 1.75rem; + margin-bottom: 1.5rem; + font-weight: 600; +} + +.endpoint-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.endpoint { + font-family: 'Courier New', monospace; + background: #ffffff; + padding: 0.75rem 1rem; + border: 1px solid #000000; + display: flex; + align-items: center; + gap: 1rem; +} + +.method { + font-weight: bold; + min-width: 80px; +} + +.method.get { color: #0066cc; } +.method.post { color: #009900; } +.method.delete { color: #cc0000; } + +.links { + display: flex; + gap: 2rem; + flex-wrap: wrap; + margin-bottom: 3rem; +} + +.link-button { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 1rem 2rem; + background: #000000; + color: #ffffff; + text-decoration: none; + font-weight: 600; + transition: all 0.2s ease; + border: 2px solid #000000; +} + +.link-button:hover { + background: #ffffff; + color: #000000; +} + +.link-button.secondary { + background: #ffffff; + color: #000000; +} + +.link-button.secondary:hover { + background: #000000; + color: #ffffff; +} + +footer { + background: #000000; + color: #ffffff; + padding: 2rem; + text-align: center; +} + +footer a { + color: #ffffff; + text-decoration: underline; +} + +@media (max-width: 768px) { + h1 { + font-size: 2rem; + } + + header { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .logo { + width: 60px; + height: 60px; + background: #000000; + padding: 8px; + border-radius: 6px; + } +} \ No newline at end of file diff --git a/external-oauth/mcp-server/src/types.ts b/external-oauth/mcp-server/src/types.ts new file mode 100644 index 0000000..1cfde8e --- /dev/null +++ b/external-oauth/mcp-server/src/types.ts @@ -0,0 +1,33 @@ +/** + * OAuth 2.0 Token Introspection Response + * Based on RFC 7662: https://tools.ietf.org/html/rfc7662 + * Used when validating tokens with the external authorization server. + */ +export interface TokenIntrospectionResponse { + /** Whether the token is currently active */ + active: boolean; + /** Space-separated list of scopes associated with the token */ + scope?: string; + /** Client identifier for the OAuth client that requested the token */ + client_id?: string; + /** Human-readable identifier for the resource owner */ + username?: string; + /** Type of the token (e.g., "Bearer") */ + token_type?: string; + /** Expiration time as seconds since Unix epoch */ + exp?: number; + /** Time at which the token was issued as seconds since Unix epoch */ + iat?: number; + /** Time before which the token is not valid as seconds since Unix epoch */ + nbf?: number; + /** Subject identifier for the resource owner */ + sub?: string; + /** Intended audience for the token */ + aud?: string | string[]; + /** Issuer of the token */ + iss?: string; + /** Unique identifier for the token */ + jti?: string; + /** Custom field for our implementation to store user ID */ + userId?: string; +} diff --git a/external-oauth/mcp-server/src/utils/logger.ts b/external-oauth/mcp-server/src/utils/logger.ts new file mode 100644 index 0000000..850c385 --- /dev/null +++ b/external-oauth/mcp-server/src/utils/logger.ts @@ -0,0 +1,205 @@ +import { AsyncLocalStorage } from 'async_hooks'; +import { Request, Response, NextFunction } from 'express'; + +// Severity levels as per Google Cloud Logging +export enum LogSeverity { + DEFAULT = 'DEFAULT', + DEBUG = 'DEBUG', + INFO = 'INFO', + NOTICE = 'NOTICE', + WARNING = 'WARNING', + ERROR = 'ERROR', + CRITICAL = 'CRITICAL', + ALERT = 'ALERT', + EMERGENCY = 'EMERGENCY' +} + +interface LogContext { + trace?: string; + spanId?: string; + requestId?: string; + userAgent?: string; + method?: string; + path?: string; + [key: string]: string | undefined; +} + +interface StructuredLogEntry { + severity: LogSeverity; + message: string; + timestamp: string; + 'logging.googleapis.com/trace'?: string; + 'logging.googleapis.com/spanId'?: string; + [key: string]: unknown; +} + +class StructuredLogger { + private asyncLocalStorage = new AsyncLocalStorage(); + private projectId: string | undefined; + + constructor() { + // Get project ID from environment or metadata server + this.projectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCP_PROJECT; + } + + /** + * Run a function with a specific logging context + */ + runWithContext(context: LogContext, fn: () => T): T { + return this.asyncLocalStorage.run(context, fn); + } + + /** + * Extract trace context from Cloud Run request + */ + extractTraceContext(req: Request): LogContext { + const context: LogContext = {}; + + const traceHeader = req.header('X-Cloud-Trace-Context'); + if (traceHeader && this.projectId) { + const [trace, spanId] = traceHeader.split('/'); + context.trace = `projects/${this.projectId}/traces/${trace}`; + if (spanId) { + context.spanId = spanId.split(';')[0]; // Remove any trace flags + } + } + + // Add other useful request context + context.requestId = req.header('X-Request-Id'); + context.userAgent = req.header('User-Agent'); + context.method = req.method; + context.path = req.path; + + return context; + } + + /** + * Create Express middleware for request context + */ + middleware() { + return (req: Request, res: Response, next: NextFunction) => { + const context = this.extractTraceContext(req); + this.runWithContext(context, () => { + next(); + }); + }; + } + + /** + * Log a structured message + */ + private log(severity: LogSeverity, message: string, metadata?: Record) { + const context = this.asyncLocalStorage.getStore() || {}; + + const entry: StructuredLogEntry = { + severity, + message, + timestamp: new Date().toISOString(), + ...metadata + }; + + // Add trace context if available + if (context.trace) { + entry['logging.googleapis.com/trace'] = context.trace; + } + if (context.spanId) { + entry['logging.googleapis.com/spanId'] = context.spanId; + } + + // Add any other context fields + Object.keys(context).forEach(key => { + if (key !== 'trace' && key !== 'spanId') { + entry[`context.${key}`] = context[key]; + } + }); + + // Output as JSON for Cloud Logging + console.log(JSON.stringify(entry)); + } + + // Convenience methods for different severity levels + debug(message: string, metadata?: Record) { + this.log(LogSeverity.DEBUG, message, metadata); + } + + info(message: string, metadata?: Record) { + this.log(LogSeverity.INFO, message, metadata); + } + + notice(message: string, metadata?: Record) { + this.log(LogSeverity.NOTICE, message, metadata); + } + + warning(message: string, metadata?: Record) { + this.log(LogSeverity.WARNING, message, metadata); + } + + error(message: string, error?: Error, metadata?: Record) { + const errorMetadata = { + ...metadata, + error: error ? { + name: error.name, + message: error.message, + stack: error.stack + } : undefined + }; + this.log(LogSeverity.ERROR, message, errorMetadata); + } + + critical(message: string, metadata?: Record) { + this.log(LogSeverity.CRITICAL, message, metadata); + } + + alert(message: string, metadata?: Record) { + this.log(LogSeverity.ALERT, message, metadata); + } + + emergency(message: string, metadata?: Record) { + this.log(LogSeverity.EMERGENCY, message, metadata); + } + + /** + * Add additional context to the current async context + */ + addContext(context: LogContext) { + const currentContext = this.asyncLocalStorage.getStore(); + if (currentContext) { + Object.assign(currentContext, context); + } + } +} + +// Export singleton instance +export const logger = new StructuredLogger(); + +// Re-export for convenience +export type { LogContext, StructuredLogEntry }; + +// MCP-specific message logging +import { JSONRPCError, JSONRPCNotification, JSONRPCRequest, JSONRPCResponse } from "@modelcontextprotocol/sdk/types.js"; + +export function logMcpMessage( + message: JSONRPCError | JSONRPCNotification | JSONRPCRequest | JSONRPCResponse, + sessionId: string, +) { + if ("method" in message) { + if (message.method === "tools/call") { + logger.info('Processing MCP method', { + sessionId, + method: message.method, + toolName: message.params?.name + }); + } else { + logger.info('Processing MCP method', { + sessionId, + method: message.method + }); + } + } else if ("error" in message) { + logger.warning('Received error message', { + sessionId, + errorMessage: message.error.message, + errorCode: message.error.code + }); + } +} \ No newline at end of file diff --git a/external-oauth/mcp-server/tsconfig.json b/external-oauth/mcp-server/tsconfig.json new file mode 100644 index 0000000..fee103a --- /dev/null +++ b/external-oauth/mcp-server/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "Node16", + "moduleResolution": "Node16", + "sourceMap": true, + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/package-lock.json b/package-lock.json index 47aeec8..42eb162 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,23 @@ { - "name": "mcp-server-everything", + "name": "mcp-server-feature-reference", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "mcp-server-everything", + "name": "mcp-server-feature-reference", + "version": "0.1.0", + "workspaces": [ + "integrated-mode", + "separate-mode/auth-server", + "separate-mode/mcp-server" + ], + "devDependencies": { + "concurrently": "^8.2.0" + } + }, + "integrated-mode": { + "name": "mcp-server-feature-reference-integrated", "version": "0.1.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.15.1", @@ -23,7 +35,6 @@ "@types/express": "^5.0.0", "@types/jest": "^29.5.14", "@types/node": "^22.10.0", - "concurrently": "^8.2.0", "jest": "^29.7.0", "ts-jest": "^29.2.5", "tsx": "^4.19.2", @@ -5489,6 +5500,18 @@ "node": ">= 0.4" } }, + "node_modules/mcp-server-feature-reference-auth-server": { + "resolved": "separate-mode/auth-server", + "link": true + }, + "node_modules/mcp-server-feature-reference-integrated": { + "resolved": "integrated-mode", + "link": true + }, + "node_modules/mcp-server-feature-reference-mcp-server": { + "resolved": "separate-mode/mcp-server", + "link": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -7247,6 +7270,58 @@ "peerDependencies": { "zod": "^3.24.1" } + }, + "separate-mode/auth-server": { + "name": "mcp-server-feature-reference-auth-server", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@redis/client": "^1.6.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-rate-limit": "^8.0.1", + "raw-body": "^3.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/content-type": "^1.1.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + } + }, + "separate-mode/mcp-server": { + "name": "mcp-server-feature-reference-mcp-server", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@redis/client": "^1.6.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-rate-limit": "^8.0.1", + "raw-body": "^3.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/content-type": "^1.1.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + } } } } diff --git a/package.json b/package.json index 2820ca8..665252e 100644 --- a/package.json +++ b/package.json @@ -1,52 +1,44 @@ { - "name": "mcp-server-everything", + "name": "mcp-server-feature-reference", "version": "0.1.0", - "description": "Example MCP Server", - "type": "module", - "main": "dist/src/index.js", + "description": "Feature Reference MCP Server - Examples for Embedded and External OAuth", + "private": true, + "workspaces": [ + "embedded-oauth", + "external-oauth/auth-server", + "external-oauth/mcp-server" + ], "scripts": { - "start": "node dist/src/index.js", - "start:auth-server": "node dist/auth-server/index.js", - "dev": "tsx watch --inspect src/index.ts", - "dev:break": "tsx --inspect-brk watch src/index.ts", - "dev:integrated": "AUTH_MODE=integrated npm run dev", - "dev:separate": "AUTH_MODE=separate AUTH_SERVER_URL=http://localhost:3001 npm run dev", - "dev:auth-server": "AUTH_SERVER_PORT=3001 tsx watch --inspect auth-server/index.ts", - "dev:with-separate-auth": "concurrently -n \"AUTH,MCP\" -c \"yellow,cyan\" \"npm run dev:auth-server\" \"npm run dev:separate\"", - "build": "tsc && npm run copy-static", - "copy-static": "mkdir -p dist/src/static && cp -r src/static/* dist/src/static/", - "lint": "eslint src/ auth-server/ shared/", - "test": "NODE_OPTIONS=--experimental-vm-modules jest", - "test:integrated": "AUTH_MODE=integrated npm test", - "test:separate": "AUTH_MODE=separate npm test", - "test:e2e:integrated": "./scripts/test-integrated-e2e.sh", - "test:e2e:separate": "./scripts/test-separate-e2e.sh" + "dev:embedded": "npm run dev --workspace=embedded-oauth", + "dev:auth-server": "npm run dev --workspace=external-oauth/auth-server", + "dev:mcp-server": "npm run dev --workspace=external-oauth/mcp-server", + "dev:external": "concurrently -n \"AUTH,MCP\" -c \"yellow,cyan\" \"npm run dev:auth-server\" \"npm run dev:mcp-server\"", + + "build": "npm run build --workspaces --if-present", + "build:embedded": "npm run build --workspace=embedded-oauth", + "build:external": "npm run build --workspace=external-oauth/auth-server && npm run build --workspace=external-oauth/mcp-server", + + "start:embedded": "npm run start --workspace=embedded-oauth", + "start:auth-server": "npm run start --workspace=external-oauth/auth-server", + "start:mcp-server": "npm run start --workspace=external-oauth/mcp-server", + "start:external": "concurrently -n \"AUTH,MCP\" -c \"yellow,cyan\" \"npm run start:auth-server\" \"npm run start:mcp-server\"", + + "test": "npm run test --workspaces --if-present", + "test:embedded": "npm run test --workspace=embedded-oauth", + "test:external": "npm run test --workspace=external-oauth/auth-server && npm run test --workspace=external-oauth/mcp-server", + "test:e2e:embedded": "./scripts/test-embedded-e2e.sh", + "test:e2e:external": "./scripts/test-external-e2e.sh", + "test:e2e": "npm run test:e2e:embedded && npm run test:e2e:external", + + "lint": "npm run lint --workspaces --if-present", + "lint:embedded": "npm run lint --workspace=embedded-oauth", + "lint:external": "npm run lint --workspace=external-oauth/auth-server && npm run lint --workspace=external-oauth/mcp-server", + + "typecheck": "npm run typecheck --workspaces --if-present", + "typecheck:embedded": "npm run typecheck --workspace=embedded-oauth", + "typecheck:external": "npm run typecheck --workspace=external-oauth/auth-server && npm run typecheck --workspace=external-oauth/mcp-server" }, "devDependencies": { - "@eslint/js": "^9.15.0", - "@types/content-type": "^1.1.8", - "@types/cors": "^2.8.17", - "@types/express": "^5.0.0", - "@types/jest": "^29.5.14", - "@types/node": "^22.10.0", - "concurrently": "^8.2.0", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "typescript-eslint": "^8.18.0" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.15.1", - "@redis/client": "^1.6.0", - "cors": "^2.8.5", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "express-rate-limit": "^8.0.1", - "raw-body": "^3.0.0" - }, - "overrides": { - "@types/express": "^5.0.0", - "@types/express-serve-static-core": "^5.0.2" + "concurrently": "^8.2.0" } } diff --git a/scripts/test-integrated-e2e.sh b/scripts/test-embedded-e2e.sh similarity index 93% rename from scripts/test-integrated-e2e.sh rename to scripts/test-embedded-e2e.sh index ceebcc7..7a1b608 100755 --- a/scripts/test-integrated-e2e.sh +++ b/scripts/test-embedded-e2e.sh @@ -2,12 +2,12 @@ set -e echo "==================================================" -echo "End-to-End Test - Integrated Mode" +echo "End-to-End Test - Embedded OAuth" echo "==================================================" # Kill any existing servers echo "🛑 Cleaning up existing servers..." -pkill -f "node.*dist/src/index" || true +pkill -f "node.*dist.*index" || true pkill -f "tsx watch.*src/index" || true sleep 2 @@ -39,9 +39,9 @@ if [ "${AUTH_MODE:-integrated}" != "integrated" ]; then echo " Or use: ./scripts/test-separate-e2e.sh" fi -# Start MCP server in integrated mode -echo "🚀 Starting MCP server in integrated mode..." -AUTH_MODE=integrated npm start & +# Start MCP server with embedded OAuth +echo "Starting MCP server with embedded OAuth..." +(cd embedded-oauth && npm start) & MCP_PID=$! sleep 5 @@ -83,7 +83,7 @@ AUTH_CODE=$(echo "$AUTH_PAGE" | grep -o 'state=[^"&]*' | cut -d= -f2) # OAuth Step 4: User Authentication & Authorization # In a real flow, the user would authenticate with the auth server here # For testing, we simulate this with the fake upstream auth endpoint -CALLBACK_RESPONSE=$(curl -s -i "$MCP_SERVER/fakeupstreamauth/callback?state=$AUTH_CODE&code=fakecode&userId=$USER_ID") +CALLBACK_RESPONSE=$(curl -s -i "$MCP_SERVER/mock-upstream-idp/callback?state=$AUTH_CODE&code=mock-auth-code&userId=$USER_ID") # OAuth Step 5: Authorization Code Redirect # Verify the auth server redirects back to our redirect_uri with the code and state @@ -225,11 +225,17 @@ else fi echo "" -echo "🎉 INTEGRATED MODE E2E TEST COMPLETE!" +echo "EMBEDDED OAUTH E2E TEST COMPLETE!" echo "=====================================" echo "📊 Verification Results:" echo " Tools: $TOOL_COUNT (README: 7) $([ "$TOOL_COUNT" = "7" ] && echo "✅" || echo "❌")" echo " Resources: $RESOURCE_COUNT (README: 100) $([ "$RESOURCE_COUNT" = "100" ] && echo "✅" || echo "❌")" echo " Prompts: $PROMPT_COUNT" echo " OAuth flow: ✅ Working" -echo " MCP features: ✅ Working" \ No newline at end of file +echo " MCP features: ✅ Working" + +# Kill the server explicitly (trap might not catch npm's child process) +kill $MCP_PID 2>/dev/null || true +pkill -P $MCP_PID 2>/dev/null || true +pkill -f "node dist/index.js" 2>/dev/null || true +sleep 1 \ No newline at end of file diff --git a/scripts/test-separate-e2e.sh b/scripts/test-external-e2e.sh similarity index 93% rename from scripts/test-separate-e2e.sh rename to scripts/test-external-e2e.sh index 846baa6..3c61ee6 100755 --- a/scripts/test-separate-e2e.sh +++ b/scripts/test-external-e2e.sh @@ -2,18 +2,16 @@ set -e echo "==================================================" -echo "End-to-End Test - Separate Mode" +echo "End-to-End Test - External OAuth" echo "==================================================" echo "This script tests the complete OAuth flow and MCP features" -echo "using separate auth server and MCP server." +echo "using external auth server and MCP server." echo "" # Kill any existing servers echo "🛑 Cleaning up existing servers..." -pkill -f "node.*dist/src/index" || true -pkill -f "node.*dist/auth-server/index" || true +pkill -f "node.*dist/index" || true pkill -f "tsx watch.*src/index" || true -pkill -f "tsx watch.*auth-server/index" || true sleep 2 # Use environment variables if available, otherwise defaults @@ -47,8 +45,8 @@ if [ "${AUTH_MODE}" = "integrated" ]; then fi # Start auth server -echo "🚀 Starting auth server..." -npm run start:auth-server & +echo "Starting auth server..." +(cd external-oauth/auth-server && npm start) & AUTH_PID=$! sleep 5 @@ -60,9 +58,9 @@ if ! curl -s -f "$AUTH_SERVER/health" > /dev/null; then fi echo "✅ Auth server is running (PID: $AUTH_PID)" -# Start MCP server in separate mode -echo "🚀 Starting MCP server in separate mode..." -AUTH_MODE=separate npm start & +# Start MCP server with external OAuth +echo "Starting MCP server with external OAuth..." +(cd external-oauth/mcp-server && npm start) & MCP_PID=$! sleep 5 @@ -125,7 +123,7 @@ echo " Auth Code: ${AUTH_CODE:0:20}..." # For testing, we simulate this with the fake upstream auth endpoint echo "" echo "🔄 Step 4: Complete fake upstream auth with auth server" -CALLBACK_URL="$AUTH_SERVER/fakeupstreamauth/callback?state=$AUTH_CODE&code=fakecode&userId=$USER_ID" +CALLBACK_URL="$AUTH_SERVER/mock-upstream-idp/callback?state=$AUTH_CODE&code=mock-auth-code&userId=$USER_ID" CALLBACK_RESPONSE=$(curl -s -i "$CALLBACK_URL") # OAuth Step 5: Authorization Code Redirect @@ -278,7 +276,7 @@ else fi echo "" -echo "🎉 SEPARATE MODE E2E TEST COMPLETE!" +echo "EXTERNAL OAUTH E2E TEST COMPLETE!" echo "===================================" echo "✅ OAuth flow: Auth server → MCP server delegation working" echo "✅ Token validation: MCP server accepts auth server tokens" @@ -292,4 +290,11 @@ echo "" echo "🏗️ Architecture Verified:" echo " ✅ Separate auth server provides OAuth endpoints" echo " ✅ MCP server validates tokens via introspection" -echo " ✅ Session ownership works across server boundaries" \ No newline at end of file +echo " ✅ Session ownership works across server boundaries" + +# Kill servers explicitly (trap might not catch npm's child processes) +kill $AUTH_PID $MCP_PID 2>/dev/null || true +pkill -P $AUTH_PID 2>/dev/null || true +pkill -P $MCP_PID 2>/dev/null || true +pkill -f "node dist/index.js" 2>/dev/null || true +sleep 1 \ No newline at end of file diff --git a/src/context.test.ts b/src/context.test.ts deleted file mode 100644 index c111cd5..0000000 --- a/src/context.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { describe, test, expect } from '@jest/globals'; -import { getFakeUpstreamInstallation, getMcpAccessToken, withContext } from "./context.js"; -import { generateToken } from "./services/auth.js"; - -describe("context", () => { - const mockContext = { - mcpAccessToken: generateToken(), - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", - }, - }; - - test("getFakeUpstreamInstallation throws when called outside context", () => { - expect(() => getFakeUpstreamInstallation()).toThrow( - "No request context found - are you calling this from within a request handler?" - ); - }); - - test("getMcpAccessToken throws when called outside context", () => { - expect(() => getMcpAccessToken()).toThrow( - "No request context found - are you calling this from within a request handler?" - ); - }); - - test("context functions return data within context", async () => { - await withContext(mockContext, () => { - const upstreamInstall = getFakeUpstreamInstallation(); - expect(upstreamInstall).toEqual(mockContext.fakeUpstreamInstallation); - - const mcpToken = getMcpAccessToken(); - expect(mcpToken).toEqual(mockContext.mcpAccessToken); - }); - }); - - test("nested contexts maintain isolation", async () => { - const context1 = { - mcpAccessToken: generateToken(), - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", - }, - }; - const context2 = { - mcpAccessToken: generateToken(), - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", - }, - }; - - await withContext(context1, async () => { - expect(getFakeUpstreamInstallation()).toEqual(context1.fakeUpstreamInstallation); - expect(getMcpAccessToken()).toEqual(context1.mcpAccessToken); - - await withContext(context2, () => { - expect(getFakeUpstreamInstallation()).toEqual(context2.fakeUpstreamInstallation); - expect(getMcpAccessToken()).toEqual(context2.mcpAccessToken); - }); - - // Outer context should be preserved - expect(getFakeUpstreamInstallation()).toEqual(context1.fakeUpstreamInstallation); - expect(getMcpAccessToken()).toEqual(context1.mcpAccessToken); - }); - }); - - test("concurrent contexts maintain isolation", async () => { - const context1 = { - mcpAccessToken: generateToken(), - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", - }, - }; - const context2 = { - mcpAccessToken: generateToken(), - fakeUpstreamInstallation: { - fakeAccessTokenForDemonstration: "fake-upstream-access-token", - fakeRefreshTokenForDemonstration: "fake-upstream-refresh-token", - }, - }; - - // Run two contexts concurrently - await Promise.all([ - withContext(context1, async () => { - // Simulate async work - await new Promise(resolve => setTimeout(resolve, 10)); - expect(getFakeUpstreamInstallation()).toEqual(context1.fakeUpstreamInstallation); - expect(getMcpAccessToken()).toEqual(context1.mcpAccessToken); - }), - withContext(context2, async () => { - // Simulate async work - await new Promise(resolve => setTimeout(resolve, 5)); - expect(getFakeUpstreamInstallation()).toEqual(context2.fakeUpstreamInstallation); - expect(getMcpAccessToken()).toEqual(context2.mcpAccessToken); - }), - ]); - }); - - test("context is preserved across async operations", async () => { - await withContext(mockContext, async () => { - const initialUpstreamInstall = getFakeUpstreamInstallation(); - const initialMcpToken = getMcpAccessToken(); - - // Simulate async work - await new Promise(resolve => setTimeout(resolve, 10)); - - const afterUpstreamInstall = getFakeUpstreamInstallation(); - const afterMcpToken = getMcpAccessToken(); - expect(afterUpstreamInstall).toEqual(initialUpstreamInstall); - expect(afterMcpToken).toEqual(initialMcpToken); - }); - }); -}); \ No newline at end of file diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 0b15454..0000000 --- a/src/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; - -// authorization code -> PendingAuthorization -export interface PendingAuthorization { - redirectUri: string; - codeChallenge: string; - codeChallengeMethod: string; - clientId: string; - state?: string; -} - -// authorization code -> MCP access token (once authorized) -export interface TokenExchange { - mcpAccessToken: string; - alreadyUsed: boolean; -} - -export interface FakeUpstreamInstallation { - fakeAccessTokenForDemonstration: string; - fakeRefreshTokenForDemonstration: string; -} - -// This is the object stored in Redis holding the upstream "Installation" + all the relevant MCP tokens -// It is stored encrypted by the MCP access token -export interface McpInstallation { - fakeUpstreamInstallation: FakeUpstreamInstallation; - mcpTokens: OAuthTokens; - clientId: string; - issuedAt: number; - userId: string; // Unique identifier for the user (not client) -} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 4c27ee1..4e1b920 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,6 @@ "module": "Node16", "moduleResolution": "Node16", "sourceMap": true, - "outDir": "./dist", "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, @@ -12,6 +11,10 @@ "isolatedModules": true, "skipLibCheck": true }, - "include": ["src/**/*", "shared/**/*", "auth-server/**/*"], - "exclude": ["node_modules", "dist"] + "files": [], + "references": [ + { "path": "./embedded-oauth" }, + { "path": "./external-oauth/auth-server" }, + { "path": "./external-oauth/mcp-server" } + ] } From c637d39f8d4a562e76f0f5344089520d9f49f17f Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Mon, 6 Oct 2025 09:56:19 -0400 Subject: [PATCH 02/12] Fix: Update package-lock.json with new workspace names The workspace names were changed during refactoring but package-lock.json wasn't updated, causing CI failures with npm ci --- package-lock.json | 99 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 42eb162..1aca130 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,17 +8,96 @@ "name": "mcp-server-feature-reference", "version": "0.1.0", "workspaces": [ - "integrated-mode", - "separate-mode/auth-server", - "separate-mode/mcp-server" + "embedded-oauth", + "external-oauth/auth-server", + "external-oauth/mcp-server" ], "devDependencies": { "concurrently": "^8.2.0" } }, + "embedded-oauth": { + "name": "mcp-server-feature-reference-embedded-oauth", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@redis/client": "^1.6.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-rate-limit": "^8.0.1", + "raw-body": "^3.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/content-type": "^1.1.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + } + }, + "external-oauth/auth-server": { + "name": "mcp-server-feature-reference-external-auth-server", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@redis/client": "^1.6.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-rate-limit": "^8.0.1", + "raw-body": "^3.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/content-type": "^1.1.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + } + }, + "external-oauth/mcp-server": { + "name": "mcp-server-feature-reference-external-mcp-server", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@redis/client": "^1.6.0", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "express-rate-limit": "^8.0.1", + "raw-body": "^3.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/content-type": "^1.1.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + } + }, "integrated-mode": { "name": "mcp-server-feature-reference-integrated", "version": "0.1.0", + "extraneous": true, "dependencies": { "@modelcontextprotocol/sdk": "^1.15.1", "@redis/client": "^1.6.0", @@ -5500,16 +5579,16 @@ "node": ">= 0.4" } }, - "node_modules/mcp-server-feature-reference-auth-server": { - "resolved": "separate-mode/auth-server", + "node_modules/mcp-server-feature-reference-embedded-oauth": { + "resolved": "embedded-oauth", "link": true }, - "node_modules/mcp-server-feature-reference-integrated": { - "resolved": "integrated-mode", + "node_modules/mcp-server-feature-reference-external-auth-server": { + "resolved": "external-oauth/auth-server", "link": true }, - "node_modules/mcp-server-feature-reference-mcp-server": { - "resolved": "separate-mode/mcp-server", + "node_modules/mcp-server-feature-reference-external-mcp-server": { + "resolved": "external-oauth/mcp-server", "link": true }, "node_modules/media-typer": { @@ -7274,6 +7353,7 @@ "separate-mode/auth-server": { "name": "mcp-server-feature-reference-auth-server", "version": "0.1.0", + "extraneous": true, "dependencies": { "@modelcontextprotocol/sdk": "^1.15.1", "@redis/client": "^1.6.0", @@ -7300,6 +7380,7 @@ "separate-mode/mcp-server": { "name": "mcp-server-feature-reference-mcp-server", "version": "0.1.0", + "extraneous": true, "dependencies": { "@modelcontextprotocol/sdk": "^1.15.1", "@redis/client": "^1.6.0", From c03a9b4e77ec5ac1b9ba8a15a1ead7bdbf8ac39a Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Mon, 6 Oct 2025 21:07:18 -0400 Subject: [PATCH 03/12] Remove embedded OAuth implementation and flatten directory structure Simplified the repository to have a single OAuth pattern with separate auth and MCP servers at the top level. Preserved embedded OAuth documentation as an alternative pattern. --- README.md | 554 +- .../auth-server => auth-server}/README.md | 5 +- .../eslint.config.mjs | 0 .../jest.config.js | 0 .../package-lock.json | 0 .../auth-server => auth-server}/package.json | 0 .../src/auth/auth-core.ts | 0 .../src/auth/provider.test.ts | 0 .../src/auth/provider.ts | 0 .../auth-server => auth-server}/src/config.ts | 0 .../src/handlers/mock-upstream-idp.ts | 35 +- .../auth-server => auth-server}/src/index.ts | 0 {embedded-oauth => auth-server}/src/redis.ts | 0 .../src/services/auth.test.ts | 0 .../src/services/auth.ts | 0 .../src/services/redis-auth.ts | 0 .../src/static/index.html | 0 .../src/static/mcp.png | Bin .../src/static/styles.css | 0 {embedded-oauth => auth-server}/src/types.ts | 0 .../src/utils/logger.ts | 0 {embedded-oauth => auth-server}/tsconfig.json | 0 docs/endpoints.md | 90 +- docs/oauth-flow.md | 91 +- docs/oauth-patterns.md | 269 + docs/session-ownership.md | 8 +- embedded-oauth/README.md | 135 - embedded-oauth/package-lock.json | 7141 ----------------- embedded-oauth/package.json | 43 - embedded-oauth/src/auth/provider.test.ts | 412 - embedded-oauth/src/config.ts | 24 - embedded-oauth/src/context.ts | 36 - embedded-oauth/src/handlers/common.ts | 63 - .../src/handlers/mock-upstream-idp.ts | 353 - embedded-oauth/src/handlers/sse.ts | 101 - embedded-oauth/src/index.ts | 220 - examples/README.md | 133 + examples/client.js | 345 + examples/curl-examples.sh | 339 + external-oauth/README.md | 109 - .../auth-server/src/auth/auth-core.ts | 87 - .../auth-server/src/auth/provider.ts | 378 - .../auth-server/src/services/auth.test.ts | 292 - .../auth-server/src/services/auth.ts | 86 - .../auth-server/src/services/redis-auth.ts | 285 - external-oauth/auth-server/src/types.ts | 92 - .../auth-server/src/utils/logger.ts | 176 - external-oauth/mcp-server/eslint.config.mjs | 19 - external-oauth/mcp-server/jest.config.js | 19 - .../src/handlers/shttp.integration.test.ts | 694 -- .../mcp-server/src/handlers/shttp.test.ts | 229 - .../mcp-server/src/handlers/shttp.ts | 177 - external-oauth/mcp-server/src/redis.ts | 289 - external-oauth/mcp-server/src/services/mcp.ts | 686 -- .../redisTransport.integration.test.ts | 291 - .../src/services/redisTransport.test.ts | 536 -- .../mcp-server/src/services/redisTransport.ts | 351 - .../mcp-server/src/static/index.html | 97 - external-oauth/mcp-server/src/static/mcp.png | Bin 40652 -> 0 bytes .../mcp-server/src/static/styles.css | 188 - external-oauth/mcp-server/tsconfig.json | 17 - .../mcp-server => mcp-server}/README.md | 10 +- .../eslint.config.mjs | 0 .../auth-server => mcp-server}/jest.config.js | 0 .../package-lock.json | 0 .../mcp-server => mcp-server}/package.json | 0 .../src/auth/external-verifier.ts | 0 .../mcp-server => mcp-server}/src/config.ts | 0 .../src/handlers/shttp.integration.test.ts | 0 .../src/handlers/shttp.test.ts | 0 .../src/handlers/shttp.ts | 0 .../src/handlers/sse.ts | 0 .../mcp-server => mcp-server}/src/index.ts | 14 + .../auth-server => mcp-server}/src/redis.ts | 0 .../src/services/mcp.ts | 0 .../redisTransport.integration.test.ts | 0 .../src/services/redisTransport.test.ts | 0 .../src/services/redisTransport.ts | 0 .../src/static/index.html | 0 .../src/static/mcp.png | Bin .../src/static/styles.css | 0 .../mcp-server => mcp-server}/src/types.ts | 0 .../src/utils/logger.ts | 0 .../auth-server => mcp-server}/tsconfig.json | 0 package-lock.json | 1564 +--- package.json | 35 +- scripts/{test-external-e2e.sh => test-e2e.sh} | 38 +- scripts/test-embedded-e2e.sh | 241 - tsconfig.json | 5 +- 89 files changed, 1556 insertions(+), 15846 deletions(-) rename {external-oauth/auth-server => auth-server}/README.md (94%) rename {embedded-oauth => auth-server}/eslint.config.mjs (100%) rename {embedded-oauth => auth-server}/jest.config.js (100%) rename {external-oauth/auth-server => auth-server}/package-lock.json (100%) rename {external-oauth/auth-server => auth-server}/package.json (100%) rename {embedded-oauth => auth-server}/src/auth/auth-core.ts (100%) rename {external-oauth/auth-server => auth-server}/src/auth/provider.test.ts (100%) rename {embedded-oauth => auth-server}/src/auth/provider.ts (100%) rename {external-oauth/auth-server => auth-server}/src/config.ts (100%) rename {external-oauth/auth-server => auth-server}/src/handlers/mock-upstream-idp.ts (90%) rename {external-oauth/auth-server => auth-server}/src/index.ts (100%) rename {embedded-oauth => auth-server}/src/redis.ts (100%) rename {embedded-oauth => auth-server}/src/services/auth.test.ts (100%) rename {embedded-oauth => auth-server}/src/services/auth.ts (100%) rename {embedded-oauth => auth-server}/src/services/redis-auth.ts (100%) rename {embedded-oauth => auth-server}/src/static/index.html (100%) rename {embedded-oauth => auth-server}/src/static/mcp.png (100%) rename {embedded-oauth => auth-server}/src/static/styles.css (100%) rename {embedded-oauth => auth-server}/src/types.ts (100%) rename {embedded-oauth => auth-server}/src/utils/logger.ts (100%) rename {embedded-oauth => auth-server}/tsconfig.json (100%) create mode 100644 docs/oauth-patterns.md delete mode 100644 embedded-oauth/README.md delete mode 100644 embedded-oauth/package-lock.json delete mode 100644 embedded-oauth/package.json delete mode 100644 embedded-oauth/src/auth/provider.test.ts delete mode 100644 embedded-oauth/src/config.ts delete mode 100644 embedded-oauth/src/context.ts delete mode 100644 embedded-oauth/src/handlers/common.ts delete mode 100644 embedded-oauth/src/handlers/mock-upstream-idp.ts delete mode 100644 embedded-oauth/src/handlers/sse.ts delete mode 100644 embedded-oauth/src/index.ts create mode 100644 examples/README.md create mode 100755 examples/client.js create mode 100755 examples/curl-examples.sh delete mode 100644 external-oauth/README.md delete mode 100644 external-oauth/auth-server/src/auth/auth-core.ts delete mode 100644 external-oauth/auth-server/src/auth/provider.ts delete mode 100644 external-oauth/auth-server/src/services/auth.test.ts delete mode 100644 external-oauth/auth-server/src/services/auth.ts delete mode 100644 external-oauth/auth-server/src/services/redis-auth.ts delete mode 100644 external-oauth/auth-server/src/types.ts delete mode 100644 external-oauth/auth-server/src/utils/logger.ts delete mode 100644 external-oauth/mcp-server/eslint.config.mjs delete mode 100644 external-oauth/mcp-server/jest.config.js delete mode 100644 external-oauth/mcp-server/src/handlers/shttp.integration.test.ts delete mode 100644 external-oauth/mcp-server/src/handlers/shttp.test.ts delete mode 100644 external-oauth/mcp-server/src/handlers/shttp.ts delete mode 100644 external-oauth/mcp-server/src/redis.ts delete mode 100644 external-oauth/mcp-server/src/services/mcp.ts delete mode 100644 external-oauth/mcp-server/src/services/redisTransport.integration.test.ts delete mode 100644 external-oauth/mcp-server/src/services/redisTransport.test.ts delete mode 100644 external-oauth/mcp-server/src/services/redisTransport.ts delete mode 100644 external-oauth/mcp-server/src/static/index.html delete mode 100644 external-oauth/mcp-server/src/static/mcp.png delete mode 100644 external-oauth/mcp-server/src/static/styles.css delete mode 100644 external-oauth/mcp-server/tsconfig.json rename {external-oauth/mcp-server => mcp-server}/README.md (92%) rename {external-oauth/auth-server => mcp-server}/eslint.config.mjs (100%) rename {external-oauth/auth-server => mcp-server}/jest.config.js (100%) rename {external-oauth/mcp-server => mcp-server}/package-lock.json (100%) rename {external-oauth/mcp-server => mcp-server}/package.json (100%) rename {external-oauth/mcp-server => mcp-server}/src/auth/external-verifier.ts (100%) rename {external-oauth/mcp-server => mcp-server}/src/config.ts (100%) rename {embedded-oauth => mcp-server}/src/handlers/shttp.integration.test.ts (100%) rename {embedded-oauth => mcp-server}/src/handlers/shttp.test.ts (100%) rename {embedded-oauth => mcp-server}/src/handlers/shttp.ts (100%) rename {external-oauth/mcp-server => mcp-server}/src/handlers/sse.ts (100%) rename {external-oauth/mcp-server => mcp-server}/src/index.ts (93%) rename {external-oauth/auth-server => mcp-server}/src/redis.ts (100%) rename {embedded-oauth => mcp-server}/src/services/mcp.ts (100%) rename {embedded-oauth => mcp-server}/src/services/redisTransport.integration.test.ts (100%) rename {embedded-oauth => mcp-server}/src/services/redisTransport.test.ts (100%) rename {embedded-oauth => mcp-server}/src/services/redisTransport.ts (100%) rename {external-oauth/auth-server => mcp-server}/src/static/index.html (100%) rename {external-oauth/auth-server => mcp-server}/src/static/mcp.png (100%) rename {external-oauth/auth-server => mcp-server}/src/static/styles.css (100%) rename {external-oauth/mcp-server => mcp-server}/src/types.ts (100%) rename {external-oauth/mcp-server => mcp-server}/src/utils/logger.ts (100%) rename {external-oauth/auth-server => mcp-server}/tsconfig.json (100%) rename scripts/{test-external-e2e.sh => test-e2e.sh} (90%) delete mode 100755 scripts/test-embedded-e2e.sh diff --git a/README.md b/README.md index acc5f6b..7d66467 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,24 @@ # MCP Feature Reference Server -_Note: these docs were AI generated based on a claude code transcript, and then edited manually for accuracy_ +A production-ready example of a Model Context Protocol (MCP) server with OAuth 2.0 authentication, demonstrating best practices for secure, scalable MCP deployments. -A comprehensive example implementation of a scalable Model Context Protocol (MCP) server that demonstrates all MCP functionality with full authentication support and horizontal scalability. +## What is this? -## Overview +This repository provides a complete implementation of an MCP server that: +- Shows how to add authentication to MCP servers using OAuth 2.0 +- Demonstrates all MCP protocol features (tools, resources, prompts) +- Uses a scalable architecture suitable for production deployments +- Serves as a learning resource and starting template for your own MCP servers -The Feature Reference Server is an open-source reference implementation that showcases: -- **Complete [MCP Protocol](https://modelcontextprotocol.io/specification) Support**: All MCP features including tools, resources, prompts, sampling, completions, and logging -- **Multiple [Transport Methods](https://modelcontextprotocol.io/docs/concepts/transports)**: Streamable HTTP (SHTTP) and Server-Sent Events (SSE) -- **Dual Authentication Modes**: Integrated and separate authorization server support -- **Horizontal Scalability**: Redis-backed session management for multi-instance deployments +The [Model Context Protocol](https://modelcontextprotocol.io) is an open standard that enables seamless integration between AI applications and external data sources, tools, and services. -This server serves as both primarily as a learning resource, and an example implementation of a scalable remote MCP server. +## Why OAuth for MCP? -## Repository Structure - -This repository contains **two complete, standalone implementations** to help you learn different MCP deployment patterns: - -``` -embedded-oauth/ # Complete example: MCP server with integrated OAuth - └── src/ # All code in one place, ready to run - -external-oauth/ - ├── auth-server/ # Standalone OAuth authorization server - │ └── src/ - └── mcp-server/ # MCP server that delegates to external auth - └── src/ -``` - -Both modes are complete, independently buildable implementations with full test coverage. For more information on the difference between embedded and external OAuth, see [Authentication Modes](#authentication-modes). +MCP servers often handle sensitive operations and data. This implementation shows how to: +- Authenticate users before granting access to MCP resources +- Integrate with existing identity providers (Auth0, Okta, Google) +- Implement user session isolation in multi-tenant environments +- Scale horizontally while maintaining security ## Quick Start @@ -48,9 +37,7 @@ npm install # Install dependencies for all workspaces # 3. Start services docker compose up -d # Start Redis -npm run dev:embedded # Start embedded-oauth server -# OR -npm run dev:external # Start both auth-server and mcp-server +npm run dev # Start both auth-server and mcp-server # 4. Test with Inspector npx -y @modelcontextprotocol/inspector @@ -59,19 +46,146 @@ npx -y @modelcontextprotocol/inspector For detailed instructions, see [Installation](#installation). +## Repository Structure + +This repository demonstrates a production-ready MCP deployment pattern with separate authorization and resource servers: + +``` +auth-server/ # OAuth 2.0 authorization server + └── src/ # Authorization endpoints and token management + +mcp-server/ # MCP resource server + └── src/ # MCP protocol implementation with external auth + +scripts/ # Testing and deployment scripts +docs/ # Architecture and API documentation +examples/ # Example code and usage patterns +``` + +The architecture separates authentication concerns from MCP functionality, allowing you to integrate with existing OAuth providers (Auth0, Okta, etc.) or deploy your own authorization server. + +## Usage Examples + +### Making Your First MCP Request + +Once the servers are running, here's how to interact with them: + +#### 1. Register an OAuth Client + +```bash +# Register your application with the auth server +curl -X POST http://localhost:3001/register \ + -H "Content-Type: application/json" \ + -d '{ + "client_name": "my-mcp-client", + "redirect_uris": ["http://localhost:3000/callback"] + }' + +# Response: +# { +# "client_id": "abc123...", +# "client_secret": "xyz789...", +# "client_name": "my-mcp-client" +# } +``` + +#### 2. Get an Access Token + +For testing, use the MCP Inspector which handles the OAuth flow automatically. For programmatic access, implement the OAuth 2.0 authorization code flow (see [docs/oauth-flow.md](docs/oauth-flow.md)). + +#### 3. Initialize MCP Session + +```bash +# Initialize a new MCP session +curl -X POST http://localhost:3232/mcp \ + -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "1", + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { + "name": "my-client", + "version": "1.0" + } + } + }' + +# Response includes session ID for subsequent requests +``` + +#### 4. Use MCP Features + +```bash +# List available tools +curl -X POST http://localhost:3232/mcp \ + -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ + -H "Mcp-Session-Id: YOUR_SESSION_ID" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "2", + "method": "tools/list" + }' + +# Call a tool (echo example) +curl -X POST http://localhost:3232/mcp \ + -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ + -H "Mcp-Session-Id: YOUR_SESSION_ID" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "3", + "method": "tools/call", + "params": { + "name": "echo", + "arguments": { + "message": "Hello, MCP!" + } + } + }' +``` + +For more examples, see the [examples/](examples/) directory. + +## Understanding the Flow + +```mermaid +sequenceDiagram + participant User + participant Client as Your App + participant Auth as Auth Server (:3001) + participant MCP as MCP Server (:3232) + + User->>Client: Wants to use MCP + Client->>Auth: 1. Register & OAuth flow + Auth->>User: 2. Login page + User->>Auth: 3. Authenticate + Auth->>Client: 4. Access token + Client->>MCP: 5. MCP request + token + MCP->>Auth: 6. Validate token + Auth->>MCP: 7. Token valid ✓ + MCP->>Client: 8. MCP response + Client->>User: 9. Results +``` + +The auth server is separate so you can easily replace it with Auth0, Okta, or any OAuth provider. See [docs/oauth-patterns.md](docs/oauth-patterns.md) for alternative architectures. + ## Table of Contents - [Quick Start](#quick-start) - [Features](#features) - [Installation](#installation) - [Configuration](#configuration) -- [Authentication Modes](#authentication-modes) +- [Architecture](#architecture) - [Development](#development) - [Testing with MCP Inspector](#testing-with-mcp-inspector) - [Automated End-to-End Testing](#automated-end-to-end-testing) - - [Interactive Testing](#interactive-testing) -- [Troubleshooting](#troubleshooting) -- [Architecture & Technical Details](#architecture--technical-details) +- [Common Issues & Solutions](#common-issues--solutions) +- [Technical Details](#technical-details) - [API Reference](#api-reference) - [Security](#security) - [Monitoring & Debugging](#monitoring--debugging) @@ -96,10 +210,10 @@ For detailed instructions, see [Installation](#installation). - **Horizontal Scaling**: Any instance can handle any request ### Authentication & Security -- **Dual Mode Support**: Run with integrated or separate authorization server - **[OAuth 2.0](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization)**: Complete authorization flow with PKCE support -- **External Auth Ready**: Demonstrates integration with external OAuth providers -- **Session Ownership**: User isolation and access control +- **Separate Auth Server**: Demonstrates integration with external OAuth providers +- **Token Introspection**: Validates tokens via [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) +- **Session Ownership**: User isolation and access control - **Security Headers**: CSP, HSTS, X-Frame-Options, and more - **Bearer Token Auth**: Middleware for protected endpoints @@ -138,25 +252,10 @@ sudo apt-get install redis-server && sudo systemctl start redis ```bash git clone https://github.com/modelcontextprotocol/example-remote-server.git cd example-remote-server -npm install # Installs dependencies for all workspaces (embedded-oauth, auth-server, mcp-server) -``` - -### Step 3: Choose Your Mode -The repository includes two complete, standalone examples: - -**Option A: Embedded OAuth (Simpler)** -```bash -cd embedded-oauth -# Everything is configured by default in .env -``` - -**Option B: External OAuth (Production-like)** -```bash -cd external-oauth/auth-server # Auth server configuration in .env -cd external-oauth/mcp-server # MCP server configuration in .env +npm install # Installs dependencies for all workspaces ``` -### Step 4: Start Redis +### Step 3: Start Redis ```bash # Ensure Docker/OrbStack is running first! docker compose up -d @@ -165,33 +264,21 @@ docker compose up -d docker compose ps ``` -### Step 5: Start and Verify - -**Embedded OAuth:** +### Step 4: Start the Servers ```bash -npm run dev:embedded -# Server starts on http://localhost:3232 -``` +# Start both servers concurrently +npm run dev -**External OAuth:** -```bash -npm run dev:external -# Auth server starts on http://localhost:3001 -# MCP server starts on http://localhost:3232 +# Or start them individually: +npm run dev:auth-server # Auth server on http://localhost:3001 +npm run dev:mcp-server # MCP server on http://localhost:3232 ``` ## Configuration -Each mode has its own `.env` file pre-configured: +Each server has its own `.env` file: -**Embedded OAuth** (`embedded-oauth/.env`): -```bash -BASE_URI=http://localhost:3232 # MCP server URL -PORT=3232 # MCP server port -REDIS_URL=redis://localhost:6379 # Redis connection -``` - -**External OAuth Auth Server** (`external-oauth/auth-server/.env`): +**Auth Server** (`auth-server/.env`): ```bash AUTH_SERVER_URL=http://localhost:3001 # Auth server URL AUTH_SERVER_PORT=3001 # Auth server port @@ -199,7 +286,7 @@ BASE_URI=http://localhost:3232 # MCP server URL (for redirects) REDIS_URL=redis://localhost:6379 # Redis connection ``` -**External OAuth MCP Server** (`external-oauth/mcp-server/.env`): +**MCP Server** (`mcp-server/.env`): ```bash BASE_URI=http://localhost:3232 # MCP server URL PORT=3232 # MCP server port @@ -207,61 +294,105 @@ AUTH_SERVER_URL=http://localhost:3001 # External auth server URL REDIS_URL=redis://localhost:6379 # Redis connection ``` -## Authentication Modes +## Architecture -Per the [MCP Authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), the authorization server may be hosted with the resource server or as a separate entity. This repository demonstrates both patterns. +Per the [MCP Authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), this implementation demonstrates a production-ready pattern with separate authorization and resource servers. -### Embedded OAuth -**Location**: `embedded-oauth/` +### System Architecture -OAuth authorization server hosted with the MCP resource server. Demonstrates self-hosted OAuth with upstream identity provider delegation (enterprise pattern where you control OAuth but delegate user authentication to corporate SSO/LDAP). +```mermaid +graph TD + Client["MCP Client
(Inspector)"] + MCP["MCP Server
(port 3232)
Resource Server"] + Auth["Auth Server
(port 3001)
OAuth Server"] -```bash -npm run dev:embedded # Single server on :3232 + Client <-->|"1. Discover metadata"| MCP + Client <-->|"2. OAuth flow
(register, authorize, token)"| Auth + Client <-->|"3. Use tokens for MCP resources"| MCP + MCP <-->|"Token validation
(introspect)"| Auth ``` -See [embedded-oauth/README.md](embedded-oauth/README.md) for details. +This architecture pattern: +- **Separates concerns**: Auth server handles OAuth, MCP server handles protocol +- **Enables integration**: Can replace auth server with Auth0, Okta, etc. +- **Scales independently**: Auth and MCP servers can scale based on their load +- **Follows standards**: Uses OAuth 2.0 and token introspection (RFC 7662) -### External OAuth -**Location**: `external-oauth/` +For alternative patterns like embedded OAuth, see [docs/oauth-patterns.md](docs/oauth-patterns.md). -MCP resource server using a completely separate OAuth authorization server. Demonstrates the OAuth-as-a-Service pattern (modern SaaS pattern using Auth0, Okta, or similar providers). +### OAuth Flow -```bash -npm run dev:external # Auth server on :3001, MCP server on :3232 -``` +The implementation uses OAuth 2.1 with PKCE. For detailed flow analysis including data storage, TTLs, and implementation details, see [docs/oauth-flow.md](docs/oauth-flow.md). + +### Understanding the Mock Identity Provider -See [external-oauth/README.md](external-oauth/README.md) for architecture and implementation details. +The `/mock-upstream-idp` endpoints simulate what a real identity provider (Google, GitHub, corporate SSO) would do. In production, users would be redirected to their actual identity provider for login. This mock implementation helps you test the complete flow locally without external dependencies. + +### Project Structure +``` +├── auth-server/ # OAuth authorization server +│ ├── src/ +│ │ ├── index.ts # Auth server entry point +│ │ ├── auth/ +│ │ │ ├── provider.ts # FeatureReferenceAuthProvider +│ │ │ └── auth-core.ts # Token generation, PKCE +│ │ ├── services/ # Auth services, redis-auth +│ │ ├── handlers/ # Mock upstream auth +│ │ └── utils/logger.ts # Structured logging +│ ├── package.json +│ ├── tsconfig.json +│ └── .env +│ +├── mcp-server/ # MCP resource server +│ ├── src/ +│ │ ├── index.ts # MCP server entry point +│ │ ├── auth/ +│ │ │ ├── external-verifier.ts # Token introspection +│ │ │ └── auth-core.ts +│ │ ├── services/ # MCP server, redisTransport +│ │ ├── handlers/ # SHTTP, SSE handlers +│ │ └── utils/logger.ts +│ ├── package.json +│ ├── tsconfig.json +│ └── .env +│ +├── scripts/ # Testing and deployment +│ └── test-e2e.sh # End-to-end OAuth + MCP verification +├── docs/ +│ ├── endpoints.md # API endpoint reference +│ ├── oauth-flow.md # OAuth flow documentation +│ ├── oauth-patterns.md # Alternative OAuth patterns +│ └── session-ownership.md # Session management details +├── examples/ # Example code and usage patterns +│ ├── client.js # Node.js client example +│ └── curl-examples.sh # Shell script with curl examples +├── package.json # Root workspace configuration +└── docker-compose.yml # Redis service +``` ## Development ```bash # Start servers -npm run dev:embedded # Embedded OAuth -npm run dev:external # External OAuth (both servers) -npm run dev:auth-server # External OAuth - auth server only -npm run dev:mcp-server # External OAuth - MCP server only +npm run dev # Both servers concurrently +npm run dev:auth-server # Auth server only +npm run dev:mcp-server # MCP server only ``` ### Build & Production ```bash npm run build # Build all workspaces -npm run build:embedded # Build embedded OAuth only -npm run build:external # Build external OAuth only - -npm run start:embedded # Run embedded OAuth -npm run start:external # Run external OAuth (both servers) +npm run start # Run both servers +npm run start:auth-server # Run auth server only +npm run start:mcp-server # Run MCP server only ``` ### Testing & Quality ```bash -npm test # All unit tests (189 total) +npm test # All unit tests npm run lint # Lint all code npm run typecheck # Typecheck all code - -npm run test:e2e # Run all e2e tests -npm run test:e2e:embedded # E2E test for embedded OAuth -npm run test:e2e:external # E2E test for external OAuth +npm run test:e2e # End-to-end test ``` ### Testing with MCP Inspector @@ -272,163 +403,87 @@ The MCP Inspector is a web-based tool for testing MCP servers. 1. Ensure Docker/OrbStack is running 2. Ensure Redis is running: `docker compose ps` -#### Test Embedded OAuth -```bash -# 1. Start the server (Redis must already be running) -npm run dev:embedded - -# 2. Launch MCP Inspector in a new terminal -npx -y @modelcontextprotocol/inspector - -# 3. Connect to: http://localhost:3232/mcp -# 4. Navigate to Auth tab and complete OAuth flow -``` - -#### Test External OAuth +#### Test the Server ```bash # 1. Start both servers (Redis must already be running) -npm run dev:external +npm run dev # 2. Launch MCP Inspector in a new terminal npx -y @modelcontextprotocol/inspector # 3. Connect to: http://localhost:3232/mcp # 4. Auth flow will redirect to :3001 for authentication +# 5. Complete OAuth flow in the Auth tab +# 6. Test MCP features (tools, resources, prompts, etc.) ``` -### Test Categories -- **Unit Tests**: Component testing (189 tests across all workspaces) -- **Integration Tests**: Transport and Redis integration -- **E2E Tests**: Complete OAuth + MCP feature verification +### Automated End-to-End Testing -### End-to-End Testing - -E2E scripts in `scripts/` verify complete OAuth flows and MCP features: +The E2E script in `scripts/test-e2e.sh` verifies the complete OAuth flow and all MCP features: ```bash -npm run test:e2e:embedded # Test embedded OAuth -npm run test:e2e:external # Test external OAuth -npm run test:e2e # Run both -``` - -Scripts automatically manage server lifecycle, test OAuth 2.0 + PKCE flow, and verify all MCP features. - -## Troubleshooting - -### Common Issues - -**"Cannot connect to Docker daemon"** -- Ensure Docker Desktop or OrbStack daemon is running -- macOS with OrbStack: `orbctl start` (verify with `orbctl status`) -- Windows/Linux/macOS with Docker Desktop: Start Docker Desktop application - -**"Redis connection refused"** -- Check Redis is running: `docker compose ps` -- If not running: `docker compose up -d` -- Ensure Docker/OrbStack is started first - -**"Missing .env file"** -- Each mode directory has a pre-configured `.env` file -- embedded-oauth/.env, external-oauth/auth-server/.env, external-oauth/mcp-server/.env - -**"Port already in use"** -- Check for existing processes: `lsof -i :3232` or `lsof -i :3001` -- Kill existing processes or change PORT in .env - -**"npm install fails"** -- Ensure Node.js >= 16 is installed: `node --version` -- Clear npm cache: `npm cache clean --force` -- Delete node_modules and package-lock.json, then retry - -**"Authentication flow fails"** -- Check server logs for error messages -- Ensure Redis is running: `docker compose ps` -- Verify .env configuration in the mode directory you're running - -## Architecture & Technical Details - -### Authentication Architecture - -#### Embedded OAuth -```mermaid -graph TD - Client["MCP Client
(Inspector)"] - MCP["MCP Server
(port 3232)
• OAuth Server
• Resource Server"] - - Client <-->|"OAuth flow & MCP resources"| MCP +npm run test:e2e ``` -#### External OAuth -```mermaid -graph TD - Client["MCP Client
(Inspector)"] - MCP["MCP Server
(port 3232)
Resource Server"] - Auth["Auth Server
(port 3001)
OAuth Server"] - - Client <-->|"1. Discover metadata"| MCP - Client <-->|"2. OAuth flow
(register, authorize, token)"| Auth - Client <-->|"3. Use tokens for MCP resources"| MCP - MCP <-->|"Token validation
(introspect)"| Auth -``` - -### OAuth Flow - -Both modes implement OAuth 2.1 with PKCE. For detailed flow analysis including data storage, TTLs, and mode-specific differences, see [docs/oauth-flow.md](docs/oauth-flow.md). - -### Project Structure -``` -├── embedded-oauth/ # Complete integrated auth example -│ ├── src/ -│ │ ├── index.ts # Main entry point (MCP + OAuth) -│ │ ├── auth/ -│ │ │ ├── provider.ts # FeatureReferenceAuthProvider -│ │ │ └── auth-core.ts # Token generation, PKCE -│ │ ├── services/ -│ │ │ ├── mcp.ts # MCP server implementation -│ │ │ ├── auth.ts # Auth service wrappers -│ │ │ ├── redis-auth.ts # Redis auth operations -│ │ │ └── redisTransport.ts # Redis-backed transport -│ │ ├── handlers/ # SHTTP, SSE, mock-upstream-idp handlers -│ │ ├── utils/logger.ts # Structured logging -│ │ └── ... -│ ├── package.json -│ ├── tsconfig.json -│ └── .env -│ -├── external-oauth/ -│ ├── auth-server/ # Standalone OAuth server -│ │ ├── src/ -│ │ │ ├── index.ts # Auth server entry point -│ │ │ ├── auth/ -│ │ │ │ ├── provider.ts # FeatureReferenceAuthProvider -│ │ │ │ └── auth-core.ts -│ │ │ ├── services/ # Auth services, redis-auth -│ │ │ ├── handlers/ # Mock upstream auth -│ │ │ └── ... -│ │ ├── package.json -│ │ └── .env -│ │ -│ └── mcp-server/ # MCP server with external auth -│ ├── src/ -│ │ ├── index.ts # MCP server entry point -│ │ ├── auth/ -│ │ │ ├── external-verifier.ts # Token introspection -│ │ │ └── auth-core.ts -│ │ ├── services/ # MCP server, redisTransport -│ │ ├── handlers/ # SHTTP, SSE handlers -│ │ └── ... -│ ├── package.json -│ └── .env -│ -├── scripts/ # End-to-end testing scripts -│ ├── test-integrated-e2e.sh # OAuth + feature verification (integrated) -│ └── test-separate-e2e.sh # OAuth + feature verification (separate) -├── docs/ -│ ├── streamable-http-design.md # SHTTP implementation details -│ └── user-id-system.md # Authentication flow documentation -├── package.json # Root workspace configuration -└── docker-compose.yml # Redis service -``` +The script: +- Automatically manages server lifecycle +- Tests OAuth 2.0 + PKCE flow +- Verifies all MCP features (tools, resources, prompts) +- Validates token introspection between servers +- Tests session management and ownership + +## Common Issues & Solutions + +### "Token validation failed" +- **Cause**: Token expired or servers not communicating +- **Solution**: + - Ensure both servers are running (`npm run dev`) + - Check that Redis is running (`docker compose ps`) + - Verify the token hasn't expired (tokens last 7 days) + +### "Cannot connect to MCP server" +- **Cause**: Incorrect URL or missing path +- **Solution**: + - Use full URL: `http://localhost:3232/mcp` (not just `:3232`) + - Include the `/mcp` path for MCP endpoints + - Ensure you have a valid Bearer token in the Authorization header + +### "Cannot connect to Docker daemon" +- **Cause**: Docker/OrbStack not running +- **Solution**: + - macOS with OrbStack: `orbctl start` (verify with `orbctl status`) + - Windows/Linux/macOS with Docker Desktop: Start Docker Desktop application + +### "Redis connection refused" +- **Cause**: Redis container not running +- **Solution**: + - Check Redis is running: `docker compose ps` + - If not running: `docker compose up -d` + - Ensure Docker/OrbStack is started first + +### "Port already in use" +- **Cause**: Another process using port 3001 or 3232 +- **Solution**: + - Check for existing processes: `lsof -i :3232` or `lsof -i :3001` + - Kill existing processes or change PORT in .env files + +### Understanding the Mock IDP +- The mock IDP simulates user login for testing +- In production, this would be replaced by: + - Your company's SSO system (LDAP, Active Directory) + - Auth0/Okta login page + - Google/GitHub OAuth +- The mock IDP creates random user IDs for testing multi-user scenarios + +### "Authentication flow fails" +- **Cause**: Misconfiguration or servers not communicating +- **Solution**: + - Check server logs for error messages + - Ensure Redis is running: `docker compose ps` + - Verify .env configuration in both server directories + - Check that both servers are running + +## Technical Details ### Scalability Architecture @@ -465,7 +520,7 @@ auth:exch:{authCode} # Token exchanges auth:refresh:{refreshToken} # Refresh tokens ``` -Note: The `auth:` prefix ensures complete namespace isolation between auth and MCP functions in both integrated and separate modes. +Note: The `auth:` prefix ensures complete namespace isolation between auth and MCP functions. ### Transport Methods @@ -476,8 +531,6 @@ Modern [transport](https://modelcontextprotocol.io/specification/2025-03-26/basi - Efficient message buffering - Automatic reconnection support -See [docs/streamable-http-design.md](docs/streamable-http-design.md) for implementation details. - #### Server-Sent Events (Legacy) Backwards-compatible [transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#server-sent-events) using SSE: - Separate endpoints for SSE streams and messages @@ -487,15 +540,16 @@ Backwards-compatible [transport](https://modelcontextprotocol.io/specification/2 ## API Reference -For a complete listing of all endpoints provided by each server configuration, including OAuth authorization endpoints, MCP resource endpoints, and demo identity provider endpoints, see [docs/endpoints.md](docs/endpoints.md). +For a complete listing of all endpoints provided by each server, including OAuth authorization endpoints, MCP resource endpoints, and demo identity provider endpoints, see [docs/endpoints.md](docs/endpoints.md). ## Security ### Implemented Security Measures - **Authentication**: [OAuth 2.0](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) with bearer tokens +- **Token Validation**: Introspection via [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) - **Authorization**: User-based session ownership - **Session Isolation**: Users can only access their own sessions -- **Security Headers**: +- **Security Headers**: - Content Security Policy (CSP) - Strict Transport Security (HSTS) - X-Frame-Options @@ -587,7 +641,7 @@ Built by the Model Context Protocol team as a reference implementation for the M ### OAuth 2.0 / 2.1 Resources - [RFC 6749: OAuth 2.0 Framework](https://datatracker.ietf.org/doc/html/rfc6749) -- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) (used in separate mode) +- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) - [OAuth 2.1 Draft](https://oauth.net/2.1/) - Modern security best practices - [OAuth.net: End User Authentication](https://oauth.net/articles/authentication/) - [The Resource Server - OAuth 2.0 Simplified](https://www.oauth.com/oauth2-servers/the-resource-server/) diff --git a/external-oauth/auth-server/README.md b/auth-server/README.md similarity index 94% rename from external-oauth/auth-server/README.md rename to auth-server/README.md index b0f24f4..aff4f49 100644 --- a/external-oauth/auth-server/README.md +++ b/auth-server/README.md @@ -118,6 +118,7 @@ This server stores in Redis: ## Related Documentation -- [Main README](../../README.md) - Complete project documentation +- [Main README](../README.md) - Complete project documentation - [MCP Server README](../mcp-server/README.md) - How MCP server uses these tokens -- [External OAuth Overview](../README.md) - Architecture explanation +- [OAuth Patterns](../docs/oauth-patterns.md) - OAuth architecture patterns +- [OAuth Flow](../docs/oauth-flow.md) - Detailed OAuth flow analysis diff --git a/embedded-oauth/eslint.config.mjs b/auth-server/eslint.config.mjs similarity index 100% rename from embedded-oauth/eslint.config.mjs rename to auth-server/eslint.config.mjs diff --git a/embedded-oauth/jest.config.js b/auth-server/jest.config.js similarity index 100% rename from embedded-oauth/jest.config.js rename to auth-server/jest.config.js diff --git a/external-oauth/auth-server/package-lock.json b/auth-server/package-lock.json similarity index 100% rename from external-oauth/auth-server/package-lock.json rename to auth-server/package-lock.json diff --git a/external-oauth/auth-server/package.json b/auth-server/package.json similarity index 100% rename from external-oauth/auth-server/package.json rename to auth-server/package.json diff --git a/embedded-oauth/src/auth/auth-core.ts b/auth-server/src/auth/auth-core.ts similarity index 100% rename from embedded-oauth/src/auth/auth-core.ts rename to auth-server/src/auth/auth-core.ts diff --git a/external-oauth/auth-server/src/auth/provider.test.ts b/auth-server/src/auth/provider.test.ts similarity index 100% rename from external-oauth/auth-server/src/auth/provider.test.ts rename to auth-server/src/auth/provider.test.ts diff --git a/embedded-oauth/src/auth/provider.ts b/auth-server/src/auth/provider.ts similarity index 100% rename from embedded-oauth/src/auth/provider.ts rename to auth-server/src/auth/provider.ts diff --git a/external-oauth/auth-server/src/config.ts b/auth-server/src/config.ts similarity index 100% rename from external-oauth/auth-server/src/config.ts rename to auth-server/src/config.ts diff --git a/external-oauth/auth-server/src/handlers/mock-upstream-idp.ts b/auth-server/src/handlers/mock-upstream-idp.ts similarity index 90% rename from external-oauth/auth-server/src/handlers/mock-upstream-idp.ts rename to auth-server/src/handlers/mock-upstream-idp.ts index 8bf8c25..54137c7 100644 --- a/external-oauth/auth-server/src/handlers/mock-upstream-idp.ts +++ b/auth-server/src/handlers/mock-upstream-idp.ts @@ -3,9 +3,38 @@ import { generateMcpTokens, readPendingAuthorization, saveMcpInstallation, saveR import { McpInstallation } from "../types.js"; import { logger } from "../utils/logger.js"; -// Mock upstream identity provider - simulates Google OAuth, corporate SAML, or other external identity providers -// that OAuth servers can delegate user authentication to (configurable in Auth0/Okta). -// In production, the OAuth server would redirect to actual external IDPs. +/** + * ============================================================================ + * MOCK UPSTREAM IDENTITY PROVIDER - FOR DEMONSTRATION ONLY + * ============================================================================ + * + * This file simulates what happens when an OAuth server delegates user + * authentication to an external identity provider. In production, this would be: + * + * - Google OAuth (accounts.google.com) + * - GitHub OAuth (github.com/login) + * - Corporate SSO (SAML, LDAP, Active Directory) + * - Auth0/Okta user database + * + * The mock implementation: + * - Shows a user selection UI + * - Generates random user IDs for testing + * - Simulates the redirect flow back to the OAuth server + * + * In production, users would see their actual identity provider's login page + * (Google's login, GitHub's login, corporate SSO portal, etc.) + * + * ============================================================================ + */ + +/** + * Mock authorization endpoint - simulates external IDP login page + * In production, this would be replaced by redirecting to: + * - https://accounts.google.com/oauth/authorize (Google) + * - https://github.com/login/oauth/authorize (GitHub) + * - https://login.microsoftonline.com (Azure AD) + * - Your corporate SSO login page + */ export async function handleMockUpstreamAuthorize(req: Request, res: Response) { // get the redirect_uri and state from the query params const { redirect_uri, state } = req.query; diff --git a/external-oauth/auth-server/src/index.ts b/auth-server/src/index.ts similarity index 100% rename from external-oauth/auth-server/src/index.ts rename to auth-server/src/index.ts diff --git a/embedded-oauth/src/redis.ts b/auth-server/src/redis.ts similarity index 100% rename from embedded-oauth/src/redis.ts rename to auth-server/src/redis.ts diff --git a/embedded-oauth/src/services/auth.test.ts b/auth-server/src/services/auth.test.ts similarity index 100% rename from embedded-oauth/src/services/auth.test.ts rename to auth-server/src/services/auth.test.ts diff --git a/embedded-oauth/src/services/auth.ts b/auth-server/src/services/auth.ts similarity index 100% rename from embedded-oauth/src/services/auth.ts rename to auth-server/src/services/auth.ts diff --git a/embedded-oauth/src/services/redis-auth.ts b/auth-server/src/services/redis-auth.ts similarity index 100% rename from embedded-oauth/src/services/redis-auth.ts rename to auth-server/src/services/redis-auth.ts diff --git a/embedded-oauth/src/static/index.html b/auth-server/src/static/index.html similarity index 100% rename from embedded-oauth/src/static/index.html rename to auth-server/src/static/index.html diff --git a/embedded-oauth/src/static/mcp.png b/auth-server/src/static/mcp.png similarity index 100% rename from embedded-oauth/src/static/mcp.png rename to auth-server/src/static/mcp.png diff --git a/embedded-oauth/src/static/styles.css b/auth-server/src/static/styles.css similarity index 100% rename from embedded-oauth/src/static/styles.css rename to auth-server/src/static/styles.css diff --git a/embedded-oauth/src/types.ts b/auth-server/src/types.ts similarity index 100% rename from embedded-oauth/src/types.ts rename to auth-server/src/types.ts diff --git a/embedded-oauth/src/utils/logger.ts b/auth-server/src/utils/logger.ts similarity index 100% rename from embedded-oauth/src/utils/logger.ts rename to auth-server/src/utils/logger.ts diff --git a/embedded-oauth/tsconfig.json b/auth-server/tsconfig.json similarity index 100% rename from embedded-oauth/tsconfig.json rename to auth-server/tsconfig.json diff --git a/docs/endpoints.md b/docs/endpoints.md index 00a6de1..2e40e5d 100644 --- a/docs/endpoints.md +++ b/docs/endpoints.md @@ -1,10 +1,10 @@ # Endpoint Reference -Complete listing of all endpoints provided by each server configuration. +Complete listing of all endpoints provided by each server in the architecture. -## Embedded OAuth Server (Port 3232) +## Auth Server (Port 3001) -Single server hosting OAuth authorization, mock identity provider, and MCP resources. +Standalone OAuth 2.0 authorization server that handles authentication and token management. ### OAuth Authorization Endpoints Provided by `mcpAuthRouter` from MCP SDK: @@ -15,62 +15,21 @@ Provided by `mcpAuthRouter` from MCP SDK: - `POST /token` - Token exchange (authorization code → tokens) and token refresh - `POST /revoke` - Token revocation -### Mock Upstream Identity Provider Endpoints -Local simulation of upstream IDP (would be external in production): - -- `GET /mock-upstream-idp/authorize` - Mock user authentication page -- `GET /mock-upstream-idp/callback` - IDP callback handler (returns userId) - -**Note**: In production, the OAuth server would redirect to external URLs like `https://accounts.google.com` or `https://login.okta.com` instead of these local endpoints. - -### MCP Resource Endpoints - -#### Streamable HTTP Transport (Recommended) -- `GET /mcp` - Establish SSE stream for session -- `POST /mcp` - Initialize session or send messages -- `DELETE /mcp` - Terminate session - -#### SSE Transport (Legacy) -- `GET /sse` - Establish SSE connection -- `POST /message` - Send messages to session - -All MCP endpoints require `Authorization: Bearer ` header. - -### Static Assets -- `GET /` - Splash page (HTML) -- `GET /mcp-logo.png` - MCP logo -- `GET /styles.css` - Stylesheet - ---- - -## External OAuth - Auth Server (Port 3001) - -Standalone OAuth authorization server (represents Auth0, Okta, etc. in production). - -### OAuth Authorization Endpoints -Provided by `mcpAuthRouter` from MCP SDK: - -- `GET /.well-known/oauth-authorization-server` - OAuth metadata discovery -- `POST /register` - Dynamic client registration -- `GET /authorize` - Authorization request -- `POST /token` - Token exchange and refresh -- `POST /revoke` - Token revocation - ### Token Introspection Custom implementation for resource server token validation: -- `POST /introspect` - Token introspection (RFC 7662) +- `POST /introspect` - Token introspection ([RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662)) - Called by MCP server to validate tokens - Returns token status, scopes, expiry, user info - Protected endpoint (not public) ### Mock Upstream Identity Provider Endpoints -Local simulation of upstream IDP: +Local simulation of upstream IDP (would be external in production): - `GET /mock-upstream-idp/authorize` - Mock user authentication page -- `GET /mock-upstream-idp/callback` - IDP callback handler +- `GET /mock-upstream-idp/callback` - IDP callback handler (returns userId) -**Note**: Commercial OAuth providers (Auth0, Okta) have their own user authentication systems. These endpoints simulate that functionality. +**Note**: In production, this would redirect to external providers like Auth0, Okta, Google, GitHub, etc. These endpoints simulate that functionality for demonstration purposes. ### Utility Endpoints - `GET /health` - Health check (returns server status) @@ -78,9 +37,9 @@ Local simulation of upstream IDP: --- -## External OAuth - MCP Server (Port 3232) +## MCP Server (Port 3232) -Pure MCP resource server with no OAuth authorization functionality. +MCP resource server that implements the Model Context Protocol with delegated authentication. ### OAuth Metadata (Read-Only) Provided by `mcpAuthMetadataRouter`: @@ -103,21 +62,21 @@ Provided by `mcpAuthMetadataRouter`: All MCP endpoints require `Authorization: Bearer ` header. Tokens are validated by calling the auth server's `/introspect` endpoint. ### Static Assets -- `GET /` - Splash page +- `GET /` - Splash page (HTML) - `GET /mcp-logo.png` - MCP logo - `GET /styles.css` - Stylesheet --- -## Key Differences +## Architecture Overview -| Endpoint Type | Embedded OAuth | External Auth Server | External MCP Server | -|---------------|----------------|---------------------|---------------------| -| OAuth authorization (`/authorize`, `/token`) | ✅ Full | ✅ Full | ❌ None | -| Token introspection (`/introspect`) | ❌ Not needed | ✅ Yes | ❌ Not needed | -| OAuth metadata discovery | ✅ Yes | ✅ Yes | ✅ Read-only redirect | -| Mock IDP (`/mock-upstream-idp`) | ✅ Yes | ✅ Yes | ❌ No | -| MCP resources (`/mcp`, `/sse`) | ✅ Yes | ❌ No | ✅ Yes | +| Endpoint Type | Auth Server | MCP Server | +|---------------|-------------|------------| +| OAuth authorization (`/authorize`, `/token`) | ✅ Full implementation | ❌ None (delegates to auth server) | +| Token introspection (`/introspect`) | ✅ Provides service | ❌ Consumes service | +| OAuth metadata discovery | ✅ Authoritative | ✅ Read-only redirect | +| Mock IDP (`/mock-upstream-idp`) | ✅ Yes | ❌ No | +| MCP resources (`/mcp`, `/sse`) | ❌ No | ✅ Yes | --- @@ -146,9 +105,20 @@ All MCP endpoints require `Authorization: Bearer ` header. Tokens are val --- +## Authentication Flow + +1. **Client discovers auth server**: GET `/.well-known/oauth-authorization-server` from MCP server +2. **Client registers**: POST to auth server's `/register` endpoint +3. **User authorizes**: Redirected to auth server's `/authorize` endpoint +4. **Token exchange**: POST to auth server's `/token` endpoint +5. **Access MCP resources**: Use bearer token with MCP server endpoints +6. **Token validation**: MCP server validates tokens via auth server's `/introspect` endpoint + +--- + ## References - [MCP Transport Specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports) - [MCP Authorization Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) - [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) -- [OAuth 2.0 Endpoints](https://www.oauth.com/oauth2-servers/definitions/) +- [OAuth 2.0 Endpoints](https://www.oauth.com/oauth2-servers/definitions/) \ No newline at end of file diff --git a/docs/oauth-flow.md b/docs/oauth-flow.md index 8457b2e..7df9b1e 100644 --- a/docs/oauth-flow.md +++ b/docs/oauth-flow.md @@ -1,10 +1,10 @@ # OAuth 2.0 + PKCE Flow Analysis -This document details the complete OAuth 2.0 authorization code flow with PKCE as implemented in this reference server, including how it differs between integrated and separate modes. +This document details the complete OAuth 2.0 authorization code flow with PKCE as implemented in this reference server with separate authorization and resource servers. ## Flow Overview -The server implements OAuth 2.1 with PKCE (Proof Key for Code Exchange) for secure authorization. Here's how each step maps to data storage, expiry, and mode-specific behavior: +The server implements OAuth 2.1 with PKCE (Proof Key for Code Exchange) for secure authorization. The architecture separates the authorization server (port 3001) from the MCP resource server (port 3232). ## 1. Client Registration @@ -19,8 +19,7 @@ Auth Server → App: {"client_id": "XYZ", "client_secret": "ABC", ...} **Storage**: - Redis key: `auth:client:{clientId}` - Expiry: 30 days (long-lived app credentials) - -**Mode differences**: None - identical in both modes. +- Server: Auth Server (port 3001) --- @@ -44,10 +43,7 @@ Auth Server: Saves pending authorization, shows authorization page - Redis key: `auth:pending:{authCode}` - Expiry: 10 minutes (short-lived temporary state) - Data: redirect_uri, code_challenge, code_challenge_method, client_id, state - -**Mode differences**: -- **Embedded OAuth**: OAuth server runs on MCP server (port 3232) -- **External OAuth**: OAuth server runs independently (port 3001) +- Server: Auth Server (port 3001) --- @@ -55,23 +51,19 @@ Auth Server: Saves pending authorization, shows authorization page **Purpose**: Authenticate the user and obtain consent. -**Flow** (identical in both modes): +**Flow**: ``` -OAuth Server → User: Shows auth page with "Continue to Authentication" button -User → OAuth Server: Clicks button -OAuth Server → Upstream IDP: Redirects to /mock-upstream-idp/authorize +Auth Server → User: Shows auth page with "Continue to Authentication" button +User → Auth Server: Clicks button +Auth Server → Upstream IDP: Redirects to /mock-upstream-idp/authorize Upstream IDP → User: Shows user selection UI User → Upstream IDP: Selects/creates user ID -Upstream IDP → OAuth Server: Redirects to /mock-upstream-idp/callback?userId=X -OAuth Server: Validates user, issues authorization code -OAuth Server → App: Redirects to app's redirect_uri with code +Upstream IDP → Auth Server: Redirects to /mock-upstream-idp/callback?userId=X +Auth Server: Validates user, issues authorization code +Auth Server → App: Redirects to app's redirect_uri with code ``` -**Mode differences**: -- **Embedded OAuth**: OAuth Server runs on MCP server (port 3232) -- **External OAuth**: OAuth Server runs independently (port 3001) - -The flow itself is identical. Both modes delegate user authentication to an upstream IDP (simulated by `/mock-upstream-idp/*` endpoints, which represent corporate SSO or social login in production). +**Note**: In production, the upstream IDP would be an external provider (Auth0, Okta, Google, GitHub, etc.). The `/mock-upstream-idp/*` endpoints simulate this for demonstration purposes. --- @@ -109,10 +101,7 @@ Auth Server → App: - `auth:installation:{accessToken}` - Active MCP installation - `auth:refresh:{refreshToken}` - Refresh token mapping - Expiry: 10 minutes for exchange record, 7 days for installation - -**Mode differences**: None in the exchange itself. The endpoint location differs: -- **Embedded OAuth**: `http://localhost:3232/token` -- **External OAuth**: `http://localhost:3001/token` +- Server: Auth Server (port 3001) --- @@ -127,29 +116,28 @@ App → MCP Server: POST /mcp Mcp-Session-Id: {MCP request} -MCP Server: Validates token → Serves MCP resource -``` +MCP Server → Auth Server: POST /introspect + token= -**Token Validation - Embedded OAuth**: -``` -MCP Server (same process as OAuth server): - - Reads auth:installation:{accessToken} from Redis - - Validates expiry, scopes - - Returns user info directly -``` +Auth Server → MCP Server: + { + "active": true, + "userId": "...", + "exp": ..., + "aud": "http://localhost:3232" + } -**Token Validation - External OAuth**: -``` -MCP Server: - - Calls POST {AUTH_SERVER_URL}/introspect with token - - Auth server validates and returns token info - - MCP server validates audience matches BASE_URI - - Proceeds with request +MCP Server: Validates audience, serves MCP resource ``` -**Mode differences**: This is the key architectural difference: -- **Embedded OAuth**: In-process token validation (fast, direct Redis access) -- **External OAuth**: Remote token validation via HTTP introspection (adds network hop, follows RFC 7662) +**Token Validation Process**: +1. MCP Server receives request with bearer token +2. Calls Auth Server's `/introspect` endpoint (RFC 7662) +3. Auth Server validates token and returns metadata +4. MCP Server validates audience matches `BASE_URI` +5. Request proceeds with authenticated user context + +This separation allows the Auth Server to be replaced with commercial providers (Auth0, Okta) while keeping the MCP server unchanged. --- @@ -179,8 +167,7 @@ Auth Server → App: {"access_token": "...", ...} - Writes: New `auth:installation:{newAccessToken}` - Writes: New `auth:refresh:{newRefreshToken}` - Expiry: 7 days - -**Mode differences**: None - identical flow, just different endpoint locations. +- Server: Auth Server (port 3001) --- @@ -205,7 +192,17 @@ PKCE prevents authorization code interception attacks: This ensures only the client that initiated the flow can exchange the code, even if the code is intercepted. -**Mode differences**: None - PKCE works identically in both modes. +--- + +## Architecture Benefits + +The separate server architecture provides: + +1. **Standards Compliance**: Follows OAuth 2.0 best practices for resource/authorization server separation +2. **Flexibility**: Auth server can be replaced with Auth0, Okta, or other providers +3. **Scalability**: Auth and MCP servers can scale independently based on load +4. **Security**: Token validation via introspection endpoint (RFC 7662) +5. **Maintainability**: Clear separation of authentication and business logic --- @@ -214,4 +211,4 @@ This ensures only the client that initiated the flow can exchange the code, even - [RFC 6749: OAuth 2.0 Framework](https://datatracker.ietf.org/doc/html/rfc6749) - [RFC 7636: PKCE](https://datatracker.ietf.org/doc/html/rfc7636) - [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) -- [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) +- [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) \ No newline at end of file diff --git a/docs/oauth-patterns.md b/docs/oauth-patterns.md new file mode 100644 index 0000000..d33e7b6 --- /dev/null +++ b/docs/oauth-patterns.md @@ -0,0 +1,269 @@ +# OAuth Architecture Patterns + +This document describes different OAuth 2.0 architecture patterns for MCP servers, as specified in the [MCP Authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization). + +## Overview + +Per the MCP specification, "the authorization server may be hosted with the resource server or as a separate entity." This leads to two primary patterns for implementing OAuth in MCP servers. + +## Pattern 1: Separate Authorization Server (Implemented) + +The current implementation uses this production-ready pattern with separate authorization and resource servers. + +### Architecture + +``` +┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ │ OAuth │ │ Token │ │ +│ MCP Client │────────>│ Auth Server │<────────│ MCP Server │ +│ │ │ (port 3001) │validate │ (port 3232) │ +│ │<────────│ │ │ │ +│ │ token │ Issues tokens │ │ Serves MCP │ +│ │ │ │ │ resources │ +│ │────────────────────────────────────> │ │ +│ │ MCP requests with token │ │ +└─────────────┘ └─────────────────┘ +``` + +### Components + +1. **Authorization Server** (port 3001) + - Handles OAuth 2.0 authorization flow + - Issues and manages tokens + - Provides token introspection endpoint (RFC 7662) + - Can be replaced with Auth0, Okta, Google OAuth, etc. + +2. **MCP Resource Server** (port 3232) + - Serves MCP protocol resources + - Validates tokens via introspection + - Contains no OAuth authorization code + - Focuses purely on MCP functionality + +### Benefits + +- **Standards Compliance**: Follows OAuth 2.0 best practices +- **Flexibility**: Easy to swap auth providers +- **Scalability**: Servers scale independently +- **Security**: Clear security boundaries +- **Maintainability**: Separation of concerns + +### Real-World Use Cases + +- **SaaS Applications**: Using Auth0 or Okta for authentication +- **Enterprise**: Integrating with corporate SSO (SAML, LDAP) +- **Social Login**: Google, GitHub, Facebook authentication +- **Cloud Native**: AWS Cognito, Azure AD integration + +### Implementation Details + +The MCP server validates tokens by calling the auth server's `/introspect` endpoint: + +```typescript +// In MCP server +const response = await fetch(`${AUTH_SERVER_URL}/introspect`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `token=${accessToken}` +}); + +const introspection = await response.json(); +if (introspection.active) { + // Token is valid, extract user info + const userId = introspection.sub; +} +``` + +--- + +## Pattern 2: Embedded Authorization Server (Alternative) + +An alternative pattern where the OAuth server is embedded within the MCP server. This demonstrates a self-hosted OAuth 2.1 authorization server running in the same process as the MCP server. + +### Architecture + +``` +┌─────────────┐ ┌─────────────────────────┐ +│ │ OAuth │ │ +│ MCP Client │────────>│ MCP Server │ +│ │ │ ┌──────────────────┐ │ +│ │<────────│ │ OAuth Server │ │ +│ │ token │ └──────────────────┘ │ +│ │ │ ┌──────────────────┐ │ +│ │────────>│ │ MCP Resources │ │ +│ │ MCP │ └──────────────────┘ │ +└─────────────┘ └─────────────────────────┘ +``` + +### Upstream Delegation Pattern + +Embedded OAuth often delegates user authentication to an upstream identity provider while maintaining control over token issuance: + +``` +MCP Client MCP+OAuth Server Upstream IDP + │ │ (Corporate SSO) + │──1. /authorize───────────────>│ │ + │<───(show auth page)───────────│ │ + │ │ │ + │──2. Click "Continue"──────────>│ │ + │<──redirect to upstream────────│ │ + │ │ │ + │──3. /upstream/authorize────────────────────────────────────────>│ + │<──(authenticate user)──────────────────────────────────────────│ + │──4. Provide credentials────────────────────────────────────────>│ + │<──redirect with userId─────────────────────────────────────────│ + │ │ │ + │──5. /callback with userId───────>│ │ + │ │──(validate userId) │ + │ │──(issue MCP tokens) │ + │<──redirect with auth code──────│ │ + │ │ │ + │──6. /token (exchange code)─────>│ │ + │<──MCP access token─────────────│ │ +``` + +### Characteristics + +- **Single Server**: OAuth and MCP in one process +- **Port**: Typically runs on single port (e.g., 3232) +- **Token Validation**: In-process, direct database access +- **Deployment**: Simpler, fewer moving parts +- **Upstream Delegation**: Can delegate authentication to corporate SSO while controlling token issuance + +### Benefits + +- **Simplicity**: Single server to deploy and manage +- **Performance**: No network hop for token validation +- **Self-Contained**: All functionality in one codebase +- **Control**: Full control over token issuance while leveraging existing identity infrastructure + +### Drawbacks + +- **Coupling**: Auth and MCP logic intertwined +- **Scalability**: Can't scale auth independently +- **Flexibility**: Harder to switch auth providers +- **Updates**: Auth changes require MCP server updates + +### Use Cases + +- **Enterprise Deployments**: Organizations needing control over OAuth token issuance while leveraging existing corporate identity infrastructure (LDAP, Active Directory, SAML) +- **Proof of Concepts**: Quick prototypes and demonstrations +- **Small Deployments**: Internal tools with simple auth needs +- **Isolated Systems**: Air-gapped environments without external connectivity +- **Custom Auth Requirements**: Specialized authentication needs not met by standard providers + +### Typical Configuration + +Environment variables for embedded pattern: +```bash +BASE_URI=http://localhost:3232 # Single server URL +PORT=3232 # Single port for OAuth + MCP +REDIS_URL=redis://localhost:6379 # Session storage +UPSTREAM_IDP_URL=https://corp.sso # Optional: upstream identity provider +``` + +### Code Organization + +Typical structure for embedded OAuth implementation: +``` +src/ +├── index.ts # Main entry point (MCP + OAuth + routing) +├── auth/ +│ ├── provider.ts # OAuth server implementation +│ └── auth-core.ts # Token generation, PKCE utilities +├── services/ +│ ├── mcp.ts # MCP protocol implementation +│ ├── auth.ts # Auth service integration +│ ├── redis-auth.ts # Redis auth operations +│ └── redisTransport.ts # Redis-backed transport +├── handlers/ +│ ├── oauth.ts # OAuth endpoints (/authorize, /token) +│ ├── mcp-shttp.ts # Streamable HTTP handler +│ ├── mcp-sse.ts # SSE handler +│ └── upstream-idp.ts # Upstream IDP integration +└── utils/ + └── logger.ts # Structured logging +``` + +--- + +## Comparison + +| Aspect | Separate Servers | Embedded Server | +|--------|------------------|-----------------| +| **Architecture** | 2+ servers | 1 server | +| **OAuth endpoints** | On auth server | On MCP server | +| **Token validation** | Remote (introspection) | In-process | +| **Deployment** | More complex | Simpler | +| **Scalability** | Independent scaling | Coupled scaling | +| **Provider integration** | Easy (standard APIs) | Difficult | +| **Code organization** | Clear separation | Mixed concerns | +| **Network hops** | Additional for validation | None for validation | +| **Upstream IDP support** | Via auth server | Direct integration | +| **Production readiness** | ✅ Recommended | ⚠️ Limited use cases | + +--- + +## Migration Path + +### From Embedded to Separate + +If starting with embedded OAuth, migration to separate servers involves: + +1. **Extract auth code**: Move OAuth handlers to separate service +2. **Implement introspection**: Add RFC 7662 endpoint +3. **Update MCP server**: Replace in-process validation with introspection calls +4. **Update configuration**: Point MCP server to auth server URL +5. **Migrate upstream IDP**: Move delegation logic to auth server +6. **Test thoroughly**: Ensure token flow works correctly + +### From Separate to Commercial Provider + +Replacing the demo auth server with a commercial provider: + +1. **Configure provider**: Set up OAuth app in Auth0/Okta +2. **Update metadata URL**: Point to provider's discovery endpoint +3. **Configure introspection**: Set up token validation +4. **Update redirect URIs**: Configure allowed callbacks +5. **Migrate users**: Import existing users if needed +6. **Test integration**: Verify full OAuth flow + +--- + +## Best Practices + +1. **Use separate servers** for production deployments +2. **Implement token caching** to reduce introspection calls +3. **Use PKCE** for all OAuth flows (prevents code interception) +4. **Validate token audience** to prevent token substitution +5. **Implement proper session management** with Redis or similar +6. **Use HTTPS** in production for all endpoints +7. **Monitor token expiration** and implement refresh logic +8. **Log authentication events** for security auditing +9. **Consider upstream delegation** for enterprise deployments +10. **Document auth flow** for your specific implementation + +--- + +## Testing Considerations + +When implementing either pattern, ensure comprehensive testing: + +- **Unit tests**: Test OAuth flows, token validation, session management +- **Integration tests**: Test auth server and MCP server interaction +- **E2E tests**: Test complete user flows from authorization to resource access +- **Security tests**: Test PKCE validation, token expiration, session isolation +- **Performance tests**: Test token validation caching, concurrent sessions + +--- + +## References + +- [MCP Authorization Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) +- [RFC 6749: OAuth 2.0 Framework](https://datatracker.ietf.org/doc/html/rfc6749) +- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) +- [RFC 7636: PKCE](https://datatracker.ietf.org/doc/html/rfc7636) +- [OAuth 2.0 Security Best Practices](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics) +- [OAuth 2.1 Draft](https://oauth.net/2.1/) - Modern security requirements +- [The OAuth 2.0 Authorization Framework in Practice](https://www.oauth.com/) \ No newline at end of file diff --git a/docs/session-ownership.md b/docs/session-ownership.md index 0f0b8d8..31074ec 100644 --- a/docs/session-ownership.md +++ b/docs/session-ownership.md @@ -23,11 +23,11 @@ User ID sources: When the MCP server validates an access token: -**Embedded OAuth**: Reads `McpInstallation` from Redis, extracts `installation.userId` +1. MCP server calls the auth server's `/introspect` endpoint with the token +2. Auth server validates the token and returns user information including the `sub` field (user ID) +3. MCP server populates `AuthInfo.extra.userId` with this user identifier -**External OAuth**: Calls auth server's `/introspect` endpoint, receives `sub` field - -Both result in `AuthInfo.extra.userId` being populated. +This separation allows the auth server to be replaced with any OAuth provider that supports token introspection. ### Session Creation diff --git a/embedded-oauth/README.md b/embedded-oauth/README.md deleted file mode 100644 index f56289c..0000000 --- a/embedded-oauth/README.md +++ /dev/null @@ -1,135 +0,0 @@ -# Embedded OAuth - MCP Feature Reference Server - -This is a **complete, standalone implementation** of an MCP server with self-hosted OAuth authentication. Everything you need is in this directory. - -## Overview - -Per the [MCP specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), "the authorization server may be hosted with the resource server." This mode demonstrates a self-hosted OAuth 2.1 authorization server running in the same process as the MCP server. - -**Pattern demonstrated**: OAuth server that delegates user authentication to an upstream identity provider (corporate SSO, LDAP, Active Directory). - -**Real-world use case**: Enterprise deployments that need control over OAuth token issuance while leveraging existing corporate identity infrastructure. - -For the OAuth-as-a-Service pattern (using Auth0/Okta directly), see [external-oauth](../external-oauth/README.md). - -## Quick Start - -```bash -# 1. Make sure Redis is running (from repo root) -docker compose up -d - -# 2. Install dependencies (if not already done) -npm install - -# 3. Start the server -npm run dev - -# 4. Test with MCP Inspector -npx -y @modelcontextprotocol/inspector -# Connect to: http://localhost:3232/mcp -``` - -## Available Commands - -```bash -npm run dev # Start with hot reload -npm run dev:break # Start with debugger -npm run build # Build to dist/ -npm start # Run built version -npm test # Run 85 unit tests -npm run lint # Lint code -npm run typecheck # Check types -``` - -## Configuration - -Environment variables are in `.env`: -```bash -BASE_URI=http://localhost:3232 -PORT=3232 -REDIS_URL=redis://localhost:6379 -``` - -## What This Demonstrates - -**OAuth + MCP integration**: -- Self-hosted OAuth 2.1 server with PKCE -- Upstream identity provider delegation (simulated via `/mock-upstream-idp/*`) -- Complete authorization flow in one codebase - -**MCP features**: -- Tools (7), resources (100), prompts (3), sampling, logging, completions -- Streamable HTTP and SSE transports -- Redis-backed horizontal scaling -- Session isolation and user ownership - -## Understanding the Upstream Delegation Pattern - -This implementation demonstrates a common enterprise OAuth pattern: - -``` -MCP Client This Server Simulated Upstream - │ │ │ - │──1. /authorize───────────────>│ │ - │<───(show auth page)───────────│ │ - │ │ │ - │──2. Click "Continue"──────────>│ │ - │<──redirect /mock-upstream-idp─│ │ - │ │ │ - │──3. /mock-upstream-idp/authorize──────────────────────────────>│ - │<──(show user selection)───────────────────────────────────────│ - │──4. Select user───────────────────────────────────────────────>│ - │<──redirect with userId────────────────────────────────────────│ - │ │ │ - │──5. /mock-upstream-idp/callback──>│ │ - │ │──(validate userId) │ - │ │──(issue MCP tokens) │ - │<──redirect with auth code─────│ │ - │ │ │ - │──6. /token (exchange code)────>│ │ - │<──MCP access token────────────│ │ -``` - -**In production**, `/mock-upstream-idp/*` would be replaced by corporate SSO (Okta, Azure AD, etc.). - -## Code Organization - -``` -src/ -├── index.ts # Main entry point (MCP + OAuth + routing) -├── auth/ -│ ├── provider.ts # FeatureReferenceAuthProvider (OAuth server logic) -│ └── auth-core.ts # Token generation, PKCE utilities -├── services/ -│ ├── mcp.ts # MCP server with all features -│ ├── auth.ts # Auth service wrappers -│ ├── redis-auth.ts # Redis auth operations -│ └── redisTransport.ts # Redis-backed transport -├── handlers/ -│ ├── shttp.ts # Streamable HTTP handler -│ ├── sse.ts # SSE handler -│ ├── mock-upstream-idp.ts # Mock upstream IDP simulation -│ └── common.ts # Shared middleware -└── utils/ - └── logger.ts # Structured logging -``` - -## Next Steps - -- Modify `src/services/mcp.ts` to add custom tools and resources -- See [external-oauth](../external-oauth/README.md) for external OAuth pattern -- See [docs/oauth-flow.md](../docs/oauth-flow.md) for detailed OAuth flow analysis - -## Testing - -```bash -npm test # 85 unit tests -npm run test:e2e:integrated # E2E test (from repo root) -``` - -## References - -- [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) -- [OAuth 2.1 Draft](https://oauth.net/2.1/) -- [RFC 6749: OAuth 2.0 Framework](https://datatracker.ietf.org/doc/html/rfc6749) -- [docs/oauth-flow.md](../docs/oauth-flow.md) - Detailed flow analysis with mode differences diff --git a/embedded-oauth/package-lock.json b/embedded-oauth/package-lock.json deleted file mode 100644 index 47cc325..0000000 --- a/embedded-oauth/package-lock.json +++ /dev/null @@ -1,7141 +0,0 @@ -{ - "name": "mcp-server-feature-reference-integrated", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "mcp-server-feature-reference-integrated", - "version": "0.1.0", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.15.1", - "@redis/client": "^1.6.0", - "cors": "^2.8.5", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "express-rate-limit": "^8.0.1", - "raw-body": "^3.0.0" - }, - "devDependencies": { - "@eslint/js": "^9.15.0", - "@types/content-type": "^1.1.8", - "@types/cors": "^2.8.17", - "@types/express": "^5.0.0", - "@types/jest": "^29.5.14", - "@types/node": "^22.10.0", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "typescript-eslint": "^8.18.0" - } - }, - "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.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@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.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@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.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "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.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.4" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "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-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "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-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "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/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.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", - "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", - "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", - "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", - "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", - "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", - "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", - "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", - "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", - "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", - "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", - "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", - "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", - "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", - "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", - "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", - "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", - "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", - "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", - "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", - "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", - "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", - "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", - "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", - "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", - "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", - "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", - "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@eslint/core": "^0.15.2", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "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/@modelcontextprotocol/sdk": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.18.2.tgz", - "integrity": "sha512-beedclIvFcCnPrYgHsylqiYJVJ/CI47Vyc4tY8no1/Li/O8U4BTlJfy6ZwxkYwx+Mx10nrgwSVrA7VBbhh4slg==", - "license": "MIT", - "dependencies": { - "ajv": "^6.12.6", - "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", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "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": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/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/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@redis/client": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", - "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", - "license": "MIT", - "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "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/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/content-type": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.9.tgz", - "integrity": "sha512-Hq9IMnfekuOCsEmYl4QX2HBrT+XsfXiupfrLLY8Dcf3Puf4BkBOxSbWYTITSOQAhJoYPBez+b4MJRpIYL65z8A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "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", - "peer": true - }, - "node_modules/@types/express": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", - "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", - "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.18.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz", - "integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz", - "integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.45.0", - "@typescript-eslint/type-utils": "8.45.0", - "@typescript-eslint/utils": "8.45.0", - "@typescript-eslint/visitor-keys": "8.45.0", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.45.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz", - "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.45.0", - "@typescript-eslint/types": "8.45.0", - "@typescript-eslint/typescript-estree": "8.45.0", - "@typescript-eslint/visitor-keys": "8.45.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz", - "integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.45.0", - "@typescript-eslint/types": "^8.45.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz", - "integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.45.0", - "@typescript-eslint/visitor-keys": "8.45.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz", - "integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz", - "integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.45.0", - "@typescript-eslint/typescript-estree": "8.45.0", - "@typescript-eslint/utils": "8.45.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz", - "integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz", - "integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.45.0", - "@typescript-eslint/tsconfig-utils": "8.45.0", - "@typescript-eslint/types": "8.45.0", - "@typescript-eslint/visitor-keys": "8.45.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz", - "integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.45.0", - "@typescript-eslint/types": "8.45.0", - "@typescript-eslint/typescript-estree": "8.45.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz", - "integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.45.0", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0", - "peer": true - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.9.tgz", - "integrity": "sha512-hY/u2lxLrbecMEWSB0IpGzGyDyeoMFQhCvZd2jGFSE5I17Fh01sYUBPCJtkWERw7zrac9+cIghxm/ytJa2X8iA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/body-parser/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.26.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", - "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", - "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.3", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "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/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001746", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz", - "integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==", - "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/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "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/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "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/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/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "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.228", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz", - "integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "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/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "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.25.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", - "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.10", - "@esbuild/android-arm": "0.25.10", - "@esbuild/android-arm64": "0.25.10", - "@esbuild/android-x64": "0.25.10", - "@esbuild/darwin-arm64": "0.25.10", - "@esbuild/darwin-x64": "0.25.10", - "@esbuild/freebsd-arm64": "0.25.10", - "@esbuild/freebsd-x64": "0.25.10", - "@esbuild/linux-arm": "0.25.10", - "@esbuild/linux-arm64": "0.25.10", - "@esbuild/linux-ia32": "0.25.10", - "@esbuild/linux-loong64": "0.25.10", - "@esbuild/linux-mips64el": "0.25.10", - "@esbuild/linux-ppc64": "0.25.10", - "@esbuild/linux-riscv64": "0.25.10", - "@esbuild/linux-s390x": "0.25.10", - "@esbuild/linux-x64": "0.25.10", - "@esbuild/netbsd-arm64": "0.25.10", - "@esbuild/netbsd-x64": "0.25.10", - "@esbuild/openbsd-arm64": "0.25.10", - "@esbuild/openbsd-x64": "0.25.10", - "@esbuild/openharmony-arm64": "0.25.10", - "@esbuild/sunos-x64": "0.25.10", - "@esbuild/win32-arm64": "0.25.10", - "@esbuild/win32-ia32": "0.25.10", - "@esbuild/win32-x64": "0.25.10" - } - }, - "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/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", - "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.36.0", - "@eslint/plugin-kit": "^0.3.5", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.1.0.tgz", - "integrity": "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA==", - "license": "MIT", - "dependencies": { - "ip-address": "10.0.1" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "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-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC", - "peer": true - }, - "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": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "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/generic-pool": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", - "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "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-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "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-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "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/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-tsconfig": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "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/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "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-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "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/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "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-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "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/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "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/lru-cache/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/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "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": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "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": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "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/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "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.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "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/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "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/router/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/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "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/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": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "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/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "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/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-jest": { - "version": "29.4.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.4.tgz", - "integrity": "sha512-ccVcRABct5ZELCT5U0+DZwkXMCcOCLi2doHRrKy1nK/s7J7bch6TzJMsrY09WxgUUIP/ITfmcDS8D2yl63rnXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.2", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jest-util": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "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/typescript-eslint": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz", - "integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.45.0", - "@typescript-eslint/parser": "8.45.0", - "@typescript-eslint/typescript-estree": "8.45.0", - "@typescript-eslint/utils": "8.45.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "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.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "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/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "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/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "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/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "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/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - } - } -} diff --git a/embedded-oauth/package.json b/embedded-oauth/package.json deleted file mode 100644 index feaf367..0000000 --- a/embedded-oauth/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "mcp-server-feature-reference-embedded-oauth", - "version": "0.1.0", - "description": "MCP Feature Reference Server - Embedded OAuth", - "type": "module", - "main": "dist/index.js", - "scripts": { - "start": "node dist/index.js", - "dev": "tsx watch --inspect src/index.ts", - "dev:break": "tsx --inspect-brk watch src/index.ts", - "build": "tsc && npm run copy-static", - "copy-static": "mkdir -p dist/static && cp -r src/static/* dist/static/", - "lint": "eslint src/", - "typecheck": "tsc --noEmit", - "test": "NODE_OPTIONS=--experimental-vm-modules jest" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.15.1", - "@redis/client": "^1.6.0", - "cors": "^2.8.5", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "express-rate-limit": "^8.0.1", - "raw-body": "^3.0.0" - }, - "devDependencies": { - "@eslint/js": "^9.15.0", - "@types/content-type": "^1.1.8", - "@types/cors": "^2.8.17", - "@types/express": "^5.0.0", - "@types/jest": "^29.5.14", - "@types/node": "^22.10.0", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "typescript-eslint": "^8.18.0" - }, - "overrides": { - "@types/express": "^5.0.0", - "@types/express-serve-static-core": "^5.0.2" - } -} diff --git a/embedded-oauth/src/auth/provider.test.ts b/embedded-oauth/src/auth/provider.test.ts deleted file mode 100644 index 03baa83..0000000 --- a/embedded-oauth/src/auth/provider.test.ts +++ /dev/null @@ -1,412 +0,0 @@ -import { jest, describe, beforeEach, it, expect } from '@jest/globals'; -import { Response } from "express"; -import { AuthorizationParams } from "@modelcontextprotocol/sdk/server/auth/provider.js"; -import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; -import { InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors.js"; -import { MockRedisClient, setRedisClient } from "../redis.js"; -import { McpInstallation, PendingAuthorization, TokenExchange } from "../types.js"; -import { FeatureReferenceAuthProvider, FeatureReferenceOAuthClientsStore } from "./provider.js"; -import * as authService from "../services/auth.js"; - -// Helper function to create sample client -function createTestClient(): OAuthClientInformationFull { - return { - client_id: "test-client-id", - client_name: "Test Client", - client_uri: "https://example.com", - redirect_uris: ["https://example.com/callback"] - }; -} - -// Helper function to create a mock Response object -function createMockResponse() { - const res = { - redirect: jest.fn().mockReturnThis(), - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - send: jest.fn().mockReturnThis(), - setHeader: jest.fn().mockReturnThis() - }; - return res as unknown as jest.Mocked; -} - - -function getMockAuthValues() { - const client = createTestClient(); - // Use properly generated tokens for encryption - const accessToken = authService.generateToken(); - const newTokens: OAuthTokens = { - access_token: authService.generateToken(), - refresh_token: authService.generateToken(), - token_type: "bearer", - expires_in: 3600, - }; - const mcpInstallation: McpInstallation = { - mockUpstreamInstallation: { - mockUpstreamAccessToken: "fake-upstream-access-token", - mockUpstreamRefreshToken: "fake-upstream-refresh-token", - }, - mcpTokens: { - access_token: accessToken, - token_type: "Bearer", - refresh_token: authService.generateToken(), - expires_in: 3600, - }, - clientId: client.client_id, - issuedAt: Date.now() / 1000, - userId: "test-user-id", - }; - - return { - client, - accessToken, - newTokens, - mcpInstallation, - } -} - -describe("FeatureReferenceOAuthClientsStore", () => { - let clientsStore: FeatureReferenceOAuthClientsStore; - - beforeEach(() => { - const mockRedis = new MockRedisClient(); - setRedisClient(mockRedis); - jest.resetAllMocks(); - clientsStore = new FeatureReferenceOAuthClientsStore(); - }); - - describe("getClient", () => { - it("returns undefined for non-existent client", async () => { - const result = await clientsStore.getClient("non-existent"); - expect(result).toBeUndefined(); - }); - - it("returns client information for existing client", async () => { - const client = createTestClient(); - // First save the client - await clientsStore.registerClient(client); - - // Then retrieve it - const result = await clientsStore.getClient(client.client_id); - - expect(result).toEqual(client); - }); - }); - - describe("registerClient", () => { - it("saves and returns client information", async () => { - const client = createTestClient(); - - const result = await clientsStore.registerClient(client); - - expect(result).toEqual(client); - - // Verify it was saved - const retrieved = await clientsStore.getClient(client.client_id); - expect(retrieved).toEqual(client); - }); - }); -}); - -describe("FeatureReferenceAuthProvider", () => { - let provider: FeatureReferenceAuthProvider; - let mockRedis: MockRedisClient; - - beforeEach(() => { - jest.resetAllMocks(); - - mockRedis = new MockRedisClient(); - setRedisClient(mockRedis); - - provider = new FeatureReferenceAuthProvider(); - }); - - describe("authorize", () => { - it("saves pending authorization and sends HTML response", async () => { - const client = createTestClient(); - // Use a type assertion to make TypeScript ignore the mismatch - const params = { - redirectUri: "https://example.com/callback", - codeChallenge: "test-challenge", - codeChallengeMethod: "S256", - } as unknown as AuthorizationParams; - const res = createMockResponse(); - - await provider.authorize(client, params, res); - - // Verify HTML sent with redirect - expect(res.send).toHaveBeenCalled(); - const sentHtml = (res.send as jest.Mock).mock.calls[0][0]; - expect(sentHtml).toContain('MCP Server Authorization'); - expect(sentHtml).toContain('Authorization Required'); - expect(sentHtml).toContain('mock-upstream-idp/authorize?redirect_uri=/mock-upstream-idp/callback&state='); - }); - }); - - describe("challengeForAuthorizationCode", () => { - it("returns code challenge for valid authorization code", async () => { - const client = createTestClient(); - const pendingAuth: PendingAuthorization = { - redirectUri: "https://example.com/callback", - codeChallenge: "test-challenge", - codeChallengeMethod: "S256", - clientId: client.client_id, - }; - - // First save the pending authorization - const authCode = authService.generateToken(); - await authService.savePendingAuthorization(authCode, pendingAuth); - - const result = await provider.challengeForAuthorizationCode(client, authCode); - - expect(result).toBe("test-challenge"); - }); - - it("throws error for non-existent authorization code", async () => { - const client = createTestClient(); - - await expect(provider.challengeForAuthorizationCode(client, "test-code")) - .rejects - .toThrow("Authorization code not found"); - }); - - it("throws error when client ID doesn't match", async () => { - const client = createTestClient(); - const pendingAuth: PendingAuthorization = { - redirectUri: "https://example.com/callback", - codeChallenge: "test-challenge", - codeChallengeMethod: "S256", - clientId: "different-client-id", - }; - - // Save pending auth with different client ID - const authCode = authService.generateToken(); - await authService.savePendingAuthorization(authCode, pendingAuth); - - await expect(provider.challengeForAuthorizationCode(client, authCode)) - .rejects - .toThrow("Authorization code does not match client"); - }); - }); - - describe("exchangeAuthorizationCode", () => { - it("returns tokens when exchange is successful", async () => { - const client = createTestClient(); - const { mcpInstallation } = getMockAuthValues(); - - // Setup: save token exchange and installation - const authCode = authService.generateToken(); - const tokenExchange: TokenExchange = { - mcpAccessToken: mcpInstallation.mcpTokens.access_token, - alreadyUsed: false, - }; - await authService.saveTokenExchange(authCode, tokenExchange); - await authService.saveMcpInstallation(mcpInstallation.mcpTokens.access_token, mcpInstallation); - - const result = await provider.exchangeAuthorizationCode(client, authCode); - - expect(result).toEqual({ - access_token: mcpInstallation.mcpTokens.access_token, - expires_in: mcpInstallation.mcpTokens.expires_in, - refresh_token: mcpInstallation.mcpTokens.refresh_token, - token_type: "Bearer", - }); - }); - - it("throws error for invalid authorization code", async () => { - const client = createTestClient(); - - await expect(provider.exchangeAuthorizationCode(client, "test-code")) - .rejects - .toThrow("Invalid authorization code"); - }); - }); - - describe("exchangeRefreshToken", () => { - it("returns new tokens when refresh token is valid", async () => { - const { - client, - accessToken, - mcpInstallation, - } = getMockAuthValues(); - - // Setup: save refresh token and installation - const refreshToken = authService.generateToken(); - await authService.saveRefreshToken(refreshToken, accessToken); - await authService.saveMcpInstallation(accessToken, mcpInstallation); - - const result = await provider.exchangeRefreshToken(client, refreshToken); - - // Should return new tokens - expect(result).toHaveProperty('access_token'); - expect(result).toHaveProperty('refresh_token'); - expect(result).toHaveProperty('expires_in', 3600); - expect(result).toHaveProperty('token_type', 'Bearer'); - }); - - it("throws error for invalid refresh token", async () => { - const client = createTestClient(); - - await expect(provider.exchangeRefreshToken(client, "test-refresh-token")) - .rejects - .toThrow("Invalid refresh token"); - }); - - it("throws error for refresh token with no installation", async () => { - const client = createTestClient(); - const refreshToken = authService.generateToken(); - const accessToken = authService.generateToken(); - - // Only save refresh token, not the installation - await authService.saveRefreshToken(refreshToken, accessToken); - - await expect(provider.exchangeRefreshToken(client, refreshToken)) - .rejects - .toThrow("Invalid refresh token"); - }); - - it("throws error when client ID doesn't match", async () => { - const client = createTestClient(); - const accessToken = authService.generateToken(); - const refreshToken = authService.generateToken(); - - const mcpInstallation: McpInstallation = { - mockUpstreamInstallation: { - mockUpstreamAccessToken: "fake-upstream-access-token", - mockUpstreamRefreshToken: "fake-upstream-refresh-token", - }, - mcpTokens: { - access_token: accessToken, - token_type: "Bearer", - expires_in: 3600, - }, - clientId: "different-client-id", - issuedAt: Date.now() / 1000, - userId: "test-user-id", - }; - - await authService.saveRefreshToken(refreshToken, accessToken); - await authService.saveMcpInstallation(accessToken, mcpInstallation); - - await expect(provider.exchangeRefreshToken(client, refreshToken)) - .rejects - .toThrow("Invalid client"); - }); - - it("works correctly even when the refresh token has expired from Redis", async () => { - const client = createTestClient(); - - // Simulate the refresh token not being found in Redis (expired) - await expect(provider.exchangeRefreshToken(client, "expired-refresh-token")) - .rejects - .toThrow("Invalid refresh token"); - }); - }); - - describe("verifyAccessToken", () => { - it("returns auth info for valid token", async () => { - const accessToken = authService.generateToken(); - const mcpInstallation: McpInstallation = { - mockUpstreamInstallation: { - mockUpstreamAccessToken: "fake-upstream-access-token", - mockUpstreamRefreshToken: "fake-upstream-refresh-token", - }, - mcpTokens: { - access_token: accessToken, - token_type: "Bearer", - expires_in: 3600, - }, - clientId: "client-id", - issuedAt: Date.now() / 1000, - userId: "test-user-id", - }; - - await authService.saveMcpInstallation(accessToken, mcpInstallation); - - const result = await provider.verifyAccessToken(accessToken); - - expect(result).toEqual({ - token: accessToken, - clientId: mcpInstallation.clientId, - scopes: ['mcp'], - expiresAt: mcpInstallation.mcpTokens.expires_in! + mcpInstallation.issuedAt, - extra: { - userId: "test-user-id" - } - }); - }); - - it("throws error for invalid token", async () => { - await expect(provider.verifyAccessToken("invalid-token")) - .rejects - .toThrow("Invalid access token"); - }); - - it("throws InvalidTokenError for expired token", async () => { - const accessToken = authService.generateToken(); - const oneDayInSeconds = 24 * 60 * 60; - const twoDaysAgoInSeconds = Math.floor(Date.now() / 1000) - (2 * oneDayInSeconds); - - const mcpInstallation: McpInstallation = { - mockUpstreamInstallation: { - mockUpstreamAccessToken: "fake-upstream-access-token", - mockUpstreamRefreshToken: "fake-upstream-refresh-token", - }, - mcpTokens: { - access_token: accessToken, - token_type: "Bearer", - expires_in: oneDayInSeconds, - }, - clientId: "client-id", - issuedAt: twoDaysAgoInSeconds, // 2 days ago, with 1-day expiry - userId: "test-user-id", - }; - - await authService.saveMcpInstallation(accessToken, mcpInstallation); - - await expect(provider.verifyAccessToken(accessToken)) - .rejects - .toThrow(InvalidTokenError); - }); - }); - - describe("revokeToken", () => { - it("revokes the installation when given an access token", async () => { - const client = createTestClient(); - const accessToken = authService.generateToken(); - const mcpInstallation: McpInstallation = { - mockUpstreamInstallation: { - mockUpstreamAccessToken: "fake-upstream-access-token", - mockUpstreamRefreshToken: "fake-upstream-refresh-token", - }, - mcpTokens: { - access_token: accessToken, - token_type: "Bearer", - expires_in: 3600, - }, - clientId: client.client_id, - issuedAt: Date.now() / 1000, - userId: "test-user-id", - }; - - // Save the installation - await authService.saveMcpInstallation(accessToken, mcpInstallation); - - // Verify it exists - const saved = await authService.readMcpInstallation(accessToken); - expect(saved).toBeTruthy(); - - // Revoke it - const request: OAuthTokenRevocationRequest = { - token: accessToken, - token_type_hint: "access_token" - }; - - await provider.revokeToken(client, request); - - // Verify it was revoked - const revoked = await authService.readMcpInstallation(accessToken); - expect(revoked).toBeUndefined(); - }); - }); -}); \ No newline at end of file diff --git a/embedded-oauth/src/config.ts b/embedded-oauth/src/config.ts deleted file mode 100644 index cfa2992..0000000 --- a/embedded-oauth/src/config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import "dotenv/config"; - -/** - * Port for the MCP server to listen on - */ -export const PORT = Number(process.env.PORT) || 3232; - -/** - * Base URI for the MCP server. Used for OAuth callbacks and metadata. - * Should match the port if specified separately. - */ -export const BASE_URI = process.env.BASE_URI || `http://localhost:${PORT}`; - -// Validate PORT and BASE_URI consistency -const baseUrl = new URL(BASE_URI); -if (baseUrl.port && parseInt(baseUrl.port) !== PORT) { - console.warn(`Warning: BASE_URI port (${baseUrl.port}) doesn't match PORT (${PORT})`); -} - -/** - * Redis connection URL - */ -export const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379'; - diff --git a/embedded-oauth/src/context.ts b/embedded-oauth/src/context.ts deleted file mode 100644 index 4785ab2..0000000 --- a/embedded-oauth/src/context.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { AsyncLocalStorage } from "async_hooks"; -import { MockUpstreamInstallation } from "./types.js"; - -interface RequestContext { - mcpAccessToken: string; - mockUpstreamInstallation: MockUpstreamInstallation; -} - -export const asyncLocalStorage = new AsyncLocalStorage(); - -export function getMockUpstreamInstallation(): MockUpstreamInstallation { - const context = asyncLocalStorage.getStore(); - if (!context) { - throw new Error( - "No request context found - are you calling this from within a request handler?", - ); - } - return context.mockUpstreamInstallation; -} - -export function getMcpAccessToken(): string { - const context = asyncLocalStorage.getStore(); - if (!context) { - throw new Error( - "No request context found - are you calling this from within a request handler?", - ); - } - return context.mcpAccessToken; -} - -export function withContext( - context: RequestContext, - fn: () => T | Promise, -): T | Promise { - return asyncLocalStorage.run(context, fn); -} diff --git a/embedded-oauth/src/handlers/common.ts b/embedded-oauth/src/handlers/common.ts deleted file mode 100644 index 48c5be1..0000000 --- a/embedded-oauth/src/handlers/common.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { NextFunction, Request, Response } from "express"; -import { withContext } from "../context.js"; -import { readMcpInstallation } from "../services/auth.js"; -import { logger } from "../utils/logger.js"; - -import { JSONRPCError, JSONRPCNotification, JSONRPCRequest, JSONRPCResponse } from "@modelcontextprotocol/sdk/types.js"; - -export function logMcpMessage( - message: JSONRPCError | JSONRPCNotification | JSONRPCRequest | JSONRPCResponse, - sessionId: string, -) { - // check if message has a method field - if ("method" in message) { - if (message.method === "tools/call") { - logger.info('Processing MCP method', { - sessionId, - method: message.method, - toolName: message.params?.name - }); - } else { - logger.info('Processing MCP method', { - sessionId, - method: message.method - }); - } - } else if ("error" in message) { - logger.warning('Received error message', { - sessionId, - errorMessage: message.error.message, - errorCode: message.error.code - }); - } -} - - -export async function authContext( - req: Request, - res: Response, - next: NextFunction, -) { - const authInfo = req.auth - - if (!authInfo) { - res.set("WWW-Authenticate", 'Bearer error="invalid_token"'); - res.status(401).json({ error: "Invalid access token" }); - return; - } - - const token = authInfo.token; - - // Load UpstreamInstallation based on the access token - const mcpInstallation = await readMcpInstallation(token); - if (!mcpInstallation) { - res.set("WWW-Authenticate", 'Bearer error="invalid_token"'); - res.status(401).json({ error: "Invalid access token" }); - return; - } - - // Wrap the rest of the request handling in the context - withContext({ mcpAccessToken: token, mockUpstreamInstallation: mcpInstallation.mockUpstreamInstallation }, () => - next(), - ); -} diff --git a/embedded-oauth/src/handlers/mock-upstream-idp.ts b/embedded-oauth/src/handlers/mock-upstream-idp.ts deleted file mode 100644 index 2eb4d8f..0000000 --- a/embedded-oauth/src/handlers/mock-upstream-idp.ts +++ /dev/null @@ -1,353 +0,0 @@ -import { Request, Response } from "express"; -import { generateMcpTokens, readPendingAuthorization, saveMcpInstallation, saveRefreshToken, saveTokenExchange } from "../services/auth.js"; -import { McpInstallation } from "../types.js"; -import { logger } from "../utils/logger.js"; - -// Mock upstream identity provider - simulates Google OAuth, corporate SAML, or other external identity providers -// that OAuth servers can delegate user authentication to (configurable in Auth0/Okta). -// In production, the OAuth server would redirect to actual external IDPs. - -export async function handleMockUpstreamAuthorize(req: Request, res: Response) { - // get the redirect_uri and state from the query params - const { redirect_uri, state } = req.query; - - // Set a more permissive CSP for auth pages to allow inline styles and scripts - res.setHeader('Content-Security-Policy', [ - "default-src 'self'", - "style-src 'self' 'unsafe-inline'", // Allow inline styles for auth page styling - "script-src 'self' 'unsafe-inline'", // Allow inline scripts for auth page functionality - "object-src 'none'", - "frame-ancestors 'none'", - "form-action 'self'", - "base-uri 'self'" - ].join('; ')); - - res.send(` - - - - - - Upstream Provider Authentication - - - -
- -

Upstream Authentication

-

Please verify your identity with the upstream provider

- -
-

Your User Identity

-
Loading...
- -
- - - -
- Testing Multiple Users: Open this page in different browser windows or incognito tabs to simulate different users. Each will have their own unique User ID and separate MCP sessions. -
-
- - - - - `); -} - - -// Callback from the mock upstream identity provider after user authentication -export async function handleMockUpstreamCallback(req: Request, res: Response) { - const { - // The state returned from the upstream auth server is actually the authorization code - state: mcpAuthorizationCode, - code: upstreamAuthorizationCode, - userId, // User ID from the authorization flow - } = req.query; - - logger.debug('Mock upstream IDP callback received', { - mcpAuthorizationCode: typeof mcpAuthorizationCode === 'string' ? mcpAuthorizationCode.substring(0, 8) + '...' : mcpAuthorizationCode, - upstreamAuthorizationCode: typeof upstreamAuthorizationCode === 'string' ? upstreamAuthorizationCode.substring(0, 8) + '...' : upstreamAuthorizationCode, - userId - }); - - // Exchange the upstream authorization code for tokens - const upstreamTokens = await mockUpstreamTokenExchange(upstreamAuthorizationCode as string); - - // Validate that it's a string - if (typeof mcpAuthorizationCode !== "string") { - throw new Error("Invalid authorization code"); - } - - const pendingAuth = await readPendingAuthorization(mcpAuthorizationCode); - logger.debug('Reading pending authorization', { - mcpAuthorizationCode: mcpAuthorizationCode.substring(0, 8) + '...', - found: !!pendingAuth - }); - - if (!pendingAuth) { - throw new Error("No matching authorization found"); - } - - logger.debug('Generating MCP tokens'); - const mcpTokens = generateMcpTokens(); - logger.debug('MCP tokens generated', { - hasAccessToken: !!mcpTokens.access_token, - hasRefreshToken: !!mcpTokens.refresh_token - }); - - const mcpInstallation: McpInstallation = { - mockUpstreamInstallation: { - mockUpstreamAccessToken: upstreamTokens.access_token, - mockUpstreamRefreshToken: upstreamTokens.refresh_token, - }, - mcpTokens, - clientId: pendingAuth.clientId, - issuedAt: Date.now() / 1000, - userId: (userId as string) || 'anonymous-user', // Include user ID from auth flow - } - - logger.debug('Saving MCP installation'); - // Store the upstream authorization data - await saveMcpInstallation(mcpTokens.access_token, mcpInstallation); - logger.debug('MCP installation saved'); - - // Store the refresh token -> access token mapping - if (mcpTokens.refresh_token) { - logger.debug('Saving refresh token mapping'); - await saveRefreshToken(mcpTokens.refresh_token, mcpTokens.access_token); - logger.debug('Refresh token mapping saved'); - } - - logger.debug('Saving token exchange data'); - // Store the token exchange data - await saveTokenExchange(mcpAuthorizationCode, { - mcpAccessToken: mcpTokens.access_token, - alreadyUsed: false, - }); - logger.debug('Token exchange data saved'); - - // Redirect back to the original application with the authorization code and state - const redirectUrl = pendingAuth.state ? - `${pendingAuth.redirectUri}?code=${mcpAuthorizationCode}&state=${pendingAuth.state}` : - `${pendingAuth.redirectUri}?code=${mcpAuthorizationCode}`; - - logger.debug('Redirecting to callback', { - redirectUrl, - hasState: !!pendingAuth.state - }); - res.redirect(redirectUrl); - logger.debug('Redirect completed'); -}; - -function mockUpstreamTokenExchange( - authorizationCode: string, -): Promise<{ access_token: string; refresh_token: string }> { - // just return the authorization code with a suffix - return new Promise((resolve) => { - resolve({ - access_token: `${authorizationCode}-exchanged-for-access-token`, - refresh_token: `${authorizationCode}-exchanged-for-refresh-token`, - }); - }); -} \ No newline at end of file diff --git a/embedded-oauth/src/handlers/sse.ts b/embedded-oauth/src/handlers/sse.ts deleted file mode 100644 index 9788d0b..0000000 --- a/embedded-oauth/src/handlers/sse.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import contentType from "content-type"; -import { Request, Response } from "express"; -import { redisClient } from "../redis.js"; -import { createMcpServer } from "../services/mcp.js"; -import { logMcpMessage } from "./common.js"; -import { logger } from "../utils/logger.js"; - -declare module "express-serve-static-core" { - interface Request { - /** - * Information about the validated access token, if the `requireBearerAuth` middleware was used. - */ - auth?: AuthInfo; - } -} - -function redisChannelForSession(sessionId: string): string { - return `mcp:${sessionId}`; -} - -export async function handleSSEConnection(req: Request, res: Response) { - const { server: mcpServer, cleanup: mcpCleanup } = createMcpServer(); - const transport = new SSEServerTransport("/message", res); - logger.info('Received MCP SSE connection', { - sessionId: transport.sessionId - }); - - const redisCleanup = await redisClient.createSubscription( - redisChannelForSession(transport.sessionId), - (json) => { - // TODO handle DELETE messages - // TODO set timeout to kill the session - - const message = JSON.parse(json); - logMcpMessage(message, transport.sessionId); - transport.handleMessage(message).catch((error) => { - logger.error('Error handling message', error as Error, { - sessionId: transport.sessionId - }); - }); - }, - (error) => { - logger.error('Disconnecting due to error in Redis subscriber', error as Error, { - sessionId: transport.sessionId - }); - transport - .close() - .catch((error) => - logger.error('Error closing transport', error as Error, { - sessionId: transport.sessionId - }), - ); - }, - ); - - const cleanup = () => { - void mcpCleanup(); - redisCleanup().catch((error) => - logger.error('Error disconnecting Redis subscriber', error as Error, { - sessionId: transport.sessionId - }), - ); - } - - // Clean up Redis subscription when the connection closes - mcpServer.onclose = cleanup - - logger.info('Listening on Redis channel', { - sessionId: transport.sessionId, - channel: redisChannelForSession(transport.sessionId) - }); - await mcpServer.connect(transport); -} - -export async function handleMessage(req: Request, res: Response) { - const sessionId = req.query.sessionId; - let body: string; - try { - if (typeof sessionId !== "string") { - throw new Error("Only one sessionId allowed"); - } - - const ct = contentType.parse(req.headers["content-type"] ?? ""); - if (ct.type !== "application/json") { - throw new Error(`Unsupported content-type: ${ct}`); - } - - body = JSON.stringify(req.body); - } catch (error) { - res.status(400).json(error); - logger.error('Bad POST request', error as Error, { - sessionId, - contentType: req.headers['content-type'] - }); - return; - } - await redisClient.publish(redisChannelForSession(sessionId), body); - res.status(202).end(); -} diff --git a/embedded-oauth/src/index.ts b/embedded-oauth/src/index.ts deleted file mode 100644 index 5125d43..0000000 --- a/embedded-oauth/src/index.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { BearerAuthMiddlewareOptions, requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"; -import { AuthRouterOptions, getOAuthProtectedResourceMetadataUrl, mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js"; -import cors from "cors"; -import rateLimit from "express-rate-limit"; -import express from "express"; -import path from "path"; -import { fileURLToPath } from "url"; -import { FeatureReferenceAuthProvider } from "./auth/provider.js"; -import { handleMockUpstreamAuthorize, handleMockUpstreamCallback } from "./handlers/mock-upstream-idp.js"; -import { BASE_URI, PORT } from "./config.js"; -import { authContext } from "./handlers/common.js"; -import { handleStreamableHTTP } from "./handlers/shttp.js"; -import { handleMessage, handleSSEConnection } from "./handlers/sse.js"; -import { redisClient } from "./redis.js"; -import { logger } from "./utils/logger.js"; - -const app = express(); - -// Get the directory of the current module -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -// Base security middleware - applied to all routes -const baseSecurityHeaders = (req: express.Request, res: express.Response, next: express.NextFunction) => { - // Basic security headers - res.setHeader('X-Content-Type-Options', 'nosniff'); - res.setHeader('X-Frame-Options', 'SAMEORIGIN'); - res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); - - // Content Security Policy - const csp = [ - "default-src 'self'", - "object-src 'none'", // Disable plugins - "frame-ancestors 'none'", // No embedding - "form-action 'self'", // Only allow forms to submit to our domain - "base-uri 'self'", // Restrict base tag - "upgrade-insecure-requests", - "block-all-mixed-content" - ].join('; '); - - res.setHeader('Content-Security-Policy', csp); - next(); -}; - -// Structured logging middleware -const loggingMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => { - const startTime = Date.now(); - - // Sanitize headers to remove sensitive information - const sanitizedHeaders = { ...req.headers }; - delete sanitizedHeaders.authorization; - delete sanitizedHeaders.cookie; - delete sanitizedHeaders['x-api-key']; - - // Log request (without sensitive data) - logger.info('Request received', { - method: req.method, - url: req.url, - // Only log specific safe headers - headers: { - 'content-type': sanitizedHeaders['content-type'], - 'user-agent': sanitizedHeaders['user-agent'], - 'mcp-protocol-version': sanitizedHeaders['mcp-protocol-version'], - 'mcp-session-id': sanitizedHeaders['mcp-session-id'], - 'accept': sanitizedHeaders['accept'], - 'x-cloud-trace-context': sanitizedHeaders['x-cloud-trace-context'] - }, - // Don't log request body as it may contain sensitive data - bodySize: req.headers['content-length'] - }); - - // Log response when finished - res.on('finish', () => { - const duration = Date.now() - startTime; - logger.info('Request completed', { - method: req.method, - url: req.url, - statusCode: res.statusCode, - duration: `${duration}ms` - }); - }); - - next(); -}; - - -// Sensitive data middleware - for routes with sensitive data -const sensitiveDataHeaders = (req: express.Request, res: express.Response, next: express.NextFunction) => { - res.setHeader('Cache-Control', 'no-store, max-age=0'); - next(); -}; - -// SSE middleware - specific for SSE endpoint -const sseHeaders = (req: express.Request, res: express.Response, next: express.NextFunction) => { - res.setHeader('Content-Type', 'text/event-stream'); - res.setHeader('Cache-Control', 'no-store, max-age=0'); - res.setHeader('Connection', 'keep-alive'); - next(); -}; - -// Configure CORS to allow any origin since this is a public API service -const corsOptions = { - origin: true, // Allow any origin - methods: ['GET', 'POST'], - allowedHeaders: ['Content-Type', 'Authorization', "Mcp-Protocol-Version", "Mcp-Protocol-Id"], - exposedHeaders: ["Mcp-Protocol-Version", "Mcp-Protocol-Id"], - credentials: true -}; - - -app.use(express.json()); - -// Add structured logging context middleware first -app.use(logger.middleware()); - -// Then add the logging middleware -app.use(loggingMiddleware); - -// Apply base security headers to all routes -app.use(baseSecurityHeaders); - -// Enable CORS pre-flight requests -app.options('*', cors(corsOptions)); - -// Rate limiting for custom endpoints -const mockUpstreamIdpRateLimit = rateLimit({ - windowMs: 60 * 1000, // 1 minute - limit: 20, // 20 auth attempts per minute - message: { error: 'too_many_requests', error_description: 'Authentication rate limit exceeded' } -}); - -const staticFileRateLimit = rateLimit({ - windowMs: 10 * 60 * 1000, // 10 minutes - limit: 25, // 25 requests per 10 minutes for static files - message: { error: 'too_many_requests', error_description: 'Static file rate limit exceeded' } -}); - -// Embedded OAuth: MCP server acts as its own OAuth authorization server -logger.info('Starting MCP server with embedded OAuth', { - baseUri: BASE_URI, - port: PORT -}); - -const authProvider = new FeatureReferenceAuthProvider(); - - const authRouterOptions: AuthRouterOptions = { - provider: authProvider, - issuerUrl: new URL(BASE_URI), - tokenOptions: { - rateLimit: { - windowMs: 5 * 1000, - limit: 100, - } - }, - clientRegistrationOptions: { - rateLimit: { - windowMs: 60 * 1000, // 1 minute - limit: 10, // Limit to 10 registrations per minute - }, - }, - }; - - // Serve OAuth endpoints - app.use(mcpAuthRouter(authRouterOptions)); - - // Configure bearer auth middleware - const bearerAuthOptions: BearerAuthMiddlewareOptions = { - verifier: { - verifyAccessToken: authProvider.verifyAccessToken.bind(authProvider), - }, - resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(new URL(BASE_URI)), - }; - -const bearerAuth = requireBearerAuth(bearerAuthOptions); - -// MCP routes (legacy SSE transport) -app.get("/sse", cors(corsOptions), bearerAuth, authContext, sseHeaders, handleSSEConnection); -app.post("/message", cors(corsOptions), bearerAuth, authContext, sensitiveDataHeaders, handleMessage); - -// MCP routes (new streamable HTTP transport) -app.get("/mcp", cors(corsOptions), bearerAuth, authContext, handleStreamableHTTP); -app.post("/mcp", cors(corsOptions), bearerAuth, authContext, handleStreamableHTTP); -app.delete("/mcp", cors(corsOptions), bearerAuth, authContext, handleStreamableHTTP); - -// Static assets -app.get("/mcp-logo.png", staticFileRateLimit, (req, res) => { - const logoPath = path.join(__dirname, "static", "mcp.png"); - res.sendFile(logoPath); -}); - -app.get("/styles.css", staticFileRateLimit, (req, res) => { - const cssPath = path.join(__dirname, "static", "styles.css"); - res.setHeader('Content-Type', 'text/css'); - res.sendFile(cssPath); -}); - -// Splash page -app.get("/", (req, res) => { - const splashPath = path.join(__dirname, "static", "index.html"); - res.sendFile(splashPath); -}); - -// Mock upstream identity provider routes -app.get("/mock-upstream-idp/authorize", mockUpstreamIdpRateLimit, cors(corsOptions), handleMockUpstreamAuthorize); -app.get("/mock-upstream-idp/callback", mockUpstreamIdpRateLimit, cors(corsOptions), handleMockUpstreamCallback); - -try { - await redisClient.connect(); -} catch (error) { - logger.error("Could not connect to Redis", error as Error); - process.exit(1); -} - -app.listen(PORT, () => { - logger.info('Server started', { - port: PORT, - url: `http://localhost:${PORT}`, - environment: process.env.NODE_ENV || 'development' - }); -}); diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..dfd72c9 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,133 @@ +# MCP Server Examples + +This directory contains example code demonstrating how to interact with the MCP server. + +## Prerequisites + +Before running any examples, ensure: +1. Redis is running: `docker compose up -d` +2. Both servers are running: `npm run dev` +3. Servers are accessible at: + - Auth Server: http://localhost:3001 + - MCP Server: http://localhost:3232 + +## Available Examples + +### curl-examples.sh + +Shell script demonstrating API interactions using curl. + +**Features:** +- OAuth client registration +- MCP session initialization +- Tool calls (echo, add) +- Resource listing and reading +- Prompt operations + +**Usage:** +```bash +# Make executable +chmod +x curl-examples.sh + +# Register client (step 1) +./curl-examples.sh + +# After getting token (via MCP Inspector) +./curl-examples.sh YOUR_ACCESS_TOKEN + +# With session ID +./curl-examples.sh YOUR_ACCESS_TOKEN YOUR_SESSION_ID +``` + +### client.js + +Node.js client showing programmatic interaction with the MCP server. + +**Features:** +- Complete OAuth flow demonstration +- Automatic client registration +- MCP session management +- Tool and resource operations + +**Usage:** +```bash +# Run the example +node client.js + +# Or make it executable +chmod +x client.js +./client.js +``` + +## Getting an Access Token + +### Option 1: MCP Inspector (Easiest) + +```bash +# Launch inspector +npx -y @modelcontextprotocol/inspector + +# Connect to http://localhost:3232/mcp +# Complete OAuth flow in the Auth tab +# Copy the access token from the debug console +``` + +### Option 2: Manual OAuth Flow + +1. Register a client (see examples) +2. Navigate to authorization URL +3. Complete authentication +4. Exchange authorization code for token +5. Use token in API calls + +## Common Patterns + +### Making MCP Requests + +All MCP requests follow this pattern: + +```javascript +{ + "jsonrpc": "2.0", + "id": "unique-id", + "method": "category/action", + "params": { /* method-specific parameters */ } +} +``` + +### Required Headers + +``` +Authorization: Bearer YOUR_ACCESS_TOKEN +Content-Type: application/json +Mcp-Session-Id: YOUR_SESSION_ID // After initialization +``` + +### Session Lifecycle + +1. **Initialize**: Create a new session +2. **Use**: Make requests with session ID +3. **Terminate**: Optional cleanup (auto-expires after 5 min) + +## Troubleshooting + +### "401 Unauthorized" +- Token may be expired (7-day TTL) +- Token may be invalid +- Get a new token via OAuth flow + +### "Session not found" +- Session expired (5-minute TTL) +- Re-initialize with the same token + +### "Cannot connect" +- Ensure servers are running: `npm run dev` +- Check Redis is running: `docker compose ps` +- Verify URLs are correct + +## Additional Resources + +- [MCP Protocol Documentation](https://modelcontextprotocol.io) +- [OAuth Flow Documentation](../docs/oauth-flow.md) +- [API Endpoints Reference](../docs/endpoints.md) +- [Main README](../README.md) \ No newline at end of file diff --git a/examples/client.js b/examples/client.js new file mode 100755 index 0000000..6ddc9a4 --- /dev/null +++ b/examples/client.js @@ -0,0 +1,345 @@ +#!/usr/bin/env node + +/** + * Simple Node.js MCP Client Example + * + * This demonstrates how to interact with the MCP server programmatically. + * It handles the OAuth flow and makes MCP requests. + * + * Prerequisites: + * - Both servers running: npm run dev + * - Node.js installed + * + * Usage: + * node client.js + */ + +const http = require('http'); +const https = require('https'); +const { URL } = require('url'); +const readline = require('readline'); + +// Configuration +const AUTH_SERVER = 'http://localhost:3001'; +const MCP_SERVER = 'http://localhost:3232'; +const REDIRECT_URI = 'http://localhost:8080/callback'; + +// Colors for console output +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m' +}; + +// Utility functions +const log = { + info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`), + success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`), + error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`), + section: (msg) => console.log(`\n${colors.bright}${colors.blue}=== ${msg} ===${colors.reset}`) +}; + +/** + * Simple HTTP request helper + */ +function makeRequest(url, options = {}) { + return new Promise((resolve, reject) => { + const parsedUrl = new URL(url); + const client = parsedUrl.protocol === 'https:' ? https : http; + + const reqOptions = { + hostname: parsedUrl.hostname, + port: parsedUrl.port, + path: parsedUrl.pathname + parsedUrl.search, + method: options.method || 'GET', + headers: options.headers || {} + }; + + const req = client.request(reqOptions, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + try { + const json = JSON.parse(data); + resolve({ status: res.statusCode, headers: res.headers, data: json }); + } catch { + resolve({ status: res.statusCode, headers: res.headers, data }); + } + }); + }); + + req.on('error', reject); + + if (options.body) { + req.write(options.body); + } + + req.end(); + }); +} + +/** + * Step 1: Register OAuth client + */ +async function registerClient() { + log.section('Registering OAuth Client'); + + const response = await makeRequest(`${AUTH_SERVER}/register`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + client_name: 'node-example-client', + redirect_uris: [REDIRECT_URI] + }) + }); + + if (response.status === 200 || response.status === 201) { + log.success('Client registered successfully'); + return { + clientId: response.data.client_id, + clientSecret: response.data.client_secret + }; + } else { + throw new Error(`Registration failed: ${JSON.stringify(response.data)}`); + } +} + +/** + * Step 2: Simple OAuth flow (manual for demonstration) + * In production, use a proper OAuth library + */ +async function performOAuthFlow(clientId, clientSecret) { + log.section('OAuth Authorization Flow'); + + log.info('For this demo, we need you to manually complete the OAuth flow.'); + log.info('In a production app, this would be automated.\n'); + + // Generate PKCE challenge (simplified for demo) + const codeVerifier = Buffer.from(Math.random().toString()).toString('base64url'); + const codeChallenge = Buffer.from(codeVerifier).toString('base64url'); + + const authUrl = `${AUTH_SERVER}/authorize?` + + `client_id=${clientId}&` + + `redirect_uri=${encodeURIComponent(REDIRECT_URI)}&` + + `response_type=code&` + + `code_challenge=${codeChallenge}&` + + `code_challenge_method=plain&` + + `state=demo-state`; + + console.log('1. Open this URL in your browser:'); + console.log(` ${colors.cyan}${authUrl}${colors.reset}\n`); + + console.log('2. Complete the authentication flow'); + console.log('3. You\'ll be redirected to a URL like:'); + console.log(` ${REDIRECT_URI}?code=AUTHORIZATION_CODE&state=demo-state\n`); + + // Get authorization code from user + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + return new Promise((resolve) => { + rl.question('4. Paste the AUTHORIZATION_CODE here: ', async (code) => { + rl.close(); + + // Exchange code for token + log.info('Exchanging authorization code for access token...'); + + const tokenResponse = await makeRequest(`${AUTH_SERVER}/token`, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `grant_type=authorization_code&` + + `code=${code}&` + + `redirect_uri=${encodeURIComponent(REDIRECT_URI)}&` + + `client_id=${clientId}&` + + `client_secret=${clientSecret}&` + + `code_verifier=${codeVerifier}` + }); + + if (tokenResponse.data.access_token) { + log.success('Access token obtained!'); + resolve(tokenResponse.data.access_token); + } else { + throw new Error(`Token exchange failed: ${JSON.stringify(tokenResponse.data)}`); + } + }); + }); +} + +/** + * Step 3: Initialize MCP session + */ +async function initializeMCPSession(accessToken) { + log.section('Initializing MCP Session'); + + const response = await makeRequest(`${MCP_SERVER}/mcp`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'init', + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { + name: 'node-example', + version: '1.0' + } + } + }) + }); + + if (response.data.result) { + log.success('MCP session initialized'); + // In a real implementation, extract session ID from headers + return response.headers['mcp-session-id'] || 'demo-session-id'; + } else { + throw new Error(`Initialization failed: ${JSON.stringify(response.data)}`); + } +} + +/** + * Step 4: Demonstrate MCP features + */ +async function demonstrateMCPFeatures(accessToken, sessionId) { + log.section('Demonstrating MCP Features'); + + // List available tools + log.info('Fetching available tools...'); + const toolsResponse = await makeRequest(`${MCP_SERVER}/mcp`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Mcp-Session-Id': sessionId, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'list-tools', + method: 'tools/list' + }) + }); + + if (toolsResponse.data.result && toolsResponse.data.result.tools) { + console.log(`Found ${toolsResponse.data.result.tools.length} tools:`); + toolsResponse.data.result.tools.forEach(tool => { + console.log(` - ${colors.cyan}${tool.name}${colors.reset}: ${tool.description}`); + }); + } + + // Call the echo tool + log.info('\nCalling echo tool...'); + const echoResponse = await makeRequest(`${MCP_SERVER}/mcp`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Mcp-Session-Id': sessionId, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'echo-1', + method: 'tools/call', + params: { + name: 'echo', + arguments: { + message: 'Hello from Node.js client!' + } + } + }) + }); + + if (echoResponse.data.result) { + console.log('Echo response:', echoResponse.data.result.content); + } + + // Call the add tool + log.info('\nCalling add tool (5 + 3)...'); + const addResponse = await makeRequest(`${MCP_SERVER}/mcp`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Mcp-Session-Id': sessionId, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'add-1', + method: 'tools/call', + params: { + name: 'add', + arguments: { a: 5, b: 3 } + } + }) + }); + + if (addResponse.data.result) { + console.log('Add result:', addResponse.data.result.content); + } + + // List resources + log.info('\nFetching available resources...'); + const resourcesResponse = await makeRequest(`${MCP_SERVER}/mcp`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Mcp-Session-Id': sessionId, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 'list-resources', + method: 'resources/list' + }) + }); + + if (resourcesResponse.data.result && resourcesResponse.data.result.resources) { + console.log(`Found ${resourcesResponse.data.result.resources.length} resources (showing first 5):`); + resourcesResponse.data.result.resources.slice(0, 5).forEach(resource => { + console.log(` - ${colors.cyan}${resource.uri}${colors.reset}: ${resource.name}`); + }); + } +} + +/** + * Main function + */ +async function main() { + console.log(`${colors.bright}${colors.blue}MCP Node.js Client Example${colors.reset}`); + console.log('==========================\n'); + + try { + // Step 1: Register client + const { clientId, clientSecret } = await registerClient(); + console.log(`Client ID: ${clientId}`); + console.log(`Client Secret: ${clientSecret}\n`); + + // Step 2: OAuth flow + const accessToken = await performOAuthFlow(clientId, clientSecret); + + // Step 3: Initialize MCP session + const sessionId = await initializeMCPSession(accessToken); + + // Step 4: Demonstrate features + await demonstrateMCPFeatures(accessToken, sessionId); + + log.success('\nExample completed successfully!'); + + } catch (error) { + log.error(`Error: ${error.message}`); + process.exit(1); + } +} + +// Run the example +main(); \ No newline at end of file diff --git a/examples/curl-examples.sh b/examples/curl-examples.sh new file mode 100755 index 0000000..f10eaa6 --- /dev/null +++ b/examples/curl-examples.sh @@ -0,0 +1,339 @@ +#!/bin/bash + +# ============================================================================= +# MCP Server API Examples using curl +# ============================================================================= +# +# This script demonstrates how to interact with the MCP server using curl. +# Before running, ensure both servers are running: npm run dev +# +# Usage: ./curl-examples.sh [access_token] +# ============================================================================= + +# Configuration +AUTH_SERVER="http://localhost:3001" +MCP_SERVER="http://localhost:3232" +CLIENT_NAME="curl-example-client" +REDIRECT_URI="http://localhost:3000/callback" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# ============================================================================= +# Helper Functions +# ============================================================================= + +print_section() { + echo -e "\n${BLUE}=== $1 ===${NC}" +} + +print_success() { + echo -e "${GREEN}✓ $1${NC}" +} + +print_error() { + echo -e "${RED}✗ $1${NC}" +} + +print_info() { + echo -e "${YELLOW}ℹ $1${NC}" +} + +# ============================================================================= +# OAuth Client Registration +# ============================================================================= + +register_client() { + print_section "Registering OAuth Client" + + RESPONSE=$(curl -s -X POST "$AUTH_SERVER/register" \ + -H "Content-Type: application/json" \ + -d "{ + \"client_name\": \"$CLIENT_NAME\", + \"redirect_uris\": [\"$REDIRECT_URI\"] + }") + + CLIENT_ID=$(echo "$RESPONSE" | grep -o '"client_id":"[^"]*' | cut -d'"' -f4) + CLIENT_SECRET=$(echo "$RESPONSE" | grep -o '"client_secret":"[^"]*' | cut -d'"' -f4) + + if [ -n "$CLIENT_ID" ]; then + print_success "Client registered successfully" + echo "Client ID: $CLIENT_ID" + echo "Client Secret: $CLIENT_SECRET" + echo + echo "Save these credentials - you'll need them for the OAuth flow" + else + print_error "Failed to register client" + echo "Response: $RESPONSE" + exit 1 + fi +} + +# ============================================================================= +# MCP Session Initialization +# ============================================================================= + +initialize_session() { + local ACCESS_TOKEN=$1 + + print_section "Initializing MCP Session" + + if [ -z "$ACCESS_TOKEN" ]; then + print_error "Access token required" + print_info "Get a token using MCP Inspector or implement OAuth flow" + return 1 + fi + + RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "init", + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { + "name": "curl-example", + "version": "1.0" + } + } + }') + + # Extract session ID from response headers (would need -i flag) + # For demonstration, we'll parse from response + echo "Response: $RESPONSE" + + # Check for error + if echo "$RESPONSE" | grep -q "error"; then + print_error "Failed to initialize session" + return 1 + else + print_success "Session initialized" + print_info "Save the Mcp-Session-Id header for subsequent requests" + return 0 + fi +} + +# ============================================================================= +# MCP Tools Examples +# ============================================================================= + +list_tools() { + local ACCESS_TOKEN=$1 + local SESSION_ID=$2 + + print_section "Listing Available Tools" + + curl -s -X POST "$MCP_SERVER/mcp" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Mcp-Session-Id: $SESSION_ID" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "list-tools", + "method": "tools/list" + }' | python3 -m json.tool +} + +call_echo_tool() { + local ACCESS_TOKEN=$1 + local SESSION_ID=$2 + local MESSAGE=${3:-"Hello from curl!"} + + print_section "Calling Echo Tool" + + curl -s -X POST "$MCP_SERVER/mcp" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Mcp-Session-Id: $SESSION_ID" \ + -H "Content-Type: application/json" \ + -d "{ + \"jsonrpc\": \"2.0\", + \"id\": \"echo-1\", + \"method\": \"tools/call\", + \"params\": { + \"name\": \"echo\", + \"arguments\": { + \"message\": \"$MESSAGE\" + } + } + }" | python3 -m json.tool +} + +call_add_tool() { + local ACCESS_TOKEN=$1 + local SESSION_ID=$2 + local A=${3:-5} + local B=${4:-3} + + print_section "Calling Add Tool" + + curl -s -X POST "$MCP_SERVER/mcp" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Mcp-Session-Id: $SESSION_ID" \ + -H "Content-Type: application/json" \ + -d "{ + \"jsonrpc\": \"2.0\", + \"id\": \"add-1\", + \"method\": \"tools/call\", + \"params\": { + \"name\": \"add\", + \"arguments\": { + \"a\": $A, + \"b\": $B + } + } + }" | python3 -m json.tool +} + +# ============================================================================= +# MCP Resources Examples +# ============================================================================= + +list_resources() { + local ACCESS_TOKEN=$1 + local SESSION_ID=$2 + + print_section "Listing Resources (First Page)" + + curl -s -X POST "$MCP_SERVER/mcp" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Mcp-Session-Id: $SESSION_ID" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "list-resources", + "method": "resources/list" + }' | python3 -m json.tool +} + +read_resource() { + local ACCESS_TOKEN=$1 + local SESSION_ID=$2 + local RESOURCE_URI=${3:-"example://resource/1"} + + print_section "Reading Resource: $RESOURCE_URI" + + curl -s -X POST "$MCP_SERVER/mcp" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Mcp-Session-Id: $SESSION_ID" \ + -H "Content-Type: application/json" \ + -d "{ + \"jsonrpc\": \"2.0\", + \"id\": \"read-resource\", + \"method\": \"resources/read\", + \"params\": { + \"uri\": \"$RESOURCE_URI\" + } + }" | python3 -m json.tool +} + +# ============================================================================= +# MCP Prompts Examples +# ============================================================================= + +list_prompts() { + local ACCESS_TOKEN=$1 + local SESSION_ID=$2 + + print_section "Listing Available Prompts" + + curl -s -X POST "$MCP_SERVER/mcp" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Mcp-Session-Id: $SESSION_ID" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "list-prompts", + "method": "prompts/list" + }' | python3 -m json.tool +} + +get_prompt() { + local ACCESS_TOKEN=$1 + local SESSION_ID=$2 + local PROMPT_NAME=${3:-"simple_prompt"} + + print_section "Getting Prompt: $PROMPT_NAME" + + curl -s -X POST "$MCP_SERVER/mcp" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Mcp-Session-Id: $SESSION_ID" \ + -H "Content-Type: application/json" \ + -d "{ + \"jsonrpc\": \"2.0\", + \"id\": \"get-prompt\", + \"method\": \"prompts/get\", + \"params\": { + \"name\": \"$PROMPT_NAME\" + } + }" | python3 -m json.tool +} + +# ============================================================================= +# Main Script +# ============================================================================= + +main() { + echo -e "${BLUE}MCP Server API Examples${NC}" + echo "=================================" + + # Check if access token is provided + ACCESS_TOKEN=$1 + SESSION_ID=$2 + + if [ -z "$ACCESS_TOKEN" ]; then + print_info "No access token provided" + print_info "Usage: $0 [session_id]" + echo + print_section "Step 1: Register OAuth Client" + register_client + echo + print_info "To continue, you need to:" + print_info "1. Complete OAuth flow to get an access token" + print_info "2. Run: $0 " + print_info "" + print_info "Use MCP Inspector for easy OAuth flow:" + print_info "npx -y @modelcontextprotocol/inspector" + print_info "Connect to: http://localhost:3232/mcp" + exit 0 + fi + + # If we have a token, demonstrate API calls + if [ -z "$SESSION_ID" ]; then + initialize_session "$ACCESS_TOKEN" + print_info "Rerun with: $0 $ACCESS_TOKEN " + exit 0 + fi + + # Demonstrate various MCP features + list_tools "$ACCESS_TOKEN" "$SESSION_ID" + echo + + call_echo_tool "$ACCESS_TOKEN" "$SESSION_ID" "Hello, MCP!" + echo + + call_add_tool "$ACCESS_TOKEN" "$SESSION_ID" 10 25 + echo + + list_resources "$ACCESS_TOKEN" "$SESSION_ID" + echo + + read_resource "$ACCESS_TOKEN" "$SESSION_ID" "example://resource/1" + echo + + list_prompts "$ACCESS_TOKEN" "$SESSION_ID" + echo + + get_prompt "$ACCESS_TOKEN" "$SESSION_ID" "simple_prompt" + + print_success "Examples completed!" +} + +# Run main function +main "$@" \ No newline at end of file diff --git a/external-oauth/README.md b/external-oauth/README.md deleted file mode 100644 index b01a2e0..0000000 --- a/external-oauth/README.md +++ /dev/null @@ -1,109 +0,0 @@ -# External OAuth - External OAuth Provider Pattern - -Two standalone servers demonstrating how MCP resource servers integrate with external OAuth providers. - -## Overview - -Per the [MCP specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), "the authorization server may be a separate entity." This mode demonstrates the OAuth-as-a-Service pattern. - -**Pattern**: MCP resource server delegating all OAuth operations to a separate authorization server. - -**Real-world use cases**: Applications using Auth0, Okta, Google OAuth, AWS Cognito, or similar providers for authentication. - -## Architecture - -``` -┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ -│ │ OAuth │ │ Token │ │ -│ MCP Client │────────>│ Auth Server │<────────│ MCP Server │ -│ │ │ (port 3001) │validate │ (port 3232) │ -│ │<────────│ │ │ │ -│ │ token │ Issues tokens │ │ Serves MCP │ -│ │ │ │ │ resources │ -│ │────────────────────────────────────> │ │ -│ │ MCP requests with token │ │ -└─────────────┘ └─────────────────┘ -``` - -**Servers**: -1. **Auth Server** - Demo OAuth provider (represents Auth0/Okta) - [Details](auth-server/README.md) -2. **MCP Server** - Resource server with token validation only - [Details](mcp-server/README.md) - -The MCP server contains no OAuth authorization code - it only validates tokens via introspection. - -## Quick Start - -```bash -# From repo root: - -# 1. Make sure Redis is running -docker compose up -d - -# 2. Install dependencies (if not already done) -npm install - -# 3. Start both servers -npm run dev:separate - -# 4. Test with MCP Inspector -npx -y @modelcontextprotocol/inspector -# Connect to: http://localhost:3232/mcp -# OAuth will redirect to auth server at :3001 -``` - -## Individual Server Commands - -```bash -# From repo root: -npm run dev:auth-server # Start just auth server (:3001) -npm run dev:mcp-server # Start just MCP server (:3232) - -# Or from individual directories: -cd auth-server && npm run dev -cd mcp-server && npm run dev -``` - -## Server Roles - -**Auth Server** (port 3001): Demo OAuth provider implementing client registration, authorization, token issuance, and introspection. In production, replace with Auth0, Okta, or similar. [Implementation details](auth-server/README.md) - -**MCP Server** (port 3232): Resource server that validates tokens via introspection and serves MCP resources. Production-ready - just configure `AUTH_SERVER_URL`. [Implementation details](mcp-server/README.md) - -## Testing - -```bash -# From repo root: -npm run test:separate # Run all tests (104 total) -npm run test:e2e:separate # Full e2e test - -# Individual workspace tests: -npm test --workspace=external-oauth/auth-server # 37 tests -npm test --workspace=external-oauth/mcp-server # 67 tests -``` - -## Key Differences from Embedded OAuth - -| Aspect | Embedded OAuth | External OAuth | -|--------|----------------|---------------| -| Servers | 1 server | 2 servers | -| OAuth endpoints | On MCP server | On auth server | -| Token validation | Direct (in-process) | Remote (introspection API) | -| Deployment | Simpler | Production-like | -| Code sharing | All in one place | Separated by concern | - -## Production Adaptation - -Replace `auth-server/` with a commercial OAuth provider (Auth0, Okta, AWS Cognito, Azure AD). The `mcp-server/` code integrates with any RFC 7662-compliant introspection endpoint. - -## References - -- [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) -- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) -- [OAuth 2.0 Resource Servers](https://www.oauth.com/oauth2-servers/the-resource-server/) -- [docs/oauth-flow.md](../docs/oauth-flow.md) - Detailed flow analysis - -## Next Steps - -- See [auth-server/README.md](auth-server/README.md) for OAuth provider implementation -- See [mcp-server/README.md](mcp-server/README.md) for token validation implementation -- Compare with [embedded-oauth](../embedded-oauth/README.md) for self-hosted OAuth alternative diff --git a/external-oauth/auth-server/src/auth/auth-core.ts b/external-oauth/auth-server/src/auth/auth-core.ts deleted file mode 100644 index 5feb735..0000000 --- a/external-oauth/auth-server/src/auth/auth-core.ts +++ /dev/null @@ -1,87 +0,0 @@ -import crypto from "crypto"; -import { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; - -/** - * Generates a PKCE code challenge from a verifier string. - * Uses S256 method as specified in RFC 7636. - * @param verifier The code verifier string - * @returns Base64url-encoded SHA256 hash of the verifier - */ -export function generatePKCEChallenge(verifier: string): string { - const buffer = Buffer.from(verifier); - const hash = crypto.createHash("sha256").update(buffer); - return hash.digest("base64url"); -} - -/** - * Generates a cryptographically secure random token. - * @returns 64-character hexadecimal string - */ -export function generateToken(): string { - return crypto.randomBytes(32).toString("hex"); -} - -/** - * Computes SHA256 hash of input data. - * @param data The string to hash - * @returns Hexadecimal representation of the hash - */ -export function sha256(data: string): string { - return crypto.createHash("sha256").update(data).digest("hex"); -} - -/** - * Encrypts a string using AES-256-CBC encryption. - * @param text The plaintext to encrypt - * @param key The encryption key (64 hex characters) - * @returns Encrypted string in format "iv:ciphertext" - */ -export function encryptString({ text, key }: { text: string; key: string }): string { - const iv = crypto.randomBytes(16); - const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(key, "hex"), iv); - let encrypted = cipher.update(text, "utf-8", "hex"); - encrypted += cipher.final("hex"); - return `${iv.toString("hex")}:${encrypted}`; -} - -/** - * Decrypts a string encrypted with encryptString. - * @param encryptedText The encrypted string in format "iv:ciphertext" - * @param key The encryption key (64 hex characters) - * @returns Decrypted plaintext - */ -export function decryptString({ - encryptedText, - key, -}: { - encryptedText: string; - key: string; -}): string { - const [ivHex, encrypted] = encryptedText.split(":"); - const iv = Buffer.from(ivHex, "hex"); - const decipher = crypto.createDecipheriv("aes-256-cbc", Buffer.from(key, "hex"), iv); - let decrypted = decipher.update(encrypted, "hex", "utf-8"); - decrypted += decipher.final("utf-8"); - return decrypted; -} - -/** - * Access token expiry time in seconds (1 hour) - */ -export const ACCESS_TOKEN_EXPIRY_SEC = 60 * 60; - -/** - * Generates a complete set of MCP OAuth tokens. - * @returns OAuth tokens with access token, refresh token, and expiry - */ -export function generateMcpTokens(): OAuthTokens { - const mcpAccessToken = generateToken(); - const mcpRefreshToken = generateToken(); - - return { - access_token: mcpAccessToken, - refresh_token: mcpRefreshToken, - expires_in: ACCESS_TOKEN_EXPIRY_SEC, - token_type: "Bearer", - }; -} \ No newline at end of file diff --git a/external-oauth/auth-server/src/auth/provider.ts b/external-oauth/auth-server/src/auth/provider.ts deleted file mode 100644 index 22aed80..0000000 --- a/external-oauth/auth-server/src/auth/provider.ts +++ /dev/null @@ -1,378 +0,0 @@ -import { Response } from 'express'; -import { OAuthServerProvider, AuthorizationParams } from '@modelcontextprotocol/sdk/server/auth/provider.js'; -import { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js'; -import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js'; -import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; -import { - exchangeToken, - generateToken, - getClientRegistration, - readPendingAuthorization, - readMcpInstallation, - revokeMcpInstallation, - saveClientRegistration, - savePendingAuthorization, - readRefreshToken, - generateMcpTokens, - saveMcpInstallation, - saveRefreshToken, -} from '../services/auth.js'; -import { InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js'; -import { logger } from '../utils/logger.js'; - -/** - * Implementation of the OAuthRegisteredClientsStore interface using the existing client registration system - */ -export class FeatureReferenceOAuthClientsStore implements OAuthRegisteredClientsStore { - async getClient(clientId: string): Promise { - const registration = await getClientRegistration(clientId); - if (!registration) { - return undefined; - } - return registration; - } - - async registerClient(client: OAuthClientInformationFull): Promise { - await saveClientRegistration(client.client_id, client); - return client; - } -} - -/** - * Implementation of the OAuthServerProvider interface for upstream authentication - */ -export class FeatureReferenceAuthProvider implements OAuthServerProvider { - private _clientsStore: FeatureReferenceOAuthClientsStore; - - constructor() { - this._clientsStore = new FeatureReferenceOAuthClientsStore(); - } - - get clientsStore(): OAuthRegisteredClientsStore { - return this._clientsStore; - } - - async authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise { - - // Client is validated by the MCP sdk. - - // Generate authorization code - const authorizationCode = generateToken(); - - // Save the pending authorization with code challenge and state - await savePendingAuthorization(authorizationCode, { - redirectUri: params.redirectUri, - codeChallenge: params.codeChallenge, - codeChallengeMethod: 'S256', // Currently only support S256 - clientId: client.client_id, - state: params.state, - }); - - logger.debug('Saved pending authorization', { - authorizationCode: authorizationCode.substring(0, 8) + '...', - clientId: client.client_id, - state: params.state?.substring(0, 8) + '...' - }); - - // TODO: should we use a different key, other than the authorization code, to store the pending authorization? - - // You can redirect to another page, or you can send an html response directly - // res.redirect(new URL(`mock-upstream-idp/authorize?metadata=${authorizationCode}`, BASE_URI).href); - - // Set permissive CSP for styling - res.setHeader('Content-Security-Policy', [ - "default-src 'self'", - "style-src 'self' 'unsafe-inline'", - "script-src 'self' 'unsafe-inline'", - "img-src 'self' data:", - "object-src 'none'", - "frame-ancestors 'none'", - "form-action 'self'", - "base-uri 'self'" - ].join('; ')); - - res.send(` - - - - - - MCP Server Authorization - - - -
-
- -
MCP
-
- -

Authorization Required

-

This client wants to connect to your MCP server

- -
-

Client Application

-
${client.client_id}
-
- -
-

What happens next?

-

You'll be redirected to authenticate with the upstream provider. Once verified, you'll be granted access to this MCP server's resources.

-
- - - Continue to Authentication - - -
- Model Context Protocol (MCP) Server -
-
- - - `); - } - - async challengeForAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string): Promise { - const pendingAuth = await readPendingAuthorization(authorizationCode); - if (!pendingAuth) { - throw new Error('Authorization code not found'); - } - - if (pendingAuth.clientId !== client.client_id) { - throw new Error('Authorization code does not match client'); - } - - return pendingAuth.codeChallenge; - } - - async exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string): Promise { - const tokenData = await exchangeToken(authorizationCode); - if (!tokenData) { - throw new Error('Invalid authorization code'); - } - - // Get the MCP installation to retrieve the full token data including refresh token - const mcpInstallation = await readMcpInstallation(tokenData.mcpAccessToken); - if (!mcpInstallation) { - throw new Error('Failed to retrieve MCP installation'); - } - - // Return the full token data including refresh token - return { - access_token: mcpInstallation.mcpTokens.access_token, - refresh_token: mcpInstallation.mcpTokens.refresh_token, - expires_in: mcpInstallation.mcpTokens.expires_in, - token_type: 'Bearer', - }; - } - - async exchangeRefreshToken(client: OAuthClientInformationFull, refreshToken: string, _scopes?: string[]): Promise { - const accessToken = await readRefreshToken(refreshToken); - - if (!accessToken) { - throw new Error('Invalid refresh token'); - } - - const mcpInstallation = await readMcpInstallation(accessToken); - - if (!mcpInstallation) { - throw new Error('Invalid refresh token'); - } - - // Check the client_id - if (mcpInstallation.clientId !== client.client_id) { - throw new Error('Invalid client'); - } - - const newTokens = generateMcpTokens(); - - if (newTokens.refresh_token) { - await saveRefreshToken(newTokens.refresh_token, newTokens.access_token); - } - - // Update the installation with the new tokens - await saveMcpInstallation(newTokens.access_token, { - ...mcpInstallation, - mcpTokens: newTokens, - issuedAt: Date.now() / 1000, - userId: mcpInstallation.userId, // Preserve the user ID - }); - - return newTokens; - } - - async verifyAccessToken(token: string): Promise { - const installation = await readMcpInstallation(token); - if (!installation) { - throw new InvalidTokenError("Invalid access token"); - } - - const expiresAt = ( - installation.mcpTokens.expires_in - ? installation.mcpTokens.expires_in + installation.issuedAt - : undefined - ); - - // This can be removed once in the SDK - // Check if the token is expired - if (!!expiresAt && expiresAt < Date.now() / 1000) { - throw new InvalidTokenError("Token has expired"); - } - - return { - token, - clientId: installation.clientId, - scopes: ['mcp'], - expiresAt, - extra: { - userId: installation.userId - } - }; - } - - async revokeToken(client: OAuthClientInformationFull, request: OAuthTokenRevocationRequest): Promise { - await revokeMcpInstallation(request.token); - } -} \ No newline at end of file diff --git a/external-oauth/auth-server/src/services/auth.test.ts b/external-oauth/auth-server/src/services/auth.test.ts deleted file mode 100644 index 1695cf1..0000000 --- a/external-oauth/auth-server/src/services/auth.test.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { jest } from '@jest/globals'; -import crypto from "crypto"; -import { - decryptString, - exchangeToken, - generateMcpTokens, - generatePKCEChallenge, - generateToken, - getClientRegistration, - readMcpInstallation, - readPendingAuthorization, - readRefreshToken, - revokeMcpInstallation, - saveClientRegistration, - saveMcpInstallation, - savePendingAuthorization, - saveRefreshToken, - saveTokenExchange, -} from "./auth.js"; -import { MockRedisClient, setRedisClient } from "../redis.js"; -import { McpInstallation, PendingAuthorization, TokenExchange } from "../types.js"; -import { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js"; - - -describe("auth utils", () => { - let mockRedis: MockRedisClient; - - beforeEach(() => { - mockRedis = new MockRedisClient(); - setRedisClient(mockRedis); - - jest.resetAllMocks(); - }); - - describe("generateToken", () => { - it("generates a 64-character hex string", () => { - const token = generateToken(); - expect(token).toMatch(/^[0-9a-f]{64}$/); - }); - - it("generates unique tokens", () => { - const token1 = generateToken(); - const token2 = generateToken(); - expect(token1).not.toBe(token2); - }); - }); - - describe("generateMcpTokens", () => { - it("generates valid token structure", () => { - const tokens = generateMcpTokens(); - - expect(tokens.access_token).toBeDefined(); - expect(tokens.access_token).toMatch(/^[0-9a-f]{64}$/); - - expect(tokens.refresh_token).toBeDefined(); - expect(tokens.refresh_token).toMatch(/^[0-9a-f]{64}$/); - - expect(tokens.token_type).toBe("Bearer"); - expect(tokens.expires_in).toBeDefined(); - }); - - it("generates unique tokens", () => { - const tokens1 = generateMcpTokens(); - const tokens2 = generateMcpTokens(); - - expect(tokens1.access_token).not.toBe(tokens2.access_token); - expect(tokens1.refresh_token).not.toBe(tokens2.refresh_token); - }); - }); - - describe("client registration", () => { - it("saves and retrieves client registration", async () => { - const clientId = "test-client-id"; - const registration: OAuthClientInformationFull = { - client_id: clientId, - client_name: "Test Client", - client_uri: "https://example.com", - redirect_uris: ["https://example.com/callback"] - }; - - await saveClientRegistration(clientId, registration); - const retrieved = await getClientRegistration(clientId); - - expect(retrieved).toEqual(registration); - }); - - it("returns undefined for non-existent client", async () => { - const result = await getClientRegistration("non-existent"); - expect(result).toBeUndefined(); - }); - }); - - describe("pending authorization", () => { - it("saves and retrieves pending authorization", async () => { - const authCode = generateToken(); - const pendingAuth: PendingAuthorization = { - redirectUri: "https://example.com/callback", - codeChallenge: "test-challenge", - codeChallengeMethod: "S256", - clientId: "test-client-id" - }; - - await savePendingAuthorization(authCode, pendingAuth); - const retrieved = await readPendingAuthorization(authCode); - - expect(retrieved).toEqual(pendingAuth); - }); - - it("returns undefined for non-existent code", async () => { - const result = await readPendingAuthorization("non-existent"); - expect(result).toBeUndefined(); - }); - }); - - describe("token exchange", () => { - it("saves and retrieves token exchange data", async () => { - const authCode = generateToken(); - const tokenExchange: TokenExchange = { - mcpAccessToken: generateToken(), - alreadyUsed: false - }; - - // For this test, we'll directly manipulate Redis to check the token - // instead of using exchangeToken which changes the value - await saveTokenExchange(authCode, tokenExchange); - - // Get the key used by saveTokenExchange (now with auth: prefix) - const key = "auth:exch:" + crypto.createHash("sha256").update(authCode).digest("hex"); - - // Get the encrypted data - const encryptedData = await mockRedis.get(key); - expect(encryptedData).not.toBeNull(); - - // Decrypt it manually to verify - const decoded = JSON.parse(decryptString({ - encryptedText: encryptedData!, - key: authCode - })); - - expect(decoded).toEqual(tokenExchange); - }); - - it("prevents duplicate token exchange", async () => { - const authCode = generateToken(); - const tokenExchange: TokenExchange = { - mcpAccessToken: generateToken(), - alreadyUsed: false - }; - - await saveTokenExchange(authCode, tokenExchange); - - // First exchange succeeds - const first = await exchangeToken(authCode); - expect(first).toBeDefined(); - - // Mock console.error to suppress expected error message - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - - // Second exchange throws - await expect(exchangeToken(authCode)).rejects.toThrow( - "Duplicate use of authorization code detected" - ); - - // Restore console.error - consoleErrorSpy.mockRestore(); - }); - - it("returns undefined for non-existent code", async () => { - const result = await exchangeToken("non-existent"); - expect(result).toBeUndefined(); - }); - }); - - describe("MCP installation", () => { - it("returns undefined for missing tokens", async () => { - const accessToken = generateToken(); - const result = await readMcpInstallation(accessToken); - expect(result).toBeUndefined(); - }); - - it("saves and retrieves installation data", async () => { - const accessToken = generateToken(); - - const mcpInstallation: McpInstallation = { - mockUpstreamInstallation: { - mockUpstreamAccessToken: "fake-upstream-access-token", - mockUpstreamRefreshToken: "fake-upstream-refresh-token", - }, - mcpTokens: { - access_token: accessToken, - token_type: "Bearer", - expires_in: 3600, - }, - clientId: "client-id", - issuedAt: Date.now() / 1000, - userId: "test-user-id", - } - - await saveMcpInstallation(accessToken, mcpInstallation); - - const result = await readMcpInstallation(accessToken); - expect(result).toEqual(mcpInstallation); - }); - }); - - describe("refresh token", () => { - it("saves and retrieves refresh token mapping", async () => { - const refreshToken = generateToken(); - const accessToken = generateToken(); - - await saveRefreshToken(refreshToken, accessToken); - const retrieved = await readRefreshToken(refreshToken); - - expect(retrieved).toBe(accessToken); - }); - - it("returns undefined for non-existent refresh token", async () => { - const result = await readRefreshToken("non-existent"); - expect(result).toBeUndefined(); - }); - }); - - describe("revokeMcpInstallation", () => { - it("revokes token for valid installation", async () => { - // For this test, we'll simply test if the WebClient is called with our mocked token - - // Create mock installation - const accessToken = generateToken(); - - // Save it to Redis with actual function - await saveMcpInstallation(accessToken, { - mockUpstreamInstallation: { - mockUpstreamAccessToken: "fake-upstream-access-token", - mockUpstreamRefreshToken: "fake-upstream-refresh-token", - }, - mcpTokens: { - access_token: accessToken, - token_type: "Bearer", - expires_in: 3600, - }, - clientId: "client-id", - issuedAt: Date.now() / 1000, - userId: "test-user-id", - }); - - const getDel = jest.spyOn(mockRedis, 'getDel').mockImplementationOnce(() => { - // Need to return encrypted data for successful decryption in the revoke function - // Create encrypted data using our access token - const mcpInstallation = { - mcpTokens: { - access_token: accessToken, - token_type: "Bearer", - expires_in: 3600, - }, - clientId: "client-id", - issuedAt: Date.now() / 1000, - userId: "test-user-id", - }; - const value = JSON.stringify(mcpInstallation); - const iv = crypto.randomBytes(16); - const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(accessToken, "hex"), iv); - let encrypted = cipher.update(value, "utf-8", "hex"); - encrypted += cipher.final("hex"); - return Promise.resolve(`${iv.toString("hex")}:${encrypted}`); - }); - - await revokeMcpInstallation(accessToken); - - // Should have called getDel with the correct key (now auth:installation:) - expect(getDel).toHaveBeenCalledWith(expect.stringContaining("auth:installation:")); - - }); - - it("handles non-existent installation without error", async () => { - await expect(revokeMcpInstallation("non-existent")).resolves.not.toThrow(); - }); - }); - - describe("generatePKCEChallenge", () => { - it("generates base64url-encoded SHA256 hash", () => { - const verifier = "test_verifier"; - const challenge = generatePKCEChallenge(verifier); - expect(challenge).toBe("0Ku4rR8EgR1w3HyHLBCxVLtPsAAks5HOlpmTEt0XhVA"); - }); - - it("generates different challenges for different verifiers", () => { - const challenge1 = generatePKCEChallenge("verifier1"); - const challenge2 = generatePKCEChallenge("verifier2"); - expect(challenge1).not.toBe(challenge2); - }); - }); -}); \ No newline at end of file diff --git a/external-oauth/auth-server/src/services/auth.ts b/external-oauth/auth-server/src/services/auth.ts deleted file mode 100644 index 5d9b4e3..0000000 --- a/external-oauth/auth-server/src/services/auth.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { redisClient } from "../redis.js"; -import { McpInstallation, PendingAuthorization, TokenExchange } from "../types.js"; -import { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js"; - -// Re-export from auth-core module -export { - generatePKCEChallenge, - generateToken, - decryptString, - generateMcpTokens -} from "../auth/auth-core.js"; - -import * as sharedRedisAuth from "./redis-auth.js"; - -// Wrapper functions that pass redisClient to shared module functions - -export async function saveClientRegistration( - clientId: string, - registration: OAuthClientInformationFull, -) { - return sharedRedisAuth.saveClientRegistration(redisClient, clientId, registration); -} - -export async function getClientRegistration( - clientId: string, -): Promise { - return sharedRedisAuth.getClientRegistration(redisClient, clientId); -} - -export async function savePendingAuthorization( - authorizationCode: string, - pendingAuthorization: PendingAuthorization, -) { - return sharedRedisAuth.savePendingAuthorization(redisClient, authorizationCode, pendingAuthorization); -} - -export async function readPendingAuthorization( - authorizationCode: string, -): Promise { - return sharedRedisAuth.readPendingAuthorization(redisClient, authorizationCode); -} - -export async function saveMcpInstallation( - mcpAccessToken: string, - installation: McpInstallation, -) { - return sharedRedisAuth.saveMcpInstallation(redisClient, mcpAccessToken, installation); -} - -export async function readMcpInstallation( - mcpAccessToken: string, -): Promise { - return sharedRedisAuth.readMcpInstallation(redisClient, mcpAccessToken); -} - -export async function saveRefreshToken( - refreshToken: string, - mcpAccessToken: string, -) { - return sharedRedisAuth.saveRefreshToken(redisClient, refreshToken, mcpAccessToken); -} - -export async function readRefreshToken( - refreshToken: string, -): Promise { - return sharedRedisAuth.readRefreshToken(redisClient, refreshToken); -} - -export async function revokeMcpInstallation( - mcpAccessToken: string, -): Promise { - return sharedRedisAuth.revokeMcpInstallation(redisClient, mcpAccessToken); -} - -export async function saveTokenExchange( - authorizationCode: string, - tokenExchange: TokenExchange, -) { - return sharedRedisAuth.saveTokenExchange(redisClient, authorizationCode, tokenExchange); -} - -export async function exchangeToken( - authorizationCode: string, -): Promise { - return sharedRedisAuth.exchangeToken(redisClient, authorizationCode); -} diff --git a/external-oauth/auth-server/src/services/redis-auth.ts b/external-oauth/auth-server/src/services/redis-auth.ts deleted file mode 100644 index 7886bbc..0000000 --- a/external-oauth/auth-server/src/services/redis-auth.ts +++ /dev/null @@ -1,285 +0,0 @@ -import { SetOptions } from "@redis/client"; -import { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js"; -import { RedisClient } from "../redis.js"; -import { McpInstallation, PendingAuthorization, TokenExchange } from "../types.js"; -import { sha256, encryptString, decryptString } from "../auth/auth-core.js"; -import { logger } from "../utils/logger.js"; - -/** - * Redis key prefixes for different data types - * All auth-related keys use "auth:" prefix to avoid collision with MCP session keys - */ -export const REDIS_KEY_PREFIXES = { - CLIENT_REGISTRATION: "auth:client:", - PENDING_AUTHORIZATION: "auth:pending:", - MCP_AUTHORIZATION: "auth:installation:", // Changed from "mcp:" to avoid collision - TOKEN_EXCHANGE: "auth:exch:", - REFRESH_TOKEN: "auth:refresh:", -} as const; - -/** - * Redis key expiry times in seconds - */ -export const REDIS_EXPIRY_TIMES = { - CLIENT_REGISTRATION: 30 * 24 * 60 * 60, // 30 days - client app credentials - PENDING_AUTHORIZATION: 10 * 60, // 10 minutes - authorization code -> PendingAuthorization - TOKEN_EXCHANGE: 10 * 60, // 10 minutes - authorization code -> MCP access token - UPSTREAM_INSTALLATION: 7 * 24 * 60 * 60, // 7 days - MCP access token -> UpstreamInstallation - REFRESH_TOKEN: 7 * 24 * 60 * 60, // 7 days - MCP refresh token -> access token -} as const; - -/** - * Saves encrypted data to Redis with optional expiry. - */ -async function saveEncrypted( - redisClient: RedisClient, - { - prefix, - key, - data, - options, - }: { - prefix: string; - key: string; - data: T; - options?: SetOptions; - } -): Promise { - const value = encryptString({ - text: JSON.stringify(data), - key: key, - }); - - return await redisClient.set(prefix + sha256(key), value, options); -} - -/** - * Reads and decrypts data from Redis. - */ -async function readEncrypted( - redisClient: RedisClient, - { - prefix, - key, - del = false, - }: { - prefix: string; - key: string; - del?: boolean; - } -): Promise { - const data = del - ? await redisClient.getDel(prefix + sha256(key)) - : await redisClient.get(prefix + sha256(key)); - - if (!data) { - return undefined; - } - - const decoded = decryptString({ - encryptedText: data, - key: key, - }); - - return JSON.parse(decoded); -} - -/** - * Saves a client registration to Redis. - */ -export async function saveClientRegistration( - redisClient: RedisClient, - clientId: string, - registration: OAuthClientInformationFull -): Promise { - await redisClient.set( - REDIS_KEY_PREFIXES.CLIENT_REGISTRATION + clientId, - JSON.stringify(registration), - { EX: REDIS_EXPIRY_TIMES.CLIENT_REGISTRATION } - ); -} - -/** - * Retrieves a client registration from Redis. - */ -export async function getClientRegistration( - redisClient: RedisClient, - clientId: string -): Promise { - const data = await redisClient.get(REDIS_KEY_PREFIXES.CLIENT_REGISTRATION + clientId); - if (!data) { - return undefined; - } - return JSON.parse(data); -} - -/** - * Saves a pending authorization to Redis. - */ -export async function savePendingAuthorization( - redisClient: RedisClient, - authorizationCode: string, - pendingAuthorization: PendingAuthorization -): Promise { - await saveEncrypted(redisClient, { - prefix: REDIS_KEY_PREFIXES.PENDING_AUTHORIZATION, - key: authorizationCode, - data: pendingAuthorization, - options: { EX: REDIS_EXPIRY_TIMES.PENDING_AUTHORIZATION }, - }); -} - -/** - * Reads a pending authorization from Redis. - */ -export async function readPendingAuthorization( - redisClient: RedisClient, - authorizationCode: string -): Promise { - return readEncrypted(redisClient, { - prefix: REDIS_KEY_PREFIXES.PENDING_AUTHORIZATION, - key: authorizationCode, - }); -} - -/** - * Saves an MCP installation to Redis. - */ -export async function saveMcpInstallation( - redisClient: RedisClient, - mcpAccessToken: string, - installation: McpInstallation -): Promise { - await saveEncrypted(redisClient, { - prefix: REDIS_KEY_PREFIXES.MCP_AUTHORIZATION, - key: mcpAccessToken, - data: installation, - options: { EX: REDIS_EXPIRY_TIMES.UPSTREAM_INSTALLATION }, - }); -} - -/** - * Reads an MCP installation from Redis. - */ -export async function readMcpInstallation( - redisClient: RedisClient, - mcpAccessToken: string -): Promise { - return readEncrypted(redisClient, { - prefix: REDIS_KEY_PREFIXES.MCP_AUTHORIZATION, - key: mcpAccessToken, - }); -} - -/** - * Links a refresh token to an MCP access token. - */ -export async function saveRefreshToken( - redisClient: RedisClient, - refreshToken: string, - mcpAccessToken: string -): Promise { - await saveEncrypted(redisClient, { - prefix: REDIS_KEY_PREFIXES.REFRESH_TOKEN, - key: refreshToken, - data: mcpAccessToken, - options: { EX: REDIS_EXPIRY_TIMES.REFRESH_TOKEN }, - }); -} - -/** - * Reads the access token associated with a refresh token. - */ -export async function readRefreshToken( - redisClient: RedisClient, - refreshToken: string -): Promise { - return readEncrypted(redisClient, { - prefix: REDIS_KEY_PREFIXES.REFRESH_TOKEN, - key: refreshToken, - }); -} - -/** - * Revokes an MCP installation. - */ -export async function revokeMcpInstallation( - redisClient: RedisClient, - mcpAccessToken: string -): Promise { - const installation = await readEncrypted(redisClient, { - prefix: REDIS_KEY_PREFIXES.MCP_AUTHORIZATION, - key: mcpAccessToken, - del: true, - }); - - if (!installation) { - return; - } - // In production, would revoke upstream tokens here -} - -/** - * Saves a token exchange record. - */ -export async function saveTokenExchange( - redisClient: RedisClient, - authorizationCode: string, - tokenExchange: TokenExchange -): Promise { - await saveEncrypted(redisClient, { - prefix: REDIS_KEY_PREFIXES.TOKEN_EXCHANGE, - key: authorizationCode, - data: tokenExchange, - options: { EX: REDIS_EXPIRY_TIMES.TOKEN_EXCHANGE }, - }); -} - -/** - * Exchanges a temporary authorization code for an MCP access token. - * Will only succeed the first time to prevent replay attacks. - */ -export async function exchangeToken( - redisClient: RedisClient, - authorizationCode: string -): Promise { - const data = await redisClient.get( - REDIS_KEY_PREFIXES.TOKEN_EXCHANGE + sha256(authorizationCode) - ); - - if (!data) { - return undefined; - } - - const decoded = decryptString({ - encryptedText: data, - key: authorizationCode, - }); - - const tokenExchange: TokenExchange = JSON.parse(decoded); - if (tokenExchange.alreadyUsed) { - logger.error('Duplicate use of authorization code detected; revoking tokens', undefined, { - authorizationCode: authorizationCode.substring(0, 8) + '...' - }); - await revokeMcpInstallation(redisClient, tokenExchange.mcpAccessToken); - throw new Error("Duplicate use of authorization code detected; tokens revoked"); - } - - const rereadData = await saveEncrypted(redisClient, { - prefix: REDIS_KEY_PREFIXES.TOKEN_EXCHANGE, - key: authorizationCode, - data: { ...tokenExchange, alreadyUsed: true }, - options: { KEEPTTL: true, GET: true }, - }); - - if (rereadData !== data) { - // Data concurrently changed while we were updating it. This necessarily means a duplicate use. - logger.error('Duplicate use of authorization code detected (concurrent update); revoking tokens', undefined, { - authorizationCode: authorizationCode.substring(0, 8) + '...' - }); - await revokeMcpInstallation(redisClient, tokenExchange.mcpAccessToken); - throw new Error("Duplicate use of authorization code detected; tokens revoked"); - } - - return tokenExchange; -} \ No newline at end of file diff --git a/external-oauth/auth-server/src/types.ts b/external-oauth/auth-server/src/types.ts deleted file mode 100644 index 877b5dc..0000000 --- a/external-oauth/auth-server/src/types.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; - -/** - * Represents a pending OAuth authorization that hasn't been exchanged for tokens yet. - * Stored in Redis with the authorization code as the key. - */ -export interface PendingAuthorization { - /** The redirect URI where the client expects to receive the authorization code */ - redirectUri: string; - /** PKCE code challenge - a derived value from the code verifier */ - codeChallenge: string; - /** Method used to derive the code challenge (currently only S256 supported) */ - codeChallengeMethod: string; - /** The OAuth client ID that initiated the authorization */ - clientId: string; - /** Optional state parameter for CSRF protection */ - state?: string; -} - -/** - * Represents the exchange of an authorization code for an MCP access token. - * Used to prevent replay attacks by tracking if a code has been used. - */ -export interface TokenExchange { - /** The MCP access token that was issued for this authorization code */ - mcpAccessToken: string; - /** Whether this authorization code has already been exchanged for tokens */ - alreadyUsed: boolean; -} - -/** - * Represents mock upstream identity provider tokens for demonstration purposes. - * In production, this would contain real upstream provider tokens. - */ -export interface MockUpstreamInstallation { - /** Access token from the mock upstream identity provider */ - mockUpstreamAccessToken: string; - /** Refresh token from the mock upstream identity provider */ - mockUpstreamRefreshToken: string; -} - -/** - * The complete installation object stored in Redis, containing both - * upstream provider information and MCP-specific tokens. - * This object is encrypted using the MCP access token as the key. - */ -export interface McpInstallation { - /** Information from the upstream authentication provider */ - mockUpstreamInstallation: MockUpstreamInstallation; - /** MCP OAuth tokens issued to the client */ - mcpTokens: OAuthTokens; - /** The OAuth client ID associated with this installation */ - clientId: string; - /** Unix timestamp (seconds) when the tokens were issued */ - issuedAt: number; - /** Unique identifier for the user (not the OAuth client) */ - userId: string; -} - -/** - * OAuth 2.0 Token Introspection Response - * Based on RFC 7662: https://tools.ietf.org/html/rfc7662 - * Used when validating tokens with an external authorization server. - */ -export interface TokenIntrospectionResponse { - /** Whether the token is currently active */ - active: boolean; - /** Space-separated list of scopes associated with the token */ - scope?: string; - /** Client identifier for the OAuth client that requested the token */ - client_id?: string; - /** Human-readable identifier for the resource owner */ - username?: string; - /** Type of the token (e.g., "Bearer") */ - token_type?: string; - /** Expiration time as seconds since Unix epoch */ - exp?: number; - /** Time at which the token was issued as seconds since Unix epoch */ - iat?: number; - /** Time before which the token is not valid as seconds since Unix epoch */ - nbf?: number; - /** Subject identifier for the resource owner */ - sub?: string; - /** Intended audience for the token */ - aud?: string | string[]; - /** Issuer of the token */ - iss?: string; - /** Unique identifier for the token */ - jti?: string; - /** Custom field for our implementation to store user ID */ - userId?: string; -} \ No newline at end of file diff --git a/external-oauth/auth-server/src/utils/logger.ts b/external-oauth/auth-server/src/utils/logger.ts deleted file mode 100644 index f9879db..0000000 --- a/external-oauth/auth-server/src/utils/logger.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { AsyncLocalStorage } from 'async_hooks'; -import { Request, Response, NextFunction } from 'express'; - -// Severity levels as per Google Cloud Logging -export enum LogSeverity { - DEFAULT = 'DEFAULT', - DEBUG = 'DEBUG', - INFO = 'INFO', - NOTICE = 'NOTICE', - WARNING = 'WARNING', - ERROR = 'ERROR', - CRITICAL = 'CRITICAL', - ALERT = 'ALERT', - EMERGENCY = 'EMERGENCY' -} - -interface LogContext { - trace?: string; - spanId?: string; - requestId?: string; - userAgent?: string; - method?: string; - path?: string; - [key: string]: string | undefined; -} - -interface StructuredLogEntry { - severity: LogSeverity; - message: string; - timestamp: string; - 'logging.googleapis.com/trace'?: string; - 'logging.googleapis.com/spanId'?: string; - [key: string]: unknown; -} - -class StructuredLogger { - private asyncLocalStorage = new AsyncLocalStorage(); - private projectId: string | undefined; - - constructor() { - // Get project ID from environment or metadata server - this.projectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCP_PROJECT; - } - - /** - * Run a function with a specific logging context - */ - runWithContext(context: LogContext, fn: () => T): T { - return this.asyncLocalStorage.run(context, fn); - } - - /** - * Extract trace context from Cloud Run request - */ - extractTraceContext(req: Request): LogContext { - const context: LogContext = {}; - - const traceHeader = req.header('X-Cloud-Trace-Context'); - if (traceHeader && this.projectId) { - const [trace, spanId] = traceHeader.split('/'); - context.trace = `projects/${this.projectId}/traces/${trace}`; - if (spanId) { - context.spanId = spanId.split(';')[0]; // Remove any trace flags - } - } - - // Add other useful request context - context.requestId = req.header('X-Request-Id'); - context.userAgent = req.header('User-Agent'); - context.method = req.method; - context.path = req.path; - - return context; - } - - /** - * Create Express middleware for request context - */ - middleware() { - return (req: Request, res: Response, next: NextFunction) => { - const context = this.extractTraceContext(req); - this.runWithContext(context, () => { - next(); - }); - }; - } - - /** - * Log a structured message - */ - private log(severity: LogSeverity, message: string, metadata?: Record) { - const context = this.asyncLocalStorage.getStore() || {}; - - const entry: StructuredLogEntry = { - severity, - message, - timestamp: new Date().toISOString(), - ...metadata - }; - - // Add trace context if available - if (context.trace) { - entry['logging.googleapis.com/trace'] = context.trace; - } - if (context.spanId) { - entry['logging.googleapis.com/spanId'] = context.spanId; - } - - // Add any other context fields - Object.keys(context).forEach(key => { - if (key !== 'trace' && key !== 'spanId') { - entry[`context.${key}`] = context[key]; - } - }); - - // Output as JSON for Cloud Logging - console.log(JSON.stringify(entry)); - } - - // Convenience methods for different severity levels - debug(message: string, metadata?: Record) { - this.log(LogSeverity.DEBUG, message, metadata); - } - - info(message: string, metadata?: Record) { - this.log(LogSeverity.INFO, message, metadata); - } - - notice(message: string, metadata?: Record) { - this.log(LogSeverity.NOTICE, message, metadata); - } - - warning(message: string, metadata?: Record) { - this.log(LogSeverity.WARNING, message, metadata); - } - - error(message: string, error?: Error, metadata?: Record) { - const errorMetadata = { - ...metadata, - error: error ? { - name: error.name, - message: error.message, - stack: error.stack - } : undefined - }; - this.log(LogSeverity.ERROR, message, errorMetadata); - } - - critical(message: string, metadata?: Record) { - this.log(LogSeverity.CRITICAL, message, metadata); - } - - alert(message: string, metadata?: Record) { - this.log(LogSeverity.ALERT, message, metadata); - } - - emergency(message: string, metadata?: Record) { - this.log(LogSeverity.EMERGENCY, message, metadata); - } - - /** - * Add additional context to the current async context - */ - addContext(context: LogContext) { - const currentContext = this.asyncLocalStorage.getStore(); - if (currentContext) { - Object.assign(currentContext, context); - } - } -} - -// Export singleton instance -export const logger = new StructuredLogger(); - -// Re-export for convenience -export type { LogContext, StructuredLogEntry }; \ No newline at end of file diff --git a/external-oauth/mcp-server/eslint.config.mjs b/external-oauth/mcp-server/eslint.config.mjs deleted file mode 100644 index 515114c..0000000 --- a/external-oauth/mcp-server/eslint.config.mjs +++ /dev/null @@ -1,19 +0,0 @@ -// @ts-check - -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; - -export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommended, - { - linterOptions: { - reportUnusedDisableDirectives: false, - }, - rules: { - "@typescript-eslint/no-unused-vars": ["error", - { "argsIgnorePattern": "^_" } - ] - } - } -); diff --git a/external-oauth/mcp-server/jest.config.js b/external-oauth/mcp-server/jest.config.js deleted file mode 100644 index 87825b9..0000000 --- a/external-oauth/mcp-server/jest.config.js +++ /dev/null @@ -1,19 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -export default { - preset: 'ts-jest/presets/default-esm', - testEnvironment: 'node', - extensionsToTreatAsEsm: ['.ts'], - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.js$': '$1', - }, - transform: { - '^.+\\.tsx?$': [ - 'ts-jest', - { - useESM: true, - }, - ], - }, - testPathIgnorePatterns: ['/node_modules/', '/dist/', '/scratch/'], - injectGlobals: true, -}; diff --git a/external-oauth/mcp-server/src/handlers/shttp.integration.test.ts b/external-oauth/mcp-server/src/handlers/shttp.integration.test.ts deleted file mode 100644 index b345d28..0000000 --- a/external-oauth/mcp-server/src/handlers/shttp.integration.test.ts +++ /dev/null @@ -1,694 +0,0 @@ -import { jest } from '@jest/globals'; -import { Request, Response } from 'express'; -import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; -import { MockRedisClient, setRedisClient } from '../redis.js'; -import { handleStreamableHTTP } from './shttp.js'; -import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; -// import { randomUUID } from 'crypto'; // Currently unused but may be needed for future tests -import { shutdownSession } from '../services/redisTransport.js'; - -// Type for MCP initialization response -interface MCPInitResponse { - jsonrpc: string; - id: string | number; - result?: { - _meta?: { - sessionId?: string; - }; - [key: string]: unknown; - }; -} - -describe('Streamable HTTP Handler Integration Tests', () => { - let mockRedis: MockRedisClient; - let mockReq: Partial; - let mockRes: Partial; - - beforeEach(() => { - mockRedis = new MockRedisClient(); - setRedisClient(mockRedis); - jest.resetAllMocks(); - - // Create mock response with chainable methods - mockRes = { - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - on: jest.fn().mockReturnThis(), - once: jest.fn().mockReturnThis(), - emit: jest.fn().mockReturnThis(), - headersSent: false, - setHeader: jest.fn().mockReturnThis(), - writeHead: jest.fn().mockReturnThis(), - write: jest.fn().mockReturnThis(), - end: jest.fn().mockReturnThis(), - getHeader: jest.fn(), - removeHeader: jest.fn().mockReturnThis(), - socket: { - setTimeout: jest.fn(), - }, - } as unknown as Partial; - - // Create mock request - mockReq = { - method: 'POST', - headers: { - 'content-type': 'application/json', - 'accept': 'application/json, text/event-stream', - 'mcp-protocol-version': '2024-11-05', - }, - body: {}, - }; - }); - - // Helper function to trigger cleanup after handleStreamableHTTP calls - const triggerResponseCleanup = async () => { - // Find all finish handlers registered during the test - const finishHandlers = (mockRes.on as jest.Mock).mock.calls - .filter(([event]) => event === 'finish') - .map(([, handler]) => handler); - - // Trigger all finish handlers - for (const handler of finishHandlers) { - if (typeof handler === 'function') { - await handler(); - } - } - }; - - // Helper to extract session ID from test context - const getSessionIdFromTest = (): string | undefined => { - // Try to get from response headers first - const setHeaderCalls = (mockRes.setHeader as jest.Mock).mock.calls; - const sessionIdHeader = setHeaderCalls.find(([name]) => name === 'mcp-session-id'); - if (sessionIdHeader?.[1]) { - return sessionIdHeader[1] as string; - } - - // Fall back to extracting from Redis channels - const allChannels = Array.from(mockRedis.subscribers.keys()); - const serverChannel = allChannels.find(channel => channel.includes('mcp:shttp:toserver:')); - return serverChannel?.split(':')[3]; - }; - - afterEach(async () => { - // Always trigger cleanup for any MCP servers created during tests - await triggerResponseCleanup(); - mockRedis.clear(); - jest.clearAllMocks(); - }); - - describe('Redis Subscription Cleanup', () => { - it('should clean up Redis subscriptions after shttp response completes', async () => { - // Set up initialization request (no session ID for new initialization) - const initRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'init-1', - method: 'initialize', - params: { - protocolVersion: '2024-11-05', - capabilities: {}, - clientInfo: { name: 'test-client', version: '1.0.0' } - } - }; - - mockReq.body = initRequest; - mockReq.auth = { - clientId: 'test-client-123', - token: 'test-token', - scopes: ['mcp'], - extra: { userId: 'test-user-123' } - } as AuthInfo; - - // Call the handler - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Wait longer for async initialization to complete - await new Promise(resolve => setTimeout(resolve, 200)); - - // get the sessionId from the response - const sessionId = getSessionIdFromTest(); - expect(sessionId).toBeDefined(); - - // Check if any subscriptions were created on any channels - // Since we don't know the exact sessionId generated, check all channels - const allChannels = Array.from(mockRedis.subscribers.keys()); - const totalSubscriptions = allChannels.reduce((sum, channel) => sum + (mockRedis.subscribers.get(channel)?.length || 0), 0); - - // Should have created at least one subscription (server channel) - expect(totalSubscriptions).toBeGreaterThan(0); - expect(allChannels.some(channel => channel.includes('mcp:shttp:toserver:'))).toBe(true); - - // Find the finish handler that was registered - const finishHandler = (mockRes.on as jest.Mock).mock.calls.find( - ([event]) => event === 'finish' - )?.[1] as (() => Promise) | undefined; - - expect(finishHandler).toBeDefined(); - - // Simulate response completion to trigger cleanup - if (finishHandler) { - await finishHandler(); - } - - // Verify cleanup handler was registered - expect(mockRes.on).toHaveBeenCalledWith('finish', expect.any(Function)); - - if (sessionId) { - await shutdownSession(sessionId) - } - }); - - it('should handle cleanup errors gracefully', async () => { - const initRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'init-1', - method: 'initialize', - params: { - protocolVersion: '2024-11-05', - capabilities: {}, - clientInfo: { name: 'test-client', version: '1.0.0' } - } - }; - - mockReq.body = initRequest; - mockReq.auth = { - clientId: 'test-client-123', - token: 'test-token', - scopes: ['mcp'], - extra: { userId: 'test-user-123' } - } as AuthInfo; - - // Call the handler - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Use Redis error simulation to test error handling - const finishHandler = (mockRes.on as jest.Mock).mock.calls.find( - ([event]) => event === 'finish' - )?.[1] as (() => Promise) | undefined; - - // Simulate error during cleanup - - // Cleanup should not throw error even if Redis operations fail - if (finishHandler) { - await expect(finishHandler()).resolves.not.toThrow(); - } - - // Clean up the MCP server by sending DELETE request - const cleanupSessionId = getSessionIdFromTest(); - - if (cleanupSessionId) { - // Send DELETE request to clean up MCP server - jest.clearAllMocks(); - mockReq.method = 'DELETE'; - if (mockReq.headers) { - mockReq.headers['mcp-session-id'] = cleanupSessionId; - } - mockReq.body = {}; - - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Wait a bit for cleanup to complete - await new Promise(resolve => setTimeout(resolve, 50)); - } - }); - }); - - describe('DELETE Request Session Cleanup', () => { - it('should trigger onsessionclosed callback which sends shutdown control message', async () => { - // First, create a session with an initialization request - const initRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'init-1', - method: 'initialize', - params: { - protocolVersion: '2024-11-05', - capabilities: {}, - clientInfo: { name: 'test-client', version: '1.0.0' } - } - }; - - mockReq.body = initRequest; - mockReq.auth = { - clientId: 'test-client-123', - token: 'test-token', - scopes: ['mcp'], - extra: { userId: 'test-user-123' } - } as AuthInfo; - - // Initialize session - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Wait for async initialization - await new Promise(resolve => setTimeout(resolve, 100)); - - // For initialization requests with StreamableHTTPServerTransport, - // the handler might not immediately return a response if using SSE mode - // Let's check different possible locations for the session ID - - // Check JSON responses - const jsonCalls = (mockRes.json as jest.Mock).mock.calls; - let sessionId: string | undefined; - - if (jsonCalls.length > 0) { - const response = jsonCalls[0][0] as MCPInitResponse; - if (response?.result?._meta?.sessionId) { - sessionId = response.result._meta.sessionId; - } - } - - // Check write calls (for SSE responses) - if (!sessionId) { - const writeCalls = (mockRes.write as jest.Mock).mock.calls; - for (const [data] of writeCalls) { - if (typeof data === 'string' && data.includes('sessionId')) { - try { - // SSE data format: "data: {...}\n\n" - const jsonStr = data.replace(/^data: /, '').trim(); - const parsed = JSON.parse(jsonStr) as MCPInitResponse; - if (parsed?.result?._meta?.sessionId) { - sessionId = parsed.result._meta.sessionId; - } - } catch { - // Not valid JSON, continue - } - } - } - } - - // Fallback to getting from Redis channels - if (!sessionId) { - sessionId = getSessionIdFromTest(); - } - - expect(sessionId).toBeDefined(); - - // Reset mocks but keep the session - jest.clearAllMocks(); - - // Now test DELETE request - mockReq.method = 'DELETE'; - mockReq.headers = { - ...mockReq.headers, - 'mcp-session-id': sessionId - }; - mockReq.body = {}; - - // Track control messages sent to Redis - const publishSpy = jest.spyOn(mockRedis, 'publish'); - - // Call DELETE handler - StreamableHTTPServerTransport should handle it - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Wait for async processing and onsessionclosed callback - await new Promise(resolve => setTimeout(resolve, 100)); - - // The StreamableHTTPServerTransport should handle the DELETE and trigger onsessionclosed - // which calls shutdownSession, sending the control message - const controlCalls = publishSpy.mock.calls.filter(call => - call[0] === `mcp:control:${sessionId}` - ); - - expect(controlCalls.length).toBeGreaterThan(0); - - // Verify the control message content - const controlCall = publishSpy.mock.calls.find(call => - call[0] === `mcp:control:${sessionId}` - ); - if (controlCall) { - const message = JSON.parse(controlCall[1]); - expect(message.type).toBe('control'); - expect(message.action).toBe('SHUTDOWN'); - } - }); - - it('should return 401 for DELETE request with wrong user', async () => { - // First, create a session as user1 - const initRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'init-1', - method: 'initialize', - params: { - protocolVersion: '2024-11-05', - capabilities: {}, - clientInfo: { name: 'test-client', version: '1.0.0' } - } - }; - - mockReq.body = initRequest; - mockReq.auth = { - clientId: 'test-client-123', - token: 'test-token', - scopes: ['mcp'], - extra: { userId: 'user1' } - } as AuthInfo; - - // Initialize session as user1 - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Wait for async initialization - await new Promise(resolve => setTimeout(resolve, 100)); - - // Get the session ID from response - let sessionId: string | undefined; - - // Check JSON responses - const jsonCalls = (mockRes.json as jest.Mock).mock.calls; - if (jsonCalls.length > 0) { - const response = jsonCalls[0][0] as MCPInitResponse; - if (response?.result?._meta?.sessionId) { - sessionId = response.result._meta.sessionId; - } - } - - // Check write calls (for SSE responses) - if (!sessionId) { - const writeCalls = (mockRes.write as jest.Mock).mock.calls; - for (const [data] of writeCalls) { - if (typeof data === 'string' && data.includes('sessionId')) { - try { - const jsonStr = data.replace(/^data: /, '').trim(); - const parsed = JSON.parse(jsonStr) as MCPInitResponse; - if (parsed?.result?._meta?.sessionId) { - sessionId = parsed.result._meta.sessionId; - } - } catch { - // Ignore JSON parse errors - } - } - } - } - - if (!sessionId) { - sessionId = getSessionIdFromTest(); - } - - // Reset mocks - jest.clearAllMocks(); - - // Now test DELETE request as user2 - mockReq.method = 'DELETE'; - mockReq.headers = { - ...mockReq.headers, - 'mcp-session-id': sessionId - }; - mockReq.body = {}; - mockReq.auth = { - clientId: 'test-client-456', - token: 'test-token-2', - scopes: ['mcp'], - extra: { userId: 'user2' } - } as AuthInfo; - - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Should return 401 for unauthorized access to another user's session - expect(mockRes.status).toHaveBeenCalledWith(401); - - // shutdown the session - if (sessionId) { - await shutdownSession(sessionId) - } - }); - }); - - describe('User Session Isolation', () => { - it('should prevent users from accessing sessions created by other users', async () => { - // Create session for user 1 - const user1Auth: AuthInfo = { - clientId: 'user1-client', - token: 'user1-token', - scopes: ['mcp'], - extra: { userId: 'user1' } - }; - - const user2Auth: AuthInfo = { - clientId: 'user2-client', - token: 'user2-token', - scopes: ['mcp'], - extra: { userId: 'user2' } - }; - - const initRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'init-1', - method: 'initialize', - params: { - protocolVersion: '2024-11-05', - capabilities: {}, - clientInfo: { name: 'user1-client', version: '1.0.0' } - } - }; - - // User 1 creates session - mockReq.body = initRequest; - mockReq.auth = user1Auth; - - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Wait for async initialization to complete - await new Promise(resolve => setTimeout(resolve, 100)); - - // Get the actual session ID from response - let actualSessionId: string | undefined; - - // Check JSON responses - const jsonCalls = (mockRes.json as jest.Mock).mock.calls; - if (jsonCalls.length > 0) { - const response = jsonCalls[0][0] as MCPInitResponse; - if (response?.result?._meta?.sessionId) { - actualSessionId = response.result._meta.sessionId; - } - } - - // Check write calls (for SSE responses) - if (!actualSessionId) { - const writeCalls = (mockRes.write as jest.Mock).mock.calls; - for (const [data] of writeCalls) { - if (typeof data === 'string' && data.includes('sessionId')) { - try { - const jsonStr = data.replace(/^data: /, '').trim(); - const parsed = JSON.parse(jsonStr) as MCPInitResponse; - if (parsed?.result?._meta?.sessionId) { - actualSessionId = parsed.result._meta.sessionId; - } - } catch { - // Ignore JSON parse errors - } - } - } - } - - if (!actualSessionId) { - actualSessionId = getSessionIdFromTest(); - } - - expect(actualSessionId).toBeDefined(); - - // Store finish handler before clearing mocks - const finishHandler1 = (mockRes.on as jest.Mock).mock.calls.find( - ([event]) => event === 'finish' - )?.[1] as (() => Promise) | undefined; - - // Reset mocks - jest.clearAllMocks(); - - // Trigger cleanup for the MCP server created in this step - if (finishHandler1) { - await finishHandler1(); - } - - // User 2 tries to access user 1's session - mockReq.headers = { - ...mockReq.headers, - 'mcp-session-id': actualSessionId - }; - mockReq.body = { - jsonrpc: '2.0', - id: 'user2-request', - method: 'tools/list', - params: {} - }; - mockReq.auth = user2Auth; - - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Should return 401 for unauthorized access to another user's session - expect(mockRes.status).toHaveBeenCalledWith(401); - - // Clean up the MCP server by sending DELETE request - if (actualSessionId) { - jest.clearAllMocks(); - mockReq.method = 'DELETE'; - mockReq.headers['mcp-session-id'] = actualSessionId; - mockReq.body = {}; - mockReq.auth = user1Auth; // Use user1's auth to delete their session - - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Wait a bit for cleanup to complete - await new Promise(resolve => setTimeout(resolve, 50)); - } - }); - - it('should allow users to create separate sessions with same session ID pattern', async () => { - // This test shows that different users should be able to use sessions - // without interfering with each other, even if session IDs might collide - - const user1Auth: AuthInfo = { - clientId: 'user1-client', - token: 'user1-token', - scopes: ['mcp'] - }; - - const user2Auth: AuthInfo = { - clientId: 'user2-client', - token: 'user2-token', - scopes: ['mcp'] - }; - - const initRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'init-1', - method: 'initialize', - params: { - protocolVersion: '2024-11-05', - capabilities: {}, - clientInfo: { name: 'test-client', version: '1.0.0' } - } - }; - - // User 1 creates session - mockReq.body = initRequest; - mockReq.auth = user1Auth; - - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Store finish handler before clearing mocks - const finishHandler1 = (mockRes.on as jest.Mock).mock.calls.find( - ([event]) => event === 'finish' - )?.[1] as (() => Promise) | undefined; - - // Reset for user 2 - jest.clearAllMocks(); - - // Trigger cleanup for User 1's MCP server - if (finishHandler1) { - await finishHandler1(); - } - - // User 2 creates their own session - mockReq.body = { - ...initRequest, - id: 'init-2', - params: { - ...initRequest.params, - clientInfo: { name: 'user2-client', version: '1.0.0' } - } - }; - mockReq.auth = user2Auth; - delete mockReq.headers!['mcp-session-id']; // New initialization - - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Trigger cleanup for User 2's MCP server - const finishHandler2 = (mockRes.on as jest.Mock).mock.calls.find( - ([event]) => event === 'finish' - )?.[1] as (() => Promise) | undefined; - - if (finishHandler2) { - await finishHandler2(); - } - - // Both users should be able to create sessions successfully - // Sessions should be isolated in Redis using user-scoped keys - expect(mockRes.status).not.toHaveBeenCalledWith(400); - expect(mockRes.status).not.toHaveBeenCalledWith(403); - }); - - it('should clean up only the requesting user\'s session on DELETE', async () => { - // Create sessions for both users - const user1Auth: AuthInfo = { - clientId: 'user1-client', - token: 'user1-token', - scopes: ['mcp'] - }; - - const user2Auth: AuthInfo = { - clientId: 'user2-client', - token: 'user2-token', - scopes: ['mcp'] - }; - - // Create session for user 1 - const initRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'init-1', - method: 'initialize', - params: { - protocolVersion: '2024-11-05', - capabilities: {}, - clientInfo: { name: 'user1-client', version: '1.0.0' } - } - }; - - mockReq.body = initRequest; - mockReq.auth = user1Auth; - - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Trigger cleanup for User 1's MCP server - const finishHandler1 = (mockRes.on as jest.Mock).mock.calls.find( - ([event]) => event === 'finish' - )?.[1] as (() => Promise) | undefined; - - if (finishHandler1) { - await finishHandler1(); - } - - // Track session 1 ID (would be returned in response headers) - const session1Id = 'user1-session-id'; // In real implementation, extract from response - - // Create session for user 2 - jest.clearAllMocks(); - mockReq.body = { - ...initRequest, - id: 'init-2', - params: { - ...initRequest.params, - clientInfo: { name: 'user2-client', version: '1.0.0' } - } - }; - mockReq.auth = user2Auth; - delete mockReq.headers!['mcp-session-id']; - - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Trigger cleanup for User 2's MCP server - const finishHandler2 = (mockRes.on as jest.Mock).mock.calls.find( - ([event]) => event === 'finish' - )?.[1] as (() => Promise) | undefined; - - if (finishHandler2) { - await finishHandler2(); - } - - // Track session 2 ID (placeholder for actual implementation) - - // User 1 deletes their session - jest.clearAllMocks(); - mockReq.method = 'DELETE'; - mockReq.headers = { - ...mockReq.headers, - 'mcp-session-id': session1Id - }; - mockReq.body = {}; - mockReq.auth = user1Auth; - - await handleStreamableHTTP(mockReq as Request, mockRes as Response); - - // Only user 1's session should be cleaned up - // User 2's session should remain active - // This test documents expected behavior for proper user isolation - }); - }); -}); \ No newline at end of file diff --git a/external-oauth/mcp-server/src/handlers/shttp.test.ts b/external-oauth/mcp-server/src/handlers/shttp.test.ts deleted file mode 100644 index a068209..0000000 --- a/external-oauth/mcp-server/src/handlers/shttp.test.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { jest } from '@jest/globals'; -import { Request, Response } from 'express'; -import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; -import { MockRedisClient, setRedisClient } from '../redis.js'; - -describe('Streamable HTTP Handler', () => { - let mockRedis: MockRedisClient; - - beforeEach(() => { - mockRedis = new MockRedisClient(); - setRedisClient(mockRedis); - jest.resetAllMocks(); - }); - - afterEach(() => { - mockRedis.clear(); - }); - - describe('Helper function tests', () => { - it('should verify Redis mock is working', async () => { - await mockRedis.set('test-key', 'test-value'); - const value = await mockRedis.get('test-key'); - expect(value).toBe('test-value'); - }); - - it('should handle Redis pub/sub', async () => { - const messageHandler = jest.fn(); - const cleanup = await mockRedis.createSubscription( - 'test-channel', - messageHandler, - jest.fn() - ); - - await mockRedis.publish('test-channel', 'test-message'); - - expect(messageHandler).toHaveBeenCalledWith('test-message'); - - await cleanup(); - }); - }); - - describe('Request validation', () => { - it('should identify initialize requests correctly', async () => { - const { isInitializeRequest } = await import('@modelcontextprotocol/sdk/types.js'); - - const initRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 1, - method: 'initialize', - params: { - protocolVersion: '2024-11-05', - capabilities: {}, - clientInfo: { name: 'test', version: '1.0' } - } - }; - - const nonInitRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 2, - method: 'tools/list', - params: {} - }; - - expect(isInitializeRequest(initRequest)).toBe(true); - expect(isInitializeRequest(nonInitRequest)).toBe(false); - }); - }); - - describe('HTTP response mock behavior', () => { - it('should create proper response mock with chainable methods', () => { - const mockRes = { - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - on: jest.fn().mockReturnThis(), - headersSent: false, - } as Partial; - - // Test chaining - const result = mockRes.status!(400).json!({ - jsonrpc: '2.0', - error: { code: -32000, message: 'Bad Request' }, - id: null - }); - - expect(mockRes.status).toHaveBeenCalledWith(400); - expect(mockRes.json).toHaveBeenCalledWith({ - jsonrpc: '2.0', - error: { code: -32000, message: 'Bad Request' }, - id: null - }); - expect(result).toBe(mockRes); - }); - }); - - describe('Session ID generation', () => { - it('should generate valid UUIDs', async () => { - const { randomUUID } = await import('crypto'); - - const sessionId = randomUUID(); - - // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx - expect(sessionId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); - }); - }); - - describe('Redis channel naming', () => { - it('should create correct channel names for server communication', () => { - const sessionId = 'test-session-123'; - const requestId = 'req-456'; - - const toServerChannel = `mcp:shttp:toserver:${sessionId}`; - const toClientChannel = `mcp:shttp:toclient:${sessionId}:${requestId}`; - const notificationChannel = `mcp:shttp:toclient:${sessionId}:__GET_stream`; - - expect(toServerChannel).toBe('mcp:shttp:toserver:test-session-123'); - expect(toClientChannel).toBe('mcp:shttp:toclient:test-session-123:req-456'); - expect(notificationChannel).toBe('mcp:shttp:toclient:test-session-123:__GET_stream'); - }); - }); - - describe('Error response formatting', () => { - it('should format JSON-RPC error responses correctly', () => { - const errorResponse = { - jsonrpc: '2.0', - error: { - code: -32000, - message: 'Bad Request: No valid session ID provided', - }, - id: null, - }; - - expect(errorResponse.jsonrpc).toBe('2.0'); - expect(errorResponse.error.code).toBe(-32000); - expect(errorResponse.error.message).toContain('Bad Request'); - expect(errorResponse.id).toBe(null); - }); - - it('should format internal error responses correctly', () => { - const internalErrorResponse = { - jsonrpc: '2.0', - error: { - code: -32603, - message: 'Internal error', - }, - id: null, - }; - - expect(internalErrorResponse.jsonrpc).toBe('2.0'); - expect(internalErrorResponse.error.code).toBe(-32603); - expect(internalErrorResponse.error.message).toBe('Internal error'); - expect(internalErrorResponse.id).toBe(null); - }); - }); - - describe('Request/Response patterns', () => { - it('should handle typical MCP message structures', () => { - const initializeRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 1, - method: 'initialize', - params: { - protocolVersion: '2024-11-05', - capabilities: {}, - clientInfo: { name: 'test-client', version: '1.0.0' } - } - }; - - const toolsListRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 2, - method: 'tools/list', - params: {} - }; - - const toolsListResponse: JSONRPCMessage = { - jsonrpc: '2.0', - id: 2, - result: { - tools: [ - { - name: 'echo', - description: 'Echo the input', - inputSchema: { - type: 'object', - properties: { - text: { type: 'string' } - } - } - } - ] - } - }; - - expect(initializeRequest.method).toBe('initialize'); - expect(toolsListRequest.method).toBe('tools/list'); - expect(toolsListResponse.result).toBeDefined(); - expect(Array.isArray(toolsListResponse.result?.tools)).toBe(true); - }); - }); - - describe('HTTP header handling', () => { - it('should extract session ID from headers', () => { - const mockReq = { - headers: { - 'mcp-session-id': 'test-session-123', - 'content-type': 'application/json' - }, - body: {} - } as Partial; - - const sessionId = mockReq.headers!['mcp-session-id'] as string; - - expect(sessionId).toBe('test-session-123'); - }); - - it('should handle missing session ID in headers', () => { - const mockReq = { - headers: { - 'content-type': 'application/json' - }, - body: {} - } as Partial; - - const sessionId = mockReq.headers!['mcp-session-id'] as string | undefined; - - expect(sessionId).toBeUndefined(); - }); - }); -}); \ No newline at end of file diff --git a/external-oauth/mcp-server/src/handlers/shttp.ts b/external-oauth/mcp-server/src/handlers/shttp.ts deleted file mode 100644 index 1b89498..0000000 --- a/external-oauth/mcp-server/src/handlers/shttp.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import { Request, Response } from "express"; -import { getShttpTransport, isSessionOwnedBy, redisRelayToMcpServer, ServerRedisTransport, setSessionOwner, shutdownSession } from "../services/redisTransport.js"; -import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; -import { randomUUID } from "crypto"; -import { createMcpServer } from "../services/mcp.js"; -import { logger } from "../utils/logger.js"; - - -declare module "express-serve-static-core" { - interface Request { - /** - * Information about the validated access token, if the `requireBearerAuth` middleware was used. - */ - auth?: AuthInfo; - } -} - -function getUserIdFromAuth(auth?: AuthInfo): string | null { - return auth?.extra?.userId as string || null; -} - -// TODO: Document Streamable HTTP implementation choices: -// 1. STATEFUL: Requires clients to initialize sessions and track session IDs -// - First request must be 'initialize' without Mcp-Session-Id header -// - Server returns session ID, client must include it in subsequent requests -// - Alternative: Could implement STATELESS mode (each request independent) -// 2. SSE RESPONSES: Returns results via Server-Sent Events stream, not JSON responses -// - Requires Accept: application/json, text/event-stream header -// - Responses formatted as: event: message\ndata: {...} -// - Alternative: Could use JSON response mode (check StreamableHTTPServerTransport options) - -export async function handleStreamableHTTP(req: Request, res: Response) { - let shttpTransport: StreamableHTTPServerTransport | undefined = undefined; - - res.on('finish', async () => { - await shttpTransport?.close(); - }); - - const onsessionclosed = async (sessionId: string) => { - logger.info('Session closed callback triggered', { - sessionId, - userId: getUserIdFromAuth(req.auth) - }); - await shutdownSession(sessionId); - } - - try { - // Check for existing session ID - const sessionId = req.headers['mcp-session-id'] as string | undefined; - const userId = getUserIdFromAuth(req.auth); - - logger.debug('SHTTP request received', { - method: req.method, - sessionId, - userId, - hasAuth: !!req.auth, - authExtra: req.auth?.extra - }); - - // if no userid, return 401, we shouldn't get here ideally - if (!userId) { - logger.warning('Request without user ID', { - sessionId, - hasAuth: !!req.auth - }); - res.status(401).json({ - "jsonrpc": "2.0", - "error": { - "code": -32002, - "message": "User ID required" - } - }); - return; - } - - const isGetRequest = req.method === 'GET'; - - // incorrect session for the authed user, return 401 - if (sessionId) { - if (!(await isSessionOwnedBy(sessionId, userId))) { - logger.warning('Session ownership mismatch', { - sessionId, - userId, - requestMethod: req.method - }); - res.status(401).json({ - "jsonrpc": "2.0", - "error": { - "code": -32001, - "message": "Session not found or access denied" - } - }); - return; - } - // Reuse existing transport for owned session - logger.info('Reusing existing session', { - sessionId, - userId, - isGetRequest - }); - shttpTransport = await getShttpTransport(sessionId, onsessionclosed, isGetRequest); - } else if (isInitializeRequest(req.body)) { - // New initialization request - use JSON response mode - logger.debug('Processing initialize request', { - body: req.body, - userId, - headerSessionId: sessionId, // This is the sessionId from header (should be undefined for init) - isInitializeRequest: true - }); - - const onsessioninitialized = async (sessionId: string) => { - logger.info('Initializing new session', { - sessionId, - userId - }); - - const { server, cleanup: mcpCleanup } = createMcpServer(); - - const serverRedisTransport = new ServerRedisTransport(sessionId); - serverRedisTransport.onclose = mcpCleanup; - await server.connect(serverRedisTransport) - - // Set session ownership - await setSessionOwner(sessionId, userId); - - logger.info('Session initialized successfully', { - sessionId, - userId - }); - } - - const newSessionId = randomUUID(); - shttpTransport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => newSessionId, - onsessionclosed, - onsessioninitialized, - }); - shttpTransport.onclose = await redisRelayToMcpServer(newSessionId, shttpTransport); - } else { - // Invalid request - no session ID and not initialization request - logger.warning('Invalid request: no session ID and not initialization', { - hasSessionId: !!sessionId, - isInitRequest: false, - userId, - method: req.method - }); - res.status(400).json({ - "jsonrpc": "2.0", - "error": { - "code": -32600, - "message": "Invalid request method for existing session" - } - }); - return; - } - // Handle the request with existing transport - no need to reconnect - await shttpTransport.handleRequest(req, res, req.body); - } catch (error) { - logger.error('Error handling MCP request', error as Error, { - sessionId: req.headers['mcp-session-id'] as string | undefined, - method: req.method, - userId: getUserIdFromAuth(req.auth) - }); - - if (!res.headersSent) { - res.status(500).json({ - "jsonrpc": "2.0", - "error": { - "code": -32603, - "message": "Internal error during request processing" - } - }); - } - } -} diff --git a/external-oauth/mcp-server/src/redis.ts b/external-oauth/mcp-server/src/redis.ts deleted file mode 100644 index 3fc1a47..0000000 --- a/external-oauth/mcp-server/src/redis.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { createClient, SetOptions } from "@redis/client"; -import { logger } from "./utils/logger.js"; -import { REDIS_URL } from "./config.js"; - -/** - * Describes the Redis primitives we use in this application, to be able to mock - * them in tests (so we don't need to actually hit Redis). - */ -export interface RedisClient { - get(key: string): Promise; - set(key: string, value: string, options?: SetOptions): Promise; - getDel(key: string): Promise; - del(key: string): Promise; - expire(key: string, seconds: number): Promise; - lpush(key: string, ...values: string[]): Promise; - lrange(key: string, start: number, stop: number): Promise; - connect(): Promise; - on(event: string, callback: (error: Error) => void): void; - options?: { url: string }; - exists(key: string): Promise; - numsub(key: string): Promise; - - /** - * Creates a pub/sub subscription. Returns a cleanup function to unsubscribe. - * Handles Redis client duplication and error handling internally. - */ - createSubscription( - channel: string, - onMessage: (message: string) => void, - onError: (error: Error) => void, - ): Promise<() => Promise>; - - /** - * Publishes a message to a channel. - */ - publish(channel: string, message: string): Promise; -} - -export class RedisClientImpl implements RedisClient { - private redis = createClient({ - url: REDIS_URL, - password: process.env.REDIS_PASSWORD, - socket: { - tls: process.env.REDIS_TLS === "1", - ca: process.env.REDIS_TLS_CA, - } - }); - - constructor() { - this.redis.on("error", (error) => - logger.error("Redis client error", error as Error), - ); - } - - async numsub(key: string): Promise { - const subs = await this.redis.pubSubNumSub(key); - return subs[key] || 0; - } - - async get(key: string): Promise { - return await this.redis.get(key); - } - - async getDel(key: string): Promise { - return await this.redis.getDel(key); - } - - async set(key: string, value: string, options?: SetOptions): Promise { - return await this.redis.set( - key, - value, - options, - ); - } - - async del(key: string): Promise { - return await this.redis.del(key); - } - - async expire(key: string, seconds: number): Promise { - return await this.redis.expire(key, seconds); - } - - async lpush(key: string, ...values: string[]): Promise { - return await this.redis.lPush(key, values); - } - - async lrange(key: string, start: number, stop: number): Promise { - return await this.redis.lRange(key, start, stop); - } - - async connect(): Promise { - await this.redis.connect(); - } - - on(event: string, callback: (error: Error) => void): void { - this.redis.on(event, callback); - } - - get options() { - return { url: REDIS_URL }; - } - - async createSubscription( - channel: string, - onMessage: (message: string) => void, - onError: (error: Error) => void, - ): Promise<() => Promise> { - const subscriber = this.redis.duplicate(); - subscriber.on("error", (error) => { - onError(error); - }); - - await subscriber.connect(); - - await subscriber.subscribe(channel, (message) => { - onMessage(message); - }); - - return async () => { - await subscriber.disconnect(); - }; - } - - async publish(channel: string, message: string): Promise { - await this.redis.publish(channel, message); - } - - async exists(key: string): Promise { - const result = await this.redis.exists(key); - return result > 0; - } -} - -// Export a mutable reference that can be swapped in tests -export let redisClient: RedisClient = new RedisClientImpl(); - -// Function to replace the Redis client (used in tests) -export function setRedisClient(client: RedisClient) { - redisClient = client; -} - -export class MockRedisClient implements RedisClient { - options = { url: "redis://localhost:6379" }; - private store = new Map(); - private lists = new Map(); - public subscribers = new Map void)[]>(); // Public for testing access - private errorCallbacks = new Map void)[]>(); - - async get(key: string): Promise { - return this.store.get(key) ?? null; - } - - async getDel(key: string): Promise { - const value = this.store.get(key) ?? null; - this.store.delete(key); - return value; - } - - async set(key: string, value: string, options?: SetOptions): Promise { - let oldValue: string | null = null; - if (options?.GET) { - oldValue = this.store.get(key) ?? null; - } - this.store.set(key, value); - return oldValue; - } - - async del(key: string): Promise { - let deleted = 0; - if (this.store.has(key)) { - this.store.delete(key); - deleted++; - } - if (this.lists.has(key)) { - this.lists.delete(key); - deleted++; - } - return deleted; - } - - async expire(key: string, _seconds: number): Promise { - // Mock implementation - just return true if key exists - return this.store.has(key) || this.lists.has(key); - } - - async lpush(key: string, ...values: string[]): Promise { - const list = this.lists.get(key) || []; - list.unshift(...values); - this.lists.set(key, list); - return list.length; - } - - async lrange(key: string, start: number, stop: number): Promise { - const list = this.lists.get(key) || []; - if (stop === -1) { - return list.slice(start); - } - return list.slice(start, stop + 1); - } - - async connect(): Promise { - // No-op in mock - } - - on(event: string, callback: (error: Error) => void): void { - if (event === "error") { - const callbacks = this.errorCallbacks.get("global") ?? []; - callbacks.push(callback); - this.errorCallbacks.set("global", callbacks); - } - } - - async createSubscription( - channel: string, - onMessage: (message: string) => void, - onError: (error: Error) => void, - ): Promise<() => Promise> { - const callbacks = this.subscribers.get(channel) ?? []; - callbacks.push(onMessage); - this.subscribers.set(channel, callbacks); - - const errorCallbacks = this.errorCallbacks.get(channel) ?? []; - errorCallbacks.push(onError); - this.errorCallbacks.set(channel, errorCallbacks); - - return async () => { - const callbacks = this.subscribers.get(channel) ?? []; - const index = callbacks.indexOf(onMessage); - if (index !== -1) { - callbacks.splice(index, 1); - } - this.subscribers.set(channel, callbacks); - - if (onError) { - const errorCallbacks = this.errorCallbacks.get(channel) ?? []; - const errorIndex = errorCallbacks.indexOf(onError); - if (errorIndex !== -1) { - errorCallbacks.splice(errorIndex, 1); - } - this.errorCallbacks.set(channel, errorCallbacks); - } - }; - } - - async publish(channel: string, message: string): Promise { - const callbacks = this.subscribers.get(channel) ?? []; - for (const callback of callbacks) { - try { - callback(message); - } catch (error) { - const errorCallbacks = this.errorCallbacks.get(channel) ?? []; - for (const errorCallback of errorCallbacks) { - errorCallback(error as Error); - } - } - } - } - - async exists(key: string): Promise { - return this.store.has(key) || this.lists.has(key); - } - - async numsub(key: string): Promise { - return (this.subscribers.get(key) || []).length; - } - - clear() { - this.store.clear(); - this.lists.clear(); - this.subscribers.clear(); - this.errorCallbacks.clear(); - } - - // Helper method for tests to simulate Redis errors - simulateError(error: Error, channel?: string): void { - if (channel) { - const callbacks = this.errorCallbacks.get(channel) ?? []; - for (const callback of callbacks) { - callback(error); - } - } else { - const callbacks = this.errorCallbacks.get("global") ?? []; - for (const callback of callbacks) { - callback(error); - } - } - } -} diff --git a/external-oauth/mcp-server/src/services/mcp.ts b/external-oauth/mcp-server/src/services/mcp.ts deleted file mode 100644 index 39192b5..0000000 --- a/external-oauth/mcp-server/src/services/mcp.ts +++ /dev/null @@ -1,686 +0,0 @@ -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; -import { - CallToolRequestSchema, - CompleteRequestSchema, - CreateMessageRequest, - CreateMessageResultSchema, - GetPromptRequestSchema, - ListPromptsRequestSchema, - ListResourcesRequestSchema, - ListResourceTemplatesRequestSchema, - ListToolsRequestSchema, - LoggingLevel, - ReadResourceRequestSchema, - Resource, - SetLevelRequestSchema, - SubscribeRequestSchema, - Tool, - UnsubscribeRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; -import { z } from "zod"; -import { zodToJsonSchema } from "zod-to-json-schema"; - -type ToolInput = { - type: "object"; - properties?: Record; - required?: string[]; -}; - -/* Input schemas for tools implemented in this server */ -const EchoSchema = z.object({ - message: z.string().describe("Message to echo"), -}); - -const AddSchema = z.object({ - a: z.number().describe("First number"), - b: z.number().describe("Second number"), -}); - -const LongRunningOperationSchema = z.object({ - duration: z - .number() - .default(10) - .describe("Duration of the operation in seconds"), - steps: z.number().default(5).describe("Number of steps in the operation"), -}); - -const SampleLLMSchema = z.object({ - prompt: z.string().describe("The prompt to send to the LLM"), - maxTokens: z - .number() - .default(100) - .describe("Maximum number of tokens to generate"), -}); - -// Example completion values -const EXAMPLE_COMPLETIONS = { - style: ["casual", "formal", "technical", "friendly"], - temperature: ["0", "0.5", "0.7", "1.0"], - resourceId: ["1", "2", "3", "4", "5"], -}; - -const GetTinyImageSchema = z.object({}); - -const AnnotatedMessageSchema = z.object({ - messageType: z - .enum(["error", "success", "debug"]) - .describe("Type of message to demonstrate different annotation patterns"), - includeImage: z - .boolean() - .default(false) - .describe("Whether to include an example image"), -}); - -const GetResourceReferenceSchema = z.object({ - resourceId: z - .number() - .min(1) - .max(100) - .describe("ID of the resource to reference (1-100)"), -}); - -enum ToolName { - ECHO = "echo", - ADD = "add", - LONG_RUNNING_OPERATION = "longRunningOperation", - SAMPLE_LLM = "sampleLLM", - GET_TINY_IMAGE = "getTinyImage", - ANNOTATED_MESSAGE = "annotatedMessage", - GET_RESOURCE_REFERENCE = "getResourceReference", -} - -enum PromptName { - SIMPLE = "simple_prompt", - COMPLEX = "complex_prompt", - RESOURCE = "resource_prompt", -} - -interface McpServerWrapper { - server: Server; - cleanup: () => void; -} - -export const createMcpServer = (): McpServerWrapper => { - const server = new Server( - { - name: "example-servers/feature-reference", - version: "1.0.0", - }, - { - capabilities: { - prompts: {}, - resources: { subscribe: true }, - tools: {}, - logging: {}, - completions: {}, - }, - } - ); - - const subscriptions: Set = new Set(); - - // Set up update interval for subscribed resources - const subsUpdateInterval = setInterval(() => { - for (const uri of subscriptions) { - server.notification({ - method: "notifications/resources/updated", - params: { uri }, - }); - } - }, 10000); - - let logLevel: LoggingLevel = "debug"; - const messages = [ - { level: "debug", data: "Debug-level message" }, - { level: "info", data: "Info-level message" }, - { level: "notice", data: "Notice-level message" }, - { level: "warning", data: "Warning-level message" }, - { level: "error", data: "Error-level message" }, - { level: "critical", data: "Critical-level message" }, - { level: "alert", data: "Alert level-message" }, - { level: "emergency", data: "Emergency-level message" }, - ]; - - const isMessageIgnored = (level: LoggingLevel): boolean => { - const currentLevel = messages.findIndex((msg) => logLevel === msg.level); - const messageLevel = messages.findIndex((msg) => level === msg.level); - return messageLevel < currentLevel; - }; - - // Set up update interval for random log messages - const logsUpdateInterval = setInterval(() => { - const message = { - method: "notifications/message", - params: messages[Math.floor(Math.random() * messages.length)], - }; - if (!isMessageIgnored(message.params.level as LoggingLevel)) - server.notification(message); - }, 20000); - - - // Set up update interval for stderr messages - const stdErrUpdateInterval = setInterval(() => { - const shortTimestamp = new Date().toLocaleTimeString([], { - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }); - server.notification({ - method: "notifications/stderr", - params: { content: `${shortTimestamp}: A stderr message` }, - }); - }, 30000); - - // Helper method to request sampling from client - const requestSampling = async ( - context: string, - uri: string, - maxTokens: number = 100 - ) => { - const request: CreateMessageRequest = { - method: "sampling/createMessage", - params: { - messages: [ - { - role: "user", - content: { - type: "text", - text: `Resource ${uri} context: ${context}`, - }, - }, - ], - systemPrompt: "You are a helpful test server.", - maxTokens, - temperature: 0.7, - includeContext: "thisServer", - }, - }; - - return await server.request(request, CreateMessageResultSchema); - }; - - const ALL_RESOURCES: Resource[] = Array.from({ length: 100 }, (_, i) => { - const uri = `test://static/resource/${i + 1}`; - if (i % 2 === 0) { - return { - uri, - name: `Resource ${i + 1}`, - mimeType: "text/plain", - text: `Resource ${i + 1}: This is a plaintext resource`, - }; - } else { - const buffer = Buffer.from(`Resource ${i + 1}: This is a base64 blob`); - return { - uri, - name: `Resource ${i + 1}`, - mimeType: "application/octet-stream", - blob: buffer.toString("base64"), - }; - } - }); - - const PAGE_SIZE = 10; - - server.setRequestHandler(ListResourcesRequestSchema, async (request) => { - const cursor = request.params?.cursor; - let startIndex = 0; - - if (cursor) { - const decodedCursor = parseInt(atob(cursor), 10); - if (!isNaN(decodedCursor)) { - startIndex = decodedCursor; - } - } - - const endIndex = Math.min(startIndex + PAGE_SIZE, ALL_RESOURCES.length); - const resources = ALL_RESOURCES.slice(startIndex, endIndex); - - let nextCursor: string | undefined; - if (endIndex < ALL_RESOURCES.length) { - nextCursor = btoa(endIndex.toString()); - } - - return { - resources, - nextCursor, - }; - }); - - server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => { - return { - resourceTemplates: [ - { - uriTemplate: "test://static/resource/{id}", - name: "Static Resource", - description: "A static resource with a numeric ID", - }, - ], - }; - }); - - server.setRequestHandler(ReadResourceRequestSchema, async (request) => { - const uri = request.params.uri; - - if (uri.startsWith("test://static/resource/")) { - const index = parseInt(uri.split("/").pop() ?? "", 10) - 1; - if (index >= 0 && index < ALL_RESOURCES.length) { - const resource = ALL_RESOURCES[index]; - return { - contents: [resource], - }; - } - } - - throw new Error(`Unknown resource: ${uri}`); - }); - - server.setRequestHandler(SubscribeRequestSchema, async (request) => { - const { uri } = request.params; - subscriptions.add(uri); - - // Request sampling from client when someone subscribes - await requestSampling("A new subscription was started", uri); - return {}; - }); - - server.setRequestHandler(UnsubscribeRequestSchema, async (request) => { - subscriptions.delete(request.params.uri); - return {}; - }); - - server.setRequestHandler(ListPromptsRequestSchema, async () => { - return { - prompts: [ - { - name: PromptName.SIMPLE, - description: "A prompt without arguments", - }, - { - name: PromptName.COMPLEX, - description: "A prompt with arguments", - arguments: [ - { - name: "temperature", - description: "Temperature setting", - required: true, - }, - { - name: "style", - description: "Output style", - required: false, - }, - ], - }, - { - name: PromptName.RESOURCE, - description: "A prompt that includes an embedded resource reference", - arguments: [ - { - name: "resourceId", - description: "Resource ID to include (1-100)", - required: true, - }, - ], - }, - ], - }; - }); - - server.setRequestHandler(GetPromptRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - if (name === PromptName.SIMPLE) { - return { - messages: [ - { - role: "user", - content: { - type: "text", - text: "This is a simple prompt without arguments.", - }, - }, - ], - }; - } - - if (name === PromptName.COMPLEX) { - return { - messages: [ - { - role: "user", - content: { - type: "text", - text: `This is a complex prompt with arguments: temperature=${args?.temperature}, style=${args?.style}`, - }, - }, - { - role: "assistant", - content: { - type: "text", - text: "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?", - }, - }, - { - role: "user", - content: { - type: "image", - data: MCP_TINY_IMAGE, - mimeType: "image/png", - }, - }, - ], - }; - } - - if (name === PromptName.RESOURCE) { - const resourceId = parseInt(args?.resourceId as string, 10); - if (isNaN(resourceId) || resourceId < 1 || resourceId > 100) { - throw new Error( - `Invalid resourceId: ${args?.resourceId}. Must be a number between 1 and 100.` - ); - } - - const resourceIndex = resourceId - 1; - const resource = ALL_RESOURCES[resourceIndex]; - - return { - messages: [ - { - role: "user", - content: { - type: "text", - text: `This prompt includes Resource ${resourceId}. Please analyze the following resource:`, - }, - }, - { - role: "user", - content: { - type: "resource", - resource: resource, - }, - }, - ], - }; - } - - throw new Error(`Unknown prompt: ${name}`); - }); - - server.setRequestHandler(ListToolsRequestSchema, async () => { - const tools: Tool[] = [ - { - name: ToolName.ECHO, - description: "Echoes back the input", - inputSchema: zodToJsonSchema(EchoSchema) as ToolInput, - }, - { - name: ToolName.ADD, - description: "Adds two numbers", - inputSchema: zodToJsonSchema(AddSchema) as ToolInput, - }, - { - name: ToolName.LONG_RUNNING_OPERATION, - description: - "Demonstrates a long running operation with progress updates", - inputSchema: zodToJsonSchema(LongRunningOperationSchema) as ToolInput, - }, - { - name: ToolName.SAMPLE_LLM, - description: "Samples from an LLM using MCP's sampling feature", - inputSchema: zodToJsonSchema(SampleLLMSchema) as ToolInput, - }, - { - name: ToolName.GET_TINY_IMAGE, - description: "Returns the MCP_TINY_IMAGE", - inputSchema: zodToJsonSchema(GetTinyImageSchema) as ToolInput, - }, - { - name: ToolName.ANNOTATED_MESSAGE, - description: - "Demonstrates how annotations can be used to provide metadata about content", - inputSchema: zodToJsonSchema(AnnotatedMessageSchema) as ToolInput, - }, - { - name: ToolName.GET_RESOURCE_REFERENCE, - description: - "Returns a resource reference that can be used by MCP clients", - inputSchema: zodToJsonSchema(GetResourceReferenceSchema) as ToolInput, - }, - ]; - - return { tools }; - }); - - server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - if (name === ToolName.ECHO) { - const validatedArgs = EchoSchema.parse(args); - return { - content: [{ type: "text", text: `Echo: ${validatedArgs.message}` }], - }; - } - - if (name === ToolName.ADD) { - const validatedArgs = AddSchema.parse(args); - const sum = validatedArgs.a + validatedArgs.b; - return { - content: [ - { - type: "text", - text: `The sum of ${validatedArgs.a} and ${validatedArgs.b} is ${sum}.`, - }, - ], - }; - } - - if (name === ToolName.LONG_RUNNING_OPERATION) { - const validatedArgs = LongRunningOperationSchema.parse(args); - const { duration, steps } = validatedArgs; - const stepDuration = duration / steps; - const progressToken = request.params._meta?.progressToken; - - for (let i = 1; i < steps + 1; i++) { - await new Promise((resolve) => - setTimeout(resolve, stepDuration * 1000) - ); - - if (progressToken !== undefined) { - await server.notification({ - method: "notifications/progress", - params: { - progress: i, - total: steps, - progressToken, - }, - }); - } - } - - return { - content: [ - { - type: "text", - text: `Long running operation completed. Duration: ${duration} seconds, Steps: ${steps}.`, - }, - ], - }; - } - - if (name === ToolName.SAMPLE_LLM) { - const validatedArgs = SampleLLMSchema.parse(args); - const { prompt, maxTokens } = validatedArgs; - - const result = await requestSampling( - prompt, - ToolName.SAMPLE_LLM, - maxTokens - ); - return { - content: [ - { type: "text", text: `LLM sampling result: ${result.content.text}` }, - ], - }; - } - - if (name === ToolName.GET_TINY_IMAGE) { - GetTinyImageSchema.parse(args); - return { - content: [ - { - type: "text", - text: "This is a tiny image:", - }, - { - type: "image", - data: MCP_TINY_IMAGE, - mimeType: "image/png", - }, - { - type: "text", - text: "The image above is the MCP tiny image.", - }, - ], - }; - } - - if (name === ToolName.GET_RESOURCE_REFERENCE) { - const validatedArgs = GetResourceReferenceSchema.parse(args); - const resourceId = validatedArgs.resourceId; - - const resourceIndex = resourceId - 1; - if (resourceIndex < 0 || resourceIndex >= ALL_RESOURCES.length) { - throw new Error(`Resource with ID ${resourceId} does not exist`); - } - - const resource = ALL_RESOURCES[resourceIndex]; - - return { - content: [ - { - type: "text", - text: `Returning resource reference for Resource ${resourceId}:`, - }, - { - type: "resource", - resource: resource, - }, - { - type: "text", - text: `You can access this resource using the URI: ${resource.uri}`, - }, - ], - }; - } - - if (name === ToolName.ANNOTATED_MESSAGE) { - const { messageType, includeImage } = AnnotatedMessageSchema.parse(args); - - const content = []; - - // Main message with different priorities/audiences based on type - if (messageType === "error") { - content.push({ - type: "text", - text: "Error: Operation failed", - annotations: { - priority: 1.0, // Errors are highest priority - audience: ["user", "assistant"], // Both need to know about errors - }, - }); - } else if (messageType === "success") { - content.push({ - type: "text", - text: "Operation completed successfully", - annotations: { - priority: 0.7, // Success messages are important but not critical - audience: ["user"], // Success mainly for user consumption - }, - }); - } else if (messageType === "debug") { - content.push({ - type: "text", - text: "Debug: Cache hit ratio 0.95, latency 150ms", - annotations: { - priority: 0.3, // Debug info is low priority - audience: ["assistant"], // Technical details for assistant - }, - }); - } - - // Optional image with its own annotations - if (includeImage) { - content.push({ - type: "image", - data: MCP_TINY_IMAGE, - mimeType: "image/png", - annotations: { - priority: 0.5, - audience: ["user"], // Images primarily for user visualization - }, - }); - } - - return { content }; - } - - throw new Error(`Unknown tool: ${name}`); - }); - - server.setRequestHandler(CompleteRequestSchema, async (request) => { - const { ref, argument } = request.params; - - if (ref.type === "ref/resource") { - const resourceId = ref.uri.split("/").pop(); - if (!resourceId) return { completion: { values: [] } }; - - // Filter resource IDs that start with the input value - const values = EXAMPLE_COMPLETIONS.resourceId.filter((id) => - id.startsWith(argument.value) - ); - return { completion: { values, hasMore: false, total: values.length } }; - } - - if (ref.type === "ref/prompt") { - // Handle completion for prompt arguments - const completions = - EXAMPLE_COMPLETIONS[argument.name as keyof typeof EXAMPLE_COMPLETIONS]; - if (!completions) return { completion: { values: [] } }; - - const values = completions.filter((value) => - value.startsWith(argument.value) - ); - return { completion: { values, hasMore: false, total: values.length } }; - } - - throw new Error(`Unknown reference type`); - }); - - server.setRequestHandler(SetLevelRequestSchema, async (request) => { - const { level } = request.params; - logLevel = level; - - // Demonstrate different log levels - await server.notification({ - method: "notifications/message", - params: { - level: "debug", - logger: "test-server", - data: `Logging level set to: ${logLevel}`, - }, - }); - - return {}; - }); - - const cleanup = async () => { - if (subsUpdateInterval) clearInterval(subsUpdateInterval); - if (logsUpdateInterval) clearInterval(logsUpdateInterval); - if (stdErrUpdateInterval) clearInterval(stdErrUpdateInterval); - }; - - return { server, cleanup }; -}; - -const MCP_TINY_IMAGE = - "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"; diff --git a/external-oauth/mcp-server/src/services/redisTransport.integration.test.ts b/external-oauth/mcp-server/src/services/redisTransport.integration.test.ts deleted file mode 100644 index 3fcc778..0000000 --- a/external-oauth/mcp-server/src/services/redisTransport.integration.test.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { jest } from '@jest/globals'; -import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; -import { MockRedisClient, setRedisClient } from '../redis.js'; -import { - ServerRedisTransport, - redisRelayToMcpServer, - shutdownSession -} from './redisTransport.js'; -import { createMcpServer } from './mcp.js'; -import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; - -describe('Redis Transport Integration', () => { - let mockRedis: MockRedisClient; - - beforeEach(() => { - mockRedis = new MockRedisClient(); - setRedisClient(mockRedis); - jest.resetAllMocks(); - }); - - afterEach(() => { - mockRedis.clear(); - }); - - describe('MCP Initialization Flow', () => { - const sessionId = 'test-init-session'; - - it('should relay initialization request from client to server through Redis', async () => { - // 1. Start the server listening to Redis - const { server, cleanup: serverCleanup } = createMcpServer(); - const serverTransport = new ServerRedisTransport(sessionId); - serverTransport.onclose = serverCleanup; - await server.connect(serverTransport); - - // 2. Create a mock client transport (simulating the streamable HTTP client side) - const mockClientTransport: Transport = { - onmessage: undefined, - onclose: undefined, - onerror: undefined, - send: jest.fn(() => Promise.resolve()), - close: jest.fn(() => Promise.resolve()), - start: jest.fn(() => Promise.resolve()) - }; - - // 3. Set up the Redis relay (this is what happens in the HTTP handler) - const cleanup = await redisRelayToMcpServer(sessionId, mockClientTransport); - - // Track messages received by server - const serverReceivedMessages: JSONRPCMessage[] = []; - const originalServerOnMessage = serverTransport.onmessage; - serverTransport.onmessage = (message, extra) => { - serverReceivedMessages.push(message); - originalServerOnMessage?.(message, extra); - }; - - // 4. Simulate client sending initialization request - const initMessage: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'init-1', - method: 'initialize', - params: { - protocolVersion: '2024-11-05', - capabilities: {}, - clientInfo: { - name: 'test-client', - version: '1.0.0' - } - } - }; - - // Trigger the client transport onmessage (simulates HTTP request) - mockClientTransport.onmessage?.(initMessage); - - // Wait for message to be relayed through Redis - await new Promise(resolve => setTimeout(resolve, 50)); - - // 5. Verify server received the init message - expect(serverReceivedMessages).toHaveLength(1); - expect(serverReceivedMessages[0]).toMatchObject({ - jsonrpc: '2.0', - id: 'init-1', - method: 'initialize' - }); - - // 6. Simulate server responding (this should get relayed back to client) - const initResponse: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'init-1', - result: { - protocolVersion: '2024-11-05', - capabilities: { - tools: {}, - prompts: {}, - resources: {} - }, - serverInfo: { - name: 'example-server', - version: '1.0.0' - } - } - }; - - await serverTransport.send(initResponse, { relatedRequestId: 'init-1' }); - - // Wait for response to be relayed back - await new Promise(resolve => setTimeout(resolve, 50)); - - // 7. Verify client transport received the response - expect(mockClientTransport.send).toHaveBeenCalledWith( - expect.objectContaining({ - jsonrpc: '2.0', - id: 'init-1', - result: expect.objectContaining({ - protocolVersion: '2024-11-05', - serverInfo: expect.objectContaining({ - name: 'example-server' - }) - }) - }), - { relatedRequestId: 'init-1' } - ); - - // Cleanup - await cleanup(); - await shutdownSession(sessionId); - serverCleanup(); // Clean up MCP server intervals - - // Ensure server transport is closed - await serverTransport.close(); - - await new Promise(resolve => setTimeout(resolve, 10)); - }); - - it('should handle tools/list request through Redis relay', async () => { - // Set up server and mock client - const { server, cleanup: serverCleanup } = createMcpServer(); - const serverTransport = new ServerRedisTransport(sessionId); - serverTransport.onclose = serverCleanup; - await server.connect(serverTransport); - - const mockClientTransport: Transport = { - onmessage: undefined, - onclose: undefined, - onerror: undefined, - send: jest.fn(() => Promise.resolve()), - close: jest.fn(() => Promise.resolve()), - start: jest.fn(() => Promise.resolve()) - }; - - const cleanup = await redisRelayToMcpServer(sessionId, mockClientTransport); - - // Send tools/list request - const toolsListMessage: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'tools-1', - method: 'tools/list', - params: {} - }; - - mockClientTransport.onmessage?.(toolsListMessage); - - // Wait for processing and response - await new Promise(resolve => setTimeout(resolve, 100)); - - // Verify client received a response with tools - expect(mockClientTransport.send).toHaveBeenCalledWith( - expect.objectContaining({ - jsonrpc: '2.0', - id: 'tools-1', - result: expect.objectContaining({ - tools: expect.any(Array) - }) - }), - undefined - ); - - // Cleanup - await cleanup(); - await shutdownSession(sessionId); - serverCleanup(); // Clean up MCP server intervals - - // Ensure server transport is closed - await serverTransport.close(); - - await new Promise(resolve => setTimeout(resolve, 10)); - }); - - it('should handle notifications through Redis relay', async () => { - // Set up server and mock client - const { server, cleanup: serverCleanup } = createMcpServer(); - const serverTransport = new ServerRedisTransport(sessionId); - serverTransport.onclose = serverCleanup; - await server.connect(serverTransport); - - const mockClientTransport: Transport = { - onmessage: undefined, - onclose: undefined, - onerror: undefined, - send: jest.fn(() => Promise.resolve()), - close: jest.fn(() => Promise.resolve()), - start: jest.fn(() => Promise.resolve()) - }; - - const cleanup = await redisRelayToMcpServer(sessionId, mockClientTransport); - - // Set up notification subscription manually since notifications don't have an id - const notificationChannel = `mcp:shttp:toclient:${sessionId}:__GET_stream`; - const notificationCleanup = await mockRedis.createSubscription(notificationChannel, async (redisMessageJson) => { - const redisMessage = JSON.parse(redisMessageJson); - if (redisMessage.type === 'mcp') { - await mockClientTransport.send(redisMessage.message, redisMessage.options); - } - }, (error) => { - mockClientTransport.onerror?.(error); - }); - - // Send a notification from server (notifications don't have an id) - const notification: JSONRPCMessage = { - jsonrpc: '2.0', - method: 'notifications/message', - params: { - level: 'info', - logger: 'test', - data: 'Test notification' - } - }; - - await serverTransport.send(notification); - - // Wait for notification to be delivered - await new Promise(resolve => setTimeout(resolve, 50)); - - // Verify client received the notification - expect(mockClientTransport.send).toHaveBeenCalledWith( - expect.objectContaining({ - jsonrpc: '2.0', - method: 'notifications/message', - params: expect.objectContaining({ - level: 'info', - data: 'Test notification' - }) - }), - undefined - ); - - // Cleanup notification subscription - await notificationCleanup(); - - // Cleanup - await cleanup(); - await shutdownSession(sessionId); - serverCleanup(); // Clean up MCP server intervals - - // Ensure server transport is closed - await serverTransport.close(); - - await new Promise(resolve => setTimeout(resolve, 10)); - }); - - it('should not create response subscriptions for notifications', async () => { - const mockClientTransport: Transport = { - onmessage: undefined, - onclose: undefined, - onerror: undefined, - send: jest.fn(() => Promise.resolve()), - close: jest.fn(() => Promise.resolve()), - start: jest.fn(() => Promise.resolve()) - }; - - const cleanup = await redisRelayToMcpServer(sessionId, mockClientTransport); - - // Send a notification (no id field) - const notification: JSONRPCMessage = { - jsonrpc: '2.0', - method: 'notifications/initialized', - params: {} - }; - - mockClientTransport.onmessage?.(notification); - - // Wait for processing - await new Promise(resolve => setTimeout(resolve, 50)); - - // Should not create any response channel subscriptions for notifications - // (we can't easily test this directly, but we can ensure no errors occur) - expect(mockClientTransport.send).not.toHaveBeenCalled(); - - await cleanup(); - }); - }); -}); \ No newline at end of file diff --git a/external-oauth/mcp-server/src/services/redisTransport.test.ts b/external-oauth/mcp-server/src/services/redisTransport.test.ts deleted file mode 100644 index 60e7e33..0000000 --- a/external-oauth/mcp-server/src/services/redisTransport.test.ts +++ /dev/null @@ -1,536 +0,0 @@ -import { jest } from '@jest/globals'; -import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; -import { MockRedisClient, setRedisClient } from '../redis.js'; -import { - ServerRedisTransport, - redisRelayToMcpServer, - isLive, - shutdownSession, - setSessionOwner, - getSessionOwner, - validateSessionOwnership, - isSessionOwnedBy -} from './redisTransport.js'; -import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; - -describe('Redis Transport', () => { - let mockRedis: MockRedisClient; - - beforeEach(() => { - mockRedis = new MockRedisClient(); - setRedisClient(mockRedis); - jest.resetAllMocks(); - }); - - afterEach(() => { - // Clear all Redis data and subscriptions - mockRedis.clear(); - }); - - describe('ServerRedisTransport', () => { - let transport: ServerRedisTransport; - const sessionId = 'test-session-123'; - - beforeEach(() => { - transport = new ServerRedisTransport(sessionId); - }); - - afterEach(async () => { - if (transport) { - await transport.close(); - } - }); - - it('should create transport with session ID', () => { - expect(transport).toBeInstanceOf(ServerRedisTransport); - }); - - it('should send response messages to request-specific channels', async () => { - const responseMessage: JSONRPCMessage = { - jsonrpc: '2.0', - id: 123, - result: { data: 'test response' } - }; - - const mockSubscriber = jest.fn(); - await mockRedis.createSubscription( - `mcp:shttp:toclient:${sessionId}:123`, - mockSubscriber, - jest.fn() - ); - - await transport.send(responseMessage, { relatedRequestId: 123 }); - - expect(mockSubscriber).toHaveBeenCalledWith( - JSON.stringify({ - type: 'mcp', - message: responseMessage, - options: { relatedRequestId: 123 } - }) - ); - }); - - it('should send notification messages to notification channel', async () => { - const notificationMessage: JSONRPCMessage = { - jsonrpc: '2.0', - method: 'notifications/message', - params: { message: 'test notification' } - }; - - const mockSubscriber = jest.fn(); - await mockRedis.createSubscription( - `mcp:shttp:toclient:${sessionId}:__GET_stream`, - mockSubscriber, - jest.fn() - ); - - await transport.send(notificationMessage); - - expect(mockSubscriber).toHaveBeenCalledWith( - JSON.stringify({ - type: 'mcp', - message: notificationMessage, - options: undefined - }) - ); - }); - - it('should handle close gracefully', async () => { - const onCloseMock = jest.fn(); - transport.onclose = onCloseMock; - - await transport.close(); - - expect(onCloseMock).toHaveBeenCalled(); - }); - - it('should respond to shutdown control messages', async () => { - await transport.start(); - - const onCloseMock = jest.fn(); - transport.onclose = onCloseMock; - - // Send a shutdown control message - await shutdownSession(sessionId); - - // Wait for async processing - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(onCloseMock).toHaveBeenCalled(); - }); - - it('should receive MCP messages from clients and call onmessage', async () => { - const onMessageMock = jest.fn(); - transport.onmessage = onMessageMock; - - await transport.start(); - - // Simulate client sending a message to server - const clientMessage: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'test-req', - method: 'tools/list', - params: {} - }; - - await mockRedis.publish( - `mcp:shttp:toserver:${sessionId}`, - JSON.stringify({ - type: 'mcp', - message: clientMessage, - extra: { authInfo: { token: 'test-token', clientId: 'test-client', scopes: [] } } - }) - ); - - // Wait for async processing - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(onMessageMock).toHaveBeenCalledWith( - clientMessage, - { authInfo: { token: 'test-token', clientId: 'test-client', scopes: [] } } - ); - - await transport.close(); - }); - }); - - - describe('redisRelayToMcpServer', () => { - let mockTransport: Transport; - const sessionId = 'test-session-456'; - - beforeEach(() => { - mockTransport = { - onmessage: undefined, - onclose: undefined, - onerror: undefined, - send: jest.fn(() => Promise.resolve()), - close: jest.fn(() => Promise.resolve()), - start: jest.fn(() => Promise.resolve()) - }; - }); - - it('should set up message relay from transport to server', async () => { - const cleanup = await redisRelayToMcpServer(sessionId, mockTransport); - - // Simulate a message from the transport - const requestMessage: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'req-123', - method: 'tools/list', - params: {} - }; - - // Trigger the onmessage handler - mockTransport.onmessage?.(requestMessage, { authInfo: { token: 'test-token', clientId: 'test-client', scopes: [] } }); - - // Wait a bit for async processing - await new Promise(resolve => setTimeout(resolve, 10)); - - // Check that message was published to server channel - const serverSubscriber = jest.fn(); - await mockRedis.createSubscription( - `mcp:shttp:toserver:${sessionId}`, - serverSubscriber, - jest.fn() - ); - - // The message should have been published - expect(mockRedis.numsub(`mcp:shttp:toserver:${sessionId}`)).resolves.toBe(1); - - await cleanup(); - }); - - it('should subscribe to response channel for request messages', async () => { - const cleanup = await redisRelayToMcpServer(sessionId, mockTransport); - - const requestMessage: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'req-456', - method: 'tools/call', - params: { name: 'echo', arguments: { text: 'hello' } } - }; - - // Trigger the onmessage handler - mockTransport.onmessage?.(requestMessage, { authInfo: { token: 'test-token', clientId: 'test-client', scopes: [] } }); - - // Wait for subscription setup - await new Promise(resolve => setTimeout(resolve, 10)); - - // Now simulate a response from the server - const responseMessage: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'req-456', - result: { content: [{ type: 'text', text: 'hello' }] } - }; - - await mockRedis.publish( - `mcp:shttp:toclient:${sessionId}:req-456`, - JSON.stringify({ - type: 'mcp', - message: responseMessage, - options: undefined - }) - ); - - // Check that the response was sent back to the transport - expect(mockTransport.send).toHaveBeenCalledWith(responseMessage, undefined); - - await cleanup(); - }); - - it('should not subscribe for notification messages (no id)', async () => { - const cleanup = await redisRelayToMcpServer(sessionId, mockTransport); - - const notificationMessage: JSONRPCMessage = { - jsonrpc: '2.0', - method: 'notifications/message', - params: { message: 'test' } - }; - - // Trigger the onmessage handler - mockTransport.onmessage?.(notificationMessage); - - // Wait a bit - await new Promise(resolve => setTimeout(resolve, 10)); - - // Should not create any response channel subscriptions - expect(await mockRedis.numsub(`mcp:shttp:toclient:${sessionId}:undefined`)).toBe(0); - - await cleanup(); - }); - }); - - describe('isLive', () => { - const sessionId = 'test-session-789'; - - it('should return true when session has active subscribers', async () => { - // Create a subscription to the server channel - await mockRedis.createSubscription( - `mcp:shttp:toserver:${sessionId}`, - jest.fn(), - jest.fn() - ); - - expect(await isLive(sessionId)).toBe(true); - }); - - it('should return false when session has no subscribers', async () => { - expect(await isLive(sessionId)).toBe(false); - }); - }); - - describe('Session Ownership', () => { - const sessionId = 'test-session-ownership'; - const userId = 'test-user-123'; - - it('should set and get session owner', async () => { - await setSessionOwner(sessionId, userId); - const owner = await getSessionOwner(sessionId); - expect(owner).toBe(userId); - }); - - it('should validate session ownership correctly', async () => { - await setSessionOwner(sessionId, userId); - - expect(await validateSessionOwnership(sessionId, userId)).toBe(true); - expect(await validateSessionOwnership(sessionId, 'different-user')).toBe(false); - }); - - it('should check if session is owned by user including liveness', async () => { - // Session not live yet - expect(await isSessionOwnedBy(sessionId, userId)).toBe(false); - - // Make session live - await mockRedis.createSubscription( - `mcp:shttp:toserver:${sessionId}`, - jest.fn(), - jest.fn() - ); - - // Still false because no owner set - expect(await isSessionOwnedBy(sessionId, userId)).toBe(false); - - // Set owner - await setSessionOwner(sessionId, userId); - - // Now should be true - expect(await isSessionOwnedBy(sessionId, userId)).toBe(true); - - // False for different user - expect(await isSessionOwnedBy(sessionId, 'different-user')).toBe(false); - }); - }); - - describe('Integration: Redis message flow', () => { - const sessionId = 'integration-test-session'; - - it('should relay messages between client and server through Redis', async () => { - // Set up client-side transport simulation - const clientTransport: Transport = { - onmessage: undefined, - onclose: undefined, - onerror: undefined, - send: jest.fn(() => Promise.resolve()), - close: jest.fn(() => Promise.resolve()), - start: jest.fn(() => Promise.resolve()) - }; - - const cleanup = await redisRelayToMcpServer(sessionId, clientTransport); - - // Client sends a request - const listToolsRequest: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'integration-req-1', - method: 'tools/list', - params: {} - }; - - // Set up subscription to simulate server receiving the message - const serverSubscriber = jest.fn(); - await mockRedis.createSubscription( - `mcp:shttp:toserver:${sessionId}`, - serverSubscriber, - jest.fn() - ); - - // Simulate client sending request - clientTransport.onmessage?.(listToolsRequest); - - // Wait for async processing - await new Promise(resolve => setTimeout(resolve, 10)); - - // Verify the message was published to server channel - expect(serverSubscriber).toHaveBeenCalledWith( - JSON.stringify({ - type: 'mcp', - message: listToolsRequest, - extra: undefined, - options: undefined - }) - ); - - // Simulate server sending response - const serverResponse: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'integration-req-1', - result: { tools: [{ name: 'echo', description: 'Echo tool' }] } - }; - - await mockRedis.publish( - `mcp:shttp:toclient:${sessionId}:integration-req-1`, - JSON.stringify({ - type: 'mcp', - message: serverResponse, - options: undefined - }) - ); - - // Wait for response processing - await new Promise(resolve => setTimeout(resolve, 10)); - - // Verify the response was sent back to client - expect(clientTransport.send).toHaveBeenCalledWith(serverResponse, undefined); - - await cleanup(); - }); - }); - - describe('Control Messages', () => { - const sessionId = 'test-control-session'; - - it('should send shutdown control messages', async () => { - const controlSubscriber = jest.fn(); - await mockRedis.createSubscription( - `mcp:control:${sessionId}`, - controlSubscriber, - jest.fn() - ); - - await shutdownSession(sessionId); - - const callArgs = controlSubscriber.mock.calls[0][0] as string; - const message = JSON.parse(callArgs); - - expect(message.type).toBe('control'); - expect(message.action).toBe('SHUTDOWN'); - expect(typeof message.timestamp).toBe('number'); - }); - - it('should properly shutdown server transport via control message', async () => { - const transport = new ServerRedisTransport(sessionId); - const onCloseMock = jest.fn(); - transport.onclose = onCloseMock; - - await transport.start(); - - // Send shutdown signal - await shutdownSession(sessionId); - - // Wait for async processing - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(onCloseMock).toHaveBeenCalled(); - }); - }); - - describe('Inactivity Timeout', () => { - const sessionId = 'test-inactivity-session'; - - beforeEach(() => { - jest.useFakeTimers({ doNotFake: ['setImmediate', 'nextTick'] }); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it('should shutdown session after 5 minutes of inactivity', async () => { - const transport = new ServerRedisTransport(sessionId); - const shutdownSpy = jest.spyOn(mockRedis, 'publish'); - - await transport.start(); - - // Fast-forward time by 5 minutes - jest.advanceTimersByTime(5 * 60 * 1000); - - // Should have published shutdown control message - expect(shutdownSpy).toHaveBeenCalledWith( - `mcp:control:${sessionId}`, - expect.stringContaining('"action":"SHUTDOWN"') - ); - - await transport.close(); - }); - - it('should reset timeout when message is received', async () => { - const transport = new ServerRedisTransport(sessionId); - const onMessageMock = jest.fn(); - transport.onmessage = onMessageMock; - - await transport.start(); - - // Fast-forward 4 minutes - jest.advanceTimersByTime(4 * 60 * 1000); - - // Manually publish a message to trigger the subscription handler - const testMessage = { jsonrpc: '2.0', method: 'ping' }; - await mockRedis.publish( - `mcp:shttp:toserver:${sessionId}`, - JSON.stringify({ - type: 'mcp', - message: testMessage - }) - ); - - // Wait for message to be processed - await new Promise(resolve => setImmediate(resolve)); - - // Verify message was received - expect(onMessageMock).toHaveBeenCalledWith(testMessage, undefined); - - // Clear the publish spy to check only future calls - const shutdownSpy = jest.spyOn(mockRedis, 'publish'); - shutdownSpy.mockClear(); - - // Fast-forward 4 more minutes (total 8, but only 4 since last message) - jest.advanceTimersByTime(4 * 60 * 1000); - - // Should not have shutdown yet - expect(shutdownSpy).not.toHaveBeenCalledWith( - `mcp:control:${sessionId}`, - expect.stringContaining('"action":"SHUTDOWN"') - ); - - // Fast-forward 2 more minutes to exceed timeout - jest.advanceTimersByTime(2 * 60 * 1000); - - // Now should have shutdown - expect(shutdownSpy).toHaveBeenCalledWith( - `mcp:control:${sessionId}`, - expect.stringContaining('"action":"SHUTDOWN"') - ); - - await transport.close(); - }, 10000); - - it('should clear timeout on close', async () => { - const transport = new ServerRedisTransport(sessionId); - const shutdownSpy = jest.spyOn(mockRedis, 'publish'); - - await transport.start(); - - // Close transport before timeout - await transport.close(); - - // Fast-forward past timeout - jest.advanceTimersByTime(10 * 60 * 1000); - - // Should not have triggered shutdown - expect(shutdownSpy).not.toHaveBeenCalledWith( - `mcp:control:${sessionId}`, - expect.stringContaining('"action":"SHUTDOWN"') - ); - }); - }); -}); \ No newline at end of file diff --git a/external-oauth/mcp-server/src/services/redisTransport.ts b/external-oauth/mcp-server/src/services/redisTransport.ts deleted file mode 100644 index e35eb9e..0000000 --- a/external-oauth/mcp-server/src/services/redisTransport.ts +++ /dev/null @@ -1,351 +0,0 @@ -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import { redisClient } from "../redis.js"; -import { Transport, TransportSendOptions } from "@modelcontextprotocol/sdk/shared/transport.js"; -import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js"; -import { JSONRPCMessage, MessageExtraInfo } from "@modelcontextprotocol/sdk/types.js"; -import { logger } from "../utils/logger.js"; - -let redisTransportCounter = 0; -const notificationStreamId = "__GET_stream"; - -// Message types for Redis transport -type RedisMessage = - | { - type: 'mcp'; - message: JSONRPCMessage; - extra?: MessageExtraInfo; - options?: TransportSendOptions; - } - | { - type: 'control'; - action: 'SHUTDOWN' | 'PING' | 'STATUS'; - timestamp?: number; - }; - -function sendToMcpServer(sessionId: string, message: JSONRPCMessage, extra?: { authInfo?: AuthInfo; }, options?: TransportSendOptions): Promise { - const toServerChannel = getToServerChannel(sessionId); - - logger.debug('Sending message to MCP server via Redis', { - sessionId, - channel: toServerChannel, - method: ('method' in message ? message.method : undefined), - id: ('id' in message ? message.id : undefined) - }); - - const redisMessage: RedisMessage = { type: 'mcp', message, extra, options }; - return redisClient.publish(toServerChannel, JSON.stringify(redisMessage)); -} - -function getToServerChannel(sessionId: string): string { - return `mcp:shttp:toserver:${sessionId}`; -} - -function getToClientChannel(sessionId: string, relatedRequestId: string): string { - return `mcp:shttp:toclient:${sessionId}:${relatedRequestId}`; -} - -function getControlChannel(sessionId: string): string { - return `mcp:control:${sessionId}`; -} - -function sendControlMessage(sessionId: string, action: 'SHUTDOWN' | 'PING' | 'STATUS'): Promise { - const controlChannel = getControlChannel(sessionId); - const redisMessage: RedisMessage = { - type: 'control', - action, - timestamp: Date.now() - }; - return redisClient.publish(controlChannel, JSON.stringify(redisMessage)); -} - -export async function shutdownSession(sessionId: string): Promise { - logger.info('Sending shutdown control message', { sessionId }); - return sendControlMessage(sessionId, 'SHUTDOWN'); -} - -export async function isLive(sessionId: string): Promise { - // Check if the session is live by checking if the key exists in Redis - const numSubs = await redisClient.numsub(getToServerChannel(sessionId)); - return numSubs > 0; -} - -export async function setSessionOwner(sessionId: string, userId: string): Promise { - logger.debug('Setting session owner', { sessionId, userId }); - await redisClient.set(`session:${sessionId}:owner`, userId); -} - -export async function getSessionOwner(sessionId: string): Promise { - return await redisClient.get(`session:${sessionId}:owner`); -} - -export async function validateSessionOwnership(sessionId: string, userId: string): Promise { - const owner = await getSessionOwner(sessionId); - return owner === userId; -} - -export async function isSessionOwnedBy(sessionId: string, userId: string): Promise { - const isLiveSession = await isLive(sessionId); - if (!isLiveSession) { - logger.debug('Session not live', { sessionId }); - return false; - } - const isOwned = await validateSessionOwnership(sessionId, userId); - logger.debug('Session ownership check', { sessionId, userId, isOwned }); - return isOwned; -} - - -export async function redisRelayToMcpServer(sessionId: string, transport: Transport, isGetRequest: boolean = false): Promise<() => Promise> { - logger.debug('Setting up Redis relay to MCP server', { - sessionId, - isGetRequest - }); - - let redisCleanup: (() => Promise) | undefined = undefined; - const cleanup = async () => { - // TODO: solve race conditions where we call cleanup while the subscription is being created / before it is created - if (redisCleanup) { - logger.debug('Cleaning up Redis relay', { sessionId }); - await redisCleanup(); - } - } - - const subscribe = async (requestId: string) => { - const toClientChannel = getToClientChannel(sessionId, requestId); - - logger.debug('Subscribing to client channel', { - sessionId, - requestId, - channel: toClientChannel - }); - - redisCleanup = await redisClient.createSubscription(toClientChannel, async (redisMessageJson) => { - const redisMessage = JSON.parse(redisMessageJson) as RedisMessage; - if (redisMessage.type === 'mcp') { - logger.debug('Relaying message from Redis to client', { - sessionId, - requestId, - method: ('method' in redisMessage.message ? redisMessage.message.method : undefined) - }); - await transport.send(redisMessage.message, redisMessage.options); - } - }, (error) => { - logger.error('Error in Redis relay subscription', error, { - sessionId, - channel: toClientChannel - }); - transport.onerror?.(error); - }); - } - - if (isGetRequest) { - await subscribe(notificationStreamId); - } else { - const messagePromise = new Promise((resolve) => { - transport.onmessage = async (message, extra) => { - // First, set up response subscription if needed - if ("id" in message) { - logger.debug('Setting up response subscription', { - sessionId, - messageId: message.id, - method: ('method' in message ? message.method : undefined) - }); - await subscribe(message.id.toString()); - } - // Now send the message to the MCP server - await sendToMcpServer(sessionId, message, extra); - resolve(message); - } - }); - - messagePromise.catch((error) => { - transport.onerror?.(error); - cleanup(); - }); - } - return cleanup; -} - - -// New Redis transport for server->client messages using request-id based channels -export class ServerRedisTransport implements Transport { - private counter: number; - private _sessionId: string; - private controlCleanup?: (() => Promise); - private serverCleanup?: (() => Promise); - private shouldShutdown = false; - private inactivityTimeout?: NodeJS.Timeout; - private readonly INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes - - onclose?: (() => void) | undefined; - onerror?: ((error: Error) => void) | undefined; - onmessage?: ((message: JSONRPCMessage, extra?: { authInfo?: AuthInfo; }) => void) | undefined; - - constructor(sessionId: string) { - this.counter = redisTransportCounter++; - this._sessionId = sessionId; - } - - private resetInactivityTimer(): void { - // Clear existing timeout if any - if (this.inactivityTimeout) { - clearTimeout(this.inactivityTimeout); - } - - // Set new timeout - this.inactivityTimeout = setTimeout(() => { - logger.info('Session timed out due to inactivity', { - sessionId: this._sessionId, - timeoutMs: this.INACTIVITY_TIMEOUT_MS - }); - void shutdownSession(this._sessionId); - }, this.INACTIVITY_TIMEOUT_MS); - } - - private clearInactivityTimer(): void { - if (this.inactivityTimeout) { - clearTimeout(this.inactivityTimeout); - this.inactivityTimeout = undefined; - } - } - - async start(): Promise { - logger.info('Starting ServerRedisTransport', { - sessionId: this._sessionId, - inactivityTimeoutMs: this.INACTIVITY_TIMEOUT_MS - }); - - // Start inactivity timer - this.resetInactivityTimer(); - - // Subscribe to MCP messages from clients - const serverChannel = getToServerChannel(this._sessionId); - logger.debug('Subscribing to server channel', { - sessionId: this._sessionId, - channel: serverChannel - }); - - this.serverCleanup = await redisClient.createSubscription( - serverChannel, - (messageJson) => { - const redisMessage = JSON.parse(messageJson) as RedisMessage; - if (redisMessage.type === 'mcp') { - // Reset inactivity timer on each message from client - this.resetInactivityTimer(); - - logger.debug('Received MCP message from client', { - sessionId: this._sessionId, - method: ('method' in redisMessage.message ? redisMessage.message.method : undefined), - id: ('id' in redisMessage.message ? redisMessage.message.id : undefined) - }); - - this.onmessage?.(redisMessage.message, redisMessage.extra); - } - }, - (error) => { - logger.error('Error in server channel subscription', error, { - sessionId: this._sessionId, - channel: serverChannel - }); - this.onerror?.(error); - } - ); - - // Subscribe to control messages for shutdown - const controlChannel = getControlChannel(this._sessionId); - logger.debug('Subscribing to control channel', { - sessionId: this._sessionId, - channel: controlChannel - }); - - this.controlCleanup = await redisClient.createSubscription( - controlChannel, - (messageJson) => { - const redisMessage = JSON.parse(messageJson) as RedisMessage; - if (redisMessage.type === 'control') { - logger.info('Received control message', { - sessionId: this._sessionId, - action: redisMessage.action - }); - - if (redisMessage.action === 'SHUTDOWN') { - logger.info('Shutting down transport due to control message', { - sessionId: this._sessionId - }); - this.shouldShutdown = true; - this.close(); - } - } - }, - (error) => { - logger.error('Error in control channel subscription', error, { - sessionId: this._sessionId, - channel: controlChannel - }); - this.onerror?.(error); - } - ); - - } - - async send(message: JSONRPCMessage, options?: TransportSendOptions): Promise { - const relatedRequestId = options?.relatedRequestId?.toString() ?? ("id" in message ? message.id?.toString() : notificationStreamId); - const channel = getToClientChannel(this._sessionId, relatedRequestId) - - logger.debug('Sending message to client', { - sessionId: this._sessionId, - channel, - method: ('method' in message ? message.method : undefined), - id: ('id' in message ? message.id : undefined), - relatedRequestId - }); - - const redisMessage: RedisMessage = { type: 'mcp', message, options }; - const messageStr = JSON.stringify(redisMessage); - await redisClient.publish(channel, messageStr); - } - - async close(): Promise { - logger.info('Closing ServerRedisTransport', { - sessionId: this._sessionId, - wasShutdown: this.shouldShutdown - }); - - // Clear inactivity timer - this.clearInactivityTimer(); - - // Clean up server message subscription - if (this.serverCleanup) { - await this.serverCleanup(); - this.serverCleanup = undefined; - } - - // Clean up control message subscription - if (this.controlCleanup) { - await this.controlCleanup(); - this.controlCleanup = undefined; - } - - this.onclose?.(); - } -} - -export async function getShttpTransport(sessionId: string, onsessionclosed: (sessionId: string) => void | Promise, isGetRequest: boolean = false): Promise { - logger.debug('Getting StreamableHTTPServerTransport for existing session', { - sessionId, - isGetRequest - }); - - // Giving undefined here and setting the sessionId means the - // transport wont try to create a new session. - const shttpTransport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - onsessionclosed, - }) - shttpTransport.sessionId = sessionId; - - // Use the new request-id based relay approach - const cleanup = await redisRelayToMcpServer(sessionId, shttpTransport, isGetRequest); - shttpTransport.onclose = cleanup; - return shttpTransport; -} \ No newline at end of file diff --git a/external-oauth/mcp-server/src/static/index.html b/external-oauth/mcp-server/src/static/index.html deleted file mode 100644 index 2e3eeb9..0000000 --- a/external-oauth/mcp-server/src/static/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - MCP Example Server - - - -
-
- -

MCP Example Server

-
- -

- A comprehensive reference implementation of the Model Context Protocol (MCP) server - demonstrating all protocol features with full authentication support and horizontal scalability. -

- -
-
-

Complete MCP Support

-

All MCP features including tools, resources, prompts, sampling, completions, and logging with full protocol compliance.

-
-
-

Multiple Transports

-

Streamable HTTP (SHTTP) and Server-Sent Events (SSE) transports for flexible client integration.

-
-
-

OAuth 2.0 Authentication

-

Complete OAuth flow with PKCE support and a built-in fake provider for testing and development.

-
-
-

Horizontal Scalability

-

Redis-backed session management enables multi-instance deployments with automatic load distribution.

-
-
-

7 Demo Tools

-

Echo, add, long-running operations, LLM sampling, image handling, annotations, and resource references.

-
-
-

100+ Resources

-

Example resources with pagination, templates, subscriptions, and real-time update notifications.

-
-
- -
-

API Endpoints

-
-
- POST - /mcp - Initialize sessions or send messages (Streamable HTTP) -
-
- GET - /mcp - Establish SSE streams (Streamable HTTP) -
-
- DELETE - /mcp - Terminate sessions (Streamable HTTP) -
-
- GET - /sse - Legacy SSE transport endpoint -
-
- POST - /message - Legacy message endpoint for SSE transport -
-
-
- - -
- - - - \ No newline at end of file diff --git a/external-oauth/mcp-server/src/static/mcp.png b/external-oauth/mcp-server/src/static/mcp.png deleted file mode 100644 index 86c52662d890dd8ebf115d6c228f36748c77176e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40652 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 Wt$UO#QT4xF{!Cg`V=wfbRA8HV?}G@QPh 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 diff --git a/external-oauth/mcp-server/src/static/styles.css b/external-oauth/mcp-server/src/static/styles.css deleted file mode 100644 index c3dcd08..0000000 --- a/external-oauth/mcp-server/src/static/styles.css +++ /dev/null @@ -1,188 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - background: #ffffff; - color: #000000; - min-height: 100vh; - display: flex; - flex-direction: column; -} - -.container { - max-width: 1200px; - margin: 0 auto; - padding: 2rem; - flex: 1; -} - -header { - display: flex; - align-items: center; - gap: 2rem; - margin-bottom: 3rem; - padding-bottom: 2rem; - border-bottom: 2px solid #000000; -} - -.logo { - width: 80px; - height: 80px; - background: #000000; - padding: 10px; - border-radius: 8px; -} - -h1 { - font-size: 2.5rem; - font-weight: 700; - letter-spacing: -0.02em; -} - -.tagline { - font-size: 1.25rem; - color: #666666; - margin-bottom: 3rem; - line-height: 1.6; -} - -.features { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 2rem; - margin-bottom: 3rem; -} - -.feature-card { - border: 2px solid #000000; - padding: 1.5rem; - background: #ffffff; - transition: all 0.2s ease; -} - -.feature-card:hover { - background: #000000; - color: #ffffff; - transform: translateY(-2px); - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); -} - -.feature-card h3 { - font-size: 1.25rem; - margin-bottom: 0.75rem; - font-weight: 600; -} - -.feature-card p { - line-height: 1.6; - opacity: 0.9; -} - -.endpoints { - background: #f5f5f5; - border: 2px solid #000000; - padding: 2rem; - margin-bottom: 3rem; -} - -.endpoints h2 { - font-size: 1.75rem; - margin-bottom: 1.5rem; - font-weight: 600; -} - -.endpoint-list { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.endpoint { - font-family: 'Courier New', monospace; - background: #ffffff; - padding: 0.75rem 1rem; - border: 1px solid #000000; - display: flex; - align-items: center; - gap: 1rem; -} - -.method { - font-weight: bold; - min-width: 80px; -} - -.method.get { color: #0066cc; } -.method.post { color: #009900; } -.method.delete { color: #cc0000; } - -.links { - display: flex; - gap: 2rem; - flex-wrap: wrap; - margin-bottom: 3rem; -} - -.link-button { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 1rem 2rem; - background: #000000; - color: #ffffff; - text-decoration: none; - font-weight: 600; - transition: all 0.2s ease; - border: 2px solid #000000; -} - -.link-button:hover { - background: #ffffff; - color: #000000; -} - -.link-button.secondary { - background: #ffffff; - color: #000000; -} - -.link-button.secondary:hover { - background: #000000; - color: #ffffff; -} - -footer { - background: #000000; - color: #ffffff; - padding: 2rem; - text-align: center; -} - -footer a { - color: #ffffff; - text-decoration: underline; -} - -@media (max-width: 768px) { - h1 { - font-size: 2rem; - } - - header { - flex-direction: column; - align-items: flex-start; - gap: 1rem; - } - - .logo { - width: 60px; - height: 60px; - background: #000000; - padding: 8px; - border-radius: 6px; - } -} \ No newline at end of file diff --git a/external-oauth/mcp-server/tsconfig.json b/external-oauth/mcp-server/tsconfig.json deleted file mode 100644 index fee103a..0000000 --- a/external-oauth/mcp-server/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "module": "Node16", - "moduleResolution": "Node16", - "sourceMap": true, - "outDir": "./dist", - "strict": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "isolatedModules": true, - "skipLibCheck": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/external-oauth/mcp-server/README.md b/mcp-server/README.md similarity index 92% rename from external-oauth/mcp-server/README.md rename to mcp-server/README.md index 3c83dd1..974c763 100644 --- a/external-oauth/mcp-server/README.md +++ b/mcp-server/README.md @@ -16,7 +16,7 @@ This server shows how to build an MCP server that delegates all OAuth operations # This server requires the auth server to be running! # Option 1: Start both servers from repo root -cd ../.. && npm run dev:separate +cd .. && npm run dev # Option 2: Start servers individually # Terminal 1: @@ -140,11 +140,11 @@ The MCP server code otherwise remains unchanged. - [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) - [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) - [OAuth 2.0 Resource Servers](https://www.oauth.com/oauth2-servers/the-resource-server/) -- [docs/oauth-flow.md](../../docs/oauth-flow.md) - Detailed flow with mode differences ## Related Documentation -- [Main README](../../README.md) - Complete project documentation +- [Main README](../README.md) - Complete project documentation - [Auth Server README](../auth-server/README.md) - The demo OAuth provider -- [External OAuth Overview](../README.md) - Architecture explanation -- [Embedded OAuth](../../embedded-oauth/README.md) - Self-hosted OAuth alternative +- [OAuth Patterns](../docs/oauth-patterns.md) - OAuth architecture patterns +- [OAuth Flow](../docs/oauth-flow.md) - Detailed OAuth flow analysis +- [Session Ownership](../docs/session-ownership.md) - Session management details diff --git a/external-oauth/auth-server/eslint.config.mjs b/mcp-server/eslint.config.mjs similarity index 100% rename from external-oauth/auth-server/eslint.config.mjs rename to mcp-server/eslint.config.mjs diff --git a/external-oauth/auth-server/jest.config.js b/mcp-server/jest.config.js similarity index 100% rename from external-oauth/auth-server/jest.config.js rename to mcp-server/jest.config.js diff --git a/external-oauth/mcp-server/package-lock.json b/mcp-server/package-lock.json similarity index 100% rename from external-oauth/mcp-server/package-lock.json rename to mcp-server/package-lock.json diff --git a/external-oauth/mcp-server/package.json b/mcp-server/package.json similarity index 100% rename from external-oauth/mcp-server/package.json rename to mcp-server/package.json diff --git a/external-oauth/mcp-server/src/auth/external-verifier.ts b/mcp-server/src/auth/external-verifier.ts similarity index 100% rename from external-oauth/mcp-server/src/auth/external-verifier.ts rename to mcp-server/src/auth/external-verifier.ts diff --git a/external-oauth/mcp-server/src/config.ts b/mcp-server/src/config.ts similarity index 100% rename from external-oauth/mcp-server/src/config.ts rename to mcp-server/src/config.ts diff --git a/embedded-oauth/src/handlers/shttp.integration.test.ts b/mcp-server/src/handlers/shttp.integration.test.ts similarity index 100% rename from embedded-oauth/src/handlers/shttp.integration.test.ts rename to mcp-server/src/handlers/shttp.integration.test.ts diff --git a/embedded-oauth/src/handlers/shttp.test.ts b/mcp-server/src/handlers/shttp.test.ts similarity index 100% rename from embedded-oauth/src/handlers/shttp.test.ts rename to mcp-server/src/handlers/shttp.test.ts diff --git a/embedded-oauth/src/handlers/shttp.ts b/mcp-server/src/handlers/shttp.ts similarity index 100% rename from embedded-oauth/src/handlers/shttp.ts rename to mcp-server/src/handlers/shttp.ts diff --git a/external-oauth/mcp-server/src/handlers/sse.ts b/mcp-server/src/handlers/sse.ts similarity index 100% rename from external-oauth/mcp-server/src/handlers/sse.ts rename to mcp-server/src/handlers/sse.ts diff --git a/external-oauth/mcp-server/src/index.ts b/mcp-server/src/index.ts similarity index 93% rename from external-oauth/mcp-server/src/index.ts rename to mcp-server/src/index.ts index 5f634de..d1ed039 100644 --- a/external-oauth/mcp-server/src/index.ts +++ b/mcp-server/src/index.ts @@ -1,3 +1,17 @@ +/** + * MCP Resource Server with External OAuth Authentication + * + * This server demonstrates how to build an MCP server that delegates ALL + * authentication to an external OAuth provider. Key points: + * + * - No OAuth authorization code in this server (no /authorize, /token endpoints) + * - Uses Bearer token authentication middleware from MCP SDK + * - Validates tokens via external auth server's /introspect endpoint + * - Focuses purely on serving MCP protocol resources + * + * In production, the external auth server would be Auth0, Okta, Google OAuth, etc. + */ + import { BearerAuthMiddlewareOptions, requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"; import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from "@modelcontextprotocol/sdk/server/auth/router.js"; import cors from "cors"; diff --git a/external-oauth/auth-server/src/redis.ts b/mcp-server/src/redis.ts similarity index 100% rename from external-oauth/auth-server/src/redis.ts rename to mcp-server/src/redis.ts diff --git a/embedded-oauth/src/services/mcp.ts b/mcp-server/src/services/mcp.ts similarity index 100% rename from embedded-oauth/src/services/mcp.ts rename to mcp-server/src/services/mcp.ts diff --git a/embedded-oauth/src/services/redisTransport.integration.test.ts b/mcp-server/src/services/redisTransport.integration.test.ts similarity index 100% rename from embedded-oauth/src/services/redisTransport.integration.test.ts rename to mcp-server/src/services/redisTransport.integration.test.ts diff --git a/embedded-oauth/src/services/redisTransport.test.ts b/mcp-server/src/services/redisTransport.test.ts similarity index 100% rename from embedded-oauth/src/services/redisTransport.test.ts rename to mcp-server/src/services/redisTransport.test.ts diff --git a/embedded-oauth/src/services/redisTransport.ts b/mcp-server/src/services/redisTransport.ts similarity index 100% rename from embedded-oauth/src/services/redisTransport.ts rename to mcp-server/src/services/redisTransport.ts diff --git a/external-oauth/auth-server/src/static/index.html b/mcp-server/src/static/index.html similarity index 100% rename from external-oauth/auth-server/src/static/index.html rename to mcp-server/src/static/index.html diff --git a/external-oauth/auth-server/src/static/mcp.png b/mcp-server/src/static/mcp.png similarity index 100% rename from external-oauth/auth-server/src/static/mcp.png rename to mcp-server/src/static/mcp.png diff --git a/external-oauth/auth-server/src/static/styles.css b/mcp-server/src/static/styles.css similarity index 100% rename from external-oauth/auth-server/src/static/styles.css rename to mcp-server/src/static/styles.css diff --git a/external-oauth/mcp-server/src/types.ts b/mcp-server/src/types.ts similarity index 100% rename from external-oauth/mcp-server/src/types.ts rename to mcp-server/src/types.ts diff --git a/external-oauth/mcp-server/src/utils/logger.ts b/mcp-server/src/utils/logger.ts similarity index 100% rename from external-oauth/mcp-server/src/utils/logger.ts rename to mcp-server/src/utils/logger.ts diff --git a/external-oauth/auth-server/tsconfig.json b/mcp-server/tsconfig.json similarity index 100% rename from external-oauth/auth-server/tsconfig.json rename to mcp-server/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 1aca130..27b50cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,41 +8,14 @@ "name": "mcp-server-feature-reference", "version": "0.1.0", "workspaces": [ - "embedded-oauth", - "external-oauth/auth-server", - "external-oauth/mcp-server" + "auth-server", + "mcp-server" ], "devDependencies": { "concurrently": "^8.2.0" } }, - "embedded-oauth": { - "name": "mcp-server-feature-reference-embedded-oauth", - "version": "0.1.0", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.15.1", - "@redis/client": "^1.6.0", - "cors": "^2.8.5", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "express-rate-limit": "^8.0.1", - "raw-body": "^3.0.0" - }, - "devDependencies": { - "@eslint/js": "^9.15.0", - "@types/content-type": "^1.1.8", - "@types/cors": "^2.8.17", - "@types/express": "^5.0.0", - "@types/jest": "^29.5.14", - "@types/node": "^22.10.0", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "typescript-eslint": "^8.18.0" - } - }, - "external-oauth/auth-server": { + "auth-server": { "name": "mcp-server-feature-reference-external-auth-server", "version": "0.1.0", "dependencies": { @@ -68,7 +41,7 @@ "typescript-eslint": "^8.18.0" } }, - "external-oauth/mcp-server": { + "mcp-server": { "name": "mcp-server-feature-reference-external-mcp-server", "version": "0.1.0", "dependencies": { @@ -94,37 +67,8 @@ "typescript-eslint": "^8.18.0" } }, - "integrated-mode": { - "name": "mcp-server-feature-reference-integrated", - "version": "0.1.0", - "extraneous": true, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.15.1", - "@redis/client": "^1.6.0", - "cors": "^2.8.5", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "express-rate-limit": "^8.0.1", - "raw-body": "^3.0.0" - }, - "devDependencies": { - "@eslint/js": "^9.15.0", - "@types/content-type": "^1.1.8", - "@types/cors": "^2.8.17", - "@types/express": "^5.0.0", - "@types/jest": "^29.5.14", - "@types/node": "^22.10.0", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "typescript-eslint": "^8.18.0" - } - }, "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": { @@ -138,8 +82,6 @@ }, "node_modules/@babel/compat-data": { "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", "dev": true, "license": "MIT", "engines": { @@ -148,8 +90,6 @@ }, "node_modules/@babel/core": { "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", "dependencies": { @@ -179,8 +119,6 @@ }, "node_modules/@babel/generator": { "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dev": true, "license": "MIT", "dependencies": { @@ -196,8 +134,6 @@ }, "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": { @@ -213,8 +149,6 @@ }, "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": { @@ -223,8 +157,6 @@ }, "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": { @@ -237,8 +169,6 @@ }, "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": { @@ -255,8 +185,6 @@ }, "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": { @@ -265,8 +193,6 @@ }, "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": { @@ -275,8 +201,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, "license": "MIT", "engines": { @@ -285,8 +209,6 @@ }, "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": { @@ -295,8 +217,6 @@ }, "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": { @@ -309,8 +229,6 @@ }, "node_modules/@babel/parser": { "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, "license": "MIT", "dependencies": { @@ -325,8 +243,6 @@ }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "license": "MIT", "dependencies": { @@ -338,8 +254,6 @@ }, "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "license": "MIT", "dependencies": { @@ -351,8 +265,6 @@ }, "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "license": "MIT", "dependencies": { @@ -364,8 +276,6 @@ }, "node_modules/@babel/plugin-syntax-class-static-block": { "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, "license": "MIT", "dependencies": { @@ -380,8 +290,6 @@ }, "node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, "license": "MIT", "dependencies": { @@ -396,8 +304,6 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "license": "MIT", "dependencies": { @@ -409,8 +315,6 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "license": "MIT", "dependencies": { @@ -422,8 +326,6 @@ }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, "license": "MIT", "dependencies": { @@ -438,8 +340,6 @@ }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, "license": "MIT", "dependencies": { @@ -451,8 +351,6 @@ }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -464,8 +362,6 @@ }, "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "license": "MIT", "dependencies": { @@ -477,8 +373,6 @@ }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "license": "MIT", "dependencies": { @@ -490,8 +384,6 @@ }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -503,8 +395,6 @@ }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, "license": "MIT", "dependencies": { @@ -516,8 +406,6 @@ }, "node_modules/@babel/plugin-syntax-private-property-in-object": { "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, "license": "MIT", "dependencies": { @@ -532,8 +420,6 @@ }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "license": "MIT", "dependencies": { @@ -548,8 +434,6 @@ }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -564,8 +448,6 @@ }, "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==", "dev": true, "license": "MIT", "engines": { @@ -574,8 +456,6 @@ }, "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": { @@ -589,8 +469,6 @@ }, "node_modules/@babel/traverse": { "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -608,8 +486,6 @@ }, "node_modules/@babel/types": { "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -622,83 +498,11 @@ }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", "cpu": [ "arm64" ], @@ -712,367 +516,8 @@ "node": ">=18" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -1090,8 +535,6 @@ }, "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1103,8 +546,6 @@ }, "node_modules/@eslint-community/regexpp": { "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, "license": "MIT", "engines": { @@ -1113,8 +554,6 @@ }, "node_modules/@eslint/config-array": { "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1129,8 +568,6 @@ }, "node_modules/@eslint/config-helpers": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1140,8 +577,6 @@ }, "node_modules/@eslint/core": { "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1154,8 +589,6 @@ }, "node_modules/@eslint/eslintrc": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "peer": true, @@ -1179,8 +612,6 @@ }, "node_modules/@eslint/js": { "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", - "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", "dev": true, "license": "MIT", "engines": { @@ -1192,8 +623,6 @@ }, "node_modules/@eslint/object-schema": { "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1203,8 +632,6 @@ }, "node_modules/@eslint/plugin-kit": { "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1218,8 +645,6 @@ }, "node_modules/@humanfs/core": { "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1229,8 +654,6 @@ }, "node_modules/@humanfs/node": { "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1244,8 +667,6 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1259,8 +680,6 @@ }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1274,8 +693,6 @@ }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "license": "ISC", "dependencies": { @@ -1291,8 +708,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { @@ -1301,8 +716,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -1315,8 +728,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "license": "MIT", "dependencies": { @@ -1329,8 +740,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -1342,8 +751,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -1358,8 +765,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -1371,8 +776,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { @@ -1381,8 +784,6 @@ }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "license": "MIT", "engines": { @@ -1391,8 +792,6 @@ }, "node_modules/@jest/console": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "license": "MIT", "dependencies": { @@ -1409,8 +808,6 @@ }, "node_modules/@jest/core": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "license": "MIT", "dependencies": { @@ -1457,8 +854,6 @@ }, "node_modules/@jest/environment": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "license": "MIT", "dependencies": { @@ -1473,8 +868,6 @@ }, "node_modules/@jest/expect": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1487,8 +880,6 @@ }, "node_modules/@jest/expect-utils": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "license": "MIT", "dependencies": { @@ -1500,8 +891,6 @@ }, "node_modules/@jest/fake-timers": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1518,8 +907,6 @@ }, "node_modules/@jest/globals": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1534,8 +921,6 @@ }, "node_modules/@jest/reporters": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "license": "MIT", "dependencies": { @@ -1578,8 +963,6 @@ }, "node_modules/@jest/schemas": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { @@ -1591,8 +974,6 @@ }, "node_modules/@jest/source-map": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "license": "MIT", "dependencies": { @@ -1606,8 +987,6 @@ }, "node_modules/@jest/test-result": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "license": "MIT", "dependencies": { @@ -1622,8 +1001,6 @@ }, "node_modules/@jest/test-sequencer": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "license": "MIT", "dependencies": { @@ -1638,8 +1015,6 @@ }, "node_modules/@jest/transform": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -1665,8 +1040,6 @@ }, "node_modules/@jest/types": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "license": "MIT", "dependencies": { @@ -1683,8 +1056,6 @@ }, "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": { @@ -1694,8 +1065,6 @@ }, "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": { @@ -1705,8 +1074,6 @@ }, "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": { @@ -1715,15 +1082,11 @@ }, "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": { @@ -1733,8 +1096,6 @@ }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.18.0.tgz", - "integrity": "sha512-JvKyB6YwS3quM+88JPR0axeRgvdDu3Pv6mdZUy+w4qVkCzGgumb9bXG/TmtDRQv+671yaofVfXSQmFLlWU5qPQ==", "license": "MIT", "dependencies": { "ajv": "^6.12.6", @@ -1756,8 +1117,6 @@ }, "node_modules/@modelcontextprotocol/sdk/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", @@ -1769,8 +1128,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -1789,8 +1146,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -1801,8 +1156,6 @@ }, "node_modules/@modelcontextprotocol/sdk/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" @@ -1810,8 +1163,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/express": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -1852,8 +1203,6 @@ }, "node_modules/@modelcontextprotocol/sdk/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" @@ -1867,8 +1216,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -1884,8 +1231,6 @@ }, "node_modules/@modelcontextprotocol/sdk/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" @@ -1893,8 +1238,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -1905,8 +1248,6 @@ }, "node_modules/@modelcontextprotocol/sdk/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" @@ -1914,8 +1255,6 @@ }, "node_modules/@modelcontextprotocol/sdk/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" @@ -1926,8 +1265,6 @@ }, "node_modules/@modelcontextprotocol/sdk/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" @@ -1935,8 +1272,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -1947,8 +1282,6 @@ }, "node_modules/@modelcontextprotocol/sdk/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" @@ -1956,8 +1289,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -1971,8 +1302,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/send": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "license": "MIT", "dependencies": { "debug": "^4.3.5", @@ -1993,8 +1322,6 @@ }, "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -2008,8 +1335,6 @@ }, "node_modules/@modelcontextprotocol/sdk/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", @@ -2022,8 +1347,6 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", "dependencies": { @@ -2036,8 +1359,6 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", "engines": { @@ -2046,8 +1367,6 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", "dependencies": { @@ -2060,8 +1379,6 @@ }, "node_modules/@redis/client": { "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", - "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", "license": "MIT", "dependencies": { "cluster-key-slot": "1.1.2", @@ -2074,15 +1391,11 @@ }, "node_modules/@sinclair/typebox": { "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true, "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2091,8 +1404,6 @@ }, "node_modules/@sinonjs/fake-timers": { "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2101,8 +1412,6 @@ }, "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": { @@ -2115,8 +1424,6 @@ }, "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": { @@ -2125,8 +1432,6 @@ }, "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": { @@ -2136,8 +1441,6 @@ }, "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": { @@ -2146,8 +1449,6 @@ }, "node_modules/@types/body-parser": { "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", "dependencies": { @@ -2157,8 +1458,6 @@ }, "node_modules/@types/connect": { "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "license": "MIT", "dependencies": { @@ -2167,15 +1466,11 @@ }, "node_modules/@types/content-type": { "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.9.tgz", - "integrity": "sha512-Hq9IMnfekuOCsEmYl4QX2HBrT+XsfXiupfrLLY8Dcf3Puf4BkBOxSbWYTITSOQAhJoYPBez+b4MJRpIYL65z8A==", "dev": true, "license": "MIT" }, "node_modules/@types/cors": { "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", "dev": true, "license": "MIT", "dependencies": { @@ -2184,16 +1479,12 @@ }, "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", "peer": true }, "node_modules/@types/express": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", - "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", "dev": true, "license": "MIT", "dependencies": { @@ -2204,8 +1495,6 @@ }, "node_modules/@types/express-serve-static-core": { "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", - "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2217,8 +1506,6 @@ }, "node_modules/@types/graceful-fs": { "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2227,22 +1514,16 @@ }, "node_modules/@types/http-errors": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "license": "MIT", "dependencies": { @@ -2251,8 +1532,6 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2261,8 +1540,6 @@ }, "node_modules/@types/jest": { "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2272,23 +1549,17 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT", "peer": true }, "node_modules/@types/mime": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "22.18.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.1.tgz", - "integrity": "sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw==", "dev": true, "license": "MIT", "dependencies": { @@ -2297,22 +1568,16 @@ }, "node_modules/@types/qs": { "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", "dev": true, "license": "MIT", "dependencies": { @@ -2322,8 +1587,6 @@ }, "node_modules/@types/serve-static": { "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", "dev": true, "license": "MIT", "dependencies": { @@ -2334,15 +1597,11 @@ }, "node_modules/@types/stack-utils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true, "license": "MIT" }, "node_modules/@types/yargs": { "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "license": "MIT", "dependencies": { @@ -2351,15 +1610,11 @@ }, "node_modules/@types/yargs-parser": { "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.43.0.tgz", - "integrity": "sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2388,8 +1643,6 @@ }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { @@ -2398,8 +1651,6 @@ }, "node_modules/@typescript-eslint/parser": { "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.43.0.tgz", - "integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==", "dev": true, "license": "MIT", "dependencies": { @@ -2423,8 +1674,6 @@ }, "node_modules/@typescript-eslint/project-service": { "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.43.0.tgz", - "integrity": "sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==", "dev": true, "license": "MIT", "dependencies": { @@ -2445,8 +1694,6 @@ }, "node_modules/@typescript-eslint/scope-manager": { "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.43.0.tgz", - "integrity": "sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==", "dev": true, "license": "MIT", "dependencies": { @@ -2463,8 +1710,6 @@ }, "node_modules/@typescript-eslint/tsconfig-utils": { "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.43.0.tgz", - "integrity": "sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==", "dev": true, "license": "MIT", "engines": { @@ -2480,8 +1725,6 @@ }, "node_modules/@typescript-eslint/type-utils": { "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.43.0.tgz", - "integrity": "sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==", "dev": true, "license": "MIT", "dependencies": { @@ -2505,8 +1748,6 @@ }, "node_modules/@typescript-eslint/types": { "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.43.0.tgz", - "integrity": "sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==", "dev": true, "license": "MIT", "engines": { @@ -2519,8 +1760,6 @@ }, "node_modules/@typescript-eslint/typescript-estree": { "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.43.0.tgz", - "integrity": "sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==", "dev": true, "license": "MIT", "dependencies": { @@ -2548,8 +1787,6 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2558,8 +1795,6 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -2574,8 +1809,6 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -2587,8 +1820,6 @@ }, "node_modules/@typescript-eslint/utils": { "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.43.0.tgz", - "integrity": "sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==", "dev": true, "license": "MIT", "dependencies": { @@ -2611,8 +1842,6 @@ }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.43.0.tgz", - "integrity": "sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==", "dev": true, "license": "MIT", "dependencies": { @@ -2629,8 +1858,6 @@ }, "node_modules/accepts": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -2642,8 +1869,6 @@ }, "node_modules/acorn": { "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "peer": true, @@ -2656,8 +1881,6 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", "peer": true, @@ -2667,8 +1890,6 @@ }, "node_modules/ajv": { "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -2683,8 +1904,6 @@ }, "node_modules/ansi-escapes": { "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2699,8 +1918,6 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -2709,8 +1926,6 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -2725,8 +1940,6 @@ }, "node_modules/anymatch": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "license": "ISC", "dependencies": { @@ -2739,22 +1952,16 @@ }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, "license": "Python-2.0", "peer": true }, "node_modules/array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, "node_modules/babel-jest": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "license": "MIT", "dependencies": { @@ -2775,8 +1982,6 @@ }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2792,8 +1997,6 @@ }, "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2809,8 +2012,6 @@ }, "node_modules/babel-plugin-jest-hoist": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "license": "MIT", "dependencies": { @@ -2825,8 +2026,6 @@ }, "node_modules/babel-preset-current-node-syntax": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -2852,8 +2051,6 @@ }, "node_modules/babel-preset-jest": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "license": "MIT", "dependencies": { @@ -2869,15 +2066,11 @@ }, "node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/body-parser": { "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -2900,8 +2093,6 @@ }, "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -2909,14 +2100,10 @@ }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/body-parser/node_modules/raw-body": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -2930,8 +2117,6 @@ }, "node_modules/brace-expansion": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2941,8 +2126,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { @@ -2954,8 +2137,6 @@ }, "node_modules/browserslist": { "version": "4.25.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", - "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", "dev": true, "funding": [ { @@ -2987,8 +2168,6 @@ }, "node_modules/bs-logger": { "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, "license": "MIT", "dependencies": { @@ -3000,8 +2179,6 @@ }, "node_modules/bser": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3010,15 +2187,11 @@ }, "node_modules/buffer-from": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, "license": "MIT" }, "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" @@ -3026,8 +2199,6 @@ }, "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", @@ -3039,8 +2210,6 @@ }, "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", @@ -3055,8 +2224,6 @@ }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { @@ -3065,8 +2232,6 @@ }, "node_modules/camelcase": { "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { @@ -3075,8 +2240,6 @@ }, "node_modules/caniuse-lite": { "version": "1.0.30001741", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", - "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", "dev": true, "funding": [ { @@ -3096,8 +2259,6 @@ }, "node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -3113,8 +2274,6 @@ }, "node_modules/chalk/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -3126,8 +2285,6 @@ }, "node_modules/char-regex": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", "engines": { @@ -3136,8 +2293,6 @@ }, "node_modules/ci-info": { "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, "funding": [ { @@ -3152,15 +2307,11 @@ }, "node_modules/cjs-module-lexer": { "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "dev": true, "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3174,8 +2325,6 @@ }, "node_modules/cluster-key-slot": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", "license": "Apache-2.0", "engines": { "node": ">=0.10.0" @@ -3183,8 +2332,6 @@ }, "node_modules/co": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "license": "MIT", "engines": { @@ -3194,15 +2341,11 @@ }, "node_modules/collect-v8-coverage": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true, "license": "MIT" }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3214,22 +2357,16 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, "node_modules/concurrently": { "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", "dev": true, "license": "MIT", "dependencies": { @@ -3256,8 +2393,6 @@ }, "node_modules/content-disposition": { "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -3268,8 +2403,6 @@ }, "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" @@ -3277,15 +2410,11 @@ }, "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.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -3293,14 +2422,10 @@ }, "node_modules/cookie-signature": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, "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", @@ -3312,8 +2437,6 @@ }, "node_modules/create-jest": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3334,8 +2457,6 @@ }, "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", @@ -3348,8 +2469,6 @@ }, "node_modules/date-fns": { "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "dev": true, "license": "MIT", "dependencies": { @@ -3365,8 +2484,6 @@ }, "node_modules/debug": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3382,8 +2499,6 @@ }, "node_modules/dedent": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3397,16 +2512,12 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, "license": "MIT", "peer": true }, "node_modules/deepmerge": { "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "license": "MIT", "engines": { @@ -3415,8 +2526,6 @@ }, "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" @@ -3424,8 +2533,6 @@ }, "node_modules/destroy": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", "engines": { "node": ">= 0.8", @@ -3434,8 +2541,6 @@ }, "node_modules/detect-newline": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", "engines": { @@ -3444,8 +2549,6 @@ }, "node_modules/diff-sequences": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "license": "MIT", "engines": { @@ -3454,8 +2557,6 @@ }, "node_modules/dotenv": { "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -3466,8 +2567,6 @@ }, "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", @@ -3480,21 +2579,15 @@ }, "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.218", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz", - "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==", "dev": true, "license": "ISC" }, "node_modules/emittery": { "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "license": "MIT", "engines": { @@ -3506,15 +2599,11 @@ }, "node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "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" @@ -3522,8 +2611,6 @@ }, "node_modules/error-ex": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "license": "MIT", "dependencies": { @@ -3532,8 +2619,6 @@ }, "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" @@ -3541,8 +2626,6 @@ }, "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" @@ -3550,8 +2633,6 @@ }, "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" @@ -3562,8 +2643,6 @@ }, "node_modules/esbuild": { "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3604,8 +2683,6 @@ }, "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": { @@ -3614,14 +2691,10 @@ }, "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/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "peer": true, @@ -3634,8 +2707,6 @@ }, "node_modules/eslint": { "version": "9.35.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", - "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", "dev": true, "license": "MIT", "peer": true, @@ -3696,8 +2767,6 @@ }, "node_modules/eslint-scope": { "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -3714,8 +2783,6 @@ }, "node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3727,8 +2794,6 @@ }, "node_modules/espree": { "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -3746,8 +2811,6 @@ }, "node_modules/esprima": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", "bin": { @@ -3760,8 +2823,6 @@ }, "node_modules/esquery": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "license": "BSD-3-Clause", "peer": true, @@ -3774,8 +2835,6 @@ }, "node_modules/esrecurse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -3788,8 +2847,6 @@ }, "node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -3799,8 +2856,6 @@ }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -3810,8 +2865,6 @@ }, "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" @@ -3819,8 +2872,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" @@ -3831,8 +2882,6 @@ }, "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" @@ -3840,8 +2889,6 @@ }, "node_modules/execa": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { @@ -3864,8 +2911,6 @@ }, "node_modules/exit": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, "engines": { "node": ">= 0.8.0" @@ -3873,8 +2918,6 @@ }, "node_modules/expect": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "license": "MIT", "dependencies": { @@ -3890,8 +2933,6 @@ }, "node_modules/express": { "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -3936,8 +2977,6 @@ }, "node_modules/express-rate-limit": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.1.0.tgz", - "integrity": "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA==", "license": "MIT", "dependencies": { "ip-address": "10.0.1" @@ -3954,8 +2993,6 @@ }, "node_modules/express/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -3963,20 +3000,14 @@ }, "node_modules/express/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "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-glob": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -3992,8 +3023,6 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { @@ -4005,22 +3034,16 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT", "peer": true }, "node_modules/fastq": { "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, "license": "ISC", "dependencies": { @@ -4029,8 +3052,6 @@ }, "node_modules/fb-watchman": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4039,8 +3060,6 @@ }, "node_modules/file-entry-cache": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "peer": true, @@ -4053,8 +3072,6 @@ }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { @@ -4066,8 +3083,6 @@ }, "node_modules/finalhandler": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -4084,8 +3099,6 @@ }, "node_modules/finalhandler/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -4093,14 +3106,10 @@ }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "peer": true, @@ -4117,8 +3126,6 @@ }, "node_modules/flat-cache": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "peer": true, @@ -4132,16 +3139,12 @@ }, "node_modules/flatted": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC", "peer": true }, "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" @@ -4149,8 +3152,6 @@ }, "node_modules/fresh": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -4158,17 +3159,12 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, "license": "ISC" }, "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": [ @@ -4180,8 +3176,6 @@ }, "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" @@ -4189,8 +3183,6 @@ }, "node_modules/generic-pool": { "version": "3.9.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", - "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", "license": "MIT", "engines": { "node": ">= 4" @@ -4198,8 +3190,6 @@ }, "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": { @@ -4208,8 +3198,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -4218,8 +3206,6 @@ }, "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", @@ -4242,8 +3228,6 @@ }, "node_modules/get-package-type": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", "engines": { @@ -4252,8 +3236,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", @@ -4265,8 +3247,6 @@ }, "node_modules/get-stream": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", "engines": { @@ -4278,8 +3258,6 @@ }, "node_modules/get-tsconfig": { "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4291,9 +3269,6 @@ }, "node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -4313,8 +3288,6 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "peer": true, @@ -4327,8 +3300,6 @@ }, "node_modules/globals": { "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", "peer": true, @@ -4341,8 +3312,6 @@ }, "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" @@ -4353,22 +3322,16 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, "license": "MIT" }, "node_modules/handlebars": { "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4389,8 +3352,6 @@ }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -4399,8 +3360,6 @@ }, "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" @@ -4411,8 +3370,6 @@ }, "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" @@ -4423,15 +3380,11 @@ }, "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/http-errors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "license": "MIT", "dependencies": { "depd": "2.0.0", @@ -4446,8 +3399,6 @@ }, "node_modules/human-signals": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4456,8 +3407,6 @@ }, "node_modules/iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -4468,8 +3417,6 @@ }, "node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "peer": true, @@ -4479,8 +3426,6 @@ }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "peer": true, @@ -4497,8 +3442,6 @@ }, "node_modules/import-local": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "license": "MIT", "dependencies": { @@ -4517,8 +3460,6 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { @@ -4527,9 +3468,6 @@ }, "node_modules/inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "license": "ISC", "dependencies": { @@ -4539,14 +3477,10 @@ }, "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/ip-address": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", "license": "MIT", "engines": { "node": ">= 12" @@ -4554,8 +3488,6 @@ }, "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" @@ -4563,15 +3495,11 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, "node_modules/is-core-module": { "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -4586,8 +3514,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -4596,8 +3522,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { @@ -4606,8 +3530,6 @@ }, "node_modules/is-generator-fn": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", "engines": { @@ -4616,8 +3538,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -4629,8 +3549,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { @@ -4639,14 +3557,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/is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", "engines": { @@ -4658,14 +3572,10 @@ }, "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/istanbul-lib-coverage": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4674,8 +3584,6 @@ }, "node_modules/istanbul-lib-instrument": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4691,8 +3599,6 @@ }, "node_modules/istanbul-lib-instrument/node_modules/semver": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -4704,8 +3610,6 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4719,8 +3623,6 @@ }, "node_modules/istanbul-lib-report/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -4732,8 +3634,6 @@ }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4747,8 +3647,6 @@ }, "node_modules/istanbul-reports": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4761,8 +3659,6 @@ }, "node_modules/jest": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", "dependencies": { @@ -4788,8 +3684,6 @@ }, "node_modules/jest-changed-files": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "license": "MIT", "dependencies": { @@ -4803,8 +3697,6 @@ }, "node_modules/jest-circus": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "license": "MIT", "dependencies": { @@ -4835,8 +3727,6 @@ }, "node_modules/jest-cli": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "license": "MIT", "dependencies": { @@ -4869,8 +3759,6 @@ }, "node_modules/jest-config": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4915,8 +3803,6 @@ }, "node_modules/jest-diff": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", "dependencies": { @@ -4931,8 +3817,6 @@ }, "node_modules/jest-docblock": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "license": "MIT", "dependencies": { @@ -4944,8 +3828,6 @@ }, "node_modules/jest-each": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4961,8 +3843,6 @@ }, "node_modules/jest-environment-node": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "license": "MIT", "dependencies": { @@ -4979,8 +3859,6 @@ }, "node_modules/jest-get-type": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "license": "MIT", "engines": { @@ -4989,8 +3867,6 @@ }, "node_modules/jest-haste-map": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "license": "MIT", "dependencies": { @@ -5015,8 +3891,6 @@ }, "node_modules/jest-leak-detector": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "license": "MIT", "dependencies": { @@ -5029,8 +3903,6 @@ }, "node_modules/jest-matcher-utils": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { @@ -5045,8 +3917,6 @@ }, "node_modules/jest-message-util": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { @@ -5066,8 +3936,6 @@ }, "node_modules/jest-mock": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "license": "MIT", "dependencies": { @@ -5081,8 +3949,6 @@ }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", "engines": { @@ -5099,8 +3965,6 @@ }, "node_modules/jest-regex-util": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "license": "MIT", "engines": { @@ -5109,8 +3973,6 @@ }, "node_modules/jest-resolve": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "license": "MIT", "dependencies": { @@ -5130,8 +3992,6 @@ }, "node_modules/jest-resolve-dependencies": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "license": "MIT", "dependencies": { @@ -5144,8 +4004,6 @@ }, "node_modules/jest-runner": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5177,8 +4035,6 @@ }, "node_modules/jest-runtime": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5211,8 +4067,6 @@ }, "node_modules/jest-snapshot": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "license": "MIT", "dependencies": { @@ -5243,8 +4097,6 @@ }, "node_modules/jest-snapshot/node_modules/semver": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -5256,8 +4108,6 @@ }, "node_modules/jest-util": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { @@ -5274,8 +4124,6 @@ }, "node_modules/jest-validate": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "license": "MIT", "dependencies": { @@ -5292,8 +4140,6 @@ }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", "engines": { @@ -5305,8 +4151,6 @@ }, "node_modules/jest-watcher": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "license": "MIT", "dependencies": { @@ -5325,8 +4169,6 @@ }, "node_modules/jest-worker": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "license": "MIT", "dependencies": { @@ -5341,15 +4183,11 @@ }, "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==", "dev": true, "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", "peer": true, @@ -5362,8 +4200,6 @@ }, "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": { @@ -5375,37 +4211,27 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT", "peer": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT", "peer": true }, "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": { @@ -5417,8 +4243,6 @@ }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "peer": true, @@ -5428,8 +4252,6 @@ }, "node_modules/kleur": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, "license": "MIT", "engines": { @@ -5438,8 +4260,6 @@ }, "node_modules/leven": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, "license": "MIT", "engines": { @@ -5448,8 +4268,6 @@ }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "peer": true, @@ -5463,15 +4281,11 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "peer": true, @@ -5487,30 +4301,22 @@ }, "node_modules/lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true, "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true, "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT", "peer": true }, "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": { @@ -5519,15 +4325,11 @@ }, "node_modules/lru-cache/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/make-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -5542,8 +4344,6 @@ }, "node_modules/make-dir/node_modules/semver": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -5555,15 +4355,11 @@ }, "node_modules/make-error": { "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true, "license": "ISC" }, "node_modules/makeerror": { "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5572,29 +4368,21 @@ }, "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/mcp-server-feature-reference-embedded-oauth": { - "resolved": "embedded-oauth", - "link": true - }, "node_modules/mcp-server-feature-reference-external-auth-server": { - "resolved": "external-oauth/auth-server", + "resolved": "auth-server", "link": true }, "node_modules/mcp-server-feature-reference-external-mcp-server": { - "resolved": "external-oauth/mcp-server", + "resolved": "mcp-server", "link": true }, "node_modules/media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5602,8 +4390,6 @@ }, "node_modules/merge-descriptors": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5611,15 +4397,11 @@ }, "node_modules/merge-stream": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", "engines": { @@ -5628,8 +4410,6 @@ }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5637,8 +4417,6 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -5651,8 +4429,6 @@ }, "node_modules/mime": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", "bin": { "mime": "cli.js" @@ -5663,8 +4439,6 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5672,8 +4446,6 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -5684,8 +4456,6 @@ }, "node_modules/mimic-fn": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { @@ -5694,8 +4464,6 @@ }, "node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -5707,8 +4475,6 @@ }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", "funding": { @@ -5717,21 +4483,15 @@ }, "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/natural-compare": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5739,29 +4499,21 @@ }, "node_modules/neo-async": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, "license": "MIT" }, "node_modules/node-int64": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true, "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", "dev": true, "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", "engines": { @@ -5770,8 +4522,6 @@ }, "node_modules/npm-run-path": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { @@ -5783,8 +4533,6 @@ }, "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" @@ -5792,8 +4540,6 @@ }, "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" @@ -5804,8 +4550,6 @@ }, "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" @@ -5816,8 +4560,6 @@ }, "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" @@ -5825,8 +4567,6 @@ }, "node_modules/onetime": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { @@ -5841,8 +4581,6 @@ }, "node_modules/optionator": { "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "peer": true, @@ -5860,8 +4598,6 @@ }, "node_modules/p-limit": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5876,8 +4612,6 @@ }, "node_modules/p-locate": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "peer": true, @@ -5893,8 +4627,6 @@ }, "node_modules/p-try": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "license": "MIT", "engines": { @@ -5903,8 +4635,6 @@ }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "peer": true, @@ -5917,8 +4647,6 @@ }, "node_modules/parse-json": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -5936,8 +4664,6 @@ }, "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" @@ -5945,8 +4671,6 @@ }, "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -5955,8 +4679,6 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", "engines": { @@ -5965,8 +4687,6 @@ }, "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" @@ -5974,28 +4694,20 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, "license": "MIT" }, "node_modules/path-to-regexp": { "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "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": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { @@ -6007,8 +4719,6 @@ }, "node_modules/pirates": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, "license": "MIT", "engines": { @@ -6017,8 +4727,6 @@ }, "node_modules/pkce-challenge": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", "license": "MIT", "engines": { "node": ">=16.20.0" @@ -6026,8 +4734,6 @@ }, "node_modules/pkg-dir": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6039,8 +4745,6 @@ }, "node_modules/pkg-dir/node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -6053,8 +4757,6 @@ }, "node_modules/pkg-dir/node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -6066,8 +4768,6 @@ }, "node_modules/pkg-dir/node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -6082,8 +4782,6 @@ }, "node_modules/pkg-dir/node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -6095,8 +4793,6 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "peer": true, @@ -6106,8 +4802,6 @@ }, "node_modules/pretty-format": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6121,8 +4815,6 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { @@ -6134,8 +4826,6 @@ }, "node_modules/prompts": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6148,8 +4838,6 @@ }, "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", @@ -6161,8 +4849,6 @@ }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "license": "MIT", "engines": { "node": ">=6" @@ -6170,8 +4856,6 @@ }, "node_modules/pure-rand": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -6187,8 +4871,6 @@ }, "node_modules/qs": { "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" @@ -6202,8 +4884,6 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -6223,8 +4903,6 @@ }, "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" @@ -6232,8 +4910,6 @@ }, "node_modules/raw-body": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -6247,8 +4923,6 @@ }, "node_modules/raw-body/node_modules/iconv-lite": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -6263,15 +4937,11 @@ }, "node_modules/react-is": { "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, "license": "MIT" }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -6280,8 +4950,6 @@ }, "node_modules/resolve": { "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "license": "MIT", "dependencies": { @@ -6301,8 +4969,6 @@ }, "node_modules/resolve-cwd": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "license": "MIT", "dependencies": { @@ -6314,8 +4980,6 @@ }, "node_modules/resolve-cwd/node_modules/resolve-from": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { @@ -6324,8 +4988,6 @@ }, "node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "peer": true, @@ -6335,8 +4997,6 @@ }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", "funding": { @@ -6345,8 +5005,6 @@ }, "node_modules/resolve.exports": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", "dev": true, "license": "MIT", "engines": { @@ -6355,8 +5013,6 @@ }, "node_modules/reusify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -6366,8 +5022,6 @@ }, "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", @@ -6382,8 +5036,6 @@ }, "node_modules/router/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", @@ -6392,8 +5044,6 @@ }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -6416,8 +5066,6 @@ }, "node_modules/rxjs": { "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6426,8 +5074,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -6446,14 +5092,10 @@ }, "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/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": { @@ -6462,8 +5104,6 @@ }, "node_modules/send": { "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -6486,8 +5126,6 @@ }, "node_modules/send/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -6495,14 +5133,10 @@ }, "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/send/node_modules/encodeurl": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -6510,8 +5144,6 @@ }, "node_modules/serve-static": { "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", @@ -6525,14 +5157,10 @@ }, "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" @@ -6543,8 +5171,6 @@ }, "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" @@ -6552,8 +5178,6 @@ }, "node_modules/shell-quote": { "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "dev": true, "license": "MIT", "engines": { @@ -6565,8 +5189,6 @@ }, "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", @@ -6584,8 +5206,6 @@ }, "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", @@ -6600,8 +5220,6 @@ }, "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", @@ -6618,8 +5236,6 @@ }, "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", @@ -6637,22 +5253,16 @@ }, "node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, "license": "ISC" }, "node_modules/sisteransi": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true, "license": "MIT" }, "node_modules/slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", "engines": { @@ -6661,8 +5271,6 @@ }, "node_modules/source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -6671,8 +5279,6 @@ }, "node_modules/source-map-support": { "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "license": "MIT", "dependencies": { @@ -6682,21 +5288,15 @@ }, "node_modules/spawn-command": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, "node_modules/sprintf-js": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/stack-utils": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6708,8 +5308,6 @@ }, "node_modules/stack-utils/node_modules/escape-string-regexp": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "license": "MIT", "engines": { @@ -6718,8 +5316,6 @@ }, "node_modules/statuses": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -6727,8 +5323,6 @@ }, "node_modules/string-length": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6741,8 +5335,6 @@ }, "node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -6756,8 +5348,6 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -6769,8 +5359,6 @@ }, "node_modules/strip-bom": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", "engines": { @@ -6779,8 +5367,6 @@ }, "node_modules/strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { @@ -6789,8 +5375,6 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { @@ -6802,8 +5386,6 @@ }, "node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6818,8 +5400,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", "engines": { @@ -6831,8 +5411,6 @@ }, "node_modules/test-exclude": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "license": "ISC", "dependencies": { @@ -6846,15 +5424,11 @@ }, "node_modules/tmpl": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6866,8 +5440,6 @@ }, "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" @@ -6875,8 +5447,6 @@ }, "node_modules/tree-kill": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", "bin": { @@ -6885,8 +5455,6 @@ }, "node_modules/ts-api-utils": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", "engines": { @@ -6898,8 +5466,6 @@ }, "node_modules/ts-jest": { "version": "29.4.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.1.tgz", - "integrity": "sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==", "dev": true, "license": "MIT", "dependencies": { @@ -6951,8 +5517,6 @@ }, "node_modules/ts-jest/node_modules/semver": { "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -6964,8 +5528,6 @@ }, "node_modules/ts-jest/node_modules/type-fest": { "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -6977,15 +5539,11 @@ }, "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==", "dev": true, "license": "0BSD" }, "node_modules/tsx": { "version": "4.20.5", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz", - "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", "dev": true, "license": "MIT", "dependencies": { @@ -7004,8 +5562,6 @@ }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "peer": true, @@ -7018,8 +5574,6 @@ }, "node_modules/type-detect": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, "license": "MIT", "engines": { @@ -7028,8 +5582,6 @@ }, "node_modules/type-fest": { "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -7041,8 +5593,6 @@ }, "node_modules/type-is": { "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", "dependencies": { "media-typer": "0.3.0", @@ -7054,8 +5604,6 @@ }, "node_modules/typescript": { "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7068,8 +5616,6 @@ }, "node_modules/typescript-eslint": { "version": "8.43.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.43.0.tgz", - "integrity": "sha512-FyRGJKUGvcFekRRcBKFBlAhnp4Ng8rhe8tuvvkR9OiU0gfd4vyvTRQHEckO6VDlH57jbeUQem2IpqPq9kLJH+w==", "dev": true, "license": "MIT", "dependencies": { @@ -7092,8 +5638,6 @@ }, "node_modules/uglify-js": { "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "license": "BSD-2-Clause", "optional": true, @@ -7106,15 +5650,11 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, "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" @@ -7122,8 +5662,6 @@ }, "node_modules/update-browserslist-db": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -7153,8 +5691,6 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -7162,8 +5698,6 @@ }, "node_modules/utils-merge": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -7171,8 +5705,6 @@ }, "node_modules/v8-to-istanbul": { "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", "dependencies": { @@ -7186,8 +5718,6 @@ }, "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" @@ -7195,8 +5725,6 @@ }, "node_modules/walker": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -7205,8 +5733,6 @@ }, "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" @@ -7220,8 +5746,6 @@ }, "node_modules/word-wrap": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "peer": true, @@ -7231,15 +5755,11 @@ }, "node_modules/wordwrap": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7256,14 +5776,10 @@ }, "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/write-file-atomic": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "license": "ISC", "dependencies": { @@ -7276,8 +5792,6 @@ }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -7286,14 +5800,10 @@ }, "node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -7311,8 +5821,6 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { @@ -7321,8 +5829,6 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { @@ -7334,8 +5840,6 @@ }, "node_modules/zod": { "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -7343,66 +5847,10 @@ }, "node_modules/zod-to-json-schema": { "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", "license": "ISC", "peerDependencies": { "zod": "^3.24.1" } - }, - "separate-mode/auth-server": { - "name": "mcp-server-feature-reference-auth-server", - "version": "0.1.0", - "extraneous": true, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.15.1", - "@redis/client": "^1.6.0", - "cors": "^2.8.5", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "express-rate-limit": "^8.0.1", - "raw-body": "^3.0.0" - }, - "devDependencies": { - "@eslint/js": "^9.15.0", - "@types/content-type": "^1.1.8", - "@types/cors": "^2.8.17", - "@types/express": "^5.0.0", - "@types/jest": "^29.5.14", - "@types/node": "^22.10.0", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "typescript-eslint": "^8.18.0" - } - }, - "separate-mode/mcp-server": { - "name": "mcp-server-feature-reference-mcp-server", - "version": "0.1.0", - "extraneous": true, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.15.1", - "@redis/client": "^1.6.0", - "cors": "^2.8.5", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "express-rate-limit": "^8.0.1", - "raw-body": "^3.0.0" - }, - "devDependencies": { - "@eslint/js": "^9.15.0", - "@types/content-type": "^1.1.8", - "@types/cors": "^2.8.17", - "@types/express": "^5.0.0", - "@types/jest": "^29.5.14", - "@types/node": "^22.10.0", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "typescript-eslint": "^8.18.0" - } } } } diff --git a/package.json b/package.json index 665252e..ba39d24 100644 --- a/package.json +++ b/package.json @@ -1,42 +1,29 @@ { "name": "mcp-server-feature-reference", "version": "0.1.0", - "description": "Feature Reference MCP Server - Examples for Embedded and External OAuth", + "description": "Feature Reference MCP Server - Example with External OAuth", "private": true, "workspaces": [ - "embedded-oauth", - "external-oauth/auth-server", - "external-oauth/mcp-server" + "auth-server", + "mcp-server" ], "scripts": { - "dev:embedded": "npm run dev --workspace=embedded-oauth", - "dev:auth-server": "npm run dev --workspace=external-oauth/auth-server", - "dev:mcp-server": "npm run dev --workspace=external-oauth/mcp-server", - "dev:external": "concurrently -n \"AUTH,MCP\" -c \"yellow,cyan\" \"npm run dev:auth-server\" \"npm run dev:mcp-server\"", + "dev:auth-server": "npm run dev --workspace=auth-server", + "dev:mcp-server": "npm run dev --workspace=mcp-server", + "dev": "concurrently -n \"AUTH,MCP\" -c \"yellow,cyan\" \"npm run dev:auth-server\" \"npm run dev:mcp-server\"", "build": "npm run build --workspaces --if-present", - "build:embedded": "npm run build --workspace=embedded-oauth", - "build:external": "npm run build --workspace=external-oauth/auth-server && npm run build --workspace=external-oauth/mcp-server", - "start:embedded": "npm run start --workspace=embedded-oauth", - "start:auth-server": "npm run start --workspace=external-oauth/auth-server", - "start:mcp-server": "npm run start --workspace=external-oauth/mcp-server", - "start:external": "concurrently -n \"AUTH,MCP\" -c \"yellow,cyan\" \"npm run start:auth-server\" \"npm run start:mcp-server\"", + "start:auth-server": "npm run start --workspace=auth-server", + "start:mcp-server": "npm run start --workspace=mcp-server", + "start": "concurrently -n \"AUTH,MCP\" -c \"yellow,cyan\" \"npm run start:auth-server\" \"npm run start:mcp-server\"", "test": "npm run test --workspaces --if-present", - "test:embedded": "npm run test --workspace=embedded-oauth", - "test:external": "npm run test --workspace=external-oauth/auth-server && npm run test --workspace=external-oauth/mcp-server", - "test:e2e:embedded": "./scripts/test-embedded-e2e.sh", - "test:e2e:external": "./scripts/test-external-e2e.sh", - "test:e2e": "npm run test:e2e:embedded && npm run test:e2e:external", + "test:e2e": "./scripts/test-e2e.sh", "lint": "npm run lint --workspaces --if-present", - "lint:embedded": "npm run lint --workspace=embedded-oauth", - "lint:external": "npm run lint --workspace=external-oauth/auth-server && npm run lint --workspace=external-oauth/mcp-server", - "typecheck": "npm run typecheck --workspaces --if-present", - "typecheck:embedded": "npm run typecheck --workspace=embedded-oauth", - "typecheck:external": "npm run typecheck --workspace=external-oauth/auth-server && npm run typecheck --workspace=external-oauth/mcp-server" + "typecheck": "npm run typecheck --workspaces --if-present" }, "devDependencies": { "concurrently": "^8.2.0" diff --git a/scripts/test-external-e2e.sh b/scripts/test-e2e.sh similarity index 90% rename from scripts/test-external-e2e.sh rename to scripts/test-e2e.sh index 3c61ee6..a7e1514 100755 --- a/scripts/test-external-e2e.sh +++ b/scripts/test-e2e.sh @@ -2,10 +2,10 @@ set -e echo "==================================================" -echo "End-to-End Test - External OAuth" +echo "End-to-End Test - OAuth Flow and MCP Features" echo "==================================================" echo "This script tests the complete OAuth flow and MCP features" -echo "using external auth server and MCP server." +echo "using auth server and MCP server." echo "" # Kill any existing servers @@ -17,13 +17,12 @@ sleep 2 # Use environment variables if available, otherwise defaults AUTH_SERVER="${AUTH_SERVER_URL:-http://localhost:3001}" MCP_SERVER="${BASE_URI:-http://localhost:3232}" -USER_ID="e2e-separate-$(date +%s)" +USER_ID="e2e-test-$(date +%s)" echo "🔧 Configuration:" echo " Auth Server: $AUTH_SERVER" echo " MCP Server: $MCP_SERVER" echo " User ID: $USER_ID" -echo " Auth Mode: ${AUTH_MODE:-separate} (from environment)" echo "" # Check prerequisites @@ -37,16 +36,9 @@ if ! docker ps | grep -q redis; then fi echo "✅ Redis is running" -# Check if wrong mode is set -if [ "${AUTH_MODE}" = "integrated" ]; then - echo "⚠️ AUTH_MODE is set to 'integrated' but this script tests separate mode" - echo " Either run: AUTH_MODE=separate $0" - echo " Or use: ./scripts/test-integrated-e2e-fixed.sh" -fi - # Start auth server echo "Starting auth server..." -(cd external-oauth/auth-server && npm start) & +(cd auth-server && npm start) & AUTH_PID=$! sleep 5 @@ -58,9 +50,9 @@ if ! curl -s -f "$AUTH_SERVER/health" > /dev/null; then fi echo "✅ Auth server is running (PID: $AUTH_PID)" -# Start MCP server with external OAuth -echo "Starting MCP server with external OAuth..." -(cd external-oauth/mcp-server && npm start) & +# Start MCP server with OAuth delegation..." +echo "Starting MCP server..." +(cd mcp-server && npm start) & MCP_PID=$! sleep 5 @@ -85,7 +77,7 @@ echo "=================================================" # This would typically be done once during app setup, not for each user echo "📝 Step 1: Register OAuth client with auth server" CLIENT_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" \ - -d "{\"client_name\":\"e2e-separate-client\",\"redirect_uris\":[\"http://localhost:3000/callback\"]}" \ + -d "{\"client_name\":\"e2e-test-client\",\"redirect_uris\":[\"http://localhost:3000/callback\"]}" \ "$AUTH_SERVER/register") CLIENT_ID=$(echo "$CLIENT_RESPONSE" | jq -r .client_id) @@ -105,7 +97,7 @@ echo " Code verifier generated" # Include state parameter for CSRF protection echo "" echo "🎫 Step 3: Get authorization code from auth server" -STATE_PARAM="separate-test-$(date +%s)" +STATE_PARAM="e2e-test-$(date +%s)" AUTH_URL="$AUTH_SERVER/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://localhost:3000/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=$STATE_PARAM" AUTH_PAGE=$(curl -s "$AUTH_URL") @@ -167,7 +159,7 @@ echo "📱 Step 1: Initialize MCP session with MCP server" INIT_RESPONSE=$(curl -i -s -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Accept: application/json, text/event-stream" \ -X POST -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":"init","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"e2e-separate","version":"1.0"}}}' \ + -d '{"jsonrpc":"2.0","id":"init","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"e2e-test","version":"1.0"}}}' \ "$MCP_SERVER/mcp") # Extract session ID from response header @@ -202,7 +194,7 @@ if echo "$TOOLS_RESPONSE" | grep -q "event: message"; then -H "Accept: application/json, text/event-stream" \ -H "Mcp-Session-Id: $SESSION_ID" \ -X POST -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":"echo","method":"tools/call","params":{"name":"echo","arguments":{"message":"Separate mode working!"}}}' \ + -d '{"jsonrpc":"2.0","id":"echo","method":"tools/call","params":{"name":"echo","arguments":{"message":"E2E working!"}}}' \ "$MCP_SERVER/mcp") if echo "$ECHO_RESPONSE" | grep -q "event: message"; then @@ -276,11 +268,11 @@ else fi echo "" -echo "EXTERNAL OAUTH E2E TEST COMPLETE!" -echo "===================================" +echo "E2E TEST COMPLETE!" +echo "===================" echo "✅ OAuth flow: Auth server → MCP server delegation working" echo "✅ Token validation: MCP server accepts auth server tokens" -echo "✅ Session management: MCP server creates sessions for external tokens" +echo "✅ Session management: MCP server creates sessions with tokens" echo "" echo "📊 Verification Results:" echo " Tools: $TOOL_COUNT (README: 7) $([ "$TOOL_COUNT" = "7" ] && echo "✅" || echo "❌")" @@ -288,7 +280,7 @@ echo " Resources: $RESOURCE_COUNT (README: 100) $([ "$RESOURCE_COUNT" = "100" echo " Prompts: $PROMPT_COUNT" echo "" echo "🏗️ Architecture Verified:" -echo " ✅ Separate auth server provides OAuth endpoints" +echo " ✅ Auth server provides OAuth endpoints" echo " ✅ MCP server validates tokens via introspection" echo " ✅ Session ownership works across server boundaries" diff --git a/scripts/test-embedded-e2e.sh b/scripts/test-embedded-e2e.sh deleted file mode 100755 index 7a1b608..0000000 --- a/scripts/test-embedded-e2e.sh +++ /dev/null @@ -1,241 +0,0 @@ -#!/bin/bash -set -e - -echo "==================================================" -echo "End-to-End Test - Embedded OAuth" -echo "==================================================" - -# Kill any existing servers -echo "🛑 Cleaning up existing servers..." -pkill -f "node.*dist.*index" || true -pkill -f "tsx watch.*src/index" || true -sleep 2 - -# Use environment variables if available, otherwise defaults -MCP_SERVER="${BASE_URI:-http://localhost:3232}" -USER_ID="e2e-test-$(date +%s)" - -echo "🔧 Configuration:" -echo " MCP Server: $MCP_SERVER" -echo " User ID: $USER_ID" -echo " Auth Mode: ${AUTH_MODE:-integrated} (from environment)" -echo "" - -# Check prerequisites -echo "🔍 Checking prerequisites..." - -# Check Redis -if ! docker ps | grep -q redis; then - echo "❌ Redis not running" - echo " Start Redis: docker compose up -d" - exit 1 -fi -echo "✅ Redis is running" - -# Check if wrong mode is set -if [ "${AUTH_MODE:-integrated}" != "integrated" ]; then - echo "⚠️ AUTH_MODE is set to '${AUTH_MODE}' but this script tests integrated mode" - echo " Either run: AUTH_MODE=integrated $0" - echo " Or use: ./scripts/test-separate-e2e.sh" -fi - -# Start MCP server with embedded OAuth -echo "Starting MCP server with embedded OAuth..." -(cd embedded-oauth && npm start) & -MCP_PID=$! -sleep 5 - -# Check MCP server -if ! curl -s -f "$MCP_SERVER/" > /dev/null; then - echo "❌ MCP server failed to start at $MCP_SERVER" - kill $MCP_PID 2>/dev/null || true - exit 1 -fi -echo "✅ MCP server is running (PID: $MCP_PID)" - -# Clean up on exit -trap "kill $MCP_PID 2>/dev/null || true" EXIT - -echo "🔐 PHASE 1: OAuth Authentication" -echo "================================" - -# OAuth Step 1: Client Registration -# Register a new OAuth client application with the authorization server -# This would typically be done once during app setup, not for each user -CLIENT_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d '{"client_name":"e2e-fixed","redirect_uris":["http://localhost:3000/callback"]}' "$MCP_SERVER/register") -CLIENT_ID=$(echo "$CLIENT_RESPONSE" | jq -r .client_id) -CLIENT_SECRET=$(echo "$CLIENT_RESPONSE" | jq -r .client_secret) - -# OAuth Step 2: Generate PKCE (Proof Key for Code Exchange) parameters -# PKCE adds security to the OAuth flow by preventing authorization code interception attacks -CODE_VERIFIER=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-43) -CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -binary -sha256 | base64 | tr "+/" "-_" | tr -d "=") - -# OAuth Step 3: Authorization Request -# Direct the user to the authorization server's /authorize endpoint -# Include state parameter for CSRF protection -STATE_PARAM="e2e-state-$(date +%s)" - -AUTH_PAGE=$(curl -s "$MCP_SERVER/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://localhost:3000/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=$STATE_PARAM") -# Extract the authorization code from the HTML response (normally would be in redirect URL) -AUTH_CODE=$(echo "$AUTH_PAGE" | grep -o 'state=[^"&]*' | cut -d= -f2) - -# OAuth Step 4: User Authentication & Authorization -# In a real flow, the user would authenticate with the auth server here -# For testing, we simulate this with the fake upstream auth endpoint -CALLBACK_RESPONSE=$(curl -s -i "$MCP_SERVER/mock-upstream-idp/callback?state=$AUTH_CODE&code=mock-auth-code&userId=$USER_ID") - -# OAuth Step 5: Authorization Code Redirect -# Verify the auth server redirects back to our redirect_uri with the code and state -# The state parameter MUST match what we sent to prevent CSRF attacks -LOCATION_HEADER=$(echo "$CALLBACK_RESPONSE" | grep -i "^location:" | tr -d '\r') -if echo "$LOCATION_HEADER" | grep -q "state=$STATE_PARAM"; then - echo "✅ State parameter verified in callback" -else - echo "❌ State parameter mismatch or missing in callback" - echo " Expected state: $STATE_PARAM" - echo " Location header: $LOCATION_HEADER" - exit 1 -fi - -# OAuth Step 6: Token Exchange -# Exchange the authorization code for access and refresh tokens -# Include the PKCE code_verifier to prove we initiated the flow -TOKEN_RESPONSE=$(curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=authorization_code&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&code=$AUTH_CODE&redirect_uri=http://localhost:3000/callback&code_verifier=$CODE_VERIFIER" "$MCP_SERVER/token") - -ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r .access_token) -echo "✅ OAuth complete, token: ${ACCESS_TOKEN:0:15}..." - -echo "" -echo "🧪 PHASE 2: MCP Feature Testing" -echo "===============================" - -# Step 1: Initialize MCP session (no session ID header) -echo "" -echo "📱 Step 1: Initialize MCP session" -INIT_RESPONSE=$(curl -i -s -H "Authorization: Bearer $ACCESS_TOKEN" \ - -H "Accept: application/json, text/event-stream" \ - -X POST -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":"init","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"e2e-test","version":"1.0"}}}' \ - "$MCP_SERVER/mcp") - -# Extract session ID from response header -SESSION_ID=$(echo "$INIT_RESPONSE" | grep -i "mcp-session-id:" | cut -d' ' -f2 | tr -d '\r') - -if [ -n "$SESSION_ID" ]; then - echo " ✅ Session initialized: $SESSION_ID" -else - echo " ❌ No session ID in response headers" - echo "Headers:" - echo "$INIT_RESPONSE" | head -20 - exit 1 -fi - -# Step 2: Test tools with session ID -echo "" -echo "🔧 Step 2: Test Tools" -TOOLS_RESPONSE=$(curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ - -H "Accept: application/json, text/event-stream" \ - -H "Mcp-Session-Id: $SESSION_ID" \ - -X POST -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":"tools","method":"tools/list"}' \ - "$MCP_SERVER/mcp") - -if echo "$TOOLS_RESPONSE" | grep -q "event: message"; then - TOOLS_JSON=$(echo "$TOOLS_RESPONSE" | grep "^data: " | sed 's/^data: //') - TOOL_COUNT=$(echo "$TOOLS_JSON" | jq '.result.tools | length') - echo " ✅ Tools: $TOOL_COUNT (README claims: 7)" - - # Test echo tool - ECHO_RESPONSE=$(curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ - -H "Accept: application/json, text/event-stream" \ - -H "Mcp-Session-Id: $SESSION_ID" \ - -X POST -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":"echo","method":"tools/call","params":{"name":"echo","arguments":{"message":"E2E working!"}}}' \ - "$MCP_SERVER/mcp") - - if echo "$ECHO_RESPONSE" | grep -q "event: message"; then - ECHO_JSON=$(echo "$ECHO_RESPONSE" | grep "^data: " | sed 's/^data: //') - ECHO_RESULT=$(echo "$ECHO_JSON" | jq -r '.result.content[0].text') - echo " 🔊 Echo test: '$ECHO_RESULT'" - fi -else - echo " ❌ Tools test failed: $TOOLS_RESPONSE" -fi - -# Step 3: Test resources -echo "" -echo "📚 Step 3: Test Resources (counting all pages)" -TOTAL_RESOURCES=0 -CURSOR="" -PAGE=1 - -while true; do - if [ -n "$CURSOR" ]; then - PARAMS="{\"cursor\":\"$CURSOR\"}" - else - PARAMS="{}" - fi - - RESOURCES_RESPONSE=$(curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ - -H "Accept: application/json, text/event-stream" \ - -H "Mcp-Session-Id: $SESSION_ID" \ - -X POST -H "Content-Type: application/json" \ - -d "{\"jsonrpc\":\"2.0\",\"id\":\"resources$PAGE\",\"method\":\"resources/list\",\"params\":$PARAMS}" \ - "$MCP_SERVER/mcp") - - if echo "$RESOURCES_RESPONSE" | grep -q "event: message"; then - RESOURCES_JSON=$(echo "$RESOURCES_RESPONSE" | grep "^data: " | sed 's/^data: //') - PAGE_COUNT=$(echo "$RESOURCES_JSON" | jq '.result.resources | length') - NEXT_CURSOR=$(echo "$RESOURCES_JSON" | jq -r '.result.nextCursor // empty') - - TOTAL_RESOURCES=$((TOTAL_RESOURCES + PAGE_COUNT)) - echo " 📄 Page $PAGE: $PAGE_COUNT resources (total: $TOTAL_RESOURCES)" - - if [ -z "$NEXT_CURSOR" ]; then - break - fi - CURSOR="$NEXT_CURSOR" - PAGE=$((PAGE + 1)) - else - echo " ❌ Resources page $PAGE failed: $RESOURCES_RESPONSE" - break - fi -done - -RESOURCE_COUNT=$TOTAL_RESOURCES -echo " 📊 Total Resources: $RESOURCE_COUNT (README claims: 100)" - -# Step 4: Test prompts -echo "" -echo "💭 Step 4: Test Prompts" -PROMPTS_RESPONSE=$(curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ - -H "Accept: application/json, text/event-stream" \ - -H "Mcp-Session-Id: $SESSION_ID" \ - -X POST -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":"prompts","method":"prompts/list"}' \ - "$MCP_SERVER/mcp") - -if echo "$PROMPTS_RESPONSE" | grep -q "event: message"; then - PROMPTS_JSON=$(echo "$PROMPTS_RESPONSE" | grep "^data: " | sed 's/^data: //') - PROMPT_COUNT=$(echo "$PROMPTS_JSON" | jq '.result.prompts | length') - echo " 💬 Prompts: $PROMPT_COUNT" -else - echo " ❌ Prompts test failed: $PROMPTS_RESPONSE" -fi - -echo "" -echo "EMBEDDED OAUTH E2E TEST COMPLETE!" -echo "=====================================" -echo "📊 Verification Results:" -echo " Tools: $TOOL_COUNT (README: 7) $([ "$TOOL_COUNT" = "7" ] && echo "✅" || echo "❌")" -echo " Resources: $RESOURCE_COUNT (README: 100) $([ "$RESOURCE_COUNT" = "100" ] && echo "✅" || echo "❌")" -echo " Prompts: $PROMPT_COUNT" -echo " OAuth flow: ✅ Working" -echo " MCP features: ✅ Working" - -# Kill the server explicitly (trap might not catch npm's child process) -kill $MCP_PID 2>/dev/null || true -pkill -P $MCP_PID 2>/dev/null || true -pkill -f "node dist/index.js" 2>/dev/null || true -sleep 1 \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 4e1b920..c2113bd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,8 +13,7 @@ }, "files": [], "references": [ - { "path": "./embedded-oauth" }, - { "path": "./external-oauth/auth-server" }, - { "path": "./external-oauth/mcp-server" } + { "path": "./auth-server" }, + { "path": "./mcp-server" } ] } From 5482d905a92555248226d76f66c2df40634c61f0 Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Mon, 6 Oct 2025 21:38:33 -0400 Subject: [PATCH 04/12] Fix security vulnerabilities identified by CodeQL - Fix information exposure: return generic error instead of stack traces in sse.ts - Add missing rate limiting to root route in index.ts --- mcp-server/src/handlers/sse.ts | 2 +- mcp-server/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mcp-server/src/handlers/sse.ts b/mcp-server/src/handlers/sse.ts index 409d792..9da3477 100644 --- a/mcp-server/src/handlers/sse.ts +++ b/mcp-server/src/handlers/sse.ts @@ -88,7 +88,7 @@ export async function handleMessage(req: Request, res: Response) { body = JSON.stringify(req.body); } catch (error) { - res.status(400).json(error); + res.status(400).json({ error: "Bad request" }); logger.error('Bad POST request', error as Error, { sessionId, contentType: req.headers['content-type'] diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts index d1ed039..e035268 100644 --- a/mcp-server/src/index.ts +++ b/mcp-server/src/index.ts @@ -229,7 +229,7 @@ app.get("/styles.css", staticFileRateLimit, (req, res) => { }); // Splash page -app.get("/", (req, res) => { +app.get("/", staticFileRateLimit, (req, res) => { const splashPath = path.join(__dirname, "static", "index.html"); res.sendFile(splashPath); }); From 500c263176e7af504de05446708c09698f37038d Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Mon, 6 Oct 2025 21:59:26 -0400 Subject: [PATCH 05/12] Fix documentation: rename oauth file and correct test commands - Rename oauth-patterns.md to oauth-architecture-patterns.md for clarity - Fix session-ownership.md test commands to run from mcp-server directory - Update redisTransport.ts path reference to include mcp-server prefix --- ...{oauth-patterns.md => oauth-architecture-patterns.md} | 0 docs/session-ownership.md | 9 ++++++--- 2 files changed, 6 insertions(+), 3 deletions(-) rename docs/{oauth-patterns.md => oauth-architecture-patterns.md} (100%) diff --git a/docs/oauth-patterns.md b/docs/oauth-architecture-patterns.md similarity index 100% rename from docs/oauth-patterns.md rename to docs/oauth-architecture-patterns.md diff --git a/docs/session-ownership.md b/docs/session-ownership.md index 31074ec..cd31c6c 100644 --- a/docs/session-ownership.md +++ b/docs/session-ownership.md @@ -76,7 +76,7 @@ When an MCP server starts handling a session, it subscribes to the channel. When ## Implementation -### Core Functions (src/services/redisTransport.ts) +### Core Functions (mcp-server/src/services/redisTransport.ts) ```typescript export async function setSessionOwner(sessionId: string, userId: string): Promise @@ -135,8 +135,11 @@ redis-cli MONITOR | grep "session:" ## Testing ```bash -npm test -- --testNamePattern="User Session Isolation" -npm test -- --testNamePattern="session ownership" +# Run from mcp-server directory to test session isolation +cd mcp-server && npm test -- --testNamePattern="User Session Isolation" + +# Run from mcp-server directory to test session ownership +cd mcp-server && npm test -- --testNamePattern="Session Ownership" ``` ## References From 1140815c7bec786027151d1947e44ac6cbfc1cb5 Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Tue, 7 Oct 2025 14:36:33 -0400 Subject: [PATCH 06/12] Add graceful degradation when auth server unavailable Server starts immediately and retries auth connection in background. Splash page and health endpoint remain accessible in degraded mode. --- auth-server/src/index.ts | 2 +- mcp-server/src/index.ts | 175 +++++++++++++++++++++++++++------------ 2 files changed, 121 insertions(+), 56 deletions(-) diff --git a/auth-server/src/index.ts b/auth-server/src/index.ts index 25a841e..79ff128 100644 --- a/auth-server/src/index.ts +++ b/auth-server/src/index.ts @@ -14,7 +14,7 @@ console.log('====================================='); console.log('MCP Demonstration Authorization Server'); console.log('====================================='); console.log('This standalone server demonstrates OAuth 2.0'); -console.log('authorization separate from the MCP resource server'); +console.log('authorization for the MCP resource server'); console.log(''); console.log('This is for demonstration purposes only.'); console.log('In production, you would use a real OAuth provider'); diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts index e035268..83cffe0 100644 --- a/mcp-server/src/index.ts +++ b/mcp-server/src/index.ts @@ -17,6 +17,7 @@ import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from "@mo import cors from "cors"; import rateLimit from "express-rate-limit"; import express from "express"; +import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; import { ExternalAuthVerifier } from "./auth/external-verifier.js"; @@ -141,72 +142,60 @@ const staticFileRateLimit = rateLimit({ message: { error: 'too_many_requests', error_description: 'Static file rate limit exceeded' } }); -// Separate mode: MCP server uses external auth server -logger.info('Starting MCP server in SEPARATE mode', { +// MCP server using external auth server +logger.info('Starting MCP server', { baseUri: BASE_URI, port: PORT, authServerUrl: AUTH_SERVER_URL }); -// Fetch metadata from external auth server with retry logic -let authMetadata; -const maxRetries = 5; -const retryDelay = 3000; // 3 seconds - -for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - logger.info(`Attempting to connect to auth server (attempt ${attempt}/${maxRetries})`, { - authServerUrl: AUTH_SERVER_URL +// Auth server state - will be populated asynchronously +let authMetadata: any; +let authServerAvailable = false; + +// OAuth metadata endpoint - responds based on current auth server status +app.get('/.well-known/oauth-authorization-server', (req, res) => { + if (authServerAvailable && authMetadata) { + // Return the auth server metadata + res.json(authMetadata); + } else { + // Auth server unavailable + res.status(503).json({ + error: 'service_unavailable', + error_description: 'Authentication server is currently unavailable. Please try again later.' }); + } +}); - const authMetadataResponse = await fetch(`${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`); - if (!authMetadataResponse.ok) { - throw new Error(`Failed to fetch auth server metadata: ${authMetadataResponse.status} ${authMetadataResponse.statusText}`); - } - authMetadata = await authMetadataResponse.json(); - logger.info('Successfully fetched auth server metadata', { - issuer: authMetadata.issuer, - authorizationEndpoint: authMetadata.authorization_endpoint, - tokenEndpoint: authMetadata.token_endpoint +// Configure bearer auth middleware that checks auth availability dynamically +const bearerAuth: express.RequestHandler = (req, res, next) => { + if (!authServerAvailable) { + // Degraded mode: return 503 for protected endpoints + res.status(503).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Authentication service unavailable', + data: { + authServerUrl: AUTH_SERVER_URL, + hint: 'The authentication server is not responding. Please ensure it is running and try again.' + } + }, + id: null }); - break; // Success, exit retry loop - - } catch (error) { - if (attempt < maxRetries) { - logger.info(`Failed to connect to auth server, retrying in ${retryDelay/1000} seconds...`, { - attempt, - maxRetries, - error: (error as Error).message - }); - await new Promise(resolve => setTimeout(resolve, retryDelay)); - } else { - logger.error('Failed to fetch auth server metadata after all retries', error as Error); - logger.error('Make sure the auth server is running at', undefined, { authServerUrl: AUTH_SERVER_URL }); - process.exit(1); - } + return; } -} -// BACKWARDS COMPATIBILITY: We serve OAuth metadata from the MCP server even in separate mode -// This is technically redundant since the auth server handles all OAuth operations, -// but some clients may expect to find .well-known/oauth-authorization-server on the -// resource server itself. The metadata points to the external auth server endpoints. -app.use(mcpAuthMetadataRouter({ - oauthMetadata: authMetadata, - resourceServerUrl: new URL(BASE_URI), - resourceName: "MCP Feature Reference Server" -})); - -// Configure bearer auth with external verifier -const externalVerifier = new ExternalAuthVerifier(AUTH_SERVER_URL); - -const bearerAuthOptions: BearerAuthMiddlewareOptions = { - verifier: externalVerifier, - resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(new URL(BASE_URI)), + // Auth is available, use the real bearer auth middleware + const externalVerifier = new ExternalAuthVerifier(AUTH_SERVER_URL); + const bearerAuthOptions: BearerAuthMiddlewareOptions = { + verifier: externalVerifier, + resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(new URL(BASE_URI)), + }; + const realBearerAuth = requireBearerAuth(bearerAuthOptions); + realBearerAuth(req, res, next); }; -const bearerAuth = requireBearerAuth(bearerAuthOptions); - // MCP routes (legacy SSE transport) app.get("/sse", cors(corsOptions), bearerAuth, sseHeaders, handleSSEConnection); app.post("/message", cors(corsOptions), bearerAuth, sensitiveDataHeaders, handleMessage); @@ -216,6 +205,19 @@ app.get("/mcp", cors(corsOptions), bearerAuth, handleStreamableHTTP); app.post("/mcp", cors(corsOptions), bearerAuth, handleStreamableHTTP); app.delete("/mcp", cors(corsOptions), bearerAuth, handleStreamableHTTP); +// Health check endpoint +app.get("/health", (req, res) => { + res.json({ + status: authServerAvailable ? 'healthy' : 'degraded', + services: { + mcp: 'operational', + auth: authServerAvailable ? 'operational' : 'unavailable', + redis: 'operational' // Will be checked if Redis connection fails + }, + authServerUrl: AUTH_SERVER_URL + }); +}); + // Static assets app.get("/mcp-logo.png", staticFileRateLimit, (req, res) => { const logoPath = path.join(__dirname, "static", "mcp.png"); @@ -231,7 +233,23 @@ app.get("/styles.css", staticFileRateLimit, (req, res) => { // Splash page app.get("/", staticFileRateLimit, (req, res) => { const splashPath = path.join(__dirname, "static", "index.html"); - res.sendFile(splashPath); + + if (!authServerAvailable) { + // Inject warning banner for degraded mode + let html = fs.readFileSync(splashPath, 'utf8'); + const warningBanner = ` +
+ ⚠️ Authentication Service Unavailable - Server Running in Degraded Mode +
+ The authentication server at ${AUTH_SERVER_URL} is not responding. + MCP endpoints will return errors until the auth server is available. +
+
`; + html = html.replace('', `${warningBanner}`); + res.send(html); + } else { + res.sendFile(splashPath); + } }); // Note: Fake upstream auth routes are not needed in separate mode @@ -250,4 +268,51 @@ app.listen(PORT, () => { url: `http://localhost:${PORT}`, environment: process.env.NODE_ENV || 'development' }); + + // Try to connect to auth server in background (don't block server startup) + connectToAuthServer(); }); + +// Attempt to connect to auth server with retries +async function connectToAuthServer() { + const maxRetries = 5; + const retryDelay = 3000; // 3 seconds + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + logger.info(`Attempting to connect to auth server (attempt ${attempt}/${maxRetries})`, { + authServerUrl: AUTH_SERVER_URL + }); + + const authMetadataResponse = await fetch(`${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`); + if (!authMetadataResponse.ok) { + throw new Error(`Failed to fetch auth server metadata: ${authMetadataResponse.status} ${authMetadataResponse.statusText}`); + } + authMetadata = await authMetadataResponse.json(); + authServerAvailable = true; + logger.info('Successfully connected to auth server', { + issuer: authMetadata.issuer, + authorizationEndpoint: authMetadata.authorization_endpoint, + tokenEndpoint: authMetadata.token_endpoint + }); + break; // Success, exit retry loop + + } catch (error) { + if (attempt < maxRetries) { + logger.info(`Failed to connect to auth server, retrying in ${retryDelay/1000} seconds...`, { + attempt, + maxRetries, + error: (error as Error).message + }); + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } else { + logger.error('Failed to connect to auth server after all retries', error as Error); + logger.warning('MCP server running in degraded mode - authentication unavailable', { + authServerUrl: AUTH_SERVER_URL + }); + logger.warning('Protected endpoints will return 503 until auth server is available'); + // Server continues in degraded mode + } + } + } +} From 7b7aa513f5e810d1ea3c5d2556c65285d8ec74ef Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Tue, 7 Oct 2025 14:36:41 -0400 Subject: [PATCH 07/12] Improve client.js and curl-examples.sh usability Fix PKCE implementation, SSE parsing, error handling, and usage clarity. Add --help flag and improve output messaging for both examples. --- README.md | 13 +- docs/endpoints.md | 4 +- docs/oauth-architecture-patterns.md | 185 +++----------------------- docs/oauth-flow.md | 12 -- examples/README.md | 29 +++- examples/client.js | 158 ++++++++++++++++------ examples/curl-examples.sh | 199 ++++++++++++++++++++++------ 7 files changed, 328 insertions(+), 272 deletions(-) diff --git a/README.md b/README.md index 7d66467..9fc94f6 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,9 @@ npm run dev # Start both auth-server and mcp-server # 4. Test with Inspector npx -y @modelcontextprotocol/inspector + # Connect to http://localhost:3232/mcp +# (Make sure to include the http:// prefix!) ``` For detailed instructions, see [Installation](#installation). @@ -441,13 +443,12 @@ The script: - Check that Redis is running (`docker compose ps`) - Verify the token hasn't expired (tokens last 7 days) -### "Cannot connect to MCP server" -- **Cause**: Incorrect URL or missing path +### "Cannot connect to MCP server" or "Connection Error - Check if your MCP server is running" +- **Cause**: Incorrect URL format or servers not running - **Solution**: - - Use full URL: `http://localhost:3232/mcp` (not just `:3232`) - - Include the `/mcp` path for MCP endpoints - - Ensure you have a valid Bearer token in the Authorization header - + - Use the full URL with http:// prefix: `http://localhost:3232/mcp` + - Ensure **both** auth and MCP servers are running (`npm run dev`) + ### "Cannot connect to Docker daemon" - **Cause**: Docker/OrbStack not running - **Solution**: diff --git a/docs/endpoints.md b/docs/endpoints.md index 2e40e5d..cdcae69 100644 --- a/docs/endpoints.md +++ b/docs/endpoints.md @@ -2,7 +2,7 @@ Complete listing of all endpoints provided by each server in the architecture. -## Auth Server (Port 3001) +## Auth Server Standalone OAuth 2.0 authorization server that handles authentication and token management. @@ -37,7 +37,7 @@ Local simulation of upstream IDP (would be external in production): --- -## MCP Server (Port 3232) +## MCP Server MCP resource server that implements the Model Context Protocol with delegated authentication. diff --git a/docs/oauth-architecture-patterns.md b/docs/oauth-architecture-patterns.md index d33e7b6..af8df69 100644 --- a/docs/oauth-architecture-patterns.md +++ b/docs/oauth-architecture-patterns.md @@ -6,9 +6,9 @@ This document describes different OAuth 2.0 architecture patterns for MCP server Per the MCP specification, "the authorization server may be hosted with the resource server or as a separate entity." This leads to two primary patterns for implementing OAuth in MCP servers. -## Pattern 1: Separate Authorization Server (Implemented) +## Pattern 1: Separate Authorization Server (Recommended) -The current implementation uses this production-ready pattern with separate authorization and resource servers. +The current implementation demonstrates this pattern with separate authorization and resource servers. (Note: the authorization server is for demonstration purposes only.) ### Architecture @@ -19,7 +19,7 @@ The current implementation uses this production-ready pattern with separate auth │ │ │ (port 3001) │validate │ (port 3232) │ │ │<────────│ │ │ │ │ │ token │ Issues tokens │ │ Serves MCP │ -│ │ │ │ │ resources │ +│ │ └──────────────────┘ │ resources │ │ │────────────────────────────────────> │ │ │ │ MCP requests with token │ │ └─────────────┘ └─────────────────┘ @@ -27,11 +27,11 @@ The current implementation uses this production-ready pattern with separate auth ### Components -1. **Authorization Server** (port 3001) +1. **Demo Authorization Server** (port 3001) - Handles OAuth 2.0 authorization flow - Issues and manages tokens - Provides token introspection endpoint (RFC 7662) - - Can be replaced with Auth0, Okta, Google OAuth, etc. + - Should be replaced with Auth0, Okta, Google OAuth, etc. 2. **MCP Resource Server** (port 3232) - Serves MCP protocol resources @@ -39,113 +39,24 @@ The current implementation uses this production-ready pattern with separate auth - Contains no OAuth authorization code - Focuses purely on MCP functionality -### Benefits +### Using a Commercial Auth Provider -- **Standards Compliance**: Follows OAuth 2.0 best practices -- **Flexibility**: Easy to swap auth providers -- **Scalability**: Servers scale independently -- **Security**: Clear security boundaries -- **Maintainability**: Separation of concerns - -### Real-World Use Cases - -- **SaaS Applications**: Using Auth0 or Okta for authentication -- **Enterprise**: Integrating with corporate SSO (SAML, LDAP) -- **Social Login**: Google, GitHub, Facebook authentication -- **Cloud Native**: AWS Cognito, Azure AD integration - -### Implementation Details - -The MCP server validates tokens by calling the auth server's `/introspect` endpoint: - -```typescript -// In MCP server -const response = await fetch(`${AUTH_SERVER_URL}/introspect`, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: `token=${accessToken}` -}); +Replacing the demo auth server with a commercial provider: -const introspection = await response.json(); -if (introspection.active) { - // Token is valid, extract user info - const userId = introspection.sub; -} -``` +1. **Configure provider**: Set up OAuth app in commercial provider (e.g. Auth0/Okta) +2. **Update metadata URL**: Point to provider's discovery endpoint +3. **Configure introspection**: Set up token validation +4. **Update redirect URIs**: Configure allowed callbacks +5. **Migrate users**: Import existing users if needed +6. **Test integration**: Verify full OAuth flow --- -## Pattern 2: Embedded Authorization Server (Alternative) - -An alternative pattern where the OAuth server is embedded within the MCP server. This demonstrates a self-hosted OAuth 2.1 authorization server running in the same process as the MCP server. - -### Architecture - -``` -┌─────────────┐ ┌─────────────────────────┐ -│ │ OAuth │ │ -│ MCP Client │────────>│ MCP Server │ -│ │ │ ┌──────────────────┐ │ -│ │<────────│ │ OAuth Server │ │ -│ │ token │ └──────────────────┘ │ -│ │ │ ┌──────────────────┐ │ -│ │────────>│ │ MCP Resources │ │ -│ │ MCP │ └──────────────────┘ │ -└─────────────┘ └─────────────────────────┘ -``` - -### Upstream Delegation Pattern - -Embedded OAuth often delegates user authentication to an upstream identity provider while maintaining control over token issuance: - -``` -MCP Client MCP+OAuth Server Upstream IDP - │ │ (Corporate SSO) - │──1. /authorize───────────────>│ │ - │<───(show auth page)───────────│ │ - │ │ │ - │──2. Click "Continue"──────────>│ │ - │<──redirect to upstream────────│ │ - │ │ │ - │──3. /upstream/authorize────────────────────────────────────────>│ - │<──(authenticate user)──────────────────────────────────────────│ - │──4. Provide credentials────────────────────────────────────────>│ - │<──redirect with userId─────────────────────────────────────────│ - │ │ │ - │──5. /callback with userId───────>│ │ - │ │──(validate userId) │ - │ │──(issue MCP tokens) │ - │<──redirect with auth code──────│ │ - │ │ │ - │──6. /token (exchange code)─────>│ │ - │<──MCP access token─────────────│ │ -``` - -### Characteristics - -- **Single Server**: OAuth and MCP in one process -- **Port**: Typically runs on single port (e.g., 3232) -- **Token Validation**: In-process, direct database access -- **Deployment**: Simpler, fewer moving parts -- **Upstream Delegation**: Can delegate authentication to corporate SSO while controlling token issuance +## Pattern 2: Embedded Authorization Server (Alternative/Legacy) -### Benefits +The MCP spec describes an alternative pattern where the OAuth server is embedded within the MCP server. However, this pattern is not recommended in the general case, and is not demonstrated in this codebase. -- **Simplicity**: Single server to deploy and manage -- **Performance**: No network hop for token validation -- **Self-Contained**: All functionality in one codebase -- **Control**: Full control over token issuance while leveraging existing identity infrastructure - -### Drawbacks - -- **Coupling**: Auth and MCP logic intertwined -- **Scalability**: Can't scale auth independently -- **Flexibility**: Harder to switch auth providers -- **Updates**: Auth changes require MCP server updates - -### Use Cases +### Possible Use Cases - **Enterprise Deployments**: Organizations needing control over OAuth token issuance while leveraging existing corporate identity infrastructure (LDAP, Active Directory, SAML) - **Proof of Concepts**: Quick prototypes and demonstrations @@ -153,39 +64,6 @@ MCP Client MCP+OAuth Server Upstream IDP - **Isolated Systems**: Air-gapped environments without external connectivity - **Custom Auth Requirements**: Specialized authentication needs not met by standard providers -### Typical Configuration - -Environment variables for embedded pattern: -```bash -BASE_URI=http://localhost:3232 # Single server URL -PORT=3232 # Single port for OAuth + MCP -REDIS_URL=redis://localhost:6379 # Session storage -UPSTREAM_IDP_URL=https://corp.sso # Optional: upstream identity provider -``` - -### Code Organization - -Typical structure for embedded OAuth implementation: -``` -src/ -├── index.ts # Main entry point (MCP + OAuth + routing) -├── auth/ -│ ├── provider.ts # OAuth server implementation -│ └── auth-core.ts # Token generation, PKCE utilities -├── services/ -│ ├── mcp.ts # MCP protocol implementation -│ ├── auth.ts # Auth service integration -│ ├── redis-auth.ts # Redis auth operations -│ └── redisTransport.ts # Redis-backed transport -├── handlers/ -│ ├── oauth.ts # OAuth endpoints (/authorize, /token) -│ ├── mcp-shttp.ts # Streamable HTTP handler -│ ├── mcp-sse.ts # SSE handler -│ └── upstream-idp.ts # Upstream IDP integration -└── utils/ - └── logger.ts # Structured logging -``` - --- ## Comparison @@ -195,42 +73,11 @@ src/ | **Architecture** | 2+ servers | 1 server | | **OAuth endpoints** | On auth server | On MCP server | | **Token validation** | Remote (introspection) | In-process | -| **Deployment** | More complex | Simpler | -| **Scalability** | Independent scaling | Coupled scaling | -| **Provider integration** | Easy (standard APIs) | Difficult | -| **Code organization** | Clear separation | Mixed concerns | -| **Network hops** | Additional for validation | None for validation | | **Upstream IDP support** | Via auth server | Direct integration | | **Production readiness** | ✅ Recommended | ⚠️ Limited use cases | --- -## Migration Path - -### From Embedded to Separate - -If starting with embedded OAuth, migration to separate servers involves: - -1. **Extract auth code**: Move OAuth handlers to separate service -2. **Implement introspection**: Add RFC 7662 endpoint -3. **Update MCP server**: Replace in-process validation with introspection calls -4. **Update configuration**: Point MCP server to auth server URL -5. **Migrate upstream IDP**: Move delegation logic to auth server -6. **Test thoroughly**: Ensure token flow works correctly - -### From Separate to Commercial Provider - -Replacing the demo auth server with a commercial provider: - -1. **Configure provider**: Set up OAuth app in Auth0/Okta -2. **Update metadata URL**: Point to provider's discovery endpoint -3. **Configure introspection**: Set up token validation -4. **Update redirect URIs**: Configure allowed callbacks -5. **Migrate users**: Import existing users if needed -6. **Test integration**: Verify full OAuth flow - ---- - ## Best Practices 1. **Use separate servers** for production deployments diff --git a/docs/oauth-flow.md b/docs/oauth-flow.md index 7df9b1e..ca46220 100644 --- a/docs/oauth-flow.md +++ b/docs/oauth-flow.md @@ -194,18 +194,6 @@ This ensures only the client that initiated the flow can exchange the code, even --- -## Architecture Benefits - -The separate server architecture provides: - -1. **Standards Compliance**: Follows OAuth 2.0 best practices for resource/authorization server separation -2. **Flexibility**: Auth server can be replaced with Auth0, Okta, or other providers -3. **Scalability**: Auth and MCP servers can scale independently based on load -4. **Security**: Token validation via introspection endpoint (RFC 7662) -5. **Maintainability**: Clear separation of authentication and business logic - ---- - ## References - [RFC 6749: OAuth 2.0 Framework](https://datatracker.ietf.org/doc/html/rfc6749) diff --git a/examples/README.md b/examples/README.md index dfd72c9..76c9280 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,21 +24,28 @@ Shell script demonstrating API interactions using curl. - Resource listing and reading - Prompt operations -**Usage:** +**Three-step workflow:** ```bash # Make executable chmod +x curl-examples.sh -# Register client (step 1) +# Step 1: Setup - Register OAuth client and get instructions ./curl-examples.sh -# After getting token (via MCP Inspector) +# Step 2: Create session - Get an access token (via Inspector or client.js), then: ./curl-examples.sh YOUR_ACCESS_TOKEN +# → Initializes MCP session and displays session ID -# With session ID +# Step 3: Run examples - Use both access token and session ID: ./curl-examples.sh YOUR_ACCESS_TOKEN YOUR_SESSION_ID +# → Demonstrates all MCP features (tools, resources, prompts) ``` +**Quick reference:** +- Run `./curl-examples.sh --help` for detailed usage +- Get access token from MCP Inspector or `node client.js` +- Each step explains what to do next + ### client.js Node.js client showing programmatic interaction with the MCP server. @@ -59,6 +66,20 @@ chmod +x client.js ./client.js ``` +**Note:** When you complete the OAuth flow in your browser, you'll be redirected to `http://localhost:8080/callback` which will show "site can't be reached". This is expected! Simply copy the authorization code from the URL in your browser's address bar (the long string after `code=`). The script will exchange this for an access token and display it for use with other tools. + +## Understanding OAuth Tokens + +**Authorization Code** vs **Access Token**: +- **Authorization Code**: The temporary code you get from the browser redirect (e.g., `302a80e8...`) + - One-time use only + - Must be exchanged for an access token + - Expires quickly (usually within minutes) +- **Access Token**: The actual bearer token for API authentication (e.g., `mcp_at_...`) + - Used in the `Authorization: Bearer` header + - Valid for 7 days + - What you need for `curl-examples.sh` + ## Getting an Access Token ### Option 1: MCP Inspector (Easiest) diff --git a/examples/client.js b/examples/client.js index 6ddc9a4..c887972 100755 --- a/examples/client.js +++ b/examples/client.js @@ -1,10 +1,20 @@ #!/usr/bin/env node /** - * Simple Node.js MCP Client Example + * Simple Node.js MCP Client Example - Manual Implementation * - * This demonstrates how to interact with the MCP server programmatically. - * It handles the OAuth flow and makes MCP requests. + * This demonstrates how to interact with the MCP server programmatically + * WITHOUT using the MCP SDK client. This is for educational purposes to + * show the underlying protocol mechanics. + * + * In production, you would use the MCP SDK client which handles: + * - SSE (Server-Sent Events) parsing + * - Session management and reconnection logic + * - Request/response correlation + * - Error handling and retries + * + * For SDK usage, see: + * @modelcontextprotocol/sdk/client/streamableHttp.js * * Prerequisites: * - Both servers running: npm run dev @@ -18,6 +28,7 @@ const http = require('http'); const https = require('https'); const { URL } = require('url'); const readline = require('readline'); +const crypto = require('crypto'); // Configuration const AUTH_SERVER = 'http://localhost:3001'; @@ -64,11 +75,31 @@ function makeRequest(url, options = {}) { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { - try { - const json = JSON.parse(data); - resolve({ status: res.statusCode, headers: res.headers, data: json }); - } catch { + // Check if this is an SSE response + if (data.startsWith('event:') || data.includes('\ndata: ')) { + // Parse SSE format + const lines = data.split('\n'); + for (const line of lines) { + if (line.startsWith('data: ')) { + try { + const json = JSON.parse(line.substring(6)); + resolve({ status: res.statusCode, headers: res.headers, data: json }); + return; + } catch { + // Continue to next line + } + } + } + // If no JSON found in SSE, return raw data resolve({ status: res.statusCode, headers: res.headers, data }); + } else { + // Try to parse as regular JSON + try { + const json = JSON.parse(data); + resolve({ status: res.statusCode, headers: res.headers, data: json }); + } catch { + resolve({ status: res.statusCode, headers: res.headers, data }); + } } }); }); @@ -89,23 +120,30 @@ function makeRequest(url, options = {}) { async function registerClient() { log.section('Registering OAuth Client'); - const response = await makeRequest(`${AUTH_SERVER}/register`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - client_name: 'node-example-client', - redirect_uris: [REDIRECT_URI] - }) - }); + try { + const response = await makeRequest(`${AUTH_SERVER}/register`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + client_name: 'node-example-client', + redirect_uris: [REDIRECT_URI] + }) + }); - if (response.status === 200 || response.status === 201) { - log.success('Client registered successfully'); - return { - clientId: response.data.client_id, - clientSecret: response.data.client_secret - }; - } else { - throw new Error(`Registration failed: ${JSON.stringify(response.data)}`); + if (response.status === 200 || response.status === 201) { + log.success('Client registered successfully'); + return { + clientId: response.data.client_id, + clientSecret: response.data.client_secret + }; + } else { + throw new Error(`Registration failed (HTTP ${response.status}): ${JSON.stringify(response.data)}`); + } + } catch (error) { + if (error.code === 'ECONNREFUSED') { + throw new Error(`Cannot connect to auth server at ${AUTH_SERVER}. Make sure it's running (npm run dev).`); + } + throw error; } } @@ -119,16 +157,19 @@ async function performOAuthFlow(clientId, clientSecret) { log.info('For this demo, we need you to manually complete the OAuth flow.'); log.info('In a production app, this would be automated.\n'); - // Generate PKCE challenge (simplified for demo) - const codeVerifier = Buffer.from(Math.random().toString()).toString('base64url'); - const codeChallenge = Buffer.from(codeVerifier).toString('base64url'); + // Generate PKCE challenge (proper S256 implementation) + const codeVerifier = crypto.randomBytes(32).toString('base64url'); + const codeChallenge = crypto + .createHash('sha256') + .update(codeVerifier) + .digest('base64url'); const authUrl = `${AUTH_SERVER}/authorize?` + `client_id=${clientId}&` + `redirect_uri=${encodeURIComponent(REDIRECT_URI)}&` + `response_type=code&` + `code_challenge=${codeChallenge}&` + - `code_challenge_method=plain&` + + `code_challenge_method=S256&` + `state=demo-state`; console.log('1. Open this URL in your browser:'); @@ -136,7 +177,11 @@ async function performOAuthFlow(clientId, clientSecret) { console.log('2. Complete the authentication flow'); console.log('3. You\'ll be redirected to a URL like:'); - console.log(` ${REDIRECT_URI}?code=AUTHORIZATION_CODE&state=demo-state\n`); + console.log(` ${REDIRECT_URI}?code=AUTHORIZATION_CODE&state=demo-state`); + console.log(` ${colors.yellow}(You'll see "site can't be reached" - this is expected!)${colors.reset}\n`); + + console.log('4. Copy the authorization code from the URL in your browser\'s address bar'); + console.log(' The code is the long string after "code=" and before "&state="\n'); // Get authorization code from user const rl = readline.createInterface({ @@ -145,7 +190,7 @@ async function performOAuthFlow(clientId, clientSecret) { }); return new Promise((resolve) => { - rl.question('4. Paste the AUTHORIZATION_CODE here: ', async (code) => { + rl.question('5. Paste the AUTHORIZATION_CODE here: ', async (code) => { rl.close(); // Exchange code for token @@ -182,7 +227,8 @@ async function initializeMCPSession(accessToken) { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/event-stream' }, body: JSON.stringify({ jsonrpc: '2.0', @@ -201,8 +247,13 @@ async function initializeMCPSession(accessToken) { if (response.data.result) { log.success('MCP session initialized'); - // In a real implementation, extract session ID from headers - return response.headers['mcp-session-id'] || 'demo-session-id'; + const sessionId = response.headers['mcp-session-id']; + if (!sessionId) { + throw new Error('No session ID received in headers'); + } + return sessionId; + } else if (response.data.error) { + throw new Error(`Initialization failed: ${response.data.error.message}`); } else { throw new Error(`Initialization failed: ${JSON.stringify(response.data)}`); } @@ -221,7 +272,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) { headers: { 'Authorization': `Bearer ${accessToken}`, 'Mcp-Session-Id': sessionId, - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/event-stream' }, body: JSON.stringify({ jsonrpc: '2.0', @@ -235,6 +287,9 @@ async function demonstrateMCPFeatures(accessToken, sessionId) { toolsResponse.data.result.tools.forEach(tool => { console.log(` - ${colors.cyan}${tool.name}${colors.reset}: ${tool.description}`); }); + } else if (toolsResponse.data.error) { + log.error(`Failed to list tools: ${toolsResponse.data.error.message}`); + return; } // Call the echo tool @@ -244,7 +299,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) { headers: { 'Authorization': `Bearer ${accessToken}`, 'Mcp-Session-Id': sessionId, - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/event-stream' }, body: JSON.stringify({ jsonrpc: '2.0', @@ -261,6 +317,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) { if (echoResponse.data.result) { console.log('Echo response:', echoResponse.data.result.content); + } else if (echoResponse.data.error) { + log.error(`Echo tool failed: ${echoResponse.data.error.message}`); } // Call the add tool @@ -270,7 +328,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) { headers: { 'Authorization': `Bearer ${accessToken}`, 'Mcp-Session-Id': sessionId, - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/event-stream' }, body: JSON.stringify({ jsonrpc: '2.0', @@ -285,6 +344,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) { if (addResponse.data.result) { console.log('Add result:', addResponse.data.result.content); + } else if (addResponse.data.error) { + log.error(`Add tool failed: ${addResponse.data.error.message}`); } // List resources @@ -294,7 +355,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) { headers: { 'Authorization': `Bearer ${accessToken}`, 'Mcp-Session-Id': sessionId, - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/event-stream' }, body: JSON.stringify({ jsonrpc: '2.0', @@ -308,6 +370,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) { resourcesResponse.data.result.resources.slice(0, 5).forEach(resource => { console.log(` - ${colors.cyan}${resource.uri}${colors.reset}: ${resource.name}`); }); + } else if (resourcesResponse.data.error) { + log.error(`Failed to list resources: ${resourcesResponse.data.error.message}`); } } @@ -335,8 +399,28 @@ async function main() { log.success('\nExample completed successfully!'); + // Show how to use curl-examples.sh as an alternative + console.log(`\n${colors.bright}${colors.cyan}Try curl-examples.sh (alternative to this script):${colors.reset}`); + console.log('─'.repeat(50)); + console.log(`\n${colors.green}Your credentials:${colors.reset}`); + console.log(` Access Token: ${colors.yellow}${accessToken}${colors.reset}`); + console.log(` Session ID: ${colors.yellow}${sessionId}${colors.reset}`); + console.log(`\n${colors.cyan}Create a new session with curl:${colors.reset}`); + console.log(` ./examples/curl-examples.sh ${accessToken}`); + console.log(`\n${colors.cyan}Reuse this session with curl:${colors.reset}`); + console.log(` ./examples/curl-examples.sh ${accessToken} ${sessionId}\n`); + } catch (error) { - log.error(`Error: ${error.message}`); + if (error.code === 'ECONNREFUSED') { + log.error('Connection refused - Is the auth server running?'); + log.error(`Could not connect to ${error.address || 'server'}:${error.port || 'unknown'}`); + console.log('\nMake sure to start the servers first:'); + console.log(' npm run dev'); + } else if (error.message) { + log.error(`Error: ${error.message}`); + } else { + log.error(`Error: ${JSON.stringify(error)}`); + } process.exit(1); } } diff --git a/examples/curl-examples.sh b/examples/curl-examples.sh index f10eaa6..94f945a 100755 --- a/examples/curl-examples.sh +++ b/examples/curl-examples.sh @@ -1,13 +1,27 @@ #!/bin/bash # ============================================================================= -# MCP Server API Examples using curl +# MCP Server API Examples using curl - Manual Implementation # ============================================================================= # -# This script demonstrates how to interact with the MCP server using curl. +# This script demonstrates how to interact with the MCP server using curl +# WITHOUT using the MCP SDK. This is for educational purposes to show the +# underlying protocol mechanics. +# +# In production, you would use the MCP SDK client which handles: +# - SSE (Server-Sent Events) parsing +# - Session management and reconnection logic +# - Request/response correlation +# - Error handling and retries +# # Before running, ensure both servers are running: npm run dev # -# Usage: ./curl-examples.sh [access_token] +# Three-step workflow: +# 1. ./curl-examples.sh → Register OAuth client, get setup instructions +# 2. ./curl-examples.sh → Initialize MCP session, get session ID +# 3. ./curl-examples.sh → Run all MCP examples (tools, resources, prompts) +# +# Run with --help for detailed usage information # ============================================================================= # Configuration @@ -43,6 +57,19 @@ print_info() { echo -e "${YELLOW}ℹ $1${NC}" } +# Parse SSE response and extract JSON data +parse_sse_response() { + local RESPONSE="$1" + # Check if response is in SSE format + if echo "$RESPONSE" | grep -q "^event:"; then + # Extract JSON from SSE format (data: line) + echo "$RESPONSE" | grep "^data: " | sed 's/^data: //' + else + # Return as-is if not SSE format + echo "$RESPONSE" + fi +} + # ============================================================================= # OAuth Client Registration # ============================================================================= @@ -64,8 +91,6 @@ register_client() { print_success "Client registered successfully" echo "Client ID: $CLIENT_ID" echo "Client Secret: $CLIENT_SECRET" - echo - echo "Save these credentials - you'll need them for the OAuth flow" else print_error "Failed to register client" echo "Response: $RESPONSE" @@ -80,17 +105,16 @@ register_client() { initialize_session() { local ACCESS_TOKEN=$1 - print_section "Initializing MCP Session" - if [ -z "$ACCESS_TOKEN" ]; then print_error "Access token required" print_info "Get a token using MCP Inspector or implement OAuth flow" return 1 fi - RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \ + RESPONSE=$(curl -s -i -X POST "$MCP_SERVER/mcp" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": "init", @@ -105,18 +129,32 @@ initialize_session() { } }') - # Extract session ID from response headers (would need -i flag) - # For demonstration, we'll parse from response - echo "Response: $RESPONSE" + # Extract session ID from response headers + SESSION_ID=$(echo "$RESPONSE" | grep -i "^mcp-session-id:" | cut -d' ' -f2 | tr -d '\r\n') + + # Extract body from response (SSE format) + BODY=$(echo "$RESPONSE" | sed -n '/^$/,$p' | tail -n +2) + + # Parse SSE response if present + if echo "$BODY" | grep -q "^event:"; then + # Extract JSON from SSE format (data: line) + JSON_DATA=$(echo "$BODY" | grep "^data: " | sed 's/^data: //') + else + JSON_DATA="$BODY" + fi # Check for error - if echo "$RESPONSE" | grep -q "error"; then + if echo "$JSON_DATA" | grep -q "error"; then print_error "Failed to initialize session" + echo "$JSON_DATA" >&2 return 1 - else - print_success "Session initialized" - print_info "Save the Mcp-Session-Id header for subsequent requests" + elif [ -n "$SESSION_ID" ]; then + # Return session ID on stdout (for capture by caller) + echo "$SESSION_ID" return 0 + else + print_error "No session ID received" + return 1 fi } @@ -130,15 +168,19 @@ list_tools() { print_section "Listing Available Tools" - curl -s -X POST "$MCP_SERVER/mcp" \ + RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Mcp-Session-Id: $SESSION_ID" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": "list-tools", "method": "tools/list" - }' | python3 -m json.tool + }') + + # Parse SSE response if needed and pretty print + parse_sse_response "$RESPONSE" | python3 -m json.tool } call_echo_tool() { @@ -148,10 +190,11 @@ call_echo_tool() { print_section "Calling Echo Tool" - curl -s -X POST "$MCP_SERVER/mcp" \ + RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Mcp-Session-Id: $SESSION_ID" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ -d "{ \"jsonrpc\": \"2.0\", \"id\": \"echo-1\", @@ -162,7 +205,10 @@ call_echo_tool() { \"message\": \"$MESSAGE\" } } - }" | python3 -m json.tool + }") + + # Parse SSE response if needed and pretty print + parse_sse_response "$RESPONSE" | python3 -m json.tool } call_add_tool() { @@ -173,10 +219,11 @@ call_add_tool() { print_section "Calling Add Tool" - curl -s -X POST "$MCP_SERVER/mcp" \ + RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Mcp-Session-Id: $SESSION_ID" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ -d "{ \"jsonrpc\": \"2.0\", \"id\": \"add-1\", @@ -188,7 +235,10 @@ call_add_tool() { \"b\": $B } } - }" | python3 -m json.tool + }") + + # Parse SSE response if needed and pretty print + parse_sse_response "$RESPONSE" | python3 -m json.tool } # ============================================================================= @@ -201,15 +251,19 @@ list_resources() { print_section "Listing Resources (First Page)" - curl -s -X POST "$MCP_SERVER/mcp" \ + RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Mcp-Session-Id: $SESSION_ID" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": "list-resources", "method": "resources/list" - }' | python3 -m json.tool + }') + + # Parse SSE response if needed and pretty print + parse_sse_response "$RESPONSE" | python3 -m json.tool } read_resource() { @@ -219,10 +273,11 @@ read_resource() { print_section "Reading Resource: $RESOURCE_URI" - curl -s -X POST "$MCP_SERVER/mcp" \ + RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Mcp-Session-Id: $SESSION_ID" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ -d "{ \"jsonrpc\": \"2.0\", \"id\": \"read-resource\", @@ -230,7 +285,10 @@ read_resource() { \"params\": { \"uri\": \"$RESOURCE_URI\" } - }" | python3 -m json.tool + }") + + # Parse SSE response if needed and pretty print + parse_sse_response "$RESPONSE" | python3 -m json.tool } # ============================================================================= @@ -243,15 +301,19 @@ list_prompts() { print_section "Listing Available Prompts" - curl -s -X POST "$MCP_SERVER/mcp" \ + RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Mcp-Session-Id: $SESSION_ID" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": "list-prompts", "method": "prompts/list" - }' | python3 -m json.tool + }') + + # Parse SSE response if needed and pretty print + parse_sse_response "$RESPONSE" | python3 -m json.tool } get_prompt() { @@ -261,10 +323,11 @@ get_prompt() { print_section "Getting Prompt: $PROMPT_NAME" - curl -s -X POST "$MCP_SERVER/mcp" \ + RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Mcp-Session-Id: $SESSION_ID" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ -d "{ \"jsonrpc\": \"2.0\", \"id\": \"get-prompt\", @@ -272,14 +335,46 @@ get_prompt() { \"params\": { \"name\": \"$PROMPT_NAME\" } - }" | python3 -m json.tool + }") + + # Parse SSE response if needed and pretty print + parse_sse_response "$RESPONSE" | python3 -m json.tool } # ============================================================================= # Main Script # ============================================================================= +show_usage() { + echo -e "${BLUE}MCP Server API Examples - Usage${NC}" + echo "=================================" + echo + echo "This script demonstrates MCP API interactions in three steps:" + echo + echo -e " ${GREEN}Step 1: Register OAuth client${NC}" + echo " ./curl-examples.sh" + echo " → Registers a client and shows how to get an access token" + echo + echo -e " ${GREEN}Step 2: Initialize MCP session${NC}" + echo " ./curl-examples.sh " + echo " → Creates an MCP session and returns a session ID" + echo + echo -e " ${GREEN}Step 3: Run MCP examples${NC}" + echo " ./curl-examples.sh " + echo " → Demonstrates tools, resources, and prompts" + echo + echo "Options:" + echo " --help, -h Show this help message" + echo +} + main() { + # Check for help flag + if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then + show_usage + exit 0 + fi + echo -e "${BLUE}MCP Server API Examples${NC}" echo "=================================" @@ -288,26 +383,46 @@ main() { SESSION_ID=$2 if [ -z "$ACCESS_TOKEN" ]; then - print_info "No access token provided" - print_info "Usage: $0 [session_id]" + print_section "Getting Started" + print_info "This script requires an OAuth access token to demonstrate MCP operations." + print_info "Since you don't have one yet, let's help you get started!" + echo + print_info "First, we'll register an OAuth client for you..." echo - print_section "Step 1: Register OAuth Client" register_client echo - print_info "To continue, you need to:" - print_info "1. Complete OAuth flow to get an access token" - print_info "2. Run: $0 " - print_info "" - print_info "Use MCP Inspector for easy OAuth flow:" - print_info "npx -y @modelcontextprotocol/inspector" - print_info "Connect to: http://localhost:3232/mcp" + print_section "Next Steps" + print_info "Now you need to get an access token. You have two options:" + echo + print_info "Option 1: Use MCP Inspector (recommended):" + print_info " npx -y @modelcontextprotocol/inspector" + print_info " Connect to: http://localhost:3232/mcp" + print_info " Complete the OAuth flow in the Auth tab" + echo + print_info "Option 2: Use the client.js example:" + print_info " node examples/client.js" + echo + print_info "Once you have an access token, run:" + print_info " $0 " exit 0 fi - # If we have a token, demonstrate API calls + # If we have a token but no session, initialize session if [ -z "$SESSION_ID" ]; then - initialize_session "$ACCESS_TOKEN" - print_info "Rerun with: $0 $ACCESS_TOKEN " + print_section "Initializing MCP Session" + RETURNED_SESSION_ID=$(initialize_session "$ACCESS_TOKEN") + + if [ $? -eq 0 ] && [ -n "$RETURNED_SESSION_ID" ]; then + echo + print_success "MCP session initialized successfully" + print_info "Session ID: $RETURNED_SESSION_ID" + echo + print_info "To run all MCP examples (tools, resources, prompts), use:" + echo -e " ${CYAN}$0 $ACCESS_TOKEN $RETURNED_SESSION_ID${NC}" + echo + print_info "For help:" + echo -e " ${CYAN}$0 --help${NC}" + fi exit 0 fi From a3ecfee613942bf0dc7dd3f7168830c5b1275199 Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Tue, 7 Oct 2025 14:44:25 -0400 Subject: [PATCH 08/12] Update documentation for graceful degradation and /health endpoint - Add /health endpoint to docs/endpoints.md for MCP server - Document degraded mode behavior in Common Issues section - Update OAuth metadata endpoint to mention 503 responses --- README.md | 11 +++++++++++ docs/endpoints.md | 9 +++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9fc94f6..2331bf6 100644 --- a/README.md +++ b/README.md @@ -476,6 +476,17 @@ The script: - Google/GitHub OAuth - The mock IDP creates random user IDs for testing multi-user scenarios +### "Authentication service unavailable" (HTTP 503) +- **Cause**: Auth server is not running or not reachable +- **What happens**: MCP server runs in degraded mode + - Splash page accessible with warning banner + - Health endpoint shows degraded status: `curl http://localhost:3232/health` + - Protected MCP endpoints return 503 with helpful error message +- **Solution**: + - Start the auth server: `npm run dev` (starts both servers) + - Or start manually: `cd auth-server && npm run dev` + - Restart the MCP server to retry connection + ### "Authentication flow fails" - **Cause**: Misconfiguration or servers not communicating - **Solution**: diff --git a/docs/endpoints.md b/docs/endpoints.md index cdcae69..4332605 100644 --- a/docs/endpoints.md +++ b/docs/endpoints.md @@ -42,10 +42,10 @@ Local simulation of upstream IDP (would be external in production): MCP resource server that implements the Model Context Protocol with delegated authentication. ### OAuth Metadata (Read-Only) -Provided by `mcpAuthMetadataRouter`: - `GET /.well-known/oauth-authorization-server` - Returns metadata pointing to external auth server - Tells clients to use auth server at :3001 + - Returns 503 if auth server is unavailable (degraded mode) - Read-only - no token issuance happens here ### MCP Resource Endpoints @@ -61,8 +61,13 @@ Provided by `mcpAuthMetadataRouter`: All MCP endpoints require `Authorization: Bearer ` header. Tokens are validated by calling the auth server's `/introspect` endpoint. +### Utility Endpoints +- `GET /health` - Health check endpoint + - Returns status: `healthy` (auth available) or `degraded` (auth unavailable) + - Shows operational status of MCP, auth, and Redis services + ### Static Assets -- `GET /` - Splash page (HTML) +- `GET /` - Splash page (HTML with degraded mode warning if auth unavailable) - `GET /mcp-logo.png` - MCP logo - `GET /styles.css` - Stylesheet From 78a7d1c1d019360a3d28fe5ecf0adcf7c341902b Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Tue, 7 Oct 2025 16:56:03 -0400 Subject: [PATCH 09/12] Deduplicate OAuth provider replacement docs Consolidate three redundant 'Production Replacement' sections into a single canonical location in docs/oauth-architecture-patterns.md. Both server READMEs now point to this guide. --- auth-server/README.md | 11 +++---- docs/oauth-architecture-patterns.md | 51 ++++++++++++++++++++++++----- mcp-server/README.md | 20 ++--------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/auth-server/README.md b/auth-server/README.md index aff4f49..d2a6aff 100644 --- a/auth-server/README.md +++ b/auth-server/README.md @@ -98,14 +98,13 @@ npm run typecheck # Type checking npm run build # Build to dist/ ``` -## Production Replacement +## Production Usage -Commercial OAuth providers supporting RFC 7662 introspection: -- Auth0, Okta, Azure AD/Microsoft Entra -- AWS Cognito, Google Identity Platform -- GitHub OAuth +This demo server should be replaced with a commercial OAuth provider in production. -The MCP server integrates with any RFC 7662-compliant provider. +See [OAuth Architecture Patterns](../docs/oauth-architecture-patterns.md#using-a-commercial-auth-provider) for detailed integration guidance. + +**Supported providers:** Auth0, Okta, Azure AD, AWS Cognito, Google, GitHub, and any RFC 7662-compliant OAuth provider. ## Redis Data diff --git a/docs/oauth-architecture-patterns.md b/docs/oauth-architecture-patterns.md index af8df69..12c000e 100644 --- a/docs/oauth-architecture-patterns.md +++ b/docs/oauth-architecture-patterns.md @@ -41,14 +41,49 @@ The current implementation demonstrates this pattern with separate authorization ### Using a Commercial Auth Provider -Replacing the demo auth server with a commercial provider: - -1. **Configure provider**: Set up OAuth app in commercial provider (e.g. Auth0/Okta) -2. **Update metadata URL**: Point to provider's discovery endpoint -3. **Configure introspection**: Set up token validation -4. **Update redirect URIs**: Configure allowed callbacks -5. **Migrate users**: Import existing users if needed -6. **Test integration**: Verify full OAuth flow +The demo auth server should be replaced with a commercial OAuth provider in production. + +**Supported providers:** +- Auth0, Okta, Azure AD/Microsoft Entra +- AWS Cognito, Google Identity Platform +- GitHub OAuth +- Any RFC 7662-compliant OAuth provider + +#### Integration Steps + +1. **Configure provider**: Set up OAuth app in your provider + - Register your MCP server as a resource server + - Configure allowed redirect URIs + - Enable token introspection endpoint + +2. **Update MCP server environment** (`mcp-server/.env`): + ```bash + AUTH_SERVER_URL=https://your-tenant.auth0.com + # or https://your-domain.okta.com + # or https://login.microsoftonline.com/your-tenant + ``` + +3. **Adjust token introspection** if needed (`mcp-server/src/auth/external-verifier.ts`): + ```typescript + // Most providers use RFC 7662 standard format, but some may differ + const response = await fetch(`${this.authServerUrl}/oauth/introspect`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + // Some providers require authentication here + 'Authorization': `Basic ${Buffer.from('client_id:client_secret').toString('base64')}` + }, + body: `token=${token}` + }); + ``` + +4. **Update redirect URIs**: Configure your provider's allowed callbacks to match your deployment URLs + +5. **Test the integration**: Verify the full OAuth flow with your provider + +**Note on token introspection:** Most providers use the RFC 7662 standard format. If your provider uses a non-standard format, you may need to adjust the response parsing in `mcp-server/src/auth/external-verifier.ts`. + +The MCP server code otherwise remains unchanged - it only needs to know where to validate tokens. --- diff --git a/mcp-server/README.md b/mcp-server/README.md index 974c763..57d707f 100644 --- a/mcp-server/README.md +++ b/mcp-server/README.md @@ -115,25 +115,11 @@ npm run typecheck # Type checking npm run build # Build to dist/ ``` -## Production Adaptation +## Production Usage -To use a commercial OAuth provider: +To use a commercial OAuth provider instead of the demo auth server, see [OAuth Architecture Patterns](../docs/oauth-architecture-patterns.md#using-a-commercial-auth-provider) for detailed integration guidance. -1. Update `.env` with provider URL: -```bash -AUTH_SERVER_URL=https://your-tenant.auth0.com -``` - -2. Modify `src/auth/external-verifier.ts` for provider-specific introspection: -```typescript -const response = await fetch(`${this.authServerUrl}/oauth/introspect`, { - // Add provider-specific authentication -}) -``` - -3. Adjust response parsing if the introspection format differs from RFC 7662 standard - -The MCP server code otherwise remains unchanged. +**Summary:** Update `AUTH_SERVER_URL` in `.env` to point to your provider. You may need to adjust `src/auth/external-verifier.ts` for provider-specific introspection formats, but the MCP server code otherwise remains unchanged. ## References From 695f9f68ae9f402ee747e7de3a8dc95fed879c3c Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Tue, 7 Oct 2025 17:08:10 -0400 Subject: [PATCH 10/12] Add customization guide and fix broken doc references - Create docs/customization-guide.md with guidance on replacing demo functionality - Add customization section to main README - Fix all references to renamed oauth-architecture-patterns.md - Update repository structure comments to clarify what to replace vs keep --- README.md | 19 ++++-- auth-server/README.md | 2 +- docs/customization-guide.md | 117 ++++++++++++++++++++++++++++++++++++ mcp-server/README.md | 3 +- 4 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 docs/customization-guide.md diff --git a/README.md b/README.md index 2331bf6..7ffec88 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,10 @@ For detailed instructions, see [Installation](#installation). This repository demonstrates a production-ready MCP deployment pattern with separate authorization and resource servers: ``` -auth-server/ # OAuth 2.0 authorization server +auth-server/ # OAuth 2.0 authorization server (demo only - replace in production) └── src/ # Authorization endpoints and token management -mcp-server/ # MCP resource server +mcp-server/ # MCP resource server (customize tools/resources/prompts) └── src/ # MCP protocol implementation with external auth scripts/ # Testing and deployment scripts @@ -64,7 +64,13 @@ docs/ # Architecture and API documentation examples/ # Example code and usage patterns ``` -The architecture separates authentication concerns from MCP functionality, allowing you to integrate with existing OAuth providers (Auth0, Okta, etc.) or deploy your own authorization server. +The architecture separates authentication concerns from MCP functionality, allowing you to integrate with commercial OAuth providers (Auth0, Okta, etc.). + +## Customizing for Your Use Case + +This is a reference implementation with demo tools, resources, and prompts. To adapt it for production: +- **Replace MCP features:** See [Customization Guide](docs/customization-guide.md) for replacing demo functionality with your own +- **Integrate OAuth provider:** See [OAuth Architecture Patterns](docs/oauth-architecture-patterns.md) for production authentication setup ## Usage Examples @@ -174,7 +180,7 @@ sequenceDiagram Client->>User: 9. Results ``` -The auth server is separate so you can easily replace it with Auth0, Okta, or any OAuth provider. See [docs/oauth-patterns.md](docs/oauth-patterns.md) for alternative architectures. +The auth server is separate so you can easily replace it with Auth0, Okta, or any OAuth provider. See [docs/oauth-architecture-patterns.md](docs/oauth-architecture-patterns.md) for integration guidance. ## Table of Contents @@ -320,7 +326,7 @@ This architecture pattern: - **Scales independently**: Auth and MCP servers can scale based on their load - **Follows standards**: Uses OAuth 2.0 and token introspection (RFC 7662) -For alternative patterns like embedded OAuth, see [docs/oauth-patterns.md](docs/oauth-patterns.md). +For alternative patterns like embedded OAuth, see [docs/oauth-architecture-patterns.md](docs/oauth-architecture-patterns.md). ### OAuth Flow @@ -361,9 +367,10 @@ The `/mock-upstream-idp` endpoints simulate what a real identity provider (Googl ├── scripts/ # Testing and deployment │ └── test-e2e.sh # End-to-end OAuth + MCP verification ├── docs/ +│ ├── customization-guide.md # How to adapt this for your use case │ ├── endpoints.md # API endpoint reference │ ├── oauth-flow.md # OAuth flow documentation -│ ├── oauth-patterns.md # Alternative OAuth patterns +│ ├── oauth-architecture-patterns.md # OAuth integration guidance │ └── session-ownership.md # Session management details ├── examples/ # Example code and usage patterns │ ├── client.js # Node.js client example diff --git a/auth-server/README.md b/auth-server/README.md index d2a6aff..333df9b 100644 --- a/auth-server/README.md +++ b/auth-server/README.md @@ -119,5 +119,5 @@ This server stores in Redis: - [Main README](../README.md) - Complete project documentation - [MCP Server README](../mcp-server/README.md) - How MCP server uses these tokens -- [OAuth Patterns](../docs/oauth-patterns.md) - OAuth architecture patterns +- [OAuth Architecture Patterns](../docs/oauth-architecture-patterns.md) - OAuth integration options - [OAuth Flow](../docs/oauth-flow.md) - Detailed OAuth flow analysis diff --git a/docs/customization-guide.md b/docs/customization-guide.md new file mode 100644 index 0000000..374e9d7 --- /dev/null +++ b/docs/customization-guide.md @@ -0,0 +1,117 @@ +# Customization Guide + +This reference implementation includes demo functionality to showcase MCP features. Here's how to adapt it for your own use case. + +## Overview: What to Customize vs. Keep + +**Replace with your own:** +- MCP tools, resources, and prompts (your business logic) +- Authentication provider (use commercial OAuth provider) + +**Keep as-is (infrastructure):** +- Redis transport and session management +- HTTP handlers and routing +- Security middleware and rate limiting +- Logging infrastructure + +--- + +## Customizing MCP Functionality + +All MCP feature implementations live in **`mcp-server/src/services/mcp.ts`**. This is where you define what your server can do. + +### Tools + +Replace the 7 demo tools (echo, add, etc.) with your actual tools: + +**Location:** `createMcpServer()` function, look for the `CallToolRequestSchema` handler + +**What to change:** +- Tool definitions: name, description, input schema (using Zod) +- Tool execution logic: what happens when the tool is called +- Return format: text, images, or embedded resources + +**Pattern:** Each tool validates input with a Zod schema and returns content in MCP format. + +### Resources + +Replace the 100 fake resources with your actual data sources: + +**Location:** `createMcpServer()` function, resource-related handlers: +- `ListResourcesRequestSchema` - List available resources +- `ReadResourceRequestSchema` - Read specific resources +- `SubscribeRequestSchema` / `UnsubscribeRequestSchema` - Resource updates + +**What to change:** +- Resource URIs and names +- Data fetching logic +- Pagination if needed +- Update notifications for subscribed resources + +**Pattern:** Resources use URIs (e.g., `db://users/123`) and return content as text, JSON, or binary. + +### Prompts + +Replace the 3 demo prompts with useful prompts for your domain: + +**Location:** `createMcpServer()` function, prompt-related handlers: +- `ListPromptsRequestSchema` - List available prompts +- `GetPromptRequestSchema` - Return prompt content + +**What to change:** +- Prompt names and descriptions +- Prompt arguments and validation +- Prompt content and embedded resources + +**Pattern:** Prompts can include dynamic arguments and reference resources. + +--- + +## Customizing Authentication + +**Replace the demo auth server** with a commercial OAuth provider (Auth0, Okta, Azure AD, AWS Cognito, Google, GitHub, etc.). + +See [OAuth Architecture Patterns](oauth-architecture-patterns.md#using-a-commercial-auth-provider) for detailed integration steps. + +**Do not repurpose the demo auth server** - it's designed for development/testing only. Commercial providers offer better security, reliability, and user management. + +--- + +## Configuration + +Update environment variables for your deployment: + +**MCP Server** (`mcp-server/.env`): +```bash +BASE_URI=https://your-mcp-server.com +PORT=443 +AUTH_SERVER_URL=https://your-tenant.auth0.com +REDIS_URL=redis://your-redis-server:6379 +``` + +**Auth Server** (only if using the demo server for development): +```bash +AUTH_SERVER_URL=http://localhost:3001 +BASE_URI=https://your-mcp-server.com +``` + +--- + +## Testing Your Customizations + +1. **Unit tests:** Add tests for your tools/resources in `mcp-server/src/services/` +2. **Integration tests:** Test with MCP Inspector or client.js +3. **Build and lint:** Run `npm run build && npm run lint` +4. **End-to-end:** Use the examples to verify OAuth and MCP flows + +--- + +## Next Steps + +1. Fork/clone this repository +2. Replace tools, resources, and prompts in `mcp-server/src/services/mcp.ts` +3. Integrate with your OAuth provider (see OAuth Architecture Patterns doc) +4. Update environment variables for your deployment +5. Test thoroughly before deploying + +For questions about the MCP protocol itself, see the [MCP specification](https://modelcontextprotocol.io/specification). diff --git a/mcp-server/README.md b/mcp-server/README.md index 57d707f..8ac32dc 100644 --- a/mcp-server/README.md +++ b/mcp-server/README.md @@ -130,7 +130,8 @@ To use a commercial OAuth provider instead of the demo auth server, see [OAuth A ## Related Documentation - [Main README](../README.md) - Complete project documentation +- [Customization Guide](../docs/customization-guide.md) - How to adapt this for your use case - [Auth Server README](../auth-server/README.md) - The demo OAuth provider -- [OAuth Patterns](../docs/oauth-patterns.md) - OAuth architecture patterns +- [OAuth Architecture Patterns](../docs/oauth-architecture-patterns.md) - OAuth integration options - [OAuth Flow](../docs/oauth-flow.md) - Detailed OAuth flow analysis - [Session Ownership](../docs/session-ownership.md) - Session management details From 78f87bbeb485248d7f782a2e88d49b9cc784481a Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Tue, 7 Oct 2025 17:21:22 -0400 Subject: [PATCH 11/12] Fix lint errors and add missing rate limiting to MCP endpoints - Remove unused mcpAuthMetadataRouter import - Fix authMetadata type (use Record instead of any) - Add rate limiting to all MCP endpoints (/mcp, /sse, /message) --- mcp-server/src/index.ts | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts index 83cffe0..c497d00 100644 --- a/mcp-server/src/index.ts +++ b/mcp-server/src/index.ts @@ -13,7 +13,7 @@ */ import { BearerAuthMiddlewareOptions, requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"; -import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from "@modelcontextprotocol/sdk/server/auth/router.js"; +import { getOAuthProtectedResourceMetadataUrl } from "@modelcontextprotocol/sdk/server/auth/router.js"; import cors from "cors"; import rateLimit from "express-rate-limit"; import express from "express"; @@ -135,13 +135,19 @@ app.use(baseSecurityHeaders); // Enable CORS pre-flight requests app.options('*', cors(corsOptions)); -// Rate limiting for custom endpoints +// Rate limiting for endpoints const staticFileRateLimit = rateLimit({ windowMs: 10 * 60 * 1000, // 10 minutes limit: 25, // 25 requests per 10 minutes for static files message: { error: 'too_many_requests', error_description: 'Static file rate limit exceeded' } }); +const mcpRateLimit = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + limit: 100, // 100 requests per 15 minutes per IP + message: { error: 'too_many_requests', error_description: 'MCP rate limit exceeded' } +}); + // MCP server using external auth server logger.info('Starting MCP server', { baseUri: BASE_URI, @@ -150,7 +156,7 @@ logger.info('Starting MCP server', { }); // Auth server state - will be populated asynchronously -let authMetadata: any; +let authMetadata: Record | undefined; let authServerAvailable = false; // OAuth metadata endpoint - responds based on current auth server status @@ -197,13 +203,13 @@ const bearerAuth: express.RequestHandler = (req, res, next) => { }; // MCP routes (legacy SSE transport) -app.get("/sse", cors(corsOptions), bearerAuth, sseHeaders, handleSSEConnection); -app.post("/message", cors(corsOptions), bearerAuth, sensitiveDataHeaders, handleMessage); +app.get("/sse", cors(corsOptions), mcpRateLimit, bearerAuth, sseHeaders, handleSSEConnection); +app.post("/message", cors(corsOptions), mcpRateLimit, bearerAuth, sensitiveDataHeaders, handleMessage); // MCP routes (new streamable HTTP transport) -app.get("/mcp", cors(corsOptions), bearerAuth, handleStreamableHTTP); -app.post("/mcp", cors(corsOptions), bearerAuth, handleStreamableHTTP); -app.delete("/mcp", cors(corsOptions), bearerAuth, handleStreamableHTTP); +app.get("/mcp", cors(corsOptions), mcpRateLimit, bearerAuth, handleStreamableHTTP); +app.post("/mcp", cors(corsOptions), mcpRateLimit, bearerAuth, handleStreamableHTTP); +app.delete("/mcp", cors(corsOptions), mcpRateLimit, bearerAuth, handleStreamableHTTP); // Health check endpoint app.get("/health", (req, res) => { @@ -291,9 +297,9 @@ async function connectToAuthServer() { authMetadata = await authMetadataResponse.json(); authServerAvailable = true; logger.info('Successfully connected to auth server', { - issuer: authMetadata.issuer, - authorizationEndpoint: authMetadata.authorization_endpoint, - tokenEndpoint: authMetadata.token_endpoint + issuer: authMetadata?.issuer, + authorizationEndpoint: authMetadata?.authorization_endpoint, + tokenEndpoint: authMetadata?.token_endpoint }); break; // Success, exit retry loop From 34730c19fdb77999ddc06801324774d5061d943b Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Tue, 7 Oct 2025 17:55:24 -0400 Subject: [PATCH 12/12] Add troubleshooting for wrong transport/endpoint combination Add documentation explaining that SSE transport requires /sse endpoint and Streamable HTTP requires /mcp endpoint. This is a common mistake when using MCP Inspector. --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 7ffec88..07b4fa9 100644 --- a/README.md +++ b/README.md @@ -455,6 +455,13 @@ The script: - **Solution**: - Use the full URL with http:// prefix: `http://localhost:3232/mcp` - Ensure **both** auth and MCP servers are running (`npm run dev`) + +### "Wrong endpoint for SSE transport" or SSE connection fails to `/mcp` +- **Cause**: Using SSE transport with Streamable HTTP endpoint +- **Solution**: + - For **SSE transport** (legacy): Use `http://localhost:3232/sse` + - For **Streamable HTTP** (recommended): Use `http://localhost:3232/mcp` + - In MCP Inspector: Match transport type with the correct endpoint ### "Cannot connect to Docker daemon" - **Cause**: Docker/OrbStack not running