Spaces:
Runtime error
Runtime error
add mcp sdk (#3)
Browse filesSigned-off-by: Jintao Zhang <zhangjintao9020@gmail.com>
- package-lock.json +359 -11
- package.json +4 -1
- server/index.js +157 -3
- server/mcp-manager.js +245 -0
- server/mcp-servers/time-service.js +363 -0
- server/mcp-servers/weather-service.js +152 -0
- src/App.jsx +9 -3
- src/components/ChatWindow.jsx +287 -1
- src/components/Settings.jsx +334 -12
- src/hooks/useLocalStorage.js +10 -7
- src/index.css +1 -0
- src/styles/mcp-settings.css +248 -0
- vite.config.js +14 -1
package-lock.json
CHANGED
|
@@ -8,12 +8,15 @@
|
|
| 8 |
"name": "thinking-model-client",
|
| 9 |
"version": "0.1.1",
|
| 10 |
"dependencies": {
|
|
|
|
| 11 |
"cors": "^2.8.5",
|
|
|
|
| 12 |
"express": "^4.18.2",
|
| 13 |
"node-fetch": "^3.3.2",
|
| 14 |
"react": "^18.2.0",
|
| 15 |
"react-dom": "^18.2.0",
|
| 16 |
-
"react-markdown": "^10.0.0"
|
|
|
|
| 17 |
},
|
| 18 |
"devDependencies": {
|
| 19 |
"@testing-library/cypress": "^10.0.3",
|
|
@@ -905,6 +908,274 @@
|
|
| 905 |
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 906 |
}
|
| 907 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 908 |
"node_modules/@nodelib/fs.scandir": {
|
| 909 |
"version": "2.1.5",
|
| 910 |
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
|
@@ -2074,7 +2345,6 @@
|
|
| 2074 |
"version": "7.0.6",
|
| 2075 |
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
| 2076 |
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
| 2077 |
-
"dev": true,
|
| 2078 |
"dependencies": {
|
| 2079 |
"path-key": "^3.1.0",
|
| 2080 |
"shebang-command": "^2.0.0",
|
|
@@ -2553,6 +2823,25 @@
|
|
| 2553 |
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
| 2554 |
"dev": true
|
| 2555 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2556 |
"node_modules/execa": {
|
| 2557 |
"version": "4.1.0",
|
| 2558 |
"resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
|
|
@@ -2639,6 +2928,20 @@
|
|
| 2639 |
"url": "https://opencollective.com/express"
|
| 2640 |
}
|
| 2641 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2642 |
"node_modules/express/node_modules/debug": {
|
| 2643 |
"version": "2.6.9",
|
| 2644 |
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
|
@@ -3515,6 +3818,11 @@
|
|
| 3515 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 3516 |
}
|
| 3517 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3518 |
"node_modules/is-stream": {
|
| 3519 |
"version": "2.0.1",
|
| 3520 |
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
|
@@ -3548,8 +3856,7 @@
|
|
| 3548 |
"node_modules/isexe": {
|
| 3549 |
"version": "2.0.0",
|
| 3550 |
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
| 3551 |
-
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
| 3552 |
-
"dev": true
|
| 3553 |
},
|
| 3554 |
"node_modules/isstream": {
|
| 3555 |
"version": "0.1.2",
|
|
@@ -4934,7 +5241,6 @@
|
|
| 4934 |
"version": "1.4.0",
|
| 4935 |
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
| 4936 |
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
| 4937 |
-
"dev": true,
|
| 4938 |
"dependencies": {
|
| 4939 |
"wrappy": "1"
|
| 4940 |
}
|
|
@@ -5025,7 +5331,6 @@
|
|
| 5025 |
"version": "3.1.1",
|
| 5026 |
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
| 5027 |
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
| 5028 |
-
"dev": true,
|
| 5029 |
"engines": {
|
| 5030 |
"node": ">=8"
|
| 5031 |
}
|
|
@@ -5120,6 +5425,14 @@
|
|
| 5120 |
"node": ">= 6"
|
| 5121 |
}
|
| 5122 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5123 |
"node_modules/portfinder": {
|
| 5124 |
"version": "1.0.35",
|
| 5125 |
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.35.tgz",
|
|
@@ -5660,6 +5973,29 @@
|
|
| 5660 |
"fsevents": "~2.3.2"
|
| 5661 |
}
|
| 5662 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5663 |
"node_modules/run-parallel": {
|
| 5664 |
"version": "1.2.0",
|
| 5665 |
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
|
@@ -5806,7 +6142,6 @@
|
|
| 5806 |
"version": "2.0.0",
|
| 5807 |
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
| 5808 |
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
| 5809 |
-
"dev": true,
|
| 5810 |
"dependencies": {
|
| 5811 |
"shebang-regex": "^3.0.0"
|
| 5812 |
},
|
|
@@ -5818,7 +6153,6 @@
|
|
| 5818 |
"version": "3.0.0",
|
| 5819 |
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
| 5820 |
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
| 5821 |
-
"dev": true,
|
| 5822 |
"engines": {
|
| 5823 |
"node": ">=8"
|
| 5824 |
}
|
|
@@ -6770,7 +7104,6 @@
|
|
| 6770 |
"version": "2.0.2",
|
| 6771 |
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
| 6772 |
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
| 6773 |
-
"dev": true,
|
| 6774 |
"dependencies": {
|
| 6775 |
"isexe": "^2.0.0"
|
| 6776 |
},
|
|
@@ -6819,8 +7152,7 @@
|
|
| 6819 |
"node_modules/wrappy": {
|
| 6820 |
"version": "1.0.2",
|
| 6821 |
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
| 6822 |
-
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
| 6823 |
-
"dev": true
|
| 6824 |
},
|
| 6825 |
"node_modules/y18n": {
|
| 6826 |
"version": "5.0.8",
|
|
@@ -6886,6 +7218,22 @@
|
|
| 6886 |
"fd-slicer": "~1.1.0"
|
| 6887 |
}
|
| 6888 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6889 |
"node_modules/zwitch": {
|
| 6890 |
"version": "2.0.4",
|
| 6891 |
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
|
|
|
|
| 8 |
"name": "thinking-model-client",
|
| 9 |
"version": "0.1.1",
|
| 10 |
"dependencies": {
|
| 11 |
+
"@modelcontextprotocol/sdk": "^1.8.0",
|
| 12 |
"cors": "^2.8.5",
|
| 13 |
+
"eventsource": "^3.0.6",
|
| 14 |
"express": "^4.18.2",
|
| 15 |
"node-fetch": "^3.3.2",
|
| 16 |
"react": "^18.2.0",
|
| 17 |
"react-dom": "^18.2.0",
|
| 18 |
+
"react-markdown": "^10.0.0",
|
| 19 |
+
"zod": "^3.24.2"
|
| 20 |
},
|
| 21 |
"devDependencies": {
|
| 22 |
"@testing-library/cypress": "^10.0.3",
|
|
|
|
| 908 |
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 909 |
}
|
| 910 |
},
|
| 911 |
+
"node_modules/@modelcontextprotocol/sdk": {
|
| 912 |
+
"version": "1.8.0",
|
| 913 |
+
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz",
|
| 914 |
+
"integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==",
|
| 915 |
+
"dependencies": {
|
| 916 |
+
"content-type": "^1.0.5",
|
| 917 |
+
"cors": "^2.8.5",
|
| 918 |
+
"cross-spawn": "^7.0.3",
|
| 919 |
+
"eventsource": "^3.0.2",
|
| 920 |
+
"express": "^5.0.1",
|
| 921 |
+
"express-rate-limit": "^7.5.0",
|
| 922 |
+
"pkce-challenge": "^4.1.0",
|
| 923 |
+
"raw-body": "^3.0.0",
|
| 924 |
+
"zod": "^3.23.8",
|
| 925 |
+
"zod-to-json-schema": "^3.24.1"
|
| 926 |
+
},
|
| 927 |
+
"engines": {
|
| 928 |
+
"node": ">=18"
|
| 929 |
+
}
|
| 930 |
+
},
|
| 931 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/accepts": {
|
| 932 |
+
"version": "2.0.0",
|
| 933 |
+
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
| 934 |
+
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
|
| 935 |
+
"dependencies": {
|
| 936 |
+
"mime-types": "^3.0.0",
|
| 937 |
+
"negotiator": "^1.0.0"
|
| 938 |
+
},
|
| 939 |
+
"engines": {
|
| 940 |
+
"node": ">= 0.6"
|
| 941 |
+
}
|
| 942 |
+
},
|
| 943 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": {
|
| 944 |
+
"version": "2.2.0",
|
| 945 |
+
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
| 946 |
+
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
|
| 947 |
+
"dependencies": {
|
| 948 |
+
"bytes": "^3.1.2",
|
| 949 |
+
"content-type": "^1.0.5",
|
| 950 |
+
"debug": "^4.4.0",
|
| 951 |
+
"http-errors": "^2.0.0",
|
| 952 |
+
"iconv-lite": "^0.6.3",
|
| 953 |
+
"on-finished": "^2.4.1",
|
| 954 |
+
"qs": "^6.14.0",
|
| 955 |
+
"raw-body": "^3.0.0",
|
| 956 |
+
"type-is": "^2.0.0"
|
| 957 |
+
},
|
| 958 |
+
"engines": {
|
| 959 |
+
"node": ">=18"
|
| 960 |
+
}
|
| 961 |
+
},
|
| 962 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": {
|
| 963 |
+
"version": "1.0.0",
|
| 964 |
+
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
| 965 |
+
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
|
| 966 |
+
"dependencies": {
|
| 967 |
+
"safe-buffer": "5.2.1"
|
| 968 |
+
},
|
| 969 |
+
"engines": {
|
| 970 |
+
"node": ">= 0.6"
|
| 971 |
+
}
|
| 972 |
+
},
|
| 973 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": {
|
| 974 |
+
"version": "1.2.2",
|
| 975 |
+
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
| 976 |
+
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
|
| 977 |
+
"engines": {
|
| 978 |
+
"node": ">=6.6.0"
|
| 979 |
+
}
|
| 980 |
+
},
|
| 981 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/express": {
|
| 982 |
+
"version": "5.1.0",
|
| 983 |
+
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
| 984 |
+
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
| 985 |
+
"dependencies": {
|
| 986 |
+
"accepts": "^2.0.0",
|
| 987 |
+
"body-parser": "^2.2.0",
|
| 988 |
+
"content-disposition": "^1.0.0",
|
| 989 |
+
"content-type": "^1.0.5",
|
| 990 |
+
"cookie": "^0.7.1",
|
| 991 |
+
"cookie-signature": "^1.2.1",
|
| 992 |
+
"debug": "^4.4.0",
|
| 993 |
+
"encodeurl": "^2.0.0",
|
| 994 |
+
"escape-html": "^1.0.3",
|
| 995 |
+
"etag": "^1.8.1",
|
| 996 |
+
"finalhandler": "^2.1.0",
|
| 997 |
+
"fresh": "^2.0.0",
|
| 998 |
+
"http-errors": "^2.0.0",
|
| 999 |
+
"merge-descriptors": "^2.0.0",
|
| 1000 |
+
"mime-types": "^3.0.0",
|
| 1001 |
+
"on-finished": "^2.4.1",
|
| 1002 |
+
"once": "^1.4.0",
|
| 1003 |
+
"parseurl": "^1.3.3",
|
| 1004 |
+
"proxy-addr": "^2.0.7",
|
| 1005 |
+
"qs": "^6.14.0",
|
| 1006 |
+
"range-parser": "^1.2.1",
|
| 1007 |
+
"router": "^2.2.0",
|
| 1008 |
+
"send": "^1.1.0",
|
| 1009 |
+
"serve-static": "^2.2.0",
|
| 1010 |
+
"statuses": "^2.0.1",
|
| 1011 |
+
"type-is": "^2.0.1",
|
| 1012 |
+
"vary": "^1.1.2"
|
| 1013 |
+
},
|
| 1014 |
+
"engines": {
|
| 1015 |
+
"node": ">= 18"
|
| 1016 |
+
},
|
| 1017 |
+
"funding": {
|
| 1018 |
+
"type": "opencollective",
|
| 1019 |
+
"url": "https://opencollective.com/express"
|
| 1020 |
+
}
|
| 1021 |
+
},
|
| 1022 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": {
|
| 1023 |
+
"version": "2.1.0",
|
| 1024 |
+
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
|
| 1025 |
+
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
|
| 1026 |
+
"dependencies": {
|
| 1027 |
+
"debug": "^4.4.0",
|
| 1028 |
+
"encodeurl": "^2.0.0",
|
| 1029 |
+
"escape-html": "^1.0.3",
|
| 1030 |
+
"on-finished": "^2.4.1",
|
| 1031 |
+
"parseurl": "^1.3.3",
|
| 1032 |
+
"statuses": "^2.0.1"
|
| 1033 |
+
},
|
| 1034 |
+
"engines": {
|
| 1035 |
+
"node": ">= 0.8"
|
| 1036 |
+
}
|
| 1037 |
+
},
|
| 1038 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/fresh": {
|
| 1039 |
+
"version": "2.0.0",
|
| 1040 |
+
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
| 1041 |
+
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
|
| 1042 |
+
"engines": {
|
| 1043 |
+
"node": ">= 0.8"
|
| 1044 |
+
}
|
| 1045 |
+
},
|
| 1046 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": {
|
| 1047 |
+
"version": "0.6.3",
|
| 1048 |
+
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
| 1049 |
+
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
| 1050 |
+
"dependencies": {
|
| 1051 |
+
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
| 1052 |
+
},
|
| 1053 |
+
"engines": {
|
| 1054 |
+
"node": ">=0.10.0"
|
| 1055 |
+
}
|
| 1056 |
+
},
|
| 1057 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": {
|
| 1058 |
+
"version": "1.1.0",
|
| 1059 |
+
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
| 1060 |
+
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
|
| 1061 |
+
"engines": {
|
| 1062 |
+
"node": ">= 0.8"
|
| 1063 |
+
}
|
| 1064 |
+
},
|
| 1065 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": {
|
| 1066 |
+
"version": "2.0.0",
|
| 1067 |
+
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
|
| 1068 |
+
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
|
| 1069 |
+
"engines": {
|
| 1070 |
+
"node": ">=18"
|
| 1071 |
+
},
|
| 1072 |
+
"funding": {
|
| 1073 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1074 |
+
}
|
| 1075 |
+
},
|
| 1076 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": {
|
| 1077 |
+
"version": "1.54.0",
|
| 1078 |
+
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
| 1079 |
+
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
| 1080 |
+
"engines": {
|
| 1081 |
+
"node": ">= 0.6"
|
| 1082 |
+
}
|
| 1083 |
+
},
|
| 1084 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": {
|
| 1085 |
+
"version": "3.0.1",
|
| 1086 |
+
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
| 1087 |
+
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
| 1088 |
+
"dependencies": {
|
| 1089 |
+
"mime-db": "^1.54.0"
|
| 1090 |
+
},
|
| 1091 |
+
"engines": {
|
| 1092 |
+
"node": ">= 0.6"
|
| 1093 |
+
}
|
| 1094 |
+
},
|
| 1095 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": {
|
| 1096 |
+
"version": "1.0.0",
|
| 1097 |
+
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
| 1098 |
+
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
| 1099 |
+
"engines": {
|
| 1100 |
+
"node": ">= 0.6"
|
| 1101 |
+
}
|
| 1102 |
+
},
|
| 1103 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/qs": {
|
| 1104 |
+
"version": "6.14.0",
|
| 1105 |
+
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
| 1106 |
+
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
| 1107 |
+
"dependencies": {
|
| 1108 |
+
"side-channel": "^1.1.0"
|
| 1109 |
+
},
|
| 1110 |
+
"engines": {
|
| 1111 |
+
"node": ">=0.6"
|
| 1112 |
+
},
|
| 1113 |
+
"funding": {
|
| 1114 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1115 |
+
}
|
| 1116 |
+
},
|
| 1117 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": {
|
| 1118 |
+
"version": "3.0.0",
|
| 1119 |
+
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
|
| 1120 |
+
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
|
| 1121 |
+
"dependencies": {
|
| 1122 |
+
"bytes": "3.1.2",
|
| 1123 |
+
"http-errors": "2.0.0",
|
| 1124 |
+
"iconv-lite": "0.6.3",
|
| 1125 |
+
"unpipe": "1.0.0"
|
| 1126 |
+
},
|
| 1127 |
+
"engines": {
|
| 1128 |
+
"node": ">= 0.8"
|
| 1129 |
+
}
|
| 1130 |
+
},
|
| 1131 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/send": {
|
| 1132 |
+
"version": "1.2.0",
|
| 1133 |
+
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
|
| 1134 |
+
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
|
| 1135 |
+
"dependencies": {
|
| 1136 |
+
"debug": "^4.3.5",
|
| 1137 |
+
"encodeurl": "^2.0.0",
|
| 1138 |
+
"escape-html": "^1.0.3",
|
| 1139 |
+
"etag": "^1.8.1",
|
| 1140 |
+
"fresh": "^2.0.0",
|
| 1141 |
+
"http-errors": "^2.0.0",
|
| 1142 |
+
"mime-types": "^3.0.1",
|
| 1143 |
+
"ms": "^2.1.3",
|
| 1144 |
+
"on-finished": "^2.4.1",
|
| 1145 |
+
"range-parser": "^1.2.1",
|
| 1146 |
+
"statuses": "^2.0.1"
|
| 1147 |
+
},
|
| 1148 |
+
"engines": {
|
| 1149 |
+
"node": ">= 18"
|
| 1150 |
+
}
|
| 1151 |
+
},
|
| 1152 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": {
|
| 1153 |
+
"version": "2.2.0",
|
| 1154 |
+
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
| 1155 |
+
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
|
| 1156 |
+
"dependencies": {
|
| 1157 |
+
"encodeurl": "^2.0.0",
|
| 1158 |
+
"escape-html": "^1.0.3",
|
| 1159 |
+
"parseurl": "^1.3.3",
|
| 1160 |
+
"send": "^1.2.0"
|
| 1161 |
+
},
|
| 1162 |
+
"engines": {
|
| 1163 |
+
"node": ">= 18"
|
| 1164 |
+
}
|
| 1165 |
+
},
|
| 1166 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/type-is": {
|
| 1167 |
+
"version": "2.0.1",
|
| 1168 |
+
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
| 1169 |
+
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
|
| 1170 |
+
"dependencies": {
|
| 1171 |
+
"content-type": "^1.0.5",
|
| 1172 |
+
"media-typer": "^1.1.0",
|
| 1173 |
+
"mime-types": "^3.0.0"
|
| 1174 |
+
},
|
| 1175 |
+
"engines": {
|
| 1176 |
+
"node": ">= 0.6"
|
| 1177 |
+
}
|
| 1178 |
+
},
|
| 1179 |
"node_modules/@nodelib/fs.scandir": {
|
| 1180 |
"version": "2.1.5",
|
| 1181 |
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
|
|
|
| 2345 |
"version": "7.0.6",
|
| 2346 |
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
| 2347 |
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
|
|
|
| 2348 |
"dependencies": {
|
| 2349 |
"path-key": "^3.1.0",
|
| 2350 |
"shebang-command": "^2.0.0",
|
|
|
|
| 2823 |
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
| 2824 |
"dev": true
|
| 2825 |
},
|
| 2826 |
+
"node_modules/eventsource": {
|
| 2827 |
+
"version": "3.0.6",
|
| 2828 |
+
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz",
|
| 2829 |
+
"integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==",
|
| 2830 |
+
"dependencies": {
|
| 2831 |
+
"eventsource-parser": "^3.0.1"
|
| 2832 |
+
},
|
| 2833 |
+
"engines": {
|
| 2834 |
+
"node": ">=18.0.0"
|
| 2835 |
+
}
|
| 2836 |
+
},
|
| 2837 |
+
"node_modules/eventsource-parser": {
|
| 2838 |
+
"version": "3.0.1",
|
| 2839 |
+
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
|
| 2840 |
+
"integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
|
| 2841 |
+
"engines": {
|
| 2842 |
+
"node": ">=18.0.0"
|
| 2843 |
+
}
|
| 2844 |
+
},
|
| 2845 |
"node_modules/execa": {
|
| 2846 |
"version": "4.1.0",
|
| 2847 |
"resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
|
|
|
|
| 2928 |
"url": "https://opencollective.com/express"
|
| 2929 |
}
|
| 2930 |
},
|
| 2931 |
+
"node_modules/express-rate-limit": {
|
| 2932 |
+
"version": "7.5.0",
|
| 2933 |
+
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
|
| 2934 |
+
"integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
|
| 2935 |
+
"engines": {
|
| 2936 |
+
"node": ">= 16"
|
| 2937 |
+
},
|
| 2938 |
+
"funding": {
|
| 2939 |
+
"url": "https://github.com/sponsors/express-rate-limit"
|
| 2940 |
+
},
|
| 2941 |
+
"peerDependencies": {
|
| 2942 |
+
"express": "^4.11 || 5 || ^5.0.0-beta.1"
|
| 2943 |
+
}
|
| 2944 |
+
},
|
| 2945 |
"node_modules/express/node_modules/debug": {
|
| 2946 |
"version": "2.6.9",
|
| 2947 |
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
|
|
|
| 3818 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 3819 |
}
|
| 3820 |
},
|
| 3821 |
+
"node_modules/is-promise": {
|
| 3822 |
+
"version": "4.0.0",
|
| 3823 |
+
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
| 3824 |
+
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
|
| 3825 |
+
},
|
| 3826 |
"node_modules/is-stream": {
|
| 3827 |
"version": "2.0.1",
|
| 3828 |
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
|
|
|
| 3856 |
"node_modules/isexe": {
|
| 3857 |
"version": "2.0.0",
|
| 3858 |
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
| 3859 |
+
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
|
|
|
| 3860 |
},
|
| 3861 |
"node_modules/isstream": {
|
| 3862 |
"version": "0.1.2",
|
|
|
|
| 5241 |
"version": "1.4.0",
|
| 5242 |
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
| 5243 |
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
|
|
|
| 5244 |
"dependencies": {
|
| 5245 |
"wrappy": "1"
|
| 5246 |
}
|
|
|
|
| 5331 |
"version": "3.1.1",
|
| 5332 |
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
| 5333 |
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
|
|
|
| 5334 |
"engines": {
|
| 5335 |
"node": ">=8"
|
| 5336 |
}
|
|
|
|
| 5425 |
"node": ">= 6"
|
| 5426 |
}
|
| 5427 |
},
|
| 5428 |
+
"node_modules/pkce-challenge": {
|
| 5429 |
+
"version": "4.1.0",
|
| 5430 |
+
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz",
|
| 5431 |
+
"integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==",
|
| 5432 |
+
"engines": {
|
| 5433 |
+
"node": ">=16.20.0"
|
| 5434 |
+
}
|
| 5435 |
+
},
|
| 5436 |
"node_modules/portfinder": {
|
| 5437 |
"version": "1.0.35",
|
| 5438 |
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.35.tgz",
|
|
|
|
| 5973 |
"fsevents": "~2.3.2"
|
| 5974 |
}
|
| 5975 |
},
|
| 5976 |
+
"node_modules/router": {
|
| 5977 |
+
"version": "2.2.0",
|
| 5978 |
+
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
| 5979 |
+
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
|
| 5980 |
+
"dependencies": {
|
| 5981 |
+
"debug": "^4.4.0",
|
| 5982 |
+
"depd": "^2.0.0",
|
| 5983 |
+
"is-promise": "^4.0.0",
|
| 5984 |
+
"parseurl": "^1.3.3",
|
| 5985 |
+
"path-to-regexp": "^8.0.0"
|
| 5986 |
+
},
|
| 5987 |
+
"engines": {
|
| 5988 |
+
"node": ">= 18"
|
| 5989 |
+
}
|
| 5990 |
+
},
|
| 5991 |
+
"node_modules/router/node_modules/path-to-regexp": {
|
| 5992 |
+
"version": "8.2.0",
|
| 5993 |
+
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
|
| 5994 |
+
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
|
| 5995 |
+
"engines": {
|
| 5996 |
+
"node": ">=16"
|
| 5997 |
+
}
|
| 5998 |
+
},
|
| 5999 |
"node_modules/run-parallel": {
|
| 6000 |
"version": "1.2.0",
|
| 6001 |
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
|
|
|
| 6142 |
"version": "2.0.0",
|
| 6143 |
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
| 6144 |
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
|
|
|
| 6145 |
"dependencies": {
|
| 6146 |
"shebang-regex": "^3.0.0"
|
| 6147 |
},
|
|
|
|
| 6153 |
"version": "3.0.0",
|
| 6154 |
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
| 6155 |
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
|
|
|
| 6156 |
"engines": {
|
| 6157 |
"node": ">=8"
|
| 6158 |
}
|
|
|
|
| 7104 |
"version": "2.0.2",
|
| 7105 |
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
| 7106 |
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
|
|
|
| 7107 |
"dependencies": {
|
| 7108 |
"isexe": "^2.0.0"
|
| 7109 |
},
|
|
|
|
| 7152 |
"node_modules/wrappy": {
|
| 7153 |
"version": "1.0.2",
|
| 7154 |
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
| 7155 |
+
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
|
|
|
| 7156 |
},
|
| 7157 |
"node_modules/y18n": {
|
| 7158 |
"version": "5.0.8",
|
|
|
|
| 7218 |
"fd-slicer": "~1.1.0"
|
| 7219 |
}
|
| 7220 |
},
|
| 7221 |
+
"node_modules/zod": {
|
| 7222 |
+
"version": "3.24.2",
|
| 7223 |
+
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
|
| 7224 |
+
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
|
| 7225 |
+
"funding": {
|
| 7226 |
+
"url": "https://github.com/sponsors/colinhacks"
|
| 7227 |
+
}
|
| 7228 |
+
},
|
| 7229 |
+
"node_modules/zod-to-json-schema": {
|
| 7230 |
+
"version": "3.24.5",
|
| 7231 |
+
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz",
|
| 7232 |
+
"integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==",
|
| 7233 |
+
"peerDependencies": {
|
| 7234 |
+
"zod": "^3.24.1"
|
| 7235 |
+
}
|
| 7236 |
+
},
|
| 7237 |
"node_modules/zwitch": {
|
| 7238 |
"version": "2.0.4",
|
| 7239 |
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
|
package.json
CHANGED
|
@@ -18,12 +18,15 @@
|
|
| 18 |
"test:build": "start-server-and-test serve:build http://localhost:5173 'cypress run --spec cypress/e2e/message-routing-simple.cy.js'"
|
| 19 |
},
|
| 20 |
"dependencies": {
|
|
|
|
| 21 |
"cors": "^2.8.5",
|
|
|
|
| 22 |
"express": "^4.18.2",
|
| 23 |
"node-fetch": "^3.3.2",
|
| 24 |
"react": "^18.2.0",
|
| 25 |
"react-dom": "^18.2.0",
|
| 26 |
-
"react-markdown": "^10.0.0"
|
|
|
|
| 27 |
},
|
| 28 |
"devDependencies": {
|
| 29 |
"@testing-library/cypress": "^10.0.3",
|
|
|
|
| 18 |
"test:build": "start-server-and-test serve:build http://localhost:5173 'cypress run --spec cypress/e2e/message-routing-simple.cy.js'"
|
| 19 |
},
|
| 20 |
"dependencies": {
|
| 21 |
+
"@modelcontextprotocol/sdk": "^1.8.0",
|
| 22 |
"cors": "^2.8.5",
|
| 23 |
+
"eventsource": "^3.0.6",
|
| 24 |
"express": "^4.18.2",
|
| 25 |
"node-fetch": "^3.3.2",
|
| 26 |
"react": "^18.2.0",
|
| 27 |
"react-dom": "^18.2.0",
|
| 28 |
+
"react-markdown": "^10.0.0",
|
| 29 |
+
"zod": "^3.24.2"
|
| 30 |
},
|
| 31 |
"devDependencies": {
|
| 32 |
"@testing-library/cypress": "^10.0.3",
|
server/index.js
CHANGED
|
@@ -4,11 +4,16 @@ import fetch from 'node-fetch';
|
|
| 4 |
import path from 'path'
|
| 5 |
import { fileURLToPath } from 'url'
|
| 6 |
import { callMcpServer, discoverMcpServerTools, executeMcpTool } from './mcp.js';
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
const app = express();
|
| 11 |
-
const port = 7860;
|
| 12 |
|
| 13 |
const __filename = fileURLToPath(import.meta.url)
|
| 14 |
const __dirname = path.dirname(__filename)
|
|
@@ -196,6 +201,155 @@ app.post('/api/mcp/execute', async (req, res) => {
|
|
| 196 |
}
|
| 197 |
});
|
| 198 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
app.listen(port, () => {
|
| 200 |
console.log(`Server running at http://localhost:${port}`);
|
| 201 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import path from 'path'
|
| 5 |
import { fileURLToPath } from 'url'
|
| 6 |
import { callMcpServer, discoverMcpServerTools, executeMcpTool } from './mcp.js';
|
| 7 |
+
import {
|
| 8 |
+
getAvailableMcpServers,
|
| 9 |
+
startMcpServer,
|
| 10 |
+
stopMcpServer,
|
| 11 |
+
getMcpServerStatus,
|
| 12 |
+
stopAllMcpServers
|
| 13 |
+
} from './mcp-manager.js';
|
| 14 |
|
| 15 |
const app = express();
|
| 16 |
+
const port = process.env.PORT || 7860;
|
| 17 |
|
| 18 |
const __filename = fileURLToPath(import.meta.url)
|
| 19 |
const __dirname = path.dirname(__filename)
|
|
|
|
| 201 |
}
|
| 202 |
});
|
| 203 |
|
| 204 |
+
// MCP 服务器管理端点
|
| 205 |
+
app.get('/api/mcp/servers/available', (req, res) => {
|
| 206 |
+
console.log('Received request for available MCP servers');
|
| 207 |
+
try {
|
| 208 |
+
const servers = getAvailableMcpServers();
|
| 209 |
+
console.log('Available MCP servers:', servers);
|
| 210 |
+
res.json({ servers });
|
| 211 |
+
} catch (error) {
|
| 212 |
+
console.error('Error getting available MCP servers:', error);
|
| 213 |
+
res.status(500).json({ error: error.message });
|
| 214 |
+
}
|
| 215 |
+
});
|
| 216 |
+
|
| 217 |
+
app.post('/api/mcp/servers/start', async (req, res) => {
|
| 218 |
+
const { serverId } = req.body;
|
| 219 |
+
|
| 220 |
+
if (!serverId) {
|
| 221 |
+
return res.status(400).json({ error: 'Server ID is required' });
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
try {
|
| 225 |
+
const result = await startMcpServer(serverId);
|
| 226 |
+
|
| 227 |
+
if (result.success) {
|
| 228 |
+
res.json({
|
| 229 |
+
success: true,
|
| 230 |
+
serverId,
|
| 231 |
+
port: result.port,
|
| 232 |
+
endpoint: `http://localhost:${result.port}`
|
| 233 |
+
});
|
| 234 |
+
} else {
|
| 235 |
+
res.status(500).json({
|
| 236 |
+
success: false,
|
| 237 |
+
serverId,
|
| 238 |
+
error: result.error
|
| 239 |
+
});
|
| 240 |
+
}
|
| 241 |
+
} catch (error) {
|
| 242 |
+
console.error(`Error starting MCP server ${serverId}:`, error);
|
| 243 |
+
res.status(500).json({
|
| 244 |
+
success: false,
|
| 245 |
+
serverId,
|
| 246 |
+
error: error.message
|
| 247 |
+
});
|
| 248 |
+
}
|
| 249 |
+
});
|
| 250 |
+
|
| 251 |
+
app.post('/api/mcp/servers/stop', async (req, res) => {
|
| 252 |
+
const { serverId } = req.body;
|
| 253 |
+
|
| 254 |
+
if (!serverId) {
|
| 255 |
+
return res.status(400).json({ error: 'Server ID is required' });
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
try {
|
| 259 |
+
const result = await stopMcpServer(serverId);
|
| 260 |
+
|
| 261 |
+
if (result.success) {
|
| 262 |
+
res.json({
|
| 263 |
+
success: true,
|
| 264 |
+
serverId
|
| 265 |
+
});
|
| 266 |
+
} else {
|
| 267 |
+
res.status(500).json({
|
| 268 |
+
success: false,
|
| 269 |
+
serverId,
|
| 270 |
+
error: result.error
|
| 271 |
+
});
|
| 272 |
+
}
|
| 273 |
+
} catch (error) {
|
| 274 |
+
console.error(`Error stopping MCP server ${serverId}:`, error);
|
| 275 |
+
res.status(500).json({
|
| 276 |
+
success: false,
|
| 277 |
+
serverId,
|
| 278 |
+
error: error.message
|
| 279 |
+
});
|
| 280 |
+
}
|
| 281 |
+
});
|
| 282 |
+
|
| 283 |
+
app.get('/api/mcp/servers/status/:serverId', (req, res) => {
|
| 284 |
+
const { serverId } = req.params;
|
| 285 |
+
|
| 286 |
+
try {
|
| 287 |
+
const status = getMcpServerStatus(serverId);
|
| 288 |
+
res.json({
|
| 289 |
+
serverId,
|
| 290 |
+
...status,
|
| 291 |
+
endpoint: status.isRunning ? `http://localhost:${status.port}` : null
|
| 292 |
+
});
|
| 293 |
+
} catch (error) {
|
| 294 |
+
console.error(`Error getting MCP server status for ${serverId}:`, error);
|
| 295 |
+
res.status(500).json({ error: error.message });
|
| 296 |
+
}
|
| 297 |
+
});
|
| 298 |
+
|
| 299 |
+
// 测试 MCP 服务器连接
|
| 300 |
+
app.post('/api/mcp/servers/test-connection', async (req, res) => {
|
| 301 |
+
const { endpoint, authToken } = req.body;
|
| 302 |
+
|
| 303 |
+
if (!endpoint) {
|
| 304 |
+
return res.status(400).json({ error: 'Endpoint is required' });
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
try {
|
| 308 |
+
// 创建一个临时服务器对象
|
| 309 |
+
const server = {
|
| 310 |
+
id: 'test-connection',
|
| 311 |
+
name: 'Test Connection',
|
| 312 |
+
endpoint,
|
| 313 |
+
authToken
|
| 314 |
+
};
|
| 315 |
+
|
| 316 |
+
// 尝试发现工具
|
| 317 |
+
const toolsInfo = await discoverMcpServerTools(server);
|
| 318 |
+
|
| 319 |
+
res.json({
|
| 320 |
+
success: true,
|
| 321 |
+
tools: toolsInfo.tools || [],
|
| 322 |
+
message: `Successfully connected to MCP server at ${endpoint}`
|
| 323 |
+
});
|
| 324 |
+
} catch (error) {
|
| 325 |
+
console.error(`Error testing connection to MCP server at ${endpoint}:`, error);
|
| 326 |
+
res.status(500).json({
|
| 327 |
+
success: false,
|
| 328 |
+
error: error.message,
|
| 329 |
+
message: `Failed to connect to MCP server at ${endpoint}: ${error.message}`
|
| 330 |
+
});
|
| 331 |
+
}
|
| 332 |
+
});
|
| 333 |
+
|
| 334 |
+
console.log('Starting server...');
|
| 335 |
app.listen(port, () => {
|
| 336 |
console.log(`Server running at http://localhost:${port}`);
|
| 337 |
});
|
| 338 |
+
console.log('Server started!');
|
| 339 |
+
|
| 340 |
+
// 确保在进程退出时停止所有 MCP 服务器
|
| 341 |
+
process.on('exit', () => {
|
| 342 |
+
stopAllMcpServers();
|
| 343 |
+
});
|
| 344 |
+
|
| 345 |
+
// 处理未捕获的异常
|
| 346 |
+
process.on('uncaughtException', (error) => {
|
| 347 |
+
console.error('Uncaught exception:', error);
|
| 348 |
+
stopAllMcpServers();
|
| 349 |
+
process.exit(1);
|
| 350 |
+
});
|
| 351 |
+
|
| 352 |
+
// 处理未处理的 Promise 拒绝
|
| 353 |
+
process.on('unhandledRejection', (reason, promise) => {
|
| 354 |
+
console.error('Unhandled rejection at:', promise, 'reason:', reason);
|
| 355 |
+
});
|
server/mcp-manager.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// MCP 服务器管理器
|
| 2 |
+
import { spawn } from 'child_process';
|
| 3 |
+
import path from 'path';
|
| 4 |
+
import fs from 'fs';
|
| 5 |
+
import { fileURLToPath } from 'url';
|
| 6 |
+
|
| 7 |
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
| 8 |
+
const MCP_SERVERS_DIR = path.join(__dirname, 'mcp-servers');
|
| 9 |
+
|
| 10 |
+
// 确保 MCP 服务器目录存在
|
| 11 |
+
if (!fs.existsSync(MCP_SERVERS_DIR)) {
|
| 12 |
+
fs.mkdirSync(MCP_SERVERS_DIR, { recursive: true });
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
// 存储运行中的 MCP 服务器进程
|
| 16 |
+
const runningServers = new Map();
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* 获取可用的内置 MCP 服务器列表
|
| 20 |
+
*/
|
| 21 |
+
export function getAvailableMcpServers() {
|
| 22 |
+
try {
|
| 23 |
+
// 读取 mcp-servers 目录中的所有 .js 文件
|
| 24 |
+
const files = fs.readdirSync(MCP_SERVERS_DIR).filter(file => file.endsWith('.js'));
|
| 25 |
+
|
| 26 |
+
return files.map(file => {
|
| 27 |
+
const name = file.replace('.js', '');
|
| 28 |
+
const isRunning = runningServers.has(name);
|
| 29 |
+
|
| 30 |
+
return {
|
| 31 |
+
id: name,
|
| 32 |
+
name: formatServerName(name),
|
| 33 |
+
description: `Built-in MCP server: ${formatServerName(name)}`,
|
| 34 |
+
isBuiltIn: true,
|
| 35 |
+
isRunning
|
| 36 |
+
};
|
| 37 |
+
});
|
| 38 |
+
} catch (error) {
|
| 39 |
+
console.error('Error getting available MCP servers:', error);
|
| 40 |
+
return [];
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/**
|
| 45 |
+
* 格式化服务器名称,将连字符和下划线替换为空格,并将每个单词首字母大写
|
| 46 |
+
*/
|
| 47 |
+
function formatServerName(name) {
|
| 48 |
+
return name
|
| 49 |
+
.replace(/[-_]/g, ' ')
|
| 50 |
+
.replace(/\w\S*/g, txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
/**
|
| 54 |
+
* 启动 MCP 服务器
|
| 55 |
+
* @param {string} serverId - 服务器 ID
|
| 56 |
+
* @returns {Promise<{success: boolean, port: number, error: string|null}>}
|
| 57 |
+
*/
|
| 58 |
+
export async function startMcpServer(serverId) {
|
| 59 |
+
try {
|
| 60 |
+
// 如果服务器已经在运行,返回成功
|
| 61 |
+
if (runningServers.has(serverId)) {
|
| 62 |
+
const serverInfo = runningServers.get(serverId);
|
| 63 |
+
return {
|
| 64 |
+
success: true,
|
| 65 |
+
port: serverInfo.port,
|
| 66 |
+
error: null
|
| 67 |
+
};
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// 检查服务器文件是否存在
|
| 71 |
+
const serverFile = path.join(MCP_SERVERS_DIR, `${serverId}.js`);
|
| 72 |
+
if (!fs.existsSync(serverFile)) {
|
| 73 |
+
return {
|
| 74 |
+
success: false,
|
| 75 |
+
port: null,
|
| 76 |
+
error: `Server file not found: ${serverFile}`
|
| 77 |
+
};
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// 分配一个端口(这里简单地使用 8000-8999 范围内的随机端口)
|
| 81 |
+
const port = 8000 + Math.floor(Math.random() * 1000);
|
| 82 |
+
|
| 83 |
+
// 启动服务器进程
|
| 84 |
+
const serverProcess = spawn('node', [serverFile], {
|
| 85 |
+
env: { ...process.env, PORT: port.toString() },
|
| 86 |
+
detached: true
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
+
// 等待服务器启动
|
| 90 |
+
const startPromise = new Promise((resolve, reject) => {
|
| 91 |
+
let output = '';
|
| 92 |
+
let errorOutput = '';
|
| 93 |
+
|
| 94 |
+
serverProcess.stdout.on('data', (data) => {
|
| 95 |
+
output += data.toString();
|
| 96 |
+
if (output.includes('MCP server running')) {
|
| 97 |
+
resolve();
|
| 98 |
+
}
|
| 99 |
+
});
|
| 100 |
+
|
| 101 |
+
serverProcess.stderr.on('data', (data) => {
|
| 102 |
+
errorOutput += data.toString();
|
| 103 |
+
console.error(`MCP server ${serverId} error:`, data.toString());
|
| 104 |
+
});
|
| 105 |
+
|
| 106 |
+
serverProcess.on('error', (error) => {
|
| 107 |
+
reject(new Error(`Failed to start MCP server: ${error.message}`));
|
| 108 |
+
});
|
| 109 |
+
|
| 110 |
+
serverProcess.on('exit', (code) => {
|
| 111 |
+
if (code !== 0 && !output.includes('MCP server running')) {
|
| 112 |
+
reject(new Error(`MCP server exited with code ${code}: ${errorOutput}`));
|
| 113 |
+
}
|
| 114 |
+
});
|
| 115 |
+
|
| 116 |
+
// 设置超时
|
| 117 |
+
setTimeout(() => {
|
| 118 |
+
if (!output.includes('MCP server running')) {
|
| 119 |
+
reject(new Error('Timeout waiting for MCP server to start'));
|
| 120 |
+
}
|
| 121 |
+
}, 5000);
|
| 122 |
+
});
|
| 123 |
+
|
| 124 |
+
try {
|
| 125 |
+
await startPromise;
|
| 126 |
+
|
| 127 |
+
// 存储服务器信息
|
| 128 |
+
runningServers.set(serverId, {
|
| 129 |
+
process: serverProcess,
|
| 130 |
+
port
|
| 131 |
+
});
|
| 132 |
+
|
| 133 |
+
console.log(`MCP server ${serverId} started on port ${port}`);
|
| 134 |
+
|
| 135 |
+
return {
|
| 136 |
+
success: true,
|
| 137 |
+
port,
|
| 138 |
+
error: null
|
| 139 |
+
};
|
| 140 |
+
} catch (error) {
|
| 141 |
+
// 如果启动失败,杀死进程
|
| 142 |
+
serverProcess.kill();
|
| 143 |
+
return {
|
| 144 |
+
success: false,
|
| 145 |
+
port: null,
|
| 146 |
+
error: error.message
|
| 147 |
+
};
|
| 148 |
+
}
|
| 149 |
+
} catch (error) {
|
| 150 |
+
console.error(`Error starting MCP server ${serverId}:`, error);
|
| 151 |
+
return {
|
| 152 |
+
success: false,
|
| 153 |
+
port: null,
|
| 154 |
+
error: error.message
|
| 155 |
+
};
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
/**
|
| 160 |
+
* 停止 MCP 服务器
|
| 161 |
+
* @param {string} serverId - 服务器 ID
|
| 162 |
+
* @returns {Promise<{success: boolean, error: string|null}>}
|
| 163 |
+
*/
|
| 164 |
+
export async function stopMcpServer(serverId) {
|
| 165 |
+
try {
|
| 166 |
+
if (!runningServers.has(serverId)) {
|
| 167 |
+
return {
|
| 168 |
+
success: false,
|
| 169 |
+
error: `Server ${serverId} is not running`
|
| 170 |
+
};
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
const serverInfo = runningServers.get(serverId);
|
| 174 |
+
|
| 175 |
+
// 杀死进程
|
| 176 |
+
serverInfo.process.kill();
|
| 177 |
+
runningServers.delete(serverId);
|
| 178 |
+
|
| 179 |
+
console.log(`MCP server ${serverId} stopped`);
|
| 180 |
+
|
| 181 |
+
return {
|
| 182 |
+
success: true,
|
| 183 |
+
error: null
|
| 184 |
+
};
|
| 185 |
+
} catch (error) {
|
| 186 |
+
console.error(`Error stopping MCP server ${serverId}:`, error);
|
| 187 |
+
return {
|
| 188 |
+
success: false,
|
| 189 |
+
error: error.message
|
| 190 |
+
};
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
/**
|
| 195 |
+
* 获取 MCP 服务器状态
|
| 196 |
+
* @param {string} serverId - 服务器 ID
|
| 197 |
+
* @returns {{isRunning: boolean, port: number|null}}
|
| 198 |
+
*/
|
| 199 |
+
export function getMcpServerStatus(serverId) {
|
| 200 |
+
if (runningServers.has(serverId)) {
|
| 201 |
+
const serverInfo = runningServers.get(serverId);
|
| 202 |
+
return {
|
| 203 |
+
isRunning: true,
|
| 204 |
+
port: serverInfo.port
|
| 205 |
+
};
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
return {
|
| 209 |
+
isRunning: false,
|
| 210 |
+
port: null
|
| 211 |
+
};
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
/**
|
| 215 |
+
* 停止所有运行中的 MCP 服务器
|
| 216 |
+
*/
|
| 217 |
+
export function stopAllMcpServers() {
|
| 218 |
+
for (const [serverId, serverInfo] of runningServers.entries()) {
|
| 219 |
+
try {
|
| 220 |
+
serverInfo.process.kill();
|
| 221 |
+
console.log(`MCP server ${serverId} stopped`);
|
| 222 |
+
} catch (error) {
|
| 223 |
+
console.error(`Error stopping MCP server ${serverId}:`, error);
|
| 224 |
+
}
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
runningServers.clear();
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
// 确保在进程退出时停止所有 MCP 服务器
|
| 231 |
+
process.on('exit', () => {
|
| 232 |
+
stopAllMcpServers();
|
| 233 |
+
});
|
| 234 |
+
|
| 235 |
+
// 处理未捕获的异常
|
| 236 |
+
process.on('uncaughtException', (error) => {
|
| 237 |
+
console.error('Uncaught exception:', error);
|
| 238 |
+
stopAllMcpServers();
|
| 239 |
+
process.exit(1);
|
| 240 |
+
});
|
| 241 |
+
|
| 242 |
+
// 处理未处理的 Promise 拒绝
|
| 243 |
+
process.on('unhandledRejection', (reason, promise) => {
|
| 244 |
+
console.error('Unhandled rejection at:', promise, 'reason:', reason);
|
| 245 |
+
});
|
server/mcp-servers/time-service.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// 时间服务 MCP 服务器 - 使用 MCP SDK 实现
|
| 2 |
+
import express from 'express';
|
| 3 |
+
import cors from 'cors';
|
| 4 |
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
| 5 |
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
| 6 |
+
import { z } from 'zod';
|
| 7 |
+
|
| 8 |
+
const app = express();
|
| 9 |
+
const port = process.env.PORT || 8004;
|
| 10 |
+
|
| 11 |
+
app.use(cors());
|
| 12 |
+
app.use(express.json());
|
| 13 |
+
|
| 14 |
+
// 创建 MCP 服务器实例
|
| 15 |
+
const server = new McpServer({
|
| 16 |
+
name: 'Time Service',
|
| 17 |
+
version: '1.0.0'
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
// 时区列表
|
| 21 |
+
const timezones = [
|
| 22 |
+
{ code: 'UTC', name: 'Coordinated Universal Time', offset: 0 },
|
| 23 |
+
{ code: 'GMT', name: 'Greenwich Mean Time', offset: 0 },
|
| 24 |
+
{ code: 'EST', name: 'Eastern Standard Time', offset: -5 },
|
| 25 |
+
{ code: 'CST', name: 'Central Standard Time', offset: -6 },
|
| 26 |
+
{ code: 'MST', name: 'Mountain Standard Time', offset: -7 },
|
| 27 |
+
{ code: 'PST', name: 'Pacific Standard Time', offset: -8 },
|
| 28 |
+
{ code: 'EDT', name: 'Eastern Daylight Time', offset: -4 },
|
| 29 |
+
{ code: 'CDT', name: 'Central Daylight Time', offset: -5 },
|
| 30 |
+
{ code: 'MDT', name: 'Mountain Daylight Time', offset: -6 },
|
| 31 |
+
{ code: 'PDT', name: 'Pacific Daylight Time', offset: -7 },
|
| 32 |
+
{ code: 'CET', name: 'Central European Time', offset: 1 },
|
| 33 |
+
{ code: 'CEST', name: 'Central European Summer Time', offset: 2 },
|
| 34 |
+
{ code: 'EET', name: 'Eastern European Time', offset: 2 },
|
| 35 |
+
{ code: 'EEST', name: 'Eastern European Summer Time', offset: 3 },
|
| 36 |
+
{ code: 'JST', name: 'Japan Standard Time', offset: 9 },
|
| 37 |
+
{ code: 'CST-China', name: 'China Standard Time', offset: 8 },
|
| 38 |
+
{ code: 'IST', name: 'India Standard Time', offset: 5.5 },
|
| 39 |
+
{ code: 'AEST', name: 'Australian Eastern Standard Time', offset: 10 },
|
| 40 |
+
{ code: 'AEDT', name: 'Australian Eastern Daylight Time', offset: 11 },
|
| 41 |
+
{ code: 'NZST', name: 'New Zealand Standard Time', offset: 12 },
|
| 42 |
+
{ code: 'NZDT', name: 'New Zealand Daylight Time', offset: 13 }
|
| 43 |
+
];
|
| 44 |
+
|
| 45 |
+
// 根据时区代码获取时区信息
|
| 46 |
+
function getTimezoneByCode(code) {
|
| 47 |
+
// 处理中国标准时间的特殊情况
|
| 48 |
+
if (code === 'CST-China') {
|
| 49 |
+
return timezones.find(tz => tz.code === 'CST-China');
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
return timezones.find(tz => tz.code === code);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// 获取当前时间
|
| 56 |
+
function getCurrentTime(timezone = 'UTC') {
|
| 57 |
+
const now = new Date();
|
| 58 |
+
const tz = getTimezoneByCode(timezone);
|
| 59 |
+
|
| 60 |
+
if (!tz) {
|
| 61 |
+
return {
|
| 62 |
+
error: `Unknown timezone: ${timezone}`
|
| 63 |
+
};
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
// 计算指定时区的时间
|
| 67 |
+
const utcTime = now.getTime() + (now.getTimezoneOffset() * 60000);
|
| 68 |
+
const tzTime = new Date(utcTime + (3600000 * tz.offset));
|
| 69 |
+
|
| 70 |
+
return {
|
| 71 |
+
timezone: tz.code,
|
| 72 |
+
timezoneName: tz.name,
|
| 73 |
+
offset: tz.offset,
|
| 74 |
+
time: tzTime.toISOString(),
|
| 75 |
+
formattedTime: tzTime.toLocaleString('en-US', {
|
| 76 |
+
weekday: 'long',
|
| 77 |
+
year: 'numeric',
|
| 78 |
+
month: 'long',
|
| 79 |
+
day: 'numeric',
|
| 80 |
+
hour: '2-digit',
|
| 81 |
+
minute: '2-digit',
|
| 82 |
+
second: '2-digit',
|
| 83 |
+
timeZone: 'UTC'
|
| 84 |
+
}),
|
| 85 |
+
timestamp: tzTime.getTime()
|
| 86 |
+
};
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
// 转换时区
|
| 90 |
+
function convertTimezone(time, fromTimezone, toTimezone) {
|
| 91 |
+
const fromTz = getTimezoneByCode(fromTimezone);
|
| 92 |
+
const toTz = getTimezoneByCode(toTimezone);
|
| 93 |
+
|
| 94 |
+
if (!fromTz) {
|
| 95 |
+
return {
|
| 96 |
+
error: `Unknown source timezone: ${fromTimezone}`
|
| 97 |
+
};
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
if (!toTz) {
|
| 101 |
+
return {
|
| 102 |
+
error: `Unknown target timezone: ${toTimezone}`
|
| 103 |
+
};
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
let dateTime;
|
| 107 |
+
|
| 108 |
+
// 如果提供了时间,则使用它;否则使用当前时间
|
| 109 |
+
if (time) {
|
| 110 |
+
try {
|
| 111 |
+
dateTime = new Date(time);
|
| 112 |
+
if (isNaN(dateTime.getTime())) {
|
| 113 |
+
// 尝试解析其他格式
|
| 114 |
+
const parts = time.split(/[- :]/);
|
| 115 |
+
if (parts.length >= 3) {
|
| 116 |
+
// 假设格式为 YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS
|
| 117 |
+
const year = parseInt(parts[0]);
|
| 118 |
+
const month = parseInt(parts[1]) - 1; // 月份从0开始
|
| 119 |
+
const day = parseInt(parts[2]);
|
| 120 |
+
const hour = parts.length > 3 ? parseInt(parts[3]) : 0;
|
| 121 |
+
const minute = parts.length > 4 ? parseInt(parts[4]) : 0;
|
| 122 |
+
const second = parts.length > 5 ? parseInt(parts[5]) : 0;
|
| 123 |
+
|
| 124 |
+
dateTime = new Date(Date.UTC(year, month, day, hour, minute, second));
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
if (isNaN(dateTime.getTime())) {
|
| 128 |
+
return {
|
| 129 |
+
error: `Invalid time format: ${time}. Please use ISO format (YYYY-MM-DDTHH:MM:SS) or YYYY-MM-DD HH:MM:SS.`
|
| 130 |
+
};
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
} catch (error) {
|
| 134 |
+
return {
|
| 135 |
+
error: `Invalid time format: ${time}. Please use ISO format (YYYY-MM-DDTHH:MM:SS) or YYYY-MM-DD HH:MM:SS.`
|
| 136 |
+
};
|
| 137 |
+
}
|
| 138 |
+
} else {
|
| 139 |
+
dateTime = new Date();
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
// 将时间转换为 UTC
|
| 143 |
+
const utcTime = dateTime.getTime() - (fromTz.offset * 3600000);
|
| 144 |
+
|
| 145 |
+
// 将 UTC 时间转换为目标时区时间
|
| 146 |
+
const targetTime = new Date(utcTime + (toTz.offset * 3600000));
|
| 147 |
+
|
| 148 |
+
return {
|
| 149 |
+
originalTime: dateTime.toISOString(),
|
| 150 |
+
originalTimezone: fromTz.code,
|
| 151 |
+
originalTimezoneName: fromTz.name,
|
| 152 |
+
targetTimezone: toTz.code,
|
| 153 |
+
targetTimezoneName: toTz.name,
|
| 154 |
+
targetTime: targetTime.toISOString(),
|
| 155 |
+
formattedTargetTime: targetTime.toLocaleString('en-US', {
|
| 156 |
+
weekday: 'long',
|
| 157 |
+
year: 'numeric',
|
| 158 |
+
month: 'long',
|
| 159 |
+
day: 'numeric',
|
| 160 |
+
hour: '2-digit',
|
| 161 |
+
minute: '2-digit',
|
| 162 |
+
second: '2-digit',
|
| 163 |
+
timeZone: 'UTC'
|
| 164 |
+
}),
|
| 165 |
+
timeDifference: `${toTz.offset - fromTz.offset} hours`
|
| 166 |
+
};
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
// 计算两个日期之间的差异
|
| 170 |
+
function calculateDateDifference(startDate, endDate) {
|
| 171 |
+
let start, end;
|
| 172 |
+
|
| 173 |
+
try {
|
| 174 |
+
start = new Date(startDate);
|
| 175 |
+
if (isNaN(start.getTime())) {
|
| 176 |
+
return {
|
| 177 |
+
error: `Invalid start date format: ${startDate}. Please use ISO format (YYYY-MM-DDTHH:MM:SS) or YYYY-MM-DD.`
|
| 178 |
+
};
|
| 179 |
+
}
|
| 180 |
+
} catch (error) {
|
| 181 |
+
return {
|
| 182 |
+
error: `Invalid start date format: ${startDate}. Please use ISO format (YYYY-MM-DDTHH:MM:SS) or YYYY-MM-DD.`
|
| 183 |
+
};
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
try {
|
| 187 |
+
end = endDate ? new Date(endDate) : new Date();
|
| 188 |
+
if (isNaN(end.getTime())) {
|
| 189 |
+
return {
|
| 190 |
+
error: `Invalid end date format: ${endDate}. Please use ISO format (YYYY-MM-DDTHH:MM:SS) or YYYY-MM-DD.`
|
| 191 |
+
};
|
| 192 |
+
}
|
| 193 |
+
} catch (error) {
|
| 194 |
+
return {
|
| 195 |
+
error: `Invalid end date format: ${endDate}. Please use ISO format (YYYY-MM-DDTHH:MM:SS) or YYYY-MM-DD.`
|
| 196 |
+
};
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
// 计算差异(毫秒)
|
| 200 |
+
const diffMs = Math.abs(end - start);
|
| 201 |
+
|
| 202 |
+
// 转换为天、小时、分钟和秒
|
| 203 |
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
| 204 |
+
const diffHours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
| 205 |
+
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
| 206 |
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
| 207 |
+
|
| 208 |
+
// 计算年和月(近似值)
|
| 209 |
+
const diffYears = Math.floor(diffDays / 365);
|
| 210 |
+
const diffMonths = Math.floor((diffDays % 365) / 30);
|
| 211 |
+
|
| 212 |
+
return {
|
| 213 |
+
startDate: start.toISOString(),
|
| 214 |
+
endDate: end.toISOString(),
|
| 215 |
+
difference: {
|
| 216 |
+
milliseconds: diffMs,
|
| 217 |
+
seconds: Math.floor(diffMs / 1000),
|
| 218 |
+
minutes: Math.floor(diffMs / (1000 * 60)),
|
| 219 |
+
hours: Math.floor(diffMs / (1000 * 60 * 60)),
|
| 220 |
+
days: diffDays,
|
| 221 |
+
months: diffYears * 12 + diffMonths,
|
| 222 |
+
years: diffYears
|
| 223 |
+
},
|
| 224 |
+
formatted: `${diffYears > 0 ? diffYears + ' years, ' : ''}${diffMonths > 0 ? diffMonths + ' months, ' : ''}${diffDays % 30 > 0 ? (diffDays % 30) + ' days, ' : ''}${diffHours > 0 ? diffHours + ' hours, ' : ''}${diffMinutes > 0 ? diffMinutes + ' minutes, ' : ''}${diffSeconds > 0 ? diffSeconds + ' seconds' : ''}`
|
| 225 |
+
};
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
// 定义 get-current-time 工具
|
| 229 |
+
server.tool(
|
| 230 |
+
'get-current-time',
|
| 231 |
+
{ timezone: z.string().optional().describe('The timezone code (e.g., UTC, GMT, EST, CST, PST, JST, etc.)') },
|
| 232 |
+
async ({ timezone = 'UTC' }) => {
|
| 233 |
+
console.log(`Executing get-current-time tool with timezone: ${timezone}`);
|
| 234 |
+
const result = getCurrentTime(timezone);
|
| 235 |
+
|
| 236 |
+
if (result.error) {
|
| 237 |
+
throw new Error(result.error);
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
return {
|
| 241 |
+
content: [{
|
| 242 |
+
type: 'text',
|
| 243 |
+
text: JSON.stringify(result, null, 2)
|
| 244 |
+
}]
|
| 245 |
+
};
|
| 246 |
+
}
|
| 247 |
+
);
|
| 248 |
+
|
| 249 |
+
// 定义 convert-timezone 工具
|
| 250 |
+
server.tool(
|
| 251 |
+
'convert-timezone',
|
| 252 |
+
{
|
| 253 |
+
time: z.string().optional().describe('The time to convert (ISO format or YYYY-MM-DD HH:MM:SS). If not provided, current time will be used.'),
|
| 254 |
+
fromTimezone: z.string().describe('The source timezone code'),
|
| 255 |
+
toTimezone: z.string().describe('The target timezone code')
|
| 256 |
+
},
|
| 257 |
+
async ({ time, fromTimezone, toTimezone }) => {
|
| 258 |
+
console.log(`Executing convert-timezone tool with parameters:`, { time, fromTimezone, toTimezone });
|
| 259 |
+
|
| 260 |
+
if (!fromTimezone || !toTimezone) {
|
| 261 |
+
throw new Error('Both fromTimezone and toTimezone are required');
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
const result = convertTimezone(time, fromTimezone, toTimezone);
|
| 265 |
+
|
| 266 |
+
if (result.error) {
|
| 267 |
+
throw new Error(result.error);
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
return {
|
| 271 |
+
content: [{
|
| 272 |
+
type: 'text',
|
| 273 |
+
text: JSON.stringify(result, null, 2)
|
| 274 |
+
}]
|
| 275 |
+
};
|
| 276 |
+
}
|
| 277 |
+
);
|
| 278 |
+
|
| 279 |
+
// 定义 calculate-date-difference 工具
|
| 280 |
+
server.tool(
|
| 281 |
+
'calculate-date-difference',
|
| 282 |
+
{
|
| 283 |
+
startDate: z.string().describe('The start date (ISO format or YYYY-MM-DD)'),
|
| 284 |
+
endDate: z.string().optional().describe('The end date (ISO format or YYYY-MM-DD). If not provided, current date will be used.')
|
| 285 |
+
},
|
| 286 |
+
async ({ startDate, endDate }) => {
|
| 287 |
+
console.log(`Executing calculate-date-difference tool with parameters:`, { startDate, endDate });
|
| 288 |
+
|
| 289 |
+
if (!startDate) {
|
| 290 |
+
throw new Error('startDate is required');
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
const result = calculateDateDifference(startDate, endDate);
|
| 294 |
+
|
| 295 |
+
if (result.error) {
|
| 296 |
+
throw new Error(result.error);
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
return {
|
| 300 |
+
content: [{
|
| 301 |
+
type: 'text',
|
| 302 |
+
text: JSON.stringify(result, null, 2)
|
| 303 |
+
}]
|
| 304 |
+
};
|
| 305 |
+
}
|
| 306 |
+
);
|
| 307 |
+
|
| 308 |
+
// 定义 list-timezones 工具
|
| 309 |
+
server.tool(
|
| 310 |
+
'list-timezones',
|
| 311 |
+
{},
|
| 312 |
+
async () => {
|
| 313 |
+
console.log('Executing list-timezones tool');
|
| 314 |
+
|
| 315 |
+
const tzList = timezones.map(tz => ({
|
| 316 |
+
code: tz.code,
|
| 317 |
+
name: tz.name,
|
| 318 |
+
offset: tz.offset
|
| 319 |
+
}));
|
| 320 |
+
|
| 321 |
+
return {
|
| 322 |
+
content: [{
|
| 323 |
+
type: 'text',
|
| 324 |
+
text: JSON.stringify({ timezones: tzList }, null, 2)
|
| 325 |
+
}]
|
| 326 |
+
};
|
| 327 |
+
}
|
| 328 |
+
);
|
| 329 |
+
|
| 330 |
+
// 存储活跃的 SSE 传输连接
|
| 331 |
+
const transports = {};
|
| 332 |
+
|
| 333 |
+
// 设置 SSE 端点
|
| 334 |
+
app.get('/sse', async (_, res) => {
|
| 335 |
+
const transport = new SSEServerTransport('/messages', res);
|
| 336 |
+
transports[transport.sessionId] = transport;
|
| 337 |
+
|
| 338 |
+
res.on('close', () => {
|
| 339 |
+
delete transports[transport.sessionId];
|
| 340 |
+
console.log(`Connection closed for session ${transport.sessionId}`);
|
| 341 |
+
});
|
| 342 |
+
|
| 343 |
+
console.log(`New SSE connection established with session ID: ${transport.sessionId}`);
|
| 344 |
+
await server.connect(transport);
|
| 345 |
+
});
|
| 346 |
+
|
| 347 |
+
// 设置消息处理端点
|
| 348 |
+
app.post('/messages', async (req, res) => {
|
| 349 |
+
const sessionId = req.query.sessionId;
|
| 350 |
+
const transport = transports[sessionId];
|
| 351 |
+
|
| 352 |
+
if (transport) {
|
| 353 |
+
await transport.handlePostMessage(req, res);
|
| 354 |
+
} else {
|
| 355 |
+
res.status(400).send('No transport found for sessionId');
|
| 356 |
+
}
|
| 357 |
+
});
|
| 358 |
+
|
| 359 |
+
// 启动服务器
|
| 360 |
+
app.listen(port, () => {
|
| 361 |
+
console.log(`Time MCP server running at http://localhost:${port}`);
|
| 362 |
+
console.log(`Connect to SSE endpoint at http://localhost:${port}/sse`);
|
| 363 |
+
});
|
server/mcp-servers/weather-service.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// 天气服务 MCP 服务器 - 使用 MCP SDK 实现
|
| 2 |
+
import express from 'express';
|
| 3 |
+
import cors from 'cors';
|
| 4 |
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
| 5 |
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
| 6 |
+
import { z } from 'zod';
|
| 7 |
+
|
| 8 |
+
const app = express();
|
| 9 |
+
const port = process.env.PORT || 8001;
|
| 10 |
+
|
| 11 |
+
app.use(cors());
|
| 12 |
+
app.use(express.json());
|
| 13 |
+
|
| 14 |
+
// 创建 MCP 服务器实例
|
| 15 |
+
const server = new McpServer({
|
| 16 |
+
name: 'Weather Service',
|
| 17 |
+
version: '1.0.0'
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
// 模拟天气数据
|
| 21 |
+
const weatherData = {
|
| 22 |
+
'New York': {
|
| 23 |
+
temperature: '15°C',
|
| 24 |
+
condition: 'Partly Cloudy',
|
| 25 |
+
humidity: '65%',
|
| 26 |
+
wind: '10 km/h'
|
| 27 |
+
},
|
| 28 |
+
'London': {
|
| 29 |
+
temperature: '12°C',
|
| 30 |
+
condition: 'Rainy',
|
| 31 |
+
humidity: '80%',
|
| 32 |
+
wind: '15 km/h'
|
| 33 |
+
},
|
| 34 |
+
'Tokyo': {
|
| 35 |
+
temperature: '20°C',
|
| 36 |
+
condition: 'Sunny',
|
| 37 |
+
humidity: '50%',
|
| 38 |
+
wind: '8 km/h'
|
| 39 |
+
},
|
| 40 |
+
'Paris': {
|
| 41 |
+
temperature: '14°C',
|
| 42 |
+
condition: 'Cloudy',
|
| 43 |
+
humidity: '70%',
|
| 44 |
+
wind: '12 km/h'
|
| 45 |
+
},
|
| 46 |
+
'Sydney': {
|
| 47 |
+
temperature: '25°C',
|
| 48 |
+
condition: 'Sunny',
|
| 49 |
+
humidity: '45%',
|
| 50 |
+
wind: '20 km/h'
|
| 51 |
+
}
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
// 默认天气数据
|
| 55 |
+
const defaultWeather = {
|
| 56 |
+
temperature: '18°C',
|
| 57 |
+
condition: 'Clear',
|
| 58 |
+
humidity: '60%',
|
| 59 |
+
wind: '10 km/h'
|
| 60 |
+
};
|
| 61 |
+
|
| 62 |
+
// 定义 get-weather 工具
|
| 63 |
+
server.tool(
|
| 64 |
+
'get-weather',
|
| 65 |
+
{ location: z.string().describe('The location to get weather for') },
|
| 66 |
+
async ({ location }) => {
|
| 67 |
+
console.log(`Executing get-weather tool with location: ${location}`);
|
| 68 |
+
const weather = weatherData[location] || defaultWeather;
|
| 69 |
+
|
| 70 |
+
return {
|
| 71 |
+
content: [{
|
| 72 |
+
type: 'text',
|
| 73 |
+
text: JSON.stringify({
|
| 74 |
+
location,
|
| 75 |
+
...weather,
|
| 76 |
+
timestamp: new Date().toISOString()
|
| 77 |
+
}, null, 2)
|
| 78 |
+
}]
|
| 79 |
+
};
|
| 80 |
+
}
|
| 81 |
+
);
|
| 82 |
+
|
| 83 |
+
// 定义 get-forecast 工具
|
| 84 |
+
server.tool(
|
| 85 |
+
'get-forecast',
|
| 86 |
+
{ location: z.string().describe('The location to get forecast for') },
|
| 87 |
+
async ({ location }) => {
|
| 88 |
+
console.log(`Executing get-forecast tool with location: ${location}`);
|
| 89 |
+
const weather = weatherData[location] || defaultWeather;
|
| 90 |
+
|
| 91 |
+
// 生成简单的 5 天预报
|
| 92 |
+
const forecast = Array.from({ length: 5 }, (_, i) => {
|
| 93 |
+
const date = new Date();
|
| 94 |
+
date.setDate(date.getDate() + i + 1);
|
| 95 |
+
|
| 96 |
+
return {
|
| 97 |
+
date: date.toISOString().split('T')[0],
|
| 98 |
+
temperature: `${Math.round(parseInt(weather.temperature) + (Math.random() * 6 - 3))}°C`,
|
| 99 |
+
condition: ['Sunny', 'Cloudy', 'Rainy', 'Partly Cloudy', 'Clear'][Math.floor(Math.random() * 5)],
|
| 100 |
+
humidity: `${Math.round(parseInt(weather.humidity) + (Math.random() * 10 - 5))}%`,
|
| 101 |
+
wind: `${Math.round(parseInt(weather.wind) + (Math.random() * 8 - 4))} km/h`
|
| 102 |
+
};
|
| 103 |
+
});
|
| 104 |
+
|
| 105 |
+
return {
|
| 106 |
+
content: [{
|
| 107 |
+
type: 'text',
|
| 108 |
+
text: JSON.stringify({
|
| 109 |
+
location,
|
| 110 |
+
current: weather,
|
| 111 |
+
forecast,
|
| 112 |
+
timestamp: new Date().toISOString()
|
| 113 |
+
}, null, 2)
|
| 114 |
+
}]
|
| 115 |
+
};
|
| 116 |
+
}
|
| 117 |
+
);
|
| 118 |
+
|
| 119 |
+
// 存储活跃的 SSE 传输连接
|
| 120 |
+
const transports = {};
|
| 121 |
+
|
| 122 |
+
// 设置 SSE 端点
|
| 123 |
+
app.get('/sse', async (_, res) => {
|
| 124 |
+
const transport = new SSEServerTransport('/messages', res);
|
| 125 |
+
transports[transport.sessionId] = transport;
|
| 126 |
+
|
| 127 |
+
res.on('close', () => {
|
| 128 |
+
delete transports[transport.sessionId];
|
| 129 |
+
console.log(`Connection closed for session ${transport.sessionId}`);
|
| 130 |
+
});
|
| 131 |
+
|
| 132 |
+
console.log(`New SSE connection established with session ID: ${transport.sessionId}`);
|
| 133 |
+
await server.connect(transport);
|
| 134 |
+
});
|
| 135 |
+
|
| 136 |
+
// 设置消息处理端点
|
| 137 |
+
app.post('/messages', async (req, res) => {
|
| 138 |
+
const sessionId = req.query.sessionId;
|
| 139 |
+
const transport = transports[sessionId];
|
| 140 |
+
|
| 141 |
+
if (transport) {
|
| 142 |
+
await transport.handlePostMessage(req, res);
|
| 143 |
+
} else {
|
| 144 |
+
res.status(400).send('No transport found for sessionId');
|
| 145 |
+
}
|
| 146 |
+
});
|
| 147 |
+
|
| 148 |
+
// 启动服务器
|
| 149 |
+
app.listen(port, () => {
|
| 150 |
+
console.log(`Weather MCP server running at http://localhost:${port}`);
|
| 151 |
+
console.log(`Connect to SSE endpoint at http://localhost:${port}/sse`);
|
| 152 |
+
});
|
src/App.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { useState } from 'react';
|
| 2 |
import ChatList from './components/ChatList';
|
| 3 |
import ChatWindow from './components/ChatWindow';
|
| 4 |
import Settings from './components/Settings';
|
|
@@ -23,7 +23,13 @@ function App() {
|
|
| 23 |
model: 'DeepSeek-R1'
|
| 24 |
});
|
| 25 |
|
| 26 |
-
const [mcpServers, setMcpServers] = useLocalStorage('mcpServers', []);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
const [activeProfileId, setActiveProfileId] = useLocalStorage('activeProfileId', 'default');
|
| 29 |
|
|
@@ -230,7 +236,7 @@ function App() {
|
|
| 230 |
isStreamingChat={isStreamingChat}
|
| 231 |
allChats={chats}
|
| 232 |
currentChatId={currentChatId}
|
| 233 |
-
mcpServers={
|
| 234 |
/>
|
| 235 |
) : (
|
| 236 |
<div className="flex flex-col items-center justify-center h-full p-5 text-center">
|
|
|
|
| 1 |
+
import { useState, useMemo } from 'react';
|
| 2 |
import ChatList from './components/ChatList';
|
| 3 |
import ChatWindow from './components/ChatWindow';
|
| 4 |
import Settings from './components/Settings';
|
|
|
|
| 23 |
model: 'DeepSeek-R1'
|
| 24 |
});
|
| 25 |
|
| 26 |
+
const [mcpServers, setMcpServers] = useLocalStorage('mcpServers', [], { disableCache: false });
|
| 27 |
+
|
| 28 |
+
// 过滤出正在运行的 MCP 服务器
|
| 29 |
+
const activeMcpServers = useMemo(() => {
|
| 30 |
+
console.log('Filtering active MCP servers:', mcpServers);
|
| 31 |
+
return mcpServers.filter(server => !server.isBuiltIn || server.isRunning);
|
| 32 |
+
}, [mcpServers]);
|
| 33 |
|
| 34 |
const [activeProfileId, setActiveProfileId] = useLocalStorage('activeProfileId', 'default');
|
| 35 |
|
|
|
|
| 236 |
isStreamingChat={isStreamingChat}
|
| 237 |
allChats={chats}
|
| 238 |
currentChatId={currentChatId}
|
| 239 |
+
mcpServers={activeMcpServers}
|
| 240 |
/>
|
| 241 |
) : (
|
| 242 |
<div className="flex flex-col items-center justify-center h-full p-5 text-center">
|
src/components/ChatWindow.jsx
CHANGED
|
@@ -54,6 +54,12 @@ function ChatWindow({
|
|
| 54 |
'percentage', 'factorial', 'exponent', 'logarithm'
|
| 55 |
];
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
// 检查用户输入是否包含这些问题类型的关键词
|
| 58 |
const input = userInput.toLowerCase();
|
| 59 |
|
|
@@ -66,8 +72,14 @@ function ChatWindow({
|
|
| 66 |
// 检查是否是计算类型的问题
|
| 67 |
const isCalculationQuestion = calculationQuestions.some(keyword => input.includes(keyword.toLowerCase()));
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
// 如果是任何一种类型的问题,则可能需要使用MCP工具
|
| 70 |
-
return isInformationQuestion || isWeatherQuestion || isCalculationQuestion;
|
| 71 |
};
|
| 72 |
|
| 73 |
// Scroll to bottom when messages change
|
|
@@ -243,6 +255,114 @@ function ChatWindow({
|
|
| 243 |
|
| 244 |
return { expression: '' };
|
| 245 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
}
|
| 247 |
};
|
| 248 |
|
|
@@ -327,6 +447,132 @@ function ChatWindow({
|
|
| 327 |
parameters: { expression }
|
| 328 |
};
|
| 329 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
} else {
|
| 331 |
// 其他信息查询问题
|
| 332 |
const searchTool = availableTools.find(tool => tool.name === 'web-search');
|
|
@@ -373,6 +619,13 @@ function ChatWindow({
|
|
| 373 |
result.links.forEach((link, index) => {
|
| 374 |
formattedResult += `${index + 1}. [${link.title}](${link.url})\n`;
|
| 375 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
} else {
|
| 377 |
formattedResult += result.result || JSON.stringify(result, null, 2);
|
| 378 |
}
|
|
@@ -385,6 +638,39 @@ function ChatWindow({
|
|
| 385 |
formattedResult = `**Calculation Result:**\n\n`;
|
| 386 |
formattedResult += `Expression: ${result.expression || toolRequest.parameters.expression}\n`;
|
| 387 |
formattedResult += `Result: ${result.result !== undefined ? result.result : 'Error in calculation'}\n`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
} else {
|
| 389 |
// 其他工具类型的默认格式
|
| 390 |
formattedResult = JSON.stringify(result, null, 2);
|
|
|
|
| 54 |
'percentage', 'factorial', 'exponent', 'logarithm'
|
| 55 |
];
|
| 56 |
|
| 57 |
+
const timeQuestions = [
|
| 58 |
+
'current time', 'time now', 'what time', 'current date', 'today date', 'what date',
|
| 59 |
+
'time in', 'time at', 'time zone', 'timezone', 'convert time', 'time difference',
|
| 60 |
+
'date difference', 'days between', 'time between', 'how long since', 'how many days'
|
| 61 |
+
];
|
| 62 |
+
|
| 63 |
// 检查用户输入是否包含这些问题类型的关键词
|
| 64 |
const input = userInput.toLowerCase();
|
| 65 |
|
|
|
|
| 72 |
// 检查是否是计算类型的问题
|
| 73 |
const isCalculationQuestion = calculationQuestions.some(keyword => input.includes(keyword.toLowerCase()));
|
| 74 |
|
| 75 |
+
// 检查是否是时间相关的问题
|
| 76 |
+
const isTimeQuestion = timeQuestions.some(keyword => input.includes(keyword.toLowerCase()));
|
| 77 |
+
|
| 78 |
+
// 检查是否包含时区代码
|
| 79 |
+
const containsTimezoneCode = /\b([A-Z]{3,4}(?:-[A-Za-z]+)?)\b/i.test(input);
|
| 80 |
+
|
| 81 |
// 如果是任何一种类型的问题,则可能需要使用MCP工具
|
| 82 |
+
return isInformationQuestion || isWeatherQuestion || isCalculationQuestion || isTimeQuestion || containsTimezoneCode;
|
| 83 |
};
|
| 84 |
|
| 85 |
// Scroll to bottom when messages change
|
|
|
|
| 255 |
|
| 256 |
return { expression: '' };
|
| 257 |
}
|
| 258 |
+
},
|
| 259 |
+
'get-current-time': {
|
| 260 |
+
intents: ['current time', 'time now', 'what time', 'current date', 'today date', 'what date'],
|
| 261 |
+
paramExtractor: (input) => {
|
| 262 |
+
// 尝试提取时区
|
| 263 |
+
const timezonePatterns = [
|
| 264 |
+
/(?:in|at)\s+([A-Z]{3,4}(?:-[A-Za-z]+)?)/i,
|
| 265 |
+
/([A-Z]{3,4}(?:-[A-Za-z]+)?)\s+(?:time|timezone)/i
|
| 266 |
+
];
|
| 267 |
+
|
| 268 |
+
for (const pattern of timezonePatterns) {
|
| 269 |
+
const match = input.match(pattern);
|
| 270 |
+
if (match && match[1]) {
|
| 271 |
+
return { timezone: match[1].toUpperCase() };
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
// 如果没有指定时区,使用 UTC
|
| 276 |
+
return { timezone: 'UTC' };
|
| 277 |
+
}
|
| 278 |
+
},
|
| 279 |
+
'convert-timezone': {
|
| 280 |
+
intents: ['convert time', 'timezone conversion', 'time difference', 'what time is it in', 'convert timezone'],
|
| 281 |
+
paramExtractor: (input) => {
|
| 282 |
+
// 尝试提取源时区和目标时区
|
| 283 |
+
const fromTimezonePattern = /from\s+([A-Z]{3,4}(?:-[A-Za-z]+)?)/i;
|
| 284 |
+
const toTimezonePattern = /to\s+([A-Z]{3,4}(?:-[A-Za-z]+)?)/i;
|
| 285 |
+
|
| 286 |
+
// 尝试提取时间
|
| 287 |
+
const timePattern = /(\d{4}-\d{2}-\d{2}(?:T|\s+)\d{2}:\d{2}(?::\d{2})?)/i;
|
| 288 |
+
|
| 289 |
+
const params = {};
|
| 290 |
+
|
| 291 |
+
// 提取时间
|
| 292 |
+
const timeMatch = input.match(timePattern);
|
| 293 |
+
if (timeMatch && timeMatch[1]) {
|
| 294 |
+
params.time = timeMatch[1];
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
// 提取源时区
|
| 298 |
+
const fromMatch = input.match(fromTimezonePattern);
|
| 299 |
+
if (fromMatch && fromMatch[1]) {
|
| 300 |
+
params.fromTimezone = fromMatch[1].toUpperCase();
|
| 301 |
+
} else {
|
| 302 |
+
// 如果没有指定源时区,使用 UTC
|
| 303 |
+
params.fromTimezone = 'UTC';
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
// 提取目标时区
|
| 307 |
+
const toMatch = input.match(toTimezonePattern);
|
| 308 |
+
if (toMatch && toMatch[1]) {
|
| 309 |
+
params.toTimezone = toMatch[1].toUpperCase();
|
| 310 |
+
} else {
|
| 311 |
+
// 尝试从“在...”的形式中提取目标时区
|
| 312 |
+
const inTimezonePattern = /(?:in|at)\s+([A-Z]{3,4}(?:-[A-Za-z]+)?)/i;
|
| 313 |
+
const inMatch = input.match(inTimezonePattern);
|
| 314 |
+
if (inMatch && inMatch[1]) {
|
| 315 |
+
params.toTimezone = inMatch[1].toUpperCase();
|
| 316 |
+
}
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
return params;
|
| 320 |
+
}
|
| 321 |
+
},
|
| 322 |
+
'calculate-date-difference': {
|
| 323 |
+
intents: ['date difference', 'days between', 'time between', 'how long since', 'how many days', 'date calculation'],
|
| 324 |
+
paramExtractor: (input) => {
|
| 325 |
+
// 尝试提取两个日期
|
| 326 |
+
const datePattern = /(\d{4}-\d{2}-\d{2})/g;
|
| 327 |
+
const dates = [];
|
| 328 |
+
let match;
|
| 329 |
+
|
| 330 |
+
while ((match = datePattern.exec(input)) !== null) {
|
| 331 |
+
dates.push(match[1]);
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
if (dates.length >= 2) {
|
| 335 |
+
return {
|
| 336 |
+
startDate: dates[0],
|
| 337 |
+
endDate: dates[1]
|
| 338 |
+
};
|
| 339 |
+
} else if (dates.length === 1) {
|
| 340 |
+
return {
|
| 341 |
+
startDate: dates[0]
|
| 342 |
+
// 不指定结束日期,将使用当前日期
|
| 343 |
+
};
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
// 如果没有找到日期,尝试从“自...”的形式中提取
|
| 347 |
+
const sincePattern = /(?:since|from)\s+(\w+\s+\d{1,2}(?:st|nd|rd|th)?(?:,\s+\d{4})?)/i;
|
| 348 |
+
const sinceMatch = input.match(sincePattern);
|
| 349 |
+
|
| 350 |
+
if (sinceMatch && sinceMatch[1]) {
|
| 351 |
+
// 尝试将文本日期转换为 ISO 格式
|
| 352 |
+
try {
|
| 353 |
+
const date = new Date(sinceMatch[1]);
|
| 354 |
+
if (!isNaN(date.getTime())) {
|
| 355 |
+
return {
|
| 356 |
+
startDate: date.toISOString().split('T')[0]
|
| 357 |
+
};
|
| 358 |
+
}
|
| 359 |
+
} catch (e) {
|
| 360 |
+
// 忽略解析错误
|
| 361 |
+
}
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
return {};
|
| 365 |
+
}
|
| 366 |
}
|
| 367 |
};
|
| 368 |
|
|
|
|
| 447 |
parameters: { expression }
|
| 448 |
};
|
| 449 |
}
|
| 450 |
+
} else if (userInput.toLowerCase().includes('time') ||
|
| 451 |
+
userInput.toLowerCase().includes('date') ||
|
| 452 |
+
userInput.toLowerCase().includes('timezone') ||
|
| 453 |
+
userInput.toLowerCase().includes('time zone') ||
|
| 454 |
+
/\b([A-Z]{3,4}(?:-[A-Za-z]+)?)\b/i.test(userInput)) {
|
| 455 |
+
// 时间相关问题
|
| 456 |
+
|
| 457 |
+
// 检查是否是时区转换请求
|
| 458 |
+
if (userInput.toLowerCase().includes('convert') ||
|
| 459 |
+
userInput.toLowerCase().includes('difference') ||
|
| 460 |
+
userInput.toLowerCase().includes('what time is it in') ||
|
| 461 |
+
(userInput.toLowerCase().includes('time') && userInput.toLowerCase().includes('in'))) {
|
| 462 |
+
const convertTool = availableTools.find(tool => tool.name === 'convert-timezone');
|
| 463 |
+
if (convertTool) {
|
| 464 |
+
// 提取源时区和目标时区
|
| 465 |
+
const fromTimezonePattern = /from\s+([A-Z]{3,4}(?:-[A-Za-z]+)?)/i;
|
| 466 |
+
const toTimezonePattern = /to\s+([A-Z]{3,4}(?:-[A-Za-z]+)?)/i;
|
| 467 |
+
const inTimezonePattern = /(?:in|at)\s+([A-Z]{3,4}(?:-[A-Za-z]+)?)/i;
|
| 468 |
+
|
| 469 |
+
const params = {};
|
| 470 |
+
|
| 471 |
+
// 提取源时区
|
| 472 |
+
const fromMatch = userInput.match(fromTimezonePattern);
|
| 473 |
+
if (fromMatch && fromMatch[1]) {
|
| 474 |
+
params.fromTimezone = fromMatch[1].toUpperCase();
|
| 475 |
+
} else {
|
| 476 |
+
params.fromTimezone = 'UTC';
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
// 提取目标时区
|
| 480 |
+
const toMatch = userInput.match(toTimezonePattern);
|
| 481 |
+
if (toMatch && toMatch[1]) {
|
| 482 |
+
params.toTimezone = toMatch[1].toUpperCase();
|
| 483 |
+
} else {
|
| 484 |
+
const inMatch = userInput.match(inTimezonePattern);
|
| 485 |
+
if (inMatch && inMatch[1]) {
|
| 486 |
+
params.toTimezone = inMatch[1].toUpperCase();
|
| 487 |
+
} else {
|
| 488 |
+
// 如果没有指定目标时区,尝试从文本中提取时区代码
|
| 489 |
+
const timezoneCodeMatch = userInput.match(/\b([A-Z]{3,4}(?:-[A-Za-z]+)?)\b/i);
|
| 490 |
+
if (timezoneCodeMatch && timezoneCodeMatch[1] && timezoneCodeMatch[1].toUpperCase() !== params.fromTimezone) {
|
| 491 |
+
params.toTimezone = timezoneCodeMatch[1].toUpperCase();
|
| 492 |
+
} else {
|
| 493 |
+
// 如果还是没有找到,使用 CST-China
|
| 494 |
+
params.toTimezone = 'CST-China';
|
| 495 |
+
}
|
| 496 |
+
}
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
toolRequest = {
|
| 500 |
+
tool: convertTool,
|
| 501 |
+
parameters: params
|
| 502 |
+
};
|
| 503 |
+
}
|
| 504 |
+
} else if (userInput.toLowerCase().includes('date difference') ||
|
| 505 |
+
userInput.toLowerCase().includes('days between') ||
|
| 506 |
+
userInput.toLowerCase().includes('time between') ||
|
| 507 |
+
userInput.toLowerCase().includes('how long since')) {
|
| 508 |
+
// 日期差异计算
|
| 509 |
+
const dateDiffTool = availableTools.find(tool => tool.name === 'calculate-date-difference');
|
| 510 |
+
if (dateDiffTool) {
|
| 511 |
+
// 提取日期
|
| 512 |
+
const datePattern = /(\d{4}-\d{2}-\d{2})/g;
|
| 513 |
+
const dates = [];
|
| 514 |
+
let match;
|
| 515 |
+
|
| 516 |
+
while ((match = datePattern.exec(userInput)) !== null) {
|
| 517 |
+
dates.push(match[1]);
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
const params = {};
|
| 521 |
+
|
| 522 |
+
if (dates.length >= 2) {
|
| 523 |
+
params.startDate = dates[0];
|
| 524 |
+
params.endDate = dates[1];
|
| 525 |
+
} else if (dates.length === 1) {
|
| 526 |
+
params.startDate = dates[0];
|
| 527 |
+
} else {
|
| 528 |
+
// 如果没有找到日期,尝试从“自...”的形式中提取
|
| 529 |
+
const sincePattern = /(?:since|from)\s+(\w+\s+\d{1,2}(?:st|nd|rd|th)?(?:,\s+\d{4})?)/i;
|
| 530 |
+
const sinceMatch = userInput.match(sincePattern);
|
| 531 |
+
|
| 532 |
+
if (sinceMatch && sinceMatch[1]) {
|
| 533 |
+
try {
|
| 534 |
+
const date = new Date(sinceMatch[1]);
|
| 535 |
+
if (!isNaN(date.getTime())) {
|
| 536 |
+
params.startDate = date.toISOString().split('T')[0];
|
| 537 |
+
}
|
| 538 |
+
} catch (e) {
|
| 539 |
+
// 忽略解析错误
|
| 540 |
+
}
|
| 541 |
+
}
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
if (params.startDate) {
|
| 545 |
+
toolRequest = {
|
| 546 |
+
tool: dateDiffTool,
|
| 547 |
+
parameters: params
|
| 548 |
+
};
|
| 549 |
+
}
|
| 550 |
+
}
|
| 551 |
+
} else {
|
| 552 |
+
// 获取当前时间
|
| 553 |
+
const timeTool = availableTools.find(tool => tool.name === 'get-current-time');
|
| 554 |
+
if (timeTool) {
|
| 555 |
+
// 提取时区
|
| 556 |
+
const timezonePattern = /(?:in|at)\s+([A-Z]{3,4}(?:-[A-Za-z]+)?)/i;
|
| 557 |
+
const timezoneMatch = userInput.match(timezonePattern);
|
| 558 |
+
|
| 559 |
+
let timezone = 'UTC';
|
| 560 |
+
if (timezoneMatch && timezoneMatch[1]) {
|
| 561 |
+
timezone = timezoneMatch[1].toUpperCase();
|
| 562 |
+
} else {
|
| 563 |
+
// 尝试从文本中提取时区代码
|
| 564 |
+
const timezoneCodeMatch = userInput.match(/\b([A-Z]{3,4}(?:-[A-Za-z]+)?)\b/i);
|
| 565 |
+
if (timezoneCodeMatch && timezoneCodeMatch[1]) {
|
| 566 |
+
timezone = timezoneCodeMatch[1].toUpperCase();
|
| 567 |
+
}
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
toolRequest = {
|
| 571 |
+
tool: timeTool,
|
| 572 |
+
parameters: { timezone }
|
| 573 |
+
};
|
| 574 |
+
}
|
| 575 |
+
}
|
| 576 |
} else {
|
| 577 |
// 其他信息查询问题
|
| 578 |
const searchTool = availableTools.find(tool => tool.name === 'web-search');
|
|
|
|
| 619 |
result.links.forEach((link, index) => {
|
| 620 |
formattedResult += `${index + 1}. [${link.title}](${link.url})\n`;
|
| 621 |
});
|
| 622 |
+
} else if (result.results && Array.isArray(result.results)) {
|
| 623 |
+
result.results.forEach((item, index) => {
|
| 624 |
+
formattedResult += `${index + 1}. [${item.title}](${item.url})\n`;
|
| 625 |
+
if (item.snippet) {
|
| 626 |
+
formattedResult += ` ${item.snippet}\n\n`;
|
| 627 |
+
}
|
| 628 |
+
});
|
| 629 |
} else {
|
| 630 |
formattedResult += result.result || JSON.stringify(result, null, 2);
|
| 631 |
}
|
|
|
|
| 638 |
formattedResult = `**Calculation Result:**\n\n`;
|
| 639 |
formattedResult += `Expression: ${result.expression || toolRequest.parameters.expression}\n`;
|
| 640 |
formattedResult += `Result: ${result.result !== undefined ? result.result : 'Error in calculation'}\n`;
|
| 641 |
+
} else if (toolRequest.tool.name === 'get-current-time') {
|
| 642 |
+
formattedResult = `**Current Time in ${result.timezoneName || result.timezone || 'the requested timezone'}:**\n\n`;
|
| 643 |
+
formattedResult += `- Time: ${result.formattedTime || result.time || 'N/A'}\n`;
|
| 644 |
+
formattedResult += `- Timezone: ${result.timezoneName || result.timezone || 'N/A'} (UTC${result.offset >= 0 ? '+' : ''}${result.offset})\n`;
|
| 645 |
+
formattedResult += `- Date: ${new Date(result.time || result.timestamp).toDateString()}\n`;
|
| 646 |
+
} else if (toolRequest.tool.name === 'convert-timezone') {
|
| 647 |
+
formattedResult = `**Time Conversion Result:**\n\n`;
|
| 648 |
+
formattedResult += `- From: ${result.originalTimezoneName || result.originalTimezone || 'N/A'} (${new Date(result.originalTime).toLocaleString()})\n`;
|
| 649 |
+
formattedResult += `- To: ${result.targetTimezoneName || result.targetTimezone || 'N/A'} (${new Date(result.targetTime).toLocaleString()})\n`;
|
| 650 |
+
formattedResult += `- Converted Time: ${result.formattedTargetTime || new Date(result.targetTime).toLocaleString()}\n`;
|
| 651 |
+
formattedResult += `- Time Difference: ${result.timeDifference || 'N/A'}\n`;
|
| 652 |
+
} else if (toolRequest.tool.name === 'calculate-date-difference') {
|
| 653 |
+
formattedResult = `**Date Difference Calculation:**\n\n`;
|
| 654 |
+
formattedResult += `- Start Date: ${new Date(result.startDate).toDateString()}\n`;
|
| 655 |
+
formattedResult += `- End Date: ${new Date(result.endDate).toDateString()}\n`;
|
| 656 |
+
formattedResult += `- Difference: ${result.formatted || 'N/A'}\n`;
|
| 657 |
+
|
| 658 |
+
if (result.difference) {
|
| 659 |
+
formattedResult += `\n**Detailed Difference:**\n`;
|
| 660 |
+
formattedResult += `- Years: ${result.difference.years || 0}\n`;
|
| 661 |
+
formattedResult += `- Months: ${result.difference.months || 0}\n`;
|
| 662 |
+
formattedResult += `- Days: ${result.difference.days || 0}\n`;
|
| 663 |
+
formattedResult += `- Hours: ${result.difference.hours || 0}\n`;
|
| 664 |
+
}
|
| 665 |
+
} else if (toolRequest.tool.name === 'list-timezones') {
|
| 666 |
+
formattedResult = `**Available Timezones:**\n\n`;
|
| 667 |
+
if (result.timezones && Array.isArray(result.timezones)) {
|
| 668 |
+
result.timezones.forEach((tz) => {
|
| 669 |
+
formattedResult += `- ${tz.code}: ${tz.name} (UTC${tz.offset >= 0 ? '+' : ''}${tz.offset})\n`;
|
| 670 |
+
});
|
| 671 |
+
} else {
|
| 672 |
+
formattedResult += JSON.stringify(result, null, 2);
|
| 673 |
+
}
|
| 674 |
} else {
|
| 675 |
// 其他工具类型的默认格式
|
| 676 |
formattedResult = JSON.stringify(result, null, 2);
|
src/components/Settings.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { useState } from 'react';
|
| 2 |
|
| 3 |
function Settings({
|
| 4 |
profiles,
|
|
@@ -19,14 +19,64 @@ function Settings({
|
|
| 19 |
model: 'DeepSeek-R1'
|
| 20 |
});
|
| 21 |
const [localMcpServers, setLocalMcpServers] = useState(mcpServers || []);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
const [editingProfileId, setEditingProfileId] = useState(activeProfileId);
|
| 23 |
const [editingMcpServerId, setEditingMcpServerId] = useState(null);
|
| 24 |
const [isHintExpanded, setIsHintExpanded] = useState(false);
|
| 25 |
const [isMcpHintExpanded, setIsMcpHintExpanded] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
const editingProfile = localProfiles.find(p => p.id === editingProfileId) || localProfiles[0];
|
| 28 |
const editingMcpServer = localMcpServers.find(s => s.id === editingMcpServerId) || null;
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
const handleProfileChange = (updatedProfile) => {
|
| 31 |
setLocalProfiles(localProfiles.map(p =>
|
| 32 |
p.id === updatedProfile.id ? updatedProfile : p
|
|
@@ -72,6 +122,143 @@ function Settings({
|
|
| 72 |
}
|
| 73 |
};
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
const handleAddMcpServer = () => {
|
| 76 |
const newId = `mcp-server-${Date.now()}`;
|
| 77 |
const newServer = {
|
|
@@ -79,7 +266,8 @@ function Settings({
|
|
| 79 |
name: `MCP Server ${localMcpServers.length + 1}`,
|
| 80 |
endpoint: '',
|
| 81 |
authToken: '',
|
| 82 |
-
description: ''
|
|
|
|
| 83 |
};
|
| 84 |
|
| 85 |
const updatedServers = [...localMcpServers, newServer];
|
|
@@ -281,14 +469,91 @@ function Settings({
|
|
| 281 |
</button>
|
| 282 |
</div>
|
| 283 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
<div className="profiles-list">
|
| 285 |
{localMcpServers.map(server => (
|
| 286 |
<div
|
| 287 |
key={server.id}
|
| 288 |
-
className={`profile-item ${server.id === editingMcpServerId ? 'active' : ''}`}
|
| 289 |
onClick={() => setEditingMcpServerId(server.id)}
|
| 290 |
>
|
| 291 |
<span>{server.name}</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
<button
|
| 293 |
className="delete-profile-button"
|
| 294 |
onClick={(e) => {
|
|
@@ -321,20 +586,47 @@ function Settings({
|
|
| 321 |
name: e.target.value
|
| 322 |
})}
|
| 323 |
placeholder="Enter server name"
|
|
|
|
| 324 |
/>
|
| 325 |
</div>
|
| 326 |
|
| 327 |
<div className="setting-item">
|
| 328 |
<label>Endpoint URL:</label>
|
| 329 |
-
<input
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
</div>
|
| 339 |
|
| 340 |
<div className="setting-item">
|
|
@@ -347,6 +639,7 @@ function Settings({
|
|
| 347 |
authToken: e.target.value
|
| 348 |
})}
|
| 349 |
placeholder="Enter authentication token (if required)"
|
|
|
|
| 350 |
/>
|
| 351 |
</div>
|
| 352 |
|
|
@@ -360,9 +653,38 @@ function Settings({
|
|
| 360 |
})}
|
| 361 |
placeholder="Enter server description"
|
| 362 |
rows="3"
|
|
|
|
| 363 |
/>
|
| 364 |
</div>
|
| 365 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
<div className="setting-hint">
|
| 367 |
<button
|
| 368 |
type="button"
|
|
|
|
| 1 |
+
import { useState, useEffect } from 'react';
|
| 2 |
|
| 3 |
function Settings({
|
| 4 |
profiles,
|
|
|
|
| 19 |
model: 'DeepSeek-R1'
|
| 20 |
});
|
| 21 |
const [localMcpServers, setLocalMcpServers] = useState(mcpServers || []);
|
| 22 |
+
|
| 23 |
+
// 当 mcpServers 变化时更新本地状态
|
| 24 |
+
useEffect(() => {
|
| 25 |
+
console.log('mcpServers changed:', mcpServers);
|
| 26 |
+
setLocalMcpServers(mcpServers || []);
|
| 27 |
+
}, [mcpServers]);
|
| 28 |
const [editingProfileId, setEditingProfileId] = useState(activeProfileId);
|
| 29 |
const [editingMcpServerId, setEditingMcpServerId] = useState(null);
|
| 30 |
const [isHintExpanded, setIsHintExpanded] = useState(false);
|
| 31 |
const [isMcpHintExpanded, setIsMcpHintExpanded] = useState(false);
|
| 32 |
+
const [builtInServers, setBuiltInServers] = useState([]);
|
| 33 |
+
const [isLoadingServers, setIsLoadingServers] = useState(false);
|
| 34 |
+
const [serverTestResult, setServerTestResult] = useState(null);
|
| 35 |
+
const [isTestingConnection, setIsTestingConnection] = useState(false);
|
| 36 |
|
| 37 |
const editingProfile = localProfiles.find(p => p.id === editingProfileId) || localProfiles[0];
|
| 38 |
const editingMcpServer = localMcpServers.find(s => s.id === editingMcpServerId) || null;
|
| 39 |
|
| 40 |
+
// 获取可用的内置 MCP 服务器
|
| 41 |
+
useEffect(() => {
|
| 42 |
+
const fetchBuiltInServers = async () => {
|
| 43 |
+
setIsLoadingServers(true);
|
| 44 |
+
try {
|
| 45 |
+
console.log('Fetching built-in MCP servers...');
|
| 46 |
+
const response = await fetch('/api/mcp/servers/available', {
|
| 47 |
+
method: 'GET',
|
| 48 |
+
headers: {
|
| 49 |
+
'Cache-Control': 'no-cache',
|
| 50 |
+
'Pragma': 'no-cache'
|
| 51 |
+
}
|
| 52 |
+
});
|
| 53 |
+
|
| 54 |
+
if (response.ok) {
|
| 55 |
+
const data = await response.json();
|
| 56 |
+
console.log('Received built-in MCP servers:', data);
|
| 57 |
+
setBuiltInServers(data.servers || []);
|
| 58 |
+
} else {
|
| 59 |
+
console.error('Failed to fetch built-in MCP servers:', response.status, response.statusText);
|
| 60 |
+
// 尝试读取错误消息
|
| 61 |
+
const errorText = await response.text();
|
| 62 |
+
console.error('Error details:', errorText);
|
| 63 |
+
}
|
| 64 |
+
} catch (error) {
|
| 65 |
+
console.error('Error fetching built-in MCP servers:', error);
|
| 66 |
+
} finally {
|
| 67 |
+
setIsLoadingServers(false);
|
| 68 |
+
}
|
| 69 |
+
};
|
| 70 |
+
|
| 71 |
+
fetchBuiltInServers();
|
| 72 |
+
|
| 73 |
+
// 每 5 秒刷新一次服务器状态
|
| 74 |
+
const intervalId = setInterval(fetchBuiltInServers, 5000);
|
| 75 |
+
|
| 76 |
+
// 清理定时器
|
| 77 |
+
return () => clearInterval(intervalId);
|
| 78 |
+
}, []);
|
| 79 |
+
|
| 80 |
const handleProfileChange = (updatedProfile) => {
|
| 81 |
setLocalProfiles(localProfiles.map(p =>
|
| 82 |
p.id === updatedProfile.id ? updatedProfile : p
|
|
|
|
| 122 |
}
|
| 123 |
};
|
| 124 |
|
| 125 |
+
// 测试 MCP 服务器连接
|
| 126 |
+
const testMcpServerConnection = async (endpoint, authToken) => {
|
| 127 |
+
if (!endpoint) return;
|
| 128 |
+
|
| 129 |
+
setIsTestingConnection(true);
|
| 130 |
+
setServerTestResult(null);
|
| 131 |
+
|
| 132 |
+
try {
|
| 133 |
+
const response = await fetch('/api/mcp/servers/test-connection', {
|
| 134 |
+
method: 'POST',
|
| 135 |
+
headers: {
|
| 136 |
+
'Content-Type': 'application/json'
|
| 137 |
+
},
|
| 138 |
+
body: JSON.stringify({ endpoint, authToken })
|
| 139 |
+
});
|
| 140 |
+
|
| 141 |
+
const result = await response.json();
|
| 142 |
+
setServerTestResult(result);
|
| 143 |
+
return result.success;
|
| 144 |
+
} catch (error) {
|
| 145 |
+
console.error('Error testing MCP server connection:', error);
|
| 146 |
+
setServerTestResult({
|
| 147 |
+
success: false,
|
| 148 |
+
message: `Connection error: ${error.message}`,
|
| 149 |
+
error: error.message
|
| 150 |
+
});
|
| 151 |
+
return false;
|
| 152 |
+
} finally {
|
| 153 |
+
setIsTestingConnection(false);
|
| 154 |
+
}
|
| 155 |
+
};
|
| 156 |
+
|
| 157 |
+
// 启动内置 MCP 服务器
|
| 158 |
+
const startBuiltInServer = async (serverId) => {
|
| 159 |
+
try {
|
| 160 |
+
const response = await fetch('/api/mcp/servers/start', {
|
| 161 |
+
method: 'POST',
|
| 162 |
+
headers: {
|
| 163 |
+
'Content-Type': 'application/json'
|
| 164 |
+
},
|
| 165 |
+
body: JSON.stringify({ serverId })
|
| 166 |
+
});
|
| 167 |
+
|
| 168 |
+
const result = await response.json();
|
| 169 |
+
|
| 170 |
+
if (result.success) {
|
| 171 |
+
// 检查是否已经添加了这个服务器
|
| 172 |
+
const existingServer = localMcpServers.find(s =>
|
| 173 |
+
s.isBuiltIn && s.builtInId === serverId
|
| 174 |
+
);
|
| 175 |
+
|
| 176 |
+
if (!existingServer) {
|
| 177 |
+
// 添加到本地 MCP 服务器列表
|
| 178 |
+
const builtInServer = builtInServers.find(s => s.id === serverId);
|
| 179 |
+
const newServer = {
|
| 180 |
+
id: `built-in-${serverId}-${Date.now()}`,
|
| 181 |
+
name: builtInServer ? builtInServer.name : `Built-in Server ${serverId}`,
|
| 182 |
+
endpoint: result.endpoint,
|
| 183 |
+
authToken: '',
|
| 184 |
+
description: builtInServer ? builtInServer.description : 'Built-in MCP server',
|
| 185 |
+
isBuiltIn: true,
|
| 186 |
+
builtInId: serverId,
|
| 187 |
+
isRunning: true
|
| 188 |
+
};
|
| 189 |
+
|
| 190 |
+
const updatedServers = [...localMcpServers, newServer];
|
| 191 |
+
setLocalMcpServers(updatedServers);
|
| 192 |
+
setEditingMcpServerId(newServer.id);
|
| 193 |
+
} else {
|
| 194 |
+
// 更新现有服务器的端点
|
| 195 |
+
const updatedServers = localMcpServers.map(s =>
|
| 196 |
+
s.id === existingServer.id
|
| 197 |
+
? { ...s, endpoint: result.endpoint, isRunning: true }
|
| 198 |
+
: s
|
| 199 |
+
);
|
| 200 |
+
setLocalMcpServers(updatedServers);
|
| 201 |
+
setEditingMcpServerId(existingServer.id);
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
// 刷新内置服务器列表
|
| 205 |
+
const response = await fetch('/api/mcp/servers/available');
|
| 206 |
+
if (response.ok) {
|
| 207 |
+
const data = await response.json();
|
| 208 |
+
setBuiltInServers(data.servers || []);
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
return true;
|
| 212 |
+
} else {
|
| 213 |
+
console.error('Failed to start built-in MCP server:', result.error);
|
| 214 |
+
return false;
|
| 215 |
+
}
|
| 216 |
+
} catch (error) {
|
| 217 |
+
console.error('Error starting built-in MCP server:', error);
|
| 218 |
+
return false;
|
| 219 |
+
}
|
| 220 |
+
};
|
| 221 |
+
|
| 222 |
+
// 停止内置 MCP 服务器
|
| 223 |
+
const stopBuiltInServer = async (serverId) => {
|
| 224 |
+
try {
|
| 225 |
+
const response = await fetch('/api/mcp/servers/stop', {
|
| 226 |
+
method: 'POST',
|
| 227 |
+
headers: {
|
| 228 |
+
'Content-Type': 'application/json'
|
| 229 |
+
},
|
| 230 |
+
body: JSON.stringify({ serverId })
|
| 231 |
+
});
|
| 232 |
+
|
| 233 |
+
const result = await response.json();
|
| 234 |
+
|
| 235 |
+
if (result.success) {
|
| 236 |
+
// 更新内置服务器的状态
|
| 237 |
+
const updatedServers = localMcpServers.map(s =>
|
| 238 |
+
s.isBuiltIn && s.builtInId === serverId
|
| 239 |
+
? { ...s, isRunning: false }
|
| 240 |
+
: s
|
| 241 |
+
);
|
| 242 |
+
setLocalMcpServers(updatedServers);
|
| 243 |
+
|
| 244 |
+
// 刷新内置服务器列表
|
| 245 |
+
const response = await fetch('/api/mcp/servers/available');
|
| 246 |
+
if (response.ok) {
|
| 247 |
+
const data = await response.json();
|
| 248 |
+
setBuiltInServers(data.servers || []);
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
return true;
|
| 252 |
+
} else {
|
| 253 |
+
console.error('Failed to stop built-in MCP server:', result.error);
|
| 254 |
+
return false;
|
| 255 |
+
}
|
| 256 |
+
} catch (error) {
|
| 257 |
+
console.error('Error stopping built-in MCP server:', error);
|
| 258 |
+
return false;
|
| 259 |
+
}
|
| 260 |
+
};
|
| 261 |
+
|
| 262 |
const handleAddMcpServer = () => {
|
| 263 |
const newId = `mcp-server-${Date.now()}`;
|
| 264 |
const newServer = {
|
|
|
|
| 266 |
name: `MCP Server ${localMcpServers.length + 1}`,
|
| 267 |
endpoint: '',
|
| 268 |
authToken: '',
|
| 269 |
+
description: '',
|
| 270 |
+
isBuiltIn: false
|
| 271 |
};
|
| 272 |
|
| 273 |
const updatedServers = [...localMcpServers, newServer];
|
|
|
|
| 469 |
</button>
|
| 470 |
</div>
|
| 471 |
|
| 472 |
+
{/* Built-in MCP Servers */}
|
| 473 |
+
<div className="built-in-servers-section">
|
| 474 |
+
<h4>Built-in MCP Servers</h4>
|
| 475 |
+
<div className="built-in-servers-list">
|
| 476 |
+
{isLoadingServers ? (
|
| 477 |
+
<div className="loading-indicator">Loading built-in servers...</div>
|
| 478 |
+
) : builtInServers.length > 0 ? (
|
| 479 |
+
builtInServers.map(server => {
|
| 480 |
+
// 检查这个内置服务器是否已经添加到用户的服务器列表中
|
| 481 |
+
const isAdded = localMcpServers.some(s => s.isBuiltIn && s.builtInId === server.id);
|
| 482 |
+
|
| 483 |
+
return (
|
| 484 |
+
<div key={server.id} className="built-in-server-item">
|
| 485 |
+
<div className="server-info">
|
| 486 |
+
<span className="server-name">{server.name}</span>
|
| 487 |
+
<span className={`server-status ${server.isRunning ? 'running' : 'stopped'}`}>
|
| 488 |
+
{server.isRunning ? 'Running' : 'Stopped'}
|
| 489 |
+
</span>
|
| 490 |
+
</div>
|
| 491 |
+
<div className="server-actions">
|
| 492 |
+
{isAdded ? (
|
| 493 |
+
<button
|
| 494 |
+
className="server-action-button"
|
| 495 |
+
onClick={() => {
|
| 496 |
+
const addedServer = localMcpServers.find(s => s.isBuiltIn && s.builtInId === server.id);
|
| 497 |
+
if (addedServer) {
|
| 498 |
+
setEditingMcpServerId(addedServer.id);
|
| 499 |
+
}
|
| 500 |
+
}}
|
| 501 |
+
>
|
| 502 |
+
View
|
| 503 |
+
</button>
|
| 504 |
+
) : (
|
| 505 |
+
<button
|
| 506 |
+
className="server-action-button"
|
| 507 |
+
onClick={() => startBuiltInServer(server.id)}
|
| 508 |
+
disabled={server.isRunning}
|
| 509 |
+
>
|
| 510 |
+
Add & Start
|
| 511 |
+
</button>
|
| 512 |
+
)}
|
| 513 |
+
|
| 514 |
+
{server.isRunning ? (
|
| 515 |
+
<button
|
| 516 |
+
className="server-action-button stop"
|
| 517 |
+
onClick={() => stopBuiltInServer(server.id)}
|
| 518 |
+
>
|
| 519 |
+
Stop
|
| 520 |
+
</button>
|
| 521 |
+
) : (
|
| 522 |
+
<button
|
| 523 |
+
className="server-action-button start"
|
| 524 |
+
onClick={() => startBuiltInServer(server.id)}
|
| 525 |
+
>
|
| 526 |
+
Start
|
| 527 |
+
</button>
|
| 528 |
+
)}
|
| 529 |
+
</div>
|
| 530 |
+
</div>
|
| 531 |
+
);
|
| 532 |
+
})
|
| 533 |
+
) : (
|
| 534 |
+
<div className="empty-state">
|
| 535 |
+
<p>No built-in MCP servers available.</p>
|
| 536 |
+
</div>
|
| 537 |
+
)}
|
| 538 |
+
</div>
|
| 539 |
+
</div>
|
| 540 |
+
|
| 541 |
+
{/* User-added MCP Servers */}
|
| 542 |
+
<h4>Your MCP Servers</h4>
|
| 543 |
<div className="profiles-list">
|
| 544 |
{localMcpServers.map(server => (
|
| 545 |
<div
|
| 546 |
key={server.id}
|
| 547 |
+
className={`profile-item ${server.id === editingMcpServerId ? 'active' : ''} ${server.isBuiltIn && server.isRunning ? 'running' : ''}`}
|
| 548 |
onClick={() => setEditingMcpServerId(server.id)}
|
| 549 |
>
|
| 550 |
<span>{server.name}</span>
|
| 551 |
+
{server.isBuiltIn && server.isRunning && (
|
| 552 |
+
<span className="server-badge running">Running</span>
|
| 553 |
+
)}
|
| 554 |
+
{server.isBuiltIn && !server.isRunning && (
|
| 555 |
+
<span className="server-badge stopped">Stopped</span>
|
| 556 |
+
)}
|
| 557 |
<button
|
| 558 |
className="delete-profile-button"
|
| 559 |
onClick={(e) => {
|
|
|
|
| 586 |
name: e.target.value
|
| 587 |
})}
|
| 588 |
placeholder="Enter server name"
|
| 589 |
+
disabled={editingMcpServer.isBuiltIn}
|
| 590 |
/>
|
| 591 |
</div>
|
| 592 |
|
| 593 |
<div className="setting-item">
|
| 594 |
<label>Endpoint URL:</label>
|
| 595 |
+
<div className="input-with-button">
|
| 596 |
+
<input
|
| 597 |
+
type="text"
|
| 598 |
+
value={editingMcpServer.endpoint}
|
| 599 |
+
onChange={(e) => handleMcpServerChange({
|
| 600 |
+
...editingMcpServer,
|
| 601 |
+
endpoint: e.target.value
|
| 602 |
+
})}
|
| 603 |
+
placeholder="Enter MCP server endpoint URL"
|
| 604 |
+
disabled={editingMcpServer.isBuiltIn}
|
| 605 |
+
/>
|
| 606 |
+
<button
|
| 607 |
+
type="button"
|
| 608 |
+
className="test-connection-button"
|
| 609 |
+
onClick={() => testMcpServerConnection(editingMcpServer.endpoint, editingMcpServer.authToken)}
|
| 610 |
+
disabled={!editingMcpServer.endpoint || isTestingConnection}
|
| 611 |
+
>
|
| 612 |
+
{isTestingConnection ? 'Testing...' : 'Test Connection'}
|
| 613 |
+
</button>
|
| 614 |
+
</div>
|
| 615 |
+
{serverTestResult && (
|
| 616 |
+
<div className={`connection-test-result ${serverTestResult.success ? 'success' : 'error'}`}>
|
| 617 |
+
{serverTestResult.message}
|
| 618 |
+
{serverTestResult.success && serverTestResult.tools && (
|
| 619 |
+
<div className="available-tools">
|
| 620 |
+
<p>Available tools: {serverTestResult.tools.length}</p>
|
| 621 |
+
<ul>
|
| 622 |
+
{serverTestResult.tools.map((tool, index) => (
|
| 623 |
+
<li key={index}>{tool.name} - {tool.description}</li>
|
| 624 |
+
))}
|
| 625 |
+
</ul>
|
| 626 |
+
</div>
|
| 627 |
+
)}
|
| 628 |
+
</div>
|
| 629 |
+
)}
|
| 630 |
</div>
|
| 631 |
|
| 632 |
<div className="setting-item">
|
|
|
|
| 639 |
authToken: e.target.value
|
| 640 |
})}
|
| 641 |
placeholder="Enter authentication token (if required)"
|
| 642 |
+
disabled={editingMcpServer.isBuiltIn}
|
| 643 |
/>
|
| 644 |
</div>
|
| 645 |
|
|
|
|
| 653 |
})}
|
| 654 |
placeholder="Enter server description"
|
| 655 |
rows="3"
|
| 656 |
+
disabled={editingMcpServer.isBuiltIn}
|
| 657 |
/>
|
| 658 |
</div>
|
| 659 |
|
| 660 |
+
{editingMcpServer.isBuiltIn && (
|
| 661 |
+
<div className="built-in-server-controls">
|
| 662 |
+
<p className="built-in-server-note">
|
| 663 |
+
This is a built-in MCP server managed by the application.
|
| 664 |
+
{editingMcpServer.isRunning
|
| 665 |
+
? ' It is currently running.'
|
| 666 |
+
: ' It is currently stopped.'}
|
| 667 |
+
</p>
|
| 668 |
+
{editingMcpServer.isRunning ? (
|
| 669 |
+
<button
|
| 670 |
+
type="button"
|
| 671 |
+
className="server-control-button stop"
|
| 672 |
+
onClick={() => stopBuiltInServer(editingMcpServer.builtInId)}
|
| 673 |
+
>
|
| 674 |
+
Stop Server
|
| 675 |
+
</button>
|
| 676 |
+
) : (
|
| 677 |
+
<button
|
| 678 |
+
type="button"
|
| 679 |
+
className="server-control-button start"
|
| 680 |
+
onClick={() => startBuiltInServer(editingMcpServer.builtInId)}
|
| 681 |
+
>
|
| 682 |
+
Start Server
|
| 683 |
+
</button>
|
| 684 |
+
)}
|
| 685 |
+
</div>
|
| 686 |
+
)}
|
| 687 |
+
|
| 688 |
<div className="setting-hint">
|
| 689 |
<button
|
| 690 |
type="button"
|
src/hooks/useLocalStorage.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import { useState, useEffect } from 'react';
|
| 2 |
|
| 3 |
-
function useLocalStorage(key, initialValue) {
|
|
|
|
| 4 |
// 获取初始值
|
| 5 |
const [storedValue, setStoredValue] = useState(() => {
|
| 6 |
try {
|
|
@@ -14,14 +15,16 @@ function useLocalStorage(key, initialValue) {
|
|
| 14 |
|
| 15 |
// 监听值的变化并更新到 localStorage
|
| 16 |
useEffect(() => {
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
-
}, [key, storedValue]);
|
| 23 |
|
| 24 |
return [storedValue, setStoredValue];
|
| 25 |
}
|
| 26 |
|
| 27 |
-
export default useLocalStorage;
|
|
|
|
| 1 |
import { useState, useEffect } from 'react';
|
| 2 |
|
| 3 |
+
function useLocalStorage(key, initialValue, options = {}) {
|
| 4 |
+
const { disableCache = false } = options;
|
| 5 |
// 获取初始值
|
| 6 |
const [storedValue, setStoredValue] = useState(() => {
|
| 7 |
try {
|
|
|
|
| 15 |
|
| 16 |
// 监听值的变化并更新到 localStorage
|
| 17 |
useEffect(() => {
|
| 18 |
+
if (!disableCache) {
|
| 19 |
+
try {
|
| 20 |
+
window.localStorage.setItem(key, JSON.stringify(storedValue));
|
| 21 |
+
} catch (error) {
|
| 22 |
+
console.error(error);
|
| 23 |
+
}
|
| 24 |
}
|
| 25 |
+
}, [key, storedValue, disableCache]);
|
| 26 |
|
| 27 |
return [storedValue, setStoredValue];
|
| 28 |
}
|
| 29 |
|
| 30 |
+
export default useLocalStorage;
|
src/index.css
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
/* Import MCP styles */
|
| 2 |
@import './styles/mcp.css';
|
|
|
|
| 3 |
|
| 4 |
@tailwind base;
|
| 5 |
@tailwind components;
|
|
|
|
| 1 |
/* Import MCP styles */
|
| 2 |
@import './styles/mcp.css';
|
| 3 |
+
@import './styles/mcp-settings.css';
|
| 4 |
|
| 5 |
@tailwind base;
|
| 6 |
@tailwind components;
|
src/styles/mcp-settings.css
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* MCP 服务器管理样式 */
|
| 2 |
+
|
| 3 |
+
/* 内置服务器部分 */
|
| 4 |
+
.built-in-servers-section {
|
| 5 |
+
margin-bottom: 20px;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
.built-in-servers-section h4 {
|
| 9 |
+
font-size: 14px;
|
| 10 |
+
font-weight: 600;
|
| 11 |
+
margin-bottom: 10px;
|
| 12 |
+
color: var(--text-color);
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.built-in-servers-list {
|
| 16 |
+
display: flex;
|
| 17 |
+
flex-direction: column;
|
| 18 |
+
gap: 8px;
|
| 19 |
+
margin-bottom: 15px;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.built-in-server-item {
|
| 23 |
+
display: flex;
|
| 24 |
+
justify-content: space-between;
|
| 25 |
+
align-items: center;
|
| 26 |
+
padding: 10px 12px;
|
| 27 |
+
background-color: #f9f9f9;
|
| 28 |
+
border: 1px solid #e0e0e0;
|
| 29 |
+
border-radius: 4px;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
.server-info {
|
| 33 |
+
display: flex;
|
| 34 |
+
flex-direction: column;
|
| 35 |
+
gap: 4px;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.server-name {
|
| 39 |
+
font-weight: 500;
|
| 40 |
+
font-size: 14px;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
.server-status {
|
| 44 |
+
font-size: 12px;
|
| 45 |
+
padding: 2px 6px;
|
| 46 |
+
border-radius: 10px;
|
| 47 |
+
display: inline-block;
|
| 48 |
+
width: fit-content;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
.server-status.running {
|
| 52 |
+
background-color: #e6f7e6;
|
| 53 |
+
color: #2e7d32;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.server-status.stopped {
|
| 57 |
+
background-color: #f5f5f5;
|
| 58 |
+
color: #757575;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.server-actions {
|
| 62 |
+
display: flex;
|
| 63 |
+
gap: 8px;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.server-action-button {
|
| 67 |
+
padding: 4px 10px;
|
| 68 |
+
font-size: 12px;
|
| 69 |
+
border-radius: 4px;
|
| 70 |
+
border: 1px solid #e0e0e0;
|
| 71 |
+
background-color: white;
|
| 72 |
+
cursor: pointer;
|
| 73 |
+
transition: all 0.2s;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
.server-action-button:hover {
|
| 77 |
+
background-color: #f0f0f0;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.server-action-button.start {
|
| 81 |
+
border-color: #4caf50;
|
| 82 |
+
color: #2e7d32;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.server-action-button.start:hover {
|
| 86 |
+
background-color: #e8f5e9;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.server-action-button.stop {
|
| 90 |
+
border-color: #f44336;
|
| 91 |
+
color: #d32f2f;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.server-action-button.stop:hover {
|
| 95 |
+
background-color: #ffebee;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.server-action-button:disabled {
|
| 99 |
+
opacity: 0.5;
|
| 100 |
+
cursor: not-allowed;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
/* 用户服务器列表 */
|
| 104 |
+
.profiles-list h4 {
|
| 105 |
+
font-size: 14px;
|
| 106 |
+
font-weight: 600;
|
| 107 |
+
margin-bottom: 10px;
|
| 108 |
+
color: var(--text-color);
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.profile-item.running {
|
| 112 |
+
border-left: 3px solid #4caf50;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.server-badge {
|
| 116 |
+
font-size: 11px;
|
| 117 |
+
padding: 2px 6px;
|
| 118 |
+
border-radius: 10px;
|
| 119 |
+
margin-left: 8px;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.server-badge.running {
|
| 123 |
+
background-color: #e6f7e6;
|
| 124 |
+
color: #2e7d32;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.server-badge.stopped {
|
| 128 |
+
background-color: #f5f5f5;
|
| 129 |
+
color: #757575;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/* 连接测试 */
|
| 133 |
+
.input-with-button {
|
| 134 |
+
display: flex;
|
| 135 |
+
gap: 8px;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.input-with-button input {
|
| 139 |
+
flex: 1;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.test-connection-button {
|
| 143 |
+
padding: 6px 12px;
|
| 144 |
+
font-size: 12px;
|
| 145 |
+
border-radius: 4px;
|
| 146 |
+
border: 1px solid #e0e0e0;
|
| 147 |
+
background-color: white;
|
| 148 |
+
cursor: pointer;
|
| 149 |
+
white-space: nowrap;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
.test-connection-button:hover {
|
| 153 |
+
background-color: #f0f0f0;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.test-connection-button:disabled {
|
| 157 |
+
opacity: 0.5;
|
| 158 |
+
cursor: not-allowed;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
.connection-test-result {
|
| 162 |
+
margin-top: 8px;
|
| 163 |
+
padding: 8px 12px;
|
| 164 |
+
border-radius: 4px;
|
| 165 |
+
font-size: 13px;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.connection-test-result.success {
|
| 169 |
+
background-color: #e8f5e9;
|
| 170 |
+
color: #2e7d32;
|
| 171 |
+
border: 1px solid #c8e6c9;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
.connection-test-result.error {
|
| 175 |
+
background-color: #ffebee;
|
| 176 |
+
color: #c62828;
|
| 177 |
+
border: 1px solid #ffcdd2;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
.available-tools {
|
| 181 |
+
margin-top: 8px;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.available-tools p {
|
| 185 |
+
font-weight: 500;
|
| 186 |
+
margin-bottom: 4px;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
.available-tools ul {
|
| 190 |
+
margin-left: 20px;
|
| 191 |
+
font-size: 12px;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.available-tools li {
|
| 195 |
+
margin-bottom: 2px;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
/* 内置服务器控制 */
|
| 199 |
+
.built-in-server-controls {
|
| 200 |
+
margin-top: 15px;
|
| 201 |
+
padding: 12px;
|
| 202 |
+
background-color: #f5f5f5;
|
| 203 |
+
border-radius: 4px;
|
| 204 |
+
border: 1px solid #e0e0e0;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.built-in-server-note {
|
| 208 |
+
font-size: 13px;
|
| 209 |
+
color: #555;
|
| 210 |
+
margin-bottom: 10px;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.server-control-button {
|
| 214 |
+
padding: 6px 12px;
|
| 215 |
+
font-size: 13px;
|
| 216 |
+
border-radius: 4px;
|
| 217 |
+
cursor: pointer;
|
| 218 |
+
transition: all 0.2s;
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
.server-control-button.start {
|
| 222 |
+
background-color: #4caf50;
|
| 223 |
+
color: white;
|
| 224 |
+
border: none;
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
.server-control-button.start:hover {
|
| 228 |
+
background-color: #388e3c;
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
.server-control-button.stop {
|
| 232 |
+
background-color: #f44336;
|
| 233 |
+
color: white;
|
| 234 |
+
border: none;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
.server-control-button.stop:hover {
|
| 238 |
+
background-color: #d32f2f;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
/* 加载指示器 */
|
| 242 |
+
.loading-indicator {
|
| 243 |
+
padding: 10px;
|
| 244 |
+
text-align: center;
|
| 245 |
+
color: #757575;
|
| 246 |
+
font-style: italic;
|
| 247 |
+
font-size: 13px;
|
| 248 |
+
}
|
vite.config.js
CHANGED
|
@@ -2,6 +2,8 @@ import { defineConfig } from 'vite'
|
|
| 2 |
import react from '@vitejs/plugin-react'
|
| 3 |
|
| 4 |
export default defineConfig({
|
|
|
|
|
|
|
| 5 |
plugins: [react()],
|
| 6 |
server: {
|
| 7 |
proxy: {
|
|
@@ -15,7 +17,18 @@ export default defineConfig({
|
|
| 15 |
},
|
| 16 |
'/api': {
|
| 17 |
target: 'http://localhost:7860',
|
| 18 |
-
changeOrigin: true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
}
|
| 20 |
}
|
| 21 |
}
|
|
|
|
| 2 |
import react from '@vitejs/plugin-react'
|
| 3 |
|
| 4 |
export default defineConfig({
|
| 5 |
+
// 启用详细日志
|
| 6 |
+
logLevel: 'info',
|
| 7 |
plugins: [react()],
|
| 8 |
server: {
|
| 9 |
proxy: {
|
|
|
|
| 17 |
},
|
| 18 |
'/api': {
|
| 19 |
target: 'http://localhost:7860',
|
| 20 |
+
changeOrigin: true,
|
| 21 |
+
configure: (proxy, options) => {
|
| 22 |
+
proxy.on('error', (err, req, res) => {
|
| 23 |
+
console.log('proxy error', err);
|
| 24 |
+
});
|
| 25 |
+
proxy.on('proxyReq', (proxyReq, req, res) => {
|
| 26 |
+
console.log('Sending Request to the Target:', req.method, req.url);
|
| 27 |
+
});
|
| 28 |
+
proxy.on('proxyRes', (proxyRes, req, res) => {
|
| 29 |
+
console.log('Received Response from the Target:', proxyRes.statusCode, req.url);
|
| 30 |
+
});
|
| 31 |
+
}
|
| 32 |
}
|
| 33 |
}
|
| 34 |
}
|