moelove commited on
Commit
f044ea5
·
unverified ·
1 Parent(s): 24caae0

add mcp sdk (#3)

Browse files

Signed-off-by: Jintao Zhang <zhangjintao9020@gmail.com>

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
- // Note: Run the mock MCP server separately with: node server/start-mock-mcp.js
 
 
 
 
 
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={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
- type="text"
331
- value={editingMcpServer.endpoint}
332
- onChange={(e) => handleMcpServerChange({
333
- ...editingMcpServer,
334
- endpoint: e.target.value
335
- })}
336
- placeholder="Enter MCP server endpoint URL"
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
- try {
18
- window.localStorage.setItem(key, JSON.stringify(storedValue));
19
- } catch (error) {
20
- console.error(error);
 
 
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
  }