diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..d54c2b8 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,49 @@ +{ + "name": "perplexity-mcp-server", + "owner": { + "name": "Perplexity AI", + "email": "api@perplexity.ai" + }, + "metadata": { + "description": "Official Perplexity AI plugin providing real-time web search, reasoning, and research capabilities", + "version": "0.9.0" + }, + "plugins": [ + { + "name": "perplexity", + "source": "./", + "description": "Real-time web search, reasoning, and research through Perplexity's API", + "version": "0.9.0", + "author": { + "name": "Perplexity AI", + "email": "api@perplexity.ai" + }, + "homepage": "https://docs.perplexity.ai/guides/mcp-server", + "repository": "https://github.com/perplexityai/modelcontextprotocol", + "license": "MIT", + "keywords": [ + "mcp", + "search", + "web-search", + "perplexity", + "research", + "reasoning", + "ai" + ], + "category": "productivity", + "strict": false, + "mcpServers": { + "perplexity": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@perplexity-ai/mcp-server"], + "env": { + "PERPLEXITY_API_KEY": "${PERPLEXITY_API_KEY}", + "PERPLEXITY_TIMEOUT_MS": "${PERPLEXITY_TIMEOUT_MS:-600000}" + } + } + } + } + ] +} + diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0fee275 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +node_modules +dist +coverage +.git +.gitignore +*.md +!README.md +.vscode +.idea +*.log +.env +.env.local +*.test.ts +*.test.js +vitest.config.ts +vitest.config.js +Dockerfile +.dockerignore + diff --git a/.github/workflows/publish-mcp.yml b/.github/workflows/publish-mcp.yml new file mode 100644 index 0000000..0ecdec4 --- /dev/null +++ b/.github/workflows/publish-mcp.yml @@ -0,0 +1,33 @@ +name: Publish to MCP Registry + +on: + push: + branches: [main] + paths: ['server.json', 'package.json'] + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + environment: + name: production + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install MCP Publisher + run: | + curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher + + - name: Login to MCP Registry + run: | + echo "${{ secrets.MCP_REGISTRY_PRIVATE_KEY }}" > key.pem + ./mcp-publisher login dns --domain perplexity.ai --private-key "$(openssl pkey -in key.pem -noout -text | grep -A3 'priv:' | tail -n +2 | tr -d ' :\n')" + rm key.pem + + - name: Publish to MCP Registry + run: ./mcp-publisher publish + diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..6656fd7 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,38 @@ +name: Publish to npm + +on: + push: + branches: [main] + paths: ['package.json'] + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + environment: + name: production + permissions: + contents: read + id-token: write # Required for OIDC + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: Get latest npm + run: npm install -g npm@latest + + - name: Install dependencies + run: npm ci + + - name: Build package + run: npm run build + + - name: Publish to npm + run: npm publish --access public --provenance diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..e3466db --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: Test + +on: + pull_request: + push: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + + - name: Run tests + env: + PERPLEXITY_API_KEY: test-api-key + run: npm test diff --git a/.github/workflows/validate-plugin.yml b/.github/workflows/validate-plugin.yml new file mode 100644 index 0000000..10733b5 --- /dev/null +++ b/.github/workflows/validate-plugin.yml @@ -0,0 +1,46 @@ +name: Validate Plugin Configuration + +on: + pull_request: + paths: + - '.claude-plugin/**' + - 'package.json' + push: + branches: [main] + paths: + - '.claude-plugin/**' + - 'package.json' + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install Claude CLI + run: npm install -g @anthropic-ai/claude-code + + - name: Validate Plugin Configuration + run: claude plugin validate . + + - name: Check Plugin Files Exist + run: | + if [ ! -f ".claude-plugin/marketplace.json" ]; then + echo "Error: .claude-plugin/marketplace.json not found" + exit 1 + fi + echo "✓ Marketplace file exists" + + - name: Validate JSON Syntax + run: | + echo "Validating marketplace.json syntax..." + cat .claude-plugin/marketplace.json | jq empty + echo "✓ JSON syntax is valid" + diff --git a/.gitignore b/.gitignore index 112c4e8..6101699 100644 --- a/.gitignore +++ b/.gitignore @@ -32,12 +32,14 @@ venv/ # Ignore dependency and build artifacts node_modules/ +dist/ npm-debug.log yarn-error.log # Ignore coverage and test reports .coverage coverage.xml +coverage/ *.cover *.pyc @@ -49,4 +51,11 @@ Desktop.ini *.csv *.tsv *.jsonl -: + +# Ignore MCP registry authentication tokens +.mcpregistry_* + +# Ignore private keys and migration artifacts +*.pem +mcp-publisher +migrate-namespace.sh diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..358463f --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,103 @@ +# Docker Setup + +This document explains how to build and run the Perplexity MCP Server using Docker. + +## Prerequisites + +- Docker installed on your system +- A Perplexity API key from the [API Portal](https://www.perplexity.ai/account/api/group) + +## Building the Docker Image + +Build the Docker image from the project root: + +```bash +docker build -t perplexity-mcp-server . +``` + +## Running the Container + +### HTTP Mode (Default) + +The Docker container runs in HTTP mode by default, making it accessible via HTTP requests: + +```bash +docker run --rm -p 8080:8080 -e PERPLEXITY_API_KEY=your_key_here perplexity-mcp-server +``` + +The server will be accessible at `http://localhost:8080/mcp` + +### With Custom Timeout + +Set a custom timeout for requests (default is 5 minutes): + +```bash +docker run --rm -p 8080:8080 \ + -e PERPLEXITY_API_KEY=your_key_here \ + -e PERPLEXITY_TIMEOUT_MS=600000 \ + perplexity-mcp-server +``` + +### With Proxy Support + +If you're behind a corporate proxy, configure it: + +```bash +docker run --rm -p 8080:8080 \ + -e PERPLEXITY_API_KEY=your_key_here \ + -e PERPLEXITY_PROXY=https://your-proxy-host:8080 \ + perplexity-mcp-server +``` + +Or with authentication: + +```bash +docker run --rm -p 8080:8080 \ + -e PERPLEXITY_API_KEY=your_key_here \ + -e PERPLEXITY_PROXY=https://username:password@your-proxy-host:8080 \ + perplexity-mcp-server +``` + +### Using Environment File + +Create a `.env` file: + +```bash +PERPLEXITY_API_KEY=your_key_here +PERPLEXITY_TIMEOUT_MS=600000 +PERPLEXITY_PROXY=https://your-proxy-host:8080 +PORT=8080 +``` + +Then run: + +```bash +docker run --rm -p 8080:8080 --env-file .env perplexity-mcp-server +``` + +## Integration with MCP Clients + +When using the HTTP Docker server, configure your MCP client to connect to the HTTP endpoint: + +```json +{ + "mcpServers": { + "perplexity": { + "url": "http://localhost:8080/mcp" + } + } +} +``` + +## STDIO Mode (Local Development) + +For local development with STDIO transport, you can still run the server locally without Docker: + +```bash +npm install +npm run build +PERPLEXITY_API_KEY=your_key_here npm start +``` + +> **Note**: The Docker image is optimized for HTTP mode deployment. For local STDIO usage, the `npx` method documented in the main README is recommended. + diff --git a/perplexity-ask/Dockerfile b/Dockerfile similarity index 61% rename from perplexity-ask/Dockerfile rename to Dockerfile index bf44767..a29dbf9 100644 --- a/perplexity-ask/Dockerfile +++ b/Dockerfile @@ -1,10 +1,15 @@ FROM node:22.12-alpine AS builder -COPY . /app - WORKDIR /app -RUN --mount=type=cache,target=/root/.npm npm install +COPY package*.json ./ +COPY tsconfig.json ./ + +RUN --mount=type=cache,target=/root/.npm npm install --ignore-scripts + +COPY src/ ./src/ + +RUN npm run build FROM node:22-alpine AS release @@ -18,4 +23,6 @@ ENV NODE_ENV=production RUN npm ci --ignore-scripts --omit-dev -ENTRYPOINT ["node", "dist/index.js"] +EXPOSE 8080 + +ENTRYPOINT ["node", "dist/http.js"] diff --git a/README.md b/README.md index 4dba088..18a4d84 100644 --- a/README.md +++ b/README.md @@ -1,148 +1,182 @@ -# Perplexity Ask MCP Server +# Perplexity API Platform MCP Server -An MCP server implementation that integrates the Sonar API to provide Claude with unparalleled real-time, web-wide research. +[![Install in Cursor](https://custom-icon-badges.demolab.com/badge/Install_in_Cursor-000000?style=for-the-badge&logo=cursor-ai-white)](https://cursor.com/en/install-mcp?name=perplexity&config=eyJ0eXBlIjoic3RkaW8iLCJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBwZXJwbGV4aXR5LWFpL21jcC1zZXJ2ZXIiXSwiZW52Ijp7IlBFUlBMRVhJVFlfQVBJX0tFWSI6IiJ9fQ==) +  +[![Install in VS Code](https://custom-icon-badges.demolab.com/badge/Install_in_VS_Code-007ACC?style=for-the-badge&logo=vsc&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=perplexity&config=%7B%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40perplexity-ai%2Fmcp-server%22%5D%2C%22env%22%3A%7B%22PERPLEXITY_API_KEY%22%3A%22%22%7D%7D) +  +[![npm version](https://img.shields.io/npm/v/%40perplexity-ai%2Fmcp-server?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/@perplexity-ai/mcp-server) -![Demo](perplexity-ask/assets/demo_screenshot.png) +The official MCP server implementation for the Perplexity API Platform, providing AI assistants with real-time web search, reasoning, and research capabilities through Sonar models and the Search API. +## Available Tools -## Tools +### **perplexity_search** +Direct web search using the Perplexity Search API. Returns ranked search results with metadata, perfect for finding current information. -- **perplexity_ask** - - Engage in a conversation with the Sonar API for live web searches. - - **Inputs:** - - `messages` (array): An array of conversation messages. - - Each message must include: - - `role` (string): The role of the message (e.g., `system`, `user`, `assistant`). - - `content` (string): The content of the message. +### **perplexity_ask** +General-purpose conversational AI with real-time web search using the `sonar-pro` model. Great for quick questions and everyday searches. + +### **perplexity_research** +Deep, comprehensive research using the `sonar-deep-research` model. Ideal for thorough analysis and detailed reports. + +### **perplexity_reason** +Advanced reasoning and problem-solving using the `sonar-reasoning-pro` model. Perfect for complex analytical tasks. + +> [!TIP] +> Available as an optional parameter for **perplexity_reason** and **perplexity_research**: `strip_thinking` +> +> Set to `true` to remove `...` tags from the response, saving context tokens. Default: `false` ## Configuration -### Step 1: +### Get Your API Key -Clone this repository: +1. Get your Perplexity API Key from the [API Portal](https://www.perplexity.ai/account/api/group) +2. Replace `your_key_here` in the configurations below with your API key +3. (Optional) Set timeout: `PERPLEXITY_TIMEOUT_MS=600000` (default: 5 minutes) +4. (Optional) Set custom base URL: `PERPLEXITY_BASE_URL=https://your-custom-url.com` (default: https://api.perplexity.ai) +5. (Optional) Set log level: `PERPLEXITY_LOG_LEVEL=DEBUG|INFO|WARN|ERROR` (default: ERROR) + +### Claude Code ```bash -git clone git@github.com:ppl-ai/modelcontextprotocol.git +claude mcp add perplexity --env PERPLEXITY_API_KEY="your_key_here" -- npx -y @perplexity-ai/mcp-server ``` -Navigate to the `perplexity-ask` directory and install the necessary dependencies: - +Or install via plugin: ```bash -cd modelcontextprotocol/perplexity-ask && npm install +export PERPLEXITY_API_KEY="your_key_here" +claude +# Then run: /plugin marketplace add perplexityai/modelcontextprotocol +# Then run: /plugin install perplexity ``` -### Step 2: Get a Sonar API Key - -1. Sign up for a [Sonar API account](https://docs.perplexity.ai/guides/getting-started). -2. Follow the account setup instructions and generate your API key from the developer dashboard. -3. Set the API key in your environment as `PERPLEXITY_API_KEY`. +### Cursor, Claude Desktop & Windsurf -### Step 3: Configure Claude Desktop +We recommend using the one-click install badge at the top of this README for Cursor. -1. Download Claude desktop [here](https://claude.ai/download). +For manual setup, all these clients use the same `mcpServers` format: -2. Add this to your `claude_desktop_config.json`: +| Client | Config File | +|--------|-------------| +| Cursor | `~/.cursor/mcp.json` | +| Claude Desktop | `claude_desktop_config.json` | +| Windsurf | `~/.codeium/windsurf/mcp_config.json` | ```json { "mcpServers": { - "perplexity-ask": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "-e", - "PERPLEXITY_API_KEY", - "mcp/perplexity-ask" - ], + "perplexity": { + "command": "npx", + "args": ["-y", "@perplexity-ai/mcp-server"], "env": { - "PERPLEXITY_API_KEY": "YOUR_API_KEY_HERE" + "PERPLEXITY_API_KEY": "your_key_here" } } } } ``` -### NPX +### VS Code + +We recommend using the one-click install badge at the top of this README for VS Code, or for manual setup, add to `.vscode/mcp.json`: ```json { - "mcpServers": { - "perplexity-ask": { + "servers": { + "perplexity": { + "type": "stdio", "command": "npx", - "args": [ - "-y", - "server-perplexity-ask" - ], + "args": ["-y", "@perplexity-ai/mcp-server"], "env": { - "PERPLEXITY_API_KEY": "YOUR_API_KEY_HERE" + "PERPLEXITY_API_KEY": "your_key_here" } } } } ``` -You can access the file using: +### Codex ```bash -vim ~/Library/Application\ Support/Claude/claude_desktop_config.json +codex mcp add perplexity --env PERPLEXITY_API_KEY="your_key_here" -- npx -y @perplexity-ai/mcp-server ``` -### Step 4: Build the Docker Image +### Other MCP Clients -Docker build: +Most clients can be manually configured to use the `mcpServers` wrapper in their configuration file (like Cursor). If your client doesn't work, check its documentation for the correct wrapper format. -```bash -docker build -t mcp/perplexity-ask:latest -f Dockerfile . -``` +### Proxy Setup (For Corporate Networks) -### Step 5: Testing +If you are running this server at work—especially behind a company firewall or proxy—you may need to tell the program how to send its internet traffic through your network's proxy. Follow these steps: -Let's make sure Claude for Desktop is picking up the two tools we've exposed in our `perplexity-ask` server. You can do this by looking for the hammer icon: +**1. Get your proxy details** -![Claude Visual Tools](perplexity-ask/assets/visual-indicator-mcp-tools.png) +- Ask your IT department for your HTTPS proxy address and port. +- You may also need a username and password. -After clicking on the hammer icon, you should see the tools that come with the Filesystem MCP Server: +**2. Set the proxy environment variable** -![Available Integration](perplexity-ask/assets/available_tools.png) +The easiest and most reliable way for Perplexity MCP is to use `PERPLEXITY_PROXY`. For example: -If you see both of these this means that the integration is active. Congratulations! This means Claude can now ask Perplexity. You can then simply use it as you would use the Perplexity web app. - -### Step 6: Advanced parameters - -Currently, the search parameters used are the default ones. You can modify any search parameter in the API call directly in the `index.ts` script. For this, please refer to the official [API documentation](https://docs.perplexity.ai/api-reference/chat-completions). +```bash +export PERPLEXITY_PROXY=https://your-proxy-host:8080 +``` -### Troubleshooting +If your proxy needs a username and password, use: -The Claude documentation provides an excellent [troubleshooting guide](https://modelcontextprotocol.io/docs/tools/debugging) you can refer to. However, you can still reach out to us at api@perplexity.ai for any additional support or [file a bug](https://github.com/ppl-ai/api-discussion/issues). +```bash +export PERPLEXITY_PROXY=https://username:password@your-proxy-host:8080 +``` +**3. Alternate: Standard environment variables** -# Cursor integration +If you'd rather use the standard variables, we support `HTTPS_PROXY` and `HTTP_PROXY`. -You can also use our MCP with Cursor (or any other app that supports this). To use Sonar with Cursor, you can follow the following steps. +> [!NOTE] +> The server checks proxy settings in this order: `PERPLEXITY_PROXY` → `HTTPS_PROXY` → `HTTP_PROXY`. If none are set, it connects directly to the internet. +> URLs must include `https://`. Typical ports are `8080`, `3128`, and `80`. -### Step 1: Navigate to your Cursor settings: +### HTTP Server Deployment -![Cursor Settings](perplexity-ask/assets/cursor-settings.png) +For cloud or shared deployments, run the server in HTTP mode. -### Step 2: Navigate to the MCP directory +#### Environment Variables -And click on `Add new global MCP server` +| Variable | Description | Default | +|----------|-------------|---------| +| `PERPLEXITY_API_KEY` | Your Perplexity API key | *Required* | +| `PERPLEXITY_BASE_URL` | Custom base URL for API requests | `https://api.perplexity.ai` | +| `PORT` | HTTP server port | `8080` | +| `BIND_ADDRESS` | Network interface to bind to | `0.0.0.0` | +| `ALLOWED_ORIGINS` | CORS origins (comma-separated) | `*` | -![Add Server](perplexity-ask/assets/cursor-mcp-directory.png) +#### Docker +```bash +docker build -t perplexity-mcp-server . +docker run -p 8080:8080 -e PERPLEXITY_API_KEY=your_key_here perplexity-mcp-server +``` -### Step 3: Insert the MCP Server Configuration from above +#### Node.js -This is the same configuration you would use for any other application that supports MCP. +```bash +export PERPLEXITY_API_KEY=your_key_here +npm install && npm run build && npm run start:http +``` -You should then see the application being part of your available tools like this: +The server will be accessible at `http://localhost:8080/mcp` -![Cursor MCP](perplexity-ask/assets/perplexity-ask-mcp-cursor.png) +## Troubleshooting +- **API Key Issues**: Ensure `PERPLEXITY_API_KEY` is set correctly +- **Connection Errors**: Check your internet connection and API key validity +- **Tool Not Found**: Make sure the package is installed and the command path is correct +- **Timeout Errors**: For very long research queries, set `PERPLEXITY_TIMEOUT_MS` to a higher value +- **Proxy Issues**: Verify your `PERPLEXITY_PROXY` or `HTTPS_PROXY` setup and ensure `api.perplexity.ai` isn't blocked by your firewall. +- **EOF / Initialize Errors**: Some strict MCP clients fail because `npx` writes installation messages to stdout. Use `npx -yq` instead of `npx -y` to suppress this output. -## License +For support, visit [community.perplexity.ai](https://community.perplexity.ai) or [file an issue](https://github.com/perplexityai/modelcontextprotocol/issues). -This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. +--- diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c5096c6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4019 @@ +{ + "name": "@perplexity-ai/mcp-server", + "version": "0.9.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@perplexity-ai/mcp-server", + "version": "0.9.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.21.1", + "cors": "^2.8.5", + "express": "^4.21.2", + "undici": "^6.20.0", + "zod": "^3.25.46" + }, + "bin": { + "perplexity-mcp": "dist/index.js" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/node": "^20", + "@vitest/coverage-v8": "^4.0.5", + "shx": "^0.4.0", + "tsx": "^4.19.4", + "typescript": "^5.9.3", + "vitest": "^4.0.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "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.27.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", + "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@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.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@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/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/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@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.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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/@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.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@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/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@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/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "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/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "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/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/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "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/node": { + "version": "20.19.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.29.tgz", + "integrity": "sha512-YrT9ArrGaHForBaCNwFjoqJWmn8G1Pr7+BH/vwyLHciA9qT/wSiuOhxGCT50JA5xLvFBd6PIiGkE3afxcPE1nw==", + "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": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.17.tgz", + "integrity": "sha512-/6zU2FLGg0jsd+ePZcwHRy3+WpNTBBhDY56P4JTRqUN/Dp6CvOEa9HrikcQ4KfV2b2kAHUFB4dl1SuocWXSFEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.17", + "ast-v8-to-istanbul": "^0.3.10", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.17", + "vitest": "4.0.17" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.17.tgz", + "integrity": "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.17", + "@vitest/utils": "4.0.17", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.17.tgz", + "integrity": "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.17", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", + "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.17.tgz", + "integrity": "sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.17.tgz", + "integrity": "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.17", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.17.tgz", + "integrity": "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz", + "integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.17", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "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/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/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/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "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.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "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/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/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/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "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/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": "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/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/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/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/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "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-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "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.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "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/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.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": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "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.14.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.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-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-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "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.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/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/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-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": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-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/hono": { + "version": "4.12.8", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.8.tgz", + "integrity": "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.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/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/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "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-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-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": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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-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-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/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, + "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/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/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/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.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "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/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "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/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-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/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "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/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/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/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "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.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "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-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/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/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/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/router/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/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": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/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/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "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/shelljs": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.9.2.tgz", + "integrity": "sha512-S3I64fEiKgTZzKCC46zT/Ib9meqofLrQVbpSswtjFfAVDW+AZ54WTnAM/3/yENoxz/V1Cy6u3kiiEbQ4DNphvw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "execa": "^1.0.0", + "fast-glob": "^3.3.2", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/shx": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.4.0.tgz", + "integrity": "sha512-Z0KixSIlGPpijKgcH6oCMCbltPImvaKy0sGH8AkLRXw1KyzpKtaCTizP2xen+hNDqVF4xxgvA0KXSb9o4Q6hnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.8", + "shelljs": "^0.9.2" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "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/undici": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "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/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/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.17.tgz", + "integrity": "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.17", + "@vitest/mocker": "4.0.17", + "@vitest/pretty-format": "4.0.17", + "@vitest/runner": "4.0.17", + "@vitest/snapshot": "4.0.17", + "@vitest/spy": "4.0.17", + "@vitest/utils": "4.0.17", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.17", + "@vitest/browser-preview": "4.0.17", + "@vitest/browser-webdriverio": "4.0.17", + "@vitest/ui": "4.0.17", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/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/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/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.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c34f6d4 --- /dev/null +++ b/package.json @@ -0,0 +1,71 @@ +{ + "name": "@perplexity-ai/mcp-server", + "version": "0.9.0", + "mcpName": "ai.perplexity/mcp-server", + "description": "Real-time web search, reasoning, and research through Perplexity's API", + "keywords": [ + "ai", + "perplexity", + "mcp", + "modelcontextprotocol" + ], + "homepage": "https://docs.perplexity.ai/guides/mcp-server", + "bugs": { + "url": "https://github.com/perplexityai/modelcontextprotocol/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/perplexityai/modelcontextprotocol.git" + }, + "license": "MIT", + "author": "Perplexity <>", + "publishConfig": { + "access": "public" + }, + "type": "module", + "main": "dist/index.js", + "bin": { + "perplexity-mcp": "dist/index.js" + }, + "files": [ + "dist/*.js", + "!dist/*.test.js", + "!dist/vitest.config.js", + "README.md", + ".claude-plugin" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch", + "start": "node dist/index.js", + "start:http": "node dist/http.js", + "start:http:public": "BIND_ADDRESS=0.0.0.0 ALLOWED_ORIGINS=* node dist/http.js", + "dev": "tsx src/index.ts", + "dev:http": "tsx src/http.ts", + "dev:http:public": "BIND_ADDRESS=0.0.0.0 ALLOWED_ORIGINS=* tsx src/http.ts", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.21.1", + "cors": "^2.8.5", + "express": "^4.21.2", + "undici": "^6.20.0", + "zod": "^3.25.46" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/node": "^20", + "@vitest/coverage-v8": "^4.0.5", + "shx": "^0.4.0", + "tsx": "^4.19.4", + "typescript": "^5.9.3", + "vitest": "^4.0.5" + }, + "engines": { + "node": ">=18" + } +} diff --git a/perplexity-ask/assets/available_tools.png b/perplexity-ask/assets/available_tools.png deleted file mode 100644 index e695100..0000000 Binary files a/perplexity-ask/assets/available_tools.png and /dev/null differ diff --git a/perplexity-ask/assets/cursor-mcp-directory.png b/perplexity-ask/assets/cursor-mcp-directory.png deleted file mode 100644 index c9b9a28..0000000 Binary files a/perplexity-ask/assets/cursor-mcp-directory.png and /dev/null differ diff --git a/perplexity-ask/assets/cursor-settings.png b/perplexity-ask/assets/cursor-settings.png deleted file mode 100644 index dfdaefb..0000000 Binary files a/perplexity-ask/assets/cursor-settings.png and /dev/null differ diff --git a/perplexity-ask/assets/demo_screenshot.png b/perplexity-ask/assets/demo_screenshot.png deleted file mode 100644 index d0b91ba..0000000 Binary files a/perplexity-ask/assets/demo_screenshot.png and /dev/null differ diff --git a/perplexity-ask/assets/perplexity-ask-mcp-cursor.png b/perplexity-ask/assets/perplexity-ask-mcp-cursor.png deleted file mode 100644 index 70aadc6..0000000 Binary files a/perplexity-ask/assets/perplexity-ask-mcp-cursor.png and /dev/null differ diff --git a/perplexity-ask/assets/visual-indicator-mcp-tools.png b/perplexity-ask/assets/visual-indicator-mcp-tools.png deleted file mode 100644 index af71d07..0000000 Binary files a/perplexity-ask/assets/visual-indicator-mcp-tools.png and /dev/null differ diff --git a/perplexity-ask/index.ts b/perplexity-ask/index.ts deleted file mode 100644 index f5e5d6d..0000000 --- a/perplexity-ask/index.ts +++ /dev/null @@ -1,310 +0,0 @@ -#!/usr/bin/env node - -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { - CallToolRequestSchema, - ListToolsRequestSchema, - Tool, -} from "@modelcontextprotocol/sdk/types.js"; - -/** - * Definition of the Perplexity Ask Tool. - * This tool accepts an array of messages and returns a chat completion response - * from the Perplexity API, with citations appended to the message if provided. - */ -const PERPLEXITY_ASK_TOOL: Tool = { - name: "perplexity_ask", - description: - "Engages in a conversation using the Sonar API. " + - "Accepts an array of messages (each with a role and content) " + - "and returns a ask completion response from the Perplexity model.", - inputSchema: { - type: "object", - properties: { - messages: { - type: "array", - items: { - type: "object", - properties: { - role: { - type: "string", - description: "Role of the message (e.g., system, user, assistant)", - }, - content: { - type: "string", - description: "The content of the message", - }, - }, - required: ["role", "content"], - }, - description: "Array of conversation messages", - }, - }, - required: ["messages"], - }, -}; - -/** - * Definition of the Perplexity Research Tool. - * This tool performs deep research queries using the Perplexity API. - */ -const PERPLEXITY_RESEARCH_TOOL: Tool = { - name: "perplexity_research", - description: - "Performs deep research using the Perplexity API. " + - "Accepts an array of messages (each with a role and content) " + - "and returns a comprehensive research response with citations.", - inputSchema: { - type: "object", - properties: { - messages: { - type: "array", - items: { - type: "object", - properties: { - role: { - type: "string", - description: "Role of the message (e.g., system, user, assistant)", - }, - content: { - type: "string", - description: "The content of the message", - }, - }, - required: ["role", "content"], - }, - description: "Array of conversation messages", - }, - }, - required: ["messages"], - }, -}; - -/** - * Definition of the Perplexity Reason Tool. - * This tool performs reasoning queries using the Perplexity API. - */ -const PERPLEXITY_REASON_TOOL: Tool = { - name: "perplexity_reason", - description: - "Performs reasoning tasks using the Perplexity API. " + - "Accepts an array of messages (each with a role and content) " + - "and returns a well-reasoned response using the sonar-reasoning-pro model.", - inputSchema: { - type: "object", - properties: { - messages: { - type: "array", - items: { - type: "object", - properties: { - role: { - type: "string", - description: "Role of the message (e.g., system, user, assistant)", - }, - content: { - type: "string", - description: "The content of the message", - }, - }, - required: ["role", "content"], - }, - description: "Array of conversation messages", - }, - }, - required: ["messages"], - }, -}; - -// Retrieve the Perplexity API key from environment variables -const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY; -if (!PERPLEXITY_API_KEY) { - console.error("Error: PERPLEXITY_API_KEY environment variable is required"); - process.exit(1); -} - -/** - * Performs a chat completion by sending a request to the Perplexity API. - * Appends citations to the returned message content if they exist. - * - * @param {Array<{ role: string; content: string }>} messages - An array of message objects. - * @param {string} model - The model to use for the completion. - * @returns {Promise} The chat completion result with appended citations. - * @throws Will throw an error if the API request fails. - */ -async function performChatCompletion( - messages: Array<{ role: string; content: string }>, - model: string = "sonar-pro" -): Promise { - // Construct the API endpoint URL and request body - const url = new URL("https://api.perplexity.ai/chat/completions"); - const body = { - model: model, // Model identifier passed as parameter - messages: messages, - // Additional parameters can be added here if required (e.g., max_tokens, temperature, etc.) - // See the Sonar API documentation for more details: - // https://docs.perplexity.ai/api-reference/chat-completions - }; - - let response; - try { - response = await fetch(url.toString(), { - method: "POST", - headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${PERPLEXITY_API_KEY}`, - }, - body: JSON.stringify(body), - }); - } catch (error) { - throw new Error(`Network error while calling Perplexity API: ${error}`); - } - - // Check for non-successful HTTP status - if (!response.ok) { - let errorText; - try { - errorText = await response.text(); - } catch (parseError) { - errorText = "Unable to parse error response"; - } - throw new Error( - `Perplexity API error: ${response.status} ${response.statusText}\n${errorText}` - ); - } - - // Attempt to parse the JSON response from the API - let data; - try { - data = await response.json(); - } catch (jsonError) { - throw new Error(`Failed to parse JSON response from Perplexity API: ${jsonError}`); - } - - // Directly retrieve the main message content from the response - let messageContent = data.choices[0].message.content; - - // If citations are provided, append them to the message content - if (data.citations && Array.isArray(data.citations) && data.citations.length > 0) { - messageContent += "\n\nCitations:\n"; - data.citations.forEach((citation: string, index: number) => { - messageContent += `[${index + 1}] ${citation}\n`; - }); - } - - return messageContent; -} - -// Initialize the server with tool metadata and capabilities -const server = new Server( - { - name: "example-servers/perplexity-ask", - version: "0.1.0", - }, - { - capabilities: { - tools: {}, - }, - } -); - -/** - * Registers a handler for listing available tools. - * When the client requests a list of tools, this handler returns all available Perplexity tools. - */ -server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools: [PERPLEXITY_ASK_TOOL, PERPLEXITY_RESEARCH_TOOL, PERPLEXITY_REASON_TOOL], -})); - -/** - * Registers a handler for calling a specific tool. - * Processes requests by validating input and invoking the appropriate tool. - * - * @param {object} request - The incoming tool call request. - * @returns {Promise} The response containing the tool's result or an error. - */ -server.setRequestHandler(CallToolRequestSchema, async (request) => { - try { - const { name, arguments: args } = request.params; - if (!args) { - throw new Error("No arguments provided"); - } - switch (name) { - case "perplexity_ask": { - if (!Array.isArray(args.messages)) { - throw new Error("Invalid arguments for perplexity_ask: 'messages' must be an array"); - } - // Invoke the chat completion function with the provided messages - const messages = args.messages; - const result = await performChatCompletion(messages, "sonar-pro"); - return { - content: [{ type: "text", text: result }], - isError: false, - }; - } - case "perplexity_research": { - if (!Array.isArray(args.messages)) { - throw new Error("Invalid arguments for perplexity_research: 'messages' must be an array"); - } - // Invoke the chat completion function with the provided messages using the deep research model - const messages = args.messages; - const result = await performChatCompletion(messages, "sonar-deep-research"); - return { - content: [{ type: "text", text: result }], - isError: false, - }; - } - case "perplexity_reason": { - if (!Array.isArray(args.messages)) { - throw new Error("Invalid arguments for perplexity_reason: 'messages' must be an array"); - } - // Invoke the chat completion function with the provided messages using the reasoning model - const messages = args.messages; - const result = await performChatCompletion(messages, "sonar-reasoning-pro"); - return { - content: [{ type: "text", text: result }], - isError: false, - }; - } - default: - // Respond with an error if an unknown tool is requested - return { - content: [{ type: "text", text: `Unknown tool: ${name}` }], - isError: true, - }; - } - } catch (error) { - // Return error details in the response - return { - content: [ - { - type: "text", - text: `Error: ${error instanceof Error ? error.message : String(error)}`, - }, - ], - isError: true, - }; - } -}); - -/** - * Initializes and runs the server using standard I/O for communication. - * Logs an error and exits if the server fails to start. - */ -async function runServer() { - try { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("Perplexity MCP Server running on stdio with Ask, Research, and Reason tools"); - } catch (error) { - console.error("Fatal error running server:", error); - process.exit(1); - } -} - -// Start the server and catch any startup errors -runServer().catch((error) => { - console.error("Fatal error running server:", error); - process.exit(1); -}); diff --git a/perplexity-ask/package-lock.json b/perplexity-ask/package-lock.json deleted file mode 100644 index f8e1c2d..0000000 --- a/perplexity-ask/package-lock.json +++ /dev/null @@ -1,443 +0,0 @@ -{ - "name": "@modelcontextprotocol/server-perplexity-ask", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@modelcontextprotocol/server-perplexity-ask", - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.0.1" - }, - "bin": { - "mcp-server-perplexity-ask": "dist/index.js" - }, - "devDependencies": { - "@types/node": "^22", - "shx": "^0.3.4", - "typescript": "^5.6.2" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.0.1.tgz", - "integrity": "sha512-slLdFaxQJ9AlRg+hw28iiTtGvShAOgOKXcD0F91nUcRYiOMuS9ZBYjcdNZRXW9G5JQ511GRTdUy1zQVZDpJ+4w==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "raw-body": "^3.0.0", - "zod": "^3.23.8" - } - }, - "node_modules/@types/node": { - "version": "22.13.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz", - "integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.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/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "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/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-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/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/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/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "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/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/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/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "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/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/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "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-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/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "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/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/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/shx": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", - "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.3", - "shelljs": "^0.8.5" - }, - "bin": { - "shx": "lib/cli.js" - }, - "engines": { - "node": ">=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" - } - }, - "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/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/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "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/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/zod": { - "version": "3.24.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", - "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/perplexity-ask/package.json b/perplexity-ask/package.json index 489e647..362689e 100644 --- a/perplexity-ask/package.json +++ b/perplexity-ask/package.json @@ -1,30 +1,34 @@ { - "name": "server-perplexity-ask", - "version": "0.1.0", - "description": "MCP server for Perplexity API integration", + "name": "@perplexity-ai/mcp-server", + "version": "0.2.0", + "description": "Official MCP server for Perplexity API Platform", "keywords": [ "ai", "perplexity", "mcp", "modelcontextprotocol" ], - "homepage": "https://modelcontextprotocol.io", + "homepage": "https://docs.perplexity.ai/guides/mcp-server", "bugs": { - "url": "https://github.com/modelcontextprotocol/servers/issues" + "url": "https://github.com/perplexityai/modelcontextprotocol/issues" }, "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/servers.git" + "url": "git+https://github.com/perplexityai/modelcontextprotocol.git" }, "license": "MIT", - "author": "Model Context Protocol (https://modelcontextprotocol.io)", + "author": "Perplexity <>", + "publishConfig": { + "access": "public" + }, "type": "module", "main": "dist/index.js", "bin": { - "mcp-server-perplexity-ask": "dist/index.js" + "perplexity-mcp": "dist/index.js" }, "files": [ - "dist" + "dist", + "README.md" ], "scripts": { "build": "tsc && shx chmod +x dist/*.js", diff --git a/server.json b/server.json new file mode 100644 index 0000000..0ebc5cd --- /dev/null +++ b/server.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "ai.perplexity/mcp-server", + "title": "Perplexity API Platform", + "description": "Real-time web search, reasoning, and research through Perplexity's API", + "version": "0.9.0", + "packages": [ + { + "registryType": "npm", + "identifier": "@perplexity-ai/mcp-server", + "version": "0.9.0", + "transport": { + "type": "stdio" + } + } + ] +} + diff --git a/smithery.yaml b/smithery.yaml new file mode 100644 index 0000000..d5e02c6 --- /dev/null +++ b/smithery.yaml @@ -0,0 +1,17 @@ +runtime: "container" +build: + dockerfile: "Dockerfile" + dockerBuildPath: "." +startCommand: + type: "http" +configSchema: + type: "object" + required: + - "PERPLEXITY_API_KEY" + properties: + PERPLEXITY_API_KEY: + type: "string" + description: "Your Perplexity API key from https://www.perplexity.ai/settings/api" + PERPLEXITY_TIMEOUT_MS: + type: "string" + description: "Request timeout in milliseconds (default: 300000)" \ No newline at end of file diff --git a/src/http.ts b/src/http.ts new file mode 100644 index 0000000..36b28aa --- /dev/null +++ b/src/http.ts @@ -0,0 +1,80 @@ +#!/usr/bin/env node + +import express from "express"; +import cors from "cors"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createPerplexityServer } from "./server.js"; +import { logger } from "./logger.js"; + +const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY; +if (!PERPLEXITY_API_KEY) { + logger.error("PERPLEXITY_API_KEY environment variable is required"); + process.exit(1); +} + +const app = express(); +const PORT = parseInt(process.env.PORT || "8080", 10); +const BIND_ADDRESS = process.env.BIND_ADDRESS || "0.0.0.0"; +const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(",") || ["*"]; + +// CORS configuration for browser-based MCP clients +app.use(cors({ + origin: (origin, callback) => { + if (!origin) return callback(null, true); + + if (ALLOWED_ORIGINS.includes("*")) { + return callback(null, true); + } + + if (ALLOWED_ORIGINS.includes(origin)) { + callback(null, true); + } else { + callback(new Error(`Origin ${origin} not allowed by CORS`)); + } + }, + exposedHeaders: ["Mcp-Session-Id", "mcp-protocol-version"], + allowedHeaders: ["Content-Type", "mcp-session-id"], +})); + +app.use(express.json()); + +const mcpServer = createPerplexityServer(); + +app.all("/mcp", async (req, res) => { + try { + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true, + }); + + res.on('close', () => { + transport.close(); + }); + + await mcpServer.connect(transport); + + await transport.handleRequest(req, res, req.body); + } catch (error) { + logger.error("Error handling MCP request", { error: String(error) }); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: "2.0", + error: { code: -32603, message: "Internal server error" }, + id: null, + }); + } + } +}); + +app.get("/health", (req, res) => { + res.json({ status: "ok", service: "perplexity-mcp-server" }); +}); + +app.listen(PORT, BIND_ADDRESS, () => { + logger.info(`Perplexity MCP Server listening on http://${BIND_ADDRESS}:${PORT}/mcp`); + logger.info(`Allowed origins: ${ALLOWED_ORIGINS.join(", ")}`); +}).on("error", (error) => { + logger.error("Server error", { error: String(error) }); + process.exit(1); +}); + diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..21897b8 --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,786 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { formatSearchResults, performChatCompletion, performSearch } from "./server.js"; + +describe("Perplexity MCP Server", () => { + let originalFetch: typeof global.fetch; + + beforeEach(() => { + originalFetch = global.fetch; + }); + + afterEach(() => { + global.fetch = originalFetch; + vi.restoreAllMocks(); + }); + + describe("formatSearchResults", () => { + it("should format search results correctly", () => { + const mockData = { + results: [ + { + title: "Test Result 1", + url: "https://example.com/1", + snippet: "This is a test snippet", + date: "2025-01-01", + }, + { + title: "Test Result 2", + url: "https://example.com/2", + snippet: "Another snippet", + }, + ], + }; + + const formatted = formatSearchResults(mockData); + + expect(formatted).toContain("Found 2 search results"); + expect(formatted).toContain("Test Result 1"); + expect(formatted).toContain("https://example.com/1"); + expect(formatted).toContain("This is a test snippet"); + expect(formatted).toContain("Date: 2025-01-01"); + expect(formatted).toContain("Test Result 2"); + }); + + it("should handle empty results", () => { + const mockData = { results: [] }; + const formatted = formatSearchResults(mockData); + expect(formatted).toContain("Found 0 search results"); + }); + + it("should handle missing results array", () => { + const mockData = {} as any; + const formatted = formatSearchResults(mockData); + expect(formatted).toBe("No search results found."); + }); + }); + + describe("performChatCompletion", () => { + it("should successfully complete chat request", async () => { + const mockResponse = { + choices: [ + { + message: { + content: "This is a test response", + }, + }, + ], + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const messages = [{ role: "user", content: "test question" }]; + const result = await performChatCompletion(messages, "sonar-pro"); + + expect(result).toBe("This is a test response"); + expect(global.fetch).toHaveBeenCalledWith( + "https://api.perplexity.ai/chat/completions", + expect.objectContaining({ + method: "POST", + headers: expect.objectContaining({ + "Content-Type": "application/json", + Authorization: "Bearer test-api-key", + "X-Source": "pplx-mcp-server", + }), + body: JSON.stringify({ + model: "sonar-pro", + messages, + }), + }) + ); + }); + + it("should append citations when present", async () => { + const mockResponse = { + choices: [ + { + message: { + content: "Response with citations", + }, + }, + ], + citations: [ + "https://example.com/source1", + "https://example.com/source2", + ], + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const messages = [{ role: "user", content: "test" }]; + const result = await performChatCompletion(messages); + + expect(result).toContain("Response with citations"); + expect(result).toContain("\n\nCitations:\n"); + expect(result).toContain("[1] https://example.com/source1"); + expect(result).toContain("[2] https://example.com/source2"); + }); + + it("should handle API errors", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 401, + statusText: "Unauthorized", + text: async () => "Invalid API key", + } as Response); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "Perplexity API error: 401 Unauthorized" + ); + }); + + it("should handle timeout errors", async () => { + process.env.PERPLEXITY_TIMEOUT_MS = "100"; + + global.fetch = vi.fn().mockImplementation((_url, options) => { + return new Promise((resolve, reject) => { + const signal = options?.signal as AbortSignal; + + if (signal) { + signal.addEventListener("abort", () => { + reject(new DOMException("The operation was aborted.", "AbortError")); + }); + } + + setTimeout(() => { + resolve({ + ok: true, + json: async () => ({ choices: [{ message: { content: "late" } }] }), + } as Response); + }, 200); + }); + }); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "Request timeout" + ); + }); + + it("should handle network errors", async () => { + global.fetch = vi.fn().mockRejectedValue(new Error("Network failure")); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "Network error while calling Perplexity API" + ); + }); + }); + + describe("performSearch", () => { + it("should successfully perform search", async () => { + const mockResponse = { + results: [ + { + title: "Search Result", + url: "https://example.com", + snippet: "Test snippet", + }, + ], + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const result = await performSearch("test query", 10, 1024); + + expect(result).toContain("Found 1 search results"); + expect(result).toContain("Search Result"); + expect(global.fetch).toHaveBeenCalledWith( + "https://api.perplexity.ai/search", + expect.objectContaining({ + method: "POST", + headers: expect.objectContaining({ + "Content-Type": "application/json", + Authorization: "Bearer test-api-key", + }), + body: JSON.stringify({ + query: "test query", + max_results: 10, + max_tokens_per_page: 1024, + }), + }) + ); + }); + + it("should include country parameter when provided", async () => { + const mockResponse = { results: [] }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + await performSearch("test", 10, 1024, "US"); + + expect(global.fetch).toHaveBeenCalledWith( + "https://api.perplexity.ai/search", + expect.objectContaining({ + body: JSON.stringify({ + query: "test", + max_results: 10, + max_tokens_per_page: 1024, + country: "US", + }), + }) + ); + }); + + it("should handle search API errors", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 500, + statusText: "Internal Server Error", + text: async () => "Server error", + } as Response); + + await expect(performSearch("test")).rejects.toThrow( + "Perplexity API error: 500 Internal Server Error" + ); + }); + + it("should handle search timeout errors", async () => { + process.env.PERPLEXITY_TIMEOUT_MS = "100"; + + global.fetch = vi.fn().mockImplementation((_url, options) => { + return new Promise((resolve, reject) => { + const signal = options?.signal as AbortSignal; + + if (signal) { + signal.addEventListener("abort", () => { + reject(new DOMException("The operation was aborted.", "AbortError")); + }); + } + + setTimeout(() => { + resolve({ + ok: true, + json: async () => ({ results: [] }), + } as Response); + }, 200); + }); + }); + + await expect(performSearch("test")).rejects.toThrow( + "Request timeout" + ); + }); + + it("should handle search network errors", async () => { + global.fetch = vi.fn().mockRejectedValue(new Error("Network failure")); + + await expect(performSearch("test")).rejects.toThrow( + "Network error while calling Perplexity API" + ); + }); + }); + + describe("API Response Validation", () => { + it("should handle empty choices array", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ choices: [] }), + } as Response); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "missing or empty choices array" + ); + }); + + it("should handle missing message content", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ choices: [{ message: null }] }), + } as Response); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "missing message content" + ); + }); + + it("should handle missing choices property", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({}), + } as Response); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "missing or empty choices array" + ); + }); + + it("should handle malformed message object", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ choices: [{ message: { content: 123 } }] }), + } as Response); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "missing message content" + ); + }); + + it("should handle null choices", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ choices: null }), + } as Response); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "missing or empty choices array" + ); + }); + + it("should handle undefined message in choice", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ choices: [{}] }), + } as Response); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "missing message content" + ); + }); + + it("should handle empty citations array gracefully", async () => { + const mockResponse = { + choices: [{ message: { content: "Response" } }], + citations: [], + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const messages = [{ role: "user", content: "test" }]; + const result = await performChatCompletion(messages); + + expect(result).toBe("Response"); + expect(result).not.toContain("Citations:"); + }); + + it("should handle non-array citations", async () => { + const mockResponse = { + choices: [{ message: { content: "Response" } }], + citations: "not-an-array", + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "Failed to parse JSON response" + ); + }); + }); + + describe("Edge Cases", () => { + it("should handle JSON parse errors gracefully", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => { + throw new Error("Invalid JSON"); + }, + } as unknown as Response); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "Failed to parse JSON response" + ); + }); + + it("should handle error text parse failures", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 500, + statusText: "Internal Server Error", + text: async () => { + throw new Error("Cannot read error"); + }, + } as unknown as Response); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "Unable to parse error response" + ); + }); + + it("should handle special characters in messages", async () => { + const mockResponse = { + choices: [{ message: { content: "Response with émojis 🎉 and unicode ñ" } }], + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const messages = [{ role: "user", content: "test with émojis 🎉" }]; + const result = await performChatCompletion(messages); + + expect(result).toContain("émojis 🎉"); + expect(result).toContain("unicode ñ"); + }); + + it("should handle very long content strings", async () => { + const longContent = "x".repeat(100000); + const mockResponse = { + choices: [{ message: { content: longContent } }], + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const messages = [{ role: "user", content: "test" }]; + const result = await performChatCompletion(messages); + + expect(result).toBe(longContent); + expect(result.length).toBe(100000); + }); + + it("should handle multiple models correctly", async () => { + const models = ["sonar-pro", "sonar-deep-research", "sonar-reasoning-pro"]; + + for (const model of models) { + if (model === "sonar-deep-research") { + // sonar-deep-research uses streaming, so provide an SSE mock + const sseData = [ + `data: ${JSON.stringify({ choices: [{ delta: { content: "Response " } }] })}\n\n`, + `data: ${JSON.stringify({ choices: [{ delta: { content: `from ${model}` } }] })}\n\n`, + `data: [DONE]\n\n`, + ].join(""); + + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(sseData)); + controller.close(); + }, + }); + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + body: stream, + } as unknown as Response); + } else { + const mockResponse = { + choices: [{ message: { content: `Response from ${model}` } }], + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + } + + const messages = [{ role: "user", content: "test" }]; + const result = await performChatCompletion(messages, model); + + expect(result).toContain(model); + expect(global.fetch).toHaveBeenCalledWith( + "https://api.perplexity.ai/chat/completions", + expect.objectContaining({ + body: expect.stringContaining(`"model":"${model}"`), + }) + ); + } + }); + + it("should handle search with boundary values", async () => { + const mockResponse = { results: [] }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + // Test max values + await performSearch("test", 20, 2048); + expect(global.fetch).toHaveBeenCalledWith( + "https://api.perplexity.ai/search", + expect.objectContaining({ + body: expect.stringContaining('"max_results":20'), + }) + ); + + // Test min values + await performSearch("test", 1, 256); + expect(global.fetch).toHaveBeenCalledWith( + "https://api.perplexity.ai/search", + expect.objectContaining({ + body: expect.stringContaining('"max_results":1'), + }) + ); + }); + + it("should handle formatSearchResults with missing optional fields", async () => { + const mockData = { + results: [ + { title: "Test", url: "https://example.com" }, + { title: "Test 2", url: "https://example.com/2", snippet: "snippet only" }, + { title: "Test 3", url: "https://example.com/3", date: "2025-01-01" }, + ], + }; + + const formatted = formatSearchResults(mockData); + + expect(formatted).toContain("Test"); + expect(formatted).toContain("Test 2"); + expect(formatted).toContain("snippet only"); + expect(formatted).toContain("Date: 2025-01-01"); + expect(formatted).not.toContain("undefined"); + }); + + it("should handle concurrent requests correctly", async () => { + let callCount = 0; + global.fetch = vi.fn().mockImplementation(async () => { + const currentCall = ++callCount; + await new Promise((resolve) => setTimeout(resolve, 10)); + return { + ok: true, + json: async () => ({ + choices: [{ message: { content: `Response ${currentCall}` } }] + }), + } as Response; + }); + + const messages = [{ role: "user", content: "test" }]; + const promises = [ + performChatCompletion(messages), + performChatCompletion(messages), + performChatCompletion(messages), + ]; + + const results = await Promise.all(promises); + + expect(results).toHaveLength(3); + expect(global.fetch).toHaveBeenCalledTimes(3); + // Results should all be present (may not be unique due to timing) + expect(results.every(r => r.startsWith("Response"))).toBe(true); + }); + + it("should respect timeout on each call independently", async () => { + // First call with long timeout + process.env.PERPLEXITY_TIMEOUT_MS = "1000"; + + global.fetch = vi.fn().mockImplementation((_url, options) => { + return new Promise((resolve) => { + const signal = options?.signal as AbortSignal; + setTimeout(() => { + if (!signal?.aborted) { + resolve({ + ok: true, + json: async () => ({ choices: [{ message: { content: "fast" } }] }), + } as Response); + } + }, 50); + }); + }); + + const messages = [{ role: "user", content: "test" }]; + const result1 = await performChatCompletion(messages); + expect(result1).toBe("fast"); + + // Second call with short timeout + process.env.PERPLEXITY_TIMEOUT_MS = "10"; + + global.fetch = vi.fn().mockImplementation((_url, options) => { + return new Promise((resolve, reject) => { + const signal = options?.signal as AbortSignal; + + if (signal) { + signal.addEventListener("abort", () => { + reject(new DOMException("The operation was aborted.", "AbortError")); + }); + } + + setTimeout(() => { + resolve({ + ok: true, + json: async () => ({ choices: [{ message: { content: "slow" } }] }), + } as Response); + }, 100); + }); + }); + + await expect(performChatCompletion(messages)).rejects.toThrow("timeout"); + }); + }); + + describe("formatSearchResults Edge Cases", () => { + it("should handle results with null/undefined values", () => { + const mockData = { + results: [ + { title: null, url: "https://example.com", snippet: undefined }, + { title: "Valid", url: null, snippet: "snippet", date: undefined }, + ], + } as any; + + const formatted = formatSearchResults(mockData); + + expect(formatted).toContain("null"); + expect(formatted).toContain("Valid"); + expect(formatted).not.toContain("undefined"); + }); + + it("should handle empty strings in result fields", () => { + const mockData = { + results: [{ title: "", url: "", snippet: "", date: "" }], + }; + + const formatted = formatSearchResults(mockData); + + expect(formatted).toContain("Found 1 search results"); + }); + + it("should handle results with extra unexpected fields", () => { + const mockData = { + results: [ + { + title: "Test", + url: "https://example.com", + unexpectedField: "should be ignored", + anotherField: 12345, + }, + ], + }; + + const formatted = formatSearchResults(mockData); + + expect(formatted).toContain("Test"); + expect(formatted).not.toContain("unexpectedField"); + expect(formatted).not.toContain("12345"); + }); + }); + + describe("strip_thinking parameter", () => { + it("should strip thinking tokens when true and keep them when false", async () => { + const mockResponse = { + choices: [ + { + message: { + content: "This is my reasoning process\n\nThe answer is 4.", + }, + }, + ], + }; + + // Test with stripThinking = true + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const messages = [{ role: "user", content: "What is 2+2?" }]; + const resultStripped = await performChatCompletion(messages, "sonar-reasoning-pro", true); + + expect(resultStripped).not.toContain(""); + expect(resultStripped).not.toContain(""); + expect(resultStripped).not.toContain("This is my reasoning process"); + expect(resultStripped).toContain("The answer is 4."); + + // Test with stripThinking = false + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const resultKept = await performChatCompletion(messages, "sonar-reasoning-pro", false); + + expect(resultKept).toContain("This is my reasoning process"); + expect(resultKept).toContain("The answer is 4."); + }); + }); + + describe("Proxy Support", () => { + const originalEnv = process.env; + + beforeEach(() => { + // Reset environment variables + process.env = { ...originalEnv }; + delete process.env.PERPLEXITY_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.HTTP_PROXY; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("should use native fetch when no proxy is configured", async () => { + const mockResponse = { + choices: [{ message: { content: "Test response" } }], + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + const messages = [{ role: "user", content: "test" }]; + await performChatCompletion(messages); + + // Verify native fetch was called (not undici) + expect(global.fetch).toHaveBeenCalled(); + }); + + it("should read PERPLEXITY_PROXY environment variable", () => { + process.env.PERPLEXITY_PROXY = "http://proxy.example.com:8080"; + expect(process.env.PERPLEXITY_PROXY).toBe("http://proxy.example.com:8080"); + }); + + it("should prioritize PERPLEXITY_PROXY over HTTPS_PROXY", () => { + process.env.PERPLEXITY_PROXY = "http://perplexity-proxy.example.com:8080"; + process.env.HTTPS_PROXY = "http://https-proxy.example.com:8080"; + + // PERPLEXITY_PROXY should take precedence + expect(process.env.PERPLEXITY_PROXY).toBe("http://perplexity-proxy.example.com:8080"); + }); + + it("should fall back to HTTPS_PROXY when PERPLEXITY_PROXY is not set", () => { + delete process.env.PERPLEXITY_PROXY; + process.env.HTTPS_PROXY = "http://https-proxy.example.com:8080"; + + expect(process.env.HTTPS_PROXY).toBe("http://https-proxy.example.com:8080"); + }); + + it("should fall back to HTTP_PROXY when others are not set", () => { + delete process.env.PERPLEXITY_PROXY; + delete process.env.HTTPS_PROXY; + process.env.HTTP_PROXY = "http://http-proxy.example.com:8080"; + + expect(process.env.HTTP_PROXY).toBe("http://http-proxy.example.com:8080"); + }); + }); +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..d2897f1 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,27 @@ +#!/usr/bin/env node + +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createPerplexityServer } from "./server.js"; + +const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY; +if (!PERPLEXITY_API_KEY) { + console.error("Error: PERPLEXITY_API_KEY environment variable is required"); + process.exit(1); +} + +async function main() { + try { + const server = createPerplexityServer("local-mcp"); + const transport = new StdioServerTransport(); + await server.connect(transport); + } catch (error) { + console.error("Fatal error running server:", error); + process.exit(1); + } +} + +main().catch((error) => { + console.error("Fatal error running server:", error); + process.exit(1); +}); + diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..7c63535 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,93 @@ +/** + * Simple structured logger for the Perplexity MCP Server + * Outputs to stderr to avoid interfering with STDIO transport + */ + +export enum LogLevel { + DEBUG = 0, + INFO = 1, + WARN = 2, + ERROR = 3, +} + +const LOG_LEVEL_NAMES: Record = { + [LogLevel.DEBUG]: "DEBUG", + [LogLevel.INFO]: "INFO", + [LogLevel.WARN]: "WARN", + [LogLevel.ERROR]: "ERROR", +}; + +/** + * Gets the configured log level from environment variable + * Defaults to ERROR to minimize noise in production + */ +function getLogLevel(): LogLevel { + const level = process.env.PERPLEXITY_LOG_LEVEL?.toUpperCase(); + switch (level) { + case "DEBUG": + return LogLevel.DEBUG; + case "INFO": + return LogLevel.INFO; + case "WARN": + return LogLevel.WARN; + case "ERROR": + return LogLevel.ERROR; + default: + return LogLevel.ERROR; + } +} + +const currentLogLevel = getLogLevel(); + +function safeStringify(obj: unknown): string { + try { + return JSON.stringify(obj); + } catch { + return "[Unstringifiable]"; + } +} + +/** + * Formats a log message with timestamp and level + */ +function formatMessage(level: LogLevel, message: string, meta?: Record): string { + const timestamp = new Date().toISOString(); + const levelName = LOG_LEVEL_NAMES[level]; + + if (meta && Object.keys(meta).length > 0) { + return `[${timestamp}] ${levelName}: ${message} ${safeStringify(meta)}`; + } + + return `[${timestamp}] ${levelName}: ${message}`; +} + +/** + * Logs a message if the configured log level allows it + */ +function log(level: LogLevel, message: string, meta?: Record): void { + if (level >= currentLogLevel) { + const formatted = formatMessage(level, message, meta); + console.error(formatted); // Use stderr to avoid interfering with STDIO + } +} + +/** + * Structured logger interface + */ +export const logger = { + debug(message: string, meta?: Record): void { + log(LogLevel.DEBUG, message, meta); + }, + + info(message: string, meta?: Record): void { + log(LogLevel.INFO, message, meta); + }, + + warn(message: string, meta?: Record): void { + log(LogLevel.WARN, message, meta); + }, + + error(message: string, meta?: Record): void { + log(LogLevel.ERROR, message, meta); + }, +}; diff --git a/src/server.test.ts b/src/server.test.ts new file mode 100644 index 0000000..d1efb3f --- /dev/null +++ b/src/server.test.ts @@ -0,0 +1,256 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { stripThinkingTokens, getProxyUrl, proxyAwareFetch, validateMessages } from "./server.js"; + +describe("Server Utility Functions", () => { + describe("stripThinkingTokens", () => { + it("should remove thinking tokens from content", () => { + const content = "Hello This is internal thinking world!"; + const result = stripThinkingTokens(content); + expect(result).toBe("Hello world!"); + }); + + it("should handle multiple thinking tokens", () => { + const content = "First thought Hello Second thought world!"; + const result = stripThinkingTokens(content); + expect(result).toBe("Hello world!"); + }); + + it("should handle multiline thinking tokens", () => { + const content = "Start \nMultiple\nLines\nOf\nThinking\n End"; + const result = stripThinkingTokens(content); + expect(result).toBe("Start End"); + }); + + it("should handle content without thinking tokens", () => { + const content = "No thinking tokens here!"; + const result = stripThinkingTokens(content); + expect(result).toBe("No thinking tokens here!"); + }); + + it("should handle empty content", () => { + const result = stripThinkingTokens(""); + expect(result).toBe(""); + }); + + it("should handle nested angle brackets within thinking tokens", () => { + const content = "Test content result"; + const result = stripThinkingTokens(content); + expect(result).toBe("Test result"); + }); + + it("should trim the result", () => { + const content = " Remove me "; + const result = stripThinkingTokens(content); + expect(result).toBe(""); + }); + + it("should pass through unclosed think tag unchanged", () => { + const content = "Start unclosed content"; + const result = stripThinkingTokens(content); + expect(result).toBe("Start unclosed content"); + }); + + it("should pass through orphan closing tag unchanged", () => { + const content = "Some content here"; + const result = stripThinkingTokens(content); + expect(result).toBe("Some content here"); + }); + }); + + describe("getProxyUrl", () => { + let originalEnv: NodeJS.ProcessEnv; + + beforeEach(() => { + originalEnv = { ...process.env }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("should return PERPLEXITY_PROXY when set", () => { + process.env.PERPLEXITY_PROXY = "http://perplexity-proxy:8080"; + process.env.HTTPS_PROXY = "http://https-proxy:8080"; + process.env.HTTP_PROXY = "http://http-proxy:8080"; + + const result = getProxyUrl(); + expect(result).toBe("http://perplexity-proxy:8080"); + }); + + it("should return HTTPS_PROXY when PERPLEXITY_PROXY not set", () => { + delete process.env.PERPLEXITY_PROXY; + process.env.HTTPS_PROXY = "http://https-proxy:8080"; + process.env.HTTP_PROXY = "http://http-proxy:8080"; + + const result = getProxyUrl(); + expect(result).toBe("http://https-proxy:8080"); + }); + + it("should return HTTP_PROXY when PERPLEXITY_PROXY and HTTPS_PROXY not set", () => { + delete process.env.PERPLEXITY_PROXY; + delete process.env.HTTPS_PROXY; + process.env.HTTP_PROXY = "http://http-proxy:8080"; + + const result = getProxyUrl(); + expect(result).toBe("http://http-proxy:8080"); + }); + + it("should return undefined when no proxy set", () => { + delete process.env.PERPLEXITY_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.HTTP_PROXY; + + const result = getProxyUrl(); + expect(result).toBeUndefined(); + }); + + it("should prioritize PERPLEXITY_PROXY over others", () => { + process.env.PERPLEXITY_PROXY = "http://specific-proxy:8080"; + process.env.HTTPS_PROXY = "http://general-proxy:8080"; + + const result = getProxyUrl(); + expect(result).toBe("http://specific-proxy:8080"); + }); + }); + + describe("proxyAwareFetch", () => { + let originalEnv: NodeJS.ProcessEnv; + let originalFetch: typeof global.fetch; + + beforeEach(() => { + originalEnv = { ...process.env }; + originalFetch = global.fetch; + }); + + afterEach(() => { + process.env = originalEnv; + global.fetch = originalFetch; + }); + + it("should use native fetch when no proxy is configured", async () => { + delete process.env.PERPLEXITY_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.HTTP_PROXY; + + const mockResponse = new Response("test", { status: 200 }); + global.fetch = vi.fn().mockResolvedValue(mockResponse); + + const result = await proxyAwareFetch("https://api.example.com/test"); + + expect(global.fetch).toHaveBeenCalledWith( + "https://api.example.com/test", + {} + ); + expect(result).toBe(mockResponse); + }); + + it("should NOT use native fetch when proxy is configured", async () => { + process.env.PERPLEXITY_PROXY = "http://proxy:8080"; + + global.fetch = vi.fn().mockResolvedValue(new Response("test")); + + try { + await proxyAwareFetch("https://api.example.com/test"); + } catch { + // Expected to fail - no proxy server is configured + } + + expect(global.fetch).not.toHaveBeenCalled(); + }); + + it("should pass through request options to native fetch", async () => { + delete process.env.PERPLEXITY_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.HTTP_PROXY; + + const mockResponse = new Response("test", { status: 200 }); + global.fetch = vi.fn().mockResolvedValue(mockResponse); + + const options: RequestInit = { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ test: "data" }), + }; + + await proxyAwareFetch("https://api.example.com/test", options); + + expect(global.fetch).toHaveBeenCalledWith( + "https://api.example.com/test", + options + ); + }); + + it("should handle fetch errors properly", async () => { + delete process.env.PERPLEXITY_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.HTTP_PROXY; + + global.fetch = vi.fn().mockRejectedValue(new Error("Network error")); + + await expect(proxyAwareFetch("https://api.example.com/test")) + .rejects.toThrow("Network error"); + }); + }); + + describe("validateMessages", () => { + it("should throw if messages is not an array", () => { + expect(() => validateMessages("not-an-array", "test_tool")) + .toThrow("Invalid arguments for test_tool: 'messages' must be an array"); + }); + + it("should throw if messages is null", () => { + expect(() => validateMessages(null, "test_tool")) + .toThrow("'messages' must be an array"); + }); + + it("should throw if message is not an object", () => { + expect(() => validateMessages(["string"], "test_tool")) + .toThrow("Invalid message at index 0: must be an object"); + }); + + it("should throw if message is null", () => { + expect(() => validateMessages([null], "test_tool")) + .toThrow("Invalid message at index 0: must be an object"); + }); + + it("should throw if role is missing", () => { + expect(() => validateMessages([{ content: "test" }], "test_tool")) + .toThrow("Invalid message at index 0: 'role' must be a string"); + }); + + it("should throw if role is not a string", () => { + expect(() => validateMessages([{ role: 123, content: "test" }], "test_tool")) + .toThrow("Invalid message at index 0: 'role' must be a string"); + }); + + it("should throw if content is missing", () => { + expect(() => validateMessages([{ role: "user" }], "test_tool")) + .toThrow("Invalid message at index 0: 'content' must be a string"); + }); + + it("should throw if content is not a string", () => { + expect(() => validateMessages([{ role: "user", content: 123 }], "test_tool")) + .toThrow("Invalid message at index 0: 'content' must be a string"); + }); + + it("should throw if content is null", () => { + expect(() => validateMessages([{ role: "user", content: null }], "test_tool")) + .toThrow("Invalid message at index 0: 'content' must be a string"); + }); + + it("should pass for valid messages", () => { + expect(() => validateMessages([ + { role: "user", content: "Hello" }, + { role: "assistant", content: "Hi there" } + ], "test_tool")).not.toThrow(); + }); + + it("should report correct index for invalid message", () => { + expect(() => validateMessages([ + { role: "user", content: "valid" }, + { role: "assistant", content: "also valid" }, + { role: "user" } // no content + ], "test_tool")).toThrow("Invalid message at index 2: 'content' must be a string"); + }); + }); +}); diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..d649ccb --- /dev/null +++ b/src/server.ts @@ -0,0 +1,536 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { fetch as undiciFetch, ProxyAgent } from "undici"; +import type { + Message, + ChatCompletionResponse, + ChatCompletionOptions, + SearchResponse, + SearchRequestBody, + UndiciRequestOptions +} from "./types.js"; +import { ChatCompletionResponseSchema, SearchResponseSchema } from "./validation.js"; + +const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY; +const PERPLEXITY_BASE_URL = process.env.PERPLEXITY_BASE_URL || "https://api.perplexity.ai"; +const VERSION = "0.9.0"; + +export function getProxyUrl(): string | undefined { + return process.env.PERPLEXITY_PROXY || + process.env.HTTPS_PROXY || + process.env.HTTP_PROXY || + undefined; +} + +export async function proxyAwareFetch(url: string, options: RequestInit = {}): Promise { + const proxyUrl = getProxyUrl(); + + if (proxyUrl) { + const proxyAgent = new ProxyAgent(proxyUrl); + const undiciOptions: UndiciRequestOptions = { + ...options, + dispatcher: proxyAgent, + }; + const response = await undiciFetch(url, undiciOptions); + return response as unknown as Response; + } + + return fetch(url, options); +} + +export function validateMessages(messages: unknown, toolName: string): asserts messages is Message[] { + if (!Array.isArray(messages)) { + throw new Error(`Invalid arguments for ${toolName}: 'messages' must be an array`); + } + + for (let i = 0; i < messages.length; i++) { + const msg = messages[i]; + if (!msg || typeof msg !== 'object') { + throw new Error(`Invalid message at index ${i}: must be an object`); + } + if (!msg.role || typeof msg.role !== 'string') { + throw new Error(`Invalid message at index ${i}: 'role' must be a string`); + } + if (msg.content === undefined || msg.content === null || typeof msg.content !== 'string') { + throw new Error(`Invalid message at index ${i}: 'content' must be a string`); + } + } +} + +export function stripThinkingTokens(content: string): string { + return content.replace(/[\s\S]*?<\/think>/g, '').trim(); +} + +async function makeApiRequest( + endpoint: string, + body: Record, + serviceOrigin: string | undefined, +): Promise { + if (!PERPLEXITY_API_KEY) { + throw new Error("PERPLEXITY_API_KEY environment variable is required"); + } + + // Read timeout fresh each time to respect env var changes + const TIMEOUT_MS = parseInt(process.env.PERPLEXITY_TIMEOUT_MS || "300000", 10); + + const url = new URL(`${PERPLEXITY_BASE_URL}/${endpoint}`); + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS); + + let response; + try { + const headers: Record = { + "Content-Type": "application/json", + "Authorization": `Bearer ${PERPLEXITY_API_KEY}`, + "User-Agent": `perplexity-mcp/${VERSION}`, + "X-Source": "pplx-mcp-server", + }; + if (serviceOrigin) { + headers["X-Service"] = serviceOrigin; + } + response = await proxyAwareFetch(url.toString(), { + method: "POST", + headers, + body: JSON.stringify(body), + signal: controller.signal, + }); + } catch (error) { + clearTimeout(timeoutId); + if (error instanceof Error && error.name === "AbortError") { + throw new Error(`Request timeout: Perplexity API did not respond within ${TIMEOUT_MS}ms. Consider increasing PERPLEXITY_TIMEOUT_MS.`); + } + throw new Error(`Network error while calling Perplexity API: ${error}`); + } + clearTimeout(timeoutId); + + if (!response.ok) { + let errorText; + try { + errorText = await response.text(); + } catch (parseError) { + errorText = "Unable to parse error response"; + } + throw new Error( + `Perplexity API error: ${response.status} ${response.statusText}\n${errorText}` + ); + } + + return response; +} + +export async function consumeSSEStream(response: Response): Promise { + const body = response.body; + if (!body) { + throw new Error("Response body is null"); + } + + const reader = (body as ReadableStream).getReader(); + const decoder = new TextDecoder(); + + let contentParts: string[] = []; + let citations: string[] | undefined; + let usage: ChatCompletionResponse["usage"] | undefined; + let id: string | undefined; + let model: string | undefined; + let created: number | undefined; + let buffer = ""; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + const lines = buffer.split("\n"); + // Keep the last potentially incomplete line in the buffer + buffer = lines.pop() || ""; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || !trimmed.startsWith("data:")) continue; + + const data = trimmed.slice("data:".length).trim(); + if (data === "[DONE]") continue; + + try { + const parsed = JSON.parse(data); + + if (parsed.id) id = parsed.id; + if (parsed.model) model = parsed.model; + if (parsed.created) created = parsed.created; + if (parsed.citations) citations = parsed.citations; + if (parsed.usage) usage = parsed.usage; + + const delta = parsed.choices?.[0]?.delta; + if (delta?.content) { + contentParts.push(delta.content); + } + } catch { + // Skip malformed JSON chunks (e.g. keep-alive pings) + } + } + } + + const assembled: ChatCompletionResponse = { + choices: [ + { + message: { content: contentParts.join("") }, + finish_reason: "stop", + index: 0, + }, + ], + ...(citations && { citations }), + ...(usage && { usage }), + ...(id && { id }), + ...(model && { model }), + ...(created && { created }), + }; + + return ChatCompletionResponseSchema.parse(assembled); +} + +export async function performChatCompletion( + messages: Message[], + model: string = "sonar-pro", + stripThinking: boolean = false, + serviceOrigin?: string, + options?: ChatCompletionOptions +): Promise { + const useStreaming = model === "sonar-deep-research"; + + const body: Record = { + model: model, + messages: messages, + ...(useStreaming && { stream: true }), + ...(options?.search_recency_filter && { search_recency_filter: options.search_recency_filter }), + ...(options?.search_domain_filter && { search_domain_filter: options.search_domain_filter }), + ...(options?.search_context_size && { web_search_options: { search_context_size: options.search_context_size } }), + ...(options?.reasoning_effort && { reasoning_effort: options.reasoning_effort }), + }; + + const response = await makeApiRequest("chat/completions", body, serviceOrigin); + + let data: ChatCompletionResponse; + try { + if (useStreaming) { + data = await consumeSSEStream(response); + } else { + const json = await response.json(); + data = ChatCompletionResponseSchema.parse(json); + } + } catch (error) { + if (error instanceof z.ZodError) { + const issues = error.issues; + if (issues.some(i => i.path.includes('message') || i.path.includes('content'))) { + throw new Error("Invalid API response: missing message content"); + } + if (issues.some(i => i.path.includes('choices'))) { + throw new Error("Invalid API response: missing or empty choices array"); + } + } + throw new Error(`Failed to parse JSON response from Perplexity API: ${error}`); + } + + const firstChoice = data.choices[0]; + + let messageContent = firstChoice.message.content; + + if (stripThinking) { + messageContent = stripThinkingTokens(messageContent); + } + + if (data.citations && Array.isArray(data.citations) && data.citations.length > 0) { + messageContent += "\n\nCitations:\n"; + data.citations.forEach((citation, index) => { + messageContent += `[${index + 1}] ${citation}\n`; + }); + } + + return messageContent; +} + +export function formatSearchResults(data: SearchResponse): string { + if (!data.results || !Array.isArray(data.results)) { + return "No search results found."; + } + + let formattedResults = `Found ${data.results.length} search results:\n\n`; + + data.results.forEach((result, index) => { + formattedResults += `${index + 1}. **${result.title}**\n`; + formattedResults += ` URL: ${result.url}\n`; + if (result.snippet) { + formattedResults += ` ${result.snippet}\n`; + } + if (result.date) { + formattedResults += ` Date: ${result.date}\n`; + } + formattedResults += `\n`; + }); + + return formattedResults; +} + +export async function performSearch( + query: string, + maxResults: number = 10, + maxTokensPerPage: number = 1024, + country?: string, + serviceOrigin?: string +): Promise { + const body: Record = { + query: query, + max_results: maxResults, + max_tokens_per_page: maxTokensPerPage, + ...(country && { country }), + }; + + const response = await makeApiRequest("search", body, serviceOrigin); + + let data: SearchResponse; + try { + const json = await response.json(); + data = SearchResponseSchema.parse(json); + } catch (error) { + throw new Error(`Failed to parse JSON response from Perplexity Search API: ${error}`); + } + + return formatSearchResults(data); +} + +export function createPerplexityServer(serviceOrigin?: string) { + const server = new McpServer( + { + name: "ai.perplexity/mcp-server", + version: VERSION, + }, + { + instructions: + "Perplexity AI server for web-grounded search, research, and reasoning. " + + "Use perplexity_search for finding URLs, facts, and recent news. " + + "Use perplexity_ask for quick AI-answered questions with citations. Supports recency filters, domain restrictions, and search context size control. " + + "Use perplexity_research for in-depth multi-source investigation (slow, 30s+). Supports reasoning_effort parameter to control depth. " + + "Use perplexity_reason for complex analysis requiring step-by-step logic. Supports recency filters, domain restrictions, and search context size control. " + + "All tools are read-only and access live web data.", + } + ); + + const messageSchema = z.object({ + role: z.enum(["system", "user", "assistant"]).describe("Role of the message sender"), + content: z.string().describe("The content of the message"), + }); + + const messagesField = z.array(messageSchema).describe("Array of conversation messages"); + + const stripThinkingField = z.boolean().optional() + .describe("If true, removes ... tags and their content from the response to save context tokens. Default is false."); + + const searchRecencyFilterField = z.enum(["hour", "day", "week", "month", "year"]).optional() + .describe("Filter search results by recency. Use 'hour' for very recent news, 'day' for today's updates, 'week' for this week, etc."); + + const searchDomainFilterField = z.array(z.string()).optional() + .describe("Restrict search results to specific domains (e.g., ['wikipedia.org', 'arxiv.org']). Use '-' prefix for exclusion (e.g., ['-reddit.com'])."); + + const searchContextSizeField = z.enum(["low", "medium", "high"]).optional() + .describe("Controls how much web context is retrieved. 'low' (default) is fastest, 'high' provides more comprehensive results."); + + const reasoningEffortField = z.enum(["minimal", "low", "medium", "high"]).optional() + .describe("Controls depth of deep research reasoning. Higher values produce more thorough analysis."); + + const responseOutputSchema = { + response: z.string().describe("AI-generated text response with numbered citation references"), + }; + + // Input schemas + const messagesOnlyInputSchema = { + messages: messagesField, + search_recency_filter: searchRecencyFilterField, + search_domain_filter: searchDomainFilterField, + search_context_size: searchContextSizeField, + }; + const messagesWithStripThinkingInputSchema = { + messages: messagesField, + strip_thinking: stripThinkingField, + search_recency_filter: searchRecencyFilterField, + search_domain_filter: searchDomainFilterField, + search_context_size: searchContextSizeField, + }; + const researchInputSchema = { + messages: messagesField, + strip_thinking: stripThinkingField, + reasoning_effort: reasoningEffortField, + }; + + server.registerTool( + "perplexity_ask", + { + title: "Ask Perplexity", + description: "Answer a question using web-grounded AI (Sonar Pro model). " + + "Best for: quick factual questions, summaries, explanations, and general Q&A. " + + "Returns a text response with numbered citations. Fastest and cheapest option. " + + "Supports filtering by recency (hour/day/week/month/year), domain restrictions, and search context size. " + + "For in-depth multi-source research, use perplexity_research instead. " + + "For step-by-step reasoning and analysis, use perplexity_reason instead.", + inputSchema: messagesOnlyInputSchema as any, + outputSchema: responseOutputSchema as any, + annotations: { + readOnlyHint: true, + openWorldHint: true, + idempotentHint: false, + destructiveHint: false, + }, + }, + async (args: any) => { + const { messages, search_recency_filter, search_domain_filter, search_context_size } = args as { + messages: Message[]; + search_recency_filter?: "hour" | "day" | "week" | "month" | "year"; + search_domain_filter?: string[]; + search_context_size?: "low" | "medium" | "high"; + }; + validateMessages(messages, "perplexity_ask"); + const options = { + ...(search_recency_filter && { search_recency_filter }), + ...(search_domain_filter && { search_domain_filter }), + ...(search_context_size && { search_context_size }), + }; + const result = await performChatCompletion(messages, "sonar-pro", false, serviceOrigin, Object.keys(options).length > 0 ? options : undefined); + return { + content: [{ type: "text" as const, text: result }], + structuredContent: { response: result }, + }; + } + ); + + server.registerTool( + "perplexity_research", + { + title: "Deep Research", + description: "Conduct deep, multi-source research on a topic (Sonar Deep Research model). " + + "Best for: literature reviews, comprehensive overviews, investigative queries needing " + + "many sources. Returns a detailed response with numbered citations. " + + "Significantly slower than other tools (30+ seconds). " + + "For quick factual questions, use perplexity_ask instead. " + + "For logical analysis and reasoning, use perplexity_reason instead.", + inputSchema: researchInputSchema as any, + outputSchema: responseOutputSchema as any, + annotations: { + readOnlyHint: true, + openWorldHint: true, + idempotentHint: false, + destructiveHint: false, + }, + }, + async (args: any) => { + const { messages, strip_thinking, reasoning_effort } = args as { + messages: Message[]; + strip_thinking?: boolean; + reasoning_effort?: "minimal" | "low" | "medium" | "high"; + }; + validateMessages(messages, "perplexity_research"); + const stripThinking = typeof strip_thinking === "boolean" ? strip_thinking : false; + const options = { + ...(reasoning_effort && { reasoning_effort }), + }; + const result = await performChatCompletion(messages, "sonar-deep-research", stripThinking, serviceOrigin, Object.keys(options).length > 0 ? options : undefined); + return { + content: [{ type: "text" as const, text: result }], + structuredContent: { response: result }, + }; + } + ); + + server.registerTool( + "perplexity_reason", + { + title: "Advanced Reasoning", + description: "Analyze a question using step-by-step reasoning with web grounding (Sonar Reasoning Pro model). " + + "Best for: math, logic, comparisons, complex arguments, and tasks requiring chain-of-thought. " + + "Returns a reasoned response with numbered citations. " + + "Supports filtering by recency (hour/day/week/month/year), domain restrictions, and search context size. " + + "For quick factual questions, use perplexity_ask instead. " + + "For comprehensive multi-source research, use perplexity_research instead.", + inputSchema: messagesWithStripThinkingInputSchema as any, + outputSchema: responseOutputSchema as any, + annotations: { + readOnlyHint: true, + openWorldHint: true, + idempotentHint: false, + destructiveHint: false, + }, + }, + async (args: any) => { + const { messages, strip_thinking, search_recency_filter, search_domain_filter, search_context_size } = args as { + messages: Message[]; + strip_thinking?: boolean; + search_recency_filter?: "hour" | "day" | "week" | "month" | "year"; + search_domain_filter?: string[]; + search_context_size?: "low" | "medium" | "high"; + }; + validateMessages(messages, "perplexity_reason"); + const stripThinking = typeof strip_thinking === "boolean" ? strip_thinking : false; + const options = { + ...(search_recency_filter && { search_recency_filter }), + ...(search_domain_filter && { search_domain_filter }), + ...(search_context_size && { search_context_size }), + }; + const result = await performChatCompletion(messages, "sonar-reasoning-pro", stripThinking, serviceOrigin, Object.keys(options).length > 0 ? options : undefined); + return { + content: [{ type: "text" as const, text: result }], + structuredContent: { response: result }, + }; + } + ); + + const searchInputSchema = { + query: z.string().describe("Search query string"), + max_results: z.number().min(1).max(20).optional() + .describe("Maximum number of results to return (1-20, default: 10)"), + max_tokens_per_page: z.number().min(256).max(2048).optional() + .describe("Maximum tokens to extract per webpage (default: 1024)"), + country: z.string().optional() + .describe("ISO 3166-1 alpha-2 country code for regional results (e.g., 'US', 'GB')"), + }; + + const searchOutputSchema = { + results: z.string().describe("Formatted search results, each with title, URL, snippet, and date"), + }; + + server.registerTool( + "perplexity_search", + { + title: "Search the Web", + description: "Search the web and return a ranked list of results with titles, URLs, snippets, and dates. " + + "Best for: finding specific URLs, checking recent news, verifying facts, discovering sources. " + + "Returns formatted results (title, URL, snippet, date) — no AI synthesis. " + + "For AI-generated answers with citations, use perplexity_ask instead.", + inputSchema: searchInputSchema as any, + outputSchema: searchOutputSchema as any, + annotations: { + readOnlyHint: true, + openWorldHint: true, + idempotentHint: false, + destructiveHint: false, + }, + }, + async (args: any) => { + const { query, max_results, max_tokens_per_page, country } = args as { + query: string; + max_results?: number; + max_tokens_per_page?: number; + country?: string; + }; + const maxResults = typeof max_results === "number" ? max_results : 10; + const maxTokensPerPage = typeof max_tokens_per_page === "number" ? max_tokens_per_page : 1024; + const countryCode = typeof country === "string" ? country : undefined; + + const result = await performSearch(query, maxResults, maxTokensPerPage, countryCode, serviceOrigin); + return { + content: [{ type: "text" as const, text: result }], + structuredContent: { results: result }, + }; + } + ); + + return server.server; +} + diff --git a/src/transport.test.ts b/src/transport.test.ts new file mode 100644 index 0000000..6dfcf7e --- /dev/null +++ b/src/transport.test.ts @@ -0,0 +1,366 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { createPerplexityServer } from "./server.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import express from "express"; +import cors from "cors"; +import { Server } from "http"; + +describe("Transport Integration Tests", () => { + let originalFetch: typeof global.fetch; + let originalEnv: NodeJS.ProcessEnv; + + beforeEach(() => { + originalFetch = global.fetch; + originalEnv = { ...process.env }; + process.env.PERPLEXITY_API_KEY = "test-api-key"; + }); + + afterEach(() => { + global.fetch = originalFetch; + process.env = originalEnv; + vi.restoreAllMocks(); + }); + + describe("Server Factory", () => { + it("should create a server with all tools registered", () => { + const server = createPerplexityServer(); + + expect(server).toBeDefined(); + // The server should be the underlying Server instance from McpServer + expect(typeof server.connect).toBe("function"); + expect(typeof server.close).toBe("function"); + }); + + it("should fail if PERPLEXITY_API_KEY is not set", () => { + delete process.env.PERPLEXITY_API_KEY; + + // The server creation itself doesn't fail, but tool calls should fail + const server = createPerplexityServer(); + expect(server).toBeDefined(); + }); + }); + + describe("STDIO Transport", () => { + it("should connect successfully to STDIO transport", async () => { + const server = createPerplexityServer(); + const transport = new StdioServerTransport(); + + // Mock the transport connection + const connectSpy = vi.spyOn(transport, 'start').mockResolvedValue(undefined); + const closeSpy = vi.spyOn(transport, 'close').mockImplementation(() => Promise.resolve()); + + await server.connect(transport); + + expect(connectSpy).toHaveBeenCalled(); + + // Clean up + transport.close(); + server.close(); + }); + + it("should handle STDIO transport errors gracefully", async () => { + const server = createPerplexityServer(); + const transport = new StdioServerTransport(); + + // Mock transport to throw error + vi.spyOn(transport, 'start').mockRejectedValue(new Error("Transport error")); + + await expect(server.connect(transport)).rejects.toThrow("Transport error"); + + server.close(); + }); + }); + + describe("HTTP Transport", () => { + let httpServer: Server; + let app: express.Application; + + beforeEach(() => { + app = express(); + app.use(cors({ + origin: "*", + exposedHeaders: ["Mcp-Session-Id", "mcp-protocol-version"], + allowedHeaders: ["Content-Type", "mcp-session-id"], + })); + app.use(express.json()); + }); + + afterEach(async () => { + if (httpServer) { + await new Promise((resolve) => { + httpServer.close(() => resolve()); + }); + } + }); + + it("should handle HTTP MCP requests with real transport", async () => { + // Set up proper MCP endpoint with real transport + app.post("/mcp", async (req, res) => { + try { + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true, + }); + + res.on("close", () => { + transport.close(); + }); + + const server = createPerplexityServer(); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + } catch (error) { + console.error("Error handling MCP request:", error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: "2.0", + error: { code: -32603, message: "Internal server error" }, + id: null, + }); + } + } + }); + + httpServer = app.listen(0); + const address = httpServer.address(); + const port = typeof address === 'object' && address ? address.port : 3000; + + // Make a real MCP tools/list request + const response = await fetch(`http://localhost:${port}/mcp`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "tools/list", + params: {} + }), + }); + + expect(response.ok).toBe(true); + const data = await response.json(); + + // Verify proper MCP response structure + expect(data.jsonrpc).toBe("2.0"); + expect(data.id).toBe(1); + expect(data.result).toBeDefined(); + expect(data.result.tools).toBeDefined(); + expect(data.result.tools).toHaveLength(4); + + // Verify all four tools are present + const toolNames = data.result.tools.map((t: { name: string }) => t.name); + expect(toolNames).toContain("perplexity_ask"); + expect(toolNames).toContain("perplexity_research"); + expect(toolNames).toContain("perplexity_reason"); + expect(toolNames).toContain("perplexity_search"); + + // Verify tool schema structure + expect(data.result.tools[0].inputSchema).toBeDefined(); + expect(data.result.tools[0].description).toBeDefined(); + }); + + it("should handle tool calls via HTTP with real transport", async () => { + // This test verifies the HTTP transport layer works correctly + // Tool execution logic is already tested in index.test.ts + + // Set up proper MCP endpoint with real transport + app.post("/mcp", async (req, res) => { + try { + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true, + }); + + res.on("close", () => { + transport.close(); + }); + + const server = createPerplexityServer(); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + } catch (error) { + console.error("Error handling tool call:", error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: "2.0", + error: { code: -32603, message: "Internal server error" }, + id: null, + }); + } + } + }); + + httpServer = app.listen(0); + const address = httpServer.address(); + const port = typeof address === 'object' && address ? address.port : 3000; + + // Test with an invalid tool call to verify error handling + const response = await fetch(`http://localhost:${port}/mcp`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 2, + method: "tools/call", + params: { + name: "nonexistent_tool", + arguments: {} + } + }), + }); + + expect(response.ok).toBe(true); // MCP errors are 200 OK with error in body + const data = await response.json(); + + // Verify proper MCP error response structure + expect(data).toBeDefined(); + expect(data.jsonrpc).toBe("2.0"); + expect(data.id).toBe(2); + + // McpServer returns tool errors as result.isError, not top-level error + expect(data.result).toBeDefined(); + expect(data.result.isError).toBe(true); + expect(data.result.content[0].text).toContain("not found"); + }); + + it("should handle HTTP errors properly", async () => { + app.post("/mcp", async (req, res) => { + res.status(400).json({ + jsonrpc: "2.0", + error: { code: -32600, message: "Invalid Request" }, + id: null, + }); + }); + + httpServer = app.listen(0); + const address = httpServer.address(); + const port = typeof address === 'object' && address ? address.port : 3000; + + const response = await fetch(`http://localhost:${port}/mcp`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "invalid/method", + params: {} + }), + }); + + expect(response.status).toBe(400); + const data = await response.json(); + expect(data.error.message).toBe("Invalid Request"); + }); + + it("should require proper Accept headers", async () => { + app.post("/mcp", async (req, res) => { + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true, + }); + + try { + const server = createPerplexityServer(); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + } catch (error) { + res.status(406).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Not Acceptable" }, + id: null, + }); + } + }); + + httpServer = app.listen(0); + const address = httpServer.address(); + const port = typeof address === 'object' && address ? address.port : 3000; + + // Test without proper Accept header + const response = await fetch(`http://localhost:${port}/mcp`, { + method: "POST", + headers: { + "Content-Type": "application/json", + // Missing Accept header + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "tools/list", + params: {} + }), + }); + + expect(response.status).toBe(406); + }); + }); + + describe("Transport Comparison", () => { + it("should produce identical results for both transports", async () => { + const mockResponse = { + choices: [{ message: { content: "Identical response" } }] + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => mockResponse, + } as Response); + + // Test STDIO (we can't easily test the actual transport, but we can test the server) + const server1 = createPerplexityServer(); + const server2 = createPerplexityServer(); + + // Both should be identical server instances with same capabilities + expect(server1).toBeDefined(); + expect(server2).toBeDefined(); + + // Clean up + server1.close(); + server2.close(); + }); + }); + + describe("Health Check", () => { + let healthApp: express.Application; + let healthHttpServer: Server; + + beforeEach(() => { + healthApp = express(); + }); + + afterEach(async () => { + if (healthHttpServer) { + await new Promise((resolve) => { + healthHttpServer.close(() => resolve()); + }); + } + }); + + it("should provide health check endpoint for HTTP mode", async () => { + healthApp.get("/health", (req: express.Request, res: express.Response) => { + res.json({ status: "ok", service: "perplexity-mcp-server" }); + }); + + healthHttpServer = healthApp.listen(0); + const address = healthHttpServer.address(); + const port = typeof address === 'object' && address ? address.port : 3000; + + const response = await fetch(`http://localhost:${port}/health`); + expect(response.ok).toBe(true); + + const data = await response.json(); + expect(data.status).toBe("ok"); + expect(data.service).toBe("perplexity-mcp-server"); + }); + }); +}); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..f7d6fb9 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,69 @@ +import type { ProxyAgent } from "undici"; + +export interface Message { + role: string; + content: string; +} + +export interface ChatMessage { + content: string; + role?: string; +} + +export interface ChatChoice { + message: ChatMessage; + finish_reason?: string; + index?: number; +} + +export interface TokenUsage { + prompt_tokens?: number; + completion_tokens?: number; + total_tokens?: number; +} + +export interface ChatCompletionResponse { + choices: ChatChoice[]; + citations?: string[]; + usage?: TokenUsage; + id?: string; + model?: string; + created?: number; +} + +export interface SearchResult { + title: string; + url: string; + snippet?: string; + date?: string; + score?: number; +} + +export interface SearchUsage { + tokens?: number; +} + +export interface SearchResponse { + results: SearchResult[]; + query?: string; + usage?: SearchUsage; +} + +export interface SearchRequestBody { + query: string; + max_results: number; + max_tokens_per_page: number; + country?: string; +} + +export interface ChatCompletionOptions { + search_recency_filter?: "hour" | "day" | "week" | "month" | "year"; + search_domain_filter?: string[]; + search_context_size?: "low" | "medium" | "high"; + reasoning_effort?: "minimal" | "low" | "medium" | "high"; +} + +export interface UndiciRequestOptions { + [key: string]: unknown; + dispatcher?: ProxyAgent; +} diff --git a/src/validation.ts b/src/validation.ts new file mode 100644 index 0000000..537f33f --- /dev/null +++ b/src/validation.ts @@ -0,0 +1,45 @@ +import { z } from "zod"; + +export const ChatMessageSchema = z.object({ + content: z.string(), + role: z.string().optional(), +}); + +export const ChatChoiceSchema = z.object({ + message: ChatMessageSchema, + finish_reason: z.string().optional(), + index: z.number().optional(), +}); + +export const TokenUsageSchema = z.object({ + prompt_tokens: z.number().optional(), + completion_tokens: z.number().optional(), + total_tokens: z.number().optional(), +}); + +export const ChatCompletionResponseSchema = z.object({ + choices: z.array(ChatChoiceSchema).min(1), + citations: z.array(z.string()).optional(), + usage: TokenUsageSchema.optional(), + id: z.string().optional(), + model: z.string().optional(), + created: z.number().optional(), +}); + +export const SearchResultSchema = z.object({ + title: z.string(), + url: z.string(), + snippet: z.string().optional(), + date: z.string().optional(), + score: z.number().optional(), +}); + +export const SearchUsageSchema = z.object({ + tokens: z.number().optional(), +}); + +export const SearchResponseSchema = z.object({ + results: z.array(SearchResultSchema), + query: z.string().optional(), + usage: SearchUsageSchema.optional(), +}); diff --git a/perplexity-ask/tsconfig.json b/tsconfig.json similarity index 79% rename from perplexity-ask/tsconfig.json rename to tsconfig.json index ba5c7b7..190e48e 100644 --- a/perplexity-ask/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { - "target": "ES2015", + "target": "ES2020", "module": "ESNext", "outDir": "./dist", - "rootDir": ".", + "rootDir": "./src", "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, @@ -11,6 +11,6 @@ "moduleResolution": "node" }, "include": [ - "./**/*.ts" + "src/**/*.ts" ] } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..b4c8877 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + exclude: ['**/node_modules/**', '**/dist/**'], + env: { + PERPLEXITY_API_KEY: 'test-api-key', + }, + coverage: { + provider: 'v8', + reporter: ['text', 'html', 'lcov'], + exclude: [ + '**/node_modules/**', + '**/dist/**', + '**/*.test.ts', + '**/*.config.ts', + ], + }, + }, +})