├── .gitignore ├── .npmignore ├── Dockerfile ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── smithery.yaml ├── src ├── index.ts ├── tools │ ├── companyResearch.ts │ ├── competitorFinder.ts │ ├── config.ts │ ├── crawling.ts │ ├── index.ts │ ├── researchPaperSearch.ts │ ├── twitterSearch.ts │ └── webSearch.ts ├── types.ts └── utils │ └── logger.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | *.log 4 | .env* -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | tests/ 3 | .github/ 4 | .gitignore 5 | .npmignore 6 | tsconfig.json 7 | *.log 8 | .env* -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official Node.js 18 image as a parent image 2 | FROM node:18-alpine AS builder 3 | 4 | # Set the working directory in the container to /app 5 | WORKDIR /app 6 | 7 | # Copy package.json and package-lock.json into the container 8 | COPY package.json package-lock.json ./ 9 | 10 | # Install dependencies 11 | RUN npm ci --ignore-scripts 12 | 13 | # Copy the rest of the application code into the container 14 | COPY src/ ./src/ 15 | COPY tsconfig.json ./ 16 | 17 | # Build the project 18 | RUN npm run build 19 | 20 | # Use a minimal node image as the base image for running 21 | FROM node:18-alpine AS runner 22 | 23 | WORKDIR /app 24 | 25 | # Copy compiled code from the builder stage 26 | COPY --from=builder /app/build ./build 27 | COPY package.json package-lock.json ./ 28 | 29 | # Install only production dependencies 30 | RUN npm ci --production --ignore-scripts 31 | 32 | # Set environment variable for the Exa API key 33 | ENV EXA_API_KEY=your-api-key-here 34 | 35 | # Expose the port the app runs on 36 | EXPOSE 3000 37 | 38 | # Run the application 39 | ENTRYPOINT ["node", "build/index.js"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Exa Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exa MCP Server 🔍 2 | [![npm version](https://badge.fury.io/js/exa-mcp-server.svg)](https://www.npmjs.com/package/exa-mcp-server) 3 | [![smithery badge](https://smithery.ai/badge/exa)](https://smithery.ai/server/exa) 4 | 5 | A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way. 6 | 7 | Demo video https://www.loom.com/share/ac676f29664e4c6cb33a2f0a63772038?sid=0e72619f-5bfc-415d-a705-63d326373f60 8 | 9 | 10 | ## What is MCP? 🤔 11 | 12 | The Model Context Protocol (MCP) is a system that lets AI apps, like Claude Desktop, connect to external tools and data sources. It gives a clear and safe way for AI assistants to work with local services and APIs while keeping the user in control. 13 | 14 | ## What does this server do? 🚀 15 | 16 | The Exa MCP server: 17 | - Enables AI assistants to perform web searches using Exa's powerful search API 18 | - Provides structured search results including titles, URLs, and content snippets 19 | - Caches recent searches as resources for reference 20 | - Handles rate limiting and error cases gracefully 21 | - Supports real-time web crawling for fresh content 22 | 23 | 24 | ## Prerequisites 📋 25 | 26 | Before you begin, ensure you have: 27 | 28 | - [Node.js](https://nodejs.org/) (v18 or higher) 29 | - [Claude Desktop](https://claude.ai/download) installed 30 | - An [Exa API key](https://dashboard.exa.ai/api-keys) 31 | - Git installed 32 | 33 | You can verify your Node.js installation by running: 34 | ```bash 35 | node --version # Should show v18.0.0 or higher 36 | ``` 37 | 38 | ## Installation 🛠️ 39 | 40 | ### NPM Installation 41 | 42 | ```bash 43 | npm install -g exa-mcp-server 44 | ``` 45 | 46 | ### Using Smithery 47 | 48 | To install the Exa MCP server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/exa): 49 | 50 | ```bash 51 | npx -y @smithery/cli install exa --client claude 52 | ``` 53 | 54 | ### Manual Installation 55 | 56 | 1. Clone the repository: 57 | 58 | ```bash 59 | git clone https://github.com/exa-labs/exa-mcp-server.git 60 | cd exa-mcp-server 61 | ``` 62 | 63 | 2. Install dependencies: 64 | 65 | ```bash 66 | npm install 67 | ``` 68 | 69 | 3. Build the project: 70 | 71 | ```bash 72 | npm run build 73 | ``` 74 | 75 | 4. Create a global link (this makes the server executable from anywhere): 76 | 77 | ```bash 78 | npm link 79 | ``` 80 | 81 | ## Configuration ⚙️ 82 | 83 | ### 1. Configure Claude Desktop to recognize the Exa MCP server 84 | 85 | You can find claude_desktop_config.json inside the settings of Claude Desktop app: 86 | 87 | Open the Claude Desktop app and enable Developer Mode from the top-left menu bar. 88 | 89 | Once enabled, open Settings (also from the top-left menu bar) and navigate to the Developer Option, where you'll find the Edit Config button. Clicking it will open the claude_desktop_config.json file, allowing you to make the necessary edits. 90 | 91 | OR (if you want to open claude_desktop_config.json from terminal) 92 | 93 | #### For macOS: 94 | 95 | 1. Open your Claude Desktop configuration: 96 | 97 | ```bash 98 | code ~/Library/Application\ Support/Claude/claude_desktop_config.json 99 | ``` 100 | 101 | #### For Windows: 102 | 103 | 1. Open your Claude Desktop configuration: 104 | 105 | ```powershell 106 | code %APPDATA%\Claude\claude_desktop_config.json 107 | ``` 108 | 109 | ### 2. Add the Exa server configuration: 110 | 111 | ```json 112 | { 113 | "mcpServers": { 114 | "exa": { 115 | "command": "npx", 116 | "args": ["/path/to/exa-mcp-server/build/index.js"], 117 | "env": { 118 | "EXA_API_KEY": "your-api-key-here" 119 | } 120 | } 121 | } 122 | } 123 | ``` 124 | 125 | Replace `your-api-key-here` with your actual Exa API key from [dashboard.exa.ai/api-keys](https://dashboard.exa.ai/api-keys). 126 | 127 | ### 3. Available Tools & Tool Selection 128 | 129 | The Exa MCP server includes the following tools: 130 | 131 | - **web_search**: Performs real-time web searches with optimized results and content extraction. 132 | - **research_paper_search**: Specialized search focused on academic papers and research content. 133 | - **twitter_search**: Dedicated Twitter/X.com search that finds tweets, profiles, and conversations. 134 | - **company_research**: Comprehensive company research tool that crawls company websites to gather detailed information about businesses. 135 | - **crawling**: Extracts content from specific URLs, useful for reading articles, PDFs, or any web page when you have the exact URL. 136 | - **competitor_finder**: Identifies competitors of a company by searching for businesses offering similar products or services. 137 | 138 | You can choose which tools to enable by adding the `--tools` parameter to your Claude Desktop configuration: 139 | 140 | #### Specify which tools to enable: 141 | 142 | ```json 143 | { 144 | "mcpServers": { 145 | "exa": { 146 | "command": "npx", 147 | "args": [ 148 | "/path/to/exa-mcp-server/build/index.js", 149 | "--tools=web_search,research_paper_search,twitter_search,company_research,crawling,competitor_finder" 150 | ], 151 | "env": { 152 | "EXA_API_KEY": "your-api-key-here" 153 | } 154 | } 155 | } 156 | } 157 | ``` 158 | 159 | For enabling multiple tools, use a comma-separated list: 160 | 161 | ```json 162 | { 163 | "mcpServers": { 164 | "exa": { 165 | "command": "npx", 166 | "args": [ 167 | "/path/to/exa-mcp-server/build/index.js", 168 | "--tools=web_search,research_paper_search,twitter_search,company_research,crawling,competitor_finder" 169 | ], 170 | "env": { 171 | "EXA_API_KEY": "your-api-key-here" 172 | } 173 | } 174 | } 175 | } 176 | ``` 177 | 178 | If you don't specify any tools, all tools enabled by default will be used. 179 | 180 | ### 4. Restart Claude Desktop 181 | 182 | For the changes to take effect: 183 | 184 | 1. Completely quit Claude Desktop (not just close the window) 185 | 2. Start Claude Desktop again 186 | 3. Look for the 🔌 icon to verify the Exa server is connected 187 | 188 | ## Using via NPX 189 | 190 | If you prefer to run the server directly, you can use npx: 191 | 192 | ```bash 193 | # Run with all tools enabled by default 194 | npx exa-mcp-server 195 | 196 | # Enable specific tools only 197 | npx exa-mcp-server --tools=web_search 198 | 199 | # Enable multiple tools 200 | npx exa-mcp-server --tools=web_search,research_paper_search 201 | 202 | # List all available tools 203 | npx exa-mcp-server --list-tools 204 | ``` 205 | 206 | ## Usage 🎯 207 | 208 | Once configured, you can ask Claude to perform web searches. Here are some example prompts: 209 | 210 | ``` 211 | Can you search for recent developments in quantum computing? 212 | ``` 213 | 214 | ``` 215 | Search for and summarize the latest news about artificial intelligence startups in new york. 216 | ``` 217 | 218 | ``` 219 | Find and analyze recent research papers about climate change solutions. 220 | ``` 221 | 222 | ``` 223 | Search Twitter for posts from @elonmusk about SpaceX. 224 | ``` 225 | 226 | ``` 227 | Find tweets from @samaltman that were published in the last week about AI safety. 228 | ``` 229 | 230 | ``` 231 | Research the company exa.ai and find information about their pricing and features. 232 | ``` 233 | 234 | ``` 235 | Extract the content from this research paper: https://arxiv.org/pdf/1706.03762 236 | ``` 237 | 238 | ``` 239 | Find competitors for a company that provides web search API services, excluding exa.ai from the results. 240 | ``` 241 | 242 | The server will: 243 | 244 | 1. Process the search request 245 | 2. Query the Exa API with optimal settings (including live crawling) 246 | 3. Return formatted results to Claude 247 | 4. Cache the search for future reference 248 | 249 | 250 | ## Testing with MCP Inspector 🔍 251 | 252 | You can test the server directly using the MCP Inspector: 253 | 254 | ```bash 255 | npx @modelcontextprotocol/inspector node ./build/index.js 256 | ``` 257 | 258 | This opens an interactive interface where you can explore the server's capabilities, execute search queries, and view cached search results. 259 | 260 | ## Troubleshooting 🔧 261 | 262 | ### Common Issues 263 | 264 | 1. **Server Not Found** 265 | * Verify the npm link is correctly set up 266 | * Check Claude Desktop configuration syntax 267 | * Ensure Node.js is properly installed 268 | 269 | 2. **API Key Issues** 270 | * Confirm your EXA_API_KEY is valid 271 | * Check the EXA_API_KEY is correctly set in the Claude Desktop config 272 | * Verify no spaces or quotes around the API key 273 | 274 | 3. **Connection Issues** 275 | * Restart Claude Desktop completely 276 | * Check Claude Desktop logs: 277 | 278 | ```bash 279 | # macOS 280 | tail -n 20 -f ~/Library/Logs/Claude/mcp*.log 281 | 282 | # Windows 283 | type "%APPDATA%\Claude\logs\mcp*.log" 284 | ``` 285 | 286 | ## Acknowledgments 🙏 287 | 288 | * [Exa AI](https://exa.ai) for their powerful search API 289 | * [Model Context Protocol](https://modelcontextprotocol.io) for the MCP specification 290 | * [Anthropic](https://anthropic.com) for Claude Desktop 291 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exa-mcp-server", 3 | "version": "0.3.6", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "exa-mcp-server", 9 | "version": "0.3.6", 10 | "dependencies": { 11 | "@modelcontextprotocol/sdk": "^1.7.0", 12 | "axios": "^1.7.8", 13 | "dotenv": "^16.4.5", 14 | "yargs": "^17.7.2", 15 | "zod": "^3.22.4" 16 | }, 17 | "bin": { 18 | "exa-mcp-server": "build/index.js" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^20.11.24", 22 | "@types/yargs": "^17.0.33", 23 | "typescript": "^5.3.3" 24 | }, 25 | "engines": { 26 | "node": ">=18.0.0" 27 | } 28 | }, 29 | "node_modules/@modelcontextprotocol/sdk": { 30 | "version": "1.7.0", 31 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.7.0.tgz", 32 | "integrity": "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==", 33 | "dependencies": { 34 | "content-type": "^1.0.5", 35 | "cors": "^2.8.5", 36 | "eventsource": "^3.0.2", 37 | "express": "^5.0.1", 38 | "express-rate-limit": "^7.5.0", 39 | "pkce-challenge": "^4.1.0", 40 | "raw-body": "^3.0.0", 41 | "zod": "^3.23.8", 42 | "zod-to-json-schema": "^3.24.1" 43 | }, 44 | "engines": { 45 | "node": ">=18" 46 | } 47 | }, 48 | "node_modules/@types/node": { 49 | "version": "20.17.8", 50 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.8.tgz", 51 | "integrity": "sha512-ahz2g6/oqbKalW9sPv6L2iRbhLnojxjYWspAqhjvqSWBgGebEJT5GvRmk0QXPj3sbC6rU0GTQjPLQkmR8CObvA==", 52 | "dev": true, 53 | "dependencies": { 54 | "undici-types": "~6.19.2" 55 | } 56 | }, 57 | "node_modules/@types/yargs": { 58 | "version": "17.0.33", 59 | "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", 60 | "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", 61 | "dev": true, 62 | "dependencies": { 63 | "@types/yargs-parser": "*" 64 | } 65 | }, 66 | "node_modules/@types/yargs-parser": { 67 | "version": "21.0.3", 68 | "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", 69 | "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", 70 | "dev": true 71 | }, 72 | "node_modules/accepts": { 73 | "version": "2.0.0", 74 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 75 | "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 76 | "dependencies": { 77 | "mime-types": "^3.0.0", 78 | "negotiator": "^1.0.0" 79 | }, 80 | "engines": { 81 | "node": ">= 0.6" 82 | } 83 | }, 84 | "node_modules/accepts/node_modules/mime-db": { 85 | "version": "1.54.0", 86 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 87 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 88 | "engines": { 89 | "node": ">= 0.6" 90 | } 91 | }, 92 | "node_modules/accepts/node_modules/mime-types": { 93 | "version": "3.0.0", 94 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", 95 | "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", 96 | "dependencies": { 97 | "mime-db": "^1.53.0" 98 | }, 99 | "engines": { 100 | "node": ">= 0.6" 101 | } 102 | }, 103 | "node_modules/ansi-regex": { 104 | "version": "5.0.1", 105 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 106 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 107 | "engines": { 108 | "node": ">=8" 109 | } 110 | }, 111 | "node_modules/ansi-styles": { 112 | "version": "4.3.0", 113 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 114 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 115 | "dependencies": { 116 | "color-convert": "^2.0.1" 117 | }, 118 | "engines": { 119 | "node": ">=8" 120 | }, 121 | "funding": { 122 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 123 | } 124 | }, 125 | "node_modules/asynckit": { 126 | "version": "0.4.0", 127 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 128 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 129 | }, 130 | "node_modules/axios": { 131 | "version": "1.7.8", 132 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz", 133 | "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==", 134 | "dependencies": { 135 | "follow-redirects": "^1.15.6", 136 | "form-data": "^4.0.0", 137 | "proxy-from-env": "^1.1.0" 138 | } 139 | }, 140 | "node_modules/body-parser": { 141 | "version": "2.1.0", 142 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz", 143 | "integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==", 144 | "dependencies": { 145 | "bytes": "^3.1.2", 146 | "content-type": "^1.0.5", 147 | "debug": "^4.4.0", 148 | "http-errors": "^2.0.0", 149 | "iconv-lite": "^0.5.2", 150 | "on-finished": "^2.4.1", 151 | "qs": "^6.14.0", 152 | "raw-body": "^3.0.0", 153 | "type-is": "^2.0.0" 154 | }, 155 | "engines": { 156 | "node": ">=18" 157 | } 158 | }, 159 | "node_modules/body-parser/node_modules/debug": { 160 | "version": "4.4.0", 161 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 162 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 163 | "dependencies": { 164 | "ms": "^2.1.3" 165 | }, 166 | "engines": { 167 | "node": ">=6.0" 168 | }, 169 | "peerDependenciesMeta": { 170 | "supports-color": { 171 | "optional": true 172 | } 173 | } 174 | }, 175 | "node_modules/body-parser/node_modules/iconv-lite": { 176 | "version": "0.5.2", 177 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", 178 | "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", 179 | "dependencies": { 180 | "safer-buffer": ">= 2.1.2 < 3" 181 | }, 182 | "engines": { 183 | "node": ">=0.10.0" 184 | } 185 | }, 186 | "node_modules/body-parser/node_modules/ms": { 187 | "version": "2.1.3", 188 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 189 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 190 | }, 191 | "node_modules/body-parser/node_modules/qs": { 192 | "version": "6.14.0", 193 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 194 | "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 195 | "dependencies": { 196 | "side-channel": "^1.1.0" 197 | }, 198 | "engines": { 199 | "node": ">=0.6" 200 | }, 201 | "funding": { 202 | "url": "https://github.com/sponsors/ljharb" 203 | } 204 | }, 205 | "node_modules/bytes": { 206 | "version": "3.1.2", 207 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 208 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 209 | "engines": { 210 | "node": ">= 0.8" 211 | } 212 | }, 213 | "node_modules/call-bind-apply-helpers": { 214 | "version": "1.0.2", 215 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 216 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 217 | "dependencies": { 218 | "es-errors": "^1.3.0", 219 | "function-bind": "^1.1.2" 220 | }, 221 | "engines": { 222 | "node": ">= 0.4" 223 | } 224 | }, 225 | "node_modules/call-bound": { 226 | "version": "1.0.4", 227 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 228 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 229 | "dependencies": { 230 | "call-bind-apply-helpers": "^1.0.2", 231 | "get-intrinsic": "^1.3.0" 232 | }, 233 | "engines": { 234 | "node": ">= 0.4" 235 | }, 236 | "funding": { 237 | "url": "https://github.com/sponsors/ljharb" 238 | } 239 | }, 240 | "node_modules/cliui": { 241 | "version": "8.0.1", 242 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", 243 | "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", 244 | "dependencies": { 245 | "string-width": "^4.2.0", 246 | "strip-ansi": "^6.0.1", 247 | "wrap-ansi": "^7.0.0" 248 | }, 249 | "engines": { 250 | "node": ">=12" 251 | } 252 | }, 253 | "node_modules/color-convert": { 254 | "version": "2.0.1", 255 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 256 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 257 | "dependencies": { 258 | "color-name": "~1.1.4" 259 | }, 260 | "engines": { 261 | "node": ">=7.0.0" 262 | } 263 | }, 264 | "node_modules/color-name": { 265 | "version": "1.1.4", 266 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 267 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 268 | }, 269 | "node_modules/combined-stream": { 270 | "version": "1.0.8", 271 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 272 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 273 | "dependencies": { 274 | "delayed-stream": "~1.0.0" 275 | }, 276 | "engines": { 277 | "node": ">= 0.8" 278 | } 279 | }, 280 | "node_modules/content-disposition": { 281 | "version": "1.0.0", 282 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", 283 | "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", 284 | "dependencies": { 285 | "safe-buffer": "5.2.1" 286 | }, 287 | "engines": { 288 | "node": ">= 0.6" 289 | } 290 | }, 291 | "node_modules/content-type": { 292 | "version": "1.0.5", 293 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 294 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 295 | "engines": { 296 | "node": ">= 0.6" 297 | } 298 | }, 299 | "node_modules/cookie": { 300 | "version": "0.7.1", 301 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 302 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 303 | "engines": { 304 | "node": ">= 0.6" 305 | } 306 | }, 307 | "node_modules/cookie-signature": { 308 | "version": "1.2.2", 309 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 310 | "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 311 | "engines": { 312 | "node": ">=6.6.0" 313 | } 314 | }, 315 | "node_modules/cors": { 316 | "version": "2.8.5", 317 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 318 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 319 | "dependencies": { 320 | "object-assign": "^4", 321 | "vary": "^1" 322 | }, 323 | "engines": { 324 | "node": ">= 0.10" 325 | } 326 | }, 327 | "node_modules/debug": { 328 | "version": "4.3.6", 329 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", 330 | "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", 331 | "dependencies": { 332 | "ms": "2.1.2" 333 | }, 334 | "engines": { 335 | "node": ">=6.0" 336 | }, 337 | "peerDependenciesMeta": { 338 | "supports-color": { 339 | "optional": true 340 | } 341 | } 342 | }, 343 | "node_modules/delayed-stream": { 344 | "version": "1.0.0", 345 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 346 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 347 | "engines": { 348 | "node": ">=0.4.0" 349 | } 350 | }, 351 | "node_modules/depd": { 352 | "version": "2.0.0", 353 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 354 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 355 | "engines": { 356 | "node": ">= 0.8" 357 | } 358 | }, 359 | "node_modules/destroy": { 360 | "version": "1.2.0", 361 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 362 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 363 | "engines": { 364 | "node": ">= 0.8", 365 | "npm": "1.2.8000 || >= 1.4.16" 366 | } 367 | }, 368 | "node_modules/dotenv": { 369 | "version": "16.4.5", 370 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", 371 | "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", 372 | "engines": { 373 | "node": ">=12" 374 | }, 375 | "funding": { 376 | "url": "https://dotenvx.com" 377 | } 378 | }, 379 | "node_modules/dunder-proto": { 380 | "version": "1.0.1", 381 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 382 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 383 | "dependencies": { 384 | "call-bind-apply-helpers": "^1.0.1", 385 | "es-errors": "^1.3.0", 386 | "gopd": "^1.2.0" 387 | }, 388 | "engines": { 389 | "node": ">= 0.4" 390 | } 391 | }, 392 | "node_modules/ee-first": { 393 | "version": "1.1.1", 394 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 395 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 396 | }, 397 | "node_modules/emoji-regex": { 398 | "version": "8.0.0", 399 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 400 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 401 | }, 402 | "node_modules/encodeurl": { 403 | "version": "2.0.0", 404 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 405 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 406 | "engines": { 407 | "node": ">= 0.8" 408 | } 409 | }, 410 | "node_modules/es-define-property": { 411 | "version": "1.0.1", 412 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 413 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 414 | "engines": { 415 | "node": ">= 0.4" 416 | } 417 | }, 418 | "node_modules/es-errors": { 419 | "version": "1.3.0", 420 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 421 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 422 | "engines": { 423 | "node": ">= 0.4" 424 | } 425 | }, 426 | "node_modules/es-object-atoms": { 427 | "version": "1.1.1", 428 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 429 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 430 | "dependencies": { 431 | "es-errors": "^1.3.0" 432 | }, 433 | "engines": { 434 | "node": ">= 0.4" 435 | } 436 | }, 437 | "node_modules/escalade": { 438 | "version": "3.2.0", 439 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 440 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 441 | "engines": { 442 | "node": ">=6" 443 | } 444 | }, 445 | "node_modules/escape-html": { 446 | "version": "1.0.3", 447 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 448 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 449 | }, 450 | "node_modules/etag": { 451 | "version": "1.8.1", 452 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 453 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 454 | "engines": { 455 | "node": ">= 0.6" 456 | } 457 | }, 458 | "node_modules/eventsource": { 459 | "version": "3.0.5", 460 | "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.5.tgz", 461 | "integrity": "sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==", 462 | "dependencies": { 463 | "eventsource-parser": "^3.0.0" 464 | }, 465 | "engines": { 466 | "node": ">=18.0.0" 467 | } 468 | }, 469 | "node_modules/eventsource-parser": { 470 | "version": "3.0.0", 471 | "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", 472 | "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==", 473 | "engines": { 474 | "node": ">=18.0.0" 475 | } 476 | }, 477 | "node_modules/express": { 478 | "version": "5.0.1", 479 | "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", 480 | "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", 481 | "dependencies": { 482 | "accepts": "^2.0.0", 483 | "body-parser": "^2.0.1", 484 | "content-disposition": "^1.0.0", 485 | "content-type": "~1.0.4", 486 | "cookie": "0.7.1", 487 | "cookie-signature": "^1.2.1", 488 | "debug": "4.3.6", 489 | "depd": "2.0.0", 490 | "encodeurl": "~2.0.0", 491 | "escape-html": "~1.0.3", 492 | "etag": "~1.8.1", 493 | "finalhandler": "^2.0.0", 494 | "fresh": "2.0.0", 495 | "http-errors": "2.0.0", 496 | "merge-descriptors": "^2.0.0", 497 | "methods": "~1.1.2", 498 | "mime-types": "^3.0.0", 499 | "on-finished": "2.4.1", 500 | "once": "1.4.0", 501 | "parseurl": "~1.3.3", 502 | "proxy-addr": "~2.0.7", 503 | "qs": "6.13.0", 504 | "range-parser": "~1.2.1", 505 | "router": "^2.0.0", 506 | "safe-buffer": "5.2.1", 507 | "send": "^1.1.0", 508 | "serve-static": "^2.1.0", 509 | "setprototypeof": "1.2.0", 510 | "statuses": "2.0.1", 511 | "type-is": "^2.0.0", 512 | "utils-merge": "1.0.1", 513 | "vary": "~1.1.2" 514 | }, 515 | "engines": { 516 | "node": ">= 18" 517 | } 518 | }, 519 | "node_modules/express-rate-limit": { 520 | "version": "7.5.0", 521 | "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", 522 | "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", 523 | "engines": { 524 | "node": ">= 16" 525 | }, 526 | "funding": { 527 | "url": "https://github.com/sponsors/express-rate-limit" 528 | }, 529 | "peerDependencies": { 530 | "express": "^4.11 || 5 || ^5.0.0-beta.1" 531 | } 532 | }, 533 | "node_modules/express/node_modules/mime-db": { 534 | "version": "1.54.0", 535 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 536 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 537 | "engines": { 538 | "node": ">= 0.6" 539 | } 540 | }, 541 | "node_modules/express/node_modules/mime-types": { 542 | "version": "3.0.0", 543 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", 544 | "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", 545 | "dependencies": { 546 | "mime-db": "^1.53.0" 547 | }, 548 | "engines": { 549 | "node": ">= 0.6" 550 | } 551 | }, 552 | "node_modules/finalhandler": { 553 | "version": "2.1.0", 554 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", 555 | "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 556 | "dependencies": { 557 | "debug": "^4.4.0", 558 | "encodeurl": "^2.0.0", 559 | "escape-html": "^1.0.3", 560 | "on-finished": "^2.4.1", 561 | "parseurl": "^1.3.3", 562 | "statuses": "^2.0.1" 563 | }, 564 | "engines": { 565 | "node": ">= 0.8" 566 | } 567 | }, 568 | "node_modules/finalhandler/node_modules/debug": { 569 | "version": "4.4.0", 570 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 571 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 572 | "dependencies": { 573 | "ms": "^2.1.3" 574 | }, 575 | "engines": { 576 | "node": ">=6.0" 577 | }, 578 | "peerDependenciesMeta": { 579 | "supports-color": { 580 | "optional": true 581 | } 582 | } 583 | }, 584 | "node_modules/finalhandler/node_modules/ms": { 585 | "version": "2.1.3", 586 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 587 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 588 | }, 589 | "node_modules/follow-redirects": { 590 | "version": "1.15.9", 591 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", 592 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 593 | "funding": [ 594 | { 595 | "type": "individual", 596 | "url": "https://github.com/sponsors/RubenVerborgh" 597 | } 598 | ], 599 | "engines": { 600 | "node": ">=4.0" 601 | }, 602 | "peerDependenciesMeta": { 603 | "debug": { 604 | "optional": true 605 | } 606 | } 607 | }, 608 | "node_modules/form-data": { 609 | "version": "4.0.1", 610 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", 611 | "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", 612 | "dependencies": { 613 | "asynckit": "^0.4.0", 614 | "combined-stream": "^1.0.8", 615 | "mime-types": "^2.1.12" 616 | }, 617 | "engines": { 618 | "node": ">= 6" 619 | } 620 | }, 621 | "node_modules/forwarded": { 622 | "version": "0.2.0", 623 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 624 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 625 | "engines": { 626 | "node": ">= 0.6" 627 | } 628 | }, 629 | "node_modules/fresh": { 630 | "version": "2.0.0", 631 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 632 | "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 633 | "engines": { 634 | "node": ">= 0.8" 635 | } 636 | }, 637 | "node_modules/function-bind": { 638 | "version": "1.1.2", 639 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 640 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 641 | "funding": { 642 | "url": "https://github.com/sponsors/ljharb" 643 | } 644 | }, 645 | "node_modules/get-caller-file": { 646 | "version": "2.0.5", 647 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 648 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 649 | "engines": { 650 | "node": "6.* || 8.* || >= 10.*" 651 | } 652 | }, 653 | "node_modules/get-intrinsic": { 654 | "version": "1.3.0", 655 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 656 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 657 | "dependencies": { 658 | "call-bind-apply-helpers": "^1.0.2", 659 | "es-define-property": "^1.0.1", 660 | "es-errors": "^1.3.0", 661 | "es-object-atoms": "^1.1.1", 662 | "function-bind": "^1.1.2", 663 | "get-proto": "^1.0.1", 664 | "gopd": "^1.2.0", 665 | "has-symbols": "^1.1.0", 666 | "hasown": "^2.0.2", 667 | "math-intrinsics": "^1.1.0" 668 | }, 669 | "engines": { 670 | "node": ">= 0.4" 671 | }, 672 | "funding": { 673 | "url": "https://github.com/sponsors/ljharb" 674 | } 675 | }, 676 | "node_modules/get-proto": { 677 | "version": "1.0.1", 678 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 679 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 680 | "dependencies": { 681 | "dunder-proto": "^1.0.1", 682 | "es-object-atoms": "^1.0.0" 683 | }, 684 | "engines": { 685 | "node": ">= 0.4" 686 | } 687 | }, 688 | "node_modules/gopd": { 689 | "version": "1.2.0", 690 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 691 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 692 | "engines": { 693 | "node": ">= 0.4" 694 | }, 695 | "funding": { 696 | "url": "https://github.com/sponsors/ljharb" 697 | } 698 | }, 699 | "node_modules/has-symbols": { 700 | "version": "1.1.0", 701 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 702 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 703 | "engines": { 704 | "node": ">= 0.4" 705 | }, 706 | "funding": { 707 | "url": "https://github.com/sponsors/ljharb" 708 | } 709 | }, 710 | "node_modules/hasown": { 711 | "version": "2.0.2", 712 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 713 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 714 | "dependencies": { 715 | "function-bind": "^1.1.2" 716 | }, 717 | "engines": { 718 | "node": ">= 0.4" 719 | } 720 | }, 721 | "node_modules/http-errors": { 722 | "version": "2.0.0", 723 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 724 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 725 | "dependencies": { 726 | "depd": "2.0.0", 727 | "inherits": "2.0.4", 728 | "setprototypeof": "1.2.0", 729 | "statuses": "2.0.1", 730 | "toidentifier": "1.0.1" 731 | }, 732 | "engines": { 733 | "node": ">= 0.8" 734 | } 735 | }, 736 | "node_modules/iconv-lite": { 737 | "version": "0.6.3", 738 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 739 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 740 | "dependencies": { 741 | "safer-buffer": ">= 2.1.2 < 3.0.0" 742 | }, 743 | "engines": { 744 | "node": ">=0.10.0" 745 | } 746 | }, 747 | "node_modules/inherits": { 748 | "version": "2.0.4", 749 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 750 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 751 | }, 752 | "node_modules/ipaddr.js": { 753 | "version": "1.9.1", 754 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 755 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 756 | "engines": { 757 | "node": ">= 0.10" 758 | } 759 | }, 760 | "node_modules/is-fullwidth-code-point": { 761 | "version": "3.0.0", 762 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 763 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 764 | "engines": { 765 | "node": ">=8" 766 | } 767 | }, 768 | "node_modules/is-promise": { 769 | "version": "4.0.0", 770 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 771 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" 772 | }, 773 | "node_modules/math-intrinsics": { 774 | "version": "1.1.0", 775 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 776 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 777 | "engines": { 778 | "node": ">= 0.4" 779 | } 780 | }, 781 | "node_modules/media-typer": { 782 | "version": "1.1.0", 783 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 784 | "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 785 | "engines": { 786 | "node": ">= 0.8" 787 | } 788 | }, 789 | "node_modules/merge-descriptors": { 790 | "version": "2.0.0", 791 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 792 | "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 793 | "engines": { 794 | "node": ">=18" 795 | }, 796 | "funding": { 797 | "url": "https://github.com/sponsors/sindresorhus" 798 | } 799 | }, 800 | "node_modules/methods": { 801 | "version": "1.1.2", 802 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 803 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 804 | "engines": { 805 | "node": ">= 0.6" 806 | } 807 | }, 808 | "node_modules/mime-db": { 809 | "version": "1.52.0", 810 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 811 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 812 | "engines": { 813 | "node": ">= 0.6" 814 | } 815 | }, 816 | "node_modules/mime-types": { 817 | "version": "2.1.35", 818 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 819 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 820 | "dependencies": { 821 | "mime-db": "1.52.0" 822 | }, 823 | "engines": { 824 | "node": ">= 0.6" 825 | } 826 | }, 827 | "node_modules/ms": { 828 | "version": "2.1.2", 829 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 830 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 831 | }, 832 | "node_modules/negotiator": { 833 | "version": "1.0.0", 834 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 835 | "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 836 | "engines": { 837 | "node": ">= 0.6" 838 | } 839 | }, 840 | "node_modules/object-assign": { 841 | "version": "4.1.1", 842 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 843 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 844 | "engines": { 845 | "node": ">=0.10.0" 846 | } 847 | }, 848 | "node_modules/object-inspect": { 849 | "version": "1.13.4", 850 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 851 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 852 | "engines": { 853 | "node": ">= 0.4" 854 | }, 855 | "funding": { 856 | "url": "https://github.com/sponsors/ljharb" 857 | } 858 | }, 859 | "node_modules/on-finished": { 860 | "version": "2.4.1", 861 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 862 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 863 | "dependencies": { 864 | "ee-first": "1.1.1" 865 | }, 866 | "engines": { 867 | "node": ">= 0.8" 868 | } 869 | }, 870 | "node_modules/once": { 871 | "version": "1.4.0", 872 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 873 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 874 | "dependencies": { 875 | "wrappy": "1" 876 | } 877 | }, 878 | "node_modules/parseurl": { 879 | "version": "1.3.3", 880 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 881 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 882 | "engines": { 883 | "node": ">= 0.8" 884 | } 885 | }, 886 | "node_modules/path-to-regexp": { 887 | "version": "8.2.0", 888 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", 889 | "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", 890 | "engines": { 891 | "node": ">=16" 892 | } 893 | }, 894 | "node_modules/pkce-challenge": { 895 | "version": "4.1.0", 896 | "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz", 897 | "integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==", 898 | "engines": { 899 | "node": ">=16.20.0" 900 | } 901 | }, 902 | "node_modules/proxy-addr": { 903 | "version": "2.0.7", 904 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 905 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 906 | "dependencies": { 907 | "forwarded": "0.2.0", 908 | "ipaddr.js": "1.9.1" 909 | }, 910 | "engines": { 911 | "node": ">= 0.10" 912 | } 913 | }, 914 | "node_modules/proxy-from-env": { 915 | "version": "1.1.0", 916 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 917 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 918 | }, 919 | "node_modules/qs": { 920 | "version": "6.13.0", 921 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 922 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 923 | "dependencies": { 924 | "side-channel": "^1.0.6" 925 | }, 926 | "engines": { 927 | "node": ">=0.6" 928 | }, 929 | "funding": { 930 | "url": "https://github.com/sponsors/ljharb" 931 | } 932 | }, 933 | "node_modules/range-parser": { 934 | "version": "1.2.1", 935 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 936 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 937 | "engines": { 938 | "node": ">= 0.6" 939 | } 940 | }, 941 | "node_modules/raw-body": { 942 | "version": "3.0.0", 943 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 944 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 945 | "dependencies": { 946 | "bytes": "3.1.2", 947 | "http-errors": "2.0.0", 948 | "iconv-lite": "0.6.3", 949 | "unpipe": "1.0.0" 950 | }, 951 | "engines": { 952 | "node": ">= 0.8" 953 | } 954 | }, 955 | "node_modules/require-directory": { 956 | "version": "2.1.1", 957 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 958 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 959 | "engines": { 960 | "node": ">=0.10.0" 961 | } 962 | }, 963 | "node_modules/router": { 964 | "version": "2.1.0", 965 | "resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz", 966 | "integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==", 967 | "dependencies": { 968 | "is-promise": "^4.0.0", 969 | "parseurl": "^1.3.3", 970 | "path-to-regexp": "^8.0.0" 971 | }, 972 | "engines": { 973 | "node": ">= 18" 974 | } 975 | }, 976 | "node_modules/safe-buffer": { 977 | "version": "5.2.1", 978 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 979 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 980 | "funding": [ 981 | { 982 | "type": "github", 983 | "url": "https://github.com/sponsors/feross" 984 | }, 985 | { 986 | "type": "patreon", 987 | "url": "https://www.patreon.com/feross" 988 | }, 989 | { 990 | "type": "consulting", 991 | "url": "https://feross.org/support" 992 | } 993 | ] 994 | }, 995 | "node_modules/safer-buffer": { 996 | "version": "2.1.2", 997 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 998 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 999 | }, 1000 | "node_modules/send": { 1001 | "version": "1.1.0", 1002 | "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", 1003 | "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", 1004 | "dependencies": { 1005 | "debug": "^4.3.5", 1006 | "destroy": "^1.2.0", 1007 | "encodeurl": "^2.0.0", 1008 | "escape-html": "^1.0.3", 1009 | "etag": "^1.8.1", 1010 | "fresh": "^0.5.2", 1011 | "http-errors": "^2.0.0", 1012 | "mime-types": "^2.1.35", 1013 | "ms": "^2.1.3", 1014 | "on-finished": "^2.4.1", 1015 | "range-parser": "^1.2.1", 1016 | "statuses": "^2.0.1" 1017 | }, 1018 | "engines": { 1019 | "node": ">= 18" 1020 | } 1021 | }, 1022 | "node_modules/send/node_modules/fresh": { 1023 | "version": "0.5.2", 1024 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1025 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 1026 | "engines": { 1027 | "node": ">= 0.6" 1028 | } 1029 | }, 1030 | "node_modules/send/node_modules/ms": { 1031 | "version": "2.1.3", 1032 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1033 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1034 | }, 1035 | "node_modules/serve-static": { 1036 | "version": "2.1.0", 1037 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", 1038 | "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", 1039 | "dependencies": { 1040 | "encodeurl": "^2.0.0", 1041 | "escape-html": "^1.0.3", 1042 | "parseurl": "^1.3.3", 1043 | "send": "^1.0.0" 1044 | }, 1045 | "engines": { 1046 | "node": ">= 18" 1047 | } 1048 | }, 1049 | "node_modules/setprototypeof": { 1050 | "version": "1.2.0", 1051 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1052 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1053 | }, 1054 | "node_modules/side-channel": { 1055 | "version": "1.1.0", 1056 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1057 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1058 | "dependencies": { 1059 | "es-errors": "^1.3.0", 1060 | "object-inspect": "^1.13.3", 1061 | "side-channel-list": "^1.0.0", 1062 | "side-channel-map": "^1.0.1", 1063 | "side-channel-weakmap": "^1.0.2" 1064 | }, 1065 | "engines": { 1066 | "node": ">= 0.4" 1067 | }, 1068 | "funding": { 1069 | "url": "https://github.com/sponsors/ljharb" 1070 | } 1071 | }, 1072 | "node_modules/side-channel-list": { 1073 | "version": "1.0.0", 1074 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1075 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1076 | "dependencies": { 1077 | "es-errors": "^1.3.0", 1078 | "object-inspect": "^1.13.3" 1079 | }, 1080 | "engines": { 1081 | "node": ">= 0.4" 1082 | }, 1083 | "funding": { 1084 | "url": "https://github.com/sponsors/ljharb" 1085 | } 1086 | }, 1087 | "node_modules/side-channel-map": { 1088 | "version": "1.0.1", 1089 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1090 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1091 | "dependencies": { 1092 | "call-bound": "^1.0.2", 1093 | "es-errors": "^1.3.0", 1094 | "get-intrinsic": "^1.2.5", 1095 | "object-inspect": "^1.13.3" 1096 | }, 1097 | "engines": { 1098 | "node": ">= 0.4" 1099 | }, 1100 | "funding": { 1101 | "url": "https://github.com/sponsors/ljharb" 1102 | } 1103 | }, 1104 | "node_modules/side-channel-weakmap": { 1105 | "version": "1.0.2", 1106 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1107 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1108 | "dependencies": { 1109 | "call-bound": "^1.0.2", 1110 | "es-errors": "^1.3.0", 1111 | "get-intrinsic": "^1.2.5", 1112 | "object-inspect": "^1.13.3", 1113 | "side-channel-map": "^1.0.1" 1114 | }, 1115 | "engines": { 1116 | "node": ">= 0.4" 1117 | }, 1118 | "funding": { 1119 | "url": "https://github.com/sponsors/ljharb" 1120 | } 1121 | }, 1122 | "node_modules/statuses": { 1123 | "version": "2.0.1", 1124 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1125 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1126 | "engines": { 1127 | "node": ">= 0.8" 1128 | } 1129 | }, 1130 | "node_modules/string-width": { 1131 | "version": "4.2.3", 1132 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1133 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1134 | "dependencies": { 1135 | "emoji-regex": "^8.0.0", 1136 | "is-fullwidth-code-point": "^3.0.0", 1137 | "strip-ansi": "^6.0.1" 1138 | }, 1139 | "engines": { 1140 | "node": ">=8" 1141 | } 1142 | }, 1143 | "node_modules/strip-ansi": { 1144 | "version": "6.0.1", 1145 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1146 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1147 | "dependencies": { 1148 | "ansi-regex": "^5.0.1" 1149 | }, 1150 | "engines": { 1151 | "node": ">=8" 1152 | } 1153 | }, 1154 | "node_modules/toidentifier": { 1155 | "version": "1.0.1", 1156 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1157 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1158 | "engines": { 1159 | "node": ">=0.6" 1160 | } 1161 | }, 1162 | "node_modules/type-is": { 1163 | "version": "2.0.0", 1164 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", 1165 | "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", 1166 | "dependencies": { 1167 | "content-type": "^1.0.5", 1168 | "media-typer": "^1.1.0", 1169 | "mime-types": "^3.0.0" 1170 | }, 1171 | "engines": { 1172 | "node": ">= 0.6" 1173 | } 1174 | }, 1175 | "node_modules/type-is/node_modules/mime-db": { 1176 | "version": "1.54.0", 1177 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 1178 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 1179 | "engines": { 1180 | "node": ">= 0.6" 1181 | } 1182 | }, 1183 | "node_modules/type-is/node_modules/mime-types": { 1184 | "version": "3.0.0", 1185 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", 1186 | "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", 1187 | "dependencies": { 1188 | "mime-db": "^1.53.0" 1189 | }, 1190 | "engines": { 1191 | "node": ">= 0.6" 1192 | } 1193 | }, 1194 | "node_modules/typescript": { 1195 | "version": "5.7.2", 1196 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 1197 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 1198 | "dev": true, 1199 | "bin": { 1200 | "tsc": "bin/tsc", 1201 | "tsserver": "bin/tsserver" 1202 | }, 1203 | "engines": { 1204 | "node": ">=14.17" 1205 | } 1206 | }, 1207 | "node_modules/undici-types": { 1208 | "version": "6.19.8", 1209 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 1210 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 1211 | "dev": true 1212 | }, 1213 | "node_modules/unpipe": { 1214 | "version": "1.0.0", 1215 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1216 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1217 | "engines": { 1218 | "node": ">= 0.8" 1219 | } 1220 | }, 1221 | "node_modules/utils-merge": { 1222 | "version": "1.0.1", 1223 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1224 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1225 | "engines": { 1226 | "node": ">= 0.4.0" 1227 | } 1228 | }, 1229 | "node_modules/vary": { 1230 | "version": "1.1.2", 1231 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1232 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1233 | "engines": { 1234 | "node": ">= 0.8" 1235 | } 1236 | }, 1237 | "node_modules/wrap-ansi": { 1238 | "version": "7.0.0", 1239 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1240 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1241 | "dependencies": { 1242 | "ansi-styles": "^4.0.0", 1243 | "string-width": "^4.1.0", 1244 | "strip-ansi": "^6.0.0" 1245 | }, 1246 | "engines": { 1247 | "node": ">=10" 1248 | }, 1249 | "funding": { 1250 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1251 | } 1252 | }, 1253 | "node_modules/wrappy": { 1254 | "version": "1.0.2", 1255 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1256 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1257 | }, 1258 | "node_modules/y18n": { 1259 | "version": "5.0.8", 1260 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 1261 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 1262 | "engines": { 1263 | "node": ">=10" 1264 | } 1265 | }, 1266 | "node_modules/yargs": { 1267 | "version": "17.7.2", 1268 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", 1269 | "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", 1270 | "dependencies": { 1271 | "cliui": "^8.0.1", 1272 | "escalade": "^3.1.1", 1273 | "get-caller-file": "^2.0.5", 1274 | "require-directory": "^2.1.1", 1275 | "string-width": "^4.2.3", 1276 | "y18n": "^5.0.5", 1277 | "yargs-parser": "^21.1.1" 1278 | }, 1279 | "engines": { 1280 | "node": ">=12" 1281 | } 1282 | }, 1283 | "node_modules/yargs-parser": { 1284 | "version": "21.1.1", 1285 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", 1286 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", 1287 | "engines": { 1288 | "node": ">=12" 1289 | } 1290 | }, 1291 | "node_modules/zod": { 1292 | "version": "3.24.2", 1293 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", 1294 | "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", 1295 | "funding": { 1296 | "url": "https://github.com/sponsors/colinhacks" 1297 | } 1298 | }, 1299 | "node_modules/zod-to-json-schema": { 1300 | "version": "3.24.4", 1301 | "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.4.tgz", 1302 | "integrity": "sha512-0uNlcvgabyrni9Ag8Vghj21drk7+7tp7VTwwR7KxxXXc/3pbXz2PHlDgj3cICahgF1kHm4dExBFj7BXrZJXzig==", 1303 | "peerDependencies": { 1304 | "zod": "^3.24.1" 1305 | } 1306 | } 1307 | } 1308 | } 1309 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exa-mcp-server", 3 | "version": "0.3.6", 4 | "description": "A Model Context Protocol server with Exa for web search, academic paper search, and Twitter/X.com search. Provides real-time web searches with configurable tool selection, allowing users to enable or disable specific search capabilities. Supports customizable result counts, live crawling options, and returns content from the most relevant websites.", 5 | "type": "module", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/exa-labs/exa-mcp-server.git" 9 | }, 10 | "bin": { 11 | "exa-mcp-server": "./build/index.js" 12 | }, 13 | "files": [ 14 | "build" 15 | ], 16 | "keywords": [ 17 | "mcp", 18 | "model context protocol", 19 | "exa", 20 | "websearch", 21 | "claude", 22 | "ai", 23 | "research", 24 | "papers", 25 | "twitter", 26 | "x.com" 27 | ], 28 | "author": "Exa Labs", 29 | "scripts": { 30 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 31 | "prepare": "npm run build", 32 | "watch": "tsc --watch", 33 | "inspector": "npx @modelcontextprotocol/inspector build/index.js", 34 | "prepublishOnly": "npm run build" 35 | }, 36 | "dependencies": { 37 | "@modelcontextprotocol/sdk": "^1.7.0", 38 | "axios": "^1.7.8", 39 | "dotenv": "^16.4.5", 40 | "yargs": "^17.7.2", 41 | "zod": "^3.22.4" 42 | }, 43 | "devDependencies": { 44 | "@types/node": "^20.11.24", 45 | "@types/yargs": "^17.0.33", 46 | "typescript": "^5.3.3" 47 | }, 48 | "engines": { 49 | "node": ">=18.0.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/deployments 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | properties: 9 | exaApiKey: 10 | type: string 11 | description: The API key for accessing the Exa Search API. 12 | commandFunction: 13 | # A function that produces the CLI command to start the MCP on stdio. 14 | |- 15 | (config) => { 16 | const env = {} 17 | if (config.exaApiKey) { 18 | env.EXA_API_KEY = config.exaApiKey 19 | } 20 | return { command: 'node', args: ['build/index.js'], env } 21 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import dotenv from "dotenv"; 5 | import yargs from 'yargs'; 6 | import { hideBin } from 'yargs/helpers'; 7 | 8 | // Import the tool registry system 9 | import { toolRegistry } from "./tools/index.js"; 10 | import { log } from "./utils/logger.js"; 11 | 12 | dotenv.config(); 13 | 14 | // Parse command line arguments to determine which tools to enable 15 | const argv = yargs(hideBin(process.argv)) 16 | .option('tools', { 17 | type: 'string', 18 | description: 'Comma-separated list of tools to enable (if not specified, all enabled-by-default tools are used)', 19 | default: '' 20 | }) 21 | .option('list-tools', { 22 | type: 'boolean', 23 | description: 'List all available tools and exit', 24 | default: false 25 | }) 26 | .help() 27 | .argv; 28 | 29 | // Convert comma-separated string to Set for easier lookups 30 | const argvObj = argv as any; 31 | const toolsString = argvObj['tools'] || ''; 32 | const specifiedTools = new Set( 33 | toolsString ? toolsString.split(',').map((tool: string) => tool.trim()) : [] 34 | ); 35 | 36 | // List all available tools if requested 37 | if (argvObj['list-tools']) { 38 | console.log("Available tools:"); 39 | 40 | Object.entries(toolRegistry).forEach(([id, tool]) => { 41 | console.log(`- ${id}: ${tool.name}`); 42 | console.log(` Description: ${tool.description}`); 43 | console.log(` Enabled by default: ${tool.enabled ? 'Yes' : 'No'}`); 44 | console.log(); 45 | }); 46 | 47 | process.exit(0); 48 | } 49 | 50 | // Check for API key after handling list-tools to allow listing without a key 51 | const API_KEY = process.env.EXA_API_KEY; 52 | if (!API_KEY) { 53 | throw new Error("EXA_API_KEY environment variable is required"); 54 | } 55 | 56 | /** 57 | * Exa AI Web Search MCP Server 58 | * 59 | * This MCP server integrates Exa AI's search capabilities with Claude and other MCP-compatible clients. 60 | * Exa is a search engine and API specifically designed for up-to-date web searching and retrieval, 61 | * offering more recent and comprehensive results than what might be available in an LLM's training data. 62 | * 63 | * The server provides tools that enable: 64 | * - Real-time web searching with configurable parameters 65 | * - Research paper searches 66 | * - And more to come! 67 | */ 68 | 69 | class ExaServer { 70 | private server: McpServer; 71 | 72 | constructor() { 73 | this.server = new McpServer({ 74 | name: "exa-search-server", 75 | version: "0.3.6" 76 | }); 77 | 78 | log("Server initialized"); 79 | } 80 | 81 | private setupTools(): string[] { 82 | // Register tools based on specifications 83 | const registeredTools: string[] = []; 84 | 85 | Object.entries(toolRegistry).forEach(([toolId, tool]) => { 86 | // If specific tools were provided, only enable those. 87 | // Otherwise, enable all tools marked as enabled by default 88 | const shouldRegister = specifiedTools.size > 0 89 | ? specifiedTools.has(toolId) 90 | : tool.enabled; 91 | 92 | if (shouldRegister) { 93 | this.server.tool( 94 | tool.name, 95 | tool.description, 96 | tool.schema, 97 | tool.handler 98 | ); 99 | registeredTools.push(toolId); 100 | } 101 | }); 102 | 103 | return registeredTools; 104 | } 105 | 106 | async run(): Promise { 107 | try { 108 | // Set up tools before connecting 109 | const registeredTools = this.setupTools(); 110 | 111 | log(`Starting Exa MCP server with ${registeredTools.length} tools: ${registeredTools.join(', ')}`); 112 | 113 | const transport = new StdioServerTransport(); 114 | 115 | // Handle connection errors 116 | transport.onerror = (error) => { 117 | log(`Transport error: ${error.message}`); 118 | }; 119 | 120 | await this.server.connect(transport); 121 | log("Exa Search MCP server running on stdio"); 122 | } catch (error) { 123 | log(`Server initialization error: ${error instanceof Error ? error.message : String(error)}`); 124 | throw error; 125 | } 126 | } 127 | } 128 | 129 | // Create and run the server with proper error handling 130 | (async () => { 131 | try { 132 | const server = new ExaServer(); 133 | await server.run(); 134 | } catch (error) { 135 | log(`Fatal server error: ${error instanceof Error ? error.message : String(error)}`); 136 | process.exit(1); 137 | } 138 | })(); -------------------------------------------------------------------------------- /src/tools/companyResearch.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import axios from "axios"; 3 | import { toolRegistry, API_CONFIG } from "./config.js"; 4 | import { ExaSearchRequest, ExaSearchResponse } from "../types.js"; 5 | import { createRequestLogger } from "../utils/logger.js"; 6 | 7 | // Register the company research tool 8 | toolRegistry["company_research"] = { 9 | name: "company_research", 10 | description: "Research companies using Exa AI - performs targeted searches of company websites to gather comprehensive information about businesses. Returns detailed information from company websites including about pages, pricing, FAQs, blogs, and other relevant content. Specify the company URL and optionally target specific sections of their website.", 11 | schema: { 12 | query: z.string().describe("Company website URL (e.g., 'exa.ai' or 'https://exa.ai')"), 13 | subpages: z.number().optional().describe("Number of subpages to crawl (default: 10)"), 14 | subpageTarget: z.array(z.string()).optional().describe("Specific sections to target (e.g., ['about', 'pricing', 'faq', 'blog']). If not provided, will crawl the most relevant pages.") 15 | }, 16 | handler: async ({ query, numResults, subpages, subpageTarget }, extra) => { 17 | const requestId = `company_research-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`; 18 | const logger = createRequestLogger(requestId, 'company_research'); 19 | 20 | logger.start(query); 21 | 22 | try { 23 | // Create a fresh axios instance for each request 24 | const axiosInstance = axios.create({ 25 | baseURL: API_CONFIG.BASE_URL, 26 | headers: { 27 | 'accept': 'application/json', 28 | 'content-type': 'application/json', 29 | 'x-api-key': process.env.EXA_API_KEY || '' 30 | }, 31 | timeout: 25000 32 | }); 33 | 34 | // Extract domain from query if it's a URL 35 | let domain = query; 36 | if (query.includes('http')) { 37 | try { 38 | const url = new URL(query); 39 | domain = url.hostname.replace('www.', ''); 40 | } catch (e) { 41 | logger.log(`Warning: Could not parse URL from query: ${query}`); 42 | } 43 | } 44 | 45 | const searchRequest: ExaSearchRequest = { 46 | query, 47 | category: "company", 48 | includeDomains: [query], 49 | type: "auto", 50 | numResults: 1, 51 | contents: { 52 | text: { 53 | maxCharacters: API_CONFIG.DEFAULT_MAX_CHARACTERS 54 | }, 55 | livecrawl: 'always', 56 | subpages: subpages || 10, 57 | } 58 | }; 59 | 60 | // Add subpage targets if provided 61 | if (subpageTarget && subpageTarget.length > 0) { 62 | searchRequest.contents.subpageTarget = subpageTarget; 63 | logger.log(`Targeting specific sections: ${subpageTarget.join(', ')}`); 64 | } 65 | 66 | logger.log(`Researching company: ${domain}`); 67 | logger.log("Sending company research request to Exa API"); 68 | 69 | const response = await axiosInstance.post( 70 | API_CONFIG.ENDPOINTS.SEARCH, 71 | searchRequest, 72 | { timeout: 25000 } 73 | ); 74 | 75 | logger.log("Received company research response from Exa API"); 76 | 77 | if (!response.data || !response.data.results) { 78 | logger.log("Warning: Empty or invalid response from Exa API for company research"); 79 | return { 80 | content: [{ 81 | type: "text" as const, 82 | text: "No company information found. Please try a different query." 83 | }] 84 | }; 85 | } 86 | 87 | logger.log(`Found ${response.data.results.length} results about the company`); 88 | 89 | const result = { 90 | content: [{ 91 | type: "text" as const, 92 | text: JSON.stringify(response.data, null, 2) 93 | }] 94 | }; 95 | 96 | logger.complete(); 97 | return result; 98 | } catch (error) { 99 | logger.error(error); 100 | 101 | if (axios.isAxiosError(error)) { 102 | // Handle Axios errors specifically 103 | const statusCode = error.response?.status || 'unknown'; 104 | const errorMessage = error.response?.data?.message || error.message; 105 | 106 | logger.log(`Axios error (${statusCode}): ${errorMessage}`); 107 | return { 108 | content: [{ 109 | type: "text" as const, 110 | text: `Company research error (${statusCode}): ${errorMessage}` 111 | }], 112 | isError: true, 113 | }; 114 | } 115 | 116 | // Handle generic errors 117 | return { 118 | content: [{ 119 | type: "text" as const, 120 | text: `Company research error: ${error instanceof Error ? error.message : String(error)}` 121 | }], 122 | isError: true, 123 | }; 124 | } 125 | }, 126 | enabled: false // Disabled by default 127 | }; -------------------------------------------------------------------------------- /src/tools/competitorFinder.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import axios from "axios"; 3 | import { toolRegistry, API_CONFIG } from "./config.js"; 4 | import { ExaSearchRequest, ExaSearchResponse } from "../types.js"; 5 | import { createRequestLogger } from "../utils/logger.js"; 6 | 7 | // Register the competitor finder tool 8 | toolRegistry["competitor_finder"] = { 9 | name: "competitor_finder", 10 | description: "Find competitors of a company using Exa AI - performs targeted searches to identify businesses that offer similar products or services. Describe what the company does (without mentioning its name) and optionally provide the company's website to exclude it from results.", 11 | schema: { 12 | query: z.string().describe("Describe what the company/product in a few words (e.g., 'web search API', 'AI image generation', 'cloud storage service'). Keep it simple. Do not include the company name."), 13 | excludeDomain: z.string().optional().describe("Optional: The company's website to exclude from results (e.g., 'exa.ai')"), 14 | numResults: z.number().optional().describe("Number of competitors to return (default: 10)") 15 | }, 16 | handler: async ({ query, excludeDomain, numResults }, extra) => { 17 | const requestId = `competitor_finder-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`; 18 | const logger = createRequestLogger(requestId, 'competitor_finder'); 19 | 20 | logger.start(query); 21 | 22 | try { 23 | // Create a fresh axios instance for each request 24 | const axiosInstance = axios.create({ 25 | baseURL: API_CONFIG.BASE_URL, 26 | headers: { 27 | 'accept': 'application/json', 28 | 'content-type': 'application/json', 29 | 'x-api-key': process.env.EXA_API_KEY || '' 30 | }, 31 | timeout: 25000 32 | }); 33 | 34 | const searchRequest: ExaSearchRequest = { 35 | query, 36 | type: "auto", 37 | numResults: numResults || 10, 38 | contents: { 39 | text: { 40 | maxCharacters: API_CONFIG.DEFAULT_MAX_CHARACTERS 41 | }, 42 | livecrawl: 'always' 43 | } 44 | }; 45 | 46 | // Add exclude domain if provided 47 | if (excludeDomain) { 48 | searchRequest.excludeDomains = [excludeDomain]; 49 | logger.log(`Excluding domain: ${excludeDomain}`); 50 | } 51 | 52 | logger.log(`Finding competitors for: ${query}`); 53 | logger.log("Sending competitor finder request to Exa API"); 54 | 55 | const response = await axiosInstance.post( 56 | API_CONFIG.ENDPOINTS.SEARCH, 57 | searchRequest, 58 | { timeout: 25000 } 59 | ); 60 | 61 | logger.log("Received competitor finder response from Exa API"); 62 | 63 | if (!response.data || !response.data.results) { 64 | logger.log("Warning: Empty or invalid response from Exa API for competitor finder"); 65 | return { 66 | content: [{ 67 | type: "text" as const, 68 | text: "No competitors found. Please try a different query." 69 | }] 70 | }; 71 | } 72 | 73 | logger.log(`Found ${response.data.results.length} potential competitors`); 74 | 75 | const result = { 76 | content: [{ 77 | type: "text" as const, 78 | text: JSON.stringify(response.data, null, 2) 79 | }] 80 | }; 81 | 82 | logger.complete(); 83 | return result; 84 | } catch (error) { 85 | logger.error(error); 86 | 87 | if (axios.isAxiosError(error)) { 88 | // Handle Axios errors specifically 89 | const statusCode = error.response?.status || 'unknown'; 90 | const errorMessage = error.response?.data?.message || error.message; 91 | 92 | logger.log(`Axios error (${statusCode}): ${errorMessage}`); 93 | return { 94 | content: [{ 95 | type: "text" as const, 96 | text: `Competitor finder error (${statusCode}): ${errorMessage}` 97 | }], 98 | isError: true, 99 | }; 100 | } 101 | 102 | // Handle generic errors 103 | return { 104 | content: [{ 105 | type: "text" as const, 106 | text: `Competitor finder error: ${error instanceof Error ? error.message : String(error)}` 107 | }], 108 | isError: true, 109 | }; 110 | } 111 | }, 112 | enabled: false // Disabled by default 113 | }; -------------------------------------------------------------------------------- /src/tools/config.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 3 | 4 | export interface ToolRegistry { 5 | name: string; // Unique name for the tool 6 | description: string; // Human-readable description 7 | schema: z.ZodRawShape; // Zod schema for tool parameters 8 | handler: ( 9 | args: { [key: string]: any }, 10 | extra: any 11 | ) => Promise<{ 12 | content: { 13 | type: "text"; 14 | text: string; 15 | }[]; 16 | isError?: boolean; 17 | }>; // Function to execute when tool is called 18 | enabled: boolean; // Whether the tool is enabled by default 19 | } 20 | 21 | // Configuration for API 22 | export const API_CONFIG = { 23 | BASE_URL: 'https://api.exa.ai', 24 | ENDPOINTS: { 25 | SEARCH: '/search' 26 | }, 27 | DEFAULT_NUM_RESULTS: 5, 28 | DEFAULT_MAX_CHARACTERS: 3000 29 | } as const; 30 | 31 | // Tool registry that will be populated by tool modules 32 | export const toolRegistry: Record = {}; -------------------------------------------------------------------------------- /src/tools/crawling.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import axios from "axios"; 3 | import { toolRegistry, API_CONFIG } from "./config.js"; 4 | import { ExaCrawlRequest } from "../types.js"; 5 | import { createRequestLogger } from "../utils/logger.js"; 6 | 7 | // Register the crawling tool 8 | toolRegistry["crawling"] = { 9 | name: "crawling", 10 | description: "Extract content from specific URLs using Exa AI - performs targeted crawling of web pages to retrieve their full content. Useful for reading articles, PDFs, or any web page when you have the exact URL. Returns the complete text content of the specified URL.", 11 | schema: { 12 | url: z.string().describe("The URL to crawl (e.g., 'exa.ai')") 13 | }, 14 | handler: async ({ url }, extra) => { 15 | const requestId = `crawling-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`; 16 | const logger = createRequestLogger(requestId, 'crawling'); 17 | 18 | logger.start(url); 19 | 20 | try { 21 | // Create a fresh axios instance for each request 22 | const axiosInstance = axios.create({ 23 | baseURL: API_CONFIG.BASE_URL, 24 | headers: { 25 | 'accept': 'application/json', 26 | 'content-type': 'application/json', 27 | 'x-api-key': process.env.EXA_API_KEY || '' 28 | }, 29 | timeout: 25000 30 | }); 31 | 32 | const crawlRequest: ExaCrawlRequest = { 33 | ids: [url], 34 | text: true, 35 | livecrawl: 'always' 36 | }; 37 | 38 | logger.log(`Crawling URL: ${url}`); 39 | logger.log("Sending crawling request to Exa API"); 40 | 41 | const response = await axiosInstance.post( 42 | '/contents', 43 | crawlRequest, 44 | { timeout: 25000 } 45 | ); 46 | 47 | logger.log("Received crawling response from Exa API"); 48 | 49 | if (!response.data || !response.data.results || response.data.results.length === 0) { 50 | logger.log("Warning: Empty or invalid response from Exa API for crawling"); 51 | return { 52 | content: [{ 53 | type: "text" as const, 54 | text: "No content found at the specified URL. Please check the URL and try again." 55 | }] 56 | }; 57 | } 58 | 59 | logger.log(`Successfully crawled content from URL`); 60 | 61 | const result = { 62 | content: [{ 63 | type: "text" as const, 64 | text: JSON.stringify(response.data, null, 2) 65 | }] 66 | }; 67 | 68 | logger.complete(); 69 | return result; 70 | } catch (error) { 71 | logger.error(error); 72 | 73 | if (axios.isAxiosError(error)) { 74 | // Handle Axios errors specifically 75 | const statusCode = error.response?.status || 'unknown'; 76 | const errorMessage = error.response?.data?.message || error.message; 77 | 78 | logger.log(`Axios error (${statusCode}): ${errorMessage}`); 79 | return { 80 | content: [{ 81 | type: "text" as const, 82 | text: `Crawling error (${statusCode}): ${errorMessage}` 83 | }], 84 | isError: true, 85 | }; 86 | } 87 | 88 | // Handle generic errors 89 | return { 90 | content: [{ 91 | type: "text" as const, 92 | text: `Crawling error: ${error instanceof Error ? error.message : String(error)}` 93 | }], 94 | isError: true, 95 | }; 96 | } 97 | }, 98 | enabled: false // Disabled by default 99 | }; -------------------------------------------------------------------------------- /src/tools/index.ts: -------------------------------------------------------------------------------- 1 | // Export the tool registry 2 | export { toolRegistry, API_CONFIG } from "./config.js"; 3 | 4 | // Import all tools to register them 5 | import "./webSearch.js"; 6 | import "./researchPaperSearch.js"; 7 | import "./twitterSearch.js"; 8 | import "./companyResearch.js"; 9 | import "./crawling.js"; 10 | import "./competitorFinder.js"; 11 | 12 | // When adding a new tool, import it here 13 | // import "./newTool.js"; -------------------------------------------------------------------------------- /src/tools/researchPaperSearch.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import axios from "axios"; 3 | import { toolRegistry, API_CONFIG } from "./config.js"; 4 | import { ExaSearchRequest, ExaSearchResponse } from "../types.js"; 5 | import { createRequestLogger } from "../utils/logger.js"; 6 | 7 | // Register the research paper search tool 8 | toolRegistry["research_paper_search"] = { 9 | name: "research_paper_search", 10 | description: "Search across 100M+ research papers with full text access using Exa AI - performs targeted academic paper searches with deep research content coverage. Returns detailed information about relevant academic papers including titles, authors, publication dates, and full text excerpts. Control the number of results and character counts returned to balance comprehensiveness with conciseness based on your task requirements.", 11 | schema: { 12 | query: z.string().describe("Research topic or keyword to search for"), 13 | numResults: z.number().optional().describe("Number of research papers to return (default: 5)"), 14 | maxCharacters: z.number().optional().describe("Maximum number of characters to return for each result's text content (Default: 3000)") 15 | }, 16 | handler: async ({ query, numResults, maxCharacters }, extra) => { 17 | const requestId = `research_paper-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`; 18 | const logger = createRequestLogger(requestId, 'research_paper_search'); 19 | 20 | logger.start(query); 21 | 22 | try { 23 | // Create a fresh axios instance for each request 24 | const axiosInstance = axios.create({ 25 | baseURL: API_CONFIG.BASE_URL, 26 | headers: { 27 | 'accept': 'application/json', 28 | 'content-type': 'application/json', 29 | 'x-api-key': process.env.EXA_API_KEY || '' 30 | }, 31 | timeout: 25000 32 | }); 33 | 34 | const searchRequest: ExaSearchRequest = { 35 | query, 36 | category: "research paper", 37 | type: "auto", 38 | numResults: numResults || API_CONFIG.DEFAULT_NUM_RESULTS, 39 | contents: { 40 | text: { 41 | maxCharacters: maxCharacters || API_CONFIG.DEFAULT_MAX_CHARACTERS 42 | }, 43 | livecrawl: 'fallback' 44 | } 45 | }; 46 | 47 | logger.log("Sending research paper request to Exa API"); 48 | 49 | const response = await axiosInstance.post( 50 | API_CONFIG.ENDPOINTS.SEARCH, 51 | searchRequest, 52 | { timeout: 25000 } 53 | ); 54 | 55 | logger.log("Received research paper response from Exa API"); 56 | 57 | if (!response.data || !response.data.results) { 58 | logger.log("Warning: Empty or invalid response from Exa API for research papers"); 59 | return { 60 | content: [{ 61 | type: "text" as const, 62 | text: "No research papers found. Please try a different query." 63 | }] 64 | }; 65 | } 66 | 67 | logger.log(`Found ${response.data.results.length} research papers`); 68 | 69 | const result = { 70 | content: [{ 71 | type: "text" as const, 72 | text: JSON.stringify(response.data, null, 2) 73 | }] 74 | }; 75 | 76 | logger.complete(); 77 | return result; 78 | } catch (error) { 79 | logger.error(error); 80 | 81 | if (axios.isAxiosError(error)) { 82 | // Handle Axios errors specifically 83 | const statusCode = error.response?.status || 'unknown'; 84 | const errorMessage = error.response?.data?.message || error.message; 85 | 86 | logger.log(`Axios error (${statusCode}): ${errorMessage}`); 87 | return { 88 | content: [{ 89 | type: "text" as const, 90 | text: `Research paper search error (${statusCode}): ${errorMessage}` 91 | }], 92 | isError: true, 93 | }; 94 | } 95 | 96 | // Handle generic errors 97 | return { 98 | content: [{ 99 | type: "text" as const, 100 | text: `Research paper search error: ${error instanceof Error ? error.message : String(error)}` 101 | }], 102 | isError: true, 103 | }; 104 | } 105 | }, 106 | enabled: false // disabled by default 107 | }; -------------------------------------------------------------------------------- /src/tools/twitterSearch.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import axios from "axios"; 3 | import { toolRegistry, API_CONFIG } from "./config.js"; 4 | import { ExaSearchRequest, ExaSearchResponse } from "../types.js"; 5 | import { createRequestLogger } from "../utils/logger.js"; 6 | 7 | // Register the Twitter search tool 8 | toolRegistry["twitter_search"] = { 9 | name: "twitter_search", 10 | description: "Search Twitter/X.com posts and accounts using Exa AI - performs targeted searches of Twitter (X.com) content including tweets, profiles, and discussions. Returns relevant tweets, profile information, and conversation threads based on your query. You can search for a user by x.com/username or from:username", 11 | schema: { 12 | query: z.string().describe("Twitter username, hashtag, or search term (e.g., 'x.com/username' or search term)"), 13 | numResults: z.number().optional().describe("Number of Twitter results to return (default: 5)"), 14 | startPublishedDate: z.string().optional().describe("Optional ISO date string (e.g., '2023-04-01T00:00:00.000Z') to filter tweets published after this date. Use only when necessary."), 15 | endPublishedDate: z.string().optional().describe("Optional ISO date string (e.g., '2023-04-30T23:59:59.999Z') to filter tweets published before this date. Use only when necessary.") 16 | }, 17 | handler: async ({ query, numResults, startPublishedDate, endPublishedDate }, extra) => { 18 | const requestId = `twitter_search-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`; 19 | const logger = createRequestLogger(requestId, 'twitter_search'); 20 | 21 | logger.start(query); 22 | 23 | try { 24 | // Create a fresh axios instance for each request 25 | const axiosInstance = axios.create({ 26 | baseURL: API_CONFIG.BASE_URL, 27 | headers: { 28 | 'accept': 'application/json', 29 | 'content-type': 'application/json', 30 | 'x-api-key': process.env.EXA_API_KEY || '' 31 | }, 32 | timeout: 25000 33 | }); 34 | 35 | const searchRequest: ExaSearchRequest = { 36 | query, 37 | includeDomains: ["x.com", "twitter.com"], 38 | type: "auto", 39 | numResults: numResults || API_CONFIG.DEFAULT_NUM_RESULTS, 40 | contents: { 41 | text: { 42 | maxCharacters: API_CONFIG.DEFAULT_MAX_CHARACTERS 43 | }, 44 | livecrawl: 'always' 45 | } 46 | }; 47 | 48 | // Add date filters only if they're provided 49 | if (startPublishedDate) { 50 | searchRequest.startPublishedDate = startPublishedDate; 51 | logger.log(`Filtering tweets after: ${startPublishedDate}`); 52 | } 53 | 54 | if (endPublishedDate) { 55 | searchRequest.endPublishedDate = endPublishedDate; 56 | logger.log(`Filtering tweets before: ${endPublishedDate}`); 57 | } 58 | 59 | logger.log("Sending Twitter search request to Exa API"); 60 | 61 | const response = await axiosInstance.post( 62 | API_CONFIG.ENDPOINTS.SEARCH, 63 | searchRequest, 64 | { timeout: 25000 } 65 | ); 66 | 67 | logger.log("Received Twitter search response from Exa API"); 68 | 69 | if (!response.data || !response.data.results) { 70 | logger.log("Warning: Empty or invalid response from Exa API for Twitter search"); 71 | return { 72 | content: [{ 73 | type: "text" as const, 74 | text: "No Twitter results found. Please try a different query." 75 | }] 76 | }; 77 | } 78 | 79 | logger.log(`Found ${response.data.results.length} Twitter results`); 80 | 81 | const result = { 82 | content: [{ 83 | type: "text" as const, 84 | text: JSON.stringify(response.data, null, 2) 85 | }] 86 | }; 87 | 88 | logger.complete(); 89 | return result; 90 | } catch (error) { 91 | logger.error(error); 92 | 93 | if (axios.isAxiosError(error)) { 94 | // Handle Axios errors specifically 95 | const statusCode = error.response?.status || 'unknown'; 96 | const errorMessage = error.response?.data?.message || error.message; 97 | 98 | logger.log(`Axios error (${statusCode}): ${errorMessage}`); 99 | return { 100 | content: [{ 101 | type: "text" as const, 102 | text: `Twitter search error (${statusCode}): ${errorMessage}` 103 | }], 104 | isError: true, 105 | }; 106 | } 107 | 108 | // Handle generic errors 109 | return { 110 | content: [{ 111 | type: "text" as const, 112 | text: `Twitter search error: ${error instanceof Error ? error.message : String(error)}` 113 | }], 114 | isError: true, 115 | }; 116 | } 117 | }, 118 | enabled: false // disabled by default 119 | }; -------------------------------------------------------------------------------- /src/tools/webSearch.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import axios from "axios"; 3 | import { toolRegistry, API_CONFIG } from "./config.js"; 4 | import { ExaSearchRequest, ExaSearchResponse } from "../types.js"; 5 | import { createRequestLogger } from "../utils/logger.js"; 6 | 7 | // Register the web search tool 8 | toolRegistry["web_search"] = { 9 | name: "web_search", 10 | description: "Search the web using Exa AI - performs real-time web searches and can scrape content from specific URLs. Supports configurable result counts and returns the content from the most relevant websites.", 11 | schema: { 12 | query: z.string().describe("Search query"), 13 | numResults: z.number().optional().describe("Number of search results to return (default: 5)") 14 | }, 15 | handler: async ({ query, numResults }, extra) => { 16 | const requestId = `web_search-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`; 17 | const logger = createRequestLogger(requestId, 'web_search'); 18 | 19 | logger.start(query); 20 | 21 | try { 22 | // Create a fresh axios instance for each request 23 | const axiosInstance = axios.create({ 24 | baseURL: API_CONFIG.BASE_URL, 25 | headers: { 26 | 'accept': 'application/json', 27 | 'content-type': 'application/json', 28 | 'x-api-key': process.env.EXA_API_KEY || '' 29 | }, 30 | timeout: 25000 31 | }); 32 | 33 | const searchRequest: ExaSearchRequest = { 34 | query, 35 | type: "auto", 36 | numResults: numResults || API_CONFIG.DEFAULT_NUM_RESULTS, 37 | contents: { 38 | text: { 39 | maxCharacters: API_CONFIG.DEFAULT_MAX_CHARACTERS 40 | }, 41 | livecrawl: 'always' 42 | } 43 | }; 44 | 45 | logger.log("Sending request to Exa API"); 46 | 47 | const response = await axiosInstance.post( 48 | API_CONFIG.ENDPOINTS.SEARCH, 49 | searchRequest, 50 | { timeout: 25000 } 51 | ); 52 | 53 | logger.log("Received response from Exa API"); 54 | 55 | if (!response.data || !response.data.results) { 56 | logger.log("Warning: Empty or invalid response from Exa API"); 57 | return { 58 | content: [{ 59 | type: "text" as const, 60 | text: "No search results found. Please try a different query." 61 | }] 62 | }; 63 | } 64 | 65 | logger.log(`Found ${response.data.results.length} results`); 66 | 67 | const result = { 68 | content: [{ 69 | type: "text" as const, 70 | text: JSON.stringify(response.data, null, 2) 71 | }] 72 | }; 73 | 74 | logger.complete(); 75 | return result; 76 | } catch (error) { 77 | logger.error(error); 78 | 79 | if (axios.isAxiosError(error)) { 80 | // Handle Axios errors specifically 81 | const statusCode = error.response?.status || 'unknown'; 82 | const errorMessage = error.response?.data?.message || error.message; 83 | 84 | logger.log(`Axios error (${statusCode}): ${errorMessage}`); 85 | return { 86 | content: [{ 87 | type: "text" as const, 88 | text: `Search error (${statusCode}): ${errorMessage}` 89 | }], 90 | isError: true, 91 | }; 92 | } 93 | 94 | // Handle generic errors 95 | return { 96 | content: [{ 97 | type: "text" as const, 98 | text: `Search error: ${error instanceof Error ? error.message : String(error)}` 99 | }], 100 | isError: true, 101 | }; 102 | } 103 | }, 104 | enabled: true // Enabled by default 105 | }; -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | // Exa API Types 2 | export interface ExaSearchRequest { 3 | query: string; 4 | type: string; 5 | category?: string; 6 | includeDomains?: string[]; 7 | excludeDomains?: string[]; 8 | startPublishedDate?: string; 9 | endPublishedDate?: string; 10 | numResults: number; 11 | contents: { 12 | text: { 13 | maxCharacters?: number; 14 | } | boolean; 15 | livecrawl?: 'always' | 'fallback'; 16 | subpages?: number; 17 | subpageTarget?: string[]; 18 | }; 19 | } 20 | 21 | export interface ExaCrawlRequest { 22 | ids: string[]; 23 | text: boolean; 24 | livecrawl?: 'always' | 'fallback'; 25 | } 26 | 27 | export interface ExaSearchResult { 28 | id: string; 29 | title: string; 30 | url: string; 31 | publishedDate: string; 32 | author: string; 33 | text: string; 34 | image?: string; 35 | favicon?: string; 36 | score?: number; 37 | } 38 | 39 | export interface ExaSearchResponse { 40 | requestId: string; 41 | autopromptString: string; 42 | resolvedSearchType: string; 43 | results: ExaSearchResult[]; 44 | } 45 | 46 | // Tool Types 47 | export interface SearchArgs { 48 | query: string; 49 | numResults?: number; 50 | livecrawl?: 'always' | 'fallback'; 51 | } -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple logging utility for MCP server 3 | */ 4 | export const log = (message: string): void => { 5 | console.error(`[EXA-MCP-DEBUG] ${message}`); 6 | }; 7 | 8 | export const createRequestLogger = (requestId: string, toolName: string) => { 9 | return { 10 | log: (message: string): void => { 11 | log(`[${requestId}] [${toolName}] ${message}`); 12 | }, 13 | start: (query: string): void => { 14 | log(`[${requestId}] [${toolName}] Starting search for query: "${query}"`); 15 | }, 16 | error: (error: unknown): void => { 17 | log(`[${requestId}] [${toolName}] Error: ${error instanceof Error ? error.message : String(error)}`); 18 | }, 19 | complete: (): void => { 20 | log(`[${requestId}] [${toolName}] Successfully completed request`); 21 | } 22 | }; 23 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "declaration": true, 13 | "declarationMap": true 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules"] 17 | } 18 | --------------------------------------------------------------------------------