From c5f1566656ec6bccef703644b68cfcaa747f0b41 Mon Sep 17 00:00:00 2001 From: BarryY Date: Thu, 27 Mar 2025 12:15:55 +0800 Subject: [PATCH] Updated API to support Tool description --- package-lock.json | 167 ++++- package.json | 6 +- src/index.ts | 1727 ++++++++++++++++++++++++++------------------- 3 files changed, 1165 insertions(+), 735 deletions(-) diff --git a/package-lock.json b/package-lock.json index f27497f..c99f74d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,12 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.7.0", "discord.js": "^14.18.0", "dotenv": "^16.4.7", "zod": "^3.24.2" }, "devDependencies": { + "@modelcontextprotocol/sdk": "^1.8.0", "@types/node": "^20.17.26", "ts-node": "^10.9.2", "typescript": "^5.8.2" @@ -189,13 +189,15 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.7.0.tgz", - "integrity": "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz", + "integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==", + "dev": true, "license": "MIT", "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", + "cross-spawn": "^7.0.3", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", @@ -301,6 +303,7 @@ "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,6 +350,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz", "integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==", + "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -367,6 +371,7 @@ "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" @@ -384,12 +389,14 @@ "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" @@ -405,6 +412,7 @@ "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" @@ -414,6 +422,7 @@ "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", @@ -427,6 +436,7 @@ "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", @@ -443,6 +453,7 @@ "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" @@ -455,6 +466,7 @@ "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" @@ -464,6 +476,7 @@ "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" @@ -473,6 +486,7 @@ "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" @@ -482,6 +496,7 @@ "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, "license": "MIT", "dependencies": { "object-assign": "^4", @@ -498,10 +513,26 @@ "dev": true, "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/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -519,6 +550,7 @@ "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" @@ -528,6 +560,7 @@ "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", @@ -592,6 +625,7 @@ "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", @@ -606,12 +640,14 @@ "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" @@ -621,6 +657,7 @@ "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" @@ -630,6 +667,7 @@ "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" @@ -639,6 +677,7 @@ "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" @@ -651,12 +690,14 @@ "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" @@ -666,6 +707,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.5.tgz", "integrity": "sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==", + "dev": true, "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.0" @@ -678,6 +720,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" @@ -687,6 +730,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", + "dev": true, "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -730,6 +774,7 @@ "version": "7.5.0", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 16" @@ -751,6 +796,7 @@ "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", @@ -768,6 +814,7 @@ "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" @@ -785,12 +832,14 @@ "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" @@ -800,6 +849,7 @@ "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" @@ -809,6 +859,7 @@ "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" @@ -818,6 +869,7 @@ "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", @@ -842,6 +894,7 @@ "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", @@ -855,6 +908,7 @@ "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" @@ -867,6 +921,7 @@ "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" @@ -879,6 +934,7 @@ "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" @@ -891,6 +947,7 @@ "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", @@ -907,6 +964,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -919,12 +977,14 @@ "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" @@ -934,8 +994,16 @@ "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": { + "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/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -965,6 +1033,7 @@ "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" @@ -974,6 +1043,7 @@ "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" @@ -983,6 +1053,7 @@ "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" @@ -995,6 +1066,7 @@ "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" @@ -1004,6 +1076,7 @@ "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" @@ -1013,6 +1086,7 @@ "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, "license": "MIT", "dependencies": { "mime-db": "^1.53.0" @@ -1025,12 +1099,14 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, "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" @@ -1040,6 +1116,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -1049,6 +1126,7 @@ "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" @@ -1061,6 +1139,7 @@ "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" @@ -1073,6 +1152,7 @@ "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" @@ -1082,15 +1162,27 @@ "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" } }, + "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-to-regexp": { "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" @@ -1100,6 +1192,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz", "integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=16.20.0" @@ -1109,6 +1202,7 @@ "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", @@ -1122,6 +1216,7 @@ "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" @@ -1137,6 +1232,7 @@ "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" @@ -1146,6 +1242,7 @@ "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", @@ -1161,6 +1258,7 @@ "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" @@ -1173,6 +1271,7 @@ "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, "license": "MIT", "dependencies": { "is-promise": "^4.0.0", @@ -1187,6 +1286,7 @@ "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", @@ -1207,12 +1307,14 @@ "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, "license": "MIT", "dependencies": { "debug": "^4.3.5", @@ -1236,6 +1338,7 @@ "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" @@ -1245,6 +1348,7 @@ "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" @@ -1254,6 +1358,7 @@ "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" @@ -1266,12 +1371,14 @@ "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, "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -1287,12 +1394,37 @@ "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": { + "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==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1312,6 +1444,7 @@ "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", @@ -1328,6 +1461,7 @@ "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", @@ -1346,6 +1480,7 @@ "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", @@ -1365,6 +1500,7 @@ "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" @@ -1374,6 +1510,7 @@ "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" @@ -1439,6 +1576,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", + "dev": true, "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -1482,6 +1620,7 @@ "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" @@ -1491,6 +1630,7 @@ "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" @@ -1507,15 +1647,33 @@ "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" } }, + "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/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, "license": "ISC" }, "node_modules/ws": { @@ -1562,6 +1720,7 @@ "version": "3.24.5", "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "dev": true, "license": "ISC", "peerDependencies": { "zod": "^3.24.1" diff --git a/package.json b/package.json index a5d7ebd..a8fe0ef 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,24 @@ { "name": "mcp-discord", - "version": "1.0.0", + "version": "1.1.0", "main": "index.js", "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "tsc", "start": "node dist/index.js", - "dev": "ts-node src/index.ts" + "dev": "node --loader ts-node/esm src/index.ts" }, "author": "", "license": "MIT", "description": "", "devDependencies": { + "@modelcontextprotocol/sdk": "^1.8.0", "@types/node": "^20.17.26", "ts-node": "^10.9.2", "typescript": "^5.8.2" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.7.0", "discord.js": "^14.18.0", "dotenv": "^16.4.7", "zod": "^3.24.2" diff --git a/src/index.ts b/src/index.ts index c5ad69b..2290df9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,11 @@ -import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { Client, GatewayIntentBits, Events, TextChannel, ForumChannel, ChannelType } from "discord.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; // Configuration parsing let config: any = {}; @@ -53,776 +57,1043 @@ const client = new Client({ }); // Create an MCP server -const server = new McpServer({ +const server = new Server( + { name: "MCP-Discord", version: "1.0.0" + }, + { + capabilities: { + tools: {} + } + } +); + +const DiscordLoginSchema = z.object({ + random_string: z.string().optional() }); -// Add a test tool -server.tool( - "test", - { name: z.string() }, - async () => ({ - content: [{ type: "text", text: `test success` }] - }) -); +const SendMessageSchema = z.object({ + channelId: z.string(), + message: z.string() +}); -// Discord login tool -server.tool( - "discord_login", - { random_string: z.string().optional() }, - async () => { - try { - const token = config.DISCORD_TOKEN; - if (!token) { - return { - content: [{ type: "text", text: "Discord token not found in config. Make sure the --config parameter is correctly set." }], - isError: true - }; - } - - await client.login(token); - return { - content: [{ type: "text", text: `Successfully logged in to Discord : ${client.user?.tag}` }] - }; - } catch (error) { - return { - content: [{ type: "text", text: `Login failed: ${error}` }], - isError: true - }; +const GetForumChannelsSchema = z.object({ + guildId: z.string() +}); + +const CreateForumPostSchema = z.object({ + forumChannelId: z.string(), + title: z.string(), + content: z.string(), + tags: z.array(z.string()).optional() +}); + +const GetForumPostSchema = z.object({ + threadId: z.string() +}); + +const ReplyToForumSchema = z.object({ + threadId: z.string(), + message: z.string() +}); + +const CreateTextChannelSchema = z.object({ + guildId: z.string(), + channelName: z.string(), + topic: z.string().optional() +}); + +const DeleteChannelSchema = z.object({ + channelId: z.string(), + reason: z.string().optional() +}); + +const ReadMessagesSchema = z.object({ + channelId: z.string(), + limit: z.number().min(1).max(100).optional().default(50) +}); + +const GetServerInfoSchema = z.object({ + guildId: z.string() +}); + +const AddReactionSchema = z.object({ + channelId: z.string(), + messageId: z.string(), + emoji: z.string() +}); + +const AddMultipleReactionsSchema = z.object({ + channelId: z.string(), + messageId: z.string(), + emojis: z.array(z.string()) +}); + +const RemoveReactionSchema = z.object({ + channelId: z.string(), + messageId: z.string(), + emoji: z.string(), + userId: z.string().optional() +}); + +const DeleteForumPostSchema = z.object({ + threadId: z.string(), + reason: z.string().optional() +}); + +const DeleteMessageSchema = z.object({ + channelId: z.string(), + messageId: z.string(), + reason: z.string().optional() +}); + +// Set up the tool list +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "test", + description: "A simple test tool to verify the MCP server is working correctly", + inputSchema: { + type: "object" } - } -); - -// Discord send message tool -server.tool( - "discord_send", - { - channelId: z.string(), - message: z.string() - }, - async ({ channelId, message }) => { - try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; - } - - const channel = await client.channels.fetch(channelId); - if (!channel || !channel.isTextBased()) { - return { - content: [{ type: "text", text: `Cannot find text channel ID: ${channelId}` }], - isError: true - }; - } - - // Ensure channel is text-based and can send messages - if ('send' in channel) { - await channel.send(message); - return { - content: [{ type: "text", text: `Message successfully sent to channel ID: ${channelId}` }] - }; - } else { - return { - content: [{ type: "text", text: `This channel type does not support sending messages` }], - isError: true - }; - } - } catch (error) { - return { - content: [{ type: "text", text: `Send message failed: ${error}` }], - isError: true - }; + }, + { + name: "discord_login", + description: "Logs in to Discord using the configured token", + inputSchema: { + type: "object", + properties: { + random_string: { type: "string" } + }, + required: [] } - } -); - -// Get forum channels tool -server.tool( - "discord_get_forum_channels", - { - guildId: z.string() - }, - async ({ guildId }) => { - try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; - } - - const guild = await client.guilds.fetch(guildId); - if (!guild) { - return { - content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }], - isError: true - }; - } - - // Fetch all channels from the guild - const channels = await guild.channels.fetch(); - - // Filter to get only forum channels - const forumChannels = channels.filter(channel => channel?.type === ChannelType.GuildForum); - - if (forumChannels.size === 0) { - return { - content: [{ type: "text", text: `No forum channels found in guild: ${guild.name}` }] - }; - } - - // Format forum channels information - const forumInfo = forumChannels.map(channel => ({ - id: channel.id, - name: channel.name, - topic: channel.topic || "No topic set" - })); - - return { - content: [{ type: "text", text: JSON.stringify(forumInfo, null, 2) }] - }; - } catch (error) { - return { - content: [{ type: "text", text: `Failed to fetch forum channels: ${error}` }], - isError: true - }; + }, + { + name: "discord_send", + description: "Sends a message to a specified Discord text channel", + inputSchema: { + type: "object", + properties: { + channelId: { type: "string" }, + message: { type: "string" } + }, + required: ["channelId", "message"] } - } -); - -// Create forum post tool -server.tool( - "discord_create_forum_post", - { - forumChannelId: z.string(), - title: z.string(), - content: z.string(), - tags: z.array(z.string()).optional() - }, - async ({ forumChannelId, title, content, tags }) => { - try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; - } - - const channel = await client.channels.fetch(forumChannelId); - if (!channel || channel.type !== ChannelType.GuildForum) { - return { - content: [{ type: "text", text: `Channel ID ${forumChannelId} is not a forum channel.` }], - isError: true - }; - } - - const forumChannel = channel as ForumChannel; - - // Get available tags in the forum - const availableTags = forumChannel.availableTags; - let selectedTagIds: string[] = []; - - // If tags are provided, find their IDs - if (tags && tags.length > 0) { - selectedTagIds = availableTags - .filter(tag => tags.includes(tag.name)) - .map(tag => tag.id); - } - - // Create the forum post - const thread = await forumChannel.threads.create({ - name: title, - message: { - content: content - }, - appliedTags: selectedTagIds.length > 0 ? selectedTagIds : undefined - }); - - return { - content: [{ - type: "text", - text: `Successfully created forum post "${title}" with ID: ${thread.id}` - }] - }; - } catch (error) { - return { - content: [{ type: "text", text: `Failed to create forum post: ${error}` }], - isError: true - }; + }, + { + name: "discord_get_forum_channels", + description: "Lists all forum channels in a specified Discord server (guild)", + inputSchema: { + type: "object", + properties: { + guildId: { type: "string" } + }, + required: ["guildId"] } - } -); - -// Get forum post (thread) details tool -server.tool( - "discord_get_forum_post", - { - threadId: z.string() - }, - async ({ threadId }) => { - try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; + }, + { + name: "discord_create_forum_post", + description: "Creates a new post in a Discord forum channel with optional tags", + inputSchema: { + type: "object", + properties: { + forumChannelId: { type: "string" }, + title: { type: "string" }, + content: { type: "string" }, + tags: { + type: "array", + items: { type: "string" } } - - const thread = await client.channels.fetch(threadId); - if (!thread || !(thread.isThread())) { - return { - content: [{ type: "text", text: `Cannot find thread with ID: ${threadId}` }], - isError: true - }; - } - - // Get messages from the thread - const messages = await thread.messages.fetch({ limit: 10 }); - - const threadDetails = { - id: thread.id, - name: thread.name, - parentId: thread.parentId, - messageCount: messages.size, - createdAt: thread.createdAt, - messages: messages.map(msg => ({ - id: msg.id, - content: msg.content, - author: msg.author.tag, - createdAt: msg.createdAt - })) - }; - - return { - content: [{ type: "text", text: JSON.stringify(threadDetails, null, 2) }] - }; - } catch (error) { - return { - content: [{ type: "text", text: `Failed to fetch forum post: ${error}` }], - isError: true - }; + }, + required: ["forumChannelId", "title", "content"] } - } -); - -// Reply to forum post tool -server.tool( - "discord_reply_to_forum", - { - threadId: z.string(), - message: z.string() - }, - async ({ threadId, message }) => { - try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; - } - - const thread = await client.channels.fetch(threadId); - if (!thread || !(thread.isThread())) { - return { - content: [{ type: "text", text: `Cannot find thread with ID: ${threadId}` }], - isError: true - }; - } - - if (!('send' in thread)) { - return { - content: [{ type: "text", text: `This thread does not support sending messages` }], - isError: true - }; - } - - // Send the reply - const sentMessage = await thread.send(message); - - return { - content: [{ - type: "text", - text: `Successfully replied to forum post. Message ID: ${sentMessage.id}` - }] - }; - } catch (error) { - return { - content: [{ type: "text", text: `Failed to reply to forum post: ${error}` }], - isError: true - }; + }, + { + name: "discord_get_forum_post", + description: "Retrieves details about a forum post including its messages", + inputSchema: { + type: "object", + properties: { + threadId: { type: "string" } + }, + required: ["threadId"] } - } -); - -// Create text channel tool -server.tool( - "discord_create_text_channel", - { - guildId: z.string(), - channelName: z.string(), - topic: z.string().optional() - }, - async ({ guildId, channelName, topic }) => { - try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; - } - - const guild = await client.guilds.fetch(guildId); - if (!guild) { - return { - content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }], - isError: true - }; - } - - // Create the text channel - const channel = await guild.channels.create({ - name: channelName, - type: ChannelType.GuildText, - topic: topic - }); - - return { - content: [{ - type: "text", - text: `Successfully created text channel "${channelName}" with ID: ${channel.id}` - }] - }; - } catch (error) { - return { - content: [{ type: "text", text: `Failed to create text channel: ${error}` }], - isError: true - }; + }, + { + name: "discord_reply_to_forum", + description: "Adds a reply to an existing forum post or thread", + inputSchema: { + type: "object", + properties: { + threadId: { type: "string" }, + message: { type: "string" } + }, + required: ["threadId", "message"] } - } -); - -// Delete channel tool -server.tool( - "discord_delete_channel", - { - channelId: z.string(), - reason: z.string().optional() - }, - async ({ channelId, reason }) => { - try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; - } - - const channel = await client.channels.fetch(channelId); - if (!channel) { - return { - content: [{ type: "text", text: `Cannot find channel with ID: ${channelId}` }], - isError: true - }; - } - - // Check if channel can be deleted (has delete method) - if (!('delete' in channel)) { - return { - content: [{ type: "text", text: `This channel type does not support deletion or the bot lacks permissions` }], - isError: true - }; - } - - // Delete the channel - await channel.delete(reason || "Channel deleted via API"); - - return { - content: [{ - type: "text", - text: `Successfully deleted channel with ID: ${channelId}` - }] - }; - } catch (error) { - return { - content: [{ type: "text", text: `Failed to delete channel: ${error}` }], - isError: true - }; + }, + { + name: "discord_create_text_channel", + description: "Creates a new text channel in a Discord server with an optional topic", + inputSchema: { + type: "object", + properties: { + guildId: { type: "string" }, + channelName: { type: "string" }, + topic: { type: "string" } + }, + required: ["guildId", "channelName"] } - } -); - -// Read messages from channel tool -server.tool( - "discord_read_messages", - { - channelId: z.string(), - limit: z.number().min(1).max(100).optional().default(50) - }, - async ({ channelId, limit }) => { - try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; - } - - const channel = await client.channels.fetch(channelId); - if (!channel) { - return { - content: [{ type: "text", text: `Cannot find channel with ID: ${channelId}` }], - isError: true - }; - } - - // Check if channel has messages (text channel, thread, etc.) - if (!channel.isTextBased() || !('messages' in channel)) { - return { - content: [{ type: "text", text: `Channel type does not support reading messages` }], - isError: true - }; - } - - // Fetch messages - const messages = await channel.messages.fetch({ limit }); - - if (messages.size === 0) { - return { - content: [{ type: "text", text: `No messages found in channel` }] - }; - } - - // Format messages - const formattedMessages = messages.map(msg => ({ - id: msg.id, - content: msg.content, - author: { - id: msg.author.id, - username: msg.author.username, - bot: msg.author.bot - }, - timestamp: msg.createdAt, - attachments: msg.attachments.size, - embeds: msg.embeds.length, - replyTo: msg.reference ? msg.reference.messageId : null - })).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); - - return { - content: [{ - type: "text", - text: JSON.stringify({ - channelId, - messageCount: formattedMessages.length, - messages: formattedMessages - }, null, 2) - }] - }; - } catch (error) { - return { - content: [{ type: "text", text: `Failed to read messages: ${error}` }], - isError: true - }; + }, + { + name: "discord_delete_channel", + description: "Deletes a Discord channel with an optional reason", + inputSchema: { + type: "object", + properties: { + channelId: { type: "string" }, + reason: { type: "string" } + }, + required: ["channelId"] } - } -); - -// Get server information tool -server.tool( - "discord_get_server_info", - { - guildId: z.string() - }, - async ({ guildId }) => { - try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; + }, + { + name: "discord_read_messages", + description: "Retrieves messages from a Discord text channel with a configurable limit", + inputSchema: { + type: "object", + properties: { + channelId: { type: "string" }, + limit: { + type: "number", + minimum: 1, + maximum: 100, + default: 50 } - - const guild = await client.guilds.fetch(guildId); - if (!guild) { - return { - content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }], - isError: true - }; - } - - // Fetch additional guild data - await guild.fetch(); - - // Fetch channel information - const channels = await guild.channels.fetch(); - - // Categorize channels by type - const channelsByType = { - text: channels.filter(c => c?.type === ChannelType.GuildText).size, - voice: channels.filter(c => c?.type === ChannelType.GuildVoice).size, - category: channels.filter(c => c?.type === ChannelType.GuildCategory).size, - forum: channels.filter(c => c?.type === ChannelType.GuildForum).size, - announcement: channels.filter(c => c?.type === ChannelType.GuildAnnouncement).size, - stage: channels.filter(c => c?.type === ChannelType.GuildStageVoice).size, - total: channels.size - }; - - // Fetch member count - const approximateMemberCount = guild.approximateMemberCount || "unknown"; - - // Format guild information - const guildInfo = { - id: guild.id, - name: guild.name, - description: guild.description, - icon: guild.iconURL(), - owner: guild.ownerId, - createdAt: guild.createdAt, - memberCount: approximateMemberCount, - channels: channelsByType, - features: guild.features, - premium: { - tier: guild.premiumTier, - subscriptions: guild.premiumSubscriptionCount - } - }; - - return { - content: [{ type: "text", text: JSON.stringify(guildInfo, null, 2) }] - }; - } catch (error) { - return { - content: [{ type: "text", text: `Failed to fetch server info: ${error}` }], - isError: true - }; + }, + required: ["channelId"] } - } -); + }, + { + name: "discord_get_server_info", + description: "Retrieves detailed information about a Discord server including channels and member count", + inputSchema: { + type: "object", + properties: { + guildId: { type: "string" } + }, + required: ["guildId"] + } + }, + { + name: "discord_add_reaction", + description: "Adds an emoji reaction to a specific Discord message", + inputSchema: { + type: "object", + properties: { + channelId: { type: "string" }, + messageId: { type: "string" }, + emoji: { type: "string" } + }, + required: ["channelId", "messageId", "emoji"] + } + }, + { + name: "discord_add_multiple_reactions", + description: "Adds multiple emoji reactions to a Discord message at once", + inputSchema: { + type: "object", + properties: { + channelId: { type: "string" }, + messageId: { type: "string" }, + emojis: { + type: "array", + items: { type: "string" } + } + }, + required: ["channelId", "messageId", "emojis"] + } + }, + { + name: "discord_remove_reaction", + description: "Removes a specific emoji reaction from a Discord message", + inputSchema: { + type: "object", + properties: { + channelId: { type: "string" }, + messageId: { type: "string" }, + emoji: { type: "string" }, + userId: { type: "string" } + }, + required: ["channelId", "messageId", "emoji"] + } + }, + { + name: "discord_delete_forum_post", + description: "Deletes a forum post or thread with an optional reason", + inputSchema: { + type: "object", + properties: { + threadId: { type: "string" }, + reason: { type: "string" } + }, + required: ["threadId"] + } + }, + { + name: "discord_delete_message", + description: "Deletes a specific message from a Discord text channel", + inputSchema: { + type: "object", + properties: { + channelId: { type: "string" }, + messageId: { type: "string" }, + reason: { type: "string" } + }, + required: ["channelId", "messageId"] + } + } + ] + }; +}); -// Add reaction to message tool -server.tool( - "discord_add_reaction", - { - channelId: z.string(), - messageId: z.string(), - emoji: z.string() - }, - async ({ channelId, messageId, emoji }) => { +// Handle tool execution requests +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case "test": { + return { + content: [{ type: "text", text: `test success` }] + }; + } + + case "discord_login": { + DiscordLoginSchema.parse(args); try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; - } + const token = config.DISCORD_TOKEN; + if (!token) { + return { + content: [{ type: "text", text: "Discord token not found in config. Make sure the --config parameter is correctly set." }], + isError: true + }; + } + + await client.login(token); + return { + content: [{ type: "text", text: `Successfully logged in to Discord : ${client.user?.tag}` }] + }; + } catch (error) { + return { + content: [{ type: "text", text: `Login failed: ${error}` }], + isError: true + }; + } + } - const channel = await client.channels.fetch(channelId); - if (!channel || !channel.isTextBased() || !('messages' in channel)) { - return { - content: [{ type: "text", text: `Cannot find text channel with ID: ${channelId}` }], - isError: true - }; - } + case "discord_send": { + const { channelId, message } = SendMessageSchema.parse(args); + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true + }; + } - const message = await channel.messages.fetch(messageId); - if (!message) { - return { - content: [{ type: "text", text: `Cannot find message with ID: ${messageId}` }], - isError: true - }; - } + const channel = await client.channels.fetch(channelId); + if (!channel || !channel.isTextBased()) { + return { + content: [{ type: "text", text: `Cannot find text channel ID: ${channelId}` }], + isError: true + }; + } - // Add the reaction + // Ensure channel is text-based and can send messages + if ('send' in channel) { + await channel.send(message); + return { + content: [{ type: "text", text: `Message successfully sent to channel ID: ${channelId}` }] + }; + } else { + return { + content: [{ type: "text", text: `This channel type does not support sending messages` }], + isError: true + }; + } + } catch (error) { + return { + content: [{ type: "text", text: `Send message failed: ${error}` }], + isError: true + }; + } + } + + case "discord_get_forum_channels": { + const { guildId } = GetForumChannelsSchema.parse(args); + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true + }; + } + + const guild = await client.guilds.fetch(guildId); + if (!guild) { + return { + content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }], + isError: true + }; + } + + // Fetch all channels from the guild + const channels = await guild.channels.fetch(); + + // Filter to get only forum channels + const forumChannels = channels.filter(channel => channel?.type === ChannelType.GuildForum); + + if (forumChannels.size === 0) { + return { + content: [{ type: "text", text: `No forum channels found in guild: ${guild.name}` }] + }; + } + + // Format forum channels information + const forumInfo = forumChannels.map(channel => ({ + id: channel.id, + name: channel.name, + topic: channel.topic || "No topic set" + })); + + return { + content: [{ type: "text", text: JSON.stringify(forumInfo, null, 2) }] + }; + } catch (error) { + return { + content: [{ type: "text", text: `Failed to fetch forum channels: ${error}` }], + isError: true + }; + } + } + + case "discord_create_forum_post": { + const { forumChannelId, title, content, tags } = CreateForumPostSchema.parse(args); + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true + }; + } + + const channel = await client.channels.fetch(forumChannelId); + if (!channel || channel.type !== ChannelType.GuildForum) { + return { + content: [{ type: "text", text: `Channel ID ${forumChannelId} is not a forum channel.` }], + isError: true + }; + } + + const forumChannel = channel as ForumChannel; + + // Get available tags in the forum + const availableTags = forumChannel.availableTags; + let selectedTagIds: string[] = []; + + // If tags are provided, find their IDs + if (tags && tags.length > 0) { + selectedTagIds = availableTags + .filter(tag => tags.includes(tag.name)) + .map(tag => tag.id); + } + + // Create the forum post + const thread = await forumChannel.threads.create({ + name: title, + message: { + content: content + }, + appliedTags: selectedTagIds.length > 0 ? selectedTagIds : undefined + }); + + return { + content: [{ + type: "text", + text: `Successfully created forum post "${title}" with ID: ${thread.id}` + }] + }; + } catch (error) { + return { + content: [{ type: "text", text: `Failed to create forum post: ${error}` }], + isError: true + }; + } + } + + case "discord_get_forum_post": { + const { threadId } = GetForumPostSchema.parse(args); + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true + }; + } + + const thread = await client.channels.fetch(threadId); + if (!thread || !(thread.isThread())) { + return { + content: [{ type: "text", text: `Cannot find thread with ID: ${threadId}` }], + isError: true + }; + } + + // Get messages from the thread + const messages = await thread.messages.fetch({ limit: 10 }); + + const threadDetails = { + id: thread.id, + name: thread.name, + parentId: thread.parentId, + messageCount: messages.size, + createdAt: thread.createdAt, + messages: messages.map(msg => ({ + id: msg.id, + content: msg.content, + author: msg.author.tag, + createdAt: msg.createdAt + })) + }; + + return { + content: [{ type: "text", text: JSON.stringify(threadDetails, null, 2) }] + }; + } catch (error) { + return { + content: [{ type: "text", text: `Failed to fetch forum post: ${error}` }], + isError: true + }; + } + } + + case "discord_reply_to_forum": { + const { threadId, message } = ReplyToForumSchema.parse(args); + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true + }; + } + + const thread = await client.channels.fetch(threadId); + if (!thread || !(thread.isThread())) { + return { + content: [{ type: "text", text: `Cannot find thread with ID: ${threadId}` }], + isError: true + }; + } + + if (!('send' in thread)) { + return { + content: [{ type: "text", text: `This thread does not support sending messages` }], + isError: true + }; + } + + // Send the reply + const sentMessage = await thread.send(message); + + return { + content: [{ + type: "text", + text: `Successfully replied to forum post. Message ID: ${sentMessage.id}` + }] + }; + } catch (error) { + return { + content: [{ type: "text", text: `Failed to reply to forum post: ${error}` }], + isError: true + }; + } + } + + case "discord_create_text_channel": { + const { guildId, channelName, topic } = CreateTextChannelSchema.parse(args); + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true + }; + } + + const guild = await client.guilds.fetch(guildId); + if (!guild) { + return { + content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }], + isError: true + }; + } + + // Create the text channel + const channel = await guild.channels.create({ + name: channelName, + type: ChannelType.GuildText, + topic: topic + }); + + return { + content: [{ + type: "text", + text: `Successfully created text channel "${channelName}" with ID: ${channel.id}` + }] + }; + } catch (error) { + return { + content: [{ type: "text", text: `Failed to create text channel: ${error}` }], + isError: true + }; + } + } + + case "discord_delete_channel": { + const { channelId, reason } = DeleteChannelSchema.parse(args); + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true + }; + } + + const channel = await client.channels.fetch(channelId); + if (!channel) { + return { + content: [{ type: "text", text: `Cannot find channel with ID: ${channelId}` }], + isError: true + }; + } + + // Check if channel can be deleted (has delete method) + if (!('delete' in channel)) { + return { + content: [{ type: "text", text: `This channel type does not support deletion or the bot lacks permissions` }], + isError: true + }; + } + + // Delete the channel + await channel.delete(reason || "Channel deleted via API"); + + return { + content: [{ + type: "text", + text: `Successfully deleted channel with ID: ${channelId}` + }] + }; + } catch (error) { + return { + content: [{ type: "text", text: `Failed to delete channel: ${error}` }], + isError: true + }; + } + } + + case "discord_read_messages": { + const { channelId, limit } = ReadMessagesSchema.parse(args); + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true + }; + } + + const channel = await client.channels.fetch(channelId); + if (!channel) { + return { + content: [{ type: "text", text: `Cannot find channel with ID: ${channelId}` }], + isError: true + }; + } + + // Check if channel has messages (text channel, thread, etc.) + if (!channel.isTextBased() || !('messages' in channel)) { + return { + content: [{ type: "text", text: `Channel type does not support reading messages` }], + isError: true + }; + } + + // Fetch messages + const messages = await channel.messages.fetch({ limit }); + + if (messages.size === 0) { + return { + content: [{ type: "text", text: `No messages found in channel` }] + }; + } + + // Format messages + const formattedMessages = messages.map(msg => ({ + id: msg.id, + content: msg.content, + author: { + id: msg.author.id, + username: msg.author.username, + bot: msg.author.bot + }, + timestamp: msg.createdAt, + attachments: msg.attachments.size, + embeds: msg.embeds.length, + replyTo: msg.reference ? msg.reference.messageId : null + })).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); + + return { + content: [{ + type: "text", + text: JSON.stringify({ + channelId, + messageCount: formattedMessages.length, + messages: formattedMessages + }, null, 2) + }] + }; + } catch (error) { + return { + content: [{ type: "text", text: `Failed to read messages: ${error}` }], + isError: true + }; + } + } + + case "discord_get_server_info": { + const { guildId } = GetServerInfoSchema.parse(args); + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true + }; + } + + const guild = await client.guilds.fetch(guildId); + if (!guild) { + return { + content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }], + isError: true + }; + } + + // Fetch additional guild data + await guild.fetch(); + + // Fetch channel information + const channels = await guild.channels.fetch(); + + // Categorize channels by type + const channelsByType = { + text: channels.filter(c => c?.type === ChannelType.GuildText).size, + voice: channels.filter(c => c?.type === ChannelType.GuildVoice).size, + category: channels.filter(c => c?.type === ChannelType.GuildCategory).size, + forum: channels.filter(c => c?.type === ChannelType.GuildForum).size, + announcement: channels.filter(c => c?.type === ChannelType.GuildAnnouncement).size, + stage: channels.filter(c => c?.type === ChannelType.GuildStageVoice).size, + total: channels.size + }; + + // Fetch member count + const approximateMemberCount = guild.approximateMemberCount || "unknown"; + + // Format guild information + const guildInfo = { + id: guild.id, + name: guild.name, + description: guild.description, + icon: guild.iconURL(), + owner: guild.ownerId, + createdAt: guild.createdAt, + memberCount: approximateMemberCount, + channels: channelsByType, + features: guild.features, + premium: { + tier: guild.premiumTier, + subscriptions: guild.premiumSubscriptionCount + } + }; + + return { + content: [{ type: "text", text: JSON.stringify(guildInfo, null, 2) }] + }; + } catch (error) { + return { + content: [{ type: "text", text: `Failed to fetch server info: ${error}` }], + isError: true + }; + } + } + + case "discord_add_reaction": { + const { channelId, messageId, emoji } = AddReactionSchema.parse(args); + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true + }; + } + + const channel = await client.channels.fetch(channelId); + if (!channel || !channel.isTextBased() || !('messages' in channel)) { + return { + content: [{ type: "text", text: `Cannot find text channel with ID: ${channelId}` }], + isError: true + }; + } + + const message = await channel.messages.fetch(messageId); + if (!message) { + return { + content: [{ type: "text", text: `Cannot find message with ID: ${messageId}` }], + isError: true + }; + } + + // Add the reaction + await message.react(emoji); + + return { + content: [{ + type: "text", + text: `Successfully added reaction ${emoji} to message ID: ${messageId}` + }] + }; + } catch (error) { + return { + content: [{ type: "text", text: `Failed to add reaction: ${error}` }], + isError: true + }; + } + } + + case "discord_add_multiple_reactions": { + const { channelId, messageId, emojis } = AddMultipleReactionsSchema.parse(args); + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true + }; + } + + const channel = await client.channels.fetch(channelId); + if (!channel || !channel.isTextBased() || !('messages' in channel)) { + return { + content: [{ type: "text", text: `Cannot find text channel with ID: ${channelId}` }], + isError: true + }; + } + + const message = await channel.messages.fetch(messageId); + if (!message) { + return { + content: [{ type: "text", text: `Cannot find message with ID: ${messageId}` }], + isError: true + }; + } + + // Add each reaction sequentially + for (const emoji of emojis) { await message.react(emoji); + // Small delay to prevent rate limiting + await new Promise(resolve => setTimeout(resolve, 300)); + } - return { - content: [{ - type: "text", - text: `Successfully added reaction ${emoji} to message ID: ${messageId}` - }] - }; + return { + content: [{ + type: "text", + text: `Successfully added ${emojis.length} reactions to message ID: ${messageId}` + }] + }; } catch (error) { - return { - content: [{ type: "text", text: `Failed to add reaction: ${error}` }], - isError: true - }; + return { + content: [{ type: "text", text: `Failed to add reactions: ${error}` }], + isError: true + }; } - } -); + } -// Add multiple reactions to message tool -server.tool( - "discord_add_multiple_reactions", - { - channelId: z.string(), - messageId: z.string(), - emojis: z.array(z.string()) - }, - async ({ channelId, messageId, emojis }) => { + case "discord_remove_reaction": { + const { channelId, messageId, emoji, userId } = RemoveReactionSchema.parse(args); try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; - } - - const channel = await client.channels.fetch(channelId); - if (!channel || !channel.isTextBased() || !('messages' in channel)) { - return { - content: [{ type: "text", text: `Cannot find text channel with ID: ${channelId}` }], - isError: true - }; - } - - const message = await channel.messages.fetch(messageId); - if (!message) { - return { - content: [{ type: "text", text: `Cannot find message with ID: ${messageId}` }], - isError: true - }; - } - - // Add each reaction sequentially - for (const emoji of emojis) { - await message.react(emoji); - // Small delay to prevent rate limiting - await new Promise(resolve => setTimeout(resolve, 300)); - } - + if (!client.isReady()) { return { - content: [{ - type: "text", - text: `Successfully added ${emojis.length} reactions to message ID: ${messageId}` - }] + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true }; + } + + const channel = await client.channels.fetch(channelId); + if (!channel || !channel.isTextBased() || !('messages' in channel)) { + return { + content: [{ type: "text", text: `Cannot find text channel with ID: ${channelId}` }], + isError: true + }; + } + + const message = await channel.messages.fetch(messageId); + if (!message) { + return { + content: [{ type: "text", text: `Cannot find message with ID: ${messageId}` }], + isError: true + }; + } + + // Get the reactions + const reactions = message.reactions.cache; + + // Find the specific reaction + const reaction = reactions.find(r => r.emoji.toString() === emoji || r.emoji.name === emoji); + + if (!reaction) { + return { + content: [{ type: "text", text: `Reaction ${emoji} not found on message ID: ${messageId}` }], + isError: true + }; + } + + if (userId) { + // Remove a specific user's reaction + await reaction.users.remove(userId); + return { + content: [{ + type: "text", + text: `Successfully removed reaction ${emoji} from user ID: ${userId} on message ID: ${messageId}` + }] + }; + } else { + // Remove bot's reaction + await reaction.users.remove(client.user.id); + return { + content: [{ + type: "text", + text: `Successfully removed bot's reaction ${emoji} from message ID: ${messageId}` + }] + }; + } } catch (error) { - return { - content: [{ type: "text", text: `Failed to add reactions: ${error}` }], - isError: true - }; + return { + content: [{ type: "text", text: `Failed to remove reaction: ${error}` }], + isError: true + }; } - } -); + } -// Remove reaction from message tool -server.tool( - "discord_remove_reaction", - { - channelId: z.string(), - messageId: z.string(), - emoji: z.string(), - userId: z.string().optional() - }, - async ({ channelId, messageId, emoji, userId }) => { + case "discord_delete_forum_post": { + const { threadId, reason } = DeleteForumPostSchema.parse(args); try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; - } - - const channel = await client.channels.fetch(channelId); - if (!channel || !channel.isTextBased() || !('messages' in channel)) { - return { - content: [{ type: "text", text: `Cannot find text channel with ID: ${channelId}` }], - isError: true - }; - } - - const message = await channel.messages.fetch(messageId); - if (!message) { - return { - content: [{ type: "text", text: `Cannot find message with ID: ${messageId}` }], - isError: true - }; - } - - // Get the reactions - const reactions = message.reactions.cache; - - // Find the specific reaction - const reaction = reactions.find(r => r.emoji.toString() === emoji || r.emoji.name === emoji); - - if (!reaction) { - return { - content: [{ type: "text", text: `Reaction ${emoji} not found on message ID: ${messageId}` }], - isError: true - }; - } - - if (userId) { - // Remove a specific user's reaction - await reaction.users.remove(userId); - return { - content: [{ - type: "text", - text: `Successfully removed reaction ${emoji} from user ID: ${userId} on message ID: ${messageId}` - }] - }; - } else { - // Remove bot's reaction - await reaction.users.remove(client.user.id); - return { - content: [{ - type: "text", - text: `Successfully removed bot's reaction ${emoji} from message ID: ${messageId}` - }] - }; - } - } catch (error) { + if (!client.isReady()) { return { - content: [{ type: "text", text: `Failed to remove reaction: ${error}` }], - isError: true + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true }; - } - } -); + } -// Delete forum post tool -server.tool( - "discord_delete_forum_post", - { - threadId: z.string(), - reason: z.string().optional() - }, - async ({ threadId, reason }) => { + const thread = await client.channels.fetch(threadId); + if (!thread || !thread.isThread()) { + return { + content: [{ type: "text", text: `Cannot find forum post/thread with ID: ${threadId}` }], + isError: true + }; + } + + // Delete the forum post/thread + await thread.delete(reason || "Forum post deleted via API"); + + return { + content: [{ + type: "text", + text: `Successfully deleted forum post/thread with ID: ${threadId}` + }] + }; + } catch (error) { + return { + content: [{ type: "text", text: `Failed to delete forum post: ${error}` }], + isError: true + }; + } + } + + case "discord_delete_message": { + const { channelId, messageId, reason } = DeleteMessageSchema.parse(args); try { - if (!client.isReady()) { - return { - content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], - isError: true - }; - } - - const thread = await client.channels.fetch(threadId); - if (!thread || !thread.isThread()) { - return { - content: [{ type: "text", text: `Cannot find forum post/thread with ID: ${threadId}` }], - isError: true - }; - } - - // Delete the forum post/thread - await thread.delete(reason || "Forum post deleted via API"); - + if (!client.isReady()) { return { - content: [{ - type: "text", - text: `Successfully deleted forum post/thread with ID: ${threadId}` - }] + content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }], + isError: true }; + } + + const channel = await client.channels.fetch(channelId); + if (!channel || !channel.isTextBased() || !('messages' in channel)) { + return { + content: [{ type: "text", text: `Cannot find text channel with ID: ${channelId}` }], + isError: true + }; + } + + // Fetch the message + const message = await channel.messages.fetch(messageId); + if (!message) { + return { + content: [{ type: "text", text: `Cannot find message with ID: ${messageId}` }], + isError: true + }; + } + + // Delete the message + await message.delete(); + + return { + content: [{ + type: "text", + text: `Successfully deleted message with ID: ${messageId} from channel: ${channelId}` + }] + }; } catch (error) { - return { - content: [{ type: "text", text: `Failed to delete forum post: ${error}` }], - isError: true - }; + return { + content: [{ type: "text", text: `Failed to delete message: ${error}` }], + isError: true + }; } + } + + 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 () => {