diff --git a/README.md b/README.md index ab51715..de3773a 100644 --- a/README.md +++ b/README.md @@ -77,56 +77,119 @@ npm run build ## Configuration -A Discord bot token is required for proper operation. You can provide it in two ways: +A Discord bot token is required for proper operation. The server supports two transport methods: stdio and streamable HTTP. + +### Transport Methods + +1. **stdio** (Default) + - Traditional stdio transport for basic usage + - Suitable for simple integrations + +2. **streamable HTTP** + - HTTP-based transport for more advanced scenarios + - Supports stateless operation + - Configurable port number + - Powered by Smithery SDK for improved reliability and performance + +### Configuration Options + +You can provide configuration in two ways: 1. Environment variables: -``` +```bash DISCORD_TOKEN=your_discord_bot_token ``` -2. Using the `--config` parameter when launching: -``` -node path/to/mcp-discord/build/index.js --config "{\"DISCORD_TOKEN\":\"your_discord_bot_token\"}" +2. Using command line arguments: +```bash +# For stdio transport (default) +node build/index.js --config "your_discord_bot_token" + +# For streamable HTTP transport +node build/index.js --transport http --port 3000 --config "your_discord_bot_token" ``` ## Usage with Claude/Cursor -- Claude - - ```json - { - "mcpServers": { - "discord": { - "command": "node", - "args": [ - "path/to/mcp-discord/build/index.js" - ], - "env": { - "DISCORD_TOKEN": "your_discord_bot_token" - } - } + +### Claude + +1. Using stdio transport: +```json +{ + "mcpServers": { + "discord": { + "command": "node", + "args": [ + "path/to/mcp-discord/build/index.js", + "--config", + "your_discord_bot_token" + ] } } - ``` +} +``` -- Cursor - - ```json - { - "mcpServers": { - "discord": { - "command": "cmd", - "args": [ - "/c", - "node", - "path/to/mcp-discord/build/index.js" - ], - "env": { - "DISCORD_TOKEN": "your_discord_bot_token" - } - } - } +2. Using streamable HTTP transport: +```json +{ + "mcpServers": { + "discord": { + "command": "node", + "args": [ + "path/to/mcp-discord/build/index.js", + "--transport", + "http", + "--port", + "3000", + "--config", + "your_discord_bot_token" + ] + } } - ``` +} +``` + +### Cursor + +1. Using stdio transport: +```json +{ + "mcpServers": { + "discord": { + "command": "cmd", + "args": [ + "/c", + "node", + "path/to/mcp-discord/build/index.js", + "--config", + "your_discord_bot_token" + ] + } + } +} +``` + +2. Using streamable HTTP transport: +```json +{ + "mcpServers": { + "discord": { + "command": "cmd", + "args": [ + "/c", + "node", + "path/to/mcp-discord/build/index.js", + "--transport", + "http", + "--port", + "3000", + "--config", + "your_discord_bot_token" + ] + } + } +} +``` ## Tools Documentation diff --git a/package-lock.json b/package-lock.json index f2b9f26..a2658ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,23 +1,28 @@ { "name": "mcp-discord", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mcp-discord", - "version": "1.1.0", + "version": "1.2.0", "license": "MIT", "dependencies": { "discord.js": "^14.18.0", - "dotenv": "^16.4.7", + "dotenv": "^16.5.0", + "express": "^5.1.0", "zod": "^3.24.2" }, + "bin": { + "mcp-discord": "build/index.js" + }, "devDependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", + "@modelcontextprotocol/sdk": "^1.11.0", + "@types/express": "^5.0.1", "@types/node": "^20.17.26", "ts-node": "^10.9.2", - "typescript": "^5.8.2" + "typescript": "^5.8.3" } }, "node_modules/@cspotcode/source-map-support": { @@ -189,9 +194,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz", - "integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.0.tgz", + "integrity": "sha512-k/1pb70eD638anoi0e8wUGAlbMJXyvdV4p62Ko+EZ7eBe1xMx8Uhak1R5DgfoofsK5IBBnRwsYGTaLZl+6/+RQ==", "dev": true, "license": "MIT", "dependencies": { @@ -201,7 +206,7 @@ "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", - "pkce-challenge": "^4.1.0", + "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" @@ -271,6 +276,66 @@ "dev": true, "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/express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", + "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "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/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": "20.17.26", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.26.tgz", @@ -280,6 +345,43 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.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/@types/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz", @@ -303,7 +405,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -347,17 +448,16 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz", - "integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", - "iconv-lite": "^0.5.2", + "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", @@ -367,52 +467,10 @@ "node": ">=18" } }, - "node_modules/body-parser/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/body-parser/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -422,7 +480,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -436,7 +493,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -453,7 +509,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -466,7 +521,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -476,7 +530,6 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -486,7 +539,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.6.0" @@ -529,13 +581,12 @@ } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -550,23 +601,11 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -610,9 +649,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -625,7 +664,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -640,14 +678,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -657,7 +693,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -667,7 +702,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -677,7 +711,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -690,14 +723,12 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -727,47 +758,45 @@ } }, "node_modules/express": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", - "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", - "dev": true, + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.0.1", + "body-parser": "^2.2.0", "content-disposition": "^1.0.0", - "content-type": "~1.0.4", - "cookie": "0.7.1", + "content-type": "^1.0.5", + "cookie": "^0.7.1", "cookie-signature": "^1.2.1", - "debug": "4.3.6", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "^2.0.0", - "fresh": "2.0.0", - "http-errors": "2.0.0", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", - "methods": "~1.1.2", "mime-types": "^3.0.0", - "on-finished": "2.4.1", - "once": "1.4.0", - "parseurl": "~1.3.3", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "router": "^2.0.0", - "safe-buffer": "5.2.1", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", "send": "^1.1.0", - "serve-static": "^2.1.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "^2.0.0", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-rate-limit": { @@ -796,7 +825,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "dev": true, "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -810,36 +838,10 @@ "node": ">= 0.8" } }, - "node_modules/finalhandler/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/finalhandler/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==", - "dev": true, - "license": "MIT" - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -849,7 +851,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -859,7 +860,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -869,7 +869,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -894,7 +893,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -908,7 +906,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -921,7 +918,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -934,7 +930,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -947,7 +942,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, "license": "MIT", "dependencies": { "depd": "2.0.0", @@ -961,13 +955,12 @@ } }, "node_modules/iconv-lite": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", - "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", - "dev": true, + "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" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -977,14 +970,12 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "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==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.10" @@ -994,7 +985,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -1033,7 +1023,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -1043,7 +1032,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -1053,7 +1041,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -1062,51 +1049,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", - "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", - "dev": true, + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", "dependencies": { - "mime-db": "^1.53.0" + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, + "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/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -1126,7 +1099,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -1139,7 +1111,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -1152,7 +1123,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -1162,7 +1132,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -1182,16 +1151,15 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=16" } }, "node_modules/pkce-challenge": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz", - "integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", "dev": true, "license": "MIT", "engines": { @@ -1202,7 +1170,6 @@ "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==", - "dev": true, "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -1213,13 +1180,12 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -1232,7 +1198,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -1242,7 +1207,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -1254,26 +1218,14 @@ "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/router": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz", - "integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" @@ -1286,7 +1238,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -1307,24 +1258,21 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/send": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", - "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", - "dev": true, + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "license": "MIT", "dependencies": { "debug": "^4.3.5", - "destroy": "^1.2.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", - "fresh": "^0.5.2", + "fresh": "^2.0.0", "http-errors": "^2.0.0", - "mime-types": "^2.1.35", + "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", @@ -1334,57 +1282,16 @@ "node": ">= 18" } }, - "node_modules/send/node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/serve-static": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", - "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", - "send": "^1.0.0" + "send": "^1.2.0" }, "engines": { "node": ">= 18" @@ -1394,7 +1301,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, "license": "ISC" }, "node_modules/shebang-command": { @@ -1424,7 +1330,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1444,7 +1349,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1461,7 +1365,6 @@ "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==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -1480,7 +1383,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -1500,7 +1402,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -1510,7 +1411,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.6" @@ -1573,10 +1473,9 @@ "license": "0BSD" }, "node_modules/type-is": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", - "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -1588,9 +1487,9 @@ } }, "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1620,22 +1519,11 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -1647,7 +1535,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -1673,7 +1560,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/ws": { diff --git a/package.json b/package.json index e1f503c..3bad6e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-discord", - "version": "1.1.0", + "version": "1.2.0", "main": "build/index.js", "bin": { "mcp-discord": "build/index.js" @@ -10,20 +10,23 @@ "test": "echo \"Error: no test specified\" && exit 1", "build": "tsc", "start": "node build/index.js", - "dev": "node --loader ts-node/esm src/index.ts" + "dev": "node --loader ts-node/esm src/index.ts", + "test-api": "node test-api.js" }, "author": "", "license": "MIT", "description": "", "devDependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", + "@modelcontextprotocol/sdk": "^1.11.0", + "@types/express": "^5.0.1", "@types/node": "^20.17.26", "ts-node": "^10.9.2", - "typescript": "^5.8.2" + "typescript": "^5.8.3" }, "dependencies": { "discord.js": "^14.18.0", - "dotenv": "^16.4.7", + "dotenv": "^16.5.0", + "express": "^5.1.0", "zod": "^3.24.2" } } diff --git a/smithery.yaml b/smithery.yaml index 643df1e..a6a8377 100644 --- a/smithery.yaml +++ b/smithery.yaml @@ -1,9 +1,9 @@ # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml -startCommand: - type: stdio +version: 1 +start: + type: http configSchema: - # JSON Schema defining the configuration options for the MCP. type: object required: - discordToken @@ -11,13 +11,27 @@ startCommand: discordToken: type: string description: Discord bot token. Obtain this from the Discord Developer Portal. - commandFunction: - # A JS function that produces the CLI command based on the given config to start the MCP on stdio. - |- - (config) => ({ - command: 'node', - args: ['build/index.js'], - env: { DISCORD_TOKEN: config.discordToken } - }) + port: + type: number + description: Port number for the HTTP server + default: 3000 + command: + function: | + (config) => ({ + command: 'node', + args: [ + 'build/index.js', + '--transport', + 'http', + '--port', + config.port || 3000, + '--config', + config.discordToken + ], + env: { + NODE_ENV: 'production' + } + }) exampleConfig: discordToken: YOUR_DISCORD_BOT_TOKEN + port: 3000 diff --git a/src/index.ts b/src/index.ts index e339e4c..30a0a49 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,73 +1,60 @@ -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { z } from "zod"; import { Client, GatewayIntentBits } from "discord.js"; -import { - CallToolRequestSchema, - ListToolsRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; -import { toolList } from './toolList.js'; -import { - createToolContext, - loginHandler, - sendMessageHandler, - getForumChannelsHandler, - createForumPostHandler, - getForumPostHandler, - replyToForumHandler, - deleteForumPostHandler, - createTextChannelHandler, - deleteChannelHandler, - readMessagesHandler, - getServerInfoHandler, - addReactionHandler, - addMultipleReactionsHandler, - removeReactionHandler, - deleteMessageHandler, - createWebhookHandler, - sendWebhookMessageHandler, - editWebhookHandler, - deleteWebhookHandler -} from './tools/tools.js'; +import { config as dotenvConfig } from 'dotenv'; +import { DiscordMCPServer } from './server.js'; +import { StdioTransport, StreamableHttpTransport } from './transport.js'; -// Configuration parsing -let config: any = {}; +// Load environment variables from .env file if exists +dotenvConfig(); -// Read configuration from environment variables -if (process.env.DISCORD_TOKEN) { - config.DISCORD_TOKEN = process.env.DISCORD_TOKEN; - console.log("Config loaded from environment variables. Discord token available:", !!config.DISCORD_TOKEN); - if (config.DISCORD_TOKEN) { - console.log("Token length:", config.DISCORD_TOKEN.length); - } -} else { - // Try to parse configuration from command line arguments (for backward compatibility) - const configArgIndex = process.argv.indexOf('--config'); - if (configArgIndex !== -1 && configArgIndex < process.argv.length - 1) { +// Configuration with priority for command line arguments +const config = { + DISCORD_TOKEN: (() => { try { - let configStr = process.argv[configArgIndex + 1]; - - // Print raw configuration string for debugging - console.log("Raw config string:", configStr); - - // Try to parse JSON - config = JSON.parse(configStr); - console.log("Config parsed successfully. Discord token available:", !!config.DISCORD_TOKEN); - - if (config.DISCORD_TOKEN) { - console.log("Token length:", config.DISCORD_TOKEN.length); + // First try to get from command line arguments + const configIndex = process.argv.indexOf('--config'); + if (configIndex !== -1 && configIndex + 1 < process.argv.length) { + const configArg = process.argv[configIndex + 1]; + // Handle both string and object formats + if (typeof configArg === 'string') { + try { + const parsedConfig = JSON.parse(configArg); + return parsedConfig.DISCORD_TOKEN; + } catch (e) { + // If not valid JSON, try using the string directly + return configArg; + } + } } + // Then try environment variable + return process.env.DISCORD_TOKEN; } catch (error) { - console.error("Failed to parse config argument:", error); - console.error("Raw config argument:", process.argv[configArgIndex + 1]); - - // Try to read arguments directly (for debugging) - console.log("All arguments:", process.argv); + console.error('Error parsing config:', error); + return null; } - } else { - console.warn("No config found in environment variables or command line arguments"); - console.log("All arguments:", process.argv); - } + })(), + TRANSPORT: (() => { + // Check for transport type argument + const transportIndex = process.argv.indexOf('--transport'); + if (transportIndex !== -1 && transportIndex + 1 < process.argv.length) { + return process.argv[transportIndex + 1]; + } + // Default to stdio + return 'stdio'; + })(), + HTTP_PORT: (() => { + // Check for port argument + const portIndex = process.argv.indexOf('--port'); + if (portIndex !== -1 && portIndex + 1 < process.argv.length) { + return parseInt(process.argv[portIndex + 1]); + } + // Default port + return 3000; + })() +}; + +if (!config.DISCORD_TOKEN) { + console.error('Discord token not found. Please provide it via --config argument or environment variable.'); + process.exit(1); } // Create Discord client @@ -84,141 +71,13 @@ if (config.DISCORD_TOKEN) { client.token = config.DISCORD_TOKEN; } -// Create an MCP server -const server = new Server( - { - name: "MCP-Discord", - version: "1.0.0" - }, - { - capabilities: { - tools: {} - } - } -); - -// Set up the tool list -server.setRequestHandler(ListToolsRequestSchema, async () => { - return { - tools: toolList - }; -}); - -// Create tool context -const toolContext = createToolContext(client); - -// Handle tool execution requests -server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - try { - let toolResponse; - switch (name) { - case "discord_login": - toolResponse = await loginHandler(args, toolContext); - return toolResponse; - - case "discord_send": - toolResponse = await sendMessageHandler(args, toolContext); - return toolResponse; - - case "discord_get_forum_channels": - toolResponse = await getForumChannelsHandler(args, toolContext); - return toolResponse; - - case "discord_create_forum_post": - toolResponse = await createForumPostHandler(args, toolContext); - return toolResponse; - - case "discord_get_forum_post": - toolResponse = await getForumPostHandler(args, toolContext); - return toolResponse; - - case "discord_reply_to_forum": - toolResponse = await replyToForumHandler(args, toolContext); - return toolResponse; - - case "discord_delete_forum_post": - toolResponse = await deleteForumPostHandler(args, toolContext); - return toolResponse; - - case "discord_create_text_channel": - toolResponse = await createTextChannelHandler(args, toolContext); - return toolResponse; - - case "discord_delete_channel": - toolResponse = await deleteChannelHandler(args, toolContext); - return toolResponse; - - case "discord_read_messages": - toolResponse = await readMessagesHandler(args, toolContext); - return toolResponse; - - case "discord_get_server_info": - toolResponse = await getServerInfoHandler(args, toolContext); - return toolResponse; - - case "discord_add_reaction": - toolResponse = await addReactionHandler(args, toolContext); - return toolResponse; - - case "discord_add_multiple_reactions": - toolResponse = await addMultipleReactionsHandler(args, toolContext); - return toolResponse; - - case "discord_remove_reaction": - toolResponse = await removeReactionHandler(args, toolContext); - return toolResponse; - - case "discord_delete_message": - toolResponse = await deleteMessageHandler(args, toolContext); - return toolResponse; - - case "discord_create_webhook": - toolResponse = await createWebhookHandler(args, toolContext); - return toolResponse; - - case "discord_send_webhook_message": - toolResponse = await sendWebhookMessageHandler(args, toolContext); - return toolResponse; - - case "discord_edit_webhook": - toolResponse = await editWebhookHandler(args, toolContext); - return toolResponse; - - case "discord_delete_webhook": - toolResponse = await deleteWebhookHandler(args, toolContext); - return toolResponse; - - default: - throw new Error(`Unknown tool: ${name}`); - } - } catch (error) { - if (error instanceof z.ZodError) { - return { - content: [{ - type: "text", - text: `Invalid arguments: ${error.errors - .map((e) => `${e.path.join(".")}: ${e.message}`) - .join(", ")}` - }], - isError: true - }; - } - - return { - content: [{ type: "text", text: `Error executing tool: ${error}` }], - isError: true - }; - } -}); - // Auto-login on startup if token is available const autoLogin = async () => { const token = config.DISCORD_TOKEN; if (token) { try { await client.login(token); + console.log('Successfully logged in to Discord'); } catch (error) { console.error("Auto-login failed:", error); } @@ -227,8 +86,32 @@ const autoLogin = async () => { } }; +// Initialize transport based on configuration +const initializeTransport = () => { + switch (config.TRANSPORT.toLowerCase()) { + case 'http': + console.log(`Initializing HTTP transport on port ${config.HTTP_PORT}`); + return new StreamableHttpTransport(config.HTTP_PORT); + case 'stdio': + console.log('Initializing stdio transport'); + return new StdioTransport(); + default: + console.error(`Unknown transport type: ${config.TRANSPORT}. Falling back to stdio.`); + return new StdioTransport(); + } +}; + // Start auto-login process -autoLogin(); - -const transport = new StdioServerTransport(); -await server.connect(transport); \ No newline at end of file +await autoLogin(); + +// Create and start MCP server with selected transport +const transport = initializeTransport(); +const mcpServer = new DiscordMCPServer(client, transport); + +try { + await mcpServer.start(); + console.log('MCP server started successfully'); +} catch (error) { + console.error('Failed to start MCP server:', error); + process.exit(1); +} \ No newline at end of file diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..f15c763 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,184 @@ +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { Client } from "discord.js"; +import { z } from "zod"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { toolList } from './toolList.js'; +import { + createToolContext, + loginHandler, + sendMessageHandler, + getForumChannelsHandler, + createForumPostHandler, + getForumPostHandler, + replyToForumHandler, + deleteForumPostHandler, + createTextChannelHandler, + deleteChannelHandler, + readMessagesHandler, + getServerInfoHandler, + addReactionHandler, + addMultipleReactionsHandler, + removeReactionHandler, + deleteMessageHandler, + createWebhookHandler, + sendWebhookMessageHandler, + editWebhookHandler, + deleteWebhookHandler +} from './tools/tools.js'; +import { MCPTransport } from './transport.js'; + +export class DiscordMCPServer { + private server: Server; + private toolContext: ReturnType; + + constructor( + private client: Client, + private transport: MCPTransport + ) { + this.server = new Server( + { + name: "MCP-Discord", + version: "1.0.0" + }, + { + capabilities: { + tools: {} + } + } + ); + + this.toolContext = createToolContext(client); + this.setupHandlers(); + } + + private setupHandlers() { + // Set up the tool list + this.server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: toolList + }; + }); + + // Handle tool execution requests + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + let toolResponse; + switch (name) { + case "discord_login": + toolResponse = await loginHandler(args, this.toolContext); + return toolResponse; + + case "discord_send": + toolResponse = await sendMessageHandler(args, this.toolContext); + return toolResponse; + + case "discord_get_forum_channels": + toolResponse = await getForumChannelsHandler(args, this.toolContext); + return toolResponse; + + case "discord_create_forum_post": + toolResponse = await createForumPostHandler(args, this.toolContext); + return toolResponse; + + case "discord_get_forum_post": + toolResponse = await getForumPostHandler(args, this.toolContext); + return toolResponse; + + case "discord_reply_to_forum": + toolResponse = await replyToForumHandler(args, this.toolContext); + return toolResponse; + + case "discord_delete_forum_post": + toolResponse = await deleteForumPostHandler(args, this.toolContext); + return toolResponse; + + case "discord_create_text_channel": + toolResponse = await createTextChannelHandler(args, this.toolContext); + return toolResponse; + + case "discord_delete_channel": + toolResponse = await deleteChannelHandler(args, this.toolContext); + return toolResponse; + + case "discord_read_messages": + toolResponse = await readMessagesHandler(args, this.toolContext); + return toolResponse; + + case "discord_get_server_info": + toolResponse = await getServerInfoHandler(args, this.toolContext); + return toolResponse; + + case "discord_add_reaction": + toolResponse = await addReactionHandler(args, this.toolContext); + return toolResponse; + + case "discord_add_multiple_reactions": + toolResponse = await addMultipleReactionsHandler(args, this.toolContext); + return toolResponse; + + case "discord_remove_reaction": + toolResponse = await removeReactionHandler(args, this.toolContext); + return toolResponse; + + case "discord_delete_message": + toolResponse = await deleteMessageHandler(args, this.toolContext); + return toolResponse; + + case "discord_create_webhook": + toolResponse = await createWebhookHandler(args, this.toolContext); + return toolResponse; + + case "discord_send_webhook_message": + toolResponse = await sendWebhookMessageHandler(args, this.toolContext); + return toolResponse; + + case "discord_edit_webhook": + toolResponse = await editWebhookHandler(args, this.toolContext); + return toolResponse; + + case "discord_delete_webhook": + toolResponse = await deleteWebhookHandler(args, this.toolContext); + return toolResponse; + + default: + throw new Error(`Unknown tool: ${name}`); + } + } catch (error) { + if (error instanceof z.ZodError) { + return { + content: [{ + type: "text", + text: `Invalid arguments: ${error.errors + .map((e: z.ZodIssue) => `${e.path.join(".")}: ${e.message}`) + .join(", ")}` + }], + isError: true + }; + } + + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { + content: [{ type: "text", text: `Error executing tool: ${errorMessage}` }], + isError: true + }; + } + }); + } + + async start() { + // Add client to server context so transport can access it + (this.server as any)._context = { client: this.client }; + (this.server as any).client = this.client; + + await this.transport.start(this.server); + } + + async stop() { + await this.transport.stop(); + } +} \ No newline at end of file diff --git a/src/transport.ts b/src/transport.ts new file mode 100644 index 0000000..a2f462d --- /dev/null +++ b/src/transport.ts @@ -0,0 +1,375 @@ +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import express, { Request, Response } from "express"; +import { toolList } from './toolList.js'; +import { + createToolContext, + loginHandler, + sendMessageHandler, + getForumChannelsHandler, + createForumPostHandler, + getForumPostHandler, + replyToForumHandler, + deleteForumPostHandler, + createTextChannelHandler, + deleteChannelHandler, + readMessagesHandler, + getServerInfoHandler, + addReactionHandler, + addMultipleReactionsHandler, + removeReactionHandler, + deleteMessageHandler, + createWebhookHandler, + sendWebhookMessageHandler, + editWebhookHandler, + deleteWebhookHandler +} from './tools/tools.js'; +import { Client } from "discord.js"; + +export interface MCPTransport { + start(server: Server): Promise; + stop(): Promise; +} + +export class StdioTransport implements MCPTransport { + private transport: StdioServerTransport | null = null; + + async start(server: Server): Promise { + this.transport = new StdioServerTransport(); + await server.connect(this.transport); + } + + async stop(): Promise { + if (this.transport) { + await this.transport.close(); + this.transport = null; + } + } +} + +export class StreamableHttpTransport implements MCPTransport { + private app: express.Application; + private server: Server | null = null; + private httpServer: any = null; + private transport: StreamableHTTPServerTransport | null = null; + private toolContext: ReturnType | null = null; + + constructor(private port: number = 3000) { + this.app = express(); + this.app.use(express.json()); + this.setupEndpoints(); + } + + private setupEndpoints() { + // Handler for POST requests + this.app.post('/mcp', (req: Request, res: Response) => { + console.log('Received MCP request:', req.body); + this.handleMcpRequest(req, res).catch(error => { + console.error('Unhandled error in MCP request:', error); + }); + }); + + // Handler for non-POST methods + this.app.all('/mcp', (req: Request, res: Response) => { + if (req.method !== 'POST') { + res.status(405).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Method not allowed. Use POST.', + }, + id: null, + }); + } + }); + } + + private async handleMcpRequest(req: Request, res: Response) { + try { + if (!this.server) { + return res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Server not initialized', + }, + id: req.body?.id || null, + }); + } + + console.log('Request body:', JSON.stringify(req.body)); + + // Handle all tool requests in a generic way + if (!req.body.method) { + return res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32600, + message: 'Invalid Request: No method specified', + }, + id: req.body?.id || null, + }); + } + + // Handle all tools directly with proper error handling + try { + const method = req.body.method; + const params = req.body.params || {}; + + // Make sure toolContext is available + if (!this.toolContext && method !== 'list_tools') { + return res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Tool context not initialized. Service may need to be restarted.', + }, + id: req.body?.id || null, + }); + } + + let result; + + // Handle each tool method directly + switch (method) { + case 'list_tools': + result = { tools: toolList }; + break; + + case 'discord_login': + result = await loginHandler(params, this.toolContext!); + break; + + case 'discord_send': + result = await sendMessageHandler(params, this.toolContext!); + break; + + case 'discord_get_forum_channels': + result = await getForumChannelsHandler(params, this.toolContext!); + break; + + case 'discord_create_forum_post': + result = await createForumPostHandler(params, this.toolContext!); + break; + + case 'discord_get_forum_post': + result = await getForumPostHandler(params, this.toolContext!); + break; + + case 'discord_reply_to_forum': + result = await replyToForumHandler(params, this.toolContext!); + break; + + case 'discord_delete_forum_post': + result = await deleteForumPostHandler(params, this.toolContext!); + break; + + case 'discord_create_text_channel': + result = await createTextChannelHandler(params, this.toolContext!); + break; + + case 'discord_delete_channel': + result = await deleteChannelHandler(params, this.toolContext!); + break; + + case 'discord_read_messages': + result = await readMessagesHandler(params, this.toolContext!); + break; + + case 'discord_get_server_info': + result = await getServerInfoHandler(params, this.toolContext!); + break; + + case 'discord_add_reaction': + result = await addReactionHandler(params, this.toolContext!); + break; + + case 'discord_add_multiple_reactions': + result = await addMultipleReactionsHandler(params, this.toolContext!); + break; + + case 'discord_remove_reaction': + result = await removeReactionHandler(params, this.toolContext!); + break; + + case 'discord_delete_message': + result = await deleteMessageHandler(params, this.toolContext!); + break; + + case 'discord_create_webhook': + result = await createWebhookHandler(params, this.toolContext!); + break; + + case 'discord_send_webhook_message': + result = await sendWebhookMessageHandler(params, this.toolContext!); + break; + + case 'discord_edit_webhook': + result = await editWebhookHandler(params, this.toolContext!); + break; + + case 'discord_delete_webhook': + result = await deleteWebhookHandler(params, this.toolContext!); + break; + + default: + return res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32601, + message: `Method not found: ${method}`, + }, + id: req.body?.id || null, + }); + } + + console.log(`Request for ${method} handled successfully`); + + // Handle the case where tool handlers return { content, isError } + if (result && typeof result === 'object' && 'content' in result) { + // If it's an error from the tool handler + if ('isError' in result && result.isError) { + return res.status(400).json({ + jsonrpc: '2.0', + id: req.body.id, + error: { + code: -32603, + message: Array.isArray(result.content) + ? result.content.map((item: any) => item.text).join(' ') + : 'Tool execution error' + } + }); + } + + // Return success result but maintain same format as other RPC methods + return res.json({ + jsonrpc: '2.0', + id: req.body.id, + result: result + }); + } + + // Standard result format + return res.json({ + jsonrpc: '2.0', + id: req.body.id, + result: result + }); + + } catch (error) { + console.error('Error processing tool request:', error); + + // Handle validation errors + if (error && typeof error === 'object' && 'name' in error && error.name === 'ZodError') { + return res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32602, + message: `Invalid parameters: ${error && typeof error === 'object' && 'message' in error ? String(error.message) : 'Unknown validation error'}`, + }, + id: req.body?.id || null, + }); + } + + // Handle all other errors + return res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: error instanceof Error ? error.message : 'Unknown error', + }, + id: req.body?.id || null, + }); + } + + } catch (error) { + console.error('Error handling MCP request:', error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal server error', + }, + id: req.body?.id || null, + }); + } + } + } + + async start(server: Server): Promise { + this.server = server; + console.log('Starting HTTP transport with server:', !!this.server); + + // Try to get client from the DiscordMCPServer instance + // First, check if the server is passed from DiscordMCPServer + if (server) { + // Try to access client directly from server._context + const anyServer = server as any; + let client: Client | undefined; + + if (anyServer._context?.client) { + client = anyServer._context.client; + console.log('Found client in server._context'); + } + // Also check if the server object has client directly + else if (anyServer.client instanceof Client) { + client = anyServer.client; + console.log('Found client directly on server object'); + } + // Look in parent object if available + else if (anyServer._parent?.client instanceof Client) { + client = anyServer._parent.client; + console.log('Found client in server._parent'); + } + + if (client) { + this.toolContext = createToolContext(client); + console.log('Tool context initialized with Discord client'); + } else { + // Create a dummy client for testing - allows list_tools to work + console.log('Unable to get Discord client. Creating tool context without client.'); + this.toolContext = createToolContext({} as Client); + } + } + + // Create a stateless transport + this.transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined // set to undefined for stateless servers + }); + + // Connect the transport + await this.server.connect(this.transport); + console.log('Transport connected'); + + return new Promise((resolve) => { + this.httpServer = this.app.listen(this.port, () => { + console.log(`MCP Server listening on port ${this.port}`); + resolve(); + }); + }); + } + + async stop(): Promise { + if (this.transport) { + await this.transport.close(); + this.transport = null; + } + + if (this.server) { + await this.server.close(); + this.server = null; + } + + if (this.httpServer) { + return new Promise((resolve) => { + this.httpServer.close(() => { + console.log('HTTP server closed'); + this.httpServer = null; + resolve(); + }); + }); + } + } +} \ No newline at end of file