From 959e4210aa81b62b334b69298187b103b78903be Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 3 Jan 2025 08:58:48 +0100 Subject: [PATCH 01/54] first version --- .actor/Dockerfile | 57 + .actor/actor.json | 9 + .actor/input_schema.json | 41 + .dockerignore | 1 + .gitignore | 1 + package-lock.json | 6404 ++++++++++++++++++++++++++++++++++++++ package.json | 38 + src/actorsInputSchema.ts | 95 + src/const.ts | 15 + src/input.ts | 11 + src/main.ts | 71 + src/server.ts | 111 + src/types.ts | 5 + 13 files changed, 6859 insertions(+) create mode 100644 .actor/Dockerfile create mode 100644 .actor/actor.json create mode 100644 .actor/input_schema.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/actorsInputSchema.ts create mode 100644 src/const.ts create mode 100644 src/input.ts create mode 100644 src/main.ts create mode 100644 src/server.ts create mode 100644 src/types.ts diff --git a/.actor/Dockerfile b/.actor/Dockerfile new file mode 100644 index 0000000..0fcb573 --- /dev/null +++ b/.actor/Dockerfile @@ -0,0 +1,57 @@ +# Specify the base Docker image. You can read more about +# the available images at https://docs.apify.com/sdk/js/docs/guides/docker-images +# You can also use any other image from Docker Hub. +FROM apify/actor-node:20 AS builder + +# Check preinstalled packages +RUN npm ls crawlee apify puppeteer playwright + +# Copy just package.json and package-lock.json +# to speed up the build using Docker layer cache. +COPY package*.json ./ + +# Install all dependencies. Don't audit to speed up the installation. +RUN npm install --include=dev --audit=false + +# Next, copy the source files using the user set +# in the base image. +COPY . ./ + +# Install all dependencies and build the project. +# Don't audit to speed up the installation. +RUN npm run build + +# Create final image +FROM apify/actor-node:20 + +# Check preinstalled packages +RUN npm ls crawlee apify puppeteer playwright + +# Copy just package.json and package-lock.json +# to speed up the build using Docker layer cache. +COPY package*.json ./ + +# Install NPM packages, skip optional and development dependencies to +# keep the image small. Avoid logging too much and print the dependency +# tree for debugging +RUN npm --quiet set progress=false \ + && npm install --omit=dev --omit=optional \ + && echo "Installed NPM packages:" \ + && (npm list --omit=dev --all || true) \ + && echo "Node.js version:" \ + && node --version \ + && echo "NPM version:" \ + && npm --version \ + && rm -r ~/.npm + +# Copy built JS files from builder image +COPY --from=builder /usr/src/app/dist ./dist + +# Next, copy the remaining files and directories with the source code. +# Since we do this after NPM install, quick build will be really fast +# for most source file changes. +COPY . ./ + + +# Run the image. +CMD npm run start:prod --silent diff --git a/.actor/actor.json b/.actor/actor.json new file mode 100644 index 0000000..37f81fd --- /dev/null +++ b/.actor/actor.json @@ -0,0 +1,9 @@ +{ + "actorSpecification": 1, + "name": "apify-mcp-server", + "title": "Model Context Protocol Server for Apify Actors", + "description": "Implementation of a Model Context Protocol (MCP) Server for Apify Actors that enables AI applications (and AI agents) to interact with Apify Actors", + "version": "0.0", + "input": "./input_schema.json", + "dockerfile": "./Dockerfile" +} diff --git a/.actor/input_schema.json b/.actor/input_schema.json new file mode 100644 index 0000000..851a1f7 --- /dev/null +++ b/.actor/input_schema.json @@ -0,0 +1,41 @@ +{ + "title": "Apify MCP Server", + "type": "object", + "schemaVersion": 1, + "properties": { + "actorNames": { + "title": "Actors names to be exposed for an AI application (AI agent)", + "type": "array", + "description": "Specify the names of actors that will be exposed to an AI application (AI agent) and communicate with Apify Actors via the MCP protocol.", + "editor": "stringList", + "prefill": [ + "apify/website-content-crawler", + "compass/google-maps-extractor", + "apify/google-search-scraper", + "apify/instagram-scraper", + "apify/facebook-posts-scraper", + "apify/rag-web-browser" + ] + }, + "debugActorName": { + "title": "Debug actor name", + "type": "string", + "description": "Specify the name of the actor that will be used for debugging in normal mode.", + "editor": "textfield", + "prefill": "apify/rag-web-browser", + "sectionCaption": "Debugging settings (normal mode)" + }, + "debugActorInput": { + "title": "Debug actor input", + "type": "object", + "description": "Specify the input for the actor that will be used for debugging in normal mode.", + "editor": "json", + "prefill": { + "query": "hello world" + } + } + }, + "required": [ + "actorNames" + ] +} diff --git a/.dockerignore b/.dockerignore index 3ffcc99..666793e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,3 +16,4 @@ node_modules data src/storage dist +.env diff --git a/.gitignore b/.gitignore index 191865a..1e23c8f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ storage/key_value_stores/default/* # Added by Apify CLI .venv +.env diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5494f32 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6404 @@ +{ + "name": "apify-mcp-server", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "apify-mcp-server", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.4", + "ajv": "^8.17.1", + "apify": "^3.2.6", + "apify-client": "^2.11.0", + "dotenv": "^16.4.7", + "express": "^4.21.2" + }, + "devDependencies": { + "@apify/eslint-config": "^0.5.0-beta.2", + "@apify/tsconfig": "^0.1.0", + "@types/express": "^5.0.0", + "eslint": "^9.17.0", + "tsx": "^4.6.2", + "typescript": "^5.3.3", + "typescript-eslint": "^8.18.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@apify/consts": { + "version": "2.35.0", + "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.35.0.tgz", + "integrity": "sha512-ICUoIzyxSWuQZXtMwMxrnfogPTagQS2fDY1jgZZCikv4WF0c4a3/HKpIprHZ3lI90+jjF0r8TtEVEcWIaRgP4Q==", + "license": "Apache-2.0" + }, + "node_modules/@apify/datastructures": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@apify/datastructures/-/datastructures-2.0.2.tgz", + "integrity": "sha512-IN9A0s2SCHoZZE1tf4xKgk4fxHM5/0I/UrXhWbn/rSv7E5sA2o0NyHdwcMY2Go9f5qd+E7VAbX6WnESTE6GLeA==", + "license": "Apache-2.0" + }, + "node_modules/@apify/eslint-config": { + "version": "0.5.0-beta.2", + "resolved": "https://registry.npmjs.org/@apify/eslint-config/-/eslint-config-0.5.0-beta.2.tgz", + "integrity": "sha512-I6Eb8w62HTMRVVjp86NtV1nQiU24qycqOrEA0NkYuAjcdZiNTmxVBoz1hIyQcerp9kdQ9r3c3p9I6lb+uoRuyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@eslint/compat": "^1.2.2", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.31.0", + "globals": "^15.11.0" + }, + "peerDependencies": { + "eslint": "^9.0.0", + "typescript-eslint": "^8.0.0" + }, + "peerDependenciesMeta": { + "typescript-eslint": { + "optional": true + } + } + }, + "node_modules/@apify/input_secrets": { + "version": "1.1.60", + "resolved": "https://registry.npmjs.org/@apify/input_secrets/-/input_secrets-1.1.60.tgz", + "integrity": "sha512-ZSL6aCjCs/zjq5Y6187G6FlstGKmUD3vmx4Ew+C20lnxRyAFUd3wuk+lrDvfuRl9KB8GHvXod+UIGna6+/UMZw==", + "license": "Apache-2.0", + "dependencies": { + "@apify/log": "^2.5.11", + "@apify/utilities": "^2.11.1", + "ow": "^0.28.2" + } + }, + "node_modules/@apify/log": { + "version": "2.5.11", + "resolved": "https://registry.npmjs.org/@apify/log/-/log-2.5.11.tgz", + "integrity": "sha512-QDAFqOsZkpeV3UR9eYCGskI8rHSDnhF+hnbUea3MvfIhH5PfvbWIpdUrb7s04XnfnXbWpRSN+7TYLWBbHf+X5Q==", + "license": "Apache-2.0", + "dependencies": { + "@apify/consts": "^2.35.0", + "ansi-colors": "^4.1.1" + } + }, + "node_modules/@apify/ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@apify/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-VHIswI7rD/R4bToeIDuJ9WJXt+qr5SdhfoZ9RzdjmCs9mgy7l0P4RugQEUCcU+WB4sfImbd4CKwzXcn0uYx1yw==", + "license": "MIT", + "dependencies": { + "event-stream": "3.3.4" + }, + "bin": { + "ps-tree": "bin/ps-tree.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@apify/pseudo_url": { + "version": "2.0.52", + "resolved": "https://registry.npmjs.org/@apify/pseudo_url/-/pseudo_url-2.0.52.tgz", + "integrity": "sha512-7YhvBhcKLBlM9Pz4HsvpBhO7dyFmWf1gY4xldgEgPyIwlkycP6rsqg73U2fHxb5oUaxFPk99jBIaEwmIhonSGw==", + "license": "Apache-2.0", + "dependencies": { + "@apify/log": "^2.5.11" + } + }, + "node_modules/@apify/timeout": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@apify/timeout/-/timeout-0.3.1.tgz", + "integrity": "sha512-sLIuOqfySki/7AXiQ1yZoCI07vX6aYFLgP6YaJ8e8YLn8CFsRERma/Crxcz0zyCaxhc7C7EPgcs1O+p/djZchw==", + "license": "Apache-2.0" + }, + "node_modules/@apify/tsconfig": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@apify/tsconfig/-/tsconfig-0.1.0.tgz", + "integrity": "sha512-ba9Y6AMocRucO3AVTb6GM2V+oy1wByNlCDzamK+IC+aqU3pCgJwSN87uNu6iEgu+uetsqYvVbXJYakwiQO1LGA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@apify/utilities": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@apify/utilities/-/utilities-2.11.1.tgz", + "integrity": "sha512-g0oll9k7CG+ivwQ9mZiMuEZQHUXbrO+zdPtIY5xliUrp4UmJ0mmnbf6JOk3GXOeAcZaM4Acfm7g9H18Bxih1mA==", + "license": "Apache-2.0", + "dependencies": { + "@apify/consts": "^2.35.0", + "@apify/log": "^2.5.11" + } + }, + "node_modules/@crawlee/core": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@crawlee/core/-/core-3.12.1.tgz", + "integrity": "sha512-hom1ALM1Gn+ZpdFxIcQwmNjHV7mZhcOsPGIqhZuEWSETPV3lcMX67ZPs9UU3nIjsPgKuDC3FjIv6TXNu8Ga55A==", + "license": "Apache-2.0", + "dependencies": { + "@apify/consts": "^2.20.0", + "@apify/datastructures": "^2.0.0", + "@apify/log": "^2.4.0", + "@apify/pseudo_url": "^2.0.30", + "@apify/timeout": "^0.3.0", + "@apify/utilities": "^2.7.10", + "@crawlee/memory-storage": "3.12.1", + "@crawlee/types": "3.12.1", + "@crawlee/utils": "3.12.1", + "@sapphire/async-queue": "^1.5.1", + "@vladfrangu/async_event_emitter": "^2.2.2", + "csv-stringify": "^6.2.0", + "fs-extra": "^11.0.0", + "got-scraping": "^4.0.0", + "json5": "^2.2.3", + "minimatch": "^9.0.0", + "ow": "^0.28.1", + "stream-json": "^1.8.0", + "tldts": "^6.0.0", + "tough-cookie": "^5.0.0", + "tslib": "^2.4.0", + "type-fest": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@crawlee/memory-storage": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@crawlee/memory-storage/-/memory-storage-3.12.1.tgz", + "integrity": "sha512-N3WqfNIgo8m5ycJvUM5BvAZoqgglwEq11HsIlAUX4AGRKfYiFLziGpkBdhm2JAaD3/VWyZeEKyWgpkRiKZXo2w==", + "license": "Apache-2.0", + "dependencies": { + "@apify/log": "^2.4.0", + "@crawlee/types": "3.12.1", + "@sapphire/async-queue": "^1.5.0", + "@sapphire/shapeshift": "^3.0.0", + "content-type": "^1.0.4", + "fs-extra": "^11.0.0", + "json5": "^2.2.3", + "mime-types": "^2.1.35", + "proper-lockfile": "^4.1.2", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/@crawlee/types": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@crawlee/types/-/types-3.12.1.tgz", + "integrity": "sha512-KiYqRxYTB89Osy7sBbKhOSUyUusO85aGHvLLzxWAjQPrIhrY09t2kuVZhmTnjXTbgucQdMeWFrQjQuNeq8yPtA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@crawlee/utils": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@crawlee/utils/-/utils-3.12.1.tgz", + "integrity": "sha512-GknM4VD77coaAXkb2MPRkqYGVWHGMPUDuyBFmMZLGPnenHX7VoywZK+4Xw5o6FMLk0krPF/oAPAyD52fHv2B2Q==", + "license": "Apache-2.0", + "dependencies": { + "@apify/log": "^2.4.0", + "@apify/ps-tree": "^1.2.0", + "@crawlee/types": "3.12.1", + "@types/sax": "^1.2.7", + "cheerio": "1.0.0-rc.12", + "file-type": "^19.0.0", + "got-scraping": "^4.0.3", + "ow": "^0.28.1", + "robots-parser": "^3.0.1", + "sax": "^1.4.1", + "tslib": "^2.4.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@crawlee/utils/node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/@crawlee/utils/node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/compat": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.4.tgz", + "integrity": "sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.5", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", + "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", + "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.0.4.tgz", + "integrity": "sha512-C+jw1lF6HSGzs7EZpzHbXfzz9rj9him4BaoumlTciW/IDDgIpweF/qiCWKlP02QKg5PPcgY6xY2WCt5y2tpYow==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.7.tgz", + "integrity": "sha512-4It2mxPSr4OGn4HSQWGmhFMsNFGfFVhWeRPCRwbH972Ek2pzfGRZtb0pJ4Ze6oIzcyh2jw7nUDa6qGlWofgd9g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.1.tgz", + "integrity": "sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", + "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.2.tgz", + "integrity": "sha512-adig4SzPLjeQ0Tm+jvsozSGiCliI2ajeURDGHjZ2llnA+A67HihCQ+a3amtPhUakd1GlwHxSRvzOZktbEvhPPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.18.2", + "@typescript-eslint/type-utils": "8.18.2", + "@typescript-eslint/utils": "8.18.2", + "@typescript-eslint/visitor-keys": "8.18.2", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.2.tgz", + "integrity": "sha512-y7tcq4StgxQD4mDr9+Jb26dZ+HTZ/SkfqpXSiqeUXZHxOUyjWDKsmwKhJ0/tApR08DgOhrFAoAhyB80/p3ViuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.18.2", + "@typescript-eslint/types": "8.18.2", + "@typescript-eslint/typescript-estree": "8.18.2", + "@typescript-eslint/visitor-keys": "8.18.2", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.2.tgz", + "integrity": "sha512-YJFSfbd0CJjy14r/EvWapYgV4R5CHzptssoag2M7y3Ra7XNta6GPAJPPP5KGB9j14viYXyrzRO5GkX7CRfo8/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.18.2", + "@typescript-eslint/visitor-keys": "8.18.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.2.tgz", + "integrity": "sha512-AB/Wr1Lz31bzHfGm/jgbFR0VB0SML/hd2P1yxzKDM48YmP7vbyJNHRExUE/wZsQj2wUCvbWH8poNHFuxLqCTnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.18.2", + "@typescript-eslint/utils": "8.18.2", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.2.tgz", + "integrity": "sha512-Z/zblEPp8cIvmEn6+tPDIHUbRu/0z5lqZ+NvolL5SvXWT5rQy7+Nch83M0++XzO0XrWRFWECgOAyE8bsJTl1GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.2.tgz", + "integrity": "sha512-WXAVt595HjpmlfH4crSdM/1bcsqh+1weFRWIa9XMTx/XHZ9TCKMcr725tLYqWOgzKdeDrqVHxFotrvWcEsk2Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.18.2", + "@typescript-eslint/visitor-keys": "8.18.2", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.2.tgz", + "integrity": "sha512-Cr4A0H7DtVIPkauj4sTSXVl+VBWewE9/o40KcF3TV9aqDEOWoXF3/+oRXNby3DYzZeCATvbdksYsGZzplwnK/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.18.2", + "@typescript-eslint/types": "8.18.2", + "@typescript-eslint/typescript-estree": "8.18.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.2.tgz", + "integrity": "sha512-zORcwn4C3trOWiCqFQP1x6G3xTRyZ1LYydnj51cRnJ6hxBlr/cKPckk+PKPUw/fXmvfKTcw7bwY3w9izgx5jZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.18.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", + "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/apify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/apify/-/apify-3.2.6.tgz", + "integrity": "sha512-Uh8vWFb+hv6R2aPSol6QTFbUAdhRtXsVut7Cc0c/C8BbcNOqJlU2RY4xsw8clmc0fW/1UCIxMJxNRfbYnaelXQ==", + "license": "Apache-2.0", + "dependencies": { + "@apify/consts": "^2.23.0", + "@apify/input_secrets": "^1.1.40", + "@apify/log": "^2.4.3", + "@apify/timeout": "^0.3.0", + "@apify/utilities": "^2.9.3", + "@crawlee/core": "^3.9.0", + "@crawlee/types": "^3.9.0", + "@crawlee/utils": "^3.9.0", + "apify-client": "^2.9.0", + "fs-extra": "^11.2.0", + "ow": "^0.28.2", + "semver": "^7.5.4", + "tslib": "^2.6.2", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/apify-client": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/apify-client/-/apify-client-2.11.0.tgz", + "integrity": "sha512-r3UQ4wEhU0nTuT9owYCCqcCXvj/gSSMsaSFs1wszwLqq3dadfQK6n4rLg4zrTpmQHQY7i+OjMV2vp9rOgm916A==", + "license": "Apache-2.0", + "dependencies": { + "@apify/consts": "^2.25.0", + "@apify/log": "^2.2.6", + "@crawlee/types": "^3.3.0", + "agentkeepalive": "^4.2.1", + "async-retry": "^1.3.3", + "axios": "^1.6.7", + "content-type": "^1.0.5", + "ow": "^0.28.2", + "tslib": "^2.5.0", + "type-fest": "^4.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-12.0.1.tgz", + "integrity": "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.4", + "get-stream": "^9.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.4", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.1", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csv-stringify": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.5.2.tgz", + "integrity": "sha512-RFPahj0sXcmUyjrObAK+DOWtMvMIFV328n4qZJhgX3x2RqkQgOTU2mCUmiFR0CzM6AzChlRSUErjiJeEt8BaQA==", + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.1.tgz", + "integrity": "sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.76", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz", + "integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==", + "license": "ISC" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.8", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.8.tgz", + "integrity": "sha512-lfab8IzDn6EpI1ibZakcgS6WsfEBiB+43cuJo+wgylx1xKXf+Sp+YR3vFuQwC/u3sxYwV8Cxe3B0DpVUu/WiJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.6", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.0", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.17.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "license": "MIT", + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-type": { + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz", + "integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==", + "license": "MIT", + "dependencies": { + "get-stream": "^9.0.1", + "strtok3": "^9.0.1", + "token-types": "^6.0.0", + "uint8array-extras": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz", + "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generative-bayesian-network": { + "version": "2.1.61", + "resolved": "https://registry.npmjs.org/generative-bayesian-network/-/generative-bayesian-network-2.1.61.tgz", + "integrity": "sha512-eIcVAU5I3SHKDwSqQxan6sRGAC0PRgj6rVNGUoKcErtAlbMsO8fr4Wr0hcBzLAc+6YKeNRpUKXWkEo/q1RLvTg==", + "license": "Apache-2.0", + "dependencies": { + "adm-zip": "^0.5.9", + "tslib": "^2.4.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "14.4.5", + "resolved": "https://registry.npmjs.org/got/-/got-14.4.5.tgz", + "integrity": "sha512-sq+uET8TnNKRNnjEOPJzMcxeI0irT8BBNmf+GtZcJpmhYsQM1DSKmCROUjPWKsXZ5HzwD5Cf5/RV+QD9BSTxJg==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^7.0.1", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^12.0.1", + "decompress-response": "^6.0.0", + "form-data-encoder": "^4.0.2", + "http2-wrapper": "^2.2.1", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^4.0.1", + "responselike": "^3.0.0", + "type-fest": "^4.26.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got-scraping": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/got-scraping/-/got-scraping-4.0.8.tgz", + "integrity": "sha512-QCptrUWsxgtP8LAnGZqjuJMwbLELlst1DF/Ba30OUOk7wi/LJtNwuYPUxoielRxTxd9QQ38FL/CWyRVc7m7ZkQ==", + "license": "Apache-2.0", + "dependencies": { + "got": "^14.2.1", + "header-generator": "^2.1.41", + "http2-wrapper": "^2.2.0", + "mimic-response": "^4.0.0", + "ow": "^1.1.1", + "quick-lru": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/got-scraping/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/got-scraping/node_modules/callsites": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.2.0.tgz", + "integrity": "sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got-scraping/node_modules/dot-prop": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-7.2.0.tgz", + "integrity": "sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==", + "license": "MIT", + "dependencies": { + "type-fest": "^2.11.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got-scraping/node_modules/ow": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ow/-/ow-1.1.1.tgz", + "integrity": "sha512-sJBRCbS5vh1Jp9EOgwp1Ws3c16lJrUkJYlvWTYC03oyiYVwS/ns7lKRWow4w4XjDyTrA2pplQv4B2naWSR6yDA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^5.3.0", + "callsites": "^4.0.0", + "dot-prop": "^7.2.0", + "lodash.isequal": "^4.5.0", + "vali-date": "^1.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got-scraping/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/header-generator": { + "version": "2.1.61", + "resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.61.tgz", + "integrity": "sha512-jwTzKvIeTcm4ghHSeQeHwdUGLuWqLg3zrOkt+Dsrr+NeuHtLlDdtisgNkpKn2yjJ2OAakwxmKWC41SNkEH0/5w==", + "license": "Apache-2.0", + "dependencies": { + "browserslist": "^4.21.1", + "generative-bayesian-network": "^2.1.61", + "ow": "^0.28.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/http2-wrapper/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", + "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, + "node_modules/normalize-url": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ow": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.28.2.tgz", + "integrity": "sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.2.0", + "callsites": "^3.1.0", + "dot-prop": "^6.0.1", + "lodash.isequal": "^4.5.0", + "vali-date": "^1.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ow/node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-cancelable": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", + "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "license": [ + "MIT", + "Apache2" + ], + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/peek-readable": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.3.1.tgz", + "integrity": "sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.0.0.tgz", + "integrity": "sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz", + "integrity": "sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "dunder-proto": "^1.0.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/robots-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robots-parser/-/robots-parser-3.0.1.tgz", + "integrity": "sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "license": "MIT", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "license": "MIT", + "dependencies": { + "duplexer": "~0.1.1" + } + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strtok3": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz", + "integrity": "sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^5.3.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, + "node_modules/tldts": { + "version": "6.1.70", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.70.tgz", + "integrity": "sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.70" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.70", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.70.tgz", + "integrity": "sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz", + "integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", + "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.31.0.tgz", + "integrity": "sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.2.tgz", + "integrity": "sha512-KuXezG6jHkvC3MvizeXgupZzaG5wjhU3yE8E7e6viOvAvD9xAWYp8/vy0WULTGe9DYDWcQu7aW03YIV3mSitrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.18.2", + "@typescript-eslint/parser": "8.18.2", + "@typescript-eslint/utils": "8.18.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/uint8array-extras": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", + "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..559a649 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "apify-mcp-server", + "version": "0.0.1", + "type": "module", + "description": "This is an example of an Apify actor.", + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.4", + "ajv": "^8.17.1", + "apify": "^3.2.6", + "apify-client": "^2.11.0", + "dotenv": "^16.4.7", + "express": "^4.21.2" + }, + "devDependencies": { + "@apify/eslint-config": "^0.5.0-beta.2", + "@apify/tsconfig": "^0.1.0", + "@types/express": "^5.0.0", + "eslint": "^9.17.0", + "tsx": "^4.6.2", + "typescript": "^5.3.3", + "typescript-eslint": "^8.18.2" + }, + "scripts": { + "start": "npm run start:dev", + "start:prod": "node dist/main.js", + "start:dev": "tsx src/main.ts", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "build": "tsc", + "test": "echo \"Error: oops, the actor has no tests yet, sad!\" && exit 1", + "watch": "tsc --watch" + }, + "author": "It's not you it's me", + "license": "ISC" +} diff --git a/src/actorsInputSchema.ts b/src/actorsInputSchema.ts new file mode 100644 index 0000000..6a1658e --- /dev/null +++ b/src/actorsInputSchema.ts @@ -0,0 +1,95 @@ +import { Ajv } from 'ajv'; +import { log } from 'apify'; +import type { BuildCollectionClientListItem, ActorDefinition } from 'apify-client'; +import { ApifyClient } from 'apify-client'; +import dotenv from 'dotenv'; + +const ajv = new Ajv({ coerceTypes: 'array', strict: false }); + +dotenv.config({ path: '../.env' }); + +const argToken = process.argv.find((arg) => arg.startsWith('APIFY_API_TOKEN='))?.split('=')[1]; +const APIFY_API_TOKEN = process.env.APIFY_API_TOKEN || argToken; +const client = new ApifyClient({ token: APIFY_API_TOKEN }); + +interface Build extends BuildCollectionClientListItem { + buildNumber: string; +} +log.setLevel(log.LEVELS.DEBUG); + +interface ActorDefinitionExtended extends ActorDefinition { + description: string; +} + +/** + * Get actor input schema by actor name. + * First, fetch the actor details to get the default build tag and buildNumber + * Then, fetch the build list and find the buildId by buildNumber + * Finally, fetch the build details and return actorName, description, and input schema. + * @param actorFullName + */ +async function fetchActorDefinition(actorFullName: string): Promise { + const actorClient = client.actor(actorFullName); + + try { + // Fetch actor details + const actor = await actorClient.get(); + if (!actor) { + log.error(`Failed to fetch input schema for actor: ${actorFullName}. Actor not found.`); + return null; + } + + // Extract default build label + const tag = actor.defaultRunOptions?.build || ''; + const buildNumber = actor.taggedBuilds?.[tag]?.buildId || ''; + const description = actor.description || ''; + if (!buildNumber) { + log.error(`Failed to fetch input schema for actor: ${actorFullName}. Build number not found.`); + return null; + } + + // Fetch builds and find the matching build by number + const buildList = await actorClient.builds().list(); + const { items } = buildList as unknown as { items: Build[] }; + const build = items.find((b) => b.buildNumber === buildNumber); + + const buildId = build?.id || ''; + if (!buildId) { + log.error(`Failed to fetch input schema for actor: ${actorFullName}. Build ID not found.`); + return null; + } + // Fetch build details and return the input schema + const buildDetails = await client.build(buildId).get(); + if (buildDetails && 'actorDefinition' in buildDetails) { + // The buildDetails schema contains actorDefinitions but return type is ActorDefinition + const actorDefinitions = buildDetails?.actorDefinition as ActorDefinitionExtended; + actorDefinitions.description = description; + // Change the name to the actorFullName (we need to tools with a full name to call the actor) + actorDefinitions.name = actorFullName; + return actorDefinitions; + } + return null; + } catch (error) { + log.error(`Failed to fetch input schema for actor: ${actorFullName} with error ${error}.`); + return null; + } +} + +export async function getActorsAsTools(actorNames: string[]) { + // Fetch input schemas in parallel + const results = await Promise.all(actorNames.map(fetchActorDefinition)); + const tools = []; + for (const result of results) { + if (result) { + tools.push({ + name: result.name, + description: result.description, + inputSchema: result.input || {}, + ajvValidate: ajv.compile(result.input || {}), + }); + } + } + return tools; +} + +getActorsAsTools(['apify/rag-web-browser', 'apify/google-search-scraper']).catch((error) => log.error('Global Error:', error)); diff --git a/src/const.ts b/src/const.ts new file mode 100644 index 0000000..67d3582 --- /dev/null +++ b/src/const.ts @@ -0,0 +1,15 @@ +export const SERVER_NAME = 'apify-mcp-server'; +export const SERVER_VERSION = '0.1.0'; + +export const defaults = { + actorNames: [ + 'apify/google-search-scraper', + 'apify/rag-web-browser', + 'compass/google-maps-extractor', + ], +}; + +export enum Routes { + SSE = '/sse', + MESSAGE = '/message', +} diff --git a/src/input.ts b/src/input.ts new file mode 100644 index 0000000..ff21997 --- /dev/null +++ b/src/input.ts @@ -0,0 +1,11 @@ +import { defaults } from './const.js'; +import type { Input } from './types.js'; + +export async function processInput(originalInput: Partial) { + const input = { ...defaults, ...originalInput } as Input; + + if (!input.actorNames || input.actorNames.length === 0) { + throw new Error('The `actorNames` parameter must be a non-empty array.'); + } + return { input }; +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..ec74bf2 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,71 @@ +import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; +import { Actor, log } from 'apify'; +import type { Request, Response } from 'express'; +import express from 'express'; + +import { getActorsAsTools } from './actorsInputSchema.js'; +import { Routes } from './const.js'; +import { processInput } from './input.js'; +import { ApifyMcpServer, callActorGetDataset } from './server.js'; +import type { Input } from './types.js'; + +log.setLevel(log.LEVELS.DEBUG); + +await Actor.init(); + +const { input } = await processInput((await Actor.getInput>()) ?? ({} as Input)); +log.info(`Loaded input: ${JSON.stringify(input)} `); + +const STANDBY_MODE = Actor.getEnv().metaOrigin === 'STANDBY'; +const HOST = Actor.isAtHome() ? process.env.ACTOR_STANDBY_URL : 'http://localhost'; +const POST = Actor.isAtHome() ? process.env.ACTOR_STANDBY_PORT : 3001; + +const app = express(); + +// Set up MCP server with tools +const tools = await getActorsAsTools(input.actorNames); +const mcpServer = new ApifyMcpServer(tools); +let transport: SSEServerTransport; + +const HELP_MESSAGE = `Send POST requests to ${HOST}/messages to use Model context protocol.`; + +app.route('/') + .get(async (req: Request, res: Response) => { + log.info(`Received GET message at: ${req.url}`); + res.status(200).json({ message: `Actor is running in Standby mode. ${HELP_MESSAGE}` }); + }) + .head(async (res: Response) => { + res.status(200).json({ message: `Actor is running in Standby mode.` }); + }); + +app.get(Routes.SSE, async (req: Request, res: Response) => { + log.info(`Received GET message at: ${req.url}`); + transport = new SSEServerTransport(Routes.MESSAGE, res); + await mcpServer.connect(transport); +}); + +app.post(Routes.MESSAGE, async (req: Request, res: Response) => { + log.info(`Received POST message at: ${req.url}`); + await transport.handlePostMessage(req, res); +}); + +// Catch-all for undefined routes +app.use((req: Request, res: Response) => { + res.status(404).json({ message: `The is nothing at route ${req.method} ${req.originalUrl}. ${HELP_MESSAGE}` }); +}); + +if (STANDBY_MODE) { + log.info('Actor is running in the STANDBY mode.'); + + app.listen(POST, () => { + log.info(`The Actor web server is listening for user requests at ${HOST}.`); + }); +} else { + log.info('Actor is not designed to run in the NORMAL model (use this mode only for debugging purposes)'); + + if (input && !input.debugActorName && !input.debugActorInput) { + await Actor.fail('If you need to debug a specific actor, please provide the debugActorName and debugActorInput fields in the input.'); + } + await callActorGetDataset(input.debugActorName!, input.debugActorInput!); + await Actor.exit(); +} diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..30de157 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,111 @@ +#!/usr/bin/env node + +/** + * Model Context Protocol (MCP) server for RAG Web Browser Actor + */ + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; +import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; +import type { ValidateFunction } from 'ajv'; +import { log } from 'apify'; +import { ApifyClient } from 'apify-client'; +import dotenv from 'dotenv'; + +import { SERVER_NAME, SERVER_VERSION } from './const.js'; + +dotenv.config({ path: '../.env' }); + +const argToken = process.argv.find((arg) => arg.startsWith('APIFY_API_TOKEN='))?.split('=')[1]; +const APIFY_API_TOKEN = process.env.APIFY_API_TOKEN || argToken; + +export async function callActorGetDataset(actorName: string, input: unknown): Promise { + if (!APIFY_API_TOKEN) { + throw new Error('APIFY_API_TOKEN is required but not set. ' + + 'Please set it in your environment variables or pass it as a command-line argument.'); + } + try { + log.info(`Calling actor ${actorName} with input: ${JSON.stringify(input)}`); + const client = new ApifyClient({ token: APIFY_API_TOKEN }); + const actorClient = client.actor(actorName); + + const results = await actorClient.call(input); + const dataset = await client.dataset(results.defaultDatasetId).listItems(); + return dataset.items; + } catch (error) { + log.error(`Error calling actor: ${error}. Actor: ${actorName}, input: ${JSON.stringify(input)}`); + throw new Error(`Error calling actor: ${error}`); + } +} + +/** + * Create an MCP server with a tool to call RAG Web Browser Actor + */ +export class ApifyMcpServer { + private server: Server; + private readonly tools: { name: string; description: string; inputSchema: object, ajvValidate: ValidateFunction}[]; + private readonly availableTools: string[]; + + constructor(tools: { name: string; description: string; inputSchema: object, ajvValidate: ValidateFunction}[]) { + this.server = new Server( + { + name: SERVER_NAME, + version: SERVER_VERSION, + }, + { + capabilities: { + tools: {}, + }, + }, + ); + this.tools = tools; + this.availableTools = tools.map((tool) => tool.name); + this.setupErrorHandling(); + this.setupToolHandlers(); + } + + private setupErrorHandling(): void { + this.server.onerror = (error) => { + console.error('[MCP Error]', error); // eslint-disable-line no-console + }; + process.on('SIGINT', async () => { + await this.server.close(); + process.exit(0); + }); + } + + private validateArguments(name: string, args: unknown): unknown { + const tool = this.tools.find((x) => x.name === name); + if (!tool?.ajvValidate(args)) { + throw new Error(`Invalid arguments for tool ${name}: args: ${JSON.stringify(args)} error: ${JSON.stringify(tool?.ajvValidate.errors)}`); + } + return args; + } + + private setupToolHandlers(): void { + this.server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: this.tools, + }; + }); + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + if (!this.availableTools.includes(name)) { + throw new Error(`Unknown tool: ${name}`); + } + try { + log.info(`Validating arguments for tool: ${name} with arguments: ${JSON.stringify(args)}`); + const validatedArgs = this.validateArguments(name, args); + const items = await callActorGetDataset(name, validatedArgs); + return { content: items.map((item) => ({ type: 'text', text: JSON.stringify(item) })) }; + } catch (error) { + log.error(`Error calling tool: ${error}`); + throw new Error(`Error calling tool: ${error}`); + } + }); + } + + async connect(transport: Transport): Promise { + await this.server.connect(transport); + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..7800e86 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,5 @@ +export type Input = { + actorNames: string[]; + debugActorName?: string; + debugActorInput?: unknown; +}; From 9b06472de61fec2f2ef4bea782deffcad48e0d97 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 3 Jan 2025 09:23:28 +0100 Subject: [PATCH 02/54] Change logger, simplify fetching of Actor input --- src/actorsInputSchema.ts | 44 ++++++++++------------------------------ src/logger.ts | 5 +++++ src/main.ts | 5 ++--- src/server.ts | 2 +- 4 files changed, 19 insertions(+), 37 deletions(-) create mode 100644 src/logger.ts diff --git a/src/actorsInputSchema.ts b/src/actorsInputSchema.ts index 6a1658e..f4bc857 100644 --- a/src/actorsInputSchema.ts +++ b/src/actorsInputSchema.ts @@ -1,34 +1,21 @@ import { Ajv } from 'ajv'; -import { log } from 'apify'; -import type { BuildCollectionClientListItem, ActorDefinition } from 'apify-client'; +import type { ActorDefinition } from 'apify-client'; import { ApifyClient } from 'apify-client'; -import dotenv from 'dotenv'; -const ajv = new Ajv({ coerceTypes: 'array', strict: false }); +import { log } from './logger.js'; -dotenv.config({ path: '../.env' }); - -const argToken = process.argv.find((arg) => arg.startsWith('APIFY_API_TOKEN='))?.split('=')[1]; -const APIFY_API_TOKEN = process.env.APIFY_API_TOKEN || argToken; -const client = new ApifyClient({ token: APIFY_API_TOKEN }); - -interface Build extends BuildCollectionClientListItem { - buildNumber: string; -} -log.setLevel(log.LEVELS.DEBUG); - -interface ActorDefinitionExtended extends ActorDefinition { +interface ActorDefinitionWithDesc extends ActorDefinition { description: string; } /** * Get actor input schema by actor name. - * First, fetch the actor details to get the default build tag and buildNumber - * Then, fetch the build list and find the buildId by buildNumber - * Finally, fetch the build details and return actorName, description, and input schema. + * First, fetch the actor details to get the default build tag and buildId. + * Then, fetch the build details and return actorName, description, and input schema. * @param actorFullName */ -async function fetchActorDefinition(actorFullName: string): Promise { +async function fetchActorDefinition(actorFullName: string): Promise { + const client = new ApifyClient({ token: process.env.APIFY_API_TOKEN }); const actorClient = client.actor(actorFullName); try { @@ -41,19 +28,9 @@ async function fetchActorDefinition(actorFullName: string): Promise b.buildNumber === buildNumber); - const buildId = build?.id || ''; if (!buildId) { log.error(`Failed to fetch input schema for actor: ${actorFullName}. Build ID not found.`); return null; @@ -62,7 +39,7 @@ async function fetchActorDefinition(actorFullName: string): Promise log.error('Global Error:', error)); +// getActorsAsTools(['apify/rag-web-browser', 'apify/google-search-scraper']).catch((error) => log.error('Global Error:', error)); diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..39ab7c9 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,5 @@ +import { log } from 'apify'; + +log.setLevel(log.LEVELS.DEBUG); + +export { log }; diff --git a/src/main.ts b/src/main.ts index ec74bf2..4feb08a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,15 @@ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; -import { Actor, log } from 'apify'; +import { Actor } from 'apify'; import type { Request, Response } from 'express'; import express from 'express'; import { getActorsAsTools } from './actorsInputSchema.js'; import { Routes } from './const.js'; import { processInput } from './input.js'; +import { log } from './logger.js'; import { ApifyMcpServer, callActorGetDataset } from './server.js'; import type { Input } from './types.js'; -log.setLevel(log.LEVELS.DEBUG); - await Actor.init(); const { input } = await processInput((await Actor.getInput>()) ?? ({} as Input)); diff --git a/src/server.ts b/src/server.ts index 30de157..c2ae44d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -8,11 +8,11 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import type { ValidateFunction } from 'ajv'; -import { log } from 'apify'; import { ApifyClient } from 'apify-client'; import dotenv from 'dotenv'; import { SERVER_NAME, SERVER_VERSION } from './const.js'; +import { log } from './logger.js'; dotenv.config({ path: '../.env' }); From 672d79e3b967d0a57020e3e45e7c1ec0bf886a2b Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 3 Jan 2025 10:18:16 +0100 Subject: [PATCH 03/54] Change env variable APIFY_API_TOKEN to APIFY_TOKEN --- .actor/input_schema.json | 7 +++---- src/actorsInputSchema.ts | 2 +- src/main.ts | 2 +- src/server.ts | 16 ++++------------ 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/.actor/input_schema.json b/.actor/input_schema.json index 851a1f7..8e5c8b3 100644 --- a/.actor/input_schema.json +++ b/.actor/input_schema.json @@ -6,10 +6,9 @@ "actorNames": { "title": "Actors names to be exposed for an AI application (AI agent)", "type": "array", - "description": "Specify the names of actors that will be exposed to an AI application (AI agent) and communicate with Apify Actors via the MCP protocol.", + "description": "Specify the names of actors that will be exposed to an AI application (AI agent) and communicate with Apify Actors via the MCP protocol", "editor": "stringList", "prefill": [ - "apify/website-content-crawler", "compass/google-maps-extractor", "apify/google-search-scraper", "apify/instagram-scraper", @@ -20,7 +19,7 @@ "debugActorName": { "title": "Debug actor name", "type": "string", - "description": "Specify the name of the actor that will be used for debugging in normal mode.", + "description": "Specify the name of the actor that will be used for debugging in normal mode", "editor": "textfield", "prefill": "apify/rag-web-browser", "sectionCaption": "Debugging settings (normal mode)" @@ -28,7 +27,7 @@ "debugActorInput": { "title": "Debug actor input", "type": "object", - "description": "Specify the input for the actor that will be used for debugging in normal mode.", + "description": "Specify the input for the actor that will be used for debugging in normal mode", "editor": "json", "prefill": { "query": "hello world" diff --git a/src/actorsInputSchema.ts b/src/actorsInputSchema.ts index f4bc857..3a8163c 100644 --- a/src/actorsInputSchema.ts +++ b/src/actorsInputSchema.ts @@ -15,7 +15,7 @@ interface ActorDefinitionWithDesc extends ActorDefinition { * @param actorFullName */ async function fetchActorDefinition(actorFullName: string): Promise { - const client = new ApifyClient({ token: process.env.APIFY_API_TOKEN }); + const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const actorClient = client.actor(actorFullName); try { diff --git a/src/main.ts b/src/main.ts index 4feb08a..138d99d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -50,7 +50,7 @@ app.post(Routes.MESSAGE, async (req: Request, res: Response) => { // Catch-all for undefined routes app.use((req: Request, res: Response) => { - res.status(404).json({ message: `The is nothing at route ${req.method} ${req.originalUrl}. ${HELP_MESSAGE}` }); + res.status(404).json({ message: `There is nothing at route ${req.method} ${req.originalUrl}. ${HELP_MESSAGE}` }); }); if (STANDBY_MODE) { diff --git a/src/server.ts b/src/server.ts index c2ae44d..84d3a95 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,32 +1,24 @@ #!/usr/bin/env node - /** * Model Context Protocol (MCP) server for RAG Web Browser Actor */ - import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import type { ValidateFunction } from 'ajv'; import { ApifyClient } from 'apify-client'; -import dotenv from 'dotenv'; import { SERVER_NAME, SERVER_VERSION } from './const.js'; import { log } from './logger.js'; -dotenv.config({ path: '../.env' }); - -const argToken = process.argv.find((arg) => arg.startsWith('APIFY_API_TOKEN='))?.split('=')[1]; -const APIFY_API_TOKEN = process.env.APIFY_API_TOKEN || argToken; - export async function callActorGetDataset(actorName: string, input: unknown): Promise { - if (!APIFY_API_TOKEN) { - throw new Error('APIFY_API_TOKEN is required but not set. ' - + 'Please set it in your environment variables or pass it as a command-line argument.'); + const apifyApiToken = process.env.APIFY_TOKEN; + if (!apifyApiToken) { + throw new Error('APIFY_TOKEN is required but not set. Please set it as an environment variable'); } try { log.info(`Calling actor ${actorName} with input: ${JSON.stringify(input)}`); - const client = new ApifyClient({ token: APIFY_API_TOKEN }); + const client = new ApifyClient({ token: apifyApiToken }); const actorClient = client.actor(actorName); const results = await actorClient.call(input); From 1d271999206d9f02d75b247dd04e85c50cefc2e2 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 3 Jan 2025 11:09:39 +0100 Subject: [PATCH 04/54] Push data into Apify dataset --- README.md | 2 +- src/main.ts | 3 ++- src/server.ts | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3f31891..57b926d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Scrape single-page in TypeScript template +## Apify Model Context Protocol (MCP) Server A template for scraping data from a single web page in TypeScript (Node.js). The URL of the web page is passed in via input, which is defined by the [input schema](https://docs.apify.com/platform/actors/development/input-schema). The template uses the [Axios client](https://axios-http.com/docs/intro) to get the HTML of the page and the [Cheerio library](https://cheerio.js.org/) to parse the data from it. The data are then stored in a [dataset](https://docs.apify.com/sdk/js/docs/guides/result-storage#dataset) where you can easily access them. diff --git a/src/main.ts b/src/main.ts index 138d99d..acd4ee1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -65,6 +65,7 @@ if (STANDBY_MODE) { if (input && !input.debugActorName && !input.debugActorInput) { await Actor.fail('If you need to debug a specific actor, please provide the debugActorName and debugActorInput fields in the input.'); } - await callActorGetDataset(input.debugActorName!, input.debugActorInput!); + const items = await callActorGetDataset(input.debugActorName!, input.debugActorInput!); + await Actor.pushData(items); await Actor.exit(); } diff --git a/src/server.ts b/src/server.ts index 84d3a95..c98cd35 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,6 +6,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import type { ValidateFunction } from 'ajv'; +import { Actor } from 'apify'; import { ApifyClient } from 'apify-client'; import { SERVER_NAME, SERVER_VERSION } from './const.js'; @@ -23,6 +24,9 @@ export async function callActorGetDataset(actorName: string, input: unknown): Pr const results = await actorClient.call(input); const dataset = await client.dataset(results.defaultDatasetId).listItems(); + if (process.env.APIFY_IS_AT_HOME) { + await Actor.pushData(dataset.items); + } return dataset.items; } catch (error) { log.error(`Error calling actor: ${error}. Actor: ${actorName}, input: ${JSON.stringify(input)}`); From cbc172a34203a95eaab1d4b8d596336d80c8ea1d Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 3 Jan 2025 11:26:32 +0100 Subject: [PATCH 05/54] Fix standby mode --- package-lock.json | 13 ------------- package.json | 1 - src/{actorsInputSchema.ts => actorDefinition.ts} | 0 src/main.ts | 16 ++++++++-------- 4 files changed, 8 insertions(+), 22 deletions(-) rename src/{actorsInputSchema.ts => actorDefinition.ts} (100%) diff --git a/package-lock.json b/package-lock.json index 5494f32..8cf72a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "ajv": "^8.17.1", "apify": "^3.2.6", "apify-client": "^2.11.0", - "dotenv": "^16.4.7", "express": "^4.21.2" }, "devDependencies": { @@ -2387,18 +2386,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/package.json b/package.json index 559a649..5f20697 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "ajv": "^8.17.1", "apify": "^3.2.6", "apify-client": "^2.11.0", - "dotenv": "^16.4.7", "express": "^4.21.2" }, "devDependencies": { diff --git a/src/actorsInputSchema.ts b/src/actorDefinition.ts similarity index 100% rename from src/actorsInputSchema.ts rename to src/actorDefinition.ts diff --git a/src/main.ts b/src/main.ts index acd4ee1..d1c2176 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import { Actor } from 'apify'; import type { Request, Response } from 'express'; import express from 'express'; -import { getActorsAsTools } from './actorsInputSchema.js'; +import { getActorsAsTools } from './actorDefinition.js'; import { Routes } from './const.js'; import { processInput } from './input.js'; import { log } from './logger.js'; @@ -26,15 +26,16 @@ const tools = await getActorsAsTools(input.actorNames); const mcpServer = new ApifyMcpServer(tools); let transport: SSEServerTransport; -const HELP_MESSAGE = `Send POST requests to ${HOST}/messages to use Model context protocol.`; +const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse` + + ` and then send POST requests to ${HOST}/messages.`; app.route('/') .get(async (req: Request, res: Response) => { log.info(`Received GET message at: ${req.url}`); - res.status(200).json({ message: `Actor is running in Standby mode. ${HELP_MESSAGE}` }); + res.status(200).json({ message: `Actor is using Model Context Protocol. ${HELP_MESSAGE}` }).end(); }) - .head(async (res: Response) => { - res.status(200).json({ message: `Actor is running in Standby mode.` }); + .head(async (_req: Request, res: Response) => { + res.status(200).end(); }); app.get(Routes.SSE, async (req: Request, res: Response) => { @@ -50,7 +51,7 @@ app.post(Routes.MESSAGE, async (req: Request, res: Response) => { // Catch-all for undefined routes app.use((req: Request, res: Response) => { - res.status(404).json({ message: `There is nothing at route ${req.method} ${req.originalUrl}. ${HELP_MESSAGE}` }); + res.status(404).json({ message: `There is nothing at route ${req.method} ${req.originalUrl}. ${HELP_MESSAGE}` }).end(); }); if (STANDBY_MODE) { @@ -65,7 +66,6 @@ if (STANDBY_MODE) { if (input && !input.debugActorName && !input.debugActorInput) { await Actor.fail('If you need to debug a specific actor, please provide the debugActorName and debugActorInput fields in the input.'); } - const items = await callActorGetDataset(input.debugActorName!, input.debugActorInput!); - await Actor.pushData(items); + await callActorGetDataset(input.debugActorName!, input.debugActorInput!); await Actor.exit(); } From 7dd8fb2383b849a3623509d9321ec82b8bddd7b6 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 3 Jan 2025 11:49:57 +0100 Subject: [PATCH 06/54] Improve log messages --- src/main.ts | 2 +- src/server.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index d1c2176..6967770 100644 --- a/src/main.ts +++ b/src/main.ts @@ -27,7 +27,7 @@ const mcpServer = new ApifyMcpServer(tools); let transport: SSEServerTransport; const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse` - + ` and then send POST requests to ${HOST}/messages.`; + + ` and then send POST requests to ${HOST}/message.`; app.route('/') .get(async (req: Request, res: Response) => { diff --git a/src/server.ts b/src/server.ts index c98cd35..5a334e9 100644 --- a/src/server.ts +++ b/src/server.ts @@ -24,8 +24,10 @@ export async function callActorGetDataset(actorName: string, input: unknown): Pr const results = await actorClient.call(input); const dataset = await client.dataset(results.defaultDatasetId).listItems(); + log.info(`Actor ${actorName} finished with ${dataset.items.length} items`); if (process.env.APIFY_IS_AT_HOME) { await Actor.pushData(dataset.items); + log.info(`Pushed ${dataset.items.length} items to the dataset`); } return dataset.items; } catch (error) { From b4d3bd9292bcf03feabd66ce1373f5ee19609135 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 3 Jan 2025 15:31:19 +0100 Subject: [PATCH 07/54] - rename `actorNames` to `actors` - add ability to load actors in standby mode --- .actor/input_schema.json | 12 +- .github/workflows/check.yaml | 30 ++++ CHANGELOG.md | 6 + README.md | 274 ++++++++++++++++++++++++++++------- package.json | 2 +- src/actorDefinition.ts | 4 +- src/const.ts | 4 +- src/input.ts | 8 +- src/main.ts | 26 ++-- src/server.ts | 24 ++- src/types.ts | 4 +- 11 files changed, 315 insertions(+), 79 deletions(-) create mode 100644 .github/workflows/check.yaml create mode 100644 CHANGELOG.md diff --git a/.actor/input_schema.json b/.actor/input_schema.json index 8e5c8b3..f311cd5 100644 --- a/.actor/input_schema.json +++ b/.actor/input_schema.json @@ -3,20 +3,20 @@ "type": "object", "schemaVersion": 1, "properties": { - "actorNames": { + "actors": { "title": "Actors names to be exposed for an AI application (AI agent)", "type": "array", "description": "Specify the names of actors that will be exposed to an AI application (AI agent) and communicate with Apify Actors via the MCP protocol", "editor": "stringList", "prefill": [ - "compass/google-maps-extractor", + "apify/facebook-posts-scraper", "apify/google-search-scraper", "apify/instagram-scraper", - "apify/facebook-posts-scraper", - "apify/rag-web-browser" + "apify/rag-web-browser", + "compass/google-maps-extractor" ] }, - "debugActorName": { + "debugActor": { "title": "Debug actor name", "type": "string", "description": "Specify the name of the actor that will be used for debugging in normal mode", @@ -35,6 +35,6 @@ } }, "required": [ - "actorNames" + "actors" ] } diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml new file mode 100644 index 0000000..6742f94 --- /dev/null +++ b/.github/workflows/check.yaml @@ -0,0 +1,30 @@ +# This workflow runs for every pull request to lint and test the proposed changes. + +name: Check + +on: + pull_request: + + # Push to master will trigger code checks + push: + branches: + - master + tags-ignore: + - "**" # Ignore all tags to prevent duplicate builds when tags are pushed. + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'npm' + cache-dependency-path: 'package-lock.json' + - name: Install Dependencies + run: npm ci + - run: npm run lint diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..123e7fe --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +This changelog summarizes all changes of the Apify Model Context Protocol Server + +### 0.0.1 (2025-01-xx) + +🚀 Features +- Add functionality to call Apify Actors via Model Context Protocol diff --git a/README.md b/README.md index 57b926d..791035a 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,254 @@ ## Apify Model Context Protocol (MCP) Server -A template for scraping data from a single web page in TypeScript (Node.js). The URL of the web page is passed in via input, which is defined by the [input schema](https://docs.apify.com/platform/actors/development/input-schema). The template uses the [Axios client](https://axios-http.com/docs/intro) to get the HTML of the page and the [Cheerio library](https://cheerio.js.org/) to parse the data from it. The data are then stored in a [dataset](https://docs.apify.com/sdk/js/docs/guides/result-storage#dataset) where you can easily access them. +Implementation of an MCP server for [Apify Actors](https://apify.com/store). +This server enables interaction with one or more Apify Actors that can be defined in the MCP server configuration. -The scraped data in this template are page headings but you can easily edit the code to scrape whatever you want from the page. +The server can be used in two ways: +- **MCP Server Actor** - Actor runs an HTTP server that supports the MCP protocol via SSE (Server-Sent Events). +- **MCP Server CLI** - Command-line interface that supports the MCP protocol via stdio. -## Included features +## 🔄 What is model context protocol? -- **[Apify SDK](https://docs.apify.com/sdk/js/)** - a toolkit for building [Actors](https://apify.com/actors) -- **[Input schema](https://docs.apify.com/platform/actors/development/input-schema)** - define and easily validate a schema for your Actor's input -- **[Dataset](https://docs.apify.com/sdk/js/docs/guides/result-storage#dataset)** - store structured data where each object stored has the same attributes -- **[Axios client](https://axios-http.com/docs/intro)** - promise-based HTTP Client for Node.js and the browser -- **[Cheerio](https://cheerio.js.org/)** - library for parsing and manipulating HTML and XML +The Model Context Protocol (MCP) allows AI applications (and AI agents), such as Claude Desktop, to connect to external tools and data sources. +MCP is an open protocol that enables secure, controlled interactions between AI applications, AI Agents, and local or remote resources. -## How it works +## 🎯 What does this MCP server do? -1. `Actor.getInput()` gets the input where the page URL is defined -2. `axios.get(url)` fetches the page -3. `cheerio.load(response.data)` loads the page data and enables parsing the headings -4. This parses the headings from the page and here you can edit the code to parse whatever you need from the page +The MCP Server Actor allows an AI assistant to: +- Use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task. +- For example: + - [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) scrape websites of Google Maps places for contact details and get email addresses, website, location, address, zipcode, phone number, social media links. + - [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from hundreds of Facebook posts from one or multiple Facebook pages and profiles + - [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrape and download Instagram posts, profiles, places, hashtags, photos, and comments + - [RAG Web Browser](https://apify.com/apify/web-scraper) perform web search, scrape the top N URLs from the results, and return their cleaned content as Markdown - ```javascript - $("h1, h2, h3, h4, h5, h6").each((_i, element) => {...}); - ``` +## 🧱 Components -5. `Actor.pushData(headings)` stores the headings in the dataset +### Tools -## Resources +Any [Apify Actor](https://apify.com/store) can be used as a tool. +The tool name must always be the full Actor name, such as `apify/google-maps-email-extractor`, and the arguments represent the input parameters for the Actor. +Please see the examples below and refer to the specific Actor's documentation for a list of available arguments. -- [Web scraping in Node.js with Axios and Cheerio](https://blog.apify.com/web-scraping-with-axios-and-cheerio/) -- [Web scraping with Cheerio in 2023](https://blog.apify.com/web-scraping-with-cheerio/) -- [Video tutorial](https://www.youtube.com/watch?v=yTRHomGg9uQ) on building a scraper using CheerioCrawler -- [Written tutorial](https://docs.apify.com/academy/web-scraping-for-beginners/challenge) on building a scraper using CheerioCrawler -- [Integration with Zapier](https://apify.com/integrations), Make, Google Drive, and others -- [Video guide on getting scraped data using Apify API](https://www.youtube.com/watch?v=ViYYDHSBAKM) -- A short guide on how to build web scrapers using code templates: +### Prompt & Resources -[web scraper template](https://www.youtube.com/watch?v=u-i-Korzf8w) +The server does not provide any resources and prompts. -## Getting started +## ⚙️ Usage -For complete information [see this article](https://docs.apify.com/platform/actors/development#build-actor-locally). To run the actor use the following command: +The Apify MCP Server can be used in two ways: **as an Apify Actor** running at Apify platform +or as a **local server** running on your machine. + +### MCP Server Actor + +#### Standby web server + +The Actor runs in the [**Standby mode**](https://docs.apify.com/platform/actors/running/standby), where it runs an HTTP web server that receives requests. +To use Apify MCP Server, send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL: + +``` +https://mcp-server.apify.actor?token= +``` +This will start a server with predefined set of Actors and tools. + +To start a server with custom set of Actors, use the following URL: +``` +https://mcp-server.apify.actor?token=&actors=apify/google-maps-email-extractor,apify/facebook-posts-scraper +``` +This will start MCP server with Google Maps Email Extractor and Facebook Posts Scraper Actors + +Now you can interact with the server using MCP protocol via Server Sent Events (SSE). + +1. Initiate SSE connection: +```shell +https://mcp-server.apify.actor/sse?token= +``` +On connection, you’ll receive a `sessionId`: +```shell +event: endpoint +data: /message?sessionId=a1b +``` + +1. List available tools by making a POST request with the `sessionId`, `APIFY-API-TOKEN` and your query: +```shell +curl -X POST "https://mcp-server.apify.actor/message?session_id=a1b2&token=" -H "Content-Type: application/json" -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/list" +}' +``` + +## 🛠️ Configuration + +### Prerequisites + +- MacOS or Windows +- The latest version of Claude Desktop must be installed (or another MCP client) +- [Node.js](https://nodejs.org/en) (v18 or higher) +- [Apify API Token](https://docs.apify.com/platform/integrations/api#api-token) (`APIFY_API_TOKEN`) + +### Install + +Follow the steps below to set up and run the server on your local machine: +First, clone the repository using the following command: + +```bash +git clone git@github.com:apify/mcp-server-rag-web-browser.git +``` + +Navigate to the project directory and install the required dependencies: + +```bash +cd mcp-server-rag-web-browser +npm install +``` + +Before running the server, you need to build the project: + +```bash +npm run build +``` + +#### Claude Desktop + +Configure Claude Desktop to recognize the MCP server. + +1. Open your Claude Desktop configuration and edit the following file: + + - On macOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json` + - On Windows: `%APPDATA%/Claude/claude_desktop_config.json` + + ```text + "mcpServers": { + "mcp-server-rag-web-browser": { + "command": "npx", + "args": [ + "/path/to/mcp-server-rag-web-browser/build/index.js" + ] + "env": { + "APIFY-API-TOKEN": "your-apify-api-token" + } + } + } + ``` + +2. Restart Claude Desktop + + - Fully quit Claude Desktop (ensure it’s not just minimized or closed). + - Restart Claude Desktop. + - Look for the 🔌 icon to confirm that the Exa server is connected. + +3. Examples + + You can ask Claude to perform web searches, such as: + ```text + What is an MCP server and how can it be used? + What is an LLM, and what are the recent news updates? + Find and analyze recent research papers about LLMs. + ``` + +## 👷🏼 Development + +### Simple local client (stdio) + +To test the server locally, you can use `example_client_stdio.ts`: + +```bash +node build/example_client_stdio.js +``` + +The script will start the MCP server, fetch available tools, and then call the `search` tool with a query. + +### Chat local client (stdio) + +To run simple chat client, you can use `example_chat_stdio.ts`: ```bash -apify run +node build/example_chat_stdio.js ``` +Here you can interact with the server using the chat interface. -## Deploy to Apify +### Test Server-Sent Events (SSE) Transport -### Connect Git repository to Apify +The SSE transport enables **server-to-client streaming** while using **HTTP POST requests** for client-to-server communication. -If you've created a Git repository for the project, you can easily connect to Apify: +#### Step 1: Start the Server -1. Go to [Actor creation page](https://console.apify.com/actors/new) -2. Click on **Link Git Repository** button +Start the server with the following command: -### Push project on your local machine to Apify +```bash +node build/sse.js +``` + +The server will start and listen on `http://localhost:3001`. + +#### Step 2: Connect to the SSE Server (Client) + +To connect to the SSE server, use the following command (acting as the client): -You can also deploy the project on your local machine to Apify without the need for the Git repository. +```bash +curl -X GET http://localhost:3001/sse +``` -1. Log in to Apify. You will need to provide your [Apify API Token](https://console.apify.com/account/integrations) to complete this action. +Upon connection, you will receive a message containing the `sessionId`, for example: - ```bash - apify login - ``` +```text +event: endpoint +data: /message?sessionId=7bd075c8-bbd1-4854-884c-e6c837148b7b +``` -2. Deploy your Actor. This command will deploy and build the Actor on the Apify Platform. You can find your newly created Actor under [Actors -> My Actors](https://console.apify.com/actors?tab=my). +#### Step 3: Send a Message to the Server - ```bash - apify push - ``` +You can send a message to the server by making a POST request with the `sessionId` and your query: -## Documentation reference +```bash +curl -X POST "http://localhost:3001/message?session_id=181c7a3d-01a9-498e-8e16-5d5878832cd7" -H "Content-Type: application/json" -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "arguments": { "query": "recent news about LLMs" }, + "name": "search" + } +}' +``` -To learn more about Apify and Actors, take a look at the following resources: +#### Step 4: Receive the Response + +For the POST request, the server will respond with: + +```text +Accepted +``` + +The server will then invoke the `search` tool using the provided query and stream the response back to the client via SSE: + +```text +event: message +data: {"result":{"content":[{"type":"text","text":"[{\"searchResult\":{\"title\":\"Language models recent news\",\"description\":\"Amazon Launches New Generation of LLM Foundation Model...\"}} +``` + +### Debugging + +Call the RAG Web Browser Actor to test it: + +```bash +APIFY_API_TOKEN=your-apify-api-token node build/example_call_web_browser.js +```` + +Since MCP servers operate over standard input/output (stdio), debugging can be challenging. +For the best debugging experience, use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). + +Build the mcp-server-rag-web-browser package: + +```bash +npm run build +``` + +You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command: + +```bash +npx @modelcontextprotocol/inspector node ~/apify/mcp-server-rag-web-browser/build/index.js APIFY_API_TOKEN=your-apify-api-token +``` -- [Apify SDK for JavaScript documentation](https://docs.apify.com/sdk/js) -- [Apify SDK for Python documentation](https://docs.apify.com/sdk/python) -- [Apify Platform documentation](https://docs.apify.com/platform) -- [Join our developer community on Discord](https://discord.com/invite/jyEM2PRvMU) +Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging. diff --git a/package.json b/package.json index 5f20697..bd74743 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "apify-mcp-server", "version": "0.0.1", "type": "module", - "description": "This is an example of an Apify actor.", + "description": "Model Context Protocol Server for Apify Actors", "engines": { "node": ">=18.0.0" }, diff --git a/src/actorDefinition.ts b/src/actorDefinition.ts index 3a8163c..2a1a2b1 100644 --- a/src/actorDefinition.ts +++ b/src/actorDefinition.ts @@ -52,10 +52,10 @@ async function fetchActorDefinition(actorFullName: string): Promise) { const input = { ...defaults, ...originalInput } as Input; - if (!input.actorNames || input.actorNames.length === 0) { - throw new Error('The `actorNames` parameter must be a non-empty array.'); + // actors can be a string or an array of strings + if (input.actors && typeof input.actors === 'string') { + input.actors = input.actors.split(',').map((format: string) => format.trim()) as string[]; + } + if (!input.actors || input.actors.length === 0) { + throw new Error('The `actors` parameter must be a non-empty array.'); } return { input }; } diff --git a/src/main.ts b/src/main.ts index 6967770..a23aab5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,3 +1,6 @@ +import type { ParsedUrlQuery } from 'querystring'; +import { parse } from 'querystring'; + import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { Actor } from 'apify'; import type { Request, Response } from 'express'; @@ -12,18 +15,13 @@ import type { Input } from './types.js'; await Actor.init(); -const { input } = await processInput((await Actor.getInput>()) ?? ({} as Input)); -log.info(`Loaded input: ${JSON.stringify(input)} `); - const STANDBY_MODE = Actor.getEnv().metaOrigin === 'STANDBY'; const HOST = Actor.isAtHome() ? process.env.ACTOR_STANDBY_URL : 'http://localhost'; const POST = Actor.isAtHome() ? process.env.ACTOR_STANDBY_PORT : 3001; const app = express(); -// Set up MCP server with tools -const tools = await getActorsAsTools(input.actorNames); -const mcpServer = new ApifyMcpServer(tools); +const mcpServer = new ApifyMcpServer(); let transport: SSEServerTransport; const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse` @@ -32,6 +30,14 @@ const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse` app.route('/') .get(async (req: Request, res: Response) => { log.info(`Received GET message at: ${req.url}`); + const params = parse(req.url.split('?')[1] || '') as ParsedUrlQuery; + delete params.token; + log.debug(`Received input parameters: ${JSON.stringify(params)}`); + const { input } = await processInput(params as Input); + if (input.actors) { + const tools = await getActorsAsTools(input.actors as string[]); + mcpServer.updateTools(tools); + } res.status(200).json({ message: `Actor is using Model Context Protocol. ${HELP_MESSAGE}` }).end(); }) .head(async (_req: Request, res: Response) => { @@ -62,10 +68,12 @@ if (STANDBY_MODE) { }); } else { log.info('Actor is not designed to run in the NORMAL model (use this mode only for debugging purposes)'); + const { input } = await processInput((await Actor.getInput>()) ?? ({} as Input)); + log.info(`Loaded input: ${JSON.stringify(input)} `); - if (input && !input.debugActorName && !input.debugActorInput) { - await Actor.fail('If you need to debug a specific actor, please provide the debugActorName and debugActorInput fields in the input.'); + if (input && !input.debugActor && !input.debugActorInput) { + await Actor.fail('If you need to debug a specific actor, please provide the debugActor and debugActorInput fields in the input.'); } - await callActorGetDataset(input.debugActorName!, input.debugActorInput!); + await callActorGetDataset(input.debugActor!, input.debugActorInput!); await Actor.exit(); } diff --git a/src/server.ts b/src/server.ts index 5a334e9..c9fbc2b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -41,10 +41,9 @@ export async function callActorGetDataset(actorName: string, input: unknown): Pr */ export class ApifyMcpServer { private server: Server; - private readonly tools: { name: string; description: string; inputSchema: object, ajvValidate: ValidateFunction}[]; - private readonly availableTools: string[]; + private tools: { name: string; description: string; inputSchema: object, ajvValidate: ValidateFunction}[]; - constructor(tools: { name: string; description: string; inputSchema: object, ajvValidate: ValidateFunction}[]) { + constructor() { this.server = new Server( { name: SERVER_NAME, @@ -56,12 +55,24 @@ export class ApifyMcpServer { }, }, ); - this.tools = tools; - this.availableTools = tools.map((tool) => tool.name); + this.tools = []; this.setupErrorHandling(); this.setupToolHandlers(); } + public addToolIfNotExist(name: string, description: string, inputSchema: object, ajvValidate: ValidateFunction): void { + if (!this.tools.find((x) => x.name === name)) { + log.info(`Adding tool: ${name}`); + this.tools.push({ name, description, inputSchema, ajvValidate }); + } + } + + public updateTools(tools: { name: string; description: string; inputSchema: object, ajvValidate: ValidateFunction}[]): void { + for (const tool of tools) { + this.addToolIfNotExist(tool.name, tool.description, tool.inputSchema, tool.ajvValidate); + } + } + private setupErrorHandling(): void { this.server.onerror = (error) => { console.error('[MCP Error]', error); // eslint-disable-line no-console @@ -88,7 +99,8 @@ export class ApifyMcpServer { }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; - if (!this.availableTools.includes(name)) { + const availableTools = this.tools.map((tool) => tool.name); + if (!availableTools.includes(name)) { throw new Error(`Unknown tool: ${name}`); } try { diff --git a/src/types.ts b/src/types.ts index 7800e86..52e6ff6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,5 @@ export type Input = { - actorNames: string[]; - debugActorName?: string; + actors: string[] | string; + debugActor?: string; debugActorInput?: unknown; }; From e1984a077bad8579d33f89c131d1c4b4363731e1 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 3 Jan 2025 15:32:50 +0100 Subject: [PATCH 08/54] Update README.md --- README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index 791035a..68fa638 100644 --- a/README.md +++ b/README.md @@ -46,17 +46,10 @@ or as a **local server** running on your machine. The Actor runs in the [**Standby mode**](https://docs.apify.com/platform/actors/running/standby), where it runs an HTTP web server that receives requests. To use Apify MCP Server, send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL: -``` -https://mcp-server.apify.actor?token= -``` -This will start a server with predefined set of Actors and tools. - -To start a server with custom set of Actors, use the following URL: +To start a server with a custom set of Actors (Google Maps Email Extractor, Facebook Posts Scraper), use the following URL: ``` https://mcp-server.apify.actor?token=&actors=apify/google-maps-email-extractor,apify/facebook-posts-scraper ``` -This will start MCP server with Google Maps Email Extractor and Facebook Posts Scraper Actors - Now you can interact with the server using MCP protocol via Server Sent Events (SSE). 1. Initiate SSE connection: From 0c792d52ad903cec5d4791e91856e4f798207431 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Mon, 6 Jan 2025 16:08:14 +0100 Subject: [PATCH 09/54] Ability to initiate SSE with a selected actors --- README.md | 15 ++++++--------- src/main.ts | 25 +++++++++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 68fa638..6160c1b 100644 --- a/README.md +++ b/README.md @@ -44,25 +44,22 @@ or as a **local server** running on your machine. #### Standby web server The Actor runs in the [**Standby mode**](https://docs.apify.com/platform/actors/running/standby), where it runs an HTTP web server that receives requests. -To use Apify MCP Server, send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL: -To start a server with a custom set of Actors (Google Maps Email Extractor, Facebook Posts Scraper), use the following URL: +1. To use Apify MCP Server with a custom set of Actors (e.g. Google Maps Email Extractor, Facebook Posts Scraper), +send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL: ``` https://mcp-server.apify.actor?token=&actors=apify/google-maps-email-extractor,apify/facebook-posts-scraper ``` -Now you can interact with the server using MCP protocol via Server Sent Events (SSE). - -1. Initiate SSE connection: -```shell -https://mcp-server.apify.actor/sse?token= +This will start an MCP server, then initiate Server Sent Events (SSE) connection and return a `sessionId`: +``` +https://mcp-server.apify.actor/sse?token=&actors=apify/google-maps-email-extractor,apify/facebook-posts-scraper ``` -On connection, you’ll receive a `sessionId`: ```shell event: endpoint data: /message?sessionId=a1b ``` -1. List available tools by making a POST request with the `sessionId`, `APIFY-API-TOKEN` and your query: +2. List available tools by making a POST request with the `sessionId`, `APIFY-API-TOKEN` and your query: ```shell curl -X POST "https://mcp-server.apify.actor/message?session_id=a1b2&token=" -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", diff --git a/src/main.ts b/src/main.ts index a23aab5..06a02b3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -27,17 +27,25 @@ let transport: SSEServerTransport; const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse` + ` and then send POST requests to ${HOST}/message.`; +/** + * Process input parameters and update tools + * @param url + */ +async function processParamsAndUpdateTools(url: string) { + const params = parse(url.split('?')[1] || '') as ParsedUrlQuery; + delete params.token; + log.debug(`Received input parameters: ${JSON.stringify(params)}`); + const { input } = await processInput(params as Input); + if (input.actors) { + const tools = await getActorsAsTools(input.actors as string[]); + mcpServer.updateTools(tools); + } +} + app.route('/') .get(async (req: Request, res: Response) => { log.info(`Received GET message at: ${req.url}`); - const params = parse(req.url.split('?')[1] || '') as ParsedUrlQuery; - delete params.token; - log.debug(`Received input parameters: ${JSON.stringify(params)}`); - const { input } = await processInput(params as Input); - if (input.actors) { - const tools = await getActorsAsTools(input.actors as string[]); - mcpServer.updateTools(tools); - } + await processParamsAndUpdateTools(req.url); res.status(200).json({ message: `Actor is using Model Context Protocol. ${HELP_MESSAGE}` }).end(); }) .head(async (_req: Request, res: Response) => { @@ -46,6 +54,7 @@ app.route('/') app.get(Routes.SSE, async (req: Request, res: Response) => { log.info(`Received GET message at: ${req.url}`); + await processParamsAndUpdateTools(req.url); transport = new SSEServerTransport(Routes.MESSAGE, res); await mcpServer.connect(transport); }); From 65f6e412e7dd1ed282c32b8e13405523d06e5f10 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Tue, 7 Jan 2025 11:25:56 +0100 Subject: [PATCH 10/54] Update README.md, remove defaults. --- README.md | 47 +++++++++++++++++++++++++++++++++++++---------- src/const.ts | 10 ---------- src/input.ts | 3 +-- src/main.ts | 4 ++-- src/server.ts | 4 +++- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 6160c1b..30df638 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ The MCP Server Actor allows an AI assistant to: ### Tools Any [Apify Actor](https://apify.com/store) can be used as a tool. -The tool name must always be the full Actor name, such as `apify/google-maps-email-extractor`, and the arguments represent the input parameters for the Actor. +The tool name must always be the full Actor name, such as `lukaskrivka/google-maps-with-contact-details`, and the arguments represent the input parameters for the Actor. Please see the examples below and refer to the specific Actor's documentation for a list of available arguments. ### Prompt & Resources @@ -43,30 +43,57 @@ or as a **local server** running on your machine. #### Standby web server -The Actor runs in the [**Standby mode**](https://docs.apify.com/platform/actors/running/standby), where it runs an HTTP web server that receives requests. +The Actor runs in [**Standby mode**](https://docs.apify.com/platform/actors/running/standby) with an HTTP web server that receives and processes requests. -1. To use Apify MCP Server with a custom set of Actors (e.g. Google Maps Email Extractor, Facebook Posts Scraper), -send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL: +##### 1. Start server with selected Actors + +To use the Apify MCP Server with a custom set of Actors (e.g. Google Maps Email Extractor, Facebook Posts Scraper), +send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL. +Provide a comma-separated list of Actors in the `actors` query parameter: ``` -https://mcp-server.apify.actor?token=&actors=apify/google-maps-email-extractor,apify/facebook-posts-scraper +https://mcp-server.apify.actor?token=&actors=lukaskrivka/google-maps-with-contact-details,apify/facebook-posts-scraper ``` -This will start an MCP server, then initiate Server Sent Events (SSE) connection and return a `sessionId`: +##### 2. Initiate Server-Sent-Events (SSE) connection +Establish an SSE connection by sending a GET request to the following URL: ``` -https://mcp-server.apify.actor/sse?token=&actors=apify/google-maps-email-extractor,apify/facebook-posts-scraper +https://mcp-server.apify.actor/sse?token= ``` +The server will respond with a `sessionId`, which you can use to send messages to the server: ```shell event: endpoint data: /message?sessionId=a1b ``` -2. List available tools by making a POST request with the `sessionId`, `APIFY-API-TOKEN` and your query: +##### 3. Send a message to the server + +Send a message by making a POST request with the `sessionId`: ```shell -curl -X POST "https://mcp-server.apify.actor/message?session_id=a1b2&token=" -H "Content-Type: application/json" -d '{ +curl -X POST "https://mcp-server.apify.actor?token=&session_id=a1b" -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, - "method": "tools/list" + "method": "tools/call", + "params": { + "arguments": { "searchStringsArray": ["restaurants in San Francisco"], "maxCrawledPlacesPerSearch": 3 }, + "name": "lukaskrivka/google-maps-with-contact-details" + } }' ``` +The MCP server will start the Actor `lukaskrivka/google-maps-with-contact-details` with the provided arguments as input parameters. +For this POST request, the server will respond with: + +```text +Accepted +``` + +##### 4: Receive the response + +The server will invoke the specified Actor as a tool using the provided query parameters and stream the response back to the client via SSE. +The response will be returned as JSON text. + +```text +event: message +data: {"result":{"content":[{"type":"text","text":"{\"searchString\":\"restaurants in San Francisco\",\"rank\":1,\"title\":\"Gary Danko\",\"description\":\"Renowned chef Gary Danko's fixed-price menus of American cuisine ... \",\"price\":\"$100+\"...}}]}} +``` ## 🛠️ Configuration diff --git a/src/const.ts b/src/const.ts index 8fe8633..3197201 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,16 +1,6 @@ export const SERVER_NAME = 'apify-mcp-server'; export const SERVER_VERSION = '0.1.0'; -export const defaults = { - actors: [ - 'apify/facebook-posts-scraper', - 'apify/google-search-scraper', - 'apify/instagram-scraper', - 'apify/rag-web-browser', - 'compass/google-maps-extractor', - ], -}; - export enum Routes { SSE = '/sse', MESSAGE = '/message', diff --git a/src/input.ts b/src/input.ts index 186a392..7dc662b 100644 --- a/src/input.ts +++ b/src/input.ts @@ -1,8 +1,7 @@ -import { defaults } from './const.js'; import type { Input } from './types.js'; export async function processInput(originalInput: Partial) { - const input = { ...defaults, ...originalInput } as Input; + const input = originalInput; // actors can be a string or an array of strings if (input.actors && typeof input.actors === 'string') { diff --git a/src/main.ts b/src/main.ts index 06a02b3..6af28e7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -24,8 +24,8 @@ const app = express(); const mcpServer = new ApifyMcpServer(); let transport: SSEServerTransport; -const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse` - + ` and then send POST requests to ${HOST}/message.`; +const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse?token=YOUR-APIFY-API-TOKEN` + + ` and then send POST requests to ${HOST}/message?token=YOUR-APIFY-API-TOKEN.`; /** * Process input parameters and update tools diff --git a/src/server.ts b/src/server.ts index c9fbc2b..45ceb2a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -62,8 +62,10 @@ export class ApifyMcpServer { public addToolIfNotExist(name: string, description: string, inputSchema: object, ajvValidate: ValidateFunction): void { if (!this.tools.find((x) => x.name === name)) { - log.info(`Adding tool: ${name}`); this.tools.push({ name, description, inputSchema, ajvValidate }); + log.info(`Added tool: ${name}`); + } else { + log.info(`Tool already exists: ${name}`); } } From 552cedded462fae53a5e9d6cf6315a561554fd63 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Tue, 7 Jan 2025 11:29:00 +0100 Subject: [PATCH 11/54] Remove query parameters from SSE --- .actor/input_schema.json | 2 +- src/main.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.actor/input_schema.json b/.actor/input_schema.json index f311cd5..2917fac 100644 --- a/.actor/input_schema.json +++ b/.actor/input_schema.json @@ -17,7 +17,7 @@ ] }, "debugActor": { - "title": "Debug actor name", + "title": "Debug actor", "type": "string", "description": "Specify the name of the actor that will be used for debugging in normal mode", "editor": "textfield", diff --git a/src/main.ts b/src/main.ts index 6af28e7..0a1b220 100644 --- a/src/main.ts +++ b/src/main.ts @@ -54,7 +54,6 @@ app.route('/') app.get(Routes.SSE, async (req: Request, res: Response) => { log.info(`Received GET message at: ${req.url}`); - await processParamsAndUpdateTools(req.url); transport = new SSEServerTransport(Routes.MESSAGE, res); await mcpServer.connect(transport); }); From 5cf7c00ac6232901c6b584a155df2c0cbb0e8e36 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Tue, 7 Jan 2025 14:32:32 +0100 Subject: [PATCH 12/54] Update README.md --- README.md | 97 ++++++++++++++++++++++++++----------------------------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 30df638..1013f14 100644 --- a/README.md +++ b/README.md @@ -45,78 +45,71 @@ or as a **local server** running on your machine. The Actor runs in [**Standby mode**](https://docs.apify.com/platform/actors/running/standby) with an HTTP web server that receives and processes requests. -##### 1. Start server with selected Actors - -To use the Apify MCP Server with a custom set of Actors (e.g. Google Maps Email Extractor, Facebook Posts Scraper), -send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL. -Provide a comma-separated list of Actors in the `actors` query parameter: -``` -https://mcp-server.apify.actor?token=&actors=lukaskrivka/google-maps-with-contact-details,apify/facebook-posts-scraper -``` -##### 2. Initiate Server-Sent-Events (SSE) connection -Establish an SSE connection by sending a GET request to the following URL: -``` -https://mcp-server.apify.actor/sse?token= -``` -The server will respond with a `sessionId`, which you can use to send messages to the server: -```shell -event: endpoint -data: /message?sessionId=a1b -``` - -##### 3. Send a message to the server +1. **Start server with selected Actors**. To use the Apify MCP Server with a custom set of Actors (e.g. Google Maps Email Extractor, Facebook Posts Scraper), + send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL. + Provide a comma-separated list of Actors in the `actors` query parameter: + ``` + https://mcp-server.apify.actor?token=&actors=lukaskrivka/google-maps-with-contact-details,apify/facebook-posts-scraper + ``` -Send a message by making a POST request with the `sessionId`: -```shell -curl -X POST "https://mcp-server.apify.actor?token=&session_id=a1b" -H "Content-Type: application/json" -d '{ - "jsonrpc": "2.0", - "id": 1, - "method": "tools/call", - "params": { - "arguments": { "searchStringsArray": ["restaurants in San Francisco"], "maxCrawledPlacesPerSearch": 3 }, - "name": "lukaskrivka/google-maps-with-contact-details" - } -}' -``` -The MCP server will start the Actor `lukaskrivka/google-maps-with-contact-details` with the provided arguments as input parameters. -For this POST request, the server will respond with: +2. **Initiate Server-Sent-Events (SSE)** by sending a GET request to the following URL: + ``` + https://mcp-server.apify.actor/sse?token= + ``` + The server will respond with a `sessionId`, which you can use to send messages to the server: + ```shell + event: endpoint + data: /message?sessionId=a1b + ``` -```text -Accepted -``` +3. **Send a message to the server** by making a POST request with the `sessionId`: + ```shell + curl -X POST "https://mcp-server.apify.actor?token=&session_id=a1b" -H "Content-Type: application/json" -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "arguments": { "searchStringsArray": ["restaurants in San Francisco"], "maxCrawledPlacesPerSearch": 3 }, + "name": "lukaskrivka/google-maps-with-contact-details" + } + }' + ``` + The MCP server will start the Actor `lukaskrivka/google-maps-with-contact-details` with the provided arguments as input parameters. + For this POST request, the server will respond with: -##### 4: Receive the response + ```text + Accepted + ``` -The server will invoke the specified Actor as a tool using the provided query parameters and stream the response back to the client via SSE. -The response will be returned as JSON text. +4. **Receive the response.** The server will invoke the specified Actor as a tool using the provided query parameters and stream the response back to the client via SSE. + The response will be returned as JSON text. -```text -event: message -data: {"result":{"content":[{"type":"text","text":"{\"searchString\":\"restaurants in San Francisco\",\"rank\":1,\"title\":\"Gary Danko\",\"description\":\"Renowned chef Gary Danko's fixed-price menus of American cuisine ... \",\"price\":\"$100+\"...}}]}} -``` + ```text + event: message + data: {"result":{"content":[{"type":"text","text":"{\"searchString\":\"restaurants in San Francisco\",\"rank\":1,\"title\":\"Gary Danko\",\"description\":\"Renowned chef Gary Danko's fixed-price menus of American cuisine ... \",\"price\":\"$100+\"...}}]}} + ``` -## 🛠️ Configuration +### MCP Server at local host -### Prerequisites +#### Prerequisites - MacOS or Windows - The latest version of Claude Desktop must be installed (or another MCP client) - [Node.js](https://nodejs.org/en) (v18 or higher) - [Apify API Token](https://docs.apify.com/platform/integrations/api#api-token) (`APIFY_API_TOKEN`) -### Install +#### Install Follow the steps below to set up and run the server on your local machine: First, clone the repository using the following command: ```bash -git clone git@github.com:apify/mcp-server-rag-web-browser.git +git clone git@github.com:apify/actor-mcp-server.git ``` - Navigate to the project directory and install the required dependencies: ```bash -cd mcp-server-rag-web-browser +cd actor-mcp-server npm install ``` @@ -137,10 +130,10 @@ Configure Claude Desktop to recognize the MCP server. ```text "mcpServers": { - "mcp-server-rag-web-browser": { + "apify-mcp-server": { "command": "npx", "args": [ - "/path/to/mcp-server-rag-web-browser/build/index.js" + "/path/to/actor-mcp-server/dist/index.js" ] "env": { "APIFY-API-TOKEN": "your-apify-api-token" From ff4ec05832078a3bccf609d3ac7353e1afee6549 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 8 Jan 2025 10:10:35 +0100 Subject: [PATCH 13/54] Add eventsource --- package-lock.json | 33 +++++++++++++++++++++++++++++---- package.json | 3 ++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8cf72a3..f17328f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,11 @@ "version": "0.0.1", "license": "ISC", "dependencies": { - "@modelcontextprotocol/sdk": "^1.0.4", + "@modelcontextprotocol/sdk": "^1.1.0", "ajv": "^8.17.1", "apify": "^3.2.6", "apify-client": "^2.11.0", + "eventsource": "^3.0.2", "express": "^4.21.2" }, "devDependencies": { @@ -964,14 +965,17 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.0.4.tgz", - "integrity": "sha512-C+jw1lF6HSGzs7EZpzHbXfzz9rj9him4BaoumlTciW/IDDgIpweF/qiCWKlP02QKg5PPcgY6xY2WCt5y2tpYow==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.1.0.tgz", + "integrity": "sha512-o5PIPz0vc1bJYXS0oLvRr8yUOzYtxEFL1rWP4aiO8qLslCksmbKhONy6CTpq0WPuIXUt2YuXoRtVA2EcLix3fw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", "raw-body": "^3.0.0", "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" } }, "node_modules/@nodelib/fs.scandir": { @@ -3028,6 +3032,27 @@ "through": "~2.3.1" } }, + "node_modules/eventsource": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.2.tgz", + "integrity": "sha512-YolzkJNxsTL3tCJMWFxpxtG2sCjbZ4LQUBUrkdaJK0ub0p6lmJt+2+1SwhKjLc652lpH9L/79Ptez972H9tphw==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", + "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", diff --git a/package.json b/package.json index bd74743..f4d52a1 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,11 @@ "node": ">=18.0.0" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.0.4", + "@modelcontextprotocol/sdk": "^1.1.0", "ajv": "^8.17.1", "apify": "^3.2.6", "apify-client": "^2.11.0", + "eventsource": "^3.0.2", "express": "^4.21.2" }, "devDependencies": { From f516fe266c081da3995f4dd02b1883bb24697d6c Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 8 Jan 2025 22:06:48 +0100 Subject: [PATCH 14/54] Add example clients --- package-lock.json | 18 ++++++++++- package.json | 3 +- src/examples/clientStdio.ts | 62 +++++++++++++++++++++++++++++++++++++ src/examples/client_sse.py | 49 +++++++++++++++++++++++++++++ src/index.ts | 25 +++++++++++++++ 5 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 src/examples/clientStdio.ts create mode 100644 src/examples/client_sse.py create mode 100644 src/index.ts diff --git a/package-lock.json b/package-lock.json index f17328f..fb76c16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,14 +13,15 @@ "ajv": "^8.17.1", "apify": "^3.2.6", "apify-client": "^2.11.0", - "eventsource": "^3.0.2", "express": "^4.21.2" }, "devDependencies": { "@apify/eslint-config": "^0.5.0-beta.2", "@apify/tsconfig": "^0.1.0", "@types/express": "^5.0.0", + "dotenv": "^16.4.7", "eslint": "^9.17.0", + "eventsource": "^3.0.2", "tsx": "^4.6.2", "typescript": "^5.3.3", "typescript-eslint": "^8.18.2" @@ -2390,6 +2391,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3036,6 +3050,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.2.tgz", "integrity": "sha512-YolzkJNxsTL3tCJMWFxpxtG2sCjbZ4LQUBUrkdaJK0ub0p6lmJt+2+1SwhKjLc652lpH9L/79Ptez972H9tphw==", + "dev": true, "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.0" @@ -3048,6 +3063,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" diff --git a/package.json b/package.json index f4d52a1..64da032 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,15 @@ "ajv": "^8.17.1", "apify": "^3.2.6", "apify-client": "^2.11.0", - "eventsource": "^3.0.2", "express": "^4.21.2" }, "devDependencies": { "@apify/eslint-config": "^0.5.0-beta.2", "@apify/tsconfig": "^0.1.0", "@types/express": "^5.0.0", + "dotenv": "^16.4.7", "eslint": "^9.17.0", + "eventsource": "^3.0.2", "tsx": "^4.6.2", "typescript": "^5.3.3", "typescript-eslint": "^8.18.2" diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts new file mode 100644 index 0000000..886dc5d --- /dev/null +++ b/src/examples/clientStdio.ts @@ -0,0 +1,62 @@ +/* eslint-disable no-console */ +/** + * Connect to the MCP server using stdio transport and call a tool. + * You need provide a path to MCP server and APIFY_TOKEN in .env file. + * + * Also, you need to choose ACTORS to run in the server, for example: apify/rag-web-browser + */ + +import { execSync } from 'child_process'; + +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; +import dotenv from 'dotenv'; + +dotenv.config({ path: '../../.env' }); + +const SERVER_PATH = '../../dist/index.js'; +const NODE_PATH = execSync('which node').toString().trim(); +const TOOLS = 'apify/rag-web-browser,lukaskrivka/google-maps-with-contact-details'; + +// Create server parameters for stdio connection +const transport = new StdioClientTransport({ + command: NODE_PATH, + args: [SERVER_PATH, '--actors', TOOLS], + env: { APIFY_TOKEN: process.env.APIFY_TOKEN || '' }, +}); + +// Create a new client instance +const client = new Client( + { name: 'example-client', version: '0.1.0' }, + { capabilities: {} }, +); + +// Main function to run the example client +async function run() { + try { + // Connect to the MCP server + await client.connect(transport); + + // List available tools + const tools = await client.listTools(); + console.log('Available tools:', tools); + + if (tools.tools.length === 0) { + console.log('No tools available'); + return; + } + + // Call a tool + console.log('Calling actor ...'); + const result = await client.callTool( + { name: 'apify/rag-web-browser', arguments: { query: 'web browser for Anthropic' } }, + CallToolResultSchema, + ); + console.log('Tool result:', JSON.stringify(result)); + } catch (error) { + console.error('Error:', error); + } +} + +await run(); diff --git a/src/examples/client_sse.py b/src/examples/client_sse.py new file mode 100644 index 0000000..f776c4d --- /dev/null +++ b/src/examples/client_sse.py @@ -0,0 +1,49 @@ +""" +Test MCP Server with Actors using SSE client + +This script demonstrates how to test the MCP Server with Actors using the SSE client. +It is using python client as the typescript one does not support custom headers when connecting to the SSE server. + +Install dependencies: +pip install requests python-dotenv mcp +""" + +import asyncio +import os + +import requests +from dotenv import load_dotenv +from mcp.client.session import ClientSession +from mcp.client.sse import sse_client + +load_dotenv(dotenv_path="../../.env") + +MCP_SERVER_URL = "https://mcp-server.apify.actor" +ACTORS = "apify/rag-web-browser" + +HEADERS = {"Authorization": f"Bearer {os.getenv('APIFY_TOKEN')}"} + +async def run() -> None: + + print("Start MCP Server with Actors", ACTORS) + r = requests.get(MCP_SERVER_URL, params={"actors": ACTORS}, headers=HEADERS) + print("MCP Server Response:", r.json(), end="\n\n") + + async with sse_client(url=f"{MCP_SERVER_URL}/sse", timeout=60, headers=HEADERS) as (read, write): + async with ClientSession(read, write) as session: + await session.initialize() + + tools = await session.list_tools() + print("Available Tools:", tools, end="\n\n") + + if hasattr(tools, "tools") and not tools.tools: + print("No tools available! Start MCP server with Actors") + return + + result = await session.call_tool("apify/rag-web-browser", { "query": "example.com", "maxResults": 3 }) + print("Tools Call Result:") + + for content in result.content: + print(content) + +asyncio.run(run()) diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..df42151 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,25 @@ +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; + +import { ApifyMcpServer } from './server.js'; + +const argActors = process.argv.find((arg) => arg.startsWith('ACTORS='))?.split('=')[1]; + +async function main() { + const server = new ApifyMcpServer(); + + if (argActors) { + if (argActors && typeof argActors === 'string') { + const actors = argActors.split(',').map((format: string) => format.trim()) as string[]; + await server.addToolsFromActors(actors); + } + } else { + await server.addToolsFromDefaultActors(); + } + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((error) => { + console.error('Server error:', error); // eslint-disable-line no-console + process.exit(1); +}); From 76bf0fbd898641f6be492b6644c470bf9dad1c16 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 8 Jan 2025 22:25:20 +0100 Subject: [PATCH 15/54] Load 5 default Actors as a tool, to simplify onboarding --- .actor/input_schema.json | 18 +----------- README.md | 60 +++++++++++++++++++++++++++++--------- package-lock.json | 12 ++++++-- package.json | 4 ++- src/actorDefinition.ts | 10 +++++++ src/const.ts | 10 +++++++ src/index.ts | 16 ++++------ src/input.ts | 2 +- src/main.ts | 10 ++----- src/server.ts | 63 +++++++++++++++++++++++----------------- 10 files changed, 127 insertions(+), 78 deletions(-) diff --git a/.actor/input_schema.json b/.actor/input_schema.json index 2917fac..3bcfb0b 100644 --- a/.actor/input_schema.json +++ b/.actor/input_schema.json @@ -3,19 +3,6 @@ "type": "object", "schemaVersion": 1, "properties": { - "actors": { - "title": "Actors names to be exposed for an AI application (AI agent)", - "type": "array", - "description": "Specify the names of actors that will be exposed to an AI application (AI agent) and communicate with Apify Actors via the MCP protocol", - "editor": "stringList", - "prefill": [ - "apify/facebook-posts-scraper", - "apify/google-search-scraper", - "apify/instagram-scraper", - "apify/rag-web-browser", - "compass/google-maps-extractor" - ] - }, "debugActor": { "title": "Debug actor", "type": "string", @@ -33,8 +20,5 @@ "query": "hello world" } } - }, - "required": [ - "actors" - ] + } } diff --git a/README.md b/README.md index 1013f14..bd3eae4 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ MCP is an open protocol that enables secure, controlled interactions between AI ## 🎯 What does this MCP server do? The MCP Server Actor allows an AI assistant to: -- Use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task. -- For example: - - [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) scrape websites of Google Maps places for contact details and get email addresses, website, location, address, zipcode, phone number, social media links. +- Use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task.For example it can: - [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from hundreds of Facebook posts from one or multiple Facebook pages and profiles + - [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) Extract Google Maps contact details + - [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) Scrape Google Search Engine Results Pages (SERPs) - [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrape and download Instagram posts, profiles, places, hashtags, photos, and comments - [RAG Web Browser](https://apify.com/apify/web-scraper) perform web search, scrape the top N URLs from the results, and return their cleaned content as Markdown @@ -27,7 +27,18 @@ The MCP Server Actor allows an AI assistant to: ### Tools Any [Apify Actor](https://apify.com/store) can be used as a tool. -The tool name must always be the full Actor name, such as `lukaskrivka/google-maps-with-contact-details`, and the arguments represent the input parameters for the Actor. +By default, the server is pre-configured with the tools specified above, but it can be overridden by providing a list of Actor names in the `actors` query parameter. + +The tool name must always be the full Actor name, such as `lukaskrivka/google-maps-with-contact-details` + +```text +'apify/facebook-posts-scraper' +'apify/google-search-scraper' +'apify/instagram-scraper' +'apify/rag-web-browser' +'lukaskrivka/google-maps-with-contact-details' +``` +The arguments for MCP tool represent input parameters for the Actor. Please see the examples below and refer to the specific Actor's documentation for a list of available arguments. ### Prompt & Resources @@ -89,6 +100,12 @@ The Actor runs in [**Standby mode**](https://docs.apify.com/platform/actors/runn data: {"result":{"content":[{"type":"text","text":"{\"searchString\":\"restaurants in San Francisco\",\"rank\":1,\"title\":\"Gary Danko\",\"description\":\"Renowned chef Gary Danko's fixed-price menus of American cuisine ... \",\"price\":\"$100+\"...}}]}} ``` +You can also start MCP server with a different set of tools by providing a list of Actor names in the `actors` query parameter. +``` +https://mcp-server.apify.actor?token=&actors=junglee/free-amazon-product-scraper,junglee/free-amazon-product-scraper +``` +You can find list of all available Actors in the [Apify Store](https://apify.com/store). + ### MCP Server at local host #### Prerequisites @@ -142,6 +159,23 @@ Configure Claude Desktop to recognize the MCP server. } ``` + Alternatively, you can use the following command to select one or more Apify Actors as tools: + ```text + "mcpServers": { + "apify-mcp-server": { + "command": "npx", + "args": [ + "/path/to/actor-mcp-server/dist/index.js", + "--actors", + "lukaskrivka/google-maps-with-contact-details,apify/facebook-posts-scraper" + ] + "env": { + "APIFY-API-TOKEN": "your-apify-api-token" + } + } + } + ``` + 2. Restart Claude Desktop - Fully quit Claude Desktop (ensure it’s not just minimized or closed). @@ -152,33 +186,33 @@ Configure Claude Desktop to recognize the MCP server. You can ask Claude to perform web searches, such as: ```text - What is an MCP server and how can it be used? - What is an LLM, and what are the recent news updates? Find and analyze recent research papers about LLMs. + Find best restaurants in San Francisco. + Find and analyze comments about the latest iPhone. ``` ## 👷🏼 Development ### Simple local client (stdio) -To test the server locally, you can use `example_client_stdio.ts`: +To test the server locally, you can use `examples/clientStdio.ts`: ```bash -node build/example_client_stdio.js +node dist/clientStdio.js ``` -The script will start the MCP server, fetch available tools, and then call the `search` tool with a query. +The script will start the MCP server with default Actors and then call the `apify/rag-web-browser` tool with a query. ### Chat local client (stdio) To run simple chat client, you can use `example_chat_stdio.ts`: ```bash -node build/example_chat_stdio.js +node dist/example_chat_stdio.js ``` Here you can interact with the server using the chat interface. -### Test Server-Sent Events (SSE) Transport +### Server-Sent Events (SSE) Transport The SSE transport enables **server-to-client streaming** while using **HTTP POST requests** for client-to-server communication. @@ -187,7 +221,7 @@ The SSE transport enables **server-to-client streaming** while using **HTTP POST Start the server with the following command: ```bash -node build/sse.js +node dist/sse.js ``` The server will start and listen on `http://localhost:3001`. @@ -243,7 +277,7 @@ data: {"result":{"content":[{"type":"text","text":"[{\"searchResult\":{\"title\" Call the RAG Web Browser Actor to test it: ```bash -APIFY_API_TOKEN=your-apify-api-token node build/example_call_web_browser.js +APIFY_API_TOKEN=your-apify-api-token node dist/example_call_web_browser.js ```` Since MCP servers operate over standard input/output (stdio), debugging can be challenging. diff --git a/package-lock.json b/package-lock.json index fb76c16..7bf85c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,12 +13,14 @@ "ajv": "^8.17.1", "apify": "^3.2.6", "apify-client": "^2.11.0", - "express": "^4.21.2" + "express": "^4.21.2", + "minimist": "^1.2.8" }, "devDependencies": { "@apify/eslint-config": "^0.5.0-beta.2", "@apify/tsconfig": "^0.1.0", "@types/express": "^5.0.0", + "@types/minimist": "^1.2.5", "dotenv": "^16.4.7", "eslint": "^9.17.0", "eventsource": "^3.0.2", @@ -1171,6 +1173,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.10.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", @@ -4642,7 +4651,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" diff --git a/package.json b/package.json index 64da032..1934d7f 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,14 @@ "ajv": "^8.17.1", "apify": "^3.2.6", "apify-client": "^2.11.0", - "express": "^4.21.2" + "express": "^4.21.2", + "minimist": "^1.2.8" }, "devDependencies": { "@apify/eslint-config": "^0.5.0-beta.2", "@apify/tsconfig": "^0.1.0", "@types/express": "^5.0.0", + "@types/minimist": "^1.2.5", "dotenv": "^16.4.7", "eslint": "^9.17.0", "eventsource": "^3.0.2", diff --git a/src/actorDefinition.ts b/src/actorDefinition.ts index 2a1a2b1..6f9d06e 100644 --- a/src/actorDefinition.ts +++ b/src/actorDefinition.ts @@ -52,6 +52,9 @@ async function fetchActorDefinition(actorFullName: string): Promise log.error('Global Error:', error)); + +// export async function getListOfActors() { +// const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); +// const results = await client.store().list({ limit: 20, offset: 0, sortBy: 'popularity' }); +// return results.items.filter((x) => !x.categories.includes('DEVELOPER_TOOLS')).map((x) => `${x.username}/${x.name}`); +// // return v.items.map((x) => `${x.username}/${x.name}`); +// } diff --git a/src/const.ts b/src/const.ts index 3197201..8fe8633 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,6 +1,16 @@ export const SERVER_NAME = 'apify-mcp-server'; export const SERVER_VERSION = '0.1.0'; +export const defaults = { + actors: [ + 'apify/facebook-posts-scraper', + 'apify/google-search-scraper', + 'apify/instagram-scraper', + 'apify/rag-web-browser', + 'compass/google-maps-extractor', + ], +}; + export enum Routes { SSE = '/sse', MESSAGE = '/message', diff --git a/src/index.ts b/src/index.ts index df42151..0e04373 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,20 +1,16 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import minimist from 'minimist'; import { ApifyMcpServer } from './server.js'; -const argActors = process.argv.find((arg) => arg.startsWith('ACTORS='))?.split('=')[1]; +const argv = minimist(process.argv.slice(2)); +const argActors = argv.actors?.split(',').map((actor: string) => actor.trim()) || []; async function main() { const server = new ApifyMcpServer(); - - if (argActors) { - if (argActors && typeof argActors === 'string') { - const actors = argActors.split(',').map((format: string) => format.trim()) as string[]; - await server.addToolsFromActors(actors); - } - } else { - await server.addToolsFromDefaultActors(); - } + await (argActors + ? server.addToolsFromActors(argActors) + : server.addToolsFromDefaultActors()); const transport = new StdioServerTransport(); await server.connect(transport); } diff --git a/src/input.ts b/src/input.ts index 7dc662b..799d836 100644 --- a/src/input.ts +++ b/src/input.ts @@ -1,7 +1,7 @@ import type { Input } from './types.js'; export async function processInput(originalInput: Partial) { - const input = originalInput; + const input = originalInput as Input; // actors can be a string or an array of strings if (input.actors && typeof input.actors === 'string') { diff --git a/src/main.ts b/src/main.ts index 0a1b220..0bdc852 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,11 +6,10 @@ import { Actor } from 'apify'; import type { Request, Response } from 'express'; import express from 'express'; -import { getActorsAsTools } from './actorDefinition.js'; import { Routes } from './const.js'; import { processInput } from './input.js'; import { log } from './logger.js'; -import { ApifyMcpServer, callActorGetDataset } from './server.js'; +import { ApifyMcpServer } from './server.js'; import type { Input } from './types.js'; await Actor.init(); @@ -36,10 +35,7 @@ async function processParamsAndUpdateTools(url: string) { delete params.token; log.debug(`Received input parameters: ${JSON.stringify(params)}`); const { input } = await processInput(params as Input); - if (input.actors) { - const tools = await getActorsAsTools(input.actors as string[]); - mcpServer.updateTools(tools); - } + await (input.actors ? mcpServer.addToolsFromActors(input.actors as string[]) : mcpServer.addToolsFromDefaultActors()); } app.route('/') @@ -82,6 +78,6 @@ if (STANDBY_MODE) { if (input && !input.debugActor && !input.debugActorInput) { await Actor.fail('If you need to debug a specific actor, please provide the debugActor and debugActorInput fields in the input.'); } - await callActorGetDataset(input.debugActor!, input.debugActorInput!); + await mcpServer.callActorGetDataset(input.debugActor!, input.debugActorInput!); await Actor.exit(); } diff --git a/src/server.ts b/src/server.ts index 45ceb2a..058c9db 100644 --- a/src/server.ts +++ b/src/server.ts @@ -9,35 +9,12 @@ import type { ValidateFunction } from 'ajv'; import { Actor } from 'apify'; import { ApifyClient } from 'apify-client'; -import { SERVER_NAME, SERVER_VERSION } from './const.js'; +import { getActorsAsTools } from './actorDefinition.js'; +import { defaults, SERVER_NAME, SERVER_VERSION } from './const.js'; import { log } from './logger.js'; -export async function callActorGetDataset(actorName: string, input: unknown): Promise { - const apifyApiToken = process.env.APIFY_TOKEN; - if (!apifyApiToken) { - throw new Error('APIFY_TOKEN is required but not set. Please set it as an environment variable'); - } - try { - log.info(`Calling actor ${actorName} with input: ${JSON.stringify(input)}`); - const client = new ApifyClient({ token: apifyApiToken }); - const actorClient = client.actor(actorName); - - const results = await actorClient.call(input); - const dataset = await client.dataset(results.defaultDatasetId).listItems(); - log.info(`Actor ${actorName} finished with ${dataset.items.length} items`); - if (process.env.APIFY_IS_AT_HOME) { - await Actor.pushData(dataset.items); - log.info(`Pushed ${dataset.items.length} items to the dataset`); - } - return dataset.items; - } catch (error) { - log.error(`Error calling actor: ${error}. Actor: ${actorName}, input: ${JSON.stringify(input)}`); - throw new Error(`Error calling actor: ${error}`); - } -} - /** - * Create an MCP server with a tool to call RAG Web Browser Actor + * Create Apify MCP server */ export class ApifyMcpServer { private server: Server; @@ -60,6 +37,38 @@ export class ApifyMcpServer { this.setupToolHandlers(); } + public async callActorGetDataset(actorName: string, input: unknown): Promise { + if (!process.env.APIFY_TOKEN) { + throw new Error('APIFY_TOKEN is required but not set. Please set it as an environment variable'); + } + try { + log.info(`Calling actor ${actorName} with input: ${JSON.stringify(input)}`); + const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); + const actorClient = client.actor(actorName); + + const results = await actorClient.call(input); + const dataset = await client.dataset(results.defaultDatasetId).listItems(); + log.info(`Actor ${actorName} finished with ${dataset.items.length} items`); + if (process.env.APIFY_IS_AT_HOME) { + await Actor.pushData(dataset.items); + log.info(`Pushed ${dataset.items.length} items to the dataset`); + } + return dataset.items; + } catch (error) { + log.error(`Error calling actor: ${error}. Actor: ${actorName}, input: ${JSON.stringify(input)}`); + throw new Error(`Error calling actor: ${error}`); + } + } + + public async addToolsFromActors(actors: string[]) { + const tools = await getActorsAsTools(actors); + this.updateTools(tools); + } + + public async addToolsFromDefaultActors() { + await this.addToolsFromActors(defaults.actors); + } + public addToolIfNotExist(name: string, description: string, inputSchema: object, ajvValidate: ValidateFunction): void { if (!this.tools.find((x) => x.name === name)) { this.tools.push({ name, description, inputSchema, ajvValidate }); @@ -108,7 +117,7 @@ export class ApifyMcpServer { try { log.info(`Validating arguments for tool: ${name} with arguments: ${JSON.stringify(args)}`); const validatedArgs = this.validateArguments(name, args); - const items = await callActorGetDataset(name, validatedArgs); + const items = await this.callActorGetDataset(name, validatedArgs); return { content: items.map((item) => ({ type: 'text', text: JSON.stringify(item) })) }; } catch (error) { log.error(`Error calling tool: ${error}`); From 1655d7098fd7e8f3a5be29efb7c77b721064493a Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 8 Jan 2025 22:36:11 +0100 Subject: [PATCH 16/54] Update README.md --- README.md | 88 +++++++++++++------------------------------------------ 1 file changed, 21 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index bd3eae4..d864c4b 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Please see the examples below and refer to the specific Actor's documentation fo ### Prompt & Resources The server does not provide any resources and prompts. +We plan to add Apify's dataset and key-value store as resources in the future. ## ⚙️ Usage @@ -158,8 +159,7 @@ Configure Claude Desktop to recognize the MCP server. } } ``` - - Alternatively, you can use the following command to select one or more Apify Actors as tools: + Alternatively, you can use the following command to select one or more Apify Actors: ```text "mcpServers": { "apify-mcp-server": { @@ -193,7 +193,17 @@ Configure Claude Desktop to recognize the MCP server. ## 👷🏼 Development -### Simple local client (stdio) +### Prerequisites + +- [Node.js](https://nodejs.org/en) (v18 or higher) +- Python 3.6 or higher + +Create environment file `.env` with the following content: +```text +APIFY_API_TOKEN=your-apify-api-token +``` + +### Local client (stdio) To test the server locally, you can use `examples/clientStdio.ts`: @@ -201,7 +211,8 @@ To test the server locally, you can use `examples/clientStdio.ts`: node dist/clientStdio.js ``` -The script will start the MCP server with default Actors and then call the `apify/rag-web-browser` tool with a query. +The script will start the MCP server with two Actors (`lukaskrivka/google-maps-with-contact-details` and `apify/rag-web-browser`). +Then it will call `apify/rag-web-browser` tool with a query and will print the result. ### Chat local client (stdio) @@ -212,78 +223,21 @@ node dist/example_chat_stdio.js ``` Here you can interact with the server using the chat interface. -### Server-Sent Events (SSE) Transport - -The SSE transport enables **server-to-client streaming** while using **HTTP POST requests** for client-to-server communication. - -#### Step 1: Start the Server - -Start the server with the following command: - -```bash -node dist/sse.js -``` - -The server will start and listen on `http://localhost:3001`. - -#### Step 2: Connect to the SSE Server (Client) - -To connect to the SSE server, use the following command (acting as the client): - -```bash -curl -X GET http://localhost:3001/sse -``` - -Upon connection, you will receive a message containing the `sessionId`, for example: - -```text -event: endpoint -data: /message?sessionId=7bd075c8-bbd1-4854-884c-e6c837148b7b -``` +### Local client (SSE) -#### Step 3: Send a Message to the Server - -You can send a message to the server by making a POST request with the `sessionId` and your query: +To test the server with the SSE transport, you can use python script `examples/client_sse.py`: +Currently, the node.js client does not support to establish a connection to remote server witch custom headers. ```bash -curl -X POST "http://localhost:3001/message?session_id=181c7a3d-01a9-498e-8e16-5d5878832cd7" -H "Content-Type: application/json" -d '{ - "jsonrpc": "2.0", - "id": 1, - "method": "tools/call", - "params": { - "arguments": { "query": "recent news about LLMs" }, - "name": "search" - } -}' -``` - -#### Step 4: Receive the Response - -For the POST request, the server will respond with: - -```text -Accepted -``` - -The server will then invoke the `search` tool using the provided query and stream the response back to the client via SSE: - -```text -event: message -data: {"result":{"content":[{"type":"text","text":"[{\"searchResult\":{\"title\":\"Language models recent news\",\"description\":\"Amazon Launches New Generation of LLM Foundation Model...\"}} +node dist/clientSse.js ``` ### Debugging -Call the RAG Web Browser Actor to test it: - -```bash -APIFY_API_TOKEN=your-apify-api-token node dist/example_call_web_browser.js -```` - Since MCP servers operate over standard input/output (stdio), debugging can be challenging. For the best debugging experience, use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). -Build the mcp-server-rag-web-browser package: +Build the actor-mcp-server package: ```bash npm run build @@ -292,7 +246,7 @@ npm run build You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command: ```bash -npx @modelcontextprotocol/inspector node ~/apify/mcp-server-rag-web-browser/build/index.js APIFY_API_TOKEN=your-apify-api-token +npx @modelcontextprotocol/inspector node /path/to/actor-mcp-server/dist/index.js --env APIFY_API_TOKEN=your-apify-api-token ``` Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging. From 259e1c401e12ec957079d52db39a9e60b2a5664f Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 8 Jan 2025 23:40:32 +0100 Subject: [PATCH 17/54] Add chat stdio --- .env.example | 3 + README.md | 6 +- package-lock.json | 201 ++++++++++++++++++++++++++++++ package.json | 2 + src/actorDefinition.ts | 3 +- src/examples/clientStdioChat.ts | 208 ++++++++++++++++++++++++++++++++ src/index.ts | 2 +- 7 files changed, 420 insertions(+), 5 deletions(-) create mode 100644 .env.example create mode 100644 src/examples/clientStdioChat.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..063f213 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +APIFY_API_TOKEN= +# ANTHROPIC_API_KEY is only required when you want to run examples/clientStdioChat.js +ANTHROPIC_API_KEY= diff --git a/README.md b/README.md index d864c4b..50f5162 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ APIFY_API_TOKEN=your-apify-api-token To test the server locally, you can use `examples/clientStdio.ts`: ```bash -node dist/clientStdio.js +node dist/examples/clientStdio.js ``` The script will start the MCP server with two Actors (`lukaskrivka/google-maps-with-contact-details` and `apify/rag-web-browser`). @@ -216,10 +216,10 @@ Then it will call `apify/rag-web-browser` tool with a query and will print the r ### Chat local client (stdio) -To run simple chat client, you can use `example_chat_stdio.ts`: +To run simple chat client, you can use `examples/clientSdioChat.ts`: ```bash -node dist/example_chat_stdio.js +node dist/examples/clientStdioChat.js ``` Here you can interact with the server using the chat interface. diff --git a/package-lock.json b/package-lock.json index 7bf85c0..10e1bc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,8 @@ "minimist": "^1.2.8" }, "devDependencies": { + "@anthropic-ai/sdk": "^0.33.1", + "@anthropic-ai/tokenizer": "^0.0.4", "@apify/eslint-config": "^0.5.0-beta.2", "@apify/tsconfig": "^0.1.0", "@types/express": "^5.0.0", @@ -32,6 +34,74 @@ "node": ">=18.0.0" } }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.33.1.tgz", + "integrity": "sha512-VrlbxiAdVRGuKP2UQlCnsShDHJKWepzvfRCkZMpU+oaUdKLpOfmylLMRojGrAgebV+kDtPjewCVP0laHXg+vsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.70", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.70.tgz", + "integrity": "sha512-RE+K0+KZoEpDUbGGctnGdkrLFwi1eYKTlIHNl2Um98mUkGsm1u2Ff6Ltd0e8DktTtC98uy7rSj+hO8t/QuLoVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@anthropic-ai/tokenizer": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@anthropic-ai/tokenizer/-/tokenizer-0.0.4.tgz", + "integrity": "sha512-EHRKbxlxlc8W4KCBEseByJ7YwyYCmgu9OyN59H9+IYIGPoKv8tXyQXinkeGDI+cI8Tiuz9wk2jZb/kK7AyvL7g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "tiktoken": "^1.0.10" + } + }, + "node_modules/@anthropic-ai/tokenizer/node_modules/@types/node": { + "version": "18.19.70", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.70.tgz", + "integrity": "sha512-RE+K0+KZoEpDUbGGctnGdkrLFwi1eYKTlIHNl2Um98mUkGsm1u2Ff6Ltd0e8DktTtC98uy7rSj+hO8t/QuLoVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@anthropic-ai/tokenizer/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, "node_modules/@apify/consts": { "version": "2.35.0", "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.35.0.tgz", @@ -1189,6 +1259,17 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/qs": { "version": "6.9.17", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", @@ -1425,6 +1506,19 @@ "npm": ">=7.0.0" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3055,6 +3149,16 @@ "through": "~2.3.1" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/eventsource": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.2.tgz", @@ -3373,6 +3477,20 @@ "node": ">= 18" } }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4678,6 +4796,47 @@ "node": ">= 0.6" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -5861,6 +6020,13 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "license": "MIT" }, + "node_modules/tiktoken": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.18.tgz", + "integrity": "sha512-DXJesdYwmBHtkmz1sji+UMZ4AOEE8F7Uw/PS/uy0XfkKOzZC4vXkYXHMYyDT+grdflvF4bggtPt9cYaqOMslBw==", + "dev": true, + "license": "MIT" + }, "node_modules/tldts": { "version": "6.1.70", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.70.tgz", @@ -5930,6 +6096,13 @@ "node": ">=16" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -6270,6 +6443,23 @@ "node": ">= 0.8" } }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", @@ -6279,6 +6469,17 @@ "node": ">=18" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 1934d7f..c107fd6 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "minimist": "^1.2.8" }, "devDependencies": { + "@anthropic-ai/sdk": "^0.33.1", + "@anthropic-ai/tokenizer": "^0.0.4", "@apify/eslint-config": "^0.5.0-beta.2", "@apify/tsconfig": "^0.1.0", "@types/express": "^5.0.0", diff --git a/src/actorDefinition.ts b/src/actorDefinition.ts index 6f9d06e..0aa516d 100644 --- a/src/actorDefinition.ts +++ b/src/actorDefinition.ts @@ -77,7 +77,8 @@ export async function getActorsAsTools(actors: string[]) { // export async function getListOfActors() { // const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); -// const results = await client.store().list({ limit: 20, offset: 0, sortBy: 'popularity' }); +// const results = await client.store().list({ limit: 20, offset: 0, sortBy: 'relevance', search: 'instagram' }); // return results.items.filter((x) => !x.categories.includes('DEVELOPER_TOOLS')).map((x) => `${x.username}/${x.name}`); // // return v.items.map((x) => `${x.username}/${x.name}`); // } +// console.log('Actors:', await getListOfActors()); diff --git a/src/examples/clientStdioChat.ts b/src/examples/clientStdioChat.ts new file mode 100644 index 0000000..42411ae --- /dev/null +++ b/src/examples/clientStdioChat.ts @@ -0,0 +1,208 @@ +/* eslint-disable no-console */ +/** + * Create a simple chat client that connects to the Model Context Protocol server using the stdio transport. + * Based on the user input, the client sends a query to the MCP server, retrieves results and processes them. + * + * You can expect the following output: + * + * MCP Client Started! + * Type your queries or 'quit|q|exit' to exit. + * You: Search information about AI agents and provide brief summary + * [INTERNAL] Received response from Claude: [{"type":"text","text":"I'll search for information about AI agents + * and provide you with a summary."},{"type":"tool_use","id":"toolu_01He9TkzQfh2979bbeuxWVqM","name":"search", + * "input":{"query":"what are AI agents definition capabilities applications","maxResults":2}}] + * [INTERNAL] Calling tool: {"name":"search","arguments":{"query":"what are AI agents definition ... + * I can help analyze the provided content about AI agents. + * This appears to be crawled content from AWS and IBM websites explaining what AI agents are. + * Let me summarize the key points: + */ + +import { execSync } from 'child_process'; +import * as readline from 'readline'; + +import { Anthropic } from '@anthropic-ai/sdk'; +import type { Message, TextBlock, ToolUseBlock } from '@anthropic-ai/sdk/resources/messages'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; +import dotenv from 'dotenv'; + +dotenv.config({ path: '../../.env' }); + +const CLAUDE_MODEL = 'claude-3-5-sonnet-20241022'; +const DEBUG = true; +const DEBUG_SERVER_PATH = '../../dist/index.js'; + +const NODE_PATH = execSync('which node').toString().trim(); + +dotenv.config(); // Load environment variables from .env + +export type Tool = { + name: string; + description: string | undefined; + input_schema: unknown; +} + +class MCPClient { + private anthropic: Anthropic; + private client = new Client( + { + name: 'example-client', + version: '0.1.0', + }, + { + capabilities: {}, // Optional capabilities + }, + ); + + private tools: Tool[] = []; + + constructor() { + this.anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); + } + + /** + * Start the server using node and provided server script path. + * Connect to the server using stdio transport and list available tools. + */ + async connectToServer(serverArgs: string[]) { + const transport = new StdioClientTransport({ + command: NODE_PATH, + args: serverArgs, + env: { APIFY_TOKEN: process.env.APIFY_TOKEN || '' }, + }); + + await this.client.connect(transport); + const response = await this.client.listTools(); + + this.tools = response.tools.map((x) => ({ + name: x.name.replace('/', '-'), + description: x.description, + input_schema: x.inputSchema, + })); + console.log('Connected to server with tools:', this.tools.map((x) => x.name)); + } + + /** + * Process LLM response and check whether it contains any tool calls. + * If a tool call is found, call the tool and return the response and save the results to messages with type: user. + * If the tools response is too large, truncate it to the limit. + */ + async processMsg(response: Message): Promise { + const finalText: string[] = []; + + for (const content of response.content) { + if (content.type === 'text') { + finalText.push(content.text); + } else if (content.type === 'tool_use') { + finalText.push(await this.handleToolCall(content)); + } + } + return finalText.join('\n'); + } + + /** + * Call the tool and return the response. + */ + private async handleToolCall(content: ToolUseBlock) { + const finalText: string[] = []; + const messages: any[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const params = { name: content.name, arguments: content.input as any }; + // put back correct tool name + params.name = params.name.replace('-', '/'); + console.log(`[INTERNAL] Calling tool: ${JSON.stringify(params)}`); + let results; + + try { + finalText.push(`[Calling tool: ${params.name} with arguments ${JSON.stringify(params.arguments)}]`); + results = await this.client.callTool(params, CallToolResultSchema); + } catch (error) { + finalText.push(`Error calling tool: ${error}`); + return finalText.join('\n'); + } + + if ('text' in content && content.text) { + messages.push({ role: 'assistant', content: content.text }); + } + + messages.push({ role: 'user', content: JSON.stringify(results.content) }); + // Get next response from Claude + const nextResponse: Message = await this.anthropic.messages.create({ + model: CLAUDE_MODEL, + max_tokens: 1000, + messages, + tools: this.tools as any[], // eslint-disable-line @typescript-eslint/no-explicit-any + }); + const textBlock = nextResponse.content[0] as TextBlock; + finalText.push(textBlock.text); + return finalText.join('\n'); + } + + /** + * Process user query by sending it to the server and returning the response. + * Also, process any tool calls. + */ + async processQuery(query: string): Promise { + const msg: Message = await this.anthropic.messages.create({ + model: CLAUDE_MODEL, + max_tokens: 1024, + messages: [{ role: 'user', content: query }], + tools: this.tools as any[], // eslint-disable-line @typescript-eslint/no-explicit-any + }); + console.log('[INTERNAL] Received response from Claude:', JSON.stringify(msg.content)); + + return await this.processMsg(msg); + } + + /** + * Create a chat loop that reads user input from the console and sends it to the server for processing. + */ + async chatLoop() { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + prompt: 'You: ', + }); + + console.log("MCP Client Started!\nType your queries or 'quit|q|exit' to exit."); + rl.prompt(); + + rl.on('line', async (input) => { + const v = input.trim().toLowerCase(); + if (v === 'quit' || v === 'q' || v === 'exit') { + rl.close(); + return; + } + try { + const response = await this.processQuery(input); + console.log('Claude:', response); + } catch (error) { + console.error('Error processing query:', error); + } + rl.prompt(); + }); + } +} + +async function main() { + const client = new MCPClient(); + + if (process.argv.length < 3) { + if (DEBUG) { + process.argv.push(DEBUG_SERVER_PATH); + } else { + console.error('Usage: node '); + process.exit(1); + } + } + + try { + await client.connectToServer(process.argv.slice(2)); + await client.chatLoop(); + } catch (error) { + console.error('Error:', error); + } +} + +main().catch(console.error); diff --git a/src/index.ts b/src/index.ts index 0e04373..8913ff9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ const argActors = argv.actors?.split(',').map((actor: string) => actor.trim()) | async function main() { const server = new ApifyMcpServer(); - await (argActors + await (argActors.length !== 0 ? server.addToolsFromActors(argActors) : server.addToolsFromDefaultActors()); const transport = new StdioServerTransport(); From 8a9cad3ed6b72a75d6c61d08935717b117f8be63 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Thu, 9 Jan 2025 09:59:12 +0100 Subject: [PATCH 18/54] Improve error handling --- src/actorDefinition.ts | 35 +++++++++++++-------------- src/const.ts | 1 + src/examples/clientStdio.ts | 19 ++++++++++++++- src/examples/clientStdioChat.ts | 9 +++---- src/main.ts | 42 ++++++++++++++++++++++----------- src/server.ts | 38 ++++++++++++++--------------- 6 files changed, 87 insertions(+), 57 deletions(-) diff --git a/src/actorDefinition.ts b/src/actorDefinition.ts index 0aa516d..2cc2cd3 100644 --- a/src/actorDefinition.ts +++ b/src/actorDefinition.ts @@ -15,6 +15,10 @@ interface ActorDefinitionWithDesc extends ActorDefinition { * @param actorFullName */ async function fetchActorDefinition(actorFullName: string): Promise { + if (!process.env.APIFY_TOKEN) { + log.error('APIFY_TOKEN is required but not set. Please set it as an environment variable'); + return null; + } const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const actorClient = client.actor(actorFullName); @@ -38,10 +42,10 @@ async function fetchActorDefinition(actorFullName: string): Promise log.error('Global Error:', error)); - -// export async function getListOfActors() { -// const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); -// const results = await client.store().list({ limit: 20, offset: 0, sortBy: 'relevance', search: 'instagram' }); -// return results.items.filter((x) => !x.categories.includes('DEVELOPER_TOOLS')).map((x) => `${x.username}/${x.name}`); -// // return v.items.map((x) => `${x.username}/${x.name}`); -// } -// console.log('Actors:', await getListOfActors()); diff --git a/src/const.ts b/src/const.ts index 8fe8633..14d4868 100644 --- a/src/const.ts +++ b/src/const.ts @@ -12,6 +12,7 @@ export const defaults = { }; export enum Routes { + ROOT = '/', SSE = '/sse', MESSAGE = '/message', } diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts index 886dc5d..223166a 100644 --- a/src/examples/clientStdio.ts +++ b/src/examples/clientStdio.ts @@ -18,6 +18,12 @@ dotenv.config({ path: '../../.env' }); const SERVER_PATH = '../../dist/index.js'; const NODE_PATH = execSync('which node').toString().trim(); const TOOLS = 'apify/rag-web-browser,lukaskrivka/google-maps-with-contact-details'; +const SELECTED_TOOL = 'apify/rag-web-browser'; + +if (!process.env.APIFY_TOKEN) { + console.error('APIFY_TOKEN is required but not set in the environment variables.'); + process.exit(1); +} // Create server parameters for stdio connection const transport = new StdioClientTransport({ @@ -47,6 +53,14 @@ async function run() { return; } + // Example: Call the first available tool + const selectedTool = tools.tools.find((tool) => tool.name === SELECTED_TOOL); + + if (!selectedTool) { + console.error(`The specified tool: ${selectedTool} is not available. Exiting.`); + return; + } + // Call a tool console.log('Calling actor ...'); const result = await client.callTool( @@ -59,4 +73,7 @@ async function run() { } } -await run(); +run().catch((error) => { + console.error('Unhandled error:', error); + process.exit(1); +}); diff --git a/src/examples/clientStdioChat.ts b/src/examples/clientStdioChat.ts index 42411ae..3c807b8 100644 --- a/src/examples/clientStdioChat.ts +++ b/src/examples/clientStdioChat.ts @@ -7,7 +7,7 @@ * * MCP Client Started! * Type your queries or 'quit|q|exit' to exit. - * You: Search information about AI agents and provide brief summary + * You: Find to articles about AI agent and return URLs * [INTERNAL] Received response from Claude: [{"type":"text","text":"I'll search for information about AI agents * and provide you with a summary."},{"type":"tool_use","id":"toolu_01He9TkzQfh2979bbeuxWVqM","name":"search", * "input":{"query":"what are AI agents definition capabilities applications","maxResults":2}}] @@ -29,6 +29,8 @@ import dotenv from 'dotenv'; dotenv.config({ path: '../../.env' }); +const REQUEST_TIMEOUT = 120_000; // 2 minutes + const CLAUDE_MODEL = 'claude-3-5-sonnet-20241022'; const DEBUG = true; const DEBUG_SERVER_PATH = '../../dist/index.js'; @@ -76,7 +78,7 @@ class MCPClient { const response = await this.client.listTools(); this.tools = response.tools.map((x) => ({ - name: x.name.replace('/', '-'), + name: x.name, description: x.description, input_schema: x.inputSchema, })); @@ -110,13 +112,12 @@ class MCPClient { // eslint-disable-next-line @typescript-eslint/no-explicit-any const params = { name: content.name, arguments: content.input as any }; // put back correct tool name - params.name = params.name.replace('-', '/'); console.log(`[INTERNAL] Calling tool: ${JSON.stringify(params)}`); let results; try { finalText.push(`[Calling tool: ${params.name} with arguments ${JSON.stringify(params.arguments)}]`); - results = await this.client.callTool(params, CallToolResultSchema); + results = await this.client.callTool(params, CallToolResultSchema, { timeout: REQUEST_TIMEOUT }); } catch (error) { finalText.push(`Error calling tool: ${error}`); return finalText.join('\n'); diff --git a/src/main.ts b/src/main.ts index 0bdc852..a10c9e6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,7 +16,7 @@ await Actor.init(); const STANDBY_MODE = Actor.getEnv().metaOrigin === 'STANDBY'; const HOST = Actor.isAtHome() ? process.env.ACTOR_STANDBY_URL : 'http://localhost'; -const POST = Actor.isAtHome() ? process.env.ACTOR_STANDBY_PORT : 3001; +const PORT = Actor.isAtHome() ? process.env.ACTOR_STANDBY_PORT : 3001; const app = express(); @@ -28,7 +28,6 @@ const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse?toke /** * Process input parameters and update tools - * @param url */ async function processParamsAndUpdateTools(url: string) { const params = parse(url.split('?')[1] || '') as ParsedUrlQuery; @@ -38,25 +37,40 @@ async function processParamsAndUpdateTools(url: string) { await (input.actors ? mcpServer.addToolsFromActors(input.actors as string[]) : mcpServer.addToolsFromDefaultActors()); } -app.route('/') - .get(async (req: Request, res: Response) => { +app.get(Routes.ROOT, async (req: Request, res: Response) => { + try { log.info(`Received GET message at: ${req.url}`); await processParamsAndUpdateTools(req.url); res.status(200).json({ message: `Actor is using Model Context Protocol. ${HELP_MESSAGE}` }).end(); - }) - .head(async (_req: Request, res: Response) => { - res.status(200).end(); - }); + } catch (error) { + log.error(`Error in GET ${Routes.ROOT} ${error}`); + res.status(500).json({ message: 'Internal Server Error' }).end(); + } +}); + +app.head(Routes.ROOT, (_req: Request, res: Response) => { + res.status(200).end(); +}); app.get(Routes.SSE, async (req: Request, res: Response) => { - log.info(`Received GET message at: ${req.url}`); - transport = new SSEServerTransport(Routes.MESSAGE, res); - await mcpServer.connect(transport); + try { + log.info(`Received GET message at: ${req.url}`); + transport = new SSEServerTransport(Routes.MESSAGE, res); + await mcpServer.connect(transport); + } catch (error) { + log.error(`Error in GET ${Routes.SSE}: ${error}`); + res.status(500).json({ message: 'Internal Server Error' }).end(); + } }); app.post(Routes.MESSAGE, async (req: Request, res: Response) => { - log.info(`Received POST message at: ${req.url}`); - await transport.handlePostMessage(req, res); + try { + log.info(`Received POST message at: ${req.url}`); + await transport.handlePostMessage(req, res); + } catch (error) { + log.error(`Error in POST ${Routes.MESSAGE}: ${error}`); + res.status(500).json({ message: 'Internal Server Error' }).end(); + } }); // Catch-all for undefined routes @@ -67,7 +81,7 @@ app.use((req: Request, res: Response) => { if (STANDBY_MODE) { log.info('Actor is running in the STANDBY mode.'); - app.listen(POST, () => { + app.listen(PORT, () => { log.info(`The Actor web server is listening for user requests at ${HOST}.`); }); } else { diff --git a/src/server.ts b/src/server.ts index 058c9db..9799821 100644 --- a/src/server.ts +++ b/src/server.ts @@ -41,21 +41,23 @@ export class ApifyMcpServer { if (!process.env.APIFY_TOKEN) { throw new Error('APIFY_TOKEN is required but not set. Please set it as an environment variable'); } + const name = actorName.replace('_', '/'); try { - log.info(`Calling actor ${actorName} with input: ${JSON.stringify(input)}`); + log.info(`Calling actor ${name} with input: ${JSON.stringify(input)}`); const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); - const actorClient = client.actor(actorName); + const actorClient = client.actor(name); const results = await actorClient.call(input); const dataset = await client.dataset(results.defaultDatasetId).listItems(); - log.info(`Actor ${actorName} finished with ${dataset.items.length} items`); + log.info(`Actor ${name} finished with ${dataset.items.length} items`); + if (process.env.APIFY_IS_AT_HOME) { await Actor.pushData(dataset.items); log.info(`Pushed ${dataset.items.length} items to the dataset`); } return dataset.items; } catch (error) { - log.error(`Error calling actor: ${error}. Actor: ${actorName}, input: ${JSON.stringify(input)}`); + log.error(`Error calling actor: ${error}. Actor: ${name}, input: ${JSON.stringify(input)}`); throw new Error(`Error calling actor: ${error}`); } } @@ -94,30 +96,26 @@ export class ApifyMcpServer { }); } - private validateArguments(name: string, args: unknown): unknown { - const tool = this.tools.find((x) => x.name === name); - if (!tool?.ajvValidate(args)) { - throw new Error(`Invalid arguments for tool ${name}: args: ${JSON.stringify(args)} error: ${JSON.stringify(tool?.ajvValidate.errors)}`); - } - return args; - } - private setupToolHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => { - return { - tools: this.tools, - }; + return { tools: this.tools }; }); + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; - const availableTools = this.tools.map((tool) => tool.name); - if (!availableTools.includes(name)) { + + const tool = this.tools.find((t) => t.name === name); + if (!tool) { throw new Error(`Unknown tool: ${name}`); } + + log.info(`Validate arguments for tool: ${name} with arguments: ${JSON.stringify(args)}`); + if (!tool.ajvValidate(args)) { + throw new Error(`Invalid arguments for tool ${name}: args: ${JSON.stringify(args)} error: ${JSON.stringify(tool?.ajvValidate.errors)}`); + } + try { - log.info(`Validating arguments for tool: ${name} with arguments: ${JSON.stringify(args)}`); - const validatedArgs = this.validateArguments(name, args); - const items = await this.callActorGetDataset(name, validatedArgs); + const items = await this.callActorGetDataset(name, args); return { content: items.map((item) => ({ type: 'text', text: JSON.stringify(item) })) }; } catch (error) { log.error(`Error calling tool: ${error}`); From 405f70c7395fd9e9a67d7fdd67f841c8b6315b36 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Thu, 9 Jan 2025 16:10:06 +0100 Subject: [PATCH 19/54] Update clientStdioChat.ts --- src/examples/clientStdioChat.ts | 109 +++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 38 deletions(-) diff --git a/src/examples/clientStdioChat.ts b/src/examples/clientStdioChat.ts index 3c807b8..7577722 100644 --- a/src/examples/clientStdioChat.ts +++ b/src/examples/clientStdioChat.ts @@ -8,10 +8,10 @@ * MCP Client Started! * Type your queries or 'quit|q|exit' to exit. * You: Find to articles about AI agent and return URLs - * [INTERNAL] Received response from Claude: [{"type":"text","text":"I'll search for information about AI agents + * [internal] Received response from Claude: [{"type":"text","text":"I'll search for information about AI agents * and provide you with a summary."},{"type":"tool_use","id":"toolu_01He9TkzQfh2979bbeuxWVqM","name":"search", * "input":{"query":"what are AI agents definition capabilities applications","maxResults":2}}] - * [INTERNAL] Calling tool: {"name":"search","arguments":{"query":"what are AI agents definition ... + * [internal] Calling tool: {"name":"search","arguments":{"query":"what are AI agents definition ... * I can help analyze the provided content about AI agents. * This appears to be crawled content from AWS and IBM websites explaining what AI agents are. * Let me summarize the key points: @@ -21,7 +21,7 @@ import { execSync } from 'child_process'; import * as readline from 'readline'; import { Anthropic } from '@anthropic-ai/sdk'; -import type { Message, TextBlock, ToolUseBlock } from '@anthropic-ai/sdk/resources/messages'; +import type { Message, ToolUseBlock, MessageParam } from '@anthropic-ai/sdk/resources/messages'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; @@ -30,8 +30,11 @@ import dotenv from 'dotenv'; dotenv.config({ path: '../../.env' }); const REQUEST_TIMEOUT = 120_000; // 2 minutes +const MAX_TOKENS = 2048; // Maximum tokens for Claude response -const CLAUDE_MODEL = 'claude-3-5-sonnet-20241022'; +// const CLAUDE_MODEL = 'claude-3-5-sonnet-20241022'; // the most intelligent model +// const CLAUDE_MODEL = 'claude-3-5-haiku-20241022'; // a fastest model +const CLAUDE_MODEL = 'claude-3-haiku-20240307'; // a fastest and most compact model for near-instant responsiveness const DEBUG = true; const DEBUG_SERVER_PATH = '../../dist/index.js'; @@ -90,70 +93,89 @@ class MCPClient { * If a tool call is found, call the tool and return the response and save the results to messages with type: user. * If the tools response is too large, truncate it to the limit. */ - async processMsg(response: Message): Promise { - const finalText: string[] = []; - + async processMsg(response: Message, messages: MessageParam[]): Promise { for (const content of response.content) { if (content.type === 'text') { - finalText.push(content.text); + messages.push({ role: 'assistant', content: content.text }); } else if (content.type === 'tool_use') { - finalText.push(await this.handleToolCall(content)); + await this.handleToolCall(content, messages); } } - return finalText.join('\n'); + return messages; } /** * Call the tool and return the response. */ - private async handleToolCall(content: ToolUseBlock) { - const finalText: string[] = []; - const messages: any[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any + private async handleToolCall(content: ToolUseBlock, messages: MessageParam[], toolCallCount = 0): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any const params = { name: content.name, arguments: content.input as any }; - // put back correct tool name - console.log(`[INTERNAL] Calling tool: ${JSON.stringify(params)}`); + console.log(`[internal] Calling tool (count: ${toolCallCount}): ${JSON.stringify(params)}`); let results; - try { - finalText.push(`[Calling tool: ${params.name} with arguments ${JSON.stringify(params.arguments)}]`); results = await this.client.callTool(params, CallToolResultSchema, { timeout: REQUEST_TIMEOUT }); + if (results.content instanceof Array && results.content.length !== 0) { + const text = results.content.map((x) => x.text); + messages.push({ role: 'user', content: text.join('\n\n') }); + } else { + messages.push({ role: 'user', content: `No results retrieved from ${params.name}` }); + } } catch (error) { - finalText.push(`Error calling tool: ${error}`); - return finalText.join('\n'); + messages.push({ role: 'user', content: `Error calling tool: ${params.name}, error: ${error}` }); } - - if ('text' in content && content.text) { - messages.push({ role: 'assistant', content: content.text }); - } - - messages.push({ role: 'user', content: JSON.stringify(results.content) }); // Get next response from Claude const nextResponse: Message = await this.anthropic.messages.create({ model: CLAUDE_MODEL, - max_tokens: 1000, + max_tokens: MAX_TOKENS, messages, tools: this.tools as any[], // eslint-disable-line @typescript-eslint/no-explicit-any }); - const textBlock = nextResponse.content[0] as TextBlock; - finalText.push(textBlock.text); - return finalText.join('\n'); + + for (const c of nextResponse.content) { + if (c.type === 'text') { + messages.push({ role: 'assistant', content: c.text }); + } else if (c.type === 'tool_use' && toolCallCount < 3) { + return await this.handleToolCall(c, messages, toolCallCount + 1); + } + } + + return messages; + + // const lastContent = nextResponse.content[nextResponse.content.length - 1]; + // if (lastContent.type !== 'tool_use' && toolCallCount < 3) { + // return this.handleToolCall(lastContent, messages, toolCallCount + 1); + // } + // + // const lastContent = nextResponse.content[nextResponse.content.length - 1]; + // if (lastContent.type !== 'tool_use' && toolCallCount < 3) { + // return this.handleToolCall(lastContent, messages, toolCallCount + 1); + // } + // + // if (nextResponse.content typeof ToolUseBlock) { + // if (nextResponse.content.slice(-1).type !== 'tool_use' && toolCallCount < 3) { + // return this.handleToolCall(nextResponse.content.slice(-1), messages, toolCallCount + 1); + // } + // } + // + // messages.push({ role: nextResponse.role, content: nextResponse.content.map((x: TextBlockParam) => x.text || '').join('\n') }); + // + // return messages; } /** * Process user query by sending it to the server and returning the response. * Also, process any tool calls. */ - async processQuery(query: string): Promise { - const msg: Message = await this.anthropic.messages.create({ + async processQuery(query: string, messages: MessageParam[]): Promise { + messages.push({ role: 'user', content: query }); + const response: Message = await this.anthropic.messages.create({ model: CLAUDE_MODEL, - max_tokens: 1024, - messages: [{ role: 'user', content: query }], + max_tokens: MAX_TOKENS, + messages, tools: this.tools as any[], // eslint-disable-line @typescript-eslint/no-explicit-any }); - console.log('[INTERNAL] Received response from Claude:', JSON.stringify(msg.content)); - - return await this.processMsg(msg); + console.log('[internal] Received response from Claude:', JSON.stringify(response.content)); + return await this.processMsg(response, messages); } /** @@ -169,6 +191,8 @@ class MCPClient { console.log("MCP Client Started!\nType your queries or 'quit|q|exit' to exit."); rl.prompt(); + let lastPrintMessage = 0; + const messages: MessageParam[] = []; rl.on('line', async (input) => { const v = input.trim().toLowerCase(); if (v === 'quit' || v === 'q' || v === 'exit') { @@ -176,8 +200,17 @@ class MCPClient { return; } try { - const response = await this.processQuery(input); - console.log('Claude:', response); + await this.processQuery(input, messages); + for (let i = lastPrintMessage + 1; i < messages.length; i++) { + if (messages[i].role === 'assistant') { + console.log('CLAUDE:', messages[i].content); + } else if (messages[i].role === 'user') { + console.log('USER:', messages[i].content.slice(0, 500), '...'); + } else { + console.log('CLAUDE[thinking]:', messages[i].content); + } + } + lastPrintMessage += messages.length; } catch (error) { console.error('Error processing query:', error); } From af4d69662b357510e09b6737a04f7b1d234098d7 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Thu, 9 Jan 2025 16:12:08 +0100 Subject: [PATCH 20/54] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 50f5162..8828ace 100644 --- a/README.md +++ b/README.md @@ -187,8 +187,8 @@ Configure Claude Desktop to recognize the MCP server. You can ask Claude to perform web searches, such as: ```text Find and analyze recent research papers about LLMs. - Find best restaurants in San Francisco. - Find and analyze comments about the latest iPhone. + Find top 10 best Italian restaurants in San Francisco. + Find and analyze instagram profile of the Rock. ``` ## 👷🏼 Development @@ -232,6 +232,8 @@ Currently, the node.js client does not support to establish a connection to remo node dist/clientSse.js ``` +The script will start the MCP server with default Actors. + ### Debugging Since MCP servers operate over standard input/output (stdio), debugging can be challenging. From 1c9299057973bc837d20d44e7f8e840431479e8d Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 10 Jan 2025 13:23:29 +0100 Subject: [PATCH 21/54] Change env variable --- README.md | 25 ++++++++++++++----------- src/actorDefinition.ts | 6 +++--- src/examples/clientStdio.ts | 8 ++++---- src/examples/clientStdioChat.ts | 2 +- src/examples/client_sse.py | 2 +- src/server.ts | 6 +++--- 6 files changed, 26 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 8828ace..69d3998 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,29 @@ ## Apify Model Context Protocol (MCP) Server -Implementation of an MCP server for [Apify Actors](https://apify.com/store). +Implementation of an MCP server for all [Apify Actors](https://apify.com/store). This server enables interaction with one or more Apify Actors that can be defined in the MCP server configuration. The server can be used in two ways: - **MCP Server Actor** - Actor runs an HTTP server that supports the MCP protocol via SSE (Server-Sent Events). - **MCP Server CLI** - Command-line interface that supports the MCP protocol via stdio. -## 🔄 What is model context protocol? - -The Model Context Protocol (MCP) allows AI applications (and AI agents), such as Claude Desktop, to connect to external tools and data sources. -MCP is an open protocol that enables secure, controlled interactions between AI applications, AI Agents, and local or remote resources. - ## 🎯 What does this MCP server do? The MCP Server Actor allows an AI assistant to: - Use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task.For example it can: - - [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from hundreds of Facebook posts from one or multiple Facebook pages and profiles - - [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) Extract Google Maps contact details - - [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) Scrape Google Search Engine Results Pages (SERPs) - - [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrape and download Instagram posts, profiles, places, hashtags, photos, and comments - - [RAG Web Browser](https://apify.com/apify/web-scraper) perform web search, scrape the top N URLs from the results, and return their cleaned content as Markdown + - [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from hundreds of Facebook posts from one or multiple Facebook pages and profiles + - [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) Extract Google Maps contact details + - [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) Scrape Google Search Engine Results Pages (SERPs) + - [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrape and download Instagram posts, profiles, places, hashtags, photos, and comments + - [RAG Web Browser](https://apify.com/apify/web-scraper) perform web search, scrape the top N URLs from the results, and return their cleaned content as Markdown + +Once the server is started, you can use MCP clients, such as [Claude Desktop]( + + +## 🔄 What is model context protocol? + +The Model Context Protocol (MCP) allows AI applications (and AI agents), such as Claude Desktop, to connect to external tools and data sources. +MCP is an open protocol that enables secure, controlled interactions between AI applications, AI Agents, and local or remote resources. ## 🧱 Components diff --git a/src/actorDefinition.ts b/src/actorDefinition.ts index 2cc2cd3..42c12b5 100644 --- a/src/actorDefinition.ts +++ b/src/actorDefinition.ts @@ -15,11 +15,11 @@ interface ActorDefinitionWithDesc extends ActorDefinition { * @param actorFullName */ async function fetchActorDefinition(actorFullName: string): Promise { - if (!process.env.APIFY_TOKEN) { - log.error('APIFY_TOKEN is required but not set. Please set it as an environment variable'); + if (!process.env.APIFY_API_TOKEN) { + log.error('APIFY_API_TOKEN is required but not set. Please set it as an environment variable'); return null; } - const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); + const client = new ApifyClient({ token: process.env.APIFY_API_TOKEN }); const actorClient = client.actor(actorFullName); try { diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts index 223166a..685ec2f 100644 --- a/src/examples/clientStdio.ts +++ b/src/examples/clientStdio.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ /** * Connect to the MCP server using stdio transport and call a tool. - * You need provide a path to MCP server and APIFY_TOKEN in .env file. + * You need provide a path to MCP server and APIFY_API_TOKEN in .env file. * * Also, you need to choose ACTORS to run in the server, for example: apify/rag-web-browser */ @@ -20,8 +20,8 @@ const NODE_PATH = execSync('which node').toString().trim(); const TOOLS = 'apify/rag-web-browser,lukaskrivka/google-maps-with-contact-details'; const SELECTED_TOOL = 'apify/rag-web-browser'; -if (!process.env.APIFY_TOKEN) { - console.error('APIFY_TOKEN is required but not set in the environment variables.'); +if (!process.env.APIFY_API_TOKEN) { + console.error('APIFY_API_TOKEN is required but not set in the environment variables.'); process.exit(1); } @@ -29,7 +29,7 @@ if (!process.env.APIFY_TOKEN) { const transport = new StdioClientTransport({ command: NODE_PATH, args: [SERVER_PATH, '--actors', TOOLS], - env: { APIFY_TOKEN: process.env.APIFY_TOKEN || '' }, + env: { APIFY_API_TOKEN: process.env.APIFY_API_TOKEN || '' }, }); // Create a new client instance diff --git a/src/examples/clientStdioChat.ts b/src/examples/clientStdioChat.ts index 7577722..ddf2a0e 100644 --- a/src/examples/clientStdioChat.ts +++ b/src/examples/clientStdioChat.ts @@ -74,7 +74,7 @@ class MCPClient { const transport = new StdioClientTransport({ command: NODE_PATH, args: serverArgs, - env: { APIFY_TOKEN: process.env.APIFY_TOKEN || '' }, + env: { APIFY_API_TOKEN: process.env.APIFY_API_TOKEN || '' }, }); await this.client.connect(transport); diff --git a/src/examples/client_sse.py b/src/examples/client_sse.py index f776c4d..f3b6da5 100644 --- a/src/examples/client_sse.py +++ b/src/examples/client_sse.py @@ -21,7 +21,7 @@ MCP_SERVER_URL = "https://mcp-server.apify.actor" ACTORS = "apify/rag-web-browser" -HEADERS = {"Authorization": f"Bearer {os.getenv('APIFY_TOKEN')}"} +HEADERS = {"Authorization": f"Bearer {os.getenv('APIFY_API_TOKEN')}"} async def run() -> None: diff --git a/src/server.ts b/src/server.ts index 9799821..5ed6eaf 100644 --- a/src/server.ts +++ b/src/server.ts @@ -38,13 +38,13 @@ export class ApifyMcpServer { } public async callActorGetDataset(actorName: string, input: unknown): Promise { - if (!process.env.APIFY_TOKEN) { - throw new Error('APIFY_TOKEN is required but not set. Please set it as an environment variable'); + if (!process.env.APIFY_API_TOKEN) { + throw new Error('APIFY_API_TOKEN is required but not set. Please set it as an environment variable'); } const name = actorName.replace('_', '/'); try { log.info(`Calling actor ${name} with input: ${JSON.stringify(input)}`); - const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); + const client = new ApifyClient({ token: process.env.APIFY_API_TOKEN }); const actorClient = client.actor(name); const results = await actorClient.call(input); From 19bb130abb390b58e2f7cd202917fe2c56c6771e Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 10 Jan 2025 13:46:52 +0100 Subject: [PATCH 22/54] Clean up ts-config --- tsconfig.eslint.json | 2 +- tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index a9d3394..398b6a5 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "include": ["./src/**/*", "./test/**/*", "./scripts/**/*", "./types/**/*"] + "include": ["./src/**/*"] } diff --git a/tsconfig.json b/tsconfig.json index 833ac36..fb02aa7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,6 @@ "typeRoots": ["./types", "./node_modules/@types"], "strict": true }, - "include": ["./src/**/*", "./types/**/*"], + "include": ["./src/**/*"], "exclude": ["node_modules"] } From 22ac5edaa2056e559c4798e0059fe6d03fb746a6 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 10 Jan 2025 14:52:53 +0100 Subject: [PATCH 23/54] Update package.json and add github workflow from apify-eslint-config --- .github/scripts/before-beta-release.js | 32 +++++++ .github/workflows/pre_release.yaml | 103 +++++++++++++++++++++ .github/workflows/release.yaml | 119 +++++++++++++++++++++++++ README.md | 12 +-- package-lock.json | 8 +- package.json | 27 +++++- 6 files changed, 287 insertions(+), 14 deletions(-) create mode 100644 .github/scripts/before-beta-release.js create mode 100644 .github/workflows/pre_release.yaml create mode 100644 .github/workflows/release.yaml diff --git a/.github/scripts/before-beta-release.js b/.github/scripts/before-beta-release.js new file mode 100644 index 0000000..8f89856 --- /dev/null +++ b/.github/scripts/before-beta-release.js @@ -0,0 +1,32 @@ +const path = require('path'); +const fs = require('fs'); +const { execSync } = require('child_process'); + +const PKG_JSON_PATH = path.join(__dirname, '..', '..', 'package.json'); + +const pkgJson = require(PKG_JSON_PATH); + +const PACKAGE_NAME = pkgJson.name; +const VERSION = pkgJson.version; + +const nextVersion = getNextVersion(VERSION); +console.log(`before-deploy: Setting version to ${nextVersion}`); +pkgJson.version = nextVersion; + +fs.writeFileSync(PKG_JSON_PATH, JSON.stringify(pkgJson, null, 2) + '\n'); + +function getNextVersion(version) { + const versionString = execSync(`npm show ${PACKAGE_NAME} versions --json`, { encoding: 'utf8'}); + const versions = JSON.parse(versionString); + + if (versions.some(v => v === VERSION)) { + console.error(`before-deploy: A release with version ${VERSION} already exists. Please increment version accordingly.`); + process.exit(1); + } + + const prereleaseNumbers = versions + .filter(v => (v.startsWith(VERSION) && v.includes('-'))) + .map(v => Number(v.match(/\.(\d+)$/)[1])); + const lastPrereleaseNumber = Math.max(-1, ...prereleaseNumbers); + return `${version}-beta.${lastPrereleaseNumber + 1}` +} diff --git a/.github/workflows/pre_release.yaml b/.github/workflows/pre_release.yaml new file mode 100644 index 0000000..5ea800e --- /dev/null +++ b/.github/workflows/pre_release.yaml @@ -0,0 +1,103 @@ +name: Create a pre-release + +on: + # Push to master will deploy a beta version + push: + branches: + - master + tags-ignore: + - "**" # Ignore all tags to prevent duplicate builds when tags are pushed. + +concurrency: + group: release + cancel-in-progress: false + +jobs: + release_metadata: + if: "!startsWith(github.event.head_commit.message, 'docs') && !startsWith(github.event.head_commit.message, 'ci') && startsWith(github.repository, 'apify/')" + name: Prepare release metadata + runs-on: ubuntu-latest + outputs: + version_number: ${{ steps.release_metadata.outputs.version_number }} + changelog: ${{ steps.release_metadata.outputs.changelog }} + steps: + - uses: apify/workflows/git-cliff-release@main + name: Prepare release metadata + id: release_metadata + with: + release_type: prerelease + existing_changelog_path: CHANGELOG.md + + wait_for_checks: + name: Wait for code checks to pass + runs-on: ubuntu-latest + steps: + - uses: lewagon/wait-on-check-action@v1.3.4 + with: + ref: ${{ github.ref }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + check-name: 'Lint' + wait-interval: 5 + + update_changelog: + needs: [ release_metadata ] + name: Update changelog + runs-on: ubuntu-latest + outputs: + changelog_commitish: ${{ steps.commit.outputs.commit_long_sha || github.sha }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} + + - name: Use Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Update package version in package.json + run: npm version --no-git-tag-version --allow-same-version ${{ needs.release_metadata.outputs.version_number }} + + - name: Update CHANGELOG.md + uses: DamianReeves/write-file-action@master + with: + path: CHANGELOG.md + write-mode: overwrite + contents: ${{ needs.release_metadata.outputs.changelog }} + + - name: Commit changes + id: commit + uses: EndBug/add-and-commit@v9 + with: + author_name: Apify Release Bot + author_email: noreply@apify.com + message: "chore(release): Update changelog and package version [skip ci]" + + publish_to_npm: + name: Publish to NPM + needs: [ release_metadata ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.update_changelog.changelog_commitish }} + - name: Use Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Install dependencies + run: | + echo "access=public" >> .npmrc + echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc + npm install + - # Check version consistency and increment pre-release version number for beta only. + name: Bump pre-release version + run: node ./.github/scripts/before-beta-release.js + - name: Publish to NPM + run: npm publish --tag beta + +env: + NODE_AUTH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }} + NPM_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..43ef27e --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,119 @@ +name: Create a release + +on: + # Trigger a stable version release via GitHub's UI, with the ability to specify the type of release. + workflow_dispatch: + inputs: + release_type: + description: Release type + required: true + type: choice + default: auto + options: + - auto + - custom + - patch + - minor + - major + custom_version: + description: The custom version to bump to (only for "custom" type) + required: false + type: string + default: "" + +concurrency: + group: release + cancel-in-progress: false + +jobs: + release_metadata: + name: Prepare release metadata + runs-on: ubuntu-latest + outputs: + version_number: ${{ steps.release_metadata.outputs.version_number }} + tag_name: ${{ steps.release_metadata.outputs.tag_name }} + changelog: ${{ steps.release_metadata.outputs.changelog }} + release_notes: ${{ steps.release_metadata.outputs.release_notes }} + steps: + - uses: apify/workflows/git-cliff-release@main + name: Prepare release metadata + id: release_metadata + with: + release_type: ${{ inputs.release_type }} + custom_version: ${{ inputs.custom_version }} + existing_changelog_path: CHANGELOG.md + + update_changelog: + needs: [ release_metadata ] + name: Update changelog + runs-on: ubuntu-latest + outputs: + changelog_commitish: ${{ steps.commit.outputs.commit_long_sha || github.sha }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} + + - name: Use Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Update package version in package.json + run: npm version --no-git-tag-version --allow-same-version ${{ needs.release_metadata.outputs.version_number }} + + - name: Update CHANGELOG.md + uses: DamianReeves/write-file-action@master + with: + path: CHANGELOG.md + write-mode: overwrite + contents: ${{ needs.release_metadata.outputs.changelog }} + + - name: Commit changes + id: commit + uses: EndBug/add-and-commit@v9 + with: + author_name: Apify Release Bot + author_email: noreply@apify.com + message: "chore(release): Update changelog and package version [skip ci]" + + create_github_release: + name: Create github release + needs: [release_metadata, update_changelog] + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Create release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.release_metadata.outputs.tag_name }} + name: ${{ needs.release_metadata.outputs.version_number }} + target_commitish: ${{ needs.update_changelog.outputs.changelog_commitish }} + body: ${{ needs.release_metadata.outputs.release_notes }} + + publish_to_npm: + name: Publish to NPM + needs: [ update_changelog ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.update_changelog.changelog_commitish }} + - name: Use Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Install dependencies + run: | + echo "access=public" >> .npmrc + echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc + npm install + - name: Publish to NPM + run: npm publish --tag latest + +env: + NODE_AUTH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }} + NPM_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }} diff --git a/README.md b/README.md index 69d3998..7018047 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,20 @@ Implementation of an MCP server for all [Apify Actors](https://apify.com/store). This server enables interaction with one or more Apify Actors that can be defined in the MCP server configuration. The server can be used in two ways: -- **MCP Server Actor** - Actor runs an HTTP server that supports the MCP protocol via SSE (Server-Sent Events). -- **MCP Server CLI** - Command-line interface that supports the MCP protocol via stdio. +- **MCP Server Actor**, which runs an HTTP server supporting the MCP protocol via Server-Sent Events (SSE). +- **MCP Server CLI** supports the MCP protocol via stdio. ## 🎯 What does this MCP server do? The MCP Server Actor allows an AI assistant to: - Use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task.For example it can: - - [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from hundreds of Facebook posts from one or multiple Facebook pages and profiles + - [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from Facebook posts from one or multiple Facebook pages/profiles - [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) Extract Google Maps contact details - [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) Scrape Google Search Engine Results Pages (SERPs) - - [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrape and download Instagram posts, profiles, places, hashtags, photos, and comments - - [RAG Web Browser](https://apify.com/apify/web-scraper) perform web search, scrape the top N URLs from the results, and return their cleaned content as Markdown + - [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrape Instagram posts, profiles, places, hashtags, photos, and comments + - [RAG Web Browser](https://apify.com/apify/web-scraper) perform web search, scrape the top N URLs from the results, and return content -Once the server is started, you can use MCP clients, such as [Claude Desktop]( +Once the server is started, you can use MCP clients, such as [Claude Desktop](https://claude.ai/download) or ## 🔄 What is model context protocol? diff --git a/package-lock.json b/package-lock.json index 10e1bc4..b0f7b6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "apify-mcp-server", - "version": "0.0.1", + "name": "@apify/mcp-server", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "apify-mcp-server", - "version": "0.0.1", + "name": "@apify/mcp-server", + "version": "0.1.0", "license": "ISC", "dependencies": { "@modelcontextprotocol/sdk": "^1.1.0", diff --git a/package.json b/package.json index c107fd6..2dd3534 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,30 @@ { - "name": "apify-mcp-server", - "version": "0.0.1", + "name": "@apify/mcp-server", + "version": "0.1.0", "type": "module", "description": "Model Context Protocol Server for Apify Actors", "engines": { "node": ">=18.0.0" }, + "main": "dist/index.js", + "bin": { + "apify-mcp-server": "dist/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/apify/actor-mcp-server.git" + }, + "bugs": { + "url": "https://github.com/apify/actor-mcp-server/issues" + }, + "homepage": "https://apify.com/apify/mcp-server", + "keywords": [ + "apify", + "mcp", + "server", + "actors", + "model context protocol" + ], "dependencies": { "@modelcontextprotocol/sdk": "^1.1.0", "ajv": "^8.17.1", @@ -38,6 +57,6 @@ "test": "echo \"Error: oops, the actor has no tests yet, sad!\" && exit 1", "watch": "tsc --watch" }, - "author": "It's not you it's me", - "license": "ISC" + "author": "Apify", + "license": "MIT" } From 147f7a88ae79c775669d30bf0bd92d0b0f630133 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 10 Jan 2025 14:59:14 +0100 Subject: [PATCH 24/54] Fix lint issues in before-beta-release.js --- .github/scripts/before-beta-release.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/scripts/before-beta-release.js b/.github/scripts/before-beta-release.js index 8f89856..26ae4e1 100644 --- a/.github/scripts/before-beta-release.js +++ b/.github/scripts/before-beta-release.js @@ -1,32 +1,32 @@ -const path = require('path'); -const fs = require('fs'); const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); const PKG_JSON_PATH = path.join(__dirname, '..', '..', 'package.json'); -const pkgJson = require(PKG_JSON_PATH); +const pkgJson = require(PKG_JSON_PATH); // eslint-disable-line import/no-dynamic-require const PACKAGE_NAME = pkgJson.name; const VERSION = pkgJson.version; const nextVersion = getNextVersion(VERSION); -console.log(`before-deploy: Setting version to ${nextVersion}`); +console.log(`before-deploy: Setting version to ${nextVersion}`); // eslint-disable-line no-console pkgJson.version = nextVersion; -fs.writeFileSync(PKG_JSON_PATH, JSON.stringify(pkgJson, null, 2) + '\n'); +fs.writeFileSync(PKG_JSON_PATH, `${JSON.stringify(pkgJson, null, 2)}\n`); function getNextVersion(version) { - const versionString = execSync(`npm show ${PACKAGE_NAME} versions --json`, { encoding: 'utf8'}); + const versionString = execSync(`npm show ${PACKAGE_NAME} versions --json`, { encoding: 'utf8' }); const versions = JSON.parse(versionString); - if (versions.some(v => v === VERSION)) { - console.error(`before-deploy: A release with version ${VERSION} already exists. Please increment version accordingly.`); + if (versions.some((v) => v === VERSION)) { + console.error(`before-deploy: A release with version ${VERSION} already exists. Please increment version accordingly.`); // eslint-disable-line no-console process.exit(1); } const prereleaseNumbers = versions - .filter(v => (v.startsWith(VERSION) && v.includes('-'))) - .map(v => Number(v.match(/\.(\d+)$/)[1])); + .filter((v) => (v.startsWith(VERSION) && v.includes('-'))) + .map((v) => Number(v.match(/\.(\d+)$/)[1])); const lastPrereleaseNumber = Math.max(-1, ...prereleaseNumbers); - return `${version}-beta.${lastPrereleaseNumber + 1}` + return `${version}-beta.${lastPrereleaseNumber + 1}`; } From 7e975e579a168b44cf85e175bb1ab95b0e4f1852 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 10 Jan 2025 15:23:01 +0100 Subject: [PATCH 25/54] Remove dead code --- src/examples/clientStdioChat.ts | 22 +--------------------- src/server.ts | 2 +- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/examples/clientStdioChat.ts b/src/examples/clientStdioChat.ts index ddf2a0e..2baabe7 100644 --- a/src/examples/clientStdioChat.ts +++ b/src/examples/clientStdioChat.ts @@ -9,7 +9,7 @@ * Type your queries or 'quit|q|exit' to exit. * You: Find to articles about AI agent and return URLs * [internal] Received response from Claude: [{"type":"text","text":"I'll search for information about AI agents - * and provide you with a summary."},{"type":"tool_use","id":"toolu_01He9TkzQfh2979bbeuxWVqM","name":"search", + * and provide you with a summary."},{"type":"tool_use","id":"tool_01He9TkzQfh2979bbeuxWVqM","name":"search", * "input":{"query":"what are AI agents definition capabilities applications","maxResults":2}}] * [internal] Calling tool: {"name":"search","arguments":{"query":"what are AI agents definition ... * I can help analyze the provided content about AI agents. @@ -140,26 +140,6 @@ class MCPClient { } return messages; - - // const lastContent = nextResponse.content[nextResponse.content.length - 1]; - // if (lastContent.type !== 'tool_use' && toolCallCount < 3) { - // return this.handleToolCall(lastContent, messages, toolCallCount + 1); - // } - // - // const lastContent = nextResponse.content[nextResponse.content.length - 1]; - // if (lastContent.type !== 'tool_use' && toolCallCount < 3) { - // return this.handleToolCall(lastContent, messages, toolCallCount + 1); - // } - // - // if (nextResponse.content typeof ToolUseBlock) { - // if (nextResponse.content.slice(-1).type !== 'tool_use' && toolCallCount < 3) { - // return this.handleToolCall(nextResponse.content.slice(-1), messages, toolCallCount + 1); - // } - // } - // - // messages.push({ role: nextResponse.role, content: nextResponse.content.map((x: TextBlockParam) => x.text || '').join('\n') }); - // - // return messages; } /** diff --git a/src/server.ts b/src/server.ts index 5ed6eaf..75fa526 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node /** - * Model Context Protocol (MCP) server for RAG Web Browser Actor + * Model Context Protocol (MCP) server for Apify Actors */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; From d2bc65a7300299228ccebbb6f6ad0daed82f2a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Spilka?= Date: Fri, 10 Jan 2025 22:41:16 +0100 Subject: [PATCH 26/54] docs: Update documentation (#2) * Update docs and update apify-client-js with fixes for Actor definition --- README.md | 193 +++++++++++++++++++++++------------- package-lock.json | 13 ++- package.json | 34 +++---- src/actorDefinition.ts | 27 +++-- src/const.ts | 6 +- src/examples/clientStdio.ts | 3 +- src/examples/client_sse.py | 7 +- src/index.ts | 13 +++ src/input.ts | 9 +- src/main.ts | 7 +- src/server.ts | 40 +++++--- src/types.ts | 14 +++ 12 files changed, 232 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index 7018047..933c35d 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,125 @@ -## Apify Model Context Protocol (MCP) Server +# Apify Model Context Protocol (MCP) Server Implementation of an MCP server for all [Apify Actors](https://apify.com/store). This server enables interaction with one or more Apify Actors that can be defined in the MCP server configuration. The server can be used in two ways: -- **MCP Server Actor**, which runs an HTTP server supporting the MCP protocol via Server-Sent Events (SSE). -- **MCP Server CLI** supports the MCP protocol via stdio. +- **Apify MCP Server Actor**: runs an HTTP server supporting the MCP protocol via Server-Sent Events (SSE). +- **Apify MCP Server Stdio**: provides support for the MCP protocol via standard input/output stdio. -## 🎯 What does this MCP server do? +# 🎯 What does this MCP server do? The MCP Server Actor allows an AI assistant to: - Use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task.For example it can: - - [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from Facebook posts from one or multiple Facebook pages/profiles - - [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) Extract Google Maps contact details - - [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) Scrape Google Search Engine Results Pages (SERPs) + - [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from Facebook posts from multiple pages/profiles + - [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) extract Google Maps contact details + - [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) scrape Google Search Engine Results Pages (SERPs) - [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrape Instagram posts, profiles, places, hashtags, photos, and comments - [RAG Web Browser](https://apify.com/apify/web-scraper) perform web search, scrape the top N URLs from the results, and return content -Once the server is started, you can use MCP clients, such as [Claude Desktop](https://claude.ai/download) or +To interact with the Apify MCP server, you can use MCP clients such as [Claude Desktop](https://claude.ai/download), [Superinference.ai](https://superinterface.ai/), or [LibreChat](https://www.librechat.ai/). +Additionally, you can use simple example clients found in the [examples](https://github.com/apify/actor-mcp-server/tree/main/src/examples) directory. +When you have Actors integrated with the MCP server, you can ask: +- Search web and summarize recent trends about AI Agents +- Find top 10 best Italian restaurants in San Francisco +- Find and analyze Instagram profile of The Rock +- Provide a step-by-step guide on using the Model Context Protocol with source URLs. +- What Apify Actors I can use? -## 🔄 What is model context protocol? +# 🔄 What is model context protocol? The Model Context Protocol (MCP) allows AI applications (and AI agents), such as Claude Desktop, to connect to external tools and data sources. MCP is an open protocol that enables secure, controlled interactions between AI applications, AI Agents, and local or remote resources. -## 🧱 Components +# 🧱 Components -### Tools +## Tools Any [Apify Actor](https://apify.com/store) can be used as a tool. -By default, the server is pre-configured with the tools specified above, but it can be overridden by providing a list of Actor names in the `actors` query parameter. - -The tool name must always be the full Actor name, such as `lukaskrivka/google-maps-with-contact-details` +By default, the server is pre-configured with the Actors specified below, but it can be overridden by providing a list of Actor names in the `actors` query parameter. ```text -'apify/facebook-posts-scraper' -'apify/google-search-scraper' -'apify/instagram-scraper' -'apify/rag-web-browser' -'lukaskrivka/google-maps-with-contact-details' + 'apidojo/tweet-scraper', + 'apify/facebook-posts-scraper', + 'apify/google-search-scraper', + 'apify/instagram-scraper', + 'apify/rag-web-browser', + 'clockworks/free-tiktok-scraper', + 'compass/crawler-google-places', + 'lukaskrivka/google-maps-with-contact-details', + 'voyager/booking-scraper' +``` +The MCP server loads the Actor input schema and creates MCP tools corresponding to the Actors. +See this example of input schema for the [RAG Web Browser](https://apify.com/apify/rag-web-browser/input-schema). + +The tool name must always be the full Actor name, such as `apify/rag-web-browser`. +The arguments for an MCP tool represent the input parameters of the Actor. +For example, for the `apify/rag-web-browser` Actor, the arguments are: + +```json +{ + "query": "restaurants in San Francisco", + "maxResults": 3 +} ``` -The arguments for MCP tool represent input parameters for the Actor. -Please see the examples below and refer to the specific Actor's documentation for a list of available arguments. +You don't need to specify the input parameters or which Actor to call, everything is managed by an LLM. +When a tool is called, the arguments are automatically passed to the Actor by the LLM. +You can refer to the specific Actor's documentation for a list of available arguments. -### Prompt & Resources +## Prompt & Resources The server does not provide any resources and prompts. We plan to add Apify's dataset and key-value store as resources in the future. -## ⚙️ Usage +# ⚙️ Usage The Apify MCP Server can be used in two ways: **as an Apify Actor** running at Apify platform or as a **local server** running on your machine. -### MCP Server Actor +## 🇦 MCP Server Actor -#### Standby web server +### Standby web server The Actor runs in [**Standby mode**](https://docs.apify.com/platform/actors/running/standby) with an HTTP web server that receives and processes requests. -1. **Start server with selected Actors**. To use the Apify MCP Server with a custom set of Actors (e.g. Google Maps Email Extractor, Facebook Posts Scraper), - send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL. - Provide a comma-separated list of Actors in the `actors` query parameter: - ``` - https://mcp-server.apify.actor?token=&actors=lukaskrivka/google-maps-with-contact-details,apify/facebook-posts-scraper - ``` +Start server with default Actors. To use the Apify MCP Server with set of default Actors, +send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL. +``` +https://mcp-server.apify.actor?token= +``` +It is also possible to start MCP server with a different set of tools by providing a list of Actor names in the `actors` query parameter. +Provide a comma-separated list of Actors in the `actors` query parameter: +``` +https://mcp-server.apify.actor?token=&actors=junglee/free-amazon-product-scraper,lukaskrivka/google-maps-with-contact-details +``` +Find list of all available Actors in the [Apify Store](https://apify.com/store). + +#### 💬 Interact with the MCP Server + +Once the server is running, you can interact with Server-Sent Events (SSE) to send messages to the server and receive responses. +You can use MCP clients such as [Superinference.ai](https://superinterface.ai/) or [LibreChat](https://www.librechat.ai/). +([Claude Desktop](https://claude.ai/download) does not support SSE transport yet) -2. **Initiate Server-Sent-Events (SSE)** by sending a GET request to the following URL: +In the client settings you need to provide server configuration: +```json +{ + "mcpServers": { + "apify": { + "type": "sse", + "url": "https://mcp-server.apify.actor/sse", + "env": { + "APIFY-API-TOKEN": "your-apify-api-token" + } + } + } +} +``` +Alternatively, you can use simple python [client_see.py](https://github.com/apify/actor-mcp-server/tree/main/src/examples/client_sse.py) or test the server using `curl` commands. + +1. Initiate Server-Sent-Events (SSE) by sending a GET request to the following URL: ``` - https://mcp-server.apify.actor/sse?token= + curl https://mcp-server.apify.actor/sse?token= ``` The server will respond with a `sessionId`, which you can use to send messages to the server: ```shell @@ -77,7 +127,7 @@ The Actor runs in [**Standby mode**](https://docs.apify.com/platform/actors/runn data: /message?sessionId=a1b ``` -3. **Send a message to the server** by making a POST request with the `sessionId`: +2. Send a message to the server by making a POST request with the `sessionId`: ```shell curl -X POST "https://mcp-server.apify.actor?token=&session_id=a1b" -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", @@ -96,7 +146,7 @@ The Actor runs in [**Standby mode**](https://docs.apify.com/platform/actors/runn Accepted ``` -4. **Receive the response.** The server will invoke the specified Actor as a tool using the provided query parameters and stream the response back to the client via SSE. +3. Receive the response. The server will invoke the specified Actor as a tool using the provided query parameters and stream the response back to the client via SSE. The response will be returned as JSON text. ```text @@ -104,22 +154,16 @@ The Actor runs in [**Standby mode**](https://docs.apify.com/platform/actors/runn data: {"result":{"content":[{"type":"text","text":"{\"searchString\":\"restaurants in San Francisco\",\"rank\":1,\"title\":\"Gary Danko\",\"description\":\"Renowned chef Gary Danko's fixed-price menus of American cuisine ... \",\"price\":\"$100+\"...}}]}} ``` -You can also start MCP server with a different set of tools by providing a list of Actor names in the `actors` query parameter. -``` -https://mcp-server.apify.actor?token=&actors=junglee/free-amazon-product-scraper,junglee/free-amazon-product-scraper -``` -You can find list of all available Actors in the [Apify Store](https://apify.com/store). +## ⾕ MCP Server at a local host -### MCP Server at local host - -#### Prerequisites +### Prerequisites - MacOS or Windows - The latest version of Claude Desktop must be installed (or another MCP client) - [Node.js](https://nodejs.org/en) (v18 or higher) - [Apify API Token](https://docs.apify.com/platform/integrations/api#api-token) (`APIFY_API_TOKEN`) -#### Install +### Install Follow the steps below to set up and run the server on your local machine: First, clone the repository using the following command: @@ -151,7 +195,7 @@ Configure Claude Desktop to recognize the MCP server. ```text "mcpServers": { - "apify-mcp-server": { + "apify": { "command": "npx", "args": [ "/path/to/actor-mcp-server/dist/index.js" @@ -170,7 +214,7 @@ Configure Claude Desktop to recognize the MCP server. "args": [ "/path/to/actor-mcp-server/dist/index.js", "--actors", - "lukaskrivka/google-maps-with-contact-details,apify/facebook-posts-scraper" + "lukaskrivka/google-maps-with-contact-details,apify/instagram-scraper" ] "env": { "APIFY-API-TOKEN": "your-apify-api-token" @@ -194,50 +238,57 @@ Configure Claude Desktop to recognize the MCP server. Find and analyze instagram profile of the Rock. ``` -## 👷🏼 Development - -### Prerequisites - -- [Node.js](https://nodejs.org/en) (v18 or higher) -- Python 3.6 or higher +#### Stdio clients Create environment file `.env` with the following content: ```text APIFY_API_TOKEN=your-apify-api-token +# ANTHROPIC_API_KEY is only required when you want to run examples/clientStdioChat.js +ANTHROPIC_API_KEY=your-anthropic-api-token ``` +In the `examples` directory, you can find two clients that interact with the server via +standard input/output (stdio): +1. [`clientStdio.ts`](https://github.com/apify/actor-mcp-server/tree/main/src/examples/clientStdio.ts) + This client script starts the MCP server with two specified Actors. + It then calls the `apify/rag-web-browser` tool with a query and prints the result. + It demonstrates how to connect to the MCP server, list available tools, and call a specific tool using stdio transport. + ```bash + node dist/examples/clientStdio.js + ``` -### Local client (stdio) - -To test the server locally, you can use `examples/clientStdio.ts`: +2. [`clientStdioChat.ts`](https://github.com/apify/actor-mcp-server/tree/main/src/examples/clientStdioChat.ts) + This client script also starts the MCP server but provides an interactive command-line chat interface. + It prompts the user to interact with the server, allowing for dynamic tool calls and responses. + This example is useful for testing and debugging interactions with the MCP server in conversational manner. -```bash -node dist/examples/clientStdio.js -``` + ```bash + node dist/examples/clientStdioChat.js + ``` -The script will start the MCP server with two Actors (`lukaskrivka/google-maps-with-contact-details` and `apify/rag-web-browser`). -Then it will call `apify/rag-web-browser` tool with a query and will print the result. +# 👷🏼 Development -### Chat local client (stdio) +## Prerequisites -To run simple chat client, you can use `examples/clientSdioChat.ts`: +- [Node.js](https://nodejs.org/en) (v18 or higher) +- Python 3.6 or higher -```bash -node dist/examples/clientStdioChat.js +Create environment file `.env` with the following content: +```text +APIFY_API_TOKEN=your-apify-api-token +# ANTHROPIC_API_KEY is only required when you want to run examples/clientStdioChat.js +ANTHROPIC_API_KEY=your-anthropic-api-token ``` -Here you can interact with the server using the chat interface. - -### Local client (SSE) +## Local client (SSE) To test the server with the SSE transport, you can use python script `examples/client_sse.py`: Currently, the node.js client does not support to establish a connection to remote server witch custom headers. +You need to change URL to your local server URL in the script. ```bash -node dist/clientSse.js +python src/examples/client_sse.py ``` -The script will start the MCP server with default Actors. - -### Debugging +## Debugging Since MCP servers operate over standard input/output (stdio), debugging can be challenging. For the best debugging experience, use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). diff --git a/package-lock.json b/package-lock.json index b0f7b6d..57cfccd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,15 +7,18 @@ "": { "name": "@apify/mcp-server", "version": "0.1.0", - "license": "ISC", + "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.1.0", "ajv": "^8.17.1", "apify": "^3.2.6", - "apify-client": "^2.11.0", + "apify-client": "^2.11.1", "express": "^4.21.2", "minimist": "^1.2.8" }, + "bin": { + "apify-mcp-server": "dist/index.js" + }, "devDependencies": { "@anthropic-ai/sdk": "^0.33.1", "@anthropic-ai/tokenizer": "^0.0.4", @@ -1643,9 +1646,9 @@ } }, "node_modules/apify-client": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/apify-client/-/apify-client-2.11.0.tgz", - "integrity": "sha512-r3UQ4wEhU0nTuT9owYCCqcCXvj/gSSMsaSFs1wszwLqq3dadfQK6n4rLg4zrTpmQHQY7i+OjMV2vp9rOgm916A==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/apify-client/-/apify-client-2.11.1.tgz", + "integrity": "sha512-AqX5njCZv9jcPNXJqIi4KG/XBmLbTwR2wYQRBVLwDva1vhijPR22/lDcNUqYEZqj3Dc8a1xtMC3UScro0GkVeg==", "license": "Apache-2.0", "dependencies": { "@apify/consts": "^2.25.0", diff --git a/package.json b/package.json index 2dd3534..bdf593c 100644 --- a/package.json +++ b/package.json @@ -9,27 +9,27 @@ "main": "dist/index.js", "bin": { "apify-mcp-server": "dist/index.js" - }, - "repository": { - "type": "git", - "url": "https://github.com/apify/actor-mcp-server.git" - }, - "bugs": { - "url": "https://github.com/apify/actor-mcp-server/issues" - }, - "homepage": "https://apify.com/apify/mcp-server", - "keywords": [ - "apify", - "mcp", - "server", - "actors", - "model context protocol" - ], + }, + "repository": { + "type": "git", + "url": "https://github.com/apify/actor-mcp-server.git" + }, + "bugs": { + "url": "https://github.com/apify/actor-mcp-server/issues" + }, + "homepage": "https://apify.com/apify/mcp-server", + "keywords": [ + "apify", + "mcp", + "server", + "actors", + "model context protocol" + ], "dependencies": { "@modelcontextprotocol/sdk": "^1.1.0", "ajv": "^8.17.1", "apify": "^3.2.6", - "apify-client": "^2.11.0", + "apify-client": "^2.11.1", "express": "^4.21.2", "minimist": "^1.2.8" }, diff --git a/src/actorDefinition.ts b/src/actorDefinition.ts index 42c12b5..ab23acc 100644 --- a/src/actorDefinition.ts +++ b/src/actorDefinition.ts @@ -1,18 +1,15 @@ import { Ajv } from 'ajv'; -import type { ActorDefinition } from 'apify-client'; import { ApifyClient } from 'apify-client'; import { log } from './logger.js'; - -interface ActorDefinitionWithDesc extends ActorDefinition { - description: string; -} +import type { ActorDefinitionWithDesc, Tool } from './types'; /** * Get actor input schema by actor name. * First, fetch the actor details to get the default build tag and buildId. * Then, fetch the build details and return actorName, description, and input schema. - * @param actorFullName + * @param {string} actorFullName - The full name of the actor. + * @returns {Promise} - The actor definition with description or null if not found. */ async function fetchActorDefinition(actorFullName: string): Promise { if (!process.env.APIFY_API_TOKEN) { @@ -33,7 +30,6 @@ async function fetchActorDefinition(actorFullName: string): Promise} - A promise that resolves to an array of MCP tools. */ -export async function getActorsAsTools(actors: string[]) { +export async function getActorsAsTools(actors: string[]): Promise { // Fetch input schemas in parallel const ajv = new Ajv({ coerceTypes: 'array', strict: false }); const results = await Promise.all(actors.map(fetchActorDefinition)); diff --git a/src/const.ts b/src/const.ts index 14d4868..6261d5f 100644 --- a/src/const.ts +++ b/src/const.ts @@ -3,11 +3,15 @@ export const SERVER_VERSION = '0.1.0'; export const defaults = { actors: [ + 'apidojo/tweet-scraper', 'apify/facebook-posts-scraper', 'apify/google-search-scraper', 'apify/instagram-scraper', 'apify/rag-web-browser', - 'compass/google-maps-extractor', + 'clockworks/free-tiktok-scraper', + 'compass/crawler-google-places', + 'lukaskrivka/google-maps-with-contact-details', + 'voyager/booking-scraper', ], }; diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts index 685ec2f..1f55f23 100644 --- a/src/examples/clientStdio.ts +++ b/src/examples/clientStdio.ts @@ -2,8 +2,7 @@ /** * Connect to the MCP server using stdio transport and call a tool. * You need provide a path to MCP server and APIFY_API_TOKEN in .env file. - * - * Also, you need to choose ACTORS to run in the server, for example: apify/rag-web-browser + * You can choose ACTORS to run in the server, for example: apify/rag-web-browser */ import { execSync } from 'child_process'; diff --git a/src/examples/client_sse.py b/src/examples/client_sse.py index f3b6da5..4a22d8f 100644 --- a/src/examples/client_sse.py +++ b/src/examples/client_sse.py @@ -1,11 +1,10 @@ """ -Test MCP Server with Actors using SSE client +Test Apify MCP Server using SSE client -This script demonstrates how to test the MCP Server with Actors using the SSE client. It is using python client as the typescript one does not support custom headers when connecting to the SSE server. -Install dependencies: -pip install requests python-dotenv mcp +Install python dependencies (assumes you have python installed): +> pip install requests python-dotenv mcp """ import asyncio diff --git a/src/index.ts b/src/index.ts index 8913ff9..328833c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,16 @@ +/** + * This script initializes and starts the Apify MCP server using the Stdio transport. + * + * Usage: + * node --actors= + * + * Command-line arguments: + * --actors - A comma-separated list of actor full names to add to the server. + * + * Example: + * node index.js --actors=apify/google-search-scraper,apify/instagram-scraper + */ + import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import minimist from 'minimist'; diff --git a/src/input.ts b/src/input.ts index 799d836..c90bbe7 100644 --- a/src/input.ts +++ b/src/input.ts @@ -1,6 +1,11 @@ import type { Input } from './types.js'; -export async function processInput(originalInput: Partial) { +/** + * Process input parameters, split actors string into an array + * @param originalInput + * @returns input + */ +export async function processInput(originalInput: Partial): Promise { const input = originalInput as Input; // actors can be a string or an array of strings @@ -10,5 +15,5 @@ export async function processInput(originalInput: Partial) { if (!input.actors || input.actors.length === 0) { throw new Error('The `actors` parameter must be a non-empty array.'); } - return { input }; + return input; } diff --git a/src/main.ts b/src/main.ts index a10c9e6..d09dbe5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -28,12 +28,14 @@ const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse?toke /** * Process input parameters and update tools + * If URL contains query parameter actors, add tools from actors, otherwise add tools from default actors + * @param url */ async function processParamsAndUpdateTools(url: string) { const params = parse(url.split('?')[1] || '') as ParsedUrlQuery; delete params.token; log.debug(`Received input parameters: ${JSON.stringify(params)}`); - const { input } = await processInput(params as Input); + const input = await processInput(params as Input); await (input.actors ? mcpServer.addToolsFromActors(input.actors as string[]) : mcpServer.addToolsFromDefaultActors()); } @@ -55,6 +57,7 @@ app.head(Routes.ROOT, (_req: Request, res: Response) => { app.get(Routes.SSE, async (req: Request, res: Response) => { try { log.info(`Received GET message at: ${req.url}`); + await processParamsAndUpdateTools(req.url); transport = new SSEServerTransport(Routes.MESSAGE, res); await mcpServer.connect(transport); } catch (error) { @@ -86,7 +89,7 @@ if (STANDBY_MODE) { }); } else { log.info('Actor is not designed to run in the NORMAL model (use this mode only for debugging purposes)'); - const { input } = await processInput((await Actor.getInput>()) ?? ({} as Input)); + const input = await processInput((await Actor.getInput>()) ?? ({} as Input)); log.info(`Loaded input: ${JSON.stringify(input)} `); if (input && !input.debugActor && !input.debugActorInput) { diff --git a/src/server.ts b/src/server.ts index 75fa526..c84b574 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,20 +5,20 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; -import type { ValidateFunction } from 'ajv'; import { Actor } from 'apify'; import { ApifyClient } from 'apify-client'; import { getActorsAsTools } from './actorDefinition.js'; import { defaults, SERVER_NAME, SERVER_VERSION } from './const.js'; import { log } from './logger.js'; +import type { Tool } from './types'; /** * Create Apify MCP server */ export class ApifyMcpServer { private server: Server; - private tools: { name: string; description: string; inputSchema: object, ajvValidate: ValidateFunction}[]; + private tools: Map; constructor() { this.server = new Server( @@ -32,11 +32,22 @@ export class ApifyMcpServer { }, }, ); - this.tools = []; + this.tools = new Map(); this.setupErrorHandling(); this.setupToolHandlers(); } + /** + * Calls an Apify actor and retrieves the dataset items. + * + * It requires the `APIFY_API_TOKEN` environment variable to be set. + * If the `APIFY_IS_AT_HOME` the dataset items are pushed to the Apify dataset. + * + * @param {string} actorName - The name of the actor to call. + * @param {unknown} input - The input to pass to the actor. + * @returns {Promise} - A promise that resolves to an array of dataset items. + * @throws {Error} - Throws an error if the `APIFY_API_TOKEN` is not set + */ public async callActorGetDataset(actorName: string, input: unknown): Promise { if (!process.env.APIFY_API_TOKEN) { throw new Error('APIFY_API_TOKEN is required but not set. Please set it as an environment variable'); @@ -71,18 +82,10 @@ export class ApifyMcpServer { await this.addToolsFromActors(defaults.actors); } - public addToolIfNotExist(name: string, description: string, inputSchema: object, ajvValidate: ValidateFunction): void { - if (!this.tools.find((x) => x.name === name)) { - this.tools.push({ name, description, inputSchema, ajvValidate }); - log.info(`Added tool: ${name}`); - } else { - log.info(`Tool already exists: ${name}`); - } - } - - public updateTools(tools: { name: string; description: string; inputSchema: object, ajvValidate: ValidateFunction}[]): void { + public updateTools(tools: Tool[]): void { for (const tool of tools) { - this.addToolIfNotExist(tool.name, tool.description, tool.inputSchema, tool.ajvValidate); + this.tools.set(tool.name, tool); + log.info(`Added/Updated tool: ${tool.name}`); } } @@ -98,13 +101,18 @@ export class ApifyMcpServer { private setupToolHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => { - return { tools: this.tools }; + return { tools: this.tools.values() }; }); + /** + * Handles the request to call a tool. + * @param {object} request - The request object containing tool name and arguments. + * @throws {Error} - Throws an error if the tool is unknown or arguments are invalid. + */ this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; - const tool = this.tools.find((t) => t.name === name); + const tool = this.tools.get(name); if (!tool) { throw new Error(`Unknown tool: ${name}`); } diff --git a/src/types.ts b/src/types.ts index 52e6ff6..886940d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,19 @@ +import type { ValidateFunction } from 'ajv'; +import type { ActorDefinition } from 'apify-client'; + export type Input = { actors: string[] | string; debugActor?: string; debugActorInput?: unknown; }; + +export interface ActorDefinitionWithDesc extends ActorDefinition { + description: string; +} + +export interface Tool { + name: string; + description: string; + inputSchema: object; + ajvValidate: ValidateFunction; +} From c6128c3cd0e8f5611848ab4374eb5cce51dd965d Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 10 Jan 2025 22:43:00 +0100 Subject: [PATCH 27/54] Update section title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 933c35d..965437b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ The server can be used in two ways: - **Apify MCP Server Actor**: runs an HTTP server supporting the MCP protocol via Server-Sent Events (SSE). - **Apify MCP Server Stdio**: provides support for the MCP protocol via standard input/output stdio. -# 🎯 What does this MCP server do? +# 🎯 What does Apify MCP server do? The MCP Server Actor allows an AI assistant to: - Use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task.For example it can: From c676bc2beff4d4a803f0701a9b311ce391d055b9 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 10 Jan 2025 22:45:14 +0100 Subject: [PATCH 28/54] Update docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 965437b..4fce1d4 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ The server can be used in two ways: # 🎯 What does Apify MCP server do? -The MCP Server Actor allows an AI assistant to: -- Use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task.For example it can: +The MCP Server Actor allows an AI assistant to use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task. +For example it can: - [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from Facebook posts from multiple pages/profiles - [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) extract Google Maps contact details - [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) scrape Google Search Engine Results Pages (SERPs) From 7ced4a45c9b6d04f14e62c7d0019a8ecc3f87e41 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 10 Jan 2025 22:45:55 +0100 Subject: [PATCH 29/54] Update docs --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4fce1d4..088222b 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ The server can be used in two ways: The MCP Server Actor allows an AI assistant to use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task. For example it can: - - [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from Facebook posts from multiple pages/profiles - - [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) extract Google Maps contact details - - [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) scrape Google Search Engine Results Pages (SERPs) - - [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrape Instagram posts, profiles, places, hashtags, photos, and comments - - [RAG Web Browser](https://apify.com/apify/web-scraper) perform web search, scrape the top N URLs from the results, and return content +- [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from Facebook posts from multiple pages/profiles +- [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) extract Google Maps contact details +- [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) scrape Google Search Engine Results Pages (SERPs) +- [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrape Instagram posts, profiles, places, hashtags, photos, and comments +- [RAG Web Browser](https://apify.com/apify/web-scraper) perform web search, scrape the top N URLs from the results, and return content To interact with the Apify MCP server, you can use MCP clients such as [Claude Desktop](https://claude.ai/download), [Superinference.ai](https://superinterface.ai/), or [LibreChat](https://www.librechat.ai/). Additionally, you can use simple example clients found in the [examples](https://github.com/apify/actor-mcp-server/tree/main/src/examples) directory. From 5cbf12c4671274798ab4ae10bb04070c01c66f83 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 10 Jan 2025 22:47:51 +0100 Subject: [PATCH 30/54] Update docs --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 088222b..a4a762d 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,18 @@ Implementation of an MCP server for all [Apify Actors](https://apify.com/store). This server enables interaction with one or more Apify Actors that can be defined in the MCP server configuration. The server can be used in two ways: -- **Apify MCP Server Actor**: runs an HTTP server supporting the MCP protocol via Server-Sent Events (SSE). -- **Apify MCP Server Stdio**: provides support for the MCP protocol via standard input/output stdio. +- 🇦 **Apify MCP Server Actor**: runs an HTTP server supporting the MCP protocol via Server-Sent Events (SSE). +- ⾕ **Apify MCP Server Stdio**: provides support for the MCP protocol via standard input/output stdio. # 🎯 What does Apify MCP server do? The MCP Server Actor allows an AI assistant to use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task. For example it can: -- [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extract data from Facebook posts from multiple pages/profiles -- [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) extract Google Maps contact details -- [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) scrape Google Search Engine Results Pages (SERPs) -- [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrape Instagram posts, profiles, places, hashtags, photos, and comments -- [RAG Web Browser](https://apify.com/apify/web-scraper) perform web search, scrape the top N URLs from the results, and return content +- [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extracts data from Facebook posts from multiple pages/profiles +- [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) extracts Google Maps contact details +- [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) scrapes Google Search Engine Results Pages (SERPs) +- [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrapes Instagram posts, profiles, places, hashtags, photos, and comments +- [RAG Web Browser](https://apify.com/apify/web-scraper) performs web search, scrape the top N URLs from the results, and return content To interact with the Apify MCP server, you can use MCP clients such as [Claude Desktop](https://claude.ai/download), [Superinference.ai](https://superinterface.ai/), or [LibreChat](https://www.librechat.ai/). Additionally, you can use simple example clients found in the [examples](https://github.com/apify/actor-mcp-server/tree/main/src/examples) directory. From 8355f3ad3c941b821fff61e77c31379f7c4091a8 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Sun, 12 Jan 2025 23:46:00 +0100 Subject: [PATCH 31/54] fix return value --- src/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.ts b/src/server.ts index c84b574..1ec0ee6 100644 --- a/src/server.ts +++ b/src/server.ts @@ -101,7 +101,7 @@ export class ApifyMcpServer { private setupToolHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => { - return { tools: this.tools.values() }; + return { tools: Array.from(this.tools.values()) }; }); /** From 1f5c841ad5258fe5643834f3581bdec793d6d443 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Mon, 13 Jan 2025 08:49:02 +0100 Subject: [PATCH 32/54] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a4a762d..84c5f79 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Implementation of an MCP server for all [Apify Actors](https://apify.com/store). This server enables interaction with one or more Apify Actors that can be defined in the MCP server configuration. The server can be used in two ways: -- 🇦 **Apify MCP Server Actor**: runs an HTTP server supporting the MCP protocol via Server-Sent Events (SSE). +- 🇦 **Apify MCP Server Actor**: runs an HTTP server supporting the MCP protocol via Server-Sent Events. - ⾕ **Apify MCP Server Stdio**: provides support for the MCP protocol via standard input/output stdio. # 🎯 What does Apify MCP server do? From a857a7456d25c17576b03b6047e1b1573f6a55af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Spilka?= Date: Mon, 13 Jan 2025 21:37:27 +0100 Subject: [PATCH 33/54] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: František Nesveda --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 84c5f79..9f071ae 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ The server can be used in two ways: The MCP Server Actor allows an AI assistant to use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task. For example it can: -- [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) extracts data from Facebook posts from multiple pages/profiles -- [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) extracts Google Maps contact details -- [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) scrapes Google Search Engine Results Pages (SERPs) -- [Instagram Scraper](https://apify.com/apify/instagram-scraper) scrapes Instagram posts, profiles, places, hashtags, photos, and comments -- [RAG Web Browser](https://apify.com/apify/web-scraper) performs web search, scrape the top N URLs from the results, and return content +- use [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) to extract data from Facebook posts from multiple pages/profiles +- use [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) to extract Google Maps contact details +- use [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) to scrape Google Search Engine Results Pages (SERPs) +- use [Instagram Scraper](https://apify.com/apify/instagram-scraper) to scrape Instagram posts, profiles, places, hashtags, photos, and comments +- use [RAG Web Browser](https://apify.com/apify/web-scraper) to perform a web search, scrape the top N URLs from the results, and return content To interact with the Apify MCP server, you can use MCP clients such as [Claude Desktop](https://claude.ai/download), [Superinference.ai](https://superinterface.ai/), or [LibreChat](https://www.librechat.ai/). Additionally, you can use simple example clients found in the [examples](https://github.com/apify/actor-mcp-server/tree/main/src/examples) directory. From 472be0cdee8c2d02941711acdeab157635e27d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Spilka?= Date: Mon, 13 Jan 2025 21:37:42 +0100 Subject: [PATCH 34/54] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: František Nesveda --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9f071ae..98782ba 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,11 @@ To interact with the Apify MCP server, you can use MCP clients such as [Claude D Additionally, you can use simple example clients found in the [examples](https://github.com/apify/actor-mcp-server/tree/main/src/examples) directory. When you have Actors integrated with the MCP server, you can ask: -- Search web and summarize recent trends about AI Agents -- Find top 10 best Italian restaurants in San Francisco -- Find and analyze Instagram profile of The Rock -- Provide a step-by-step guide on using the Model Context Protocol with source URLs. -- What Apify Actors I can use? +- "Search web and summarize recent trends about AI Agents" +- "Find top 10 best Italian restaurants in San Francisco" +- "Find and analyze Instagram profile of The Rock" +- "Provide a step-by-step guide on using the Model Context Protocol with source URLs." +- "What Apify Actors I can use?" # 🔄 What is model context protocol? From 036c6b3534bd6eafa59adceb26be1c56934eff73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Spilka?= Date: Mon, 13 Jan 2025 21:37:54 +0100 Subject: [PATCH 35/54] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: František Nesveda --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 98782ba..f206a67 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ When you have Actors integrated with the MCP server, you can ask: - "Provide a step-by-step guide on using the Model Context Protocol with source URLs." - "What Apify Actors I can use?" -# 🔄 What is model context protocol? +# 🔄 What is the Model Context Protocol? The Model Context Protocol (MCP) allows AI applications (and AI agents), such as Claude Desktop, to connect to external tools and data sources. MCP is an open protocol that enables secure, controlled interactions between AI applications, AI Agents, and local or remote resources. From e1a0c6a84ec6b7473269ca9ae38fea2849640c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Spilka?= Date: Mon, 13 Jan 2025 21:43:17 +0100 Subject: [PATCH 36/54] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: František Nesveda --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bdf593c..143cfc6 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@anthropic-ai/tokenizer": "^0.0.4", "@apify/eslint-config": "^0.5.0-beta.2", "@apify/tsconfig": "^0.1.0", - "@types/express": "^5.0.0", + "@types/express": "^4.0.0", "@types/minimist": "^1.2.5", "dotenv": "^16.4.7", "eslint": "^9.17.0", From 34ecf2543cbc146f9a9ff2163c11c2933f4ad91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Spilka?= Date: Mon, 13 Jan 2025 21:44:01 +0100 Subject: [PATCH 37/54] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: František Nesveda --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f206a67..d96735f 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ In the client settings you need to provide server configuration: "type": "sse", "url": "https://mcp-server.apify.actor/sse", "env": { - "APIFY-API-TOKEN": "your-apify-api-token" + "APIFY_API_TOKEN": "your-apify-api-token" } } } From 50e3d854a4cd7ed14025931ed2299ef4adc660d5 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Mon, 13 Jan 2025 21:56:26 +0100 Subject: [PATCH 38/54] Replace APIFY-API-TOKEN by APIFY_API_TOKEN --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d96735f..2737d20 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ Configure Claude Desktop to recognize the MCP server. "/path/to/actor-mcp-server/dist/index.js" ] "env": { - "APIFY-API-TOKEN": "your-apify-api-token" + "APIFY_API_TOKEN": "your-apify-api-token" } } } @@ -217,7 +217,7 @@ Configure Claude Desktop to recognize the MCP server. "lukaskrivka/google-maps-with-contact-details,apify/instagram-scraper" ] "env": { - "APIFY-API-TOKEN": "your-apify-api-token" + "APIFY_API_TOKEN": "your-apify-api-token" } } } From 364f49d67fd5336807211d5a6951be89bc2928fa Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Mon, 13 Jan 2025 21:58:40 +0100 Subject: [PATCH 39/54] Update package-lock.json --- package-lock.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 57cfccd..4a15498 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@anthropic-ai/tokenizer": "^0.0.4", "@apify/eslint-config": "^0.5.0-beta.2", "@apify/tsconfig": "^0.1.0", - "@types/express": "^5.0.0", + "@types/express": "^4.0.0", "@types/minimist": "^1.2.5", "dotenv": "^16.4.7", "eslint": "^9.17.0", @@ -1187,22 +1187,22 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", - "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", + "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", - "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", "dev": true, "license": "MIT", "dependencies": { From 1e7d6f8a5c6ef9f2d3aca40ed2c5c1bafcedc6f5 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 08:57:46 +0100 Subject: [PATCH 40/54] Fix clientStdio.ts for win --- src/examples/clientStdio.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts index 1f55f23..452fe58 100644 --- a/src/examples/clientStdio.ts +++ b/src/examples/clientStdio.ts @@ -10,12 +10,14 @@ import { execSync } from 'child_process'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; +import path from 'path'; import dotenv from 'dotenv'; -dotenv.config({ path: '../../.env' }); +dotenv.config({ path: path.resolve(__dirname, '../../.env') }); + +const SERVER_PATH = path.resolve(__dirname, '../../dist/index.js'); +const NODE_PATH = execSync(process.platform === 'win32' ? 'where node' : 'which node').toString().trim(); -const SERVER_PATH = '../../dist/index.js'; -const NODE_PATH = execSync('which node').toString().trim(); const TOOLS = 'apify/rag-web-browser,lukaskrivka/google-maps-with-contact-details'; const SELECTED_TOOL = 'apify/rag-web-browser'; From 5233088f3db1cb93bceaef939d66ef22413c2800 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 10:04:53 +0100 Subject: [PATCH 41/54] Fix clientStdio.ts for win --- src/examples/clientStdio.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts index 452fe58..537f316 100644 --- a/src/examples/clientStdio.ts +++ b/src/examples/clientStdio.ts @@ -6,20 +6,24 @@ */ import { execSync } from 'child_process'; +import path from 'path'; +import { fileURLToPath } from 'url'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; -import path from 'path'; import dotenv from 'dotenv'; -dotenv.config({ path: path.resolve(__dirname, '../../.env') }); +// Resolve dirname equivalent in ES module +const filename = fileURLToPath(import.meta.url); +const dirname = path.dirname(filename); -const SERVER_PATH = path.resolve(__dirname, '../../dist/index.js'); +dotenv.config({ path: path.resolve(dirname, '../../.env') }); +const SERVER_PATH = path.resolve(dirname, '../../dist/index.js'); const NODE_PATH = execSync(process.platform === 'win32' ? 'where node' : 'which node').toString().trim(); const TOOLS = 'apify/rag-web-browser,lukaskrivka/google-maps-with-contact-details'; -const SELECTED_TOOL = 'apify/rag-web-browser'; +const SELECTED_TOOL = 'apify_rag-web-browser'; // We need to change / to _ in the tool name if (!process.env.APIFY_API_TOKEN) { console.error('APIFY_API_TOKEN is required but not set in the environment variables.'); @@ -65,7 +69,7 @@ async function run() { // Call a tool console.log('Calling actor ...'); const result = await client.callTool( - { name: 'apify/rag-web-browser', arguments: { query: 'web browser for Anthropic' } }, + { name: SELECTED_TOOL, arguments: { query: 'web browser for Anthropic' } }, CallToolResultSchema, ); console.log('Tool result:', JSON.stringify(result)); From 5c9d89d90fc0e641ea40b43055407ab4f36c26f2 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 10:05:26 +0100 Subject: [PATCH 42/54] Update mcp typescript sdk to the newest version. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a15498..6500680 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.1.0", + "@modelcontextprotocol/sdk": "^1.1.1", "ajv": "^8.17.1", "apify": "^3.2.6", "apify-client": "^2.11.1", @@ -1041,9 +1041,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.1.0.tgz", - "integrity": "sha512-o5PIPz0vc1bJYXS0oLvRr8yUOzYtxEFL1rWP4aiO8qLslCksmbKhONy6CTpq0WPuIXUt2YuXoRtVA2EcLix3fw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.1.1.tgz", + "integrity": "sha512-siCApQgBn3U8R93TdumLtezRyRIlrA/a63GrTRO1jP31fRyOohpu0iPLvXzsyptxmy7B8GDxr8+r+Phu6mHgzg==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", diff --git a/package.json b/package.json index 143cfc6..b0da442 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "model context protocol" ], "dependencies": { - "@modelcontextprotocol/sdk": "^1.1.0", + "@modelcontextprotocol/sdk": "^1.1.1", "ajv": "^8.17.1", "apify": "^3.2.6", "apify-client": "^2.11.1", From 57af1564bfd8347352445f08cc13069cf2fceb73 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 12:04:41 +0100 Subject: [PATCH 43/54] Fix clientStdio.ts --- src/examples/clientStdio.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts index 537f316..61ce7f6 100644 --- a/src/examples/clientStdio.ts +++ b/src/examples/clientStdio.ts @@ -1,8 +1,9 @@ /* eslint-disable no-console */ /** * Connect to the MCP server using stdio transport and call a tool. - * You need provide a path to MCP server and APIFY_API_TOKEN in .env file. - * You can choose ACTORS to run in the server, for example: apify/rag-web-browser + * This script uses a selected tool without LLM involvement. + * You need to provide the path to the MCP server and `APIFY_API_TOKEN` in the `.env` file. + * You can choose actors to run in the server, for example: `apify/rag-web-browser`. */ import { execSync } from 'child_process'; From 0aae5c677f4807eec09597382f70107f05741607 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 13:48:05 +0100 Subject: [PATCH 44/54] Add roadmap to README.md --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2737d20..cfd0e54 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ When you have Actors integrated with the MCP server, you can ask: - "Provide a step-by-step guide on using the Model Context Protocol with source URLs." - "What Apify Actors I can use?" +In the future, we plan to load Actors dynamically and provide Apify's dataset and key-value store as resources. +See the [Roadmap](#-roadmap-january-2025) for more details. + # 🔄 What is the Model Context Protocol? The Model Context Protocol (MCP) allows AI applications (and AI agents), such as Claude Desktop, to connect to external tools and data sources. @@ -46,7 +49,6 @@ By default, the server is pre-configured with the Actors specified below, but it 'apify/instagram-scraper', 'apify/rag-web-browser', 'clockworks/free-tiktok-scraper', - 'compass/crawler-google-places', 'lukaskrivka/google-maps-with-contact-details', 'voyager/booking-scraper' ``` @@ -306,3 +308,10 @@ npx @modelcontextprotocol/inspector node /path/to/actor-mcp-server/dist/index.js ``` Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging. + +# 🚀 Roadmap (January 2025) + +- Document examples for [Superinference.ai](https://superinterface.ai/) and [LibreChat](https://www.librechat.ai/). +- Provide tools to search for Actors and load them as needed. +- Add Apify's dataset and key-value store as resources. +- Add tools such as Actor logs and Actor runs for debugging. From c8b2da449217e5432f1c5ff658ba3aaef342142e Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 13:49:28 +0100 Subject: [PATCH 45/54] Start Standby server with Actors provided at input --- .actor/input_schema.json | 16 ++++++++++++++++ src/actorDefinition.ts | 3 +++ src/examples/clientStdioChat.ts | 9 +++++++-- src/input.ts | 3 ++- src/main.ts | 12 +++++++++--- src/server.ts | 19 +++++++++---------- src/types.ts | 1 + 7 files changed, 47 insertions(+), 16 deletions(-) diff --git a/.actor/input_schema.json b/.actor/input_schema.json index 3bcfb0b..0705be7 100644 --- a/.actor/input_schema.json +++ b/.actor/input_schema.json @@ -3,6 +3,22 @@ "type": "object", "schemaVersion": 1, "properties": { + "actors": { + "title": "Actors names to be exposed for an AI application (AI agent)", + "type": "array", + "description": "Specify the names of actors that will be exposed to an AI application (AI agent) and communicate with Apify Actors via the MCP protocol", + "editor": "stringList", + "prefill": [ + "apidojo/tweet-scraper", + "apify/facebook-posts-scraper", + "apify/google-search-scraper", + "apify/instagram-scraper", + "apify/rag-web-browser", + "clockworks/free-tiktok-scraper", + "lukaskrivka/google-maps-with-contact-details", + "voyager/booking-scraper" + ] + }, "debugActor": { "title": "Debug actor", "type": "string", diff --git a/src/actorDefinition.ts b/src/actorDefinition.ts index ab23acc..bbcf73e 100644 --- a/src/actorDefinition.ts +++ b/src/actorDefinition.ts @@ -56,6 +56,8 @@ async function fetchActorDefinition(actorFullName: string): Promise} - A promise that resolves to an array of MCP tools. */ @@ -69,6 +71,7 @@ export async function getActorsAsTools(actors: string[]): Promise { try { tools.push({ name: result.name.replace('/', '_'), + actorName: result.name, description: result.description, inputSchema: result.input || {}, ajvValidate: ajv.compile(result.input || {}), diff --git a/src/examples/clientStdioChat.ts b/src/examples/clientStdioChat.ts index 2baabe7..eb3d336 100644 --- a/src/examples/clientStdioChat.ts +++ b/src/examples/clientStdioChat.ts @@ -19,6 +19,7 @@ import { execSync } from 'child_process'; import * as readline from 'readline'; +import { fileURLToPath } from "url"; import { Anthropic } from '@anthropic-ai/sdk'; import type { Message, ToolUseBlock, MessageParam } from '@anthropic-ai/sdk/resources/messages'; @@ -26,8 +27,12 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; +import path from "path"; -dotenv.config({ path: '../../.env' }); +const filename = fileURLToPath(import.meta.url); +const dirname = path.dirname(filename); + +dotenv.config({ path: path.resolve(dirname, '../../.env') }); const REQUEST_TIMEOUT = 120_000; // 2 minutes const MAX_TOKENS = 2048; // Maximum tokens for Claude response @@ -36,7 +41,7 @@ const MAX_TOKENS = 2048; // Maximum tokens for Claude response // const CLAUDE_MODEL = 'claude-3-5-haiku-20241022'; // a fastest model const CLAUDE_MODEL = 'claude-3-haiku-20240307'; // a fastest and most compact model for near-instant responsiveness const DEBUG = true; -const DEBUG_SERVER_PATH = '../../dist/index.js'; +const DEBUG_SERVER_PATH = path.resolve(dirname, '../../dist/index.js'); const NODE_PATH = execSync('which node').toString().trim(); diff --git a/src/input.ts b/src/input.ts index c90bbe7..ab51923 100644 --- a/src/input.ts +++ b/src/input.ts @@ -1,3 +1,4 @@ +import { defaults } from './const.js'; import type { Input } from './types.js'; /** @@ -6,7 +7,7 @@ import type { Input } from './types.js'; * @returns input */ export async function processInput(originalInput: Partial): Promise { - const input = originalInput as Input; + const input = { ...defaults, ...originalInput } as Input; // actors can be a string or an array of strings if (input.actors && typeof input.actors === 'string') { diff --git a/src/main.ts b/src/main.ts index d09dbe5..184feaf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -36,7 +36,9 @@ async function processParamsAndUpdateTools(url: string) { delete params.token; log.debug(`Received input parameters: ${JSON.stringify(params)}`); const input = await processInput(params as Input); - await (input.actors ? mcpServer.addToolsFromActors(input.actors as string[]) : mcpServer.addToolsFromDefaultActors()); + if (input.actors) { + await mcpServer.addToolsFromActors(input.actors as string[]); + } } app.get(Routes.ROOT, async (req: Request, res: Response) => { @@ -81,16 +83,20 @@ app.use((req: Request, res: Response) => { res.status(404).json({ message: `There is nothing at route ${req.method} ${req.originalUrl}. ${HELP_MESSAGE}` }).end(); }); +const input = await processInput((await Actor.getInput>()) ?? ({} as Input)); +log.info(`Loaded input: ${JSON.stringify(input)} `); + if (STANDBY_MODE) { log.info('Actor is running in the STANDBY mode.'); + if (input.actors && input.actors.length > 0) { + await mcpServer.addToolsFromActors(input.actors as string[]); + } app.listen(PORT, () => { log.info(`The Actor web server is listening for user requests at ${HOST}.`); }); } else { log.info('Actor is not designed to run in the NORMAL model (use this mode only for debugging purposes)'); - const input = await processInput((await Actor.getInput>()) ?? ({} as Input)); - log.info(`Loaded input: ${JSON.stringify(input)} `); if (input && !input.debugActor && !input.debugActorInput) { await Actor.fail('If you need to debug a specific actor, please provide the debugActor and debugActorInput fields in the input.'); diff --git a/src/server.ts b/src/server.ts index 1ec0ee6..8104dc0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -52,15 +52,14 @@ export class ApifyMcpServer { if (!process.env.APIFY_API_TOKEN) { throw new Error('APIFY_API_TOKEN is required but not set. Please set it as an environment variable'); } - const name = actorName.replace('_', '/'); try { - log.info(`Calling actor ${name} with input: ${JSON.stringify(input)}`); + log.info(`Calling actor ${actorName} with input: ${JSON.stringify(input)}`); const client = new ApifyClient({ token: process.env.APIFY_API_TOKEN }); - const actorClient = client.actor(name); + const actorClient = client.actor(actorName); const results = await actorClient.call(input); const dataset = await client.dataset(results.defaultDatasetId).listItems(); - log.info(`Actor ${name} finished with ${dataset.items.length} items`); + log.info(`Actor ${actorName} finished with ${dataset.items.length} items`); if (process.env.APIFY_IS_AT_HOME) { await Actor.pushData(dataset.items); @@ -68,7 +67,7 @@ export class ApifyMcpServer { } return dataset.items; } catch (error) { - log.error(`Error calling actor: ${error}. Actor: ${name}, input: ${JSON.stringify(input)}`); + log.error(`Error calling actor: ${error}. Actor: ${actorName}, input: ${JSON.stringify(input)}`); throw new Error(`Error calling actor: ${error}`); } } @@ -112,18 +111,18 @@ export class ApifyMcpServer { this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; - const tool = this.tools.get(name); + // Anthropic can't handle '/' in tool names. The replace is only necessary when calling the tool from stdio clients. + const tool = this.tools.get(name) || this.tools.get(name.replace('/', '_')); if (!tool) { throw new Error(`Unknown tool: ${name}`); } - - log.info(`Validate arguments for tool: ${name} with arguments: ${JSON.stringify(args)}`); + log.info(`Validate arguments for tool: ${tool.name} with arguments: ${JSON.stringify(args)}`); if (!tool.ajvValidate(args)) { - throw new Error(`Invalid arguments for tool ${name}: args: ${JSON.stringify(args)} error: ${JSON.stringify(tool?.ajvValidate.errors)}`); + throw new Error(`Invalid arguments for tool ${tool.name}: args: ${JSON.stringify(args)} error: ${JSON.stringify(tool?.ajvValidate.errors)}`); } try { - const items = await this.callActorGetDataset(name, args); + const items = await this.callActorGetDataset(tool.actorName, args); return { content: items.map((item) => ({ type: 'text', text: JSON.stringify(item) })) }; } catch (error) { log.error(`Error calling tool: ${error}`); diff --git a/src/types.ts b/src/types.ts index 886940d..717d740 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,6 +13,7 @@ export interface ActorDefinitionWithDesc extends ActorDefinition { export interface Tool { name: string; + actorName: string; description: string; inputSchema: object; ajvValidate: ValidateFunction; From e12cf8e3213d01fd3680ff2b61f3e43a5498328b Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 14:36:30 +0100 Subject: [PATCH 46/54] Truncate tool output and limit tool response. Also limit number of Actors used to avoid rate limiting. --- README.md | 5 ----- src/const.ts | 10 ++++------ src/examples/clientStdioChat.ts | 2 +- src/server.ts | 18 +++++++++++++++--- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index cfd0e54..7bb15e2 100644 --- a/README.md +++ b/README.md @@ -43,14 +43,9 @@ Any [Apify Actor](https://apify.com/store) can be used as a tool. By default, the server is pre-configured with the Actors specified below, but it can be overridden by providing a list of Actor names in the `actors` query parameter. ```text - 'apidojo/tweet-scraper', - 'apify/facebook-posts-scraper', - 'apify/google-search-scraper', 'apify/instagram-scraper', 'apify/rag-web-browser', - 'clockworks/free-tiktok-scraper', 'lukaskrivka/google-maps-with-contact-details', - 'voyager/booking-scraper' ``` The MCP server loads the Actor input schema and creates MCP tools corresponding to the Actors. See this example of input schema for the [RAG Web Browser](https://apify.com/apify/rag-web-browser/input-schema). diff --git a/src/const.ts b/src/const.ts index 6261d5f..f93bcb0 100644 --- a/src/const.ts +++ b/src/const.ts @@ -3,18 +3,16 @@ export const SERVER_VERSION = '0.1.0'; export const defaults = { actors: [ - 'apidojo/tweet-scraper', - 'apify/facebook-posts-scraper', - 'apify/google-search-scraper', 'apify/instagram-scraper', 'apify/rag-web-browser', - 'clockworks/free-tiktok-scraper', - 'compass/crawler-google-places', 'lukaskrivka/google-maps-with-contact-details', - 'voyager/booking-scraper', ], }; +export const ACTOR_OUTPUT_MAX_CHARS_PER_ITEM = 1_000; +export const ACTOR_OUTPUT_TRUNCATED_MESSAGE = `Output was truncated because it will not fit into context.` + + `There is no reason to call this tool again!`; + export enum Routes { ROOT = '/', SSE = '/sse', diff --git a/src/examples/clientStdioChat.ts b/src/examples/clientStdioChat.ts index eb3d336..adc9fbe 100644 --- a/src/examples/clientStdioChat.ts +++ b/src/examples/clientStdioChat.ts @@ -121,7 +121,7 @@ class MCPClient { results = await this.client.callTool(params, CallToolResultSchema, { timeout: REQUEST_TIMEOUT }); if (results.content instanceof Array && results.content.length !== 0) { const text = results.content.map((x) => x.text); - messages.push({ role: 'user', content: text.join('\n\n') }); + messages.push({ role: 'user', content: `Tool result: ${text.join('\n\n')}` }); } else { messages.push({ role: 'user', content: `No results retrieved from ${params.name}` }); } diff --git a/src/server.ts b/src/server.ts index 8104dc0..d247ca2 100644 --- a/src/server.ts +++ b/src/server.ts @@ -9,7 +9,13 @@ import { Actor } from 'apify'; import { ApifyClient } from 'apify-client'; import { getActorsAsTools } from './actorDefinition.js'; -import { defaults, SERVER_NAME, SERVER_VERSION } from './const.js'; +import { + ACTOR_OUTPUT_MAX_CHARS_PER_ITEM, + ACTOR_OUTPUT_TRUNCATED_MESSAGE, + defaults, + SERVER_NAME, + SERVER_VERSION +} from './const.js'; import { log } from './logger.js'; import type { Tool } from './types'; @@ -123,14 +129,20 @@ export class ApifyMcpServer { try { const items = await this.callActorGetDataset(tool.actorName, args); - return { content: items.map((item) => ({ type: 'text', text: JSON.stringify(item) })) }; + const content = items.map(item => { + let text = JSON.stringify(item).slice(0, ACTOR_OUTPUT_MAX_CHARS_PER_ITEM); + return text.length === ACTOR_OUTPUT_MAX_CHARS_PER_ITEM + ? { type: 'text', text: `${text} ... ${ACTOR_OUTPUT_TRUNCATED_MESSAGE}` } + : { type: 'text', text }; + }); + return { content: content }; + } catch (error) { log.error(`Error calling tool: ${error}`); throw new Error(`Error calling tool: ${error}`); } }); } - async connect(transport: Transport): Promise { await this.server.connect(transport); } From 2d474cea106ec8f0d277a927c401fabb9bc6e880 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 14:41:54 +0100 Subject: [PATCH 47/54] Limit number of default Actors --- .actor/input_schema.json | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.actor/input_schema.json b/.actor/input_schema.json index 0705be7..50d7a8c 100644 --- a/.actor/input_schema.json +++ b/.actor/input_schema.json @@ -6,17 +6,12 @@ "actors": { "title": "Actors names to be exposed for an AI application (AI agent)", "type": "array", - "description": "Specify the names of actors that will be exposed to an AI application (AI agent) and communicate with Apify Actors via the MCP protocol", + "description": "List the names of Actors to be exposed to an AI application (AI agent) for communication via the MCP protocol. \n\n Ensure the Actor definitions fit within the LLM context by limiting the number of used Actors.", "editor": "stringList", "prefill": [ - "apidojo/tweet-scraper", - "apify/facebook-posts-scraper", - "apify/google-search-scraper", "apify/instagram-scraper", "apify/rag-web-browser", - "clockworks/free-tiktok-scraper", - "lukaskrivka/google-maps-with-contact-details", - "voyager/booking-scraper" + "lukaskrivka/google-maps-with-contact-details" ] }, "debugActor": { From ca69a8832e9ee68536aa21daf796a627e26ad6fe Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 15:03:49 +0100 Subject: [PATCH 48/54] Rename APIFY_API_TOKEN to APIFY_TOKEN (env variable at Apify platform is APIFY_TOKE) --- .env.example | 2 +- README.md | 24 ++++++++++++------------ src/actorDefinition.ts | 6 +++--- src/const.ts | 4 ++-- src/examples/clientStdio.ts | 8 ++++---- src/examples/clientStdioChat.ts | 6 +++--- src/examples/client_sse.py | 2 +- src/main.ts | 4 ++-- src/server.ts | 20 ++++++++++---------- 9 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.env.example b/.env.example index 063f213..1dde9d2 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,3 @@ -APIFY_API_TOKEN= +APIFY_TOKEN= # ANTHROPIC_API_KEY is only required when you want to run examples/clientStdioChat.js ANTHROPIC_API_KEY= diff --git a/README.md b/README.md index 7bb15e2..69ef14f 100644 --- a/README.md +++ b/README.md @@ -83,12 +83,12 @@ The Actor runs in [**Standby mode**](https://docs.apify.com/platform/actors/runn Start server with default Actors. To use the Apify MCP Server with set of default Actors, send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL. ``` -https://mcp-server.apify.actor?token= +https://actors-mcp-server.apify.actor?token= ``` It is also possible to start MCP server with a different set of tools by providing a list of Actor names in the `actors` query parameter. Provide a comma-separated list of Actors in the `actors` query parameter: ``` -https://mcp-server.apify.actor?token=&actors=junglee/free-amazon-product-scraper,lukaskrivka/google-maps-with-contact-details +https://actors-mcp-server.apify.actor?token=&actors=junglee/free-amazon-product-scraper,lukaskrivka/google-maps-with-contact-details ``` Find list of all available Actors in the [Apify Store](https://apify.com/store). @@ -104,9 +104,9 @@ In the client settings you need to provide server configuration: "mcpServers": { "apify": { "type": "sse", - "url": "https://mcp-server.apify.actor/sse", + "url": "https://actors-mcp-server.apify.actor/sse", "env": { - "APIFY_API_TOKEN": "your-apify-api-token" + "APIFY_TOKEN": "your-apify-token" } } } @@ -116,7 +116,7 @@ Alternatively, you can use simple python [client_see.py](https://github.com/apif 1. Initiate Server-Sent-Events (SSE) by sending a GET request to the following URL: ``` - curl https://mcp-server.apify.actor/sse?token= + curl https://actors-mcp-server.apify.actor/sse?token= ``` The server will respond with a `sessionId`, which you can use to send messages to the server: ```shell @@ -126,7 +126,7 @@ Alternatively, you can use simple python [client_see.py](https://github.com/apif 2. Send a message to the server by making a POST request with the `sessionId`: ```shell - curl -X POST "https://mcp-server.apify.actor?token=&session_id=a1b" -H "Content-Type: application/json" -d '{ + curl -X POST "https://actors-mcp-server.apify.actor?token=&session_id=a1b" -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", @@ -158,7 +158,7 @@ Alternatively, you can use simple python [client_see.py](https://github.com/apif - MacOS or Windows - The latest version of Claude Desktop must be installed (or another MCP client) - [Node.js](https://nodejs.org/en) (v18 or higher) -- [Apify API Token](https://docs.apify.com/platform/integrations/api#api-token) (`APIFY_API_TOKEN`) +- [Apify API Token](https://docs.apify.com/platform/integrations/api#api-token) (`APIFY_TOKEN`) ### Install @@ -198,7 +198,7 @@ Configure Claude Desktop to recognize the MCP server. "/path/to/actor-mcp-server/dist/index.js" ] "env": { - "APIFY_API_TOKEN": "your-apify-api-token" + "APIFY_TOKEN": "your-apify-token" } } } @@ -214,7 +214,7 @@ Configure Claude Desktop to recognize the MCP server. "lukaskrivka/google-maps-with-contact-details,apify/instagram-scraper" ] "env": { - "APIFY_API_TOKEN": "your-apify-api-token" + "APIFY_TOKEN": "your-apify-token" } } } @@ -239,7 +239,7 @@ Configure Claude Desktop to recognize the MCP server. Create environment file `.env` with the following content: ```text -APIFY_API_TOKEN=your-apify-api-token +APIFY_TOKEN=your-apify-token # ANTHROPIC_API_KEY is only required when you want to run examples/clientStdioChat.js ANTHROPIC_API_KEY=your-anthropic-api-token ``` @@ -271,7 +271,7 @@ standard input/output (stdio): Create environment file `.env` with the following content: ```text -APIFY_API_TOKEN=your-apify-api-token +APIFY_TOKEN=your-apify-token # ANTHROPIC_API_KEY is only required when you want to run examples/clientStdioChat.js ANTHROPIC_API_KEY=your-anthropic-api-token ``` @@ -299,7 +299,7 @@ npm run build You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command: ```bash -npx @modelcontextprotocol/inspector node /path/to/actor-mcp-server/dist/index.js --env APIFY_API_TOKEN=your-apify-api-token +npx @modelcontextprotocol/inspector node /path/to/actor-mcp-server/dist/index.js --env APIFY_TOKEN=your-apify-token ``` Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging. diff --git a/src/actorDefinition.ts b/src/actorDefinition.ts index bbcf73e..1f63e92 100644 --- a/src/actorDefinition.ts +++ b/src/actorDefinition.ts @@ -12,11 +12,11 @@ import type { ActorDefinitionWithDesc, Tool } from './types'; * @returns {Promise} - The actor definition with description or null if not found. */ async function fetchActorDefinition(actorFullName: string): Promise { - if (!process.env.APIFY_API_TOKEN) { - log.error('APIFY_API_TOKEN is required but not set. Please set it as an environment variable'); + if (!process.env.APIFY_TOKEN) { + log.error('APIFY_TOKEN is required but not set. Please set it as an environment variable'); return null; } - const client = new ApifyClient({ token: process.env.APIFY_API_TOKEN }); + const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const actorClient = client.actor(actorFullName); try { diff --git a/src/const.ts b/src/const.ts index f93bcb0..7abdde3 100644 --- a/src/const.ts +++ b/src/const.ts @@ -10,8 +10,8 @@ export const defaults = { }; export const ACTOR_OUTPUT_MAX_CHARS_PER_ITEM = 1_000; -export const ACTOR_OUTPUT_TRUNCATED_MESSAGE = `Output was truncated because it will not fit into context.` + - `There is no reason to call this tool again!`; +export const ACTOR_OUTPUT_TRUNCATED_MESSAGE = `Output was truncated because it will not fit into context.` + + `There is no reason to call this tool again!`; export enum Routes { ROOT = '/', diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts index 61ce7f6..e9c9c33 100644 --- a/src/examples/clientStdio.ts +++ b/src/examples/clientStdio.ts @@ -2,7 +2,7 @@ /** * Connect to the MCP server using stdio transport and call a tool. * This script uses a selected tool without LLM involvement. - * You need to provide the path to the MCP server and `APIFY_API_TOKEN` in the `.env` file. + * You need to provide the path to the MCP server and `APIFY_TOKEN` in the `.env` file. * You can choose actors to run in the server, for example: `apify/rag-web-browser`. */ @@ -26,8 +26,8 @@ const NODE_PATH = execSync(process.platform === 'win32' ? 'where node' : 'which const TOOLS = 'apify/rag-web-browser,lukaskrivka/google-maps-with-contact-details'; const SELECTED_TOOL = 'apify_rag-web-browser'; // We need to change / to _ in the tool name -if (!process.env.APIFY_API_TOKEN) { - console.error('APIFY_API_TOKEN is required but not set in the environment variables.'); +if (!process.env.APIFY_TOKEN) { + console.error('APIFY_TOKEN is required but not set in the environment variables.'); process.exit(1); } @@ -35,7 +35,7 @@ if (!process.env.APIFY_API_TOKEN) { const transport = new StdioClientTransport({ command: NODE_PATH, args: [SERVER_PATH, '--actors', TOOLS], - env: { APIFY_API_TOKEN: process.env.APIFY_API_TOKEN || '' }, + env: { APIFY_TOKEN: process.env.APIFY_TOKEN || '' }, }); // Create a new client instance diff --git a/src/examples/clientStdioChat.ts b/src/examples/clientStdioChat.ts index adc9fbe..1ebdf60 100644 --- a/src/examples/clientStdioChat.ts +++ b/src/examples/clientStdioChat.ts @@ -18,8 +18,9 @@ */ import { execSync } from 'child_process'; +import path from 'path'; import * as readline from 'readline'; -import { fileURLToPath } from "url"; +import { fileURLToPath } from 'url'; import { Anthropic } from '@anthropic-ai/sdk'; import type { Message, ToolUseBlock, MessageParam } from '@anthropic-ai/sdk/resources/messages'; @@ -27,7 +28,6 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; -import path from "path"; const filename = fileURLToPath(import.meta.url); const dirname = path.dirname(filename); @@ -79,7 +79,7 @@ class MCPClient { const transport = new StdioClientTransport({ command: NODE_PATH, args: serverArgs, - env: { APIFY_API_TOKEN: process.env.APIFY_API_TOKEN || '' }, + env: { APIFY_TOKEN: process.env.APIFY_TOKEN || '' }, }); await this.client.connect(transport); diff --git a/src/examples/client_sse.py b/src/examples/client_sse.py index 4a22d8f..e151917 100644 --- a/src/examples/client_sse.py +++ b/src/examples/client_sse.py @@ -20,7 +20,7 @@ MCP_SERVER_URL = "https://mcp-server.apify.actor" ACTORS = "apify/rag-web-browser" -HEADERS = {"Authorization": f"Bearer {os.getenv('APIFY_API_TOKEN')}"} +HEADERS = {"Authorization": f"Bearer {os.getenv('APIFY_TOKEN')}"} async def run() -> None: diff --git a/src/main.ts b/src/main.ts index 184feaf..987d119 100644 --- a/src/main.ts +++ b/src/main.ts @@ -23,8 +23,8 @@ const app = express(); const mcpServer = new ApifyMcpServer(); let transport: SSEServerTransport; -const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse?token=YOUR-APIFY-API-TOKEN` - + ` and then send POST requests to ${HOST}/message?token=YOUR-APIFY-API-TOKEN.`; +const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse?token=YOUR-APIFY-TOKEN` + + ` and then send POST requests to ${HOST}/message?token=YOUR-APIFY-TOKEN.`; /** * Process input parameters and update tools diff --git a/src/server.ts b/src/server.ts index d247ca2..fc1df82 100644 --- a/src/server.ts +++ b/src/server.ts @@ -14,7 +14,7 @@ import { ACTOR_OUTPUT_TRUNCATED_MESSAGE, defaults, SERVER_NAME, - SERVER_VERSION + SERVER_VERSION, } from './const.js'; import { log } from './logger.js'; import type { Tool } from './types'; @@ -46,21 +46,21 @@ export class ApifyMcpServer { /** * Calls an Apify actor and retrieves the dataset items. * - * It requires the `APIFY_API_TOKEN` environment variable to be set. + * It requires the `APIFY_TOKEN` environment variable to be set. * If the `APIFY_IS_AT_HOME` the dataset items are pushed to the Apify dataset. * * @param {string} actorName - The name of the actor to call. * @param {unknown} input - The input to pass to the actor. * @returns {Promise} - A promise that resolves to an array of dataset items. - * @throws {Error} - Throws an error if the `APIFY_API_TOKEN` is not set + * @throws {Error} - Throws an error if the `APIFY_TOKEN` is not set */ public async callActorGetDataset(actorName: string, input: unknown): Promise { - if (!process.env.APIFY_API_TOKEN) { - throw new Error('APIFY_API_TOKEN is required but not set. Please set it as an environment variable'); + if (!process.env.APIFY_TOKEN) { + throw new Error('APIFY_TOKEN is required but not set. Please set it as an environment variable'); } try { log.info(`Calling actor ${actorName} with input: ${JSON.stringify(input)}`); - const client = new ApifyClient({ token: process.env.APIFY_API_TOKEN }); + const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const actorClient = client.actor(actorName); const results = await actorClient.call(input); @@ -129,20 +129,20 @@ export class ApifyMcpServer { try { const items = await this.callActorGetDataset(tool.actorName, args); - const content = items.map(item => { - let text = JSON.stringify(item).slice(0, ACTOR_OUTPUT_MAX_CHARS_PER_ITEM); + const content = items.map((item) => { + const text = JSON.stringify(item).slice(0, ACTOR_OUTPUT_MAX_CHARS_PER_ITEM); return text.length === ACTOR_OUTPUT_MAX_CHARS_PER_ITEM ? { type: 'text', text: `${text} ... ${ACTOR_OUTPUT_TRUNCATED_MESSAGE}` } : { type: 'text', text }; }); - return { content: content }; - + return { content }; } catch (error) { log.error(`Error calling tool: ${error}`); throw new Error(`Error calling tool: ${error}`); } }); } + async connect(transport: Transport): Promise { await this.server.connect(transport); } From b84651ed2dce879be8f6075c589032fb5cd6a2d0 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 16:03:43 +0100 Subject: [PATCH 49/54] Minor changes, add clientSse.ts --- README.md | 1 + src/const.ts | 4 +- src/examples/clientSse.ts | 100 +++++++++++++++++++++++++++++++++++++ src/examples/client_sse.py | 12 ++--- 4 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 src/examples/clientSse.ts diff --git a/README.md b/README.md index 69ef14f..af7d6d5 100644 --- a/README.md +++ b/README.md @@ -310,3 +310,4 @@ Upon launching, the Inspector will display a URL that you can access in your bro - Provide tools to search for Actors and load them as needed. - Add Apify's dataset and key-value store as resources. - Add tools such as Actor logs and Actor runs for debugging. +- Prune Actors input schema to reduce context size. diff --git a/src/const.ts b/src/const.ts index 7abdde3..cdbae1d 100644 --- a/src/const.ts +++ b/src/const.ts @@ -9,9 +9,9 @@ export const defaults = { ], }; -export const ACTOR_OUTPUT_MAX_CHARS_PER_ITEM = 1_000; +export const ACTOR_OUTPUT_MAX_CHARS_PER_ITEM = 2_000; export const ACTOR_OUTPUT_TRUNCATED_MESSAGE = `Output was truncated because it will not fit into context.` - + `There is no reason to call this tool again!`; + + ` There is no reason to call this tool again!`; export enum Routes { ROOT = '/', diff --git a/src/examples/clientSse.ts b/src/examples/clientSse.ts new file mode 100644 index 0000000..cac6971 --- /dev/null +++ b/src/examples/clientSse.ts @@ -0,0 +1,100 @@ +/* eslint-disable no-console */ +/** + * Connect to the MCP server using SSE transport and call a tool. + * The Actors MCP Server will load default Actors. + * + * !!! This example needs to be fixed as it does not work !!! + */ + +import path from 'path'; +import { fileURLToPath } from 'url'; + +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; +import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; +import dotenv from 'dotenv'; +import { EventSource } from 'eventsource'; + +// Resolve dirname equivalent in ES module +const filename = fileURLToPath(import.meta.url); +const dirname = path.dirname(filename); + +dotenv.config({ path: path.resolve(dirname, '../../.env') }); + +const SERVER_URL = 'https://actors-mcp-server/sse'; +// We need to change forward slash / to underscore _ in the tool name as Anthropic does not allow forward slashes in the tool name +const SELECTED_TOOL = 'apify_rag-web-browser'; + +if (!process.env.APIFY_TOKEN) { + console.error('APIFY_TOKEN is required but not set in the environment variables.'); + process.exit(1); +} + +if (typeof globalThis.EventSource === 'undefined') { + globalThis.EventSource = EventSource as unknown as typeof globalThis.EventSource; +} + +async function main(): Promise { + const transport = new SSEClientTransport( + new URL(SERVER_URL), + { + requestInit: { + headers: { + authorization: `Bearer ${process.env.APIFY_TOKEN}`, + }, + }, + eventSourceInit: { + // The EventSource package augments EventSourceInit with a "fetch" parameter. + // You can use this to set additional headers on the outgoing request. + // Based on this example: https://github.com/modelcontextprotocol/typescript-sdk/issues/118 + async fetch(input: Request | URL | string, init?: RequestInit) { + const headers = new Headers(init?.headers || {}); + headers.set('authorization', `Bearer ${process.env.APIFY_TOKEN}`); + return fetch(input, { ...init, headers }); + }, + // We have to cast to "any" to use it, since it's non-standard + } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + }, + ); + const client = new Client( + { name: 'example-client', version: '1.0.0' }, + { capabilities: {} }, + ); + + try { + // Connect to the MCP server + await client.connect(transport); + + // List available tools + const tools = await client.listTools(); + console.log('Available tools:', tools); + + if (tools.tools.length === 0) { + console.log('No tools available'); + return; + } + + const selectedTool = tools.tools.find((tool) => tool.name === SELECTED_TOOL); + if (!selectedTool) { + console.error(`The specified tool: ${selectedTool} is not available. Exiting.`); + return; + } + + // Call a tool + console.log('Calling actor ...'); + const result = await client.callTool( + { name: SELECTED_TOOL, arguments: { query: 'web browser for Anthropic' } }, + CallToolResultSchema, + ); + console.log('Tool result:', JSON.stringify(result, null, 2)); + } catch (error: unknown) { + if (error instanceof Error) { + console.error('Error:', error.message); + console.error(error.stack); + } else { + console.error('An unknown error occurred:', error); + } + } +} + +await main(); diff --git a/src/examples/client_sse.py b/src/examples/client_sse.py index e151917..117fa25 100644 --- a/src/examples/client_sse.py +++ b/src/examples/client_sse.py @@ -9,23 +9,23 @@ import asyncio import os +from pathlib import Path import requests from dotenv import load_dotenv from mcp.client.session import ClientSession from mcp.client.sse import sse_client -load_dotenv(dotenv_path="../../.env") +load_dotenv(Path(__file__).resolve().parent.parent.parent / ".env") -MCP_SERVER_URL = "https://mcp-server.apify.actor" -ACTORS = "apify/rag-web-browser" +MCP_SERVER_URL = "https://actors-mcp-server.apify.actor" HEADERS = {"Authorization": f"Bearer {os.getenv('APIFY_TOKEN')}"} async def run() -> None: - print("Start MCP Server with Actors", ACTORS) - r = requests.get(MCP_SERVER_URL, params={"actors": ACTORS}, headers=HEADERS) + print("Start MCP Server with Actors",) + r = requests.get(MCP_SERVER_URL, headers=HEADERS) print("MCP Server Response:", r.json(), end="\n\n") async with sse_client(url=f"{MCP_SERVER_URL}/sse", timeout=60, headers=HEADERS) as (read, write): @@ -36,7 +36,7 @@ async def run() -> None: print("Available Tools:", tools, end="\n\n") if hasattr(tools, "tools") and not tools.tools: - print("No tools available! Start MCP server with Actors") + print("No tools available!") return result = await session.call_tool("apify/rag-web-browser", { "query": "example.com", "maxResults": 3 }) From 479e9be87333b9c056a394d4880284e3d32219a1 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 16:20:27 +0100 Subject: [PATCH 50/54] Update README.md with task changes. --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index af7d6d5..608f589 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ MCP is an open protocol that enables secure, controlled interactions between AI ## Tools Any [Apify Actor](https://apify.com/store) can be used as a tool. -By default, the server is pre-configured with the Actors specified below, but it can be overridden by providing a list of Actor names in the `actors` query parameter. +By default, the server is pre-configured with the Actors specified below, but it can be overridden by providing Actor input. ```text 'apify/instagram-scraper', @@ -85,12 +85,15 @@ send an HTTP GET request with your [Apify API token](https://console.apify.com/s ``` https://actors-mcp-server.apify.actor?token= ``` -It is also possible to start MCP server with a different set of tools by providing a list of Actor names in the `actors` query parameter. -Provide a comma-separated list of Actors in the `actors` query parameter: -``` -https://actors-mcp-server.apify.actor?token=&actors=junglee/free-amazon-product-scraper,lukaskrivka/google-maps-with-contact-details +It is also possible to start the MCP server with a different set of Actors. +To do this, create a [task](https://docs.apify.com/platform/actors/running/tasks) and specify the list of Actors you want to use. + +Then, run task in Standby mode with the selected Actors using your Apify API token. +```shell +https://actors-mcp-server-task.apify.actor?token= ``` -Find list of all available Actors in the [Apify Store](https://apify.com/store). + +You can find a list of all available Actors in the [Apify Store](https://apify.com/store). #### 💬 Interact with the MCP Server From f39dfc535014b5b210f8bff3969ea31c03dcacdb Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 16:32:09 +0100 Subject: [PATCH 51/54] Add explanation to Actor definition --- src/actorDefinition.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/actorDefinition.ts b/src/actorDefinition.ts index 1f63e92..37d5417 100644 --- a/src/actorDefinition.ts +++ b/src/actorDefinition.ts @@ -27,7 +27,9 @@ async function fetchActorDefinition(actorFullName: string): Promise { } return tools; } + +fetchActorDefinition('apify/rag-web-browser'); From d6e03fc39ee50689ebc5a859d46945f96f5f041c Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 15 Jan 2025 16:40:51 +0100 Subject: [PATCH 52/54] Fix lint issues --- src/actorDefinition.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/actorDefinition.ts b/src/actorDefinition.ts index 37d5417..42b147b 100644 --- a/src/actorDefinition.ts +++ b/src/actorDefinition.ts @@ -85,5 +85,3 @@ export async function getActorsAsTools(actors: string[]): Promise { } return tools; } - -fetchActorDefinition('apify/rag-web-browser'); From 3a5be34a563a80f18e50edb7a71282c022e70a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Spilka?= Date: Thu, 16 Jan 2025 11:52:21 +0100 Subject: [PATCH 53/54] Update .actor/input_schema.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: František Nesveda --- .actor/input_schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.actor/input_schema.json b/.actor/input_schema.json index 50d7a8c..86aa7a4 100644 --- a/.actor/input_schema.json +++ b/.actor/input_schema.json @@ -4,7 +4,7 @@ "schemaVersion": 1, "properties": { "actors": { - "title": "Actors names to be exposed for an AI application (AI agent)", + "title": "Actors to be exposed for an AI application (AI agent)", "type": "array", "description": "List the names of Actors to be exposed to an AI application (AI agent) for communication via the MCP protocol. \n\n Ensure the Actor definitions fit within the LLM context by limiting the number of used Actors.", "editor": "stringList", From fc40b596fe7bd277fbf1fbefeb8b5fefcf406578 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Thu, 16 Jan 2025 13:31:41 +0100 Subject: [PATCH 54/54] Add log message to explain users how to add Actors. Simplify handling input. --- .actor/input_schema.json | 10 +++++----- README.md | 6 +++--- src/examples/client_sse.py | 2 +- src/input.ts | 3 +-- src/main.ts | 8 ++++---- src/server.ts | 4 ++++ 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/.actor/input_schema.json b/.actor/input_schema.json index 86aa7a4..4616796 100644 --- a/.actor/input_schema.json +++ b/.actor/input_schema.json @@ -6,7 +6,7 @@ "actors": { "title": "Actors to be exposed for an AI application (AI agent)", "type": "array", - "description": "List the names of Actors to be exposed to an AI application (AI agent) for communication via the MCP protocol. \n\n Ensure the Actor definitions fit within the LLM context by limiting the number of used Actors.", + "description": "List Actors to be exposed to an AI application (AI agent) for communication via the MCP protocol. \n\n Ensure the Actor definitions fit within the LLM context by limiting the number of used Actors.", "editor": "stringList", "prefill": [ "apify/instagram-scraper", @@ -15,17 +15,17 @@ ] }, "debugActor": { - "title": "Debug actor", + "title": "Debug Actor", "type": "string", - "description": "Specify the name of the actor that will be used for debugging in normal mode", + "description": "Specify the name of the Actor that will be used for debugging in normal mode", "editor": "textfield", "prefill": "apify/rag-web-browser", "sectionCaption": "Debugging settings (normal mode)" }, "debugActorInput": { - "title": "Debug actor input", + "title": "Debug Actor input", "type": "object", - "description": "Specify the input for the actor that will be used for debugging in normal mode", + "description": "Specify the input for the Actor that will be used for debugging in normal mode", "editor": "json", "prefill": { "query": "hello world" diff --git a/README.md b/README.md index 608f589..47615ea 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ Any [Apify Actor](https://apify.com/store) can be used as a tool. By default, the server is pre-configured with the Actors specified below, but it can be overridden by providing Actor input. ```text - 'apify/instagram-scraper', - 'apify/rag-web-browser', - 'lukaskrivka/google-maps-with-contact-details', +'apify/instagram-scraper' +'apify/rag-web-browser' +'lukaskrivka/google-maps-with-contact-details' ``` The MCP server loads the Actor input schema and creates MCP tools corresponding to the Actors. See this example of input schema for the [RAG Web Browser](https://apify.com/apify/rag-web-browser/input-schema). diff --git a/src/examples/client_sse.py b/src/examples/client_sse.py index 117fa25..76c3c53 100644 --- a/src/examples/client_sse.py +++ b/src/examples/client_sse.py @@ -24,7 +24,7 @@ async def run() -> None: - print("Start MCP Server with Actors",) + print("Start MCP Server with Actors") r = requests.get(MCP_SERVER_URL, headers=HEADERS) print("MCP Server Response:", r.json(), end="\n\n") diff --git a/src/input.ts b/src/input.ts index ab51923..c90bbe7 100644 --- a/src/input.ts +++ b/src/input.ts @@ -1,4 +1,3 @@ -import { defaults } from './const.js'; import type { Input } from './types.js'; /** @@ -7,7 +6,7 @@ import type { Input } from './types.js'; * @returns input */ export async function processInput(originalInput: Partial): Promise { - const input = { ...defaults, ...originalInput } as Input; + const input = originalInput as Input; // actors can be a string or an array of strings if (input.actors && typeof input.actors === 'string') { diff --git a/src/main.ts b/src/main.ts index 987d119..db1bccd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -38,6 +38,9 @@ async function processParamsAndUpdateTools(url: string) { const input = await processInput(params as Input); if (input.actors) { await mcpServer.addToolsFromActors(input.actors as string[]); + } else { + log.debug(`Server is running in STANDBY mode with the following Actors (tools): ${mcpServer.getToolNames()}. + To use different Actors, provide them in query parameter "actors" or include them in the Actor Task input.`); } } @@ -88,10 +91,7 @@ log.info(`Loaded input: ${JSON.stringify(input)} `); if (STANDBY_MODE) { log.info('Actor is running in the STANDBY mode.'); - if (input.actors && input.actors.length > 0) { - await mcpServer.addToolsFromActors(input.actors as string[]); - } - + await mcpServer.addToolsFromDefaultActors(); app.listen(PORT, () => { log.info(`The Actor web server is listening for user requests at ${HOST}.`); }); diff --git a/src/server.ts b/src/server.ts index fc1df82..1e54fdf 100644 --- a/src/server.ts +++ b/src/server.ts @@ -94,6 +94,10 @@ export class ApifyMcpServer { } } + public getToolNames(): string[] { + return Array.from(this.tools.keys()); + } + private setupErrorHandling(): void { this.server.onerror = (error) => { console.error('[MCP Error]', error); // eslint-disable-line no-console