accoding-mcp-server-test-new 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -9
- package/build/accoding/accoding-base-service.d.ts +29 -0
- package/build/accoding/accoding-service-impl.d.ts +5 -0
- package/build/accoding/accoding-service-impl.js +21 -0
- package/build/accoding/accoding-service-impl.js.map +1 -1
- package/build/accoding/api/contest-api.js +3 -2
- package/build/accoding/api/contest-api.js.map +1 -1
- package/build/accoding/api/problem-api.js +2 -1
- package/build/accoding/api/problem-api.js.map +1 -1
- package/build/accoding/api/user-api.d.ts +27 -3
- package/build/accoding/api/user-api.js +92 -7
- package/build/accoding/api/user-api.js.map +1 -1
- package/build/index.js +40 -37
- package/build/index.js.map +1 -1
- package/build/mcp/tools/contest-tools.js +3 -1
- package/build/mcp/tools/contest-tools.js.map +1 -1
- package/build/mcp/tools/user-tools.d.ts +20 -0
- package/build/mcp/tools/user-tools.js +219 -1
- package/build/mcp/tools/user-tools.js.map +1 -1
- package/build/transport/http-server.d.ts +4 -0
- package/build/transport/http-server.js +192 -0
- package/build/transport/http-server.js.map +1 -0
- package/build/transport/mcp-server-factory.d.ts +7 -0
- package/build/transport/mcp-server-factory.js +48 -0
- package/build/transport/mcp-server-factory.js.map +1 -0
- package/build/utils/http-utils.d.ts +14 -0
- package/build/utils/http-utils.js +51 -0
- package/build/utils/http-utils.js.map +1 -0
- package/build/utils/logger.d.ts +5 -0
- package/build/utils/logger.js +8 -1
- package/build/utils/logger.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -29,8 +29,12 @@ accoding-mcp-server/
|
|
|
29
29
|
│ │ ├── resource-registry.ts # 资源注册基类
|
|
30
30
|
│ │ ├── problem-resources.ts # 题目资源
|
|
31
31
|
│ │ └── contest-resources.ts # 比赛资源
|
|
32
|
+
│ ├── transport/ # 传输层
|
|
33
|
+
│ │ ├── http-server.ts # HTTP 服务器实现
|
|
34
|
+
│ │ └── mcp-server-factory.ts # MCP Server 创建工厂
|
|
32
35
|
│ ├── utils/
|
|
33
|
-
│ │
|
|
36
|
+
│ │ ├── logger.ts # 日志工具
|
|
37
|
+
│ │ └── http-utils.ts # HTTP 工具函数(CORS、cookie解析)
|
|
34
38
|
│ └── index.ts # 程序入口
|
|
35
39
|
├── package.json
|
|
36
40
|
├── tsconfig.json
|
|
@@ -41,8 +45,11 @@ accoding-mcp-server/
|
|
|
41
45
|
|
|
42
46
|
1. **工厂模式**:使用 `AccodingServiceFactory` 创建服务实例
|
|
43
47
|
2. **注册模式**:使用 `RegistryBase` 统一管理工具和资源的注册
|
|
44
|
-
3.
|
|
45
|
-
|
|
48
|
+
3. **传输层抽象**:支持 stdio 和 HTTP 两种传输模式
|
|
49
|
+
4. **会话隔离**:HTTP 模式下每个连接独立的会话和服务实例
|
|
50
|
+
5. **分层架构**:
|
|
51
|
+
- 入口层 (`index.ts`) - 解析参数、选择传输模式
|
|
52
|
+
- 传输层 (`transport/`) - HTTP 服务器实现和会话管理
|
|
46
53
|
- 服务层 (`accoding/`) - 定义接口、实现业务逻辑
|
|
47
54
|
- API 层 (`accoding/api/`) - RESTful API 调用封装
|
|
48
55
|
- 工具/资源层 (`mcp/`) - MCP 工具和资源的注册
|
|
@@ -56,17 +63,84 @@ npm install
|
|
|
56
63
|
# 编译
|
|
57
64
|
npm run build
|
|
58
65
|
|
|
59
|
-
#
|
|
60
|
-
node build/index.js
|
|
66
|
+
# stdio 模式运行(默认,用于本地开发)
|
|
67
|
+
node build/index.js --session "your-cookie"
|
|
61
68
|
|
|
62
|
-
#
|
|
63
|
-
node build/index.js --
|
|
69
|
+
# HTTP 模式运行(用于服务器部署)
|
|
70
|
+
node build/index.js --mode http --port 3000
|
|
64
71
|
```
|
|
65
72
|
|
|
73
|
+
### 传输模式说明
|
|
74
|
+
|
|
75
|
+
#### stdio 模式(默认)
|
|
76
|
+
- 使用标准输入输出进行通信
|
|
77
|
+
- 适合本地开发和 CLI 工具集成
|
|
78
|
+
- 通过 `--session` 参数传递 Accoding session cookie
|
|
79
|
+
|
|
80
|
+
#### HTTP 模式
|
|
81
|
+
- 通过 HTTP POST 请求进行通信
|
|
82
|
+
- 支持多客户端并发访问
|
|
83
|
+
- 每个连接独立的会话隔离
|
|
84
|
+
- Session cookie 通过 HTTP 请求头传递:
|
|
85
|
+
- `X-Accoding-Session` header(优先级最高)
|
|
86
|
+
- `Cookie` header(自动解析)
|
|
87
|
+
|
|
66
88
|
## 环境变量
|
|
67
89
|
|
|
68
|
-
- `
|
|
69
|
-
|
|
90
|
+
- `ACCODING_SESSION`: Accoding 会话 Cookie(stdio 模式使用,HTTP 模式从请求头获取)
|
|
91
|
+
|
|
92
|
+
## HTTP 模式使用说明
|
|
93
|
+
|
|
94
|
+
### 启动服务器
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
node build/index.js --mode http --port 3000
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 客户端请求示例
|
|
101
|
+
|
|
102
|
+
#### 1. 初始化连接
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
curl -X POST http://localhost:3000/mcp \
|
|
106
|
+
-H "Content-Type: application/json" \
|
|
107
|
+
-H "X-Accoding-Session: your-session-cookie" \
|
|
108
|
+
-d '{
|
|
109
|
+
"jsonrpc": "2.0",
|
|
110
|
+
"id": "1",
|
|
111
|
+
"method": "initialize",
|
|
112
|
+
"params": {}
|
|
113
|
+
}'
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
响应会包含 `mcp-session-id` header,后续请求需要使用此 ID。
|
|
117
|
+
|
|
118
|
+
#### 2. 调用工具
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
curl -X POST http://localhost:3000/mcp \
|
|
122
|
+
-H "Content-Type: application/json" \
|
|
123
|
+
-H "mcp-session-id: <从初始化响应中获取的session-id>" \
|
|
124
|
+
-H "X-Accoding-Session: your-session-cookie" \
|
|
125
|
+
-d '{
|
|
126
|
+
"jsonrpc": "2.0",
|
|
127
|
+
"id": "2",
|
|
128
|
+
"method": "tools/call",
|
|
129
|
+
"params": {
|
|
130
|
+
"name": "get_contest_problems",
|
|
131
|
+
"arguments": {
|
|
132
|
+
"contestId": "12345"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 会话隔离
|
|
139
|
+
|
|
140
|
+
- 每个 HTTP 连接都有独立的会话 ID
|
|
141
|
+
- 每个会话都有独立的 `AccodingService` 实例
|
|
142
|
+
- 不同客户端的 session cookie 互不影响
|
|
143
|
+
- 连接关闭后自动清理会话资源
|
|
70
144
|
|
|
71
145
|
## 待实现功能
|
|
72
146
|
|
|
@@ -6,6 +6,35 @@ export interface AccodingBaseService {
|
|
|
6
6
|
* 根据比赛ID获取比赛题目详情
|
|
7
7
|
*/
|
|
8
8
|
getContestProblems(contestId: string): Promise<any>;
|
|
9
|
+
/**
|
|
10
|
+
* 获取当前登录用户信息
|
|
11
|
+
* 需要有效的 session cookie
|
|
12
|
+
*/
|
|
13
|
+
getUserInfo(): Promise<any>;
|
|
14
|
+
/**
|
|
15
|
+
* 获取班级列表
|
|
16
|
+
* 根据班级名称和课程名称搜索班级,如果都为空则返回全部班级
|
|
17
|
+
* 需要有效的 session cookie
|
|
18
|
+
*/
|
|
19
|
+
getClassList(className?: string, courseName?: string): Promise<any>;
|
|
20
|
+
/**
|
|
21
|
+
* 获取当前用户加入的班级列表
|
|
22
|
+
* 返回当前登录用户加入的所有班级信息
|
|
23
|
+
* 需要有效的 session cookie
|
|
24
|
+
*/
|
|
25
|
+
getMyClassList(): Promise<any>;
|
|
26
|
+
/**
|
|
27
|
+
* 获取用户提交统计
|
|
28
|
+
* 返回各种提交状态的数量:AC(通过)、CE(编译错误)、PE(输出错误)、OE(其他错误)、WA(未通过)、TLE(超时)
|
|
29
|
+
* 需要有效的 session cookie
|
|
30
|
+
*/
|
|
31
|
+
getUserSubmissionStats(): Promise<any>;
|
|
32
|
+
/**
|
|
33
|
+
* 获取用户本周提交情况
|
|
34
|
+
* 返回7天中每天的提交情况,每天包含6种状态的数量(按顺序:AC、CE、WA、PE、TLE、OE)
|
|
35
|
+
* 需要有效的 session cookie
|
|
36
|
+
*/
|
|
37
|
+
getUserWeeklySubmissions(): Promise<any>;
|
|
9
38
|
/**
|
|
10
39
|
* 检查当前服务是否有有效的认证凭证
|
|
11
40
|
*/
|
|
@@ -6,5 +6,10 @@ export declare class AccodingServiceImpl implements AccodingBaseService {
|
|
|
6
6
|
private session?;
|
|
7
7
|
constructor(session?: string);
|
|
8
8
|
getContestProblems(contestId: string): Promise<any>;
|
|
9
|
+
getUserInfo(): Promise<any>;
|
|
10
|
+
getClassList(className?: string, courseName?: string): Promise<any>;
|
|
11
|
+
getMyClassList(): Promise<any>;
|
|
12
|
+
getUserSubmissionStats(): Promise<any>;
|
|
13
|
+
getUserWeeklySubmissions(): Promise<any>;
|
|
9
14
|
isAuthenticated(): boolean;
|
|
10
15
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as userApi from "./api/user-api.js";
|
|
1
2
|
import * as contestApi from "./api/contest-api.js";
|
|
2
3
|
/**
|
|
3
4
|
* Accoding服务实现
|
|
@@ -11,6 +12,26 @@ export class AccodingServiceImpl {
|
|
|
11
12
|
// 调用真实的contestApi获取比赛题目
|
|
12
13
|
return await contestApi.getContestProblems(contestId, this.session);
|
|
13
14
|
}
|
|
15
|
+
async getUserInfo() {
|
|
16
|
+
// 调用真实的userApi获取用户信息
|
|
17
|
+
return await userApi.getUserInfo(this.session);
|
|
18
|
+
}
|
|
19
|
+
async getClassList(className, courseName) {
|
|
20
|
+
// 调用真实的userApi获取班级列表
|
|
21
|
+
return await userApi.getClassList(className, courseName, this.session);
|
|
22
|
+
}
|
|
23
|
+
async getMyClassList() {
|
|
24
|
+
// 调用真实的userApi获取我的班级列表
|
|
25
|
+
return await userApi.getMyClassList(this.session);
|
|
26
|
+
}
|
|
27
|
+
async getUserSubmissionStats() {
|
|
28
|
+
// 调用真实的userApi获取用户提交统计
|
|
29
|
+
return await userApi.getUserSubmissionStats(this.session);
|
|
30
|
+
}
|
|
31
|
+
async getUserWeeklySubmissions() {
|
|
32
|
+
// 调用真实的userApi获取用户本周提交情况
|
|
33
|
+
return await userApi.getUserWeeklySubmissions(this.session);
|
|
34
|
+
}
|
|
14
35
|
isAuthenticated() {
|
|
15
36
|
return !!this.session;
|
|
16
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accoding-service-impl.js","sourceRoot":"","sources":["../../src/accoding/accoding-service-impl.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"accoding-service-impl.js","sourceRoot":"","sources":["../../src/accoding/accoding-service-impl.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAC7C,OAAO,KAAK,UAAU,MAAM,sBAAsB,CAAC;AAGnD;;GAEG;AACH,MAAM,OAAO,mBAAmB;IACpB,OAAO,CAAU;IAEzB,YAAY,OAAgB;QACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,SAAiB;QACtC,wBAAwB;QACxB,OAAO,MAAM,UAAU,CAAC,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,WAAW;QACb,qBAAqB;QACrB,OAAO,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,SAAkB,EAAE,UAAmB;QACtD,qBAAqB;QACrB,OAAO,MAAM,OAAO,CAAC,YAAY,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3E,CAAC;IAED,KAAK,CAAC,cAAc;QAChB,uBAAuB;QACvB,OAAO,MAAM,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,sBAAsB;QACxB,uBAAuB;QACvB,OAAO,MAAM,OAAO,CAAC,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9D,CAAC;IAED,KAAK,CAAC,wBAAwB;QAC1B,yBAAyB;QACzB,OAAO,MAAM,OAAO,CAAC,wBAAwB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAChE,CAAC;IAED,eAAe;QACX,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;IAC1B,CAAC;CACJ"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Accoding平台比赛相关API调用
|
|
3
3
|
*/
|
|
4
|
-
const API_BASE_URL = "https://accoding.buaa.edu.cn";
|
|
4
|
+
//const API_BASE_URL = "https://accoding.buaa.edu.cn";
|
|
5
|
+
const API_BASE_URL = "https://http://10.251.252.33";
|
|
5
6
|
/**
|
|
6
7
|
* 向Accoding API发起HTTP请求
|
|
7
8
|
*/
|
|
@@ -12,7 +13,7 @@ async function apiRequest(endpoint, options = {}, session) {
|
|
|
12
13
|
...options.headers
|
|
13
14
|
};
|
|
14
15
|
if (session) {
|
|
15
|
-
headers["
|
|
16
|
+
headers["Authorization"] = session;
|
|
16
17
|
}
|
|
17
18
|
const response = await fetch(url, {
|
|
18
19
|
...options,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contest-api.js","sourceRoot":"","sources":["../../../src/accoding/api/contest-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,YAAY,GAAG,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"contest-api.js","sourceRoot":"","sources":["../../../src/accoding/api/contest-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"problem-api.js","sourceRoot":"","sources":["../../../src/accoding/api/problem-api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,YAAY,GAAG,8BAA8B,CAAC;AAEpD;;GAEG;AACH,KAAK,UAAU,UAAU,CACrB,QAAgB,EAChB,UAAuB,EAAE,EACzB,OAAgB;IAEhB,MAAM,GAAG,GAAG,GAAG,YAAY,GAAG,QAAQ,EAAE,CAAC;IACzC,MAAM,OAAO,GAA2B;QACpC,cAAc,EAAE,kBAAkB;QAClC,GAAI,OAAO,CAAC,OAAkC;KACjD,CAAC;IAEF,IAAI,OAAO,EAAE,CAAC;QACV,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC;IAChC,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC9B,GAAG,OAAO;QACV,OAAO;KACV,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,YAAY,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;AACjC,CAAC;;AAED,iCAAiC"}
|
|
1
|
+
{"version":3,"file":"problem-api.js","sourceRoot":"","sources":["../../../src/accoding/api/problem-api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,sDAAsD;AACtD,MAAM,YAAY,GAAG,8BAA8B,CAAC;AAEpD;;GAEG;AACH,KAAK,UAAU,UAAU,CACrB,QAAgB,EAChB,UAAuB,EAAE,EACzB,OAAgB;IAEhB,MAAM,GAAG,GAAG,GAAG,YAAY,GAAG,QAAQ,EAAE,CAAC;IACzC,MAAM,OAAO,GAA2B;QACpC,cAAc,EAAE,kBAAkB;QAClC,GAAI,OAAO,CAAC,OAAkC;KACjD,CAAC;IAEF,IAAI,OAAO,EAAE,CAAC;QACV,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC;IAChC,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC9B,GAAG,OAAO;QACV,OAAO;KACV,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,YAAY,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;AACjC,CAAC;;AAED,iCAAiC"}
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* 获取当前登录用户信息
|
|
3
|
+
* 需要有效的 session cookie
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
5
|
+
export declare function getUserInfo(session?: string): Promise<any>;
|
|
6
|
+
/**
|
|
7
|
+
* 获取班级列表
|
|
8
|
+
* 根据班级名称和课程名称搜索班级,如果都为空则返回全部班级
|
|
9
|
+
* 需要有效的 session cookie
|
|
10
|
+
*/
|
|
11
|
+
export declare function getClassList(className?: string, courseName?: string, session?: string): Promise<any>;
|
|
12
|
+
/**
|
|
13
|
+
* 获取当前用户加入的班级列表
|
|
14
|
+
* 返回当前登录用户加入的所有班级信息
|
|
15
|
+
* 需要有效的 session cookie
|
|
16
|
+
*/
|
|
17
|
+
export declare function getMyClassList(session?: string): Promise<any>;
|
|
18
|
+
/**
|
|
19
|
+
* 获取用户提交统计
|
|
20
|
+
* 返回各种提交状态的数量:AC(通过)、CE(编译错误)、PE(输出错误)、OE(其他错误)、WA(未通过)、TLE(超时)
|
|
21
|
+
* 需要有效的 session cookie
|
|
22
|
+
*/
|
|
23
|
+
export declare function getUserSubmissionStats(session?: string): Promise<any>;
|
|
24
|
+
/**
|
|
25
|
+
* 获取用户本周提交情况
|
|
26
|
+
* 返回7天中每天的提交情况,每天包含6种状态的数量(按顺序:AC、CE、WA、PE、TLE、OE)
|
|
27
|
+
* 需要有效的 session cookie
|
|
28
|
+
*/
|
|
29
|
+
export declare function getUserWeeklySubmissions(session?: string): Promise<any>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Accoding平台用户相关API调用
|
|
3
|
-
* 预留文件,当前无需实现具体功能
|
|
4
3
|
*/
|
|
5
|
-
const API_BASE_URL = "https://accoding.buaa.edu.cn";
|
|
4
|
+
//const API_BASE_URL = "https://accoding.buaa.edu.cn";
|
|
5
|
+
const API_BASE_URL = "http://10.251.252.33";
|
|
6
6
|
/**
|
|
7
7
|
* 向Accoding API发起HTTP请求
|
|
8
8
|
*/
|
|
@@ -13,17 +13,102 @@ async function apiRequest(endpoint, options = {}, session) {
|
|
|
13
13
|
...options.headers
|
|
14
14
|
};
|
|
15
15
|
if (session) {
|
|
16
|
-
headers["
|
|
16
|
+
headers["Authorization"] = session;
|
|
17
17
|
}
|
|
18
18
|
const response = await fetch(url, {
|
|
19
19
|
...options,
|
|
20
20
|
headers
|
|
21
21
|
});
|
|
22
22
|
if (!response.ok) {
|
|
23
|
-
|
|
23
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
24
|
+
throw new Error(`API请求失败: ${response.status} ${errorText}`);
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
+
const contentType = response.headers.get("content-type");
|
|
27
|
+
if (contentType && contentType.includes("application/json")) {
|
|
28
|
+
return await response.json();
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const text = await response.text();
|
|
32
|
+
throw new Error(`API返回了非JSON内容: ${contentType}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 获取当前登录用户信息
|
|
37
|
+
* 需要有效的 session cookie
|
|
38
|
+
*/
|
|
39
|
+
export async function getUserInfo(session) {
|
|
40
|
+
const response = await apiRequest("/api/user/info", { method: "GET" }, session);
|
|
41
|
+
// 检查响应格式
|
|
42
|
+
if (response.status !== 200) {
|
|
43
|
+
throw new Error(`获取用户信息失败: ${response.msg || "未知错误"}`);
|
|
44
|
+
}
|
|
45
|
+
// 返回用户数据
|
|
46
|
+
return response.data || {};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 获取班级列表
|
|
50
|
+
* 根据班级名称和课程名称搜索班级,如果都为空则返回全部班级
|
|
51
|
+
* 需要有效的 session cookie
|
|
52
|
+
*/
|
|
53
|
+
export async function getClassList(className, courseName, session) {
|
|
54
|
+
// 构建查询参数
|
|
55
|
+
const params = new URLSearchParams();
|
|
56
|
+
if (className) {
|
|
57
|
+
params.append("class_name", className);
|
|
58
|
+
}
|
|
59
|
+
if (courseName) {
|
|
60
|
+
params.append("course_name", courseName);
|
|
61
|
+
}
|
|
62
|
+
const queryString = params.toString();
|
|
63
|
+
const endpoint = queryString ? `/api/class/list?${queryString}` : "/api/class/list";
|
|
64
|
+
const response = await apiRequest(endpoint, { method: "GET" }, session);
|
|
65
|
+
// 检查响应格式
|
|
66
|
+
if (response.status !== 200) {
|
|
67
|
+
throw new Error(`获取班级列表失败: ${response.msg || "未知错误"}`);
|
|
68
|
+
}
|
|
69
|
+
// 返回班级列表数据
|
|
70
|
+
return response.data || [];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 获取当前用户加入的班级列表
|
|
74
|
+
* 返回当前登录用户加入的所有班级信息
|
|
75
|
+
* 需要有效的 session cookie
|
|
76
|
+
*/
|
|
77
|
+
export async function getMyClassList(session) {
|
|
78
|
+
const response = await apiRequest("/api/class/myList", { method: "GET" }, session);
|
|
79
|
+
// 检查响应格式
|
|
80
|
+
if (response.status !== 200) {
|
|
81
|
+
throw new Error(`获取我的班级列表失败: ${response.msg || "未知错误"}`);
|
|
82
|
+
}
|
|
83
|
+
// 返回班级列表数据
|
|
84
|
+
return response.data || [];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 获取用户提交统计
|
|
88
|
+
* 返回各种提交状态的数量:AC(通过)、CE(编译错误)、PE(输出错误)、OE(其他错误)、WA(未通过)、TLE(超时)
|
|
89
|
+
* 需要有效的 session cookie
|
|
90
|
+
*/
|
|
91
|
+
export async function getUserSubmissionStats(session) {
|
|
92
|
+
const response = await apiRequest("/api/user/sub-type", { method: "GET" }, session);
|
|
93
|
+
// 检查响应格式
|
|
94
|
+
if (response.status !== 200) {
|
|
95
|
+
throw new Error(`获取用户提交统计失败: ${response.msg || "未知错误"}`);
|
|
96
|
+
}
|
|
97
|
+
// 返回统计数据
|
|
98
|
+
return response.data || {};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 获取用户本周提交情况
|
|
102
|
+
* 返回7天中每天的提交情况,每天包含6种状态的数量(按顺序:AC、CE、WA、PE、TLE、OE)
|
|
103
|
+
* 需要有效的 session cookie
|
|
104
|
+
*/
|
|
105
|
+
export async function getUserWeeklySubmissions(session) {
|
|
106
|
+
const response = await apiRequest("/api/user/sub-week", { method: "GET" }, session);
|
|
107
|
+
// 检查响应格式
|
|
108
|
+
if (response.status !== 200) {
|
|
109
|
+
throw new Error(`获取用户本周提交情况失败: ${response.msg || "未知错误"}`);
|
|
110
|
+
}
|
|
111
|
+
// 返回本周提交数据
|
|
112
|
+
return response.data || [];
|
|
26
113
|
}
|
|
27
|
-
export {};
|
|
28
|
-
// 此文件暂时没有导出的API函数,后续需要用户相关功能时再实现
|
|
29
114
|
//# sourceMappingURL=user-api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-api.js","sourceRoot":"","sources":["../../../src/accoding/api/user-api.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"user-api.js","sourceRoot":"","sources":["../../../src/accoding/api/user-api.ts"],"names":[],"mappings":"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"}
|
package/build/index.js
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
3
|
import minimist from "minimist";
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
6
|
-
import { dirname, join } from "node:path";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
4
|
import { AccodingServiceFactory } from "./accoding/accoding-service-factory.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { registerContestTools } from "./mcp/tools/contest-tools.js";
|
|
12
|
-
import { registerProblemTools } from "./mcp/tools/problem-tools.js";
|
|
13
|
-
import { registerSubmissionTools } from "./mcp/tools/submission-tools.js";
|
|
14
|
-
import { registerUserTools } from "./mcp/tools/user-tools.js";
|
|
5
|
+
import { startHttpServer } from "./transport/http-server.js";
|
|
6
|
+
import { createMCPServer } from "./transport/mcp-server-factory.js";
|
|
15
7
|
import logger from "./utils/logger.js";
|
|
16
8
|
/**
|
|
17
9
|
* 解析并验证命令行参数
|
|
18
10
|
*/
|
|
19
11
|
function parseArgs() {
|
|
20
12
|
const args = minimist(process.argv.slice(2), {
|
|
21
|
-
string: ["session"],
|
|
13
|
+
string: ["session", "mode", "port"],
|
|
22
14
|
boolean: ["help"],
|
|
23
15
|
alias: {
|
|
24
16
|
s: "session",
|
|
17
|
+
m: "mode",
|
|
18
|
+
p: "port",
|
|
25
19
|
h: "help"
|
|
20
|
+
},
|
|
21
|
+
default: {
|
|
22
|
+
mode: "stdio",
|
|
23
|
+
port: "3000"
|
|
26
24
|
}
|
|
27
25
|
});
|
|
28
26
|
if (args.help) {
|
|
@@ -32,46 +30,51 @@ Accoding MCP Server - 北航Accoding平台MCP服务
|
|
|
32
30
|
用法: accoding-mcp-server [选项]
|
|
33
31
|
|
|
34
32
|
选项:
|
|
35
|
-
--session, -s <cookie> Accoding平台会话Cookie
|
|
33
|
+
--session, -s <cookie> Accoding平台会话Cookie(stdio模式使用)
|
|
34
|
+
--mode, -m <mode> 传输模式: stdio(默认)或 http
|
|
35
|
+
--port, -p <port> HTTP模式端口号(默认: 3000)
|
|
36
36
|
--help, -h 显示帮助信息
|
|
37
|
+
|
|
38
|
+
示例:
|
|
39
|
+
# stdio 模式(默认)
|
|
40
|
+
accoding-mcp-server --session "your-cookie"
|
|
41
|
+
|
|
42
|
+
# HTTP 模式
|
|
43
|
+
accoding-mcp-server --mode http --port 3000
|
|
37
44
|
`);
|
|
38
45
|
process.exit(0);
|
|
39
46
|
}
|
|
47
|
+
const mode = args.mode === "http" ? "http" : "stdio";
|
|
48
|
+
const port = mode === "http" ? parseInt(args.port, 10) || 3000 : undefined;
|
|
40
49
|
return {
|
|
41
|
-
session: args.session || process.env.ACCODING_SESSION
|
|
50
|
+
session: args.session || process.env.ACCODING_SESSION,
|
|
51
|
+
mode,
|
|
52
|
+
port
|
|
42
53
|
};
|
|
43
54
|
}
|
|
44
55
|
/**
|
|
45
|
-
*
|
|
56
|
+
* 启动 stdio 模式的 MCP 服务器
|
|
46
57
|
*/
|
|
47
|
-
function
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
async function startStdioServer(session) {
|
|
59
|
+
const accodingService = AccodingServiceFactory.createService(session);
|
|
60
|
+
const server = createMCPServer(accodingService);
|
|
61
|
+
const transport = new StdioServerTransport();
|
|
62
|
+
await server.connect(transport);
|
|
63
|
+
logger.info("Accoding MCP Server (stdio模式) 已启动");
|
|
53
64
|
}
|
|
54
65
|
/**
|
|
55
|
-
*
|
|
66
|
+
* 主函数:根据模式选择启动方式
|
|
56
67
|
*/
|
|
57
68
|
async function main() {
|
|
58
69
|
const options = parseArgs();
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
registerUserTools(server, accodingService);
|
|
68
|
-
registerContestTools(server, accodingService);
|
|
69
|
-
registerSubmissionTools(server, accodingService);
|
|
70
|
-
registerProblemResources(server, accodingService);
|
|
71
|
-
registerContestResources(server, accodingService);
|
|
72
|
-
const transport = new StdioServerTransport();
|
|
73
|
-
await server.connect(transport);
|
|
74
|
-
logger.info("Accoding MCP Server 已启动");
|
|
70
|
+
if (options.mode === "http") {
|
|
71
|
+
// HTTP 模式:启动 HTTP 服务器
|
|
72
|
+
await startHttpServer(options.port);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// stdio 模式:使用标准输入输出
|
|
76
|
+
await startStdioServer(options.session);
|
|
77
|
+
}
|
|
75
78
|
}
|
|
76
79
|
main().catch((error) => {
|
|
77
80
|
logger.error("启动Accoding MCP Server失败: %s", error);
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,MAAM,MAAM,mBAAmB,CAAC;AAEvC;;GAEG;AACH,SAAS,SAAS;IACd,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACzC,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC;QACnC,OAAO,EAAE,CAAC,MAAM,CAAC;QACjB,KAAK,EAAE;YACH,CAAC,EAAE,SAAS;YACZ,CAAC,EAAE,MAAM;YACT,CAAC,EAAE,MAAM;YACT,CAAC,EAAE,MAAM;SACZ;QACD,OAAO,EAAE;YACL,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,MAAM;SACf;KACJ,CAAC,CAAC;IAEH,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;SAiBX,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IACrD,MAAM,IAAI,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAE3E,OAAO;QACH,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB;QACrD,IAAI;QACJ,IAAI;KACP,CAAC;AACN,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,gBAAgB,CAAC,OAAgB;IAC5C,MAAM,eAAe,GACjB,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAElD,MAAM,MAAM,GAAG,eAAe,CAAC,eAAe,CAAC,CAAC;IAEhD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,MAAM,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;AACrD,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,IAAI;IACf,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;IAE5B,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC1B,sBAAsB;QACtB,MAAM,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;SAAM,CAAC;QACJ,oBAAoB;QACpB,MAAM,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACnB,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC;IACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
|
|
@@ -6,7 +6,9 @@ import { ToolRegistry } from "./tool-registry.js";
|
|
|
6
6
|
*/
|
|
7
7
|
export class ContestToolRegistry extends ToolRegistry {
|
|
8
8
|
registerCommon() {
|
|
9
|
-
|
|
9
|
+
//注册
|
|
10
|
+
//测试用
|
|
11
|
+
//this.registerGetContestProblems();
|
|
10
12
|
}
|
|
11
13
|
registerAuthenticated() {
|
|
12
14
|
// 需要认证的比赛工具可以在这里注册
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contest-tools.js","sourceRoot":"","sources":["../../../src/mcp/tools/contest-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;GAGG;AACH,MAAM,OAAO,mBAAoB,SAAQ,YAAY;IACvC,cAAc;QACpB,IAAI,
|
|
1
|
+
{"version":3,"file":"contest-tools.js","sourceRoot":"","sources":["../../../src/mcp/tools/contest-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;GAGG;AACH,MAAM,OAAO,mBAAoB,SAAQ,YAAY;IACvC,cAAc;QACpB,IAAI;QACJ,KAAK;QACL,oCAAoC;IACxC,CAAC;IAES,qBAAqB;QAC3B,mBAAmB;IACvB,CAAC;IAED;;OAEG;IACK,0BAA0B;QAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,CACZ,sBAAsB,EACtB,oBAAoB,EACpB;YACI,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC;SAC9C,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;YACpB,IAAI,CAAC;gBACD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;gBAE1E,OAAO;oBACH,OAAO,EAAE;wBACL;4BACI,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gCACjB,SAAS,EAAE,SAAS;gCACpB,QAAQ,EAAE,QAAQ;gCAClB,KAAK,EAAE,QAAQ,CAAC,MAAM;6BACzB,EAAE,IAAI,EAAE,CAAC,CAAC;yBACd;qBACJ;iBACJ,CAAC;YACN,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBAClB,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;gBACpD,OAAO;oBACH,OAAO,EAAE;wBACL;4BACI,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gCACjB,KAAK,EAAE,gCAAgC;gCACvC,OAAO,EAAE,YAAY;gCACrB,SAAS,EAAE,SAAS;6BACvB,CAAC;yBACL;qBACJ;oBACD,OAAO,EAAE,IAAI;iBAChB,CAAC;YACN,CAAC;QACL,CAAC,CACJ,CAAC;IACN,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAChC,MAAiB,EACjB,eAAoC;IAEpC,MAAM,QAAQ,GAAG,IAAI,mBAAmB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAClE,QAAQ,CAAC,aAAa,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -8,6 +8,26 @@ import { ToolRegistry } from "./tool-registry.js";
|
|
|
8
8
|
export declare class UserToolRegistry extends ToolRegistry {
|
|
9
9
|
protected registerCommon(): void;
|
|
10
10
|
protected registerAuthenticated(): void;
|
|
11
|
+
/**
|
|
12
|
+
* 注册获取用户信息的工具
|
|
13
|
+
*/
|
|
14
|
+
private registerGetUserInfo;
|
|
15
|
+
/**
|
|
16
|
+
* 注册获取班级列表的工具
|
|
17
|
+
*/
|
|
18
|
+
private registerGetClassList;
|
|
19
|
+
/**
|
|
20
|
+
* 注册获取我的班级列表的工具
|
|
21
|
+
*/
|
|
22
|
+
private registerGetMyClassList;
|
|
23
|
+
/**
|
|
24
|
+
* 注册获取用户提交统计的工具
|
|
25
|
+
*/
|
|
26
|
+
private registerGetUserSubmissionStats;
|
|
27
|
+
/**
|
|
28
|
+
* 注册获取用户本周提交情况的工具
|
|
29
|
+
*/
|
|
30
|
+
private registerGetUserWeeklySubmissions;
|
|
11
31
|
}
|
|
12
32
|
/**
|
|
13
33
|
* 注册所有用户相关工具
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import { ToolRegistry } from "./tool-registry.js";
|
|
2
3
|
/**
|
|
3
4
|
* 用户工具注册器
|
|
@@ -8,7 +9,224 @@ export class UserToolRegistry extends ToolRegistry {
|
|
|
8
9
|
// 不需要认证的用户工具可以在这里注册
|
|
9
10
|
}
|
|
10
11
|
registerAuthenticated() {
|
|
11
|
-
|
|
12
|
+
this.registerGetUserInfo();
|
|
13
|
+
this.registerGetClassList();
|
|
14
|
+
this.registerGetMyClassList();
|
|
15
|
+
this.registerGetUserSubmissionStats();
|
|
16
|
+
this.registerGetUserWeeklySubmissions();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 注册获取用户信息的工具
|
|
20
|
+
*/
|
|
21
|
+
registerGetUserInfo() {
|
|
22
|
+
this.server.tool("get_user_info", "获取当前登录用户的详细信息,包括昵称、邮箱、学校、学号、专业等", {}, async () => {
|
|
23
|
+
try {
|
|
24
|
+
const userInfo = await this.accodingService.getUserInfo();
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: JSON.stringify({
|
|
30
|
+
userInfo: userInfo
|
|
31
|
+
}, null, 2)
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const errorMessage = error.message || String(error);
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: JSON.stringify({
|
|
43
|
+
error: "Failed to get user info",
|
|
44
|
+
message: errorMessage
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
isError: true
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 注册获取班级列表的工具
|
|
55
|
+
*/
|
|
56
|
+
registerGetClassList() {
|
|
57
|
+
this.server.tool("get_class_list", "根据班级名称和课程名称搜索班级列表,如果两个参数都为空则返回全部班级", {
|
|
58
|
+
className: z.string().optional().describe("班级名称,用于搜索过滤(可选)"),
|
|
59
|
+
courseName: z.string().optional().describe("课程名称,用于搜索过滤(可选)")
|
|
60
|
+
}, async ({ className, courseName }) => {
|
|
61
|
+
try {
|
|
62
|
+
const classList = await this.accodingService.getClassList(className, courseName);
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: JSON.stringify({
|
|
68
|
+
classes: classList,
|
|
69
|
+
count: classList.length,
|
|
70
|
+
filters: {
|
|
71
|
+
className: className || null,
|
|
72
|
+
courseName: courseName || null
|
|
73
|
+
}
|
|
74
|
+
}, null, 2)
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const errorMessage = error.message || String(error);
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: JSON.stringify({
|
|
86
|
+
error: "Failed to get class list",
|
|
87
|
+
message: errorMessage,
|
|
88
|
+
filters: {
|
|
89
|
+
className: className || null,
|
|
90
|
+
courseName: courseName || null
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
isError: true
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 注册获取我的班级列表的工具
|
|
102
|
+
*/
|
|
103
|
+
registerGetMyClassList() {
|
|
104
|
+
this.server.tool("get_my_class_list", "获取当前登录用户加入的所有班级列表,包括班级ID、课程ID、班级名称、课程名称、成员数量、考试数量等信息", {}, async () => {
|
|
105
|
+
try {
|
|
106
|
+
const classList = await this.accodingService.getMyClassList();
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: JSON.stringify({
|
|
112
|
+
myClasses: classList,
|
|
113
|
+
count: classList.length
|
|
114
|
+
}, null, 2)
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
const errorMessage = error.message || String(error);
|
|
121
|
+
return {
|
|
122
|
+
content: [
|
|
123
|
+
{
|
|
124
|
+
type: "text",
|
|
125
|
+
text: JSON.stringify({
|
|
126
|
+
error: "Failed to get my class list",
|
|
127
|
+
message: errorMessage
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
isError: true
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 注册获取用户提交统计的工具
|
|
138
|
+
*/
|
|
139
|
+
registerGetUserSubmissionStats() {
|
|
140
|
+
this.server.tool("get_user_submission_stats", "获取当前登录用户的提交统计信息,包括各种提交状态的数量:AC(通过)、CE(编译错误)、PE(输出错误)、OE(其他错误)、WA(未通过)、TLE(超时)", {}, async () => {
|
|
141
|
+
try {
|
|
142
|
+
const stats = await this.accodingService.getUserSubmissionStats();
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: JSON.stringify({
|
|
148
|
+
submissionStats: stats,
|
|
149
|
+
description: {
|
|
150
|
+
AC: "通过 (Accepted)",
|
|
151
|
+
CE: "编译错误 (Compilation Error)",
|
|
152
|
+
PE: "输出错误 (Presentation Error)",
|
|
153
|
+
OE: "其他错误 (Other Error)",
|
|
154
|
+
WA: "未通过 (Wrong Answer)",
|
|
155
|
+
TLE: "超时 (Time Limit Exceeded)"
|
|
156
|
+
}
|
|
157
|
+
}, null, 2)
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
const errorMessage = error.message || String(error);
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: "text",
|
|
168
|
+
text: JSON.stringify({
|
|
169
|
+
error: "Failed to get user submission stats",
|
|
170
|
+
message: errorMessage
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
isError: true
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 注册获取用户本周提交情况的工具
|
|
181
|
+
*/
|
|
182
|
+
registerGetUserWeeklySubmissions() {
|
|
183
|
+
this.server.tool("get_user_weekly_submissions", "获取当前登录用户本周(最近7天)的提交情况,返回每天6种状态的数量(按顺序:AC、CE、WA、PE、TLE、OE)", {}, async () => {
|
|
184
|
+
try {
|
|
185
|
+
const weeklyData = await this.accodingService.getUserWeeklySubmissions();
|
|
186
|
+
// 格式化数据,添加日期和状态说明
|
|
187
|
+
const daysOfWeek = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
|
|
188
|
+
const statusLabels = ["AC(通过)", "CE(编译错误)", "WA(未通过)", "PE(输出错误)", "TLE(超时)", "OE(其他错误)"];
|
|
189
|
+
const formattedData = weeklyData.map((dayData, index) => {
|
|
190
|
+
const dayStats = {};
|
|
191
|
+
dayData.forEach((count, statusIndex) => {
|
|
192
|
+
dayStats[statusLabels[statusIndex]] = count;
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
day: daysOfWeek[index],
|
|
196
|
+
stats: dayStats,
|
|
197
|
+
raw: dayData
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
content: [
|
|
202
|
+
{
|
|
203
|
+
type: "text",
|
|
204
|
+
text: JSON.stringify({
|
|
205
|
+
weeklySubmissions: formattedData,
|
|
206
|
+
rawData: weeklyData,
|
|
207
|
+
statusOrder: statusLabels,
|
|
208
|
+
note: "数据按时间顺序从周日到周六,每天包含6种状态的数量"
|
|
209
|
+
}, null, 2)
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
const errorMessage = error.message || String(error);
|
|
216
|
+
return {
|
|
217
|
+
content: [
|
|
218
|
+
{
|
|
219
|
+
type: "text",
|
|
220
|
+
text: JSON.stringify({
|
|
221
|
+
error: "Failed to get user weekly submissions",
|
|
222
|
+
message: errorMessage
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
isError: true
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
});
|
|
12
230
|
}
|
|
13
231
|
}
|
|
14
232
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-tools.js","sourceRoot":"","sources":["../../../src/mcp/tools/user-tools.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"user-tools.js","sourceRoot":"","sources":["../../../src/mcp/tools/user-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import { AccodingServiceFactory } from "../accoding/accoding-service-factory.js";
|
|
5
|
+
import { extractSessionFromRequest, setCommonHeaders } from "../utils/http-utils.js";
|
|
6
|
+
import logger from "../utils/logger.js";
|
|
7
|
+
import { createMCPServer } from "./mcp-server-factory.js";
|
|
8
|
+
/**
|
|
9
|
+
* 会话管理器:存储和管理所有活跃的会话
|
|
10
|
+
*/
|
|
11
|
+
class SessionManager {
|
|
12
|
+
sessions = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* 创建新会话(但不立即 connect)
|
|
15
|
+
*/
|
|
16
|
+
createSession(sessionCookie) {
|
|
17
|
+
// 为每个会话创建独立的 AccodingService
|
|
18
|
+
const accodingService = AccodingServiceFactory.createService(sessionCookie);
|
|
19
|
+
// 创建 MCP Server
|
|
20
|
+
const server = createMCPServer(accodingService);
|
|
21
|
+
// 生成 sessionId(使用 UUID)
|
|
22
|
+
const sessionId = randomUUID();
|
|
23
|
+
// 创建 StreamableHTTPServerTransport
|
|
24
|
+
const transport = new StreamableHTTPServerTransport({
|
|
25
|
+
sessionIdGenerator: () => sessionId
|
|
26
|
+
});
|
|
27
|
+
// 监听 transport 关闭事件,清理会话
|
|
28
|
+
transport.onclose = () => {
|
|
29
|
+
this.removeSession(sessionId);
|
|
30
|
+
logger.info(`会话已关闭: ${sessionId}`);
|
|
31
|
+
};
|
|
32
|
+
const sessionInfo = {
|
|
33
|
+
transport,
|
|
34
|
+
server,
|
|
35
|
+
accodingService,
|
|
36
|
+
sessionId,
|
|
37
|
+
createdAt: new Date(),
|
|
38
|
+
initialized: false
|
|
39
|
+
};
|
|
40
|
+
this.sessions.set(sessionId, sessionInfo);
|
|
41
|
+
logger.info(`新会话已创建: ${sessionId}, cookie 长度: ${sessionCookie?.length || 0}`);
|
|
42
|
+
return sessionInfo;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 获取会话
|
|
46
|
+
*/
|
|
47
|
+
getSession(sessionId) {
|
|
48
|
+
return this.sessions.get(sessionId);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 移除会话
|
|
52
|
+
*/
|
|
53
|
+
removeSession(sessionId) {
|
|
54
|
+
const session = this.sessions.get(sessionId);
|
|
55
|
+
if (session) {
|
|
56
|
+
this.sessions.delete(sessionId);
|
|
57
|
+
logger.info(`会话已移除: ${sessionId}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 获取活跃会话数量
|
|
62
|
+
*/
|
|
63
|
+
getSessionCount() {
|
|
64
|
+
return this.sessions.size;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 确保请求的 Accept header 正确设置
|
|
69
|
+
*/
|
|
70
|
+
function ensureAcceptHeader(req) {
|
|
71
|
+
const acceptHeader = req.headers.accept;
|
|
72
|
+
const requiredAccept = "application/json, text/event-stream";
|
|
73
|
+
if (!acceptHeader ||
|
|
74
|
+
!acceptHeader.includes("application/json") ||
|
|
75
|
+
!acceptHeader.includes("text/event-stream")) {
|
|
76
|
+
// 修改请求对象的 headers
|
|
77
|
+
req.headers.accept = requiredAccept;
|
|
78
|
+
logger.debug(`自动设置 Accept header: ${requiredAccept}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 启动 HTTP 服务器
|
|
83
|
+
*/
|
|
84
|
+
export async function startHttpServer(port = 3000) {
|
|
85
|
+
const app = express();
|
|
86
|
+
const sessionManager = new SessionManager();
|
|
87
|
+
// 添加 JSON 解析中间件
|
|
88
|
+
app.use(express.json());
|
|
89
|
+
// 处理 OPTIONS 请求(CORS 预检)
|
|
90
|
+
app.options("/mcp", (req, res) => {
|
|
91
|
+
setCommonHeaders(res);
|
|
92
|
+
res.status(204).end();
|
|
93
|
+
});
|
|
94
|
+
// 处理 GET /mcp 请求(可选,用于 SSE 通知)
|
|
95
|
+
app.get("/mcp", (req, res) => {
|
|
96
|
+
setCommonHeaders(res);
|
|
97
|
+
res.status(405)
|
|
98
|
+
.set("Allow", "POST")
|
|
99
|
+
.json({
|
|
100
|
+
error: "Method Not Allowed",
|
|
101
|
+
message: "当前服务器不支持 GET 方法,仅支持 POST 方法"
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
// 处理 POST /mcp 请求
|
|
105
|
+
app.post("/mcp", async (req, res) => {
|
|
106
|
+
setCommonHeaders(res);
|
|
107
|
+
// 确保 Accept header 正确
|
|
108
|
+
ensureAcceptHeader(req);
|
|
109
|
+
try {
|
|
110
|
+
const body = req.body;
|
|
111
|
+
const method = body?.method;
|
|
112
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
113
|
+
const session = sessionId ? sessionManager.getSession(sessionId) : undefined;
|
|
114
|
+
// 处理初始化请求
|
|
115
|
+
if (!session && method === "initialize") {
|
|
116
|
+
// 从请求中提取 session cookie
|
|
117
|
+
const sessionCookie = extractSessionFromRequest(req);
|
|
118
|
+
if (sessionCookie) {
|
|
119
|
+
logger.info("从请求中提取到 session cookie" + sessionCookie);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
logger.warn("未从请求中提取到 session cookie,将使用未认证模式");
|
|
123
|
+
}
|
|
124
|
+
// 创建新会话(但不立即 connect)
|
|
125
|
+
const newSession = sessionManager.createSession(sessionCookie);
|
|
126
|
+
try {
|
|
127
|
+
// 关键:先连接 server 到 transport
|
|
128
|
+
// 这必须在 handleRequest 之前完成
|
|
129
|
+
await newSession.server.connect(newSession.transport);
|
|
130
|
+
logger.debug(`Server 已连接到 transport: ${newSession.sessionId}`);
|
|
131
|
+
// 现在处理 initialize 请求
|
|
132
|
+
// handleRequest 内部会调用 server 的 initialize 方法
|
|
133
|
+
res.setHeader("mcp-session-id", newSession.sessionId);
|
|
134
|
+
await newSession.transport.handleRequest(req, res, body);
|
|
135
|
+
// 标记会话已初始化
|
|
136
|
+
newSession.initialized = true;
|
|
137
|
+
// 在响应头中返回 sessionId
|
|
138
|
+
logger.info(`会话初始化成功: ${newSession.sessionId}`);
|
|
139
|
+
}
|
|
140
|
+
catch (connectError) {
|
|
141
|
+
// 如果连接或初始化失败,清理会话
|
|
142
|
+
sessionManager.removeSession(newSession.sessionId);
|
|
143
|
+
throw connectError;
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// 处理非初始化请求
|
|
148
|
+
if (session) {
|
|
149
|
+
// 确保会话已初始化
|
|
150
|
+
if (!session.initialized) {
|
|
151
|
+
logger.warn(`会话未初始化,尝试重新初始化: ${session.sessionId}`);
|
|
152
|
+
try {
|
|
153
|
+
await session.server.connect(session.transport);
|
|
154
|
+
session.initialized = true;
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
logger.error(`重新初始化会话失败: ${error.message}`);
|
|
158
|
+
res.status(400).json({
|
|
159
|
+
error: "Session Not Initialized",
|
|
160
|
+
message: "会话未正确初始化"
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// 会话存在且已初始化,处理请求
|
|
166
|
+
await session.transport.handleRequest(req, res, body);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// 没有会话且不是初始化请求
|
|
170
|
+
res.status(400).json({
|
|
171
|
+
error: "Invalid Request",
|
|
172
|
+
message: "非法的 SessionId 或者非初始化操作"
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
logger.error(`处理请求时出错: ${error.message}`);
|
|
177
|
+
logger.error(error);
|
|
178
|
+
if (!res.headersSent) {
|
|
179
|
+
res.status(500).json({
|
|
180
|
+
error: "Internal Server Error",
|
|
181
|
+
message: error.message || "服务器内部错误"
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// 启动服务器
|
|
187
|
+
app.listen(port, () => {
|
|
188
|
+
logger.info(`Accoding MCP Server (HTTP模式) 已启动,监听端口: ${port}`);
|
|
189
|
+
logger.info(`访问地址: http://localhost:${port}/mcp`);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=http-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../src/transport/http-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { AccodingBaseService } from "../accoding/accoding-base-service.js";
|
|
3
|
+
/**
|
|
4
|
+
* 创建并配置 MCP Server 实例
|
|
5
|
+
* 为每个会话创建独立的 MCP Server,确保会话隔离
|
|
6
|
+
*/
|
|
7
|
+
export declare function createMCPServer(accodingService: AccodingBaseService): McpServer;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { registerProblemResources } from "../mcp/resources/problem-resources.js";
|
|
6
|
+
import { registerContestResources } from "../mcp/resources/contest-resources.js";
|
|
7
|
+
import { registerContestTools } from "../mcp/tools/contest-tools.js";
|
|
8
|
+
import { registerProblemTools } from "../mcp/tools/problem-tools.js";
|
|
9
|
+
import { registerSubmissionTools } from "../mcp/tools/submission-tools.js";
|
|
10
|
+
import { registerUserTools } from "../mcp/tools/user-tools.js";
|
|
11
|
+
import logger from "../utils/logger.js";
|
|
12
|
+
/**
|
|
13
|
+
* 获取 package.json 中的项目元数据
|
|
14
|
+
*/
|
|
15
|
+
function getPackageJson() {
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
const packageJSONPath = join(__dirname, "..", "..", "package.json");
|
|
19
|
+
const packageJSON = JSON.parse(readFileSync(packageJSONPath, "utf-8"));
|
|
20
|
+
return packageJSON;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 创建并配置 MCP Server 实例
|
|
24
|
+
* 为每个会话创建独立的 MCP Server,确保会话隔离
|
|
25
|
+
*/
|
|
26
|
+
export function createMCPServer(accodingService) {
|
|
27
|
+
const packageJSON = getPackageJson();
|
|
28
|
+
const server = new McpServer({
|
|
29
|
+
name: "Accoding MCP Server",
|
|
30
|
+
version: packageJSON.version
|
|
31
|
+
});
|
|
32
|
+
//注册所有工具和资源
|
|
33
|
+
try {
|
|
34
|
+
registerProblemTools(server, accodingService);
|
|
35
|
+
registerUserTools(server, accodingService);
|
|
36
|
+
registerContestTools(server, accodingService);
|
|
37
|
+
registerSubmissionTools(server, accodingService);
|
|
38
|
+
registerProblemResources(server, accodingService);
|
|
39
|
+
registerContestResources(server, accodingService);
|
|
40
|
+
logger.debug("所有工具和资源已注册");
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
logger.error(`注册工具和资源时出错: ${error.message}`);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
return server;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=mcp-server-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server-factory.js","sourceRoot":"","sources":["../../src/transport/mcp-server-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC;;GAEG;AACH,SAAS,cAAc;IACnB,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtC,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;IACpE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;IACvE,OAAO,WAAW,CAAC;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,eAAoC;IAChE,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QACzB,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,WAAW,CAAC,OAAO;KAC/B,CAAC,CAAC;IAEH,WAAW;IACX,IAAI,CAAC;QACD,oBAAoB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;QAC9C,iBAAiB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;QAC3C,oBAAoB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;QAC9C,uBAAuB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;QAEjD,wBAAwB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;QAClD,wBAAwB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;QAElD,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,MAAM,CAAC,KAAK,CAAC,eAAe,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7C,MAAM,KAAK,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { IncomingMessage } from "node:http";
|
|
2
|
+
import { ServerResponse } from "node:http";
|
|
3
|
+
/**
|
|
4
|
+
* 设置 CORS 响应头
|
|
5
|
+
*/
|
|
6
|
+
export declare function setCommonHeaders(res: ServerResponse): void;
|
|
7
|
+
/**
|
|
8
|
+
* 从 HTTP 请求中提取 Accoding session cookie
|
|
9
|
+
* 优先级:
|
|
10
|
+
* 1. X-Accoding-Session header(直接传递 cookie 字符串)
|
|
11
|
+
* 2. Cookie header 中解析(查找包含 session 相关的 cookie)
|
|
12
|
+
* 3. 返回 undefined(使用默认行为)
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractSessionFromRequest(req: IncomingMessage): string | undefined;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 设置 CORS 响应头
|
|
3
|
+
*/
|
|
4
|
+
export function setCommonHeaders(res) {
|
|
5
|
+
// 允许哪些域(Origin)可以访问该服务
|
|
6
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
7
|
+
// 允许客户端在跨域请求中使用哪些 HTTP 方法
|
|
8
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, OPTIONS");
|
|
9
|
+
// 指定客户端请求时允许携带的自定义请求头
|
|
10
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id, X-Accoding-Session");
|
|
11
|
+
// 允许前端 JS 访问响应中的哪些自定义头
|
|
12
|
+
res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 从 HTTP 请求中提取 Accoding session cookie
|
|
16
|
+
* 优先级:
|
|
17
|
+
* 1. X-Accoding-Session header(直接传递 cookie 字符串)
|
|
18
|
+
* 2. Cookie header 中解析(查找包含 session 相关的 cookie)
|
|
19
|
+
* 3. 返回 undefined(使用默认行为)
|
|
20
|
+
*/
|
|
21
|
+
export function extractSessionFromRequest(req) {
|
|
22
|
+
// 优先级1: 从 X-Accoding-Session header 获取
|
|
23
|
+
const customHeader = req.headers["x-accoding-session"];
|
|
24
|
+
if (customHeader && typeof customHeader === "string") {
|
|
25
|
+
return customHeader;
|
|
26
|
+
}
|
|
27
|
+
// 优先级2: 从 Cookie header 中解析
|
|
28
|
+
const cookieHeader = req.headers.cookie;
|
|
29
|
+
if (cookieHeader && typeof cookieHeader === "string") {
|
|
30
|
+
// 解析 Cookie header(格式:key1=value1; key2=value2)
|
|
31
|
+
const cookies = cookieHeader.split(";").map((cookie) => cookie.trim());
|
|
32
|
+
// 查找可能的 session cookie(常见的命名:sessionid, session, accoding_session 等)
|
|
33
|
+
for (const cookie of cookies) {
|
|
34
|
+
const [key, value] = cookie.split("=").map((s) => s.trim());
|
|
35
|
+
if (key &&
|
|
36
|
+
value &&
|
|
37
|
+
(key.toLowerCase().includes("session") ||
|
|
38
|
+
key.toLowerCase().includes("token") ||
|
|
39
|
+
key.toLowerCase().includes("auth"))) {
|
|
40
|
+
// 返回完整的 cookie 字符串(保持原始格式)
|
|
41
|
+
return cookieHeader;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// 如果没有找到特定的 session cookie,但存在 Cookie header,返回整个 Cookie header
|
|
45
|
+
// 这样可以让 Accoding API 自己解析需要的 cookie
|
|
46
|
+
return cookieHeader;
|
|
47
|
+
}
|
|
48
|
+
// 优先级3: 返回 undefined,使用默认行为
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=http-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-utils.js","sourceRoot":"","sources":["../../src/utils/http-utils.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAmB;IAChD,uBAAuB;IACvB,GAAG,CAAC,SAAS,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;IAClD,0BAA0B;IAC1B,GAAG,CAAC,SAAS,CAAC,8BAA8B,EAAE,4BAA4B,CAAC,CAAC;IAC5E,sBAAsB;IACtB,GAAG,CAAC,SAAS,CAAC,8BAA8B,EAAE,kDAAkD,CAAC,CAAC;IAClG,uBAAuB;IACvB,GAAG,CAAC,SAAS,CAAC,+BAA+B,EAAE,gBAAgB,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAoB;IAC1D,uCAAuC;IACvC,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACvD,IAAI,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;QACnD,OAAO,YAAY,CAAC;IACxB,CAAC;IAED,4BAA4B;IAC5B,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;IACxC,IAAI,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;QACnD,gDAAgD;QAChD,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAEvE,qEAAqE;QACrE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAC5D,IACI,GAAG;gBACH,KAAK;gBACL,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC;oBAClC,GAAG,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;oBACnC,GAAG,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EACzC,CAAC;gBACC,2BAA2B;gBAC3B,OAAO,YAAY,CAAC;YACxB,CAAC;QACL,CAAC;QAED,gEAAgE;QAChE,oCAAoC;QACpC,OAAO,YAAY,CAAC;IACxB,CAAC;IAED,4BAA4B;IAC5B,OAAO,SAAS,CAAC;AACrB,CAAC"}
|
package/build/utils/logger.d.ts
CHANGED
package/build/utils/logger.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { pino } from "pino";
|
|
2
|
+
/**
|
|
3
|
+
* 创建 logger 实例
|
|
4
|
+
* 在 stdio 模式下,MCP 使用 stdout 进行 JSONRPC 通信
|
|
5
|
+
* 因此日志必须输出到 stderr,避免干扰 JSONRPC 消息
|
|
6
|
+
*/
|
|
2
7
|
const logger = pino({
|
|
3
8
|
level: "info",
|
|
4
9
|
formatters: {
|
|
@@ -7,6 +12,8 @@ const logger = pino({
|
|
|
7
12
|
timestamp: () => `,"timestamp":"${new Date().toISOString()}"`,
|
|
8
13
|
messageKey: "message",
|
|
9
14
|
nestedKey: "payload"
|
|
10
|
-
}
|
|
15
|
+
},
|
|
16
|
+
// 输出到 stderr,避免干扰 MCP 的 JSONRPC 通信(使用 stdout)
|
|
17
|
+
process.stderr);
|
|
11
18
|
export default logger;
|
|
12
19
|
//# sourceMappingURL=logger.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,MAAM,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;;;GAIG;AACH,MAAM,MAAM,GAAG,IAAI,CACf;IACI,KAAK,EAAE,MAAM;IACb,UAAU,EAAE;QACR,KAAK,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;KAC7D;IACD,SAAS,EAAE,GAAG,EAAE,CAAC,iBAAiB,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,GAAG;IAC7D,UAAU,EAAE,SAAS;IACrB,SAAS,EAAE,SAAS;CACvB;AACD,8CAA8C;AAC9C,OAAO,CAAC,MAAM,CACjB,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "accoding-mcp-server-test-new",
|
|
3
3
|
"description": "MCP Server for Accoding API-test",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"author": "",
|
|
6
6
|
"main": "./build/index.js",
|
|
7
7
|
"keywords": [
|
|
@@ -26,11 +26,13 @@
|
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
29
|
+
"express": "^4.18.2",
|
|
29
30
|
"minimist": "^1.2.8",
|
|
30
31
|
"pino": "^9.6.0",
|
|
31
32
|
"zod": "^3.24.2"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
35
|
+
"@types/express": "^4.17.21",
|
|
34
36
|
"@types/minimist": "^1.2.5",
|
|
35
37
|
"@types/node": "^22.14.0",
|
|
36
38
|
"prettier": "^3.5.3",
|