├── .gitattributes ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── package-lock.json ├── package.json ├── smithery.yaml ├── src ├── client.ts ├── fetch-capabilities.ts ├── fetch-metamcp.ts ├── fetch-tools.ts ├── index.ts ├── mcp-proxy.ts ├── report-tools.ts ├── sessions.ts ├── sse.ts ├── streamable-http.ts ├── tool-logs.ts └── utils.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | dist/**/* 133 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official uv Debian image as base 2 | FROM ghcr.io/astral-sh/uv:debian 3 | 4 | # Install Node.js and npm 5 | RUN apt-get update && apt-get install -y \ 6 | curl \ 7 | gnupg \ 8 | && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ 9 | && apt-get install -y nodejs \ 10 | && apt-get clean \ 11 | && rm -rf /var/lib/apt/lists/* 12 | 13 | # Verify Node.js and npm installation 14 | RUN node --version && npm --version 15 | 16 | # Verify uv is installed correctly 17 | RUN uv --version 18 | 19 | # Verify npx is available 20 | RUN npx --version || npm install -g npx 21 | 22 | # Set the working directory 23 | WORKDIR /app 24 | 25 | # Copy package files 26 | COPY package*.json ./ 27 | 28 | # Install dependencies 29 | RUN npm ci 30 | 31 | # Copy the rest of the application 32 | COPY . . 33 | 34 | # Build the application 35 | RUN npm run build 36 | 37 | # Set environment variables 38 | ENV NODE_ENV=production 39 | 40 | # Expose the application port 41 | EXPOSE 3000 42 | 43 | # Run the application 44 | ENTRYPOINT ["node", "dist/index.js"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2025] [Jincheng Zhang, MetaMCP, metamcp.com, https://github.com/metatool-ai/metatool-app] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MetaMCP MCP Server 2 | 3 | [https://metamcp.com](https://metamcp.com): The One MCP to manage all your MCPs 4 | 5 | MetaMCP MCP Server is a proxy server that joins multiple MCP⁠ servers into one. It fetches tool/prompt/resource configurations from MetaMCP App⁠ and routes tool/prompt/resource requests to the correct underlying server. 6 | 7 | [![smithery badge](https://smithery.ai/badge/@metatool-ai/mcp-server-metamcp)](https://smithery.ai/server/@metatool-ai/mcp-server-metamcp) 8 | 9 | 10 | MetaServer MCP server 11 | 12 | 13 | MetaMCP App repo: https://github.com/metatool-ai/metatool-app 14 | 15 | ## Installation 16 | 17 | ### Installing via Smithery 18 | 19 | Sometimes Smithery works (confirmed in Windsurf locally) but sometimes it is unstable because MetaMCP is special that it runs other MCPs on top of it. Please consider using manual installation if it doesn't work instead. 20 | 21 | To install MetaMCP MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@metatool-ai/mcp-server-metamcp): 22 | 23 | ```bash 24 | npx -y @smithery/cli install @metatool-ai/mcp-server-metamcp --client claude 25 | ``` 26 | 27 | ### Manual Installation 28 | 29 | ```bash 30 | export METAMCP_API_KEY= 31 | npx -y @metamcp/mcp-server-metamcp@latest 32 | ``` 33 | 34 | ```json 35 | { 36 | "mcpServers": { 37 | "MetaMCP": { 38 | "command": "npx", 39 | "args": ["-y", "@metamcp/mcp-server-metamcp@latest"], 40 | "env": { 41 | "METAMCP_API_KEY": "" 42 | } 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | ## Usage 49 | 50 | ### Using as a stdio server (default) 51 | 52 | ```bash 53 | mcp-server-metamcp --metamcp-api-key 54 | ``` 55 | 56 | ### Using as an SSE server 57 | 58 | ```bash 59 | mcp-server-metamcp --metamcp-api-key --transport sse --port 12006 60 | ``` 61 | 62 | With the SSE transport option, the server will start an Express.js web server that listens for SSE connections on the `/sse` endpoint and accepts messages on the `/messages` endpoint. 63 | 64 | ### Using as a Streamable HTTP server 65 | 66 | ```bash 67 | mcp-server-metamcp --metamcp-api-key --transport streamable-http --port 12006 68 | ``` 69 | 70 | With the Streamable HTTP transport option, the server will start an Express.js web server that handles HTTP requests. You can optionally use `--stateless` mode for stateless operation. 71 | 72 | ### Using with Docker 73 | 74 | When running the server inside a Docker container and connecting to services on the host machine, use the `--use-docker-host` option to automatically transform localhost URLs: 75 | 76 | ```bash 77 | mcp-server-metamcp --metamcp-api-key --transport sse --port 12006 --use-docker-host 78 | ``` 79 | 80 | This will transform any localhost or 127.0.0.1 URLs to `host.docker.internal`, allowing the container to properly connect to services running on the host. 81 | 82 | ### Configuring stderr handling 83 | 84 | For STDIO transport, you can control how stderr is handled from child MCP processes: 85 | 86 | ```bash 87 | # Use inherit to see stderr output from child processes 88 | mcp-server-metamcp --metamcp-api-key --stderr inherit 89 | 90 | # Use pipe to capture stderr (default is ignore) 91 | mcp-server-metamcp --metamcp-api-key --stderr pipe 92 | 93 | # Or set via environment variable 94 | METAMCP_STDERR=inherit mcp-server-metamcp --metamcp-api-key 95 | ``` 96 | 97 | Available stderr options: 98 | - `ignore` (default): Ignore stderr output from child processes 99 | - `inherit`: Pass through stderr from child processes to the parent 100 | - `pipe`: Capture stderr in a pipe for processing 101 | - `overlapped`: Use overlapped I/O (Windows-specific) 102 | 103 | ### Command Line Options 104 | 105 | ``` 106 | Options: 107 | --metamcp-api-key API key for MetaMCP (can also be set via METAMCP_API_KEY env var) 108 | --metamcp-api-base-url Base URL for MetaMCP API (can also be set via METAMCP_API_BASE_URL env var) 109 | --report Fetch all MCPs, initialize clients, and report tools to MetaMCP API 110 | --transport Transport type to use (stdio, sse, or streamable-http) (default: "stdio") 111 | --port Port to use for SSE or Streamable HTTP transport, defaults to 12006 (default: "12006") 112 | --require-api-auth Require API key in SSE or Streamable HTTP URL path 113 | --stateless Use stateless mode for Streamable HTTP transport 114 | --use-docker-host Transform localhost URLs to use host.docker.internal (can also be set via USE_DOCKER_HOST env var) 115 | --stderr Stderr handling for STDIO transport (overlapped, pipe, ignore, inherit) (default: "ignore") 116 | -h, --help display help for command 117 | ``` 118 | 119 | ## Environment Variables 120 | 121 | - `METAMCP_API_KEY`: API key for MetaMCP 122 | - `METAMCP_API_BASE_URL`: Base URL for MetaMCP API 123 | - `USE_DOCKER_HOST`: When set to "true", transforms localhost URLs to host.docker.internal for Docker compatibility 124 | - `METAMCP_STDERR`: Stderr handling for STDIO transport (overlapped, pipe, ignore, inherit). Defaults to "ignore" 125 | 126 | ## Development 127 | 128 | ```bash 129 | # Install dependencies 130 | npm install 131 | 132 | # Build the application 133 | npm run build 134 | 135 | # Watch for changes 136 | npm run watch 137 | ``` 138 | 139 | ## Highlights 140 | 141 | - Compatible with ANY MCP Client 142 | - Multi-Workspaces layer enables you to switch to another set of MCP configs within one-click. 143 | - GUI dynamic updates of MCP configs. 144 | - Namespace isolation for joined MCPs. 145 | 146 | ## Architecture Overview 147 | 148 | ```mermaid 149 | sequenceDiagram 150 | participant MCPClient as MCP Client (e.g. Claude Desktop) 151 | participant MetaMCP-mcp-server as MetaMCP MCP Server 152 | participant MetaMCPApp as MetaMCP App 153 | participant MCPServers as Installed MCP Servers in Metatool App 154 | 155 | MCPClient ->> MetaMCP-mcp-server: Request list tools 156 | MetaMCP-mcp-server ->> MetaMCPApp: Get tools configuration & status 157 | MetaMCPApp ->> MetaMCP-mcp-server: Return tools configuration & status 158 | 159 | loop For each listed MCP Server 160 | MetaMCP-mcp-server ->> MCPServers: Request list_tools 161 | MCPServers ->> MetaMCP-mcp-server: Return list of tools 162 | end 163 | 164 | MetaMCP-mcp-server ->> MetaMCP-mcp-server: Aggregate tool lists 165 | MetaMCP-mcp-server ->> MCPClient: Return aggregated list of tools 166 | 167 | MCPClient ->> MetaMCP-mcp-server: Call tool 168 | MetaMCP-mcp-server ->> MCPServers: call_tool to target MCP Server 169 | MCPServers ->> MetaMCP-mcp-server: Return tool response 170 | MetaMCP-mcp-server ->> MCPClient: Return tool response 171 | ``` 172 | 173 | ## Credits 174 | 175 | - Inspirations and some code (refactored in this project) from https://github.com/adamwattis/mcp-proxy-server/ -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mcp-server: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | ports: 7 | - "3000:3000" 8 | env_file: 9 | - .env.production.local 10 | entrypoint: ["/bin/bash"] 11 | command: ["-c", "uvx --version && echo 'uvx is working!' && tail -f /dev/null"] 12 | healthcheck: 13 | test: ["CMD", "ps", "aux", "|", "grep", "tail"] 14 | interval: 30s 15 | timeout: 10s 16 | retries: 3 17 | environment: 18 | - NODE_ENV=production 19 | restart: unless-stopped 20 | # Add any additional environment variables or command arguments here 21 | # command: --metamcp-api-key your-api-key --metamcp-api-base-url your-base-url 22 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metamcp/mcp-server-metamcp", 3 | "version": "0.6.5", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@metamcp/mcp-server-metamcp", 9 | "version": "0.6.5", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "^1.11.4", 13 | "axios": "^1.7.9", 14 | "commander": "^13.1.0", 15 | "express": "^4.21.2", 16 | "zod": "^3.24.2" 17 | }, 18 | "bin": { 19 | "mcp-server-metamcp": "dist/index.js" 20 | }, 21 | "devDependencies": { 22 | "@types/express": "^5.0.1", 23 | "@types/node": "^22.13.4", 24 | "dotenv-cli": "^8.0.0", 25 | "shx": "^0.3.4", 26 | "typescript": "^5.8.2" 27 | } 28 | }, 29 | "node_modules/@modelcontextprotocol/sdk": { 30 | "version": "1.11.4", 31 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.4.tgz", 32 | "integrity": "sha512-OTbhe5slIjiOtLxXhKalkKGhIQrwvhgCDs/C2r8kcBTy5HR/g43aDQU0l7r8O0VGbJPTNJvDc7ZdQMdQDJXmbw==", 33 | "license": "MIT", 34 | "dependencies": { 35 | "ajv": "^8.17.1", 36 | "content-type": "^1.0.5", 37 | "cors": "^2.8.5", 38 | "cross-spawn": "^7.0.5", 39 | "eventsource": "^3.0.2", 40 | "express": "^5.0.1", 41 | "express-rate-limit": "^7.5.0", 42 | "pkce-challenge": "^5.0.0", 43 | "raw-body": "^3.0.0", 44 | "zod": "^3.23.8", 45 | "zod-to-json-schema": "^3.24.1" 46 | }, 47 | "engines": { 48 | "node": ">=18" 49 | } 50 | }, 51 | "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { 52 | "version": "2.0.0", 53 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 54 | "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 55 | "license": "MIT", 56 | "dependencies": { 57 | "mime-types": "^3.0.0", 58 | "negotiator": "^1.0.0" 59 | }, 60 | "engines": { 61 | "node": ">= 0.6" 62 | } 63 | }, 64 | "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { 65 | "version": "2.2.0", 66 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", 67 | "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", 68 | "license": "MIT", 69 | "dependencies": { 70 | "bytes": "^3.1.2", 71 | "content-type": "^1.0.5", 72 | "debug": "^4.4.0", 73 | "http-errors": "^2.0.0", 74 | "iconv-lite": "^0.6.3", 75 | "on-finished": "^2.4.1", 76 | "qs": "^6.14.0", 77 | "raw-body": "^3.0.0", 78 | "type-is": "^2.0.0" 79 | }, 80 | "engines": { 81 | "node": ">=18" 82 | } 83 | }, 84 | "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { 85 | "version": "1.0.0", 86 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", 87 | "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", 88 | "license": "MIT", 89 | "dependencies": { 90 | "safe-buffer": "5.2.1" 91 | }, 92 | "engines": { 93 | "node": ">= 0.6" 94 | } 95 | }, 96 | "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { 97 | "version": "1.2.2", 98 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 99 | "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 100 | "license": "MIT", 101 | "engines": { 102 | "node": ">=6.6.0" 103 | } 104 | }, 105 | "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { 106 | "version": "4.4.0", 107 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 108 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 109 | "license": "MIT", 110 | "dependencies": { 111 | "ms": "^2.1.3" 112 | }, 113 | "engines": { 114 | "node": ">=6.0" 115 | }, 116 | "peerDependenciesMeta": { 117 | "supports-color": { 118 | "optional": true 119 | } 120 | } 121 | }, 122 | "node_modules/@modelcontextprotocol/sdk/node_modules/express": { 123 | "version": "5.1.0", 124 | "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", 125 | "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", 126 | "license": "MIT", 127 | "dependencies": { 128 | "accepts": "^2.0.0", 129 | "body-parser": "^2.2.0", 130 | "content-disposition": "^1.0.0", 131 | "content-type": "^1.0.5", 132 | "cookie": "^0.7.1", 133 | "cookie-signature": "^1.2.1", 134 | "debug": "^4.4.0", 135 | "encodeurl": "^2.0.0", 136 | "escape-html": "^1.0.3", 137 | "etag": "^1.8.1", 138 | "finalhandler": "^2.1.0", 139 | "fresh": "^2.0.0", 140 | "http-errors": "^2.0.0", 141 | "merge-descriptors": "^2.0.0", 142 | "mime-types": "^3.0.0", 143 | "on-finished": "^2.4.1", 144 | "once": "^1.4.0", 145 | "parseurl": "^1.3.3", 146 | "proxy-addr": "^2.0.7", 147 | "qs": "^6.14.0", 148 | "range-parser": "^1.2.1", 149 | "router": "^2.2.0", 150 | "send": "^1.1.0", 151 | "serve-static": "^2.2.0", 152 | "statuses": "^2.0.1", 153 | "type-is": "^2.0.1", 154 | "vary": "^1.1.2" 155 | }, 156 | "engines": { 157 | "node": ">= 18" 158 | }, 159 | "funding": { 160 | "type": "opencollective", 161 | "url": "https://opencollective.com/express" 162 | } 163 | }, 164 | "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { 165 | "version": "2.1.0", 166 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", 167 | "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 168 | "license": "MIT", 169 | "dependencies": { 170 | "debug": "^4.4.0", 171 | "encodeurl": "^2.0.0", 172 | "escape-html": "^1.0.3", 173 | "on-finished": "^2.4.1", 174 | "parseurl": "^1.3.3", 175 | "statuses": "^2.0.1" 176 | }, 177 | "engines": { 178 | "node": ">= 0.8" 179 | } 180 | }, 181 | "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { 182 | "version": "2.0.0", 183 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 184 | "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 185 | "license": "MIT", 186 | "engines": { 187 | "node": ">= 0.8" 188 | } 189 | }, 190 | "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { 191 | "version": "0.6.3", 192 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 193 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 194 | "license": "MIT", 195 | "dependencies": { 196 | "safer-buffer": ">= 2.1.2 < 3.0.0" 197 | }, 198 | "engines": { 199 | "node": ">=0.10.0" 200 | } 201 | }, 202 | "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { 203 | "version": "1.1.0", 204 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 205 | "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 206 | "license": "MIT", 207 | "engines": { 208 | "node": ">= 0.8" 209 | } 210 | }, 211 | "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { 212 | "version": "2.0.0", 213 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 214 | "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 215 | "license": "MIT", 216 | "engines": { 217 | "node": ">=18" 218 | }, 219 | "funding": { 220 | "url": "https://github.com/sponsors/sindresorhus" 221 | } 222 | }, 223 | "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { 224 | "version": "1.54.0", 225 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 226 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 227 | "license": "MIT", 228 | "engines": { 229 | "node": ">= 0.6" 230 | } 231 | }, 232 | "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { 233 | "version": "3.0.1", 234 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", 235 | "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 236 | "license": "MIT", 237 | "dependencies": { 238 | "mime-db": "^1.54.0" 239 | }, 240 | "engines": { 241 | "node": ">= 0.6" 242 | } 243 | }, 244 | "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { 245 | "version": "2.1.3", 246 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 247 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 248 | "license": "MIT" 249 | }, 250 | "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { 251 | "version": "1.0.0", 252 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 253 | "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 254 | "license": "MIT", 255 | "engines": { 256 | "node": ">= 0.6" 257 | } 258 | }, 259 | "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { 260 | "version": "6.14.0", 261 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 262 | "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 263 | "license": "BSD-3-Clause", 264 | "dependencies": { 265 | "side-channel": "^1.1.0" 266 | }, 267 | "engines": { 268 | "node": ">=0.6" 269 | }, 270 | "funding": { 271 | "url": "https://github.com/sponsors/ljharb" 272 | } 273 | }, 274 | "node_modules/@modelcontextprotocol/sdk/node_modules/send": { 275 | "version": "1.2.0", 276 | "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", 277 | "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", 278 | "license": "MIT", 279 | "dependencies": { 280 | "debug": "^4.3.5", 281 | "encodeurl": "^2.0.0", 282 | "escape-html": "^1.0.3", 283 | "etag": "^1.8.1", 284 | "fresh": "^2.0.0", 285 | "http-errors": "^2.0.0", 286 | "mime-types": "^3.0.1", 287 | "ms": "^2.1.3", 288 | "on-finished": "^2.4.1", 289 | "range-parser": "^1.2.1", 290 | "statuses": "^2.0.1" 291 | }, 292 | "engines": { 293 | "node": ">= 18" 294 | } 295 | }, 296 | "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { 297 | "version": "2.2.0", 298 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", 299 | "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", 300 | "license": "MIT", 301 | "dependencies": { 302 | "encodeurl": "^2.0.0", 303 | "escape-html": "^1.0.3", 304 | "parseurl": "^1.3.3", 305 | "send": "^1.2.0" 306 | }, 307 | "engines": { 308 | "node": ">= 18" 309 | } 310 | }, 311 | "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { 312 | "version": "2.0.1", 313 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", 314 | "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", 315 | "license": "MIT", 316 | "dependencies": { 317 | "content-type": "^1.0.5", 318 | "media-typer": "^1.1.0", 319 | "mime-types": "^3.0.0" 320 | }, 321 | "engines": { 322 | "node": ">= 0.6" 323 | } 324 | }, 325 | "node_modules/@types/body-parser": { 326 | "version": "1.19.5", 327 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", 328 | "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", 329 | "dev": true, 330 | "license": "MIT", 331 | "dependencies": { 332 | "@types/connect": "*", 333 | "@types/node": "*" 334 | } 335 | }, 336 | "node_modules/@types/connect": { 337 | "version": "3.4.38", 338 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 339 | "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 340 | "dev": true, 341 | "license": "MIT", 342 | "dependencies": { 343 | "@types/node": "*" 344 | } 345 | }, 346 | "node_modules/@types/express": { 347 | "version": "5.0.1", 348 | "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", 349 | "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", 350 | "dev": true, 351 | "license": "MIT", 352 | "dependencies": { 353 | "@types/body-parser": "*", 354 | "@types/express-serve-static-core": "^5.0.0", 355 | "@types/serve-static": "*" 356 | } 357 | }, 358 | "node_modules/@types/express-serve-static-core": { 359 | "version": "5.0.6", 360 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", 361 | "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", 362 | "dev": true, 363 | "license": "MIT", 364 | "dependencies": { 365 | "@types/node": "*", 366 | "@types/qs": "*", 367 | "@types/range-parser": "*", 368 | "@types/send": "*" 369 | } 370 | }, 371 | "node_modules/@types/http-errors": { 372 | "version": "2.0.4", 373 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", 374 | "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", 375 | "dev": true, 376 | "license": "MIT" 377 | }, 378 | "node_modules/@types/mime": { 379 | "version": "1.3.5", 380 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", 381 | "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", 382 | "dev": true, 383 | "license": "MIT" 384 | }, 385 | "node_modules/@types/node": { 386 | "version": "22.14.0", 387 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", 388 | "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", 389 | "dev": true, 390 | "license": "MIT", 391 | "dependencies": { 392 | "undici-types": "~6.21.0" 393 | } 394 | }, 395 | "node_modules/@types/qs": { 396 | "version": "6.9.18", 397 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", 398 | "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", 399 | "dev": true, 400 | "license": "MIT" 401 | }, 402 | "node_modules/@types/range-parser": { 403 | "version": "1.2.7", 404 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", 405 | "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", 406 | "dev": true, 407 | "license": "MIT" 408 | }, 409 | "node_modules/@types/send": { 410 | "version": "0.17.4", 411 | "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", 412 | "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", 413 | "dev": true, 414 | "license": "MIT", 415 | "dependencies": { 416 | "@types/mime": "^1", 417 | "@types/node": "*" 418 | } 419 | }, 420 | "node_modules/@types/serve-static": { 421 | "version": "1.15.7", 422 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", 423 | "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", 424 | "dev": true, 425 | "license": "MIT", 426 | "dependencies": { 427 | "@types/http-errors": "*", 428 | "@types/node": "*", 429 | "@types/send": "*" 430 | } 431 | }, 432 | "node_modules/accepts": { 433 | "version": "1.3.8", 434 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 435 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 436 | "license": "MIT", 437 | "dependencies": { 438 | "mime-types": "~2.1.34", 439 | "negotiator": "0.6.3" 440 | }, 441 | "engines": { 442 | "node": ">= 0.6" 443 | } 444 | }, 445 | "node_modules/ajv": { 446 | "version": "8.17.1", 447 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", 448 | "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", 449 | "license": "MIT", 450 | "dependencies": { 451 | "fast-deep-equal": "^3.1.3", 452 | "fast-uri": "^3.0.1", 453 | "json-schema-traverse": "^1.0.0", 454 | "require-from-string": "^2.0.2" 455 | }, 456 | "funding": { 457 | "type": "github", 458 | "url": "https://github.com/sponsors/epoberezkin" 459 | } 460 | }, 461 | "node_modules/array-flatten": { 462 | "version": "1.1.1", 463 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 464 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", 465 | "license": "MIT" 466 | }, 467 | "node_modules/asynckit": { 468 | "version": "0.4.0", 469 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 470 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 471 | "license": "MIT" 472 | }, 473 | "node_modules/axios": { 474 | "version": "1.8.4", 475 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", 476 | "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", 477 | "license": "MIT", 478 | "dependencies": { 479 | "follow-redirects": "^1.15.6", 480 | "form-data": "^4.0.0", 481 | "proxy-from-env": "^1.1.0" 482 | } 483 | }, 484 | "node_modules/balanced-match": { 485 | "version": "1.0.2", 486 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 487 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 488 | "dev": true, 489 | "license": "MIT" 490 | }, 491 | "node_modules/body-parser": { 492 | "version": "1.20.3", 493 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", 494 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 495 | "license": "MIT", 496 | "dependencies": { 497 | "bytes": "3.1.2", 498 | "content-type": "~1.0.5", 499 | "debug": "2.6.9", 500 | "depd": "2.0.0", 501 | "destroy": "1.2.0", 502 | "http-errors": "2.0.0", 503 | "iconv-lite": "0.4.24", 504 | "on-finished": "2.4.1", 505 | "qs": "6.13.0", 506 | "raw-body": "2.5.2", 507 | "type-is": "~1.6.18", 508 | "unpipe": "1.0.0" 509 | }, 510 | "engines": { 511 | "node": ">= 0.8", 512 | "npm": "1.2.8000 || >= 1.4.16" 513 | } 514 | }, 515 | "node_modules/body-parser/node_modules/raw-body": { 516 | "version": "2.5.2", 517 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 518 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 519 | "license": "MIT", 520 | "dependencies": { 521 | "bytes": "3.1.2", 522 | "http-errors": "2.0.0", 523 | "iconv-lite": "0.4.24", 524 | "unpipe": "1.0.0" 525 | }, 526 | "engines": { 527 | "node": ">= 0.8" 528 | } 529 | }, 530 | "node_modules/brace-expansion": { 531 | "version": "1.1.11", 532 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 533 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 534 | "dev": true, 535 | "license": "MIT", 536 | "dependencies": { 537 | "balanced-match": "^1.0.0", 538 | "concat-map": "0.0.1" 539 | } 540 | }, 541 | "node_modules/bytes": { 542 | "version": "3.1.2", 543 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 544 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 545 | "license": "MIT", 546 | "engines": { 547 | "node": ">= 0.8" 548 | } 549 | }, 550 | "node_modules/call-bind-apply-helpers": { 551 | "version": "1.0.2", 552 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 553 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 554 | "license": "MIT", 555 | "dependencies": { 556 | "es-errors": "^1.3.0", 557 | "function-bind": "^1.1.2" 558 | }, 559 | "engines": { 560 | "node": ">= 0.4" 561 | } 562 | }, 563 | "node_modules/call-bound": { 564 | "version": "1.0.4", 565 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 566 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 567 | "license": "MIT", 568 | "dependencies": { 569 | "call-bind-apply-helpers": "^1.0.2", 570 | "get-intrinsic": "^1.3.0" 571 | }, 572 | "engines": { 573 | "node": ">= 0.4" 574 | }, 575 | "funding": { 576 | "url": "https://github.com/sponsors/ljharb" 577 | } 578 | }, 579 | "node_modules/combined-stream": { 580 | "version": "1.0.8", 581 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 582 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 583 | "license": "MIT", 584 | "dependencies": { 585 | "delayed-stream": "~1.0.0" 586 | }, 587 | "engines": { 588 | "node": ">= 0.8" 589 | } 590 | }, 591 | "node_modules/commander": { 592 | "version": "13.1.0", 593 | "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", 594 | "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", 595 | "license": "MIT", 596 | "engines": { 597 | "node": ">=18" 598 | } 599 | }, 600 | "node_modules/concat-map": { 601 | "version": "0.0.1", 602 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 603 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 604 | "dev": true, 605 | "license": "MIT" 606 | }, 607 | "node_modules/content-disposition": { 608 | "version": "0.5.4", 609 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 610 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 611 | "license": "MIT", 612 | "dependencies": { 613 | "safe-buffer": "5.2.1" 614 | }, 615 | "engines": { 616 | "node": ">= 0.6" 617 | } 618 | }, 619 | "node_modules/content-type": { 620 | "version": "1.0.5", 621 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 622 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 623 | "license": "MIT", 624 | "engines": { 625 | "node": ">= 0.6" 626 | } 627 | }, 628 | "node_modules/cookie": { 629 | "version": "0.7.1", 630 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 631 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 632 | "license": "MIT", 633 | "engines": { 634 | "node": ">= 0.6" 635 | } 636 | }, 637 | "node_modules/cookie-signature": { 638 | "version": "1.0.6", 639 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 640 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", 641 | "license": "MIT" 642 | }, 643 | "node_modules/cors": { 644 | "version": "2.8.5", 645 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 646 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 647 | "license": "MIT", 648 | "dependencies": { 649 | "object-assign": "^4", 650 | "vary": "^1" 651 | }, 652 | "engines": { 653 | "node": ">= 0.10" 654 | } 655 | }, 656 | "node_modules/cross-spawn": { 657 | "version": "7.0.6", 658 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 659 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 660 | "license": "MIT", 661 | "dependencies": { 662 | "path-key": "^3.1.0", 663 | "shebang-command": "^2.0.0", 664 | "which": "^2.0.1" 665 | }, 666 | "engines": { 667 | "node": ">= 8" 668 | } 669 | }, 670 | "node_modules/debug": { 671 | "version": "2.6.9", 672 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 673 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 674 | "license": "MIT", 675 | "dependencies": { 676 | "ms": "2.0.0" 677 | } 678 | }, 679 | "node_modules/delayed-stream": { 680 | "version": "1.0.0", 681 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 682 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 683 | "license": "MIT", 684 | "engines": { 685 | "node": ">=0.4.0" 686 | } 687 | }, 688 | "node_modules/depd": { 689 | "version": "2.0.0", 690 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 691 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 692 | "license": "MIT", 693 | "engines": { 694 | "node": ">= 0.8" 695 | } 696 | }, 697 | "node_modules/destroy": { 698 | "version": "1.2.0", 699 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 700 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 701 | "license": "MIT", 702 | "engines": { 703 | "node": ">= 0.8", 704 | "npm": "1.2.8000 || >= 1.4.16" 705 | } 706 | }, 707 | "node_modules/dotenv": { 708 | "version": "16.4.7", 709 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", 710 | "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", 711 | "dev": true, 712 | "license": "BSD-2-Clause", 713 | "engines": { 714 | "node": ">=12" 715 | }, 716 | "funding": { 717 | "url": "https://dotenvx.com" 718 | } 719 | }, 720 | "node_modules/dotenv-cli": { 721 | "version": "8.0.0", 722 | "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-8.0.0.tgz", 723 | "integrity": "sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw==", 724 | "dev": true, 725 | "license": "MIT", 726 | "dependencies": { 727 | "cross-spawn": "^7.0.6", 728 | "dotenv": "^16.3.0", 729 | "dotenv-expand": "^10.0.0", 730 | "minimist": "^1.2.6" 731 | }, 732 | "bin": { 733 | "dotenv": "cli.js" 734 | } 735 | }, 736 | "node_modules/dotenv-expand": { 737 | "version": "10.0.0", 738 | "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", 739 | "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", 740 | "dev": true, 741 | "license": "BSD-2-Clause", 742 | "engines": { 743 | "node": ">=12" 744 | } 745 | }, 746 | "node_modules/dunder-proto": { 747 | "version": "1.0.1", 748 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 749 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 750 | "license": "MIT", 751 | "dependencies": { 752 | "call-bind-apply-helpers": "^1.0.1", 753 | "es-errors": "^1.3.0", 754 | "gopd": "^1.2.0" 755 | }, 756 | "engines": { 757 | "node": ">= 0.4" 758 | } 759 | }, 760 | "node_modules/ee-first": { 761 | "version": "1.1.1", 762 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 763 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 764 | "license": "MIT" 765 | }, 766 | "node_modules/encodeurl": { 767 | "version": "2.0.0", 768 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 769 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 770 | "license": "MIT", 771 | "engines": { 772 | "node": ">= 0.8" 773 | } 774 | }, 775 | "node_modules/es-define-property": { 776 | "version": "1.0.1", 777 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 778 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 779 | "license": "MIT", 780 | "engines": { 781 | "node": ">= 0.4" 782 | } 783 | }, 784 | "node_modules/es-errors": { 785 | "version": "1.3.0", 786 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 787 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 788 | "license": "MIT", 789 | "engines": { 790 | "node": ">= 0.4" 791 | } 792 | }, 793 | "node_modules/es-object-atoms": { 794 | "version": "1.1.1", 795 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 796 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 797 | "license": "MIT", 798 | "dependencies": { 799 | "es-errors": "^1.3.0" 800 | }, 801 | "engines": { 802 | "node": ">= 0.4" 803 | } 804 | }, 805 | "node_modules/es-set-tostringtag": { 806 | "version": "2.1.0", 807 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", 808 | "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", 809 | "license": "MIT", 810 | "dependencies": { 811 | "es-errors": "^1.3.0", 812 | "get-intrinsic": "^1.2.6", 813 | "has-tostringtag": "^1.0.2", 814 | "hasown": "^2.0.2" 815 | }, 816 | "engines": { 817 | "node": ">= 0.4" 818 | } 819 | }, 820 | "node_modules/escape-html": { 821 | "version": "1.0.3", 822 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 823 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 824 | "license": "MIT" 825 | }, 826 | "node_modules/etag": { 827 | "version": "1.8.1", 828 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 829 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 830 | "license": "MIT", 831 | "engines": { 832 | "node": ">= 0.6" 833 | } 834 | }, 835 | "node_modules/eventsource": { 836 | "version": "3.0.6", 837 | "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", 838 | "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==", 839 | "license": "MIT", 840 | "dependencies": { 841 | "eventsource-parser": "^3.0.1" 842 | }, 843 | "engines": { 844 | "node": ">=18.0.0" 845 | } 846 | }, 847 | "node_modules/eventsource-parser": { 848 | "version": "3.0.1", 849 | "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", 850 | "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", 851 | "license": "MIT", 852 | "engines": { 853 | "node": ">=18.0.0" 854 | } 855 | }, 856 | "node_modules/express": { 857 | "version": "4.21.2", 858 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", 859 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 860 | "license": "MIT", 861 | "dependencies": { 862 | "accepts": "~1.3.8", 863 | "array-flatten": "1.1.1", 864 | "body-parser": "1.20.3", 865 | "content-disposition": "0.5.4", 866 | "content-type": "~1.0.4", 867 | "cookie": "0.7.1", 868 | "cookie-signature": "1.0.6", 869 | "debug": "2.6.9", 870 | "depd": "2.0.0", 871 | "encodeurl": "~2.0.0", 872 | "escape-html": "~1.0.3", 873 | "etag": "~1.8.1", 874 | "finalhandler": "1.3.1", 875 | "fresh": "0.5.2", 876 | "http-errors": "2.0.0", 877 | "merge-descriptors": "1.0.3", 878 | "methods": "~1.1.2", 879 | "on-finished": "2.4.1", 880 | "parseurl": "~1.3.3", 881 | "path-to-regexp": "0.1.12", 882 | "proxy-addr": "~2.0.7", 883 | "qs": "6.13.0", 884 | "range-parser": "~1.2.1", 885 | "safe-buffer": "5.2.1", 886 | "send": "0.19.0", 887 | "serve-static": "1.16.2", 888 | "setprototypeof": "1.2.0", 889 | "statuses": "2.0.1", 890 | "type-is": "~1.6.18", 891 | "utils-merge": "1.0.1", 892 | "vary": "~1.1.2" 893 | }, 894 | "engines": { 895 | "node": ">= 0.10.0" 896 | }, 897 | "funding": { 898 | "type": "opencollective", 899 | "url": "https://opencollective.com/express" 900 | } 901 | }, 902 | "node_modules/express-rate-limit": { 903 | "version": "7.5.0", 904 | "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", 905 | "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", 906 | "license": "MIT", 907 | "engines": { 908 | "node": ">= 16" 909 | }, 910 | "funding": { 911 | "url": "https://github.com/sponsors/express-rate-limit" 912 | }, 913 | "peerDependencies": { 914 | "express": "^4.11 || 5 || ^5.0.0-beta.1" 915 | } 916 | }, 917 | "node_modules/fast-deep-equal": { 918 | "version": "3.1.3", 919 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 920 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 921 | "license": "MIT" 922 | }, 923 | "node_modules/fast-uri": { 924 | "version": "3.0.6", 925 | "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", 926 | "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", 927 | "funding": [ 928 | { 929 | "type": "github", 930 | "url": "https://github.com/sponsors/fastify" 931 | }, 932 | { 933 | "type": "opencollective", 934 | "url": "https://opencollective.com/fastify" 935 | } 936 | ], 937 | "license": "BSD-3-Clause" 938 | }, 939 | "node_modules/finalhandler": { 940 | "version": "1.3.1", 941 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", 942 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 943 | "license": "MIT", 944 | "dependencies": { 945 | "debug": "2.6.9", 946 | "encodeurl": "~2.0.0", 947 | "escape-html": "~1.0.3", 948 | "on-finished": "2.4.1", 949 | "parseurl": "~1.3.3", 950 | "statuses": "2.0.1", 951 | "unpipe": "~1.0.0" 952 | }, 953 | "engines": { 954 | "node": ">= 0.8" 955 | } 956 | }, 957 | "node_modules/follow-redirects": { 958 | "version": "1.15.9", 959 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", 960 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 961 | "funding": [ 962 | { 963 | "type": "individual", 964 | "url": "https://github.com/sponsors/RubenVerborgh" 965 | } 966 | ], 967 | "license": "MIT", 968 | "engines": { 969 | "node": ">=4.0" 970 | }, 971 | "peerDependenciesMeta": { 972 | "debug": { 973 | "optional": true 974 | } 975 | } 976 | }, 977 | "node_modules/form-data": { 978 | "version": "4.0.2", 979 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", 980 | "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", 981 | "license": "MIT", 982 | "dependencies": { 983 | "asynckit": "^0.4.0", 984 | "combined-stream": "^1.0.8", 985 | "es-set-tostringtag": "^2.1.0", 986 | "mime-types": "^2.1.12" 987 | }, 988 | "engines": { 989 | "node": ">= 6" 990 | } 991 | }, 992 | "node_modules/forwarded": { 993 | "version": "0.2.0", 994 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 995 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 996 | "license": "MIT", 997 | "engines": { 998 | "node": ">= 0.6" 999 | } 1000 | }, 1001 | "node_modules/fresh": { 1002 | "version": "0.5.2", 1003 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1004 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 1005 | "license": "MIT", 1006 | "engines": { 1007 | "node": ">= 0.6" 1008 | } 1009 | }, 1010 | "node_modules/fs.realpath": { 1011 | "version": "1.0.0", 1012 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1013 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 1014 | "dev": true, 1015 | "license": "ISC" 1016 | }, 1017 | "node_modules/function-bind": { 1018 | "version": "1.1.2", 1019 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 1020 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 1021 | "license": "MIT", 1022 | "funding": { 1023 | "url": "https://github.com/sponsors/ljharb" 1024 | } 1025 | }, 1026 | "node_modules/get-intrinsic": { 1027 | "version": "1.3.0", 1028 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 1029 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 1030 | "license": "MIT", 1031 | "dependencies": { 1032 | "call-bind-apply-helpers": "^1.0.2", 1033 | "es-define-property": "^1.0.1", 1034 | "es-errors": "^1.3.0", 1035 | "es-object-atoms": "^1.1.1", 1036 | "function-bind": "^1.1.2", 1037 | "get-proto": "^1.0.1", 1038 | "gopd": "^1.2.0", 1039 | "has-symbols": "^1.1.0", 1040 | "hasown": "^2.0.2", 1041 | "math-intrinsics": "^1.1.0" 1042 | }, 1043 | "engines": { 1044 | "node": ">= 0.4" 1045 | }, 1046 | "funding": { 1047 | "url": "https://github.com/sponsors/ljharb" 1048 | } 1049 | }, 1050 | "node_modules/get-proto": { 1051 | "version": "1.0.1", 1052 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 1053 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 1054 | "license": "MIT", 1055 | "dependencies": { 1056 | "dunder-proto": "^1.0.1", 1057 | "es-object-atoms": "^1.0.0" 1058 | }, 1059 | "engines": { 1060 | "node": ">= 0.4" 1061 | } 1062 | }, 1063 | "node_modules/glob": { 1064 | "version": "7.2.3", 1065 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 1066 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 1067 | "deprecated": "Glob versions prior to v9 are no longer supported", 1068 | "dev": true, 1069 | "license": "ISC", 1070 | "dependencies": { 1071 | "fs.realpath": "^1.0.0", 1072 | "inflight": "^1.0.4", 1073 | "inherits": "2", 1074 | "minimatch": "^3.1.1", 1075 | "once": "^1.3.0", 1076 | "path-is-absolute": "^1.0.0" 1077 | }, 1078 | "engines": { 1079 | "node": "*" 1080 | }, 1081 | "funding": { 1082 | "url": "https://github.com/sponsors/isaacs" 1083 | } 1084 | }, 1085 | "node_modules/gopd": { 1086 | "version": "1.2.0", 1087 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 1088 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 1089 | "license": "MIT", 1090 | "engines": { 1091 | "node": ">= 0.4" 1092 | }, 1093 | "funding": { 1094 | "url": "https://github.com/sponsors/ljharb" 1095 | } 1096 | }, 1097 | "node_modules/has-symbols": { 1098 | "version": "1.1.0", 1099 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 1100 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 1101 | "license": "MIT", 1102 | "engines": { 1103 | "node": ">= 0.4" 1104 | }, 1105 | "funding": { 1106 | "url": "https://github.com/sponsors/ljharb" 1107 | } 1108 | }, 1109 | "node_modules/has-tostringtag": { 1110 | "version": "1.0.2", 1111 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", 1112 | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 1113 | "license": "MIT", 1114 | "dependencies": { 1115 | "has-symbols": "^1.0.3" 1116 | }, 1117 | "engines": { 1118 | "node": ">= 0.4" 1119 | }, 1120 | "funding": { 1121 | "url": "https://github.com/sponsors/ljharb" 1122 | } 1123 | }, 1124 | "node_modules/hasown": { 1125 | "version": "2.0.2", 1126 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 1127 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1128 | "license": "MIT", 1129 | "dependencies": { 1130 | "function-bind": "^1.1.2" 1131 | }, 1132 | "engines": { 1133 | "node": ">= 0.4" 1134 | } 1135 | }, 1136 | "node_modules/http-errors": { 1137 | "version": "2.0.0", 1138 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 1139 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1140 | "license": "MIT", 1141 | "dependencies": { 1142 | "depd": "2.0.0", 1143 | "inherits": "2.0.4", 1144 | "setprototypeof": "1.2.0", 1145 | "statuses": "2.0.1", 1146 | "toidentifier": "1.0.1" 1147 | }, 1148 | "engines": { 1149 | "node": ">= 0.8" 1150 | } 1151 | }, 1152 | "node_modules/iconv-lite": { 1153 | "version": "0.4.24", 1154 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1155 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1156 | "license": "MIT", 1157 | "dependencies": { 1158 | "safer-buffer": ">= 2.1.2 < 3" 1159 | }, 1160 | "engines": { 1161 | "node": ">=0.10.0" 1162 | } 1163 | }, 1164 | "node_modules/inflight": { 1165 | "version": "1.0.6", 1166 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1167 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1168 | "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", 1169 | "dev": true, 1170 | "license": "ISC", 1171 | "dependencies": { 1172 | "once": "^1.3.0", 1173 | "wrappy": "1" 1174 | } 1175 | }, 1176 | "node_modules/inherits": { 1177 | "version": "2.0.4", 1178 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1179 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1180 | "license": "ISC" 1181 | }, 1182 | "node_modules/interpret": { 1183 | "version": "1.4.0", 1184 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", 1185 | "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", 1186 | "dev": true, 1187 | "license": "MIT", 1188 | "engines": { 1189 | "node": ">= 0.10" 1190 | } 1191 | }, 1192 | "node_modules/ipaddr.js": { 1193 | "version": "1.9.1", 1194 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1195 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 1196 | "license": "MIT", 1197 | "engines": { 1198 | "node": ">= 0.10" 1199 | } 1200 | }, 1201 | "node_modules/is-core-module": { 1202 | "version": "2.16.1", 1203 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", 1204 | "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", 1205 | "dev": true, 1206 | "license": "MIT", 1207 | "dependencies": { 1208 | "hasown": "^2.0.2" 1209 | }, 1210 | "engines": { 1211 | "node": ">= 0.4" 1212 | }, 1213 | "funding": { 1214 | "url": "https://github.com/sponsors/ljharb" 1215 | } 1216 | }, 1217 | "node_modules/is-promise": { 1218 | "version": "4.0.0", 1219 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 1220 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", 1221 | "license": "MIT" 1222 | }, 1223 | "node_modules/isexe": { 1224 | "version": "2.0.0", 1225 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1226 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1227 | "license": "ISC" 1228 | }, 1229 | "node_modules/json-schema-traverse": { 1230 | "version": "1.0.0", 1231 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 1232 | "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", 1233 | "license": "MIT" 1234 | }, 1235 | "node_modules/math-intrinsics": { 1236 | "version": "1.1.0", 1237 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 1238 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 1239 | "license": "MIT", 1240 | "engines": { 1241 | "node": ">= 0.4" 1242 | } 1243 | }, 1244 | "node_modules/media-typer": { 1245 | "version": "0.3.0", 1246 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1247 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 1248 | "license": "MIT", 1249 | "engines": { 1250 | "node": ">= 0.6" 1251 | } 1252 | }, 1253 | "node_modules/merge-descriptors": { 1254 | "version": "1.0.3", 1255 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", 1256 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", 1257 | "license": "MIT", 1258 | "funding": { 1259 | "url": "https://github.com/sponsors/sindresorhus" 1260 | } 1261 | }, 1262 | "node_modules/methods": { 1263 | "version": "1.1.2", 1264 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1265 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 1266 | "license": "MIT", 1267 | "engines": { 1268 | "node": ">= 0.6" 1269 | } 1270 | }, 1271 | "node_modules/mime": { 1272 | "version": "1.6.0", 1273 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1274 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1275 | "license": "MIT", 1276 | "bin": { 1277 | "mime": "cli.js" 1278 | }, 1279 | "engines": { 1280 | "node": ">=4" 1281 | } 1282 | }, 1283 | "node_modules/mime-db": { 1284 | "version": "1.52.0", 1285 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1286 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1287 | "license": "MIT", 1288 | "engines": { 1289 | "node": ">= 0.6" 1290 | } 1291 | }, 1292 | "node_modules/mime-types": { 1293 | "version": "2.1.35", 1294 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1295 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1296 | "license": "MIT", 1297 | "dependencies": { 1298 | "mime-db": "1.52.0" 1299 | }, 1300 | "engines": { 1301 | "node": ">= 0.6" 1302 | } 1303 | }, 1304 | "node_modules/minimatch": { 1305 | "version": "3.1.2", 1306 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1307 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1308 | "dev": true, 1309 | "license": "ISC", 1310 | "dependencies": { 1311 | "brace-expansion": "^1.1.7" 1312 | }, 1313 | "engines": { 1314 | "node": "*" 1315 | } 1316 | }, 1317 | "node_modules/minimist": { 1318 | "version": "1.2.8", 1319 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 1320 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 1321 | "dev": true, 1322 | "license": "MIT", 1323 | "funding": { 1324 | "url": "https://github.com/sponsors/ljharb" 1325 | } 1326 | }, 1327 | "node_modules/ms": { 1328 | "version": "2.0.0", 1329 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1330 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 1331 | "license": "MIT" 1332 | }, 1333 | "node_modules/negotiator": { 1334 | "version": "0.6.3", 1335 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1336 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 1337 | "license": "MIT", 1338 | "engines": { 1339 | "node": ">= 0.6" 1340 | } 1341 | }, 1342 | "node_modules/object-assign": { 1343 | "version": "4.1.1", 1344 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1345 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 1346 | "license": "MIT", 1347 | "engines": { 1348 | "node": ">=0.10.0" 1349 | } 1350 | }, 1351 | "node_modules/object-inspect": { 1352 | "version": "1.13.4", 1353 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 1354 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 1355 | "license": "MIT", 1356 | "engines": { 1357 | "node": ">= 0.4" 1358 | }, 1359 | "funding": { 1360 | "url": "https://github.com/sponsors/ljharb" 1361 | } 1362 | }, 1363 | "node_modules/on-finished": { 1364 | "version": "2.4.1", 1365 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1366 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1367 | "license": "MIT", 1368 | "dependencies": { 1369 | "ee-first": "1.1.1" 1370 | }, 1371 | "engines": { 1372 | "node": ">= 0.8" 1373 | } 1374 | }, 1375 | "node_modules/once": { 1376 | "version": "1.4.0", 1377 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1378 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1379 | "license": "ISC", 1380 | "dependencies": { 1381 | "wrappy": "1" 1382 | } 1383 | }, 1384 | "node_modules/parseurl": { 1385 | "version": "1.3.3", 1386 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1387 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1388 | "license": "MIT", 1389 | "engines": { 1390 | "node": ">= 0.8" 1391 | } 1392 | }, 1393 | "node_modules/path-is-absolute": { 1394 | "version": "1.0.1", 1395 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1396 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1397 | "dev": true, 1398 | "license": "MIT", 1399 | "engines": { 1400 | "node": ">=0.10.0" 1401 | } 1402 | }, 1403 | "node_modules/path-key": { 1404 | "version": "3.1.1", 1405 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1406 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1407 | "license": "MIT", 1408 | "engines": { 1409 | "node": ">=8" 1410 | } 1411 | }, 1412 | "node_modules/path-parse": { 1413 | "version": "1.0.7", 1414 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1415 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1416 | "dev": true, 1417 | "license": "MIT" 1418 | }, 1419 | "node_modules/path-to-regexp": { 1420 | "version": "0.1.12", 1421 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", 1422 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", 1423 | "license": "MIT" 1424 | }, 1425 | "node_modules/pkce-challenge": { 1426 | "version": "5.0.0", 1427 | "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", 1428 | "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", 1429 | "license": "MIT", 1430 | "engines": { 1431 | "node": ">=16.20.0" 1432 | } 1433 | }, 1434 | "node_modules/proxy-addr": { 1435 | "version": "2.0.7", 1436 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1437 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1438 | "license": "MIT", 1439 | "dependencies": { 1440 | "forwarded": "0.2.0", 1441 | "ipaddr.js": "1.9.1" 1442 | }, 1443 | "engines": { 1444 | "node": ">= 0.10" 1445 | } 1446 | }, 1447 | "node_modules/proxy-from-env": { 1448 | "version": "1.1.0", 1449 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 1450 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 1451 | "license": "MIT" 1452 | }, 1453 | "node_modules/qs": { 1454 | "version": "6.13.0", 1455 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 1456 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 1457 | "license": "BSD-3-Clause", 1458 | "dependencies": { 1459 | "side-channel": "^1.0.6" 1460 | }, 1461 | "engines": { 1462 | "node": ">=0.6" 1463 | }, 1464 | "funding": { 1465 | "url": "https://github.com/sponsors/ljharb" 1466 | } 1467 | }, 1468 | "node_modules/range-parser": { 1469 | "version": "1.2.1", 1470 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1471 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1472 | "license": "MIT", 1473 | "engines": { 1474 | "node": ">= 0.6" 1475 | } 1476 | }, 1477 | "node_modules/raw-body": { 1478 | "version": "3.0.0", 1479 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 1480 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 1481 | "license": "MIT", 1482 | "dependencies": { 1483 | "bytes": "3.1.2", 1484 | "http-errors": "2.0.0", 1485 | "iconv-lite": "0.6.3", 1486 | "unpipe": "1.0.0" 1487 | }, 1488 | "engines": { 1489 | "node": ">= 0.8" 1490 | } 1491 | }, 1492 | "node_modules/raw-body/node_modules/iconv-lite": { 1493 | "version": "0.6.3", 1494 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 1495 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 1496 | "license": "MIT", 1497 | "dependencies": { 1498 | "safer-buffer": ">= 2.1.2 < 3.0.0" 1499 | }, 1500 | "engines": { 1501 | "node": ">=0.10.0" 1502 | } 1503 | }, 1504 | "node_modules/rechoir": { 1505 | "version": "0.6.2", 1506 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 1507 | "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", 1508 | "dev": true, 1509 | "dependencies": { 1510 | "resolve": "^1.1.6" 1511 | }, 1512 | "engines": { 1513 | "node": ">= 0.10" 1514 | } 1515 | }, 1516 | "node_modules/require-from-string": { 1517 | "version": "2.0.2", 1518 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", 1519 | "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", 1520 | "license": "MIT", 1521 | "engines": { 1522 | "node": ">=0.10.0" 1523 | } 1524 | }, 1525 | "node_modules/resolve": { 1526 | "version": "1.22.10", 1527 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", 1528 | "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", 1529 | "dev": true, 1530 | "license": "MIT", 1531 | "dependencies": { 1532 | "is-core-module": "^2.16.0", 1533 | "path-parse": "^1.0.7", 1534 | "supports-preserve-symlinks-flag": "^1.0.0" 1535 | }, 1536 | "bin": { 1537 | "resolve": "bin/resolve" 1538 | }, 1539 | "engines": { 1540 | "node": ">= 0.4" 1541 | }, 1542 | "funding": { 1543 | "url": "https://github.com/sponsors/ljharb" 1544 | } 1545 | }, 1546 | "node_modules/router": { 1547 | "version": "2.2.0", 1548 | "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", 1549 | "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", 1550 | "license": "MIT", 1551 | "dependencies": { 1552 | "debug": "^4.4.0", 1553 | "depd": "^2.0.0", 1554 | "is-promise": "^4.0.0", 1555 | "parseurl": "^1.3.3", 1556 | "path-to-regexp": "^8.0.0" 1557 | }, 1558 | "engines": { 1559 | "node": ">= 18" 1560 | } 1561 | }, 1562 | "node_modules/router/node_modules/debug": { 1563 | "version": "4.4.0", 1564 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 1565 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 1566 | "license": "MIT", 1567 | "dependencies": { 1568 | "ms": "^2.1.3" 1569 | }, 1570 | "engines": { 1571 | "node": ">=6.0" 1572 | }, 1573 | "peerDependenciesMeta": { 1574 | "supports-color": { 1575 | "optional": true 1576 | } 1577 | } 1578 | }, 1579 | "node_modules/router/node_modules/ms": { 1580 | "version": "2.1.3", 1581 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1582 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1583 | "license": "MIT" 1584 | }, 1585 | "node_modules/router/node_modules/path-to-regexp": { 1586 | "version": "8.2.0", 1587 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", 1588 | "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", 1589 | "license": "MIT", 1590 | "engines": { 1591 | "node": ">=16" 1592 | } 1593 | }, 1594 | "node_modules/safe-buffer": { 1595 | "version": "5.2.1", 1596 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1597 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1598 | "funding": [ 1599 | { 1600 | "type": "github", 1601 | "url": "https://github.com/sponsors/feross" 1602 | }, 1603 | { 1604 | "type": "patreon", 1605 | "url": "https://www.patreon.com/feross" 1606 | }, 1607 | { 1608 | "type": "consulting", 1609 | "url": "https://feross.org/support" 1610 | } 1611 | ], 1612 | "license": "MIT" 1613 | }, 1614 | "node_modules/safer-buffer": { 1615 | "version": "2.1.2", 1616 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1617 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1618 | "license": "MIT" 1619 | }, 1620 | "node_modules/send": { 1621 | "version": "0.19.0", 1622 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", 1623 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 1624 | "license": "MIT", 1625 | "dependencies": { 1626 | "debug": "2.6.9", 1627 | "depd": "2.0.0", 1628 | "destroy": "1.2.0", 1629 | "encodeurl": "~1.0.2", 1630 | "escape-html": "~1.0.3", 1631 | "etag": "~1.8.1", 1632 | "fresh": "0.5.2", 1633 | "http-errors": "2.0.0", 1634 | "mime": "1.6.0", 1635 | "ms": "2.1.3", 1636 | "on-finished": "2.4.1", 1637 | "range-parser": "~1.2.1", 1638 | "statuses": "2.0.1" 1639 | }, 1640 | "engines": { 1641 | "node": ">= 0.8.0" 1642 | } 1643 | }, 1644 | "node_modules/send/node_modules/encodeurl": { 1645 | "version": "1.0.2", 1646 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1647 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 1648 | "license": "MIT", 1649 | "engines": { 1650 | "node": ">= 0.8" 1651 | } 1652 | }, 1653 | "node_modules/send/node_modules/ms": { 1654 | "version": "2.1.3", 1655 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1656 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1657 | "license": "MIT" 1658 | }, 1659 | "node_modules/serve-static": { 1660 | "version": "1.16.2", 1661 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", 1662 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 1663 | "license": "MIT", 1664 | "dependencies": { 1665 | "encodeurl": "~2.0.0", 1666 | "escape-html": "~1.0.3", 1667 | "parseurl": "~1.3.3", 1668 | "send": "0.19.0" 1669 | }, 1670 | "engines": { 1671 | "node": ">= 0.8.0" 1672 | } 1673 | }, 1674 | "node_modules/setprototypeof": { 1675 | "version": "1.2.0", 1676 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1677 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 1678 | "license": "ISC" 1679 | }, 1680 | "node_modules/shebang-command": { 1681 | "version": "2.0.0", 1682 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1683 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1684 | "license": "MIT", 1685 | "dependencies": { 1686 | "shebang-regex": "^3.0.0" 1687 | }, 1688 | "engines": { 1689 | "node": ">=8" 1690 | } 1691 | }, 1692 | "node_modules/shebang-regex": { 1693 | "version": "3.0.0", 1694 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1695 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1696 | "license": "MIT", 1697 | "engines": { 1698 | "node": ">=8" 1699 | } 1700 | }, 1701 | "node_modules/shelljs": { 1702 | "version": "0.8.5", 1703 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", 1704 | "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", 1705 | "dev": true, 1706 | "license": "BSD-3-Clause", 1707 | "dependencies": { 1708 | "glob": "^7.0.0", 1709 | "interpret": "^1.0.0", 1710 | "rechoir": "^0.6.2" 1711 | }, 1712 | "bin": { 1713 | "shjs": "bin/shjs" 1714 | }, 1715 | "engines": { 1716 | "node": ">=4" 1717 | } 1718 | }, 1719 | "node_modules/shx": { 1720 | "version": "0.3.4", 1721 | "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", 1722 | "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", 1723 | "dev": true, 1724 | "license": "MIT", 1725 | "dependencies": { 1726 | "minimist": "^1.2.3", 1727 | "shelljs": "^0.8.5" 1728 | }, 1729 | "bin": { 1730 | "shx": "lib/cli.js" 1731 | }, 1732 | "engines": { 1733 | "node": ">=6" 1734 | } 1735 | }, 1736 | "node_modules/side-channel": { 1737 | "version": "1.1.0", 1738 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1739 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1740 | "license": "MIT", 1741 | "dependencies": { 1742 | "es-errors": "^1.3.0", 1743 | "object-inspect": "^1.13.3", 1744 | "side-channel-list": "^1.0.0", 1745 | "side-channel-map": "^1.0.1", 1746 | "side-channel-weakmap": "^1.0.2" 1747 | }, 1748 | "engines": { 1749 | "node": ">= 0.4" 1750 | }, 1751 | "funding": { 1752 | "url": "https://github.com/sponsors/ljharb" 1753 | } 1754 | }, 1755 | "node_modules/side-channel-list": { 1756 | "version": "1.0.0", 1757 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1758 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1759 | "license": "MIT", 1760 | "dependencies": { 1761 | "es-errors": "^1.3.0", 1762 | "object-inspect": "^1.13.3" 1763 | }, 1764 | "engines": { 1765 | "node": ">= 0.4" 1766 | }, 1767 | "funding": { 1768 | "url": "https://github.com/sponsors/ljharb" 1769 | } 1770 | }, 1771 | "node_modules/side-channel-map": { 1772 | "version": "1.0.1", 1773 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1774 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1775 | "license": "MIT", 1776 | "dependencies": { 1777 | "call-bound": "^1.0.2", 1778 | "es-errors": "^1.3.0", 1779 | "get-intrinsic": "^1.2.5", 1780 | "object-inspect": "^1.13.3" 1781 | }, 1782 | "engines": { 1783 | "node": ">= 0.4" 1784 | }, 1785 | "funding": { 1786 | "url": "https://github.com/sponsors/ljharb" 1787 | } 1788 | }, 1789 | "node_modules/side-channel-weakmap": { 1790 | "version": "1.0.2", 1791 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1792 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1793 | "license": "MIT", 1794 | "dependencies": { 1795 | "call-bound": "^1.0.2", 1796 | "es-errors": "^1.3.0", 1797 | "get-intrinsic": "^1.2.5", 1798 | "object-inspect": "^1.13.3", 1799 | "side-channel-map": "^1.0.1" 1800 | }, 1801 | "engines": { 1802 | "node": ">= 0.4" 1803 | }, 1804 | "funding": { 1805 | "url": "https://github.com/sponsors/ljharb" 1806 | } 1807 | }, 1808 | "node_modules/statuses": { 1809 | "version": "2.0.1", 1810 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1811 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1812 | "license": "MIT", 1813 | "engines": { 1814 | "node": ">= 0.8" 1815 | } 1816 | }, 1817 | "node_modules/supports-preserve-symlinks-flag": { 1818 | "version": "1.0.0", 1819 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1820 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1821 | "dev": true, 1822 | "license": "MIT", 1823 | "engines": { 1824 | "node": ">= 0.4" 1825 | }, 1826 | "funding": { 1827 | "url": "https://github.com/sponsors/ljharb" 1828 | } 1829 | }, 1830 | "node_modules/toidentifier": { 1831 | "version": "1.0.1", 1832 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1833 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1834 | "license": "MIT", 1835 | "engines": { 1836 | "node": ">=0.6" 1837 | } 1838 | }, 1839 | "node_modules/type-is": { 1840 | "version": "1.6.18", 1841 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1842 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1843 | "license": "MIT", 1844 | "dependencies": { 1845 | "media-typer": "0.3.0", 1846 | "mime-types": "~2.1.24" 1847 | }, 1848 | "engines": { 1849 | "node": ">= 0.6" 1850 | } 1851 | }, 1852 | "node_modules/typescript": { 1853 | "version": "5.8.2", 1854 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", 1855 | "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", 1856 | "dev": true, 1857 | "license": "Apache-2.0", 1858 | "bin": { 1859 | "tsc": "bin/tsc", 1860 | "tsserver": "bin/tsserver" 1861 | }, 1862 | "engines": { 1863 | "node": ">=14.17" 1864 | } 1865 | }, 1866 | "node_modules/undici-types": { 1867 | "version": "6.21.0", 1868 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", 1869 | "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", 1870 | "dev": true, 1871 | "license": "MIT" 1872 | }, 1873 | "node_modules/unpipe": { 1874 | "version": "1.0.0", 1875 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1876 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1877 | "license": "MIT", 1878 | "engines": { 1879 | "node": ">= 0.8" 1880 | } 1881 | }, 1882 | "node_modules/utils-merge": { 1883 | "version": "1.0.1", 1884 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1885 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1886 | "license": "MIT", 1887 | "engines": { 1888 | "node": ">= 0.4.0" 1889 | } 1890 | }, 1891 | "node_modules/vary": { 1892 | "version": "1.1.2", 1893 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1894 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1895 | "license": "MIT", 1896 | "engines": { 1897 | "node": ">= 0.8" 1898 | } 1899 | }, 1900 | "node_modules/which": { 1901 | "version": "2.0.2", 1902 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1903 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1904 | "license": "ISC", 1905 | "dependencies": { 1906 | "isexe": "^2.0.0" 1907 | }, 1908 | "bin": { 1909 | "node-which": "bin/node-which" 1910 | }, 1911 | "engines": { 1912 | "node": ">= 8" 1913 | } 1914 | }, 1915 | "node_modules/wrappy": { 1916 | "version": "1.0.2", 1917 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1918 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1919 | "license": "ISC" 1920 | }, 1921 | "node_modules/zod": { 1922 | "version": "3.24.2", 1923 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", 1924 | "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", 1925 | "license": "MIT", 1926 | "funding": { 1927 | "url": "https://github.com/sponsors/colinhacks" 1928 | } 1929 | }, 1930 | "node_modules/zod-to-json-schema": { 1931 | "version": "3.24.5", 1932 | "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", 1933 | "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", 1934 | "license": "ISC", 1935 | "peerDependencies": { 1936 | "zod": "^3.24.1" 1937 | } 1938 | } 1939 | } 1940 | } 1941 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metamcp/mcp-server-metamcp", 3 | "version": "0.6.5", 4 | "description": "MCP Server MetaMCP manages all your other MCPs in one MCP.", 5 | "scripts": { 6 | "build": "tsc && shx chmod +x dist/*.js", 7 | "watch": "tsc --watch", 8 | "inspector": "dotenv -e .env.local npx @modelcontextprotocol/inspector dist/index.js -e METAMCP_API_KEY=${METAMCP_API_KEY} -e METAMCP_API_BASE_URL=${METAMCP_API_BASE_URL}", 9 | "inspector:prod": "dotenv -e .env.production.local npx @modelcontextprotocol/inspector dist/index.js -e METAMCP_API_KEY=${METAMCP_API_KEY}", 10 | "report": "dotenv -e .env.local -- node dist/index.js --report" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/metatool-ai/mcp-server-metamcp.git" 15 | }, 16 | "author": "James Zhang", 17 | "license": "Apache-2.0", 18 | "bugs": { 19 | "url": "https://github.com/metatool-ai/mcp-server-metamcp/issues" 20 | }, 21 | "homepage": "https://github.com/metatool-ai/mcp-server-metamcp#readme", 22 | "dependencies": { 23 | "@modelcontextprotocol/sdk": "^1.11.4", 24 | "axios": "^1.7.9", 25 | "commander": "^13.1.0", 26 | "express": "^4.21.2", 27 | "zod": "^3.24.2" 28 | }, 29 | "devDependencies": { 30 | "@types/express": "^5.0.1", 31 | "@types/node": "^22.13.4", 32 | "dotenv-cli": "^8.0.0", 33 | "shx": "^0.3.4", 34 | "typescript": "^5.8.2" 35 | }, 36 | "type": "module", 37 | "bin": { 38 | "mcp-server-metamcp": "dist/index.js" 39 | }, 40 | "files": [ 41 | "dist" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - metamcpApiKey 10 | properties: 11 | metamcpApiKey: 12 | type: string 13 | description: The API key from metamcp.com/api-keys. Required. 14 | metamcpApiBaseUrl: 15 | type: string 16 | description: Optional override for the MetaMCP App URL (default is https://api.metamcp.com). 17 | commandFunction: 18 | # A function that produces the CLI command to start the MCP on stdio. 19 | # Note: Command line arguments can also be used directly: 20 | # --metamcp-api-key --metamcp-api-base-url 21 | |- 22 | (config) => ({ command: 'node', args: ['dist/index.js'], env: { METAMCP_API_KEY: config.metamcpApiKey, ...(config.metamcpApiBaseUrl && { METAMCP_API_BASE_URL: config.metamcpApiBaseUrl }) } }) 23 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; 2 | import { 3 | StdioClientTransport, 4 | StdioServerParameters, 5 | } from "@modelcontextprotocol/sdk/client/stdio.js"; 6 | import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; 7 | import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; 8 | import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; 9 | import { ServerParameters, IOType } from "./fetch-metamcp.js"; 10 | 11 | const sleep = (time: number) => 12 | new Promise((resolve) => setTimeout(() => resolve(), time)); 13 | export interface ConnectedClient { 14 | client: Client; 15 | cleanup: () => Promise; 16 | } 17 | 18 | /** 19 | * Transforms localhost URLs to use host.docker.internal when running inside Docker 20 | */ 21 | const transformDockerUrl = (url: string): string => { 22 | if (process.env.USE_DOCKER_HOST === "true") { 23 | return url.replace(/localhost|127\.0\.0\.1/g, "host.docker.internal"); 24 | } 25 | return url; 26 | }; 27 | 28 | export const createMetaMcpClient = ( 29 | serverParams: ServerParameters 30 | ): { client: Client | undefined; transport: Transport | undefined } => { 31 | let transport: Transport | undefined; 32 | 33 | // Create the appropriate transport based on server type 34 | // Default to "STDIO" if type is undefined 35 | if (!serverParams.type || serverParams.type === "STDIO") { 36 | // Get stderr value from serverParams, environment variable, or default to "ignore" 37 | const stderrValue: IOType = 38 | serverParams.stderr || 39 | (process.env.METAMCP_STDERR as IOType) || 40 | "ignore"; 41 | 42 | const stdioParams: StdioServerParameters = { 43 | command: serverParams.command || "", 44 | args: serverParams.args || undefined, 45 | env: serverParams.env || undefined, 46 | stderr: stderrValue, 47 | }; 48 | transport = new StdioClientTransport(stdioParams); 49 | 50 | // Handle stderr stream when set to "pipe" 51 | if (stderrValue === "pipe" && (transport as any).stderr) { 52 | const stderrStream = (transport as any).stderr; 53 | 54 | stderrStream.on('data', (chunk: Buffer) => { 55 | console.error(`[${serverParams.name}] ${chunk.toString().trim()}`); 56 | }); 57 | 58 | stderrStream.on('error', (error: Error) => { 59 | console.error(`[${serverParams.name}] stderr error:`, error); 60 | }); 61 | } 62 | } else if (serverParams.type === "SSE" && serverParams.url) { 63 | // Transform the URL if USE_DOCKER_HOST is set to "true" 64 | const transformedUrl = transformDockerUrl(serverParams.url); 65 | 66 | if (!serverParams.oauth_tokens) { 67 | transport = new SSEClientTransport(new URL(transformedUrl)); 68 | } else { 69 | const headers: HeadersInit = {}; 70 | headers[ 71 | "Authorization" 72 | ] = `Bearer ${serverParams.oauth_tokens.access_token}`; 73 | transport = new SSEClientTransport(new URL(transformedUrl), { 74 | requestInit: { 75 | headers, 76 | }, 77 | eventSourceInit: { 78 | fetch: (url, init) => fetch(url, { ...init, headers }), 79 | }, 80 | }); 81 | } 82 | } else if (serverParams.type === "STREAMABLE_HTTP" && serverParams.url) { 83 | // Transform the URL if USE_DOCKER_HOST is set to "true" 84 | const transformedUrl = transformDockerUrl(serverParams.url); 85 | 86 | if (!serverParams.oauth_tokens) { 87 | transport = new StreamableHTTPClientTransport(new URL(transformedUrl)); 88 | } else { 89 | const headers: HeadersInit = {}; 90 | headers[ 91 | "Authorization" 92 | ] = `Bearer ${serverParams.oauth_tokens.access_token}`; 93 | transport = new StreamableHTTPClientTransport(new URL(transformedUrl), { 94 | requestInit: { 95 | headers, 96 | }, 97 | }); 98 | } 99 | } else { 100 | console.error(`Unsupported server type: ${serverParams.type}`); 101 | return { client: undefined, transport: undefined }; 102 | } 103 | 104 | const client = new Client( 105 | { 106 | name: "MetaMCP", 107 | version: "0.6.5", 108 | }, 109 | { 110 | capabilities: { 111 | prompts: {}, 112 | resources: { subscribe: true }, 113 | tools: {}, 114 | }, 115 | } 116 | ); 117 | return { client, transport }; 118 | }; 119 | 120 | export const connectMetaMcpClient = async ( 121 | client: Client, 122 | transport: Transport 123 | ): Promise => { 124 | const waitFor = 2500; 125 | const retries = 3; 126 | let count = 0; 127 | let retry = true; 128 | 129 | while (retry) { 130 | try { 131 | await client.connect(transport); 132 | 133 | return { 134 | client, 135 | cleanup: async () => { 136 | await transport.close(); 137 | await client.close(); 138 | }, 139 | }; 140 | } catch (error) { 141 | count++; 142 | retry = count < retries; 143 | if (retry) { 144 | try { 145 | await client.close(); 146 | } catch {} 147 | await sleep(waitFor); 148 | } 149 | } 150 | } 151 | }; 152 | -------------------------------------------------------------------------------- /src/fetch-capabilities.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { getMetaMcpApiBaseUrl, getMetaMcpApiKey } from "./utils.js"; 3 | 4 | export enum ProfileCapability { 5 | TOOLS_MANAGEMENT = "TOOLS_MANAGEMENT", 6 | TOOL_LOGS = "TOOL_LOGS", 7 | } 8 | 9 | let _capabilitiesCache: ProfileCapability[] | null = null; 10 | let _capabilitiesCacheTimestamp: number = 0; 11 | const CACHE_TTL_MS = 1000; // 1 second cache TTL 12 | 13 | export async function getProfileCapabilities( 14 | forceRefresh: boolean = false 15 | ): Promise { 16 | const currentTime = Date.now(); 17 | const cacheAge = currentTime - _capabilitiesCacheTimestamp; 18 | 19 | // Use cache if it exists, is not null, and either: 20 | // 1. forceRefresh is false, or 21 | // 2. forceRefresh is true but cache is less than 1 second old 22 | if ( 23 | _capabilitiesCache !== null && 24 | (!forceRefresh || cacheAge < CACHE_TTL_MS) 25 | ) { 26 | return _capabilitiesCache; 27 | } 28 | 29 | try { 30 | const apiKey = getMetaMcpApiKey(); 31 | const apiBaseUrl = getMetaMcpApiBaseUrl(); 32 | 33 | if (!apiKey) { 34 | console.error( 35 | "METAMCP_API_KEY is not set. Please set it via environment variable or command line argument." 36 | ); 37 | return _capabilitiesCache || []; 38 | } 39 | 40 | const headers = { Authorization: `Bearer ${apiKey}` }; 41 | const response = await axios.get(`${apiBaseUrl}/api/profile-capabilities`, { 42 | headers, 43 | }); 44 | const data = response.data; 45 | 46 | // Access the 'profileCapabilities' array in the response 47 | if (data && data.profileCapabilities) { 48 | const capabilities = data.profileCapabilities 49 | .map((capability: string) => { 50 | // Map string to enum value if it exists, otherwise return undefined 51 | return ProfileCapability[ 52 | capability as keyof typeof ProfileCapability 53 | ]; 54 | }) 55 | .filter( 56 | ( 57 | capability: ProfileCapability | undefined 58 | ): capability is ProfileCapability => capability !== undefined 59 | ); 60 | 61 | _capabilitiesCache = capabilities; 62 | _capabilitiesCacheTimestamp = currentTime; 63 | return capabilities; 64 | } 65 | 66 | return _capabilitiesCache || []; 67 | } catch (error) { 68 | // Return empty array if API doesn't exist or has errors 69 | if (_capabilitiesCache !== null) { 70 | return _capabilitiesCache; 71 | } 72 | return []; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/fetch-metamcp.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { 3 | getDefaultEnvironment, 4 | getMetaMcpApiBaseUrl, 5 | getMetaMcpApiKey, 6 | } from "./utils.js"; 7 | 8 | // Define IOType for stderr handling 9 | export type IOType = "overlapped" | "pipe" | "ignore" | "inherit"; 10 | 11 | // Define a new interface for server parameters that can be STDIO, SSE or STREAMABLE_HTTP 12 | export interface ServerParameters { 13 | uuid: string; 14 | name: string; 15 | description: string; 16 | type?: "STDIO" | "SSE" | "STREAMABLE_HTTP"; // Optional field, defaults to "STDIO" when undefined 17 | command?: string | null; 18 | args?: string[] | null; 19 | env?: Record | null; 20 | stderr?: IOType; // Optional field for stderr handling, defaults to "ignore" 21 | url?: string | null; 22 | created_at: string; 23 | profile_uuid: string; 24 | status: string; 25 | oauth_tokens?: { 26 | access_token: string; 27 | token_type: string; 28 | expires_in?: number | undefined; 29 | scope?: string | undefined; 30 | refresh_token?: string | undefined; 31 | } | null; 32 | } 33 | 34 | let _mcpServersCache: Record | null = null; 35 | let _mcpServersCacheTimestamp: number = 0; 36 | const CACHE_TTL_MS = 1000; // 1 second cache TTL 37 | 38 | export async function getMcpServers( 39 | forceRefresh: boolean = false 40 | ): Promise> { 41 | const currentTime = Date.now(); 42 | const cacheAge = currentTime - _mcpServersCacheTimestamp; 43 | 44 | // Use cache if it exists, is not null, and either: 45 | // 1. forceRefresh is false, or 46 | // 2. forceRefresh is true but cache is less than 1 second old 47 | if (_mcpServersCache !== null && (!forceRefresh || cacheAge < CACHE_TTL_MS)) { 48 | return _mcpServersCache; 49 | } 50 | 51 | try { 52 | const apiKey = getMetaMcpApiKey(); 53 | const apiBaseUrl = getMetaMcpApiBaseUrl(); 54 | 55 | if (!apiKey) { 56 | console.error( 57 | "METAMCP_API_KEY is not set. Please set it via environment variable or command line argument." 58 | ); 59 | return _mcpServersCache || {}; 60 | } 61 | 62 | const headers = { Authorization: `Bearer ${apiKey}` }; 63 | const response = await axios.get(`${apiBaseUrl}/api/mcp-servers`, { 64 | headers, 65 | }); 66 | const data = response.data; 67 | 68 | const serverDict: Record = {}; 69 | for (const serverParams of data) { 70 | const params: ServerParameters = { 71 | ...serverParams, 72 | type: serverParams.type || "STDIO", 73 | }; 74 | 75 | // Process based on server type 76 | if (params.type === "STDIO") { 77 | if ("args" in params && !params.args) { 78 | params.args = undefined; 79 | } 80 | 81 | params.env = { 82 | ...getDefaultEnvironment(), 83 | ...(params.env || {}), 84 | }; 85 | } else if (params.type === "SSE" || params.type === "STREAMABLE_HTTP") { 86 | // For SSE or STREAMABLE_HTTP servers, ensure url is present 87 | if (!params.url) { 88 | console.warn( 89 | `${params.type} server ${params.uuid} is missing url field, skipping` 90 | ); 91 | continue; 92 | } 93 | } 94 | 95 | const uuid = params.uuid; 96 | if (uuid) { 97 | serverDict[uuid] = params; 98 | } 99 | } 100 | 101 | _mcpServersCache = serverDict; 102 | _mcpServersCacheTimestamp = currentTime; 103 | return serverDict; 104 | } catch (error) { 105 | if (_mcpServersCache !== null) { 106 | return _mcpServersCache; 107 | } 108 | return {}; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/fetch-tools.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { getMetaMcpApiBaseUrl, getMetaMcpApiKey } from "./utils.js"; 3 | 4 | enum ToolStatus { 5 | ACTIVE = "ACTIVE", 6 | INACTIVE = "INACTIVE", 7 | } 8 | 9 | // Define interface for tool parameters with only required fields 10 | export interface ToolParameters { 11 | mcp_server_uuid: string; 12 | name: string; 13 | status: ToolStatus; 14 | } 15 | 16 | let _toolsCache: Record | null = null; 17 | let _toolsCacheTimestamp: number = 0; 18 | const CACHE_TTL_MS = 1000; // 1 second cache TTL 19 | 20 | export async function getInactiveTools( 21 | forceRefresh: boolean = false 22 | ): Promise> { 23 | const currentTime = Date.now(); 24 | const cacheAge = currentTime - _toolsCacheTimestamp; 25 | 26 | // Use cache if it exists, is not null, and either: 27 | // 1. forceRefresh is false, or 28 | // 2. forceRefresh is true but cache is less than 1 second old 29 | if (_toolsCache !== null && (!forceRefresh || cacheAge < CACHE_TTL_MS)) { 30 | return _toolsCache; 31 | } 32 | 33 | try { 34 | const apiKey = getMetaMcpApiKey(); 35 | const apiBaseUrl = getMetaMcpApiBaseUrl(); 36 | 37 | if (!apiKey) { 38 | console.error( 39 | "METAMCP_API_KEY is not set. Please set it via environment variable or command line argument." 40 | ); 41 | return _toolsCache || {}; 42 | } 43 | 44 | const headers = { Authorization: `Bearer ${apiKey}` }; 45 | const response = await axios.get( 46 | `${apiBaseUrl}/api/tools?status=${ToolStatus.INACTIVE}`, 47 | { 48 | headers, 49 | } 50 | ); 51 | const data = response.data; 52 | 53 | const toolDict: Record = {}; 54 | // Access the 'results' array in the response 55 | if (data && data.results) { 56 | for (const tool of data.results) { 57 | const params: ToolParameters = { 58 | mcp_server_uuid: tool.mcp_server_uuid, 59 | name: tool.name, 60 | status: tool.status, 61 | }; 62 | 63 | const uniqueId = `${tool.mcp_server_uuid}:${tool.name}`; 64 | toolDict[uniqueId] = params; 65 | } 66 | } 67 | 68 | _toolsCache = toolDict; 69 | _toolsCacheTimestamp = currentTime; 70 | return toolDict; 71 | } catch (error) { 72 | // Return empty object if API doesn't exist or has errors 73 | if (_toolsCache !== null) { 74 | return _toolsCache; 75 | } 76 | return {}; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import { createServer } from "./mcp-proxy.js"; 5 | import { Command } from "commander"; 6 | import { reportAllTools } from "./report-tools.js"; 7 | import { cleanupAllSessions } from "./sessions.js"; 8 | import { startSSEServer } from "./sse.js"; 9 | import { startStreamableHTTPServer } from "./streamable-http.js"; 10 | import { IOType } from "./fetch-metamcp.js"; 11 | 12 | const program = new Command(); 13 | 14 | program 15 | .name("mcp-server-metamcp") 16 | .description("MetaMCP MCP Server - The One MCP to manage all your MCPs") 17 | .option( 18 | "--metamcp-api-key ", 19 | "API key for MetaMCP (can also be set via METAMCP_API_KEY env var)" 20 | ) 21 | .option( 22 | "--metamcp-api-base-url ", 23 | "Base URL for MetaMCP API (can also be set via METAMCP_API_BASE_URL env var)" 24 | ) 25 | .option( 26 | "--report", 27 | "Fetch all MCPs, initialize clients, and report tools to MetaMCP API" 28 | ) 29 | .option("--transport ", "Transport type to use (stdio, sse, or streamable-http)", "stdio") 30 | .option("--port ", "Port to use for SSE or Streamable HTTP transport, defaults to 12006", "12006") 31 | .option("--require-api-auth", "Require API key in SSE or Streamable HTTP URL path") 32 | .option("--stateless", "Use stateless mode for Streamable HTTP transport") 33 | .option( 34 | "--use-docker-host", 35 | "Transform localhost URLs to use host.docker.internal (can also be set via USE_DOCKER_HOST env var)" 36 | ) 37 | .option( 38 | "--stderr ", 39 | "Stderr handling for STDIO transport (overlapped, pipe, ignore, inherit)", 40 | "ignore" 41 | ) 42 | .parse(process.argv); 43 | 44 | const options = program.opts(); 45 | 46 | // Validate stderr option 47 | const validStderrTypes: IOType[] = ["overlapped", "pipe", "ignore", "inherit"]; 48 | if (!validStderrTypes.includes(options.stderr as IOType)) { 49 | console.error(`Invalid stderr type: ${options.stderr}. Must be one of: ${validStderrTypes.join(", ")}`); 50 | process.exit(1); 51 | } 52 | 53 | // Set environment variables from command line arguments 54 | if (options.metamcpApiKey) { 55 | process.env.METAMCP_API_KEY = options.metamcpApiKey; 56 | } 57 | if (options.metamcpApiBaseUrl) { 58 | process.env.METAMCP_API_BASE_URL = options.metamcpApiBaseUrl; 59 | } 60 | if (options.useDockerHost) { 61 | process.env.USE_DOCKER_HOST = "true"; 62 | } 63 | if (options.stderr) { 64 | process.env.METAMCP_STDERR = options.stderr; 65 | } 66 | 67 | async function main() { 68 | // If --report flag is set, run the reporting function instead of starting the server 69 | if (options.report) { 70 | await reportAllTools(); 71 | await cleanupAllSessions(); 72 | return; 73 | } 74 | 75 | const { server, cleanup } = await createServer(); 76 | 77 | if (options.transport.toLowerCase() === "sse") { 78 | // Start SSE server 79 | const port = parseInt(options.port) || 12006; 80 | const sseCleanup = await startSSEServer(server, { 81 | port, 82 | requireApiAuth: options.requireApiAuth, 83 | }); 84 | 85 | // Cleanup on exit 86 | const handleExit = async () => { 87 | await cleanup(); 88 | await sseCleanup(); 89 | await server.close(); 90 | process.exit(0); 91 | }; 92 | 93 | process.on("SIGINT", handleExit); 94 | process.on("SIGTERM", handleExit); 95 | } else if (options.transport.toLowerCase() === "streamable-http") { 96 | // Start Streamable HTTP server 97 | const port = parseInt(options.port) || 12006; 98 | const streamableHttpCleanup = await startStreamableHTTPServer(server, { 99 | port, 100 | requireApiAuth: options.requireApiAuth, 101 | stateless: options.stateless, 102 | }); 103 | 104 | // Cleanup on exit 105 | const handleExit = async () => { 106 | await cleanup(); 107 | await streamableHttpCleanup(); 108 | await server.close(); 109 | process.exit(0); 110 | }; 111 | 112 | process.on("SIGINT", handleExit); 113 | process.on("SIGTERM", handleExit); 114 | } else { 115 | // Default: Start stdio server 116 | const transport = new StdioServerTransport(); 117 | await server.connect(transport); 118 | 119 | const handleExit = async () => { 120 | await cleanup(); 121 | await transport.close(); 122 | await server.close(); 123 | process.exit(0); 124 | }; 125 | 126 | // Cleanup on exit 127 | process.on("SIGINT", handleExit); 128 | process.on("SIGTERM", handleExit); 129 | 130 | process.stdin.resume(); 131 | process.stdin.on("end", handleExit); 132 | process.stdin.on("close", handleExit); 133 | } 134 | } 135 | 136 | main().catch((error) => { 137 | console.error("Server error:", error); 138 | }); 139 | -------------------------------------------------------------------------------- /src/mcp-proxy.ts: -------------------------------------------------------------------------------- 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 2 | import { 3 | CallToolRequestSchema, 4 | GetPromptRequestSchema, 5 | ListPromptsRequestSchema, 6 | ListResourcesRequestSchema, 7 | ListToolsRequestSchema, 8 | ReadResourceRequestSchema, 9 | Tool, 10 | ListToolsResultSchema, 11 | ListPromptsResultSchema, 12 | ListResourcesResultSchema, 13 | ReadResourceResultSchema, 14 | ListResourceTemplatesRequestSchema, 15 | ListResourceTemplatesResultSchema, 16 | ResourceTemplate, 17 | CompatibilityCallToolResultSchema, 18 | GetPromptResultSchema, 19 | } from "@modelcontextprotocol/sdk/types.js"; 20 | import { z } from "zod"; 21 | import { getMcpServers } from "./fetch-metamcp.js"; 22 | import { getSessionKey, sanitizeName } from "./utils.js"; 23 | import { cleanupAllSessions, getSession, initSessions } from "./sessions.js"; 24 | import { ConnectedClient } from "./client.js"; 25 | import { reportToolsToMetaMcp } from "./report-tools.js"; 26 | import { getInactiveTools, ToolParameters } from "./fetch-tools.js"; 27 | import { 28 | getProfileCapabilities, 29 | ProfileCapability, 30 | } from "./fetch-capabilities.js"; 31 | import { ToolLogManager } from "./tool-logs.js"; 32 | 33 | const toolToClient: Record = {}; 34 | const toolToServerUuid: Record = {}; 35 | const promptToClient: Record = {}; 36 | const resourceToClient: Record = {}; 37 | const inactiveToolsMap: Record = {}; 38 | 39 | export const createServer = async () => { 40 | const server = new Server( 41 | { 42 | name: "MetaMCP", 43 | version: "0.6.5", 44 | }, 45 | { 46 | capabilities: { 47 | prompts: {}, 48 | resources: {}, 49 | tools: {}, 50 | }, 51 | } 52 | ); 53 | 54 | // Initialize sessions in the background when server starts 55 | initSessions().catch(); 56 | 57 | // List Tools Handler 58 | server.setRequestHandler(ListToolsRequestSchema, async (request) => { 59 | const profileCapabilities = await getProfileCapabilities(true); 60 | const serverParams = await getMcpServers(true); 61 | 62 | // Fetch inactive tools only if tools management capability is present 63 | let inactiveTools: Record = {}; 64 | if (profileCapabilities.includes(ProfileCapability.TOOLS_MANAGEMENT)) { 65 | inactiveTools = await getInactiveTools(true); 66 | // Clear existing inactive tools map before rebuilding 67 | Object.keys(inactiveToolsMap).forEach( 68 | (key) => delete inactiveToolsMap[key] 69 | ); 70 | } 71 | 72 | const allTools: Tool[] = []; 73 | 74 | await Promise.allSettled( 75 | Object.entries(serverParams).map(async ([uuid, params]) => { 76 | const sessionKey = getSessionKey(uuid, params); 77 | const session = await getSession(sessionKey, uuid, params); 78 | if (!session) return; 79 | 80 | const capabilities = session.client.getServerCapabilities(); 81 | if (!capabilities?.tools) return; 82 | 83 | const serverName = session.client.getServerVersion()?.name || ""; 84 | try { 85 | const result = await session.client.request( 86 | { 87 | method: "tools/list", 88 | params: { _meta: request.params?._meta }, 89 | }, 90 | ListToolsResultSchema 91 | ); 92 | 93 | const toolsWithSource = 94 | result.tools 95 | ?.filter((tool) => { 96 | // Only filter inactive tools if tools management is enabled 97 | if ( 98 | profileCapabilities.includes( 99 | ProfileCapability.TOOLS_MANAGEMENT 100 | ) 101 | ) { 102 | return !inactiveTools[`${uuid}:${tool.name}`]; 103 | } 104 | return true; 105 | }) 106 | .map((tool) => { 107 | const toolName = `${sanitizeName(serverName)}__${tool.name}`; 108 | toolToClient[toolName] = session; 109 | toolToServerUuid[toolName] = uuid; 110 | return { 111 | ...tool, 112 | name: toolName, 113 | description: tool.description, 114 | }; 115 | }) || []; 116 | 117 | // Update our inactive tools map only if tools management is enabled 118 | if ( 119 | profileCapabilities.includes(ProfileCapability.TOOLS_MANAGEMENT) 120 | ) { 121 | result.tools?.forEach((tool) => { 122 | const isInactive = inactiveTools[`${uuid}:${tool.name}`]; 123 | if (isInactive) { 124 | const formattedName = `${sanitizeName(serverName)}__${ 125 | tool.name 126 | }`; 127 | inactiveToolsMap[formattedName] = true; 128 | } 129 | }); 130 | 131 | // Report full tools for this server 132 | reportToolsToMetaMcp( 133 | result.tools.map((tool) => ({ 134 | name: tool.name, 135 | description: tool.description, 136 | toolSchema: tool.inputSchema, 137 | mcp_server_uuid: uuid, 138 | })) 139 | ).catch(); 140 | } 141 | 142 | allTools.push(...toolsWithSource); 143 | } catch (error) { 144 | console.error(`Error fetching tools from: ${serverName}`, error); 145 | } 146 | }) 147 | ); 148 | 149 | return { tools: allTools }; 150 | }); 151 | 152 | // Call Tool Handler 153 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 154 | const { name, arguments: args } = request.params; 155 | const originalToolName = name.split("__")[1]; 156 | const clientForTool = toolToClient[name]; 157 | const toolLogManager = ToolLogManager.getInstance(); 158 | let logId: string | undefined; 159 | let startTime = Date.now(); 160 | 161 | if (!clientForTool) { 162 | throw new Error(`Unknown tool: ${name}`); 163 | } 164 | 165 | // Get MCP server UUID for the tool 166 | const mcpServerUuid = toolToServerUuid[name] || ""; 167 | 168 | if (!mcpServerUuid) { 169 | console.error(`Could not determine MCP server UUID for tool: ${name}`); 170 | } 171 | 172 | // Get profile capabilities 173 | const profileCapabilities = await getProfileCapabilities(); 174 | 175 | // Only check inactive tools if tools management capability is present 176 | if ( 177 | profileCapabilities.includes(ProfileCapability.TOOLS_MANAGEMENT) && 178 | inactiveToolsMap[name] 179 | ) { 180 | throw new Error(`Tool is inactive: ${name}`); 181 | } 182 | 183 | // Check if TOOL_LOGS capability is enabled 184 | const hasToolsLogCapability = profileCapabilities.includes( 185 | ProfileCapability.TOOL_LOGS 186 | ); 187 | 188 | try { 189 | // Create initial pending log only if TOOL_LOGS capability is present 190 | if (hasToolsLogCapability) { 191 | const log = await toolLogManager.createLog( 192 | originalToolName, 193 | mcpServerUuid, 194 | args || {} 195 | ); 196 | logId = log.id; 197 | } 198 | 199 | // Reset the timer right before making the actual tool call 200 | startTime = Date.now(); 201 | 202 | // Use the correct schema for tool calls 203 | const result = await clientForTool.client.request( 204 | { 205 | method: "tools/call", 206 | params: { 207 | name: originalToolName, 208 | arguments: args || {}, 209 | _meta: { 210 | progressToken: request.params._meta?.progressToken, 211 | }, 212 | }, 213 | }, 214 | CompatibilityCallToolResultSchema 215 | ); 216 | 217 | const executionTime = Date.now() - startTime; 218 | 219 | // Update log with success result only if TOOL_LOGS capability is present 220 | if (hasToolsLogCapability && logId) { 221 | try { 222 | await toolLogManager.completeLog(logId, result, executionTime); 223 | } catch (logError) {} 224 | } 225 | 226 | return result; 227 | } catch (error: any) { 228 | const executionTime = Date.now() - startTime; 229 | 230 | // Update log with error only if TOOL_LOGS capability is present 231 | if (hasToolsLogCapability && logId) { 232 | try { 233 | await toolLogManager.failLog( 234 | logId, 235 | error.message || "Unknown error", 236 | executionTime 237 | ); 238 | } catch (logError) {} 239 | } 240 | 241 | console.error( 242 | `Error calling tool "${name}" through ${ 243 | clientForTool.client.getServerVersion()?.name || "unknown" 244 | }:`, 245 | error 246 | ); 247 | throw error; 248 | } 249 | }); 250 | 251 | // Get Prompt Handler 252 | server.setRequestHandler(GetPromptRequestSchema, async (request) => { 253 | const { name } = request.params; 254 | const clientForPrompt = promptToClient[name]; 255 | 256 | if (!clientForPrompt) { 257 | throw new Error(`Unknown prompt: ${name}`); 258 | } 259 | 260 | try { 261 | const promptName = name.split("__")[1]; 262 | const response = await clientForPrompt.client.request( 263 | { 264 | method: "prompts/get", 265 | params: { 266 | name: promptName, 267 | arguments: request.params.arguments || {}, 268 | _meta: request.params._meta, 269 | }, 270 | }, 271 | GetPromptResultSchema 272 | ); 273 | 274 | return response; 275 | } catch (error) { 276 | console.error( 277 | `Error getting prompt through ${ 278 | clientForPrompt.client.getServerVersion()?.name 279 | }:`, 280 | error 281 | ); 282 | throw error; 283 | } 284 | }); 285 | 286 | // List Prompts Handler 287 | server.setRequestHandler(ListPromptsRequestSchema, async (request) => { 288 | const serverParams = await getMcpServers(true); 289 | const allPrompts: z.infer["prompts"] = []; 290 | 291 | await Promise.allSettled( 292 | Object.entries(serverParams).map(async ([uuid, params]) => { 293 | const sessionKey = getSessionKey(uuid, params); 294 | const session = await getSession(sessionKey, uuid, params); 295 | if (!session) return; 296 | 297 | const capabilities = session.client.getServerCapabilities(); 298 | if (!capabilities?.prompts) return; 299 | 300 | const serverName = session.client.getServerVersion()?.name || ""; 301 | try { 302 | const result = await session.client.request( 303 | { 304 | method: "prompts/list", 305 | params: { 306 | cursor: request.params?.cursor, 307 | _meta: request.params?._meta, 308 | }, 309 | }, 310 | ListPromptsResultSchema 311 | ); 312 | 313 | if (result.prompts) { 314 | const promptsWithSource = result.prompts.map((prompt) => { 315 | const promptName = `${sanitizeName(serverName)}__${prompt.name}`; 316 | promptToClient[promptName] = session; 317 | return { 318 | ...prompt, 319 | name: promptName, 320 | description: prompt.description || "", 321 | }; 322 | }); 323 | allPrompts.push(...promptsWithSource); 324 | } 325 | } catch (error) { 326 | console.error(`Error fetching prompts from: ${serverName}`, error); 327 | } 328 | }) 329 | ); 330 | 331 | return { 332 | prompts: allPrompts, 333 | nextCursor: request.params?.cursor, 334 | }; 335 | }); 336 | 337 | // List Resources Handler 338 | server.setRequestHandler(ListResourcesRequestSchema, async (request) => { 339 | const serverParams = await getMcpServers(true); 340 | const allResources: z.infer["resources"] = 341 | []; 342 | 343 | await Promise.allSettled( 344 | Object.entries(serverParams).map(async ([uuid, params]) => { 345 | const sessionKey = getSessionKey(uuid, params); 346 | const session = await getSession(sessionKey, uuid, params); 347 | if (!session) return; 348 | 349 | const capabilities = session.client.getServerCapabilities(); 350 | if (!capabilities?.resources) return; 351 | 352 | const serverName = session.client.getServerVersion()?.name || ""; 353 | try { 354 | const result = await session.client.request( 355 | { 356 | method: "resources/list", 357 | params: { 358 | cursor: request.params?.cursor, 359 | _meta: request.params?._meta, 360 | }, 361 | }, 362 | ListResourcesResultSchema 363 | ); 364 | 365 | if (result.resources) { 366 | const resourcesWithSource = result.resources.map((resource) => { 367 | resourceToClient[resource.uri] = session; 368 | return { 369 | ...resource, 370 | name: resource.name || "", 371 | }; 372 | }); 373 | allResources.push(...resourcesWithSource); 374 | } 375 | } catch (error) { 376 | console.error(`Error fetching resources from: ${serverName}`, error); 377 | } 378 | }) 379 | ); 380 | 381 | return { 382 | resources: allResources, 383 | nextCursor: request.params?.cursor, 384 | }; 385 | }); 386 | 387 | // Read Resource Handler 388 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => { 389 | const { uri } = request.params; 390 | const clientForResource = resourceToClient[uri]; 391 | 392 | if (!clientForResource) { 393 | throw new Error(`Unknown resource: ${uri}`); 394 | } 395 | 396 | try { 397 | return await clientForResource.client.request( 398 | { 399 | method: "resources/read", 400 | params: { 401 | uri, 402 | _meta: request.params._meta, 403 | }, 404 | }, 405 | ReadResourceResultSchema 406 | ); 407 | } catch (error) { 408 | console.error( 409 | `Error reading resource through ${ 410 | clientForResource.client.getServerVersion()?.name 411 | }:`, 412 | error 413 | ); 414 | throw error; 415 | } 416 | }); 417 | 418 | // List Resource Templates Handler 419 | server.setRequestHandler( 420 | ListResourceTemplatesRequestSchema, 421 | async (request) => { 422 | const serverParams = await getMcpServers(true); 423 | const allTemplates: ResourceTemplate[] = []; 424 | 425 | await Promise.allSettled( 426 | Object.entries(serverParams).map(async ([uuid, params]) => { 427 | const sessionKey = getSessionKey(uuid, params); 428 | const session = await getSession(sessionKey, uuid, params); 429 | if (!session) return; 430 | 431 | const capabilities = session.client.getServerCapabilities(); 432 | if (!capabilities?.resources) return; 433 | 434 | try { 435 | const result = await session.client.request( 436 | { 437 | method: "resources/templates/list", 438 | params: { 439 | cursor: request.params?.cursor, 440 | _meta: request.params?._meta, 441 | }, 442 | }, 443 | ListResourceTemplatesResultSchema 444 | ); 445 | 446 | if (result.resourceTemplates) { 447 | const templatesWithSource = result.resourceTemplates.map( 448 | (template) => ({ 449 | ...template, 450 | name: template.name || "", 451 | }) 452 | ); 453 | allTemplates.push(...templatesWithSource); 454 | } 455 | } catch (error) { 456 | return; 457 | } 458 | }) 459 | ); 460 | 461 | return { 462 | resourceTemplates: allTemplates, 463 | nextCursor: request.params?.cursor, 464 | }; 465 | } 466 | ); 467 | 468 | const cleanup = async () => { 469 | await cleanupAllSessions(); 470 | }; 471 | 472 | return { server, cleanup }; 473 | }; 474 | -------------------------------------------------------------------------------- /src/report-tools.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { getMetaMcpApiBaseUrl, getMetaMcpApiKey } from "./utils.js"; 3 | import { getMcpServers } from "./fetch-metamcp.js"; 4 | import { initSessions, getSession } from "./sessions.js"; 5 | import { getSessionKey } from "./utils.js"; 6 | import { ListToolsResultSchema } from "@modelcontextprotocol/sdk/types.js"; 7 | 8 | // Define interface for tool data structure 9 | export interface MetaMcpTool { 10 | name: string; 11 | description?: string; 12 | toolSchema: any; 13 | mcp_server_uuid: string; 14 | } 15 | 16 | // API route handler for submitting tools to MetaMCP 17 | export async function reportToolsToMetaMcp(tools: MetaMcpTool[]) { 18 | try { 19 | const apiKey = getMetaMcpApiKey(); 20 | const apiBaseUrl = getMetaMcpApiBaseUrl(); 21 | 22 | if (!apiKey) { 23 | return { error: "API key not set" }; 24 | } 25 | 26 | // Validate that tools is an array 27 | if (!Array.isArray(tools) || tools.length === 0) { 28 | return { 29 | error: "Request must include a non-empty array of tools", 30 | status: 400, 31 | }; 32 | } 33 | 34 | // Validate required fields for all tools and prepare for submission 35 | const validTools = []; 36 | const errors = []; 37 | 38 | for (const tool of tools) { 39 | const { name, description, toolSchema, mcp_server_uuid } = tool; 40 | 41 | // Validate required fields for each tool 42 | if (!name || !toolSchema || !mcp_server_uuid) { 43 | errors.push({ 44 | tool, 45 | error: 46 | "Missing required fields: name, toolSchema, or mcp_server_uuid", 47 | }); 48 | continue; 49 | } 50 | 51 | validTools.push({ 52 | name, 53 | description, 54 | toolSchema, 55 | mcp_server_uuid, 56 | }); 57 | } 58 | 59 | // Submit valid tools to MetaMCP API 60 | let results: any[] = []; 61 | if (validTools.length > 0) { 62 | try { 63 | const response = await axios.post( 64 | `${apiBaseUrl}/api/tools`, 65 | { tools: validTools }, 66 | { 67 | headers: { 68 | "Content-Type": "application/json", 69 | Authorization: `Bearer ${apiKey}`, 70 | }, 71 | } 72 | ); 73 | 74 | results = response.data.results || []; 75 | } catch (error: any) { 76 | if (error.response) { 77 | // The request was made and the server responded with a status code outside of 2xx 78 | return { 79 | error: error.response.data.error || "Failed to submit tools", 80 | status: error.response.status, 81 | details: error.response.data, 82 | }; 83 | } else if (error.request) { 84 | // The request was made but no response was received 85 | return { 86 | error: "No response received from server", 87 | details: error.request, 88 | }; 89 | } else { 90 | // Something happened in setting up the request 91 | return { 92 | error: "Error setting up request", 93 | details: error.message, 94 | }; 95 | } 96 | } 97 | } 98 | 99 | return { 100 | results, 101 | errors, 102 | success: results.length > 0, 103 | failureCount: errors.length, 104 | successCount: results.length, 105 | }; 106 | } catch (error: any) { 107 | return { 108 | error: "Failed to process tools request", 109 | status: 500, 110 | }; 111 | } 112 | } 113 | 114 | // Function to fetch all MCP servers, initialize clients, and report tools to MetaMCP API 115 | export async function reportAllTools() { 116 | console.log("Fetching all MCPs and initializing clients..."); 117 | 118 | // Get all MCP servers 119 | const serverParams = await getMcpServers(); 120 | 121 | // Initialize all sessions 122 | await initSessions(); 123 | 124 | console.log(`Found ${Object.keys(serverParams).length} MCP servers`); 125 | 126 | // For each server, get its tools and report them 127 | await Promise.allSettled( 128 | Object.entries(serverParams).map(async ([uuid, params]) => { 129 | const sessionKey = getSessionKey(uuid, params); 130 | const session = await getSession(sessionKey, uuid, params); 131 | 132 | if (!session) { 133 | console.log(`Could not establish session for ${params.name} (${uuid})`); 134 | return; 135 | } 136 | 137 | const capabilities = session.client.getServerCapabilities(); 138 | if (!capabilities?.tools) { 139 | console.log(`Server ${params.name} (${uuid}) does not support tools`); 140 | return; 141 | } 142 | 143 | try { 144 | console.log(`Fetching tools from ${params.name} (${uuid})...`); 145 | 146 | const result = await session.client.request( 147 | { method: "tools/list", params: {} }, 148 | ListToolsResultSchema 149 | ); 150 | 151 | if (result.tools && result.tools.length > 0) { 152 | console.log( 153 | `Reporting ${result.tools.length} tools from ${params.name} to MetaMCP API...` 154 | ); 155 | 156 | const reportResult = await reportToolsToMetaMcp( 157 | result.tools.map((tool) => ({ 158 | name: tool.name, 159 | description: tool.description, 160 | toolSchema: tool.inputSchema, 161 | mcp_server_uuid: uuid, 162 | })) 163 | ); 164 | 165 | console.log( 166 | `Reported tools from ${params.name}: ${reportResult.successCount} succeeded, ${reportResult.failureCount} failed` 167 | ); 168 | } else { 169 | console.log(`No tools found for ${params.name}`); 170 | } 171 | } catch (error) { 172 | console.error(`Error reporting tools for ${params.name}:`, error); 173 | } 174 | }) 175 | ); 176 | 177 | console.log("Finished reporting all tools to MetaMCP API"); 178 | process.exit(0); 179 | } 180 | -------------------------------------------------------------------------------- /src/sessions.ts: -------------------------------------------------------------------------------- 1 | import { getMcpServers, ServerParameters } from "./fetch-metamcp.js"; 2 | import { 3 | ConnectedClient, 4 | createMetaMcpClient, 5 | connectMetaMcpClient, 6 | } from "./client.js"; 7 | import { getSessionKey } from "./utils.js"; 8 | 9 | const _sessions: Record = {}; 10 | 11 | export const getSession = async ( 12 | sessionKey: string, 13 | uuid: string, 14 | params: ServerParameters 15 | ): Promise => { 16 | if (sessionKey in _sessions) { 17 | return _sessions[sessionKey]; 18 | } else { 19 | // Close existing session for this UUID if it exists with a different hash 20 | const old_session_keys = Object.keys(_sessions).filter((k) => 21 | k.startsWith(`${uuid}_`) 22 | ); 23 | 24 | await Promise.allSettled( 25 | old_session_keys.map(async (old_session_key) => { 26 | await _sessions[old_session_key].cleanup(); 27 | delete _sessions[old_session_key]; 28 | }) 29 | ); 30 | 31 | const { client, transport } = createMetaMcpClient(params); 32 | if (!client || !transport) { 33 | return; 34 | } 35 | 36 | const newClient = await connectMetaMcpClient(client, transport); 37 | if (!newClient) { 38 | return; 39 | } 40 | 41 | _sessions[sessionKey] = newClient; 42 | 43 | return newClient; 44 | } 45 | }; 46 | 47 | export const initSessions = async (): Promise => { 48 | const serverParams = await getMcpServers(true); 49 | 50 | await Promise.allSettled( 51 | Object.entries(serverParams).map(async ([uuid, params]) => { 52 | const sessionKey = getSessionKey(uuid, params); 53 | try { 54 | await getSession(sessionKey, uuid, params); 55 | } catch (error) {} 56 | }) 57 | ); 58 | }; 59 | 60 | export const cleanupAllSessions = async (): Promise => { 61 | await Promise.allSettled( 62 | Object.entries(_sessions).map(async ([sessionKey, session]) => { 63 | await session.cleanup(); 64 | delete _sessions[sessionKey]; 65 | }) 66 | ); 67 | }; 68 | -------------------------------------------------------------------------------- /src/sse.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; 3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 4 | 5 | export interface SSEServerOptions { 6 | port: number; 7 | requireApiAuth?: boolean; 8 | } 9 | 10 | // Starts an SSE server and returns a cleanup function 11 | export async function startSSEServer( 12 | server: Server, 13 | options: SSEServerOptions 14 | ): Promise<() => Promise> { 15 | const app = express(); 16 | const port = options.port || 12006; 17 | const requireApiAuth = options.requireApiAuth || false; 18 | const apiKey = process.env.METAMCP_API_KEY; 19 | 20 | // to support multiple simultaneous connections we have a lookup object from 21 | // sessionId to transport 22 | const transports: { [sessionId: string]: SSEServerTransport } = {}; 23 | 24 | // Define the SSE endpoint based on authentication requirement 25 | const sseEndpoint = requireApiAuth ? `/:apiKey/sse` : `/sse`; 26 | 27 | app.get(sseEndpoint, async (req: express.Request, res: express.Response) => { 28 | // If API auth is required, validate the API key 29 | if (requireApiAuth) { 30 | const requestApiKey = req.params.apiKey; 31 | if (!apiKey || requestApiKey !== apiKey) { 32 | res.status(401).send("Unauthorized: Invalid API key"); 33 | return; 34 | } 35 | } 36 | 37 | // Set the messages path based on authentication requirement 38 | const messagesPath = requireApiAuth ? `/${apiKey}/messages` : `/messages`; 39 | const transport = new SSEServerTransport(messagesPath, res); 40 | transports[transport.sessionId] = transport; 41 | res.on("close", () => { 42 | delete transports[transport.sessionId]; 43 | }); 44 | await server.connect(transport); 45 | }); 46 | 47 | // Define the messages endpoint 48 | const messagesEndpoint = requireApiAuth ? `/:apiKey/messages` : `/messages`; 49 | 50 | app.post( 51 | messagesEndpoint, 52 | async (req: express.Request, res: express.Response) => { 53 | // If API auth is required, validate the API key 54 | if (requireApiAuth) { 55 | const requestApiKey = req.params.apiKey; 56 | if (!apiKey || requestApiKey !== apiKey) { 57 | res.status(401).send("Unauthorized: Invalid API key"); 58 | return; 59 | } 60 | } 61 | 62 | const sessionId = req.query.sessionId as string; 63 | const transport = transports[sessionId]; 64 | if (transport) { 65 | await transport.handlePostMessage(req, res); 66 | } else { 67 | res.status(400).send("No transport found for sessionId"); 68 | } 69 | } 70 | ); 71 | 72 | const serverInstance = app.listen(port, () => { 73 | const baseUrl = `http://localhost:${port}`; 74 | const sseUrl = requireApiAuth 75 | ? `${baseUrl}/${apiKey}/sse` 76 | : `${baseUrl}/sse`; 77 | console.log(`SSE server listening on port ${port}`); 78 | console.log(`SSE endpoint: ${sseUrl}`); 79 | }); 80 | 81 | // Return cleanup function 82 | return async () => { 83 | // Close all active transports 84 | await Promise.all( 85 | Object.values(transports).map((transport) => transport.close()) 86 | ); 87 | serverInstance.close(); 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /src/streamable-http.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; 3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 4 | import { randomUUID } from "crypto"; 5 | 6 | export interface StreamableHTTPServerOptions { 7 | port: number; 8 | requireApiAuth?: boolean; 9 | stateless?: boolean; 10 | } 11 | 12 | // Starts a Streamable HTTP server and returns a cleanup function 13 | export async function startStreamableHTTPServer( 14 | server: Server, 15 | options: StreamableHTTPServerOptions 16 | ): Promise<() => Promise> { 17 | const app = express(); 18 | app.use(express.json()); 19 | 20 | const port = options.port || 12006; 21 | const requireApiAuth = options.requireApiAuth || false; 22 | const stateless = options.stateless || false; 23 | const apiKey = process.env.METAMCP_API_KEY; 24 | 25 | // Map to store transports by session ID (when using stateful mode) 26 | const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; 27 | 28 | // Define the MCP endpoint path based on authentication requirement 29 | const mcpEndpoint = requireApiAuth ? `/:apiKey/mcp` : `/mcp`; 30 | 31 | // Handle all HTTP methods for the MCP endpoint 32 | app.all(mcpEndpoint, async (req: express.Request, res: express.Response) => { 33 | // If API auth is required, validate the API key 34 | if (requireApiAuth) { 35 | const requestApiKey = req.params.apiKey; 36 | if (!apiKey || requestApiKey !== apiKey) { 37 | res.status(401).send("Unauthorized: Invalid API key"); 38 | return; 39 | } 40 | } 41 | 42 | if (stateless) { 43 | // Stateless mode: Create a new transport for each request 44 | const transport = new StreamableHTTPServerTransport({ 45 | sessionIdGenerator: undefined, // No session management 46 | }); 47 | 48 | res.on("close", () => { 49 | transport.close(); 50 | }); 51 | 52 | try { 53 | // Connect to the server 54 | await server.connect(transport); 55 | // Handle the request 56 | await transport.handleRequest(req, res, req.body); 57 | } catch (error) { 58 | console.error("Error handling streamable HTTP request:", error); 59 | if (!res.headersSent) { 60 | res.status(500).json({ 61 | jsonrpc: "2.0", 62 | error: { 63 | code: -32603, 64 | message: "Internal server error", 65 | }, 66 | id: null, 67 | }); 68 | } 69 | } 70 | } else { 71 | // Stateful mode: Use session management 72 | const sessionId = req.headers["mcp-session-id"] as string | undefined; 73 | let transport: StreamableHTTPServerTransport; 74 | 75 | if (sessionId && transports[sessionId]) { 76 | // Reuse existing transport 77 | transport = transports[sessionId]; 78 | } else if (!sessionId && req.method === "POST") { 79 | // New initialization request 80 | transport = new StreamableHTTPServerTransport({ 81 | sessionIdGenerator: () => randomUUID(), 82 | onsessioninitialized: (sessionId) => { 83 | // Store the transport by session ID 84 | transports[sessionId] = transport; 85 | } 86 | }); 87 | 88 | // Clean up transport when closed 89 | transport.onclose = () => { 90 | if (transport.sessionId) { 91 | delete transports[transport.sessionId]; 92 | } 93 | }; 94 | 95 | // Connect to the server 96 | await server.connect(transport); 97 | } else { 98 | // Invalid request 99 | res.status(400).json({ 100 | jsonrpc: "2.0", 101 | error: { 102 | code: -32000, 103 | message: "Bad Request: No valid session ID provided", 104 | }, 105 | id: null, 106 | }); 107 | return; 108 | } 109 | 110 | try { 111 | // Handle the request 112 | await transport.handleRequest(req, res, req.body); 113 | } catch (error) { 114 | console.error("Error handling streamable HTTP request:", error); 115 | if (!res.headersSent) { 116 | res.status(500).json({ 117 | jsonrpc: "2.0", 118 | error: { 119 | code: -32603, 120 | message: "Internal server error", 121 | }, 122 | id: null, 123 | }); 124 | } 125 | } 126 | } 127 | }); 128 | 129 | const serverInstance = app.listen(port, () => { 130 | const baseUrl = `http://localhost:${port}`; 131 | const mcpUrl = requireApiAuth ? `${baseUrl}/${apiKey}/mcp` : `${baseUrl}/mcp`; 132 | console.log(`Streamable HTTP server listening on port ${port}`); 133 | console.log(`MCP endpoint: ${mcpUrl}`); 134 | console.log(`Mode: ${stateless ? "Stateless" : "Stateful"}`); 135 | }); 136 | 137 | // Return cleanup function 138 | return async () => { 139 | // Close all active transports 140 | await Promise.all( 141 | Object.values(transports).map((transport) => transport.close()) 142 | ); 143 | serverInstance.close(); 144 | }; 145 | } -------------------------------------------------------------------------------- /src/tool-logs.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { getMetaMcpApiBaseUrl, getMetaMcpApiKey } from "./utils.js"; 3 | import { 4 | ProfileCapability, 5 | getProfileCapabilities, 6 | } from "./fetch-capabilities.js"; 7 | 8 | // Define status enum for tool execution 9 | export enum ToolExecutionStatus { 10 | SUCCESS = "SUCCESS", 11 | ERROR = "ERROR", 12 | PENDING = "PENDING", 13 | } 14 | 15 | // Define interface for tool execution log data 16 | export interface ToolExecutionLog { 17 | id?: string; 18 | tool_name: string; 19 | payload: any; 20 | status: ToolExecutionStatus; 21 | result?: any; 22 | mcp_server_uuid: string; 23 | error_message?: string | null; 24 | execution_time_ms: number; 25 | created_at?: string; 26 | updated_at?: string; 27 | } 28 | 29 | // Response interfaces 30 | export interface ToolLogResponse { 31 | id?: string; 32 | success: boolean; 33 | data?: any; 34 | error?: string; 35 | status?: number; 36 | details?: any; 37 | } 38 | 39 | // Class to manage tool execution logs 40 | export class ToolLogManager { 41 | private static instance: ToolLogManager; 42 | private logStore: Map = new Map(); 43 | 44 | private constructor() {} 45 | 46 | public static getInstance(): ToolLogManager { 47 | if (!ToolLogManager.instance) { 48 | ToolLogManager.instance = new ToolLogManager(); 49 | } 50 | return ToolLogManager.instance; 51 | } 52 | 53 | /** 54 | * Creates a new tool execution log 55 | * @param toolName Name of the tool 56 | * @param serverUuid UUID of the MCP server 57 | * @param payload The input parameters for the tool 58 | * @returns Log object with tracking ID 59 | */ 60 | public async createLog( 61 | toolName: string, 62 | serverUuid: string, 63 | payload: any 64 | ): Promise { 65 | // Check for TOOL_LOGS capability first 66 | const profileCapabilities = await getProfileCapabilities(); 67 | const hasToolsLogCapability = profileCapabilities.includes( 68 | ProfileCapability.TOOL_LOGS 69 | ); 70 | 71 | // Generate a temporary ID for tracking 72 | const tempId = `${Date.now()}-${Math.random() 73 | .toString(36) 74 | .substring(2, 9)}`; 75 | 76 | const log: ToolExecutionLog = { 77 | id: tempId, // Will be replaced with the real ID from the API 78 | tool_name: toolName, 79 | mcp_server_uuid: serverUuid, 80 | payload, 81 | status: ToolExecutionStatus.PENDING, 82 | execution_time_ms: 0, 83 | created_at: new Date().toISOString(), 84 | }; 85 | 86 | // Store in memory 87 | this.logStore.set(tempId, log); 88 | 89 | // Submit to API only if TOOL_LOGS capability is present 90 | if (hasToolsLogCapability) { 91 | const response = await reportToolExecutionLog(log); 92 | 93 | // Update with real ID if available 94 | if (response.success && response.data?.id) { 95 | const newId = response.data.id; 96 | log.id = newId; 97 | this.logStore.delete(tempId); 98 | this.logStore.set(newId, log); 99 | } 100 | } 101 | 102 | return log; 103 | } 104 | 105 | /** 106 | * Updates the status of a tool execution log 107 | * @param logId ID of the log to update 108 | * @param status New status 109 | * @param result Optional result data 110 | * @param errorMessage Optional error message 111 | * @param executionTimeMs Optional execution time in milliseconds 112 | * @returns Updated log 113 | */ 114 | public async updateLogStatus( 115 | logId: string, 116 | status: ToolExecutionStatus, 117 | result?: any, 118 | errorMessage?: string | null, 119 | executionTimeMs?: number 120 | ): Promise { 121 | const log = this.logStore.get(logId); 122 | 123 | if (!log) { 124 | console.error(`Cannot update log: Log with ID ${logId} not found`); 125 | return null; 126 | } 127 | 128 | // Update log properties 129 | log.status = status; 130 | if (result !== undefined) log.result = result; 131 | if (errorMessage !== undefined) log.error_message = errorMessage; 132 | if (executionTimeMs !== undefined) log.execution_time_ms = executionTimeMs; 133 | log.updated_at = new Date().toISOString(); 134 | 135 | // Update in memory 136 | this.logStore.set(logId, log); 137 | 138 | // Check for TOOL_LOGS capability before sending update to API 139 | const profileCapabilities = await getProfileCapabilities(); 140 | const hasToolsLogCapability = profileCapabilities.includes( 141 | ProfileCapability.TOOL_LOGS 142 | ); 143 | 144 | // Send update to API only if TOOL_LOGS capability is present 145 | if (hasToolsLogCapability) { 146 | await updateToolExecutionLog(logId, { 147 | status, 148 | result, 149 | error_message: errorMessage, 150 | execution_time_ms: executionTimeMs, 151 | }); 152 | } 153 | 154 | return log; 155 | } 156 | 157 | /** 158 | * Get a log by ID 159 | * @param logId ID of the log 160 | * @returns Log or null if not found 161 | */ 162 | public getLog(logId: string): ToolExecutionLog | null { 163 | return this.logStore.get(logId) || null; 164 | } 165 | 166 | /** 167 | * Complete the tool execution log with success status 168 | * @param logId ID of the log to complete 169 | * @param result Result data 170 | * @param executionTimeMs Execution time in milliseconds 171 | * @returns Updated log 172 | */ 173 | public async completeLog( 174 | logId: string, 175 | result: any, 176 | executionTimeMs: number 177 | ): Promise { 178 | return this.updateLogStatus( 179 | logId, 180 | ToolExecutionStatus.SUCCESS, 181 | result, 182 | null, 183 | executionTimeMs 184 | ); 185 | } 186 | 187 | /** 188 | * Mark the tool execution log as failed 189 | * @param logId ID of the log to fail 190 | * @param errorMessage Error message 191 | * @param executionTimeMs Execution time in milliseconds 192 | * @returns Updated log 193 | */ 194 | public async failLog( 195 | logId: string, 196 | errorMessage: string, 197 | executionTimeMs: number 198 | ): Promise { 199 | return this.updateLogStatus( 200 | logId, 201 | ToolExecutionStatus.ERROR, 202 | null, 203 | errorMessage, 204 | executionTimeMs 205 | ); 206 | } 207 | } 208 | 209 | /** 210 | * Reports a tool execution log to the MetaMCP API 211 | * @param logData The tool execution log data 212 | * @returns Result of the API call 213 | */ 214 | export async function reportToolExecutionLog( 215 | logData: ToolExecutionLog 216 | ): Promise { 217 | try { 218 | // Check for TOOL_LOGS capability first 219 | const profileCapabilities = await getProfileCapabilities(); 220 | const hasToolsLogCapability = profileCapabilities.includes( 221 | ProfileCapability.TOOL_LOGS 222 | ); 223 | 224 | if (!hasToolsLogCapability) { 225 | return { success: false, error: "TOOL_LOGS capability not enabled" }; 226 | } 227 | 228 | const apiKey = getMetaMcpApiKey(); 229 | const apiBaseUrl = getMetaMcpApiBaseUrl(); 230 | 231 | if (!apiKey) { 232 | return { success: false, error: "API key not set" }; 233 | } 234 | 235 | // Validate required fields 236 | if (!logData.tool_name || !logData.mcp_server_uuid) { 237 | return { 238 | success: false, 239 | error: "Missing required fields: tool_name or mcp_server_uuid", 240 | status: 400, 241 | }; 242 | } 243 | 244 | // Submit log to MetaMCP API 245 | try { 246 | const response = await axios.post( 247 | `${apiBaseUrl}/api/tool-execution-logs`, 248 | logData, 249 | { 250 | headers: { 251 | "Content-Type": "application/json", 252 | Authorization: `Bearer ${apiKey}`, 253 | }, 254 | } 255 | ); 256 | 257 | return { 258 | success: true, 259 | data: response.data, 260 | }; 261 | } catch (error: any) { 262 | if (error.response) { 263 | // The request was made and the server responded with a status code outside of 2xx 264 | return { 265 | success: false, 266 | error: 267 | error.response.data.error || "Failed to submit tool execution log", 268 | status: error.response.status, 269 | details: error.response.data, 270 | }; 271 | } else if (error.request) { 272 | // The request was made but no response was received 273 | return { 274 | success: false, 275 | error: "No response received from server", 276 | details: error.request, 277 | }; 278 | } else { 279 | // Something happened in setting up the request 280 | return { 281 | success: false, 282 | error: "Error setting up request", 283 | details: error.message, 284 | }; 285 | } 286 | } 287 | } catch (error: any) { 288 | return { 289 | success: false, 290 | error: "Failed to process tool execution log request", 291 | status: 500, 292 | details: error.message, 293 | }; 294 | } 295 | } 296 | 297 | /** 298 | * Updates an existing tool execution log 299 | * @param logId The ID of the log to update 300 | * @param updateData The updated log data 301 | * @returns Result of the API call 302 | */ 303 | export async function updateToolExecutionLog( 304 | logId: string, 305 | updateData: Partial 306 | ): Promise { 307 | try { 308 | // Check for TOOL_LOGS capability first 309 | const profileCapabilities = await getProfileCapabilities(); 310 | const hasToolsLogCapability = profileCapabilities.includes( 311 | ProfileCapability.TOOL_LOGS 312 | ); 313 | 314 | if (!hasToolsLogCapability) { 315 | return { success: false, error: "TOOL_LOGS capability not enabled" }; 316 | } 317 | 318 | const apiKey = getMetaMcpApiKey(); 319 | const apiBaseUrl = getMetaMcpApiBaseUrl(); 320 | 321 | if (!apiKey) { 322 | return { success: false, error: "API key not set" }; 323 | } 324 | 325 | if (!logId) { 326 | return { 327 | success: false, 328 | error: "Log ID is required for updates", 329 | }; 330 | } 331 | 332 | // Submit update to MetaMCP API 333 | try { 334 | const response = await axios.put( 335 | `${apiBaseUrl}/api/tool-execution-logs/${logId}`, 336 | updateData, 337 | { 338 | headers: { 339 | "Content-Type": "application/json", 340 | Authorization: `Bearer ${apiKey}`, 341 | }, 342 | } 343 | ); 344 | 345 | return { 346 | success: true, 347 | data: response.data, 348 | }; 349 | } catch (error: any) { 350 | if (error.response) { 351 | return { 352 | success: false, 353 | error: 354 | error.response.data.error || "Failed to update tool execution log", 355 | status: error.response.status, 356 | details: error.response.data, 357 | }; 358 | } else if (error.request) { 359 | return { 360 | success: false, 361 | error: "No response received from server", 362 | details: error.request, 363 | }; 364 | } else { 365 | return { 366 | success: false, 367 | error: "Error setting up request", 368 | details: error.message, 369 | }; 370 | } 371 | } 372 | } catch (error: any) { 373 | return { 374 | success: false, 375 | error: "Failed to process update request", 376 | status: 500, 377 | details: error.message, 378 | }; 379 | } 380 | } 381 | 382 | /** 383 | * Simple function to log a tool execution 384 | * @param toolName Name of the tool 385 | * @param serverUuid UUID of the MCP server 386 | * @param payload The input parameters for the tool 387 | * @param result The result of the tool execution 388 | * @param status Status of the execution 389 | * @param errorMessage Optional error message if execution failed 390 | * @param executionTimeMs Time taken to execute the tool in milliseconds 391 | * @returns Result of the API call 392 | */ 393 | export async function logToolExecution( 394 | toolName: string, 395 | serverUuid: string, 396 | payload: any, 397 | result: any = null, 398 | status: ToolExecutionStatus = ToolExecutionStatus.SUCCESS, 399 | errorMessage: string | null = null, 400 | executionTimeMs: number = 0 401 | ): Promise { 402 | // Check for TOOL_LOGS capability first 403 | const profileCapabilities = await getProfileCapabilities(); 404 | const hasToolsLogCapability = profileCapabilities.includes( 405 | ProfileCapability.TOOL_LOGS 406 | ); 407 | 408 | if (!hasToolsLogCapability) { 409 | return { success: false, error: "TOOL_LOGS capability not enabled" }; 410 | } 411 | 412 | const logData: ToolExecutionLog = { 413 | tool_name: toolName, 414 | mcp_server_uuid: serverUuid, 415 | payload, 416 | status, 417 | result, 418 | error_message: errorMessage, 419 | execution_time_ms: executionTimeMs, 420 | }; 421 | 422 | return await reportToolExecutionLog(logData); 423 | } 424 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { ServerParameters } from "./fetch-metamcp.js"; 2 | import crypto from "crypto"; 3 | 4 | /** 5 | * Environment variables to inherit by default, if an environment is not explicitly given. 6 | */ 7 | export const DEFAULT_INHERITED_ENV_VARS = 8 | process.platform === "win32" 9 | ? [ 10 | "APPDATA", 11 | "HOMEDRIVE", 12 | "HOMEPATH", 13 | "LOCALAPPDATA", 14 | "PATH", 15 | "PROCESSOR_ARCHITECTURE", 16 | "SYSTEMDRIVE", 17 | "SYSTEMROOT", 18 | "TEMP", 19 | "USERNAME", 20 | "USERPROFILE", 21 | ] 22 | : /* list inspired by the default env inheritance of sudo */ 23 | ["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"]; 24 | 25 | /** 26 | * Returns a default environment object including only environment variables deemed safe to inherit. 27 | */ 28 | export function getDefaultEnvironment(): Record { 29 | const env: Record = {}; 30 | 31 | for (const key of DEFAULT_INHERITED_ENV_VARS) { 32 | const value = process.env[key]; 33 | if (value === undefined) { 34 | continue; 35 | } 36 | 37 | if (value.startsWith("()")) { 38 | // Skip functions, which are a security risk. 39 | continue; 40 | } 41 | 42 | env[key] = value; 43 | } 44 | 45 | return env; 46 | } 47 | 48 | /** 49 | * Get the MetaMCP API base URL from environment variables 50 | */ 51 | export function getMetaMcpApiBaseUrl(): string { 52 | return process.env.METAMCP_API_BASE_URL || "https://api.metamcp.com"; 53 | } 54 | 55 | /** 56 | * Get the MetaMCP API key from environment variables 57 | */ 58 | export function getMetaMcpApiKey(): string | undefined { 59 | return process.env.METAMCP_API_KEY; 60 | } 61 | 62 | export function sanitizeName(name: string): string { 63 | return name.replace(/[^a-zA-Z0-9_-]/g, ""); 64 | } 65 | 66 | export function computeParamsHash( 67 | params: ServerParameters, 68 | uuid: string 69 | ): string { 70 | let paramsDict: any; 71 | 72 | // Default to "STDIO" if type is undefined 73 | if (!params.type || params.type === "STDIO") { 74 | paramsDict = { 75 | uuid, 76 | type: "STDIO", // Explicitly set type to "STDIO" for consistent hashing 77 | command: params.command, 78 | args: params.args, 79 | env: params.env 80 | ? Object.fromEntries( 81 | Object.entries(params.env).sort((a, b) => a[0].localeCompare(b[0])) 82 | ) 83 | : null, 84 | }; 85 | } else if (params.type === "SSE" || params.type === "STREAMABLE_HTTP") { 86 | paramsDict = { 87 | uuid, 88 | type: params.type, 89 | url: params.url, 90 | }; 91 | } else { 92 | throw new Error(`Unsupported server type: ${params.type}`); 93 | } 94 | 95 | const paramsJson = JSON.stringify(paramsDict); 96 | return crypto.createHash("sha256").update(paramsJson).digest("hex"); 97 | } 98 | 99 | export function getSessionKey(uuid: string, params: ServerParameters): string { 100 | return `${uuid}_${computeParamsHash(params, uuid)}`; 101 | } 102 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | --------------------------------------------------------------------------------