mcp-paike 1.0.0
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/LICENSE +674 -0
- package/README.md +124 -0
- package/dist/__tests__/__mocks__/open.d.ts +2 -0
- package/dist/__tests__/__mocks__/open.d.ts.map +1 -0
- package/dist/__tests__/__mocks__/open.js +5 -0
- package/dist/__tests__/__mocks__/open.js.map +1 -0
- package/dist/__tests__/toolRegistry.test.d.ts +2 -0
- package/dist/__tests__/toolRegistry.test.d.ts.map +1 -0
- package/dist/__tests__/toolRegistry.test.js +160 -0
- package/dist/__tests__/toolRegistry.test.js.map +1 -0
- package/dist/api/client.d.ts +10 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +58 -0
- package/dist/api/client.js.map +1 -0
- package/dist/auth/tokenManager.d.ts +30 -0
- package/dist/auth/tokenManager.d.ts.map +1 -0
- package/dist/auth/tokenManager.js +107 -0
- package/dist/auth/tokenManager.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +20 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +144 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/attendances.d.ts +145 -0
- package/dist/tools/attendances.d.ts.map +1 -0
- package/dist/tools/attendances.js +202 -0
- package/dist/tools/attendances.js.map +1 -0
- package/dist/tools/auth.d.ts +62 -0
- package/dist/tools/auth.d.ts.map +1 -0
- package/dist/tools/auth.js +193 -0
- package/dist/tools/auth.js.map +1 -0
- package/dist/tools/classes.d.ts +125 -0
- package/dist/tools/classes.d.ts.map +1 -0
- package/dist/tools/classes.js +195 -0
- package/dist/tools/classes.js.map +1 -0
- package/dist/tools/finance.d.ts +152 -0
- package/dist/tools/finance.d.ts.map +1 -0
- package/dist/tools/finance.js +319 -0
- package/dist/tools/finance.js.map +1 -0
- package/dist/tools/index.d.ts +29 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +46 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/sessions.d.ts +126 -0
- package/dist/tools/sessions.d.ts.map +1 -0
- package/dist/tools/sessions.js +169 -0
- package/dist/tools/sessions.js.map +1 -0
- package/dist/tools/students.d.ts +171 -0
- package/dist/tools/students.d.ts.map +1 -0
- package/dist/tools/students.js +295 -0
- package/dist/tools/students.js.map +1 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +73 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# MCP Server - 薄荷排课系统 Claude Code 接口
|
|
2
|
+
|
|
3
|
+
这是一个 MCP (Model Context Protocol) 服务器,让 Claude Code 可以查询排课系统的数据。
|
|
4
|
+
|
|
5
|
+
> 完整使用指南请参考: [MCP 使用指南](../docs/MCP_USER_GUIDE.md)
|
|
6
|
+
|
|
7
|
+
## 快速开始(3步完成)
|
|
8
|
+
|
|
9
|
+
### 1. 安装
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g git+https://gitee.com/bohecoding/mintpublicmcp.git
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2. 配置
|
|
16
|
+
|
|
17
|
+
编辑 `~/.claude.json`(如果文件不存在就新建),添加以下内容:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"paike": {
|
|
23
|
+
"command": "mcp-paike"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
> 如果文件已有其他内容,只需添加 `mcpServers` 字段即可。
|
|
30
|
+
|
|
31
|
+
### 3. 登录
|
|
32
|
+
|
|
33
|
+
1. 重启 Claude Code(退出后重新打开)
|
|
34
|
+
2. 在 Claude Code 中说:「登录排课系统」
|
|
35
|
+
3. 浏览器会自动打开,扫码登录即可
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 可用工具
|
|
40
|
+
|
|
41
|
+
| 工具名 | 描述 |
|
|
42
|
+
|--------|------|
|
|
43
|
+
| `login` | 企业微信登录 |
|
|
44
|
+
| `get_current_user` | 获取当前登录用户信息 |
|
|
45
|
+
| `list_students` | 查询学员列表 |
|
|
46
|
+
| `get_student` | 获取学员详情 |
|
|
47
|
+
| `get_student_class` | 获取学员当前班级 |
|
|
48
|
+
| `get_student_packages` | 获取学员课包 |
|
|
49
|
+
| `get_student_balances` | 获取学员课次余额 |
|
|
50
|
+
| `list_classes` | 查询班级列表 |
|
|
51
|
+
| `get_class` | 获取班级详情 |
|
|
52
|
+
| `get_class_students` | 获取班级学员名单 |
|
|
53
|
+
| `list_sessions` | 查询课程排期 |
|
|
54
|
+
| `get_session` | 获取课程详情 |
|
|
55
|
+
| `list_attendances` | 查询考勤记录 |
|
|
56
|
+
| `get_attendance` | 获取考勤详情 |
|
|
57
|
+
| `list_teachers` | 获取教师列表 |
|
|
58
|
+
|
|
59
|
+
## 使用示例
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
# 查询在学学员
|
|
63
|
+
list_students(status: "ACTIVE")
|
|
64
|
+
|
|
65
|
+
# 搜索学员
|
|
66
|
+
list_students(search: "张三")
|
|
67
|
+
|
|
68
|
+
# 获取学员详情
|
|
69
|
+
get_student(studentId: "xxx-xxx-xxx")
|
|
70
|
+
|
|
71
|
+
# 查询本周课程
|
|
72
|
+
list_sessions(startDate: "2025-12-22", endDate: "2025-12-28")
|
|
73
|
+
|
|
74
|
+
# 查询考勤
|
|
75
|
+
list_attendances(sessionId: "xxx-xxx-xxx")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Token 说明
|
|
79
|
+
|
|
80
|
+
- Token 有效期为 **24 小时**
|
|
81
|
+
- 过期后重新说「登录排课系统」即可
|
|
82
|
+
- Token 保存在 `~/.config/mcp-paike/credentials.json`
|
|
83
|
+
|
|
84
|
+
## 注意事项
|
|
85
|
+
|
|
86
|
+
1. **只读接口**: 不能修改任何数据,仅提供查询功能
|
|
87
|
+
2. **权限控制**: 查询结果受用户权限控制(教师只能看到自己班级的学员)
|
|
88
|
+
3. **Token 安全**: Token 请勿分享,它代表您的身份
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 本地开发
|
|
93
|
+
|
|
94
|
+
如需修改 MCP 代码,可使用本地开发模式:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# 克隆仓库
|
|
98
|
+
git clone https://gitee.com/bohecoding/mintpublicmcp.git
|
|
99
|
+
cd mintpublicmcp
|
|
100
|
+
|
|
101
|
+
# 安装依赖并构建
|
|
102
|
+
npm install
|
|
103
|
+
npm run build
|
|
104
|
+
|
|
105
|
+
# 开发模式
|
|
106
|
+
npm run dev
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
本地开发模式的配置(`~/.claude.json`):
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"mcpServers": {
|
|
114
|
+
"paike": {
|
|
115
|
+
"command": "node",
|
|
116
|
+
"args": ["/path/to/mintpublicmcp/dist/index.js"],
|
|
117
|
+
"env": {
|
|
118
|
+
"MCP_API_BASE_URL": "https://paike.bohecoding.com",
|
|
119
|
+
"MCP_AUTH_PAGE_URL": "https://paike.bohecoding.com/mcp-auth"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../../src/__tests__/__mocks__/open.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open.js","sourceRoot":"","sources":["../../../src/__tests__/__mocks__/open.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,MAAc;IACzC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolRegistry.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/toolRegistry.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Unit tests for role-based tool filtering logic
|
|
2
|
+
// Testing the filtering algorithm directly without importing heavy dependencies
|
|
3
|
+
// Extract the filtering logic for testing
|
|
4
|
+
function getToolsForRole(tools, userRole) {
|
|
5
|
+
return tools.filter((tool) => {
|
|
6
|
+
// No role requirement = available to all authenticated users
|
|
7
|
+
if (!tool.requiredRole)
|
|
8
|
+
return true;
|
|
9
|
+
// ADMIN requirement: Only ADMIN or SUPER_ADMIN
|
|
10
|
+
if (tool.requiredRole === 'ADMIN') {
|
|
11
|
+
return userRole === 'ADMIN' || userRole === 'SUPER_ADMIN';
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
describe('Role-based tool filtering', () => {
|
|
17
|
+
// Mock tools for testing
|
|
18
|
+
const mockTools = [
|
|
19
|
+
{
|
|
20
|
+
name: 'login',
|
|
21
|
+
description: 'Login tool',
|
|
22
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
23
|
+
handler: async () => ({ content: [] }),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'get_teacher_sales',
|
|
27
|
+
description: 'Teacher sales tool (all users)',
|
|
28
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
29
|
+
handler: async () => ({ content: [] }),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'get_student_balance_detail',
|
|
33
|
+
description: 'Student balance tool (all users)',
|
|
34
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
35
|
+
handler: async () => ({ content: [] }),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'get_session_fee_rules',
|
|
39
|
+
description: 'Session fee rules (admin only)',
|
|
40
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
41
|
+
handler: async () => ({ content: [] }),
|
|
42
|
+
requiredRole: 'ADMIN',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
describe('getToolsForRole', () => {
|
|
46
|
+
it('should hide admin-only tools from STAFF users', () => {
|
|
47
|
+
const tools = getToolsForRole(mockTools, 'STAFF');
|
|
48
|
+
const toolNames = tools.map((t) => t.name);
|
|
49
|
+
expect(toolNames).not.toContain('get_session_fee_rules');
|
|
50
|
+
expect(toolNames).toContain('get_teacher_sales');
|
|
51
|
+
expect(toolNames).toContain('get_student_balance_detail');
|
|
52
|
+
expect(toolNames).toContain('login');
|
|
53
|
+
expect(tools.length).toBe(3);
|
|
54
|
+
});
|
|
55
|
+
it('should show admin-only tools to ADMIN users', () => {
|
|
56
|
+
const tools = getToolsForRole(mockTools, 'ADMIN');
|
|
57
|
+
const toolNames = tools.map((t) => t.name);
|
|
58
|
+
expect(toolNames).toContain('get_session_fee_rules');
|
|
59
|
+
expect(toolNames).toContain('get_teacher_sales');
|
|
60
|
+
expect(toolNames).toContain('get_student_balance_detail');
|
|
61
|
+
expect(tools.length).toBe(4);
|
|
62
|
+
});
|
|
63
|
+
it('should show admin-only tools to SUPER_ADMIN users', () => {
|
|
64
|
+
const tools = getToolsForRole(mockTools, 'SUPER_ADMIN');
|
|
65
|
+
const toolNames = tools.map((t) => t.name);
|
|
66
|
+
expect(toolNames).toContain('get_session_fee_rules');
|
|
67
|
+
expect(tools.length).toBe(4);
|
|
68
|
+
});
|
|
69
|
+
it('should hide admin-only tools when user is null (not authenticated)', () => {
|
|
70
|
+
const tools = getToolsForRole(mockTools, null);
|
|
71
|
+
const toolNames = tools.map((t) => t.name);
|
|
72
|
+
expect(toolNames).not.toContain('get_session_fee_rules');
|
|
73
|
+
expect(toolNames).toContain('login');
|
|
74
|
+
expect(tools.length).toBe(3);
|
|
75
|
+
});
|
|
76
|
+
it('should handle empty tools array', () => {
|
|
77
|
+
const tools = getToolsForRole([], 'ADMIN');
|
|
78
|
+
expect(tools.length).toBe(0);
|
|
79
|
+
});
|
|
80
|
+
it('should handle unknown role by hiding admin tools', () => {
|
|
81
|
+
const tools = getToolsForRole(mockTools, 'UNKNOWN_ROLE');
|
|
82
|
+
const toolNames = tools.map((t) => t.name);
|
|
83
|
+
expect(toolNames).not.toContain('get_session_fee_rules');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('Tool configuration validation', () => {
|
|
87
|
+
it('admin-only tools should have requiredRole set to ADMIN', () => {
|
|
88
|
+
const adminTool = mockTools.find((t) => t.name === 'get_session_fee_rules');
|
|
89
|
+
expect(adminTool?.requiredRole).toBe('ADMIN');
|
|
90
|
+
});
|
|
91
|
+
it('regular tools should not have requiredRole set', () => {
|
|
92
|
+
const regularTool = mockTools.find((t) => t.name === 'get_teacher_sales');
|
|
93
|
+
expect(regularTool?.requiredRole).toBeUndefined();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('STAFF data isolation for get_teacher_sales', () => {
|
|
98
|
+
// Test the permission logic for STAFF users
|
|
99
|
+
function canStaffAccessTeacherData(userRole, userId, requestedTeacherId) {
|
|
100
|
+
const isAdmin = userRole === 'ADMIN' || userRole === 'SUPER_ADMIN';
|
|
101
|
+
if (!isAdmin) {
|
|
102
|
+
// STAFF can only query their own data
|
|
103
|
+
if (requestedTeacherId && requestedTeacherId !== userId) {
|
|
104
|
+
return { allowed: false, reason: 'STAFF can only query own data' };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { allowed: true };
|
|
108
|
+
}
|
|
109
|
+
it('STAFF should be able to query their own data', () => {
|
|
110
|
+
const result = canStaffAccessTeacherData('STAFF', 'user-123', 'user-123');
|
|
111
|
+
expect(result.allowed).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
it('STAFF should NOT be able to query other teachers data', () => {
|
|
114
|
+
const result = canStaffAccessTeacherData('STAFF', 'user-123', 'other-user-456');
|
|
115
|
+
expect(result.allowed).toBe(false);
|
|
116
|
+
expect(result.reason).toBe('STAFF can only query own data');
|
|
117
|
+
});
|
|
118
|
+
it('STAFF should be able to query without specifying teacherId (defaults to self)', () => {
|
|
119
|
+
const result = canStaffAccessTeacherData('STAFF', 'user-123', undefined);
|
|
120
|
+
expect(result.allowed).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
it('ADMIN should be able to query any teacher data', () => {
|
|
123
|
+
const result = canStaffAccessTeacherData('ADMIN', 'admin-user', 'other-teacher-789');
|
|
124
|
+
expect(result.allowed).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
it('SUPER_ADMIN should be able to query any teacher data', () => {
|
|
127
|
+
const result = canStaffAccessTeacherData('SUPER_ADMIN', 'super-admin', 'any-teacher');
|
|
128
|
+
expect(result.allowed).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe('Currency parsing', () => {
|
|
132
|
+
// Test the fixed currency parsing logic
|
|
133
|
+
function parseCurrency(value) {
|
|
134
|
+
return parseFloat((value || '¥0').replace('¥', '').replace(/,/g, '')) || 0;
|
|
135
|
+
}
|
|
136
|
+
it('should parse simple currency values', () => {
|
|
137
|
+
expect(parseCurrency('¥123.45')).toBe(123.45);
|
|
138
|
+
});
|
|
139
|
+
it('should parse values with thousands separator', () => {
|
|
140
|
+
expect(parseCurrency('¥12,345.67')).toBe(12345.67);
|
|
141
|
+
});
|
|
142
|
+
it('should parse values with multiple commas', () => {
|
|
143
|
+
expect(parseCurrency('¥1,234,567.89')).toBe(1234567.89);
|
|
144
|
+
});
|
|
145
|
+
it('should handle null values', () => {
|
|
146
|
+
expect(parseCurrency(null)).toBe(0);
|
|
147
|
+
});
|
|
148
|
+
it('should handle undefined values', () => {
|
|
149
|
+
expect(parseCurrency(undefined)).toBe(0);
|
|
150
|
+
});
|
|
151
|
+
it('should handle values without currency symbol', () => {
|
|
152
|
+
expect(parseCurrency('1,234.56')).toBe(1234.56);
|
|
153
|
+
});
|
|
154
|
+
it('should handle zero values', () => {
|
|
155
|
+
expect(parseCurrency('¥0')).toBe(0);
|
|
156
|
+
expect(parseCurrency('¥0.00')).toBe(0);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
export {};
|
|
160
|
+
//# sourceMappingURL=toolRegistry.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolRegistry.test.js","sourceRoot":"","sources":["../../src/__tests__/toolRegistry.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare class ApiClient {
|
|
2
|
+
private instance;
|
|
3
|
+
constructor();
|
|
4
|
+
private formatError;
|
|
5
|
+
get<T>(url: string, params?: Record<string, any>): Promise<T>;
|
|
6
|
+
post<T>(url: string, data?: any): Promise<T>;
|
|
7
|
+
}
|
|
8
|
+
export declare const apiClient: ApiClient;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAKA,cAAM,SAAS;IACb,OAAO,CAAC,QAAQ,CAAgB;;IAoChC,OAAO,CAAC,WAAW;IAcb,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAK7D,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;CAInD;AAED,eAAO,MAAM,SAAS,WAAkB,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { config } from '../config/index.js';
|
|
3
|
+
import { tokenManager } from '../auth/tokenManager.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
class ApiClient {
|
|
6
|
+
instance;
|
|
7
|
+
constructor() {
|
|
8
|
+
this.instance = axios.create({
|
|
9
|
+
baseURL: config.apiBaseUrl,
|
|
10
|
+
timeout: 30000,
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
// Add auth token to requests
|
|
16
|
+
this.instance.interceptors.request.use((config) => {
|
|
17
|
+
const token = tokenManager.getToken();
|
|
18
|
+
if (token) {
|
|
19
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
20
|
+
}
|
|
21
|
+
return config;
|
|
22
|
+
});
|
|
23
|
+
// Handle errors
|
|
24
|
+
this.instance.interceptors.response.use((response) => response, (error) => {
|
|
25
|
+
if (error.response?.status === 401) {
|
|
26
|
+
logger.warn('Token 已过期或无效');
|
|
27
|
+
// Clear credentials asynchronously, don't block error response
|
|
28
|
+
tokenManager.clearCredentials().catch(() => {
|
|
29
|
+
// Ignore errors during cleanup
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
throw this.formatError(error);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
formatError(error) {
|
|
36
|
+
const response = error.response;
|
|
37
|
+
if (response?.data?.message) {
|
|
38
|
+
return new Error(response.data.message);
|
|
39
|
+
}
|
|
40
|
+
if (error.code === 'ECONNREFUSED') {
|
|
41
|
+
return new Error('无法连接到服务器,请检查网络或服务是否运行');
|
|
42
|
+
}
|
|
43
|
+
if (error.code === 'ECONNABORTED') {
|
|
44
|
+
return new Error('请求超时,请稍后重试');
|
|
45
|
+
}
|
|
46
|
+
return new Error(error.message || '请求失败');
|
|
47
|
+
}
|
|
48
|
+
async get(url, params) {
|
|
49
|
+
const response = await this.instance.get(url, { params });
|
|
50
|
+
return response.data.data;
|
|
51
|
+
}
|
|
52
|
+
async post(url, data) {
|
|
53
|
+
const response = await this.instance.post(url, data);
|
|
54
|
+
return response.data.data;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export const apiClient = new ApiClient();
|
|
58
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,SAAS;IACL,QAAQ,CAAgB;IAEhC;QACE,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC;YAC3B,OAAO,EAAE,MAAM,CAAC,UAAU;YAC1B,OAAO,EAAE,KAAK;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;aACnC;SACF,CAAC,CAAC;QAEH,6BAA6B;QAC7B,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YAChD,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC;YACtC,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,OAAO,CAAC,aAAa,GAAG,UAAU,KAAK,EAAE,CAAC;YACnD,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC,CAAC;QAEH,gBAAgB;QAChB,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CACrC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,EACtB,CAAC,KAAiB,EAAE,EAAE;YACpB,IAAI,KAAK,CAAC,QAAQ,EAAE,MAAM,KAAK,GAAG,EAAE,CAAC;gBACnC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;gBAC5B,+DAA+D;gBAC/D,YAAY,CAAC,gBAAgB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;oBACzC,+BAA+B;gBACjC,CAAC,CAAC,CAAC;YACL,CAAC;YACD,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC,CACF,CAAC;IACJ,CAAC;IAEO,WAAW,CAAC,KAAiB;QACnC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAe,CAAC;QACvC,IAAI,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;YAC5B,OAAO,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YAClC,OAAO,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC5C,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YAClC,OAAO,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC;QACjC,CAAC;QACD,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,MAA4B;QACpD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAgC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACzF,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,IAAI,CAAI,GAAW,EAAE,IAAU;QACnC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAgC,GAAG,EAAE,IAAI,CAAC,CAAC;QACpF,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,SAAS,EAAE,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface StoredCredentials {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
issuedAt: number;
|
|
4
|
+
expiresIn: number;
|
|
5
|
+
user: {
|
|
6
|
+
id: string;
|
|
7
|
+
username: string;
|
|
8
|
+
realName: string;
|
|
9
|
+
role: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
declare class TokenManager {
|
|
13
|
+
private credentials;
|
|
14
|
+
private initPromise;
|
|
15
|
+
init(): Promise<void>;
|
|
16
|
+
private doInit;
|
|
17
|
+
storeCredentials(credentials: StoredCredentials): Promise<void>;
|
|
18
|
+
clearCredentials(): Promise<void>;
|
|
19
|
+
isAuthenticated(): boolean;
|
|
20
|
+
isTokenExpiringSoon(): boolean;
|
|
21
|
+
getToken(): string | null;
|
|
22
|
+
getUser(): StoredCredentials['user'] | null;
|
|
23
|
+
getLoginDuration(): string;
|
|
24
|
+
getTimeUntilExpiry(): string;
|
|
25
|
+
getUserRole(): string | null;
|
|
26
|
+
isAdmin(): boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare const tokenManager: TokenManager;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=tokenManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenManager.d.ts","sourceRoot":"","sources":["../../src/auth/tokenManager.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAKD,cAAM,YAAY;IAChB,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,WAAW,CAA8B;IAE3C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAQb,MAAM;IAYd,gBAAgB,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/D,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAUvC,eAAe,IAAI,OAAO;IAS1B,mBAAmB,IAAI,OAAO;IAU9B,QAAQ,IAAI,MAAM,GAAG,IAAI;IAKzB,OAAO,IAAI,iBAAiB,CAAC,MAAM,CAAC,GAAG,IAAI;IAI3C,gBAAgB,IAAI,MAAM;IAgB1B,kBAAkB,IAAI,MAAM;IAkB5B,WAAW,IAAI,MAAM,GAAG,IAAI;IAI5B,OAAO,IAAI,OAAO;CAInB;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.config', 'mcp-paike');
|
|
6
|
+
const CREDENTIALS_FILE = join(CONFIG_DIR, 'credentials.json');
|
|
7
|
+
class TokenManager {
|
|
8
|
+
credentials = null;
|
|
9
|
+
initPromise = null;
|
|
10
|
+
async init() {
|
|
11
|
+
// Use Promise caching to prevent race conditions
|
|
12
|
+
if (this.initPromise)
|
|
13
|
+
return this.initPromise;
|
|
14
|
+
this.initPromise = this.doInit();
|
|
15
|
+
return this.initPromise;
|
|
16
|
+
}
|
|
17
|
+
async doInit() {
|
|
18
|
+
try {
|
|
19
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
20
|
+
const data = await fs.readFile(CREDENTIALS_FILE, 'utf-8');
|
|
21
|
+
this.credentials = JSON.parse(data);
|
|
22
|
+
logger.debug('已加载存储的凭证');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// File doesn't exist or is invalid - that's OK
|
|
26
|
+
this.credentials = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async storeCredentials(credentials) {
|
|
30
|
+
this.credentials = credentials;
|
|
31
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
32
|
+
await fs.writeFile(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), {
|
|
33
|
+
mode: 0o600, // Only owner can read/write
|
|
34
|
+
});
|
|
35
|
+
logger.info('凭证已保存');
|
|
36
|
+
}
|
|
37
|
+
async clearCredentials() {
|
|
38
|
+
this.credentials = null;
|
|
39
|
+
try {
|
|
40
|
+
await fs.unlink(CREDENTIALS_FILE);
|
|
41
|
+
logger.info('凭证已清除');
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// Ignore if file doesn't exist
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
isAuthenticated() {
|
|
48
|
+
if (!this.credentials)
|
|
49
|
+
return false;
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const expiresAt = this.credentials.issuedAt + this.credentials.expiresIn * 1000;
|
|
52
|
+
return now < expiresAt;
|
|
53
|
+
}
|
|
54
|
+
isTokenExpiringSoon() {
|
|
55
|
+
if (!this.credentials)
|
|
56
|
+
return true;
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const expiresAt = this.credentials.issuedAt + this.credentials.expiresIn * 1000;
|
|
59
|
+
const thirtyMinutes = 30 * 60 * 1000;
|
|
60
|
+
return now > expiresAt - thirtyMinutes;
|
|
61
|
+
}
|
|
62
|
+
getToken() {
|
|
63
|
+
if (!this.isAuthenticated())
|
|
64
|
+
return null;
|
|
65
|
+
return this.credentials?.accessToken || null;
|
|
66
|
+
}
|
|
67
|
+
getUser() {
|
|
68
|
+
return this.credentials?.user || null;
|
|
69
|
+
}
|
|
70
|
+
getLoginDuration() {
|
|
71
|
+
if (!this.credentials)
|
|
72
|
+
return '未登录';
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
const loginTime = this.credentials.issuedAt;
|
|
75
|
+
const diff = now - loginTime;
|
|
76
|
+
const hours = Math.floor(diff / (60 * 60 * 1000));
|
|
77
|
+
const minutes = Math.floor((diff % (60 * 60 * 1000)) / (60 * 1000));
|
|
78
|
+
if (hours > 0) {
|
|
79
|
+
return `已登录 ${hours} 小时 ${minutes} 分钟`;
|
|
80
|
+
}
|
|
81
|
+
return `已登录 ${minutes} 分钟`;
|
|
82
|
+
}
|
|
83
|
+
getTimeUntilExpiry() {
|
|
84
|
+
if (!this.credentials)
|
|
85
|
+
return '未登录';
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
const expiresAt = this.credentials.issuedAt + this.credentials.expiresIn * 1000;
|
|
88
|
+
const remaining = expiresAt - now;
|
|
89
|
+
if (remaining <= 0)
|
|
90
|
+
return '已过期';
|
|
91
|
+
const hours = Math.floor(remaining / (60 * 60 * 1000));
|
|
92
|
+
const minutes = Math.floor((remaining % (60 * 60 * 1000)) / (60 * 1000));
|
|
93
|
+
if (hours > 0) {
|
|
94
|
+
return `${hours} 小时 ${minutes} 分钟后过期`;
|
|
95
|
+
}
|
|
96
|
+
return `${minutes} 分钟后过期`;
|
|
97
|
+
}
|
|
98
|
+
getUserRole() {
|
|
99
|
+
return this.credentials?.user?.role || null;
|
|
100
|
+
}
|
|
101
|
+
isAdmin() {
|
|
102
|
+
const role = this.getUserRole();
|
|
103
|
+
return role === 'ADMIN' || role === 'SUPER_ADMIN';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export const tokenManager = new TokenManager();
|
|
107
|
+
//# sourceMappingURL=tokenManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenManager.js","sourceRoot":"","sources":["../../src/auth/tokenManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,MAAM;;;;CAIlB,CAAC;AAEF,wBAAgB,cAAc,IAAI,IAAI,CAQrC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
2
|
+
import { resolve, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
// Load .env from package directory
|
|
6
|
+
dotenvConfig({ path: resolve(__dirname, '../../.env') });
|
|
7
|
+
export const config = {
|
|
8
|
+
apiBaseUrl: process.env.MCP_API_BASE_URL || 'https://paike.bohecoding.com',
|
|
9
|
+
authPageUrl: process.env.MCP_AUTH_PAGE_URL || 'https://paike.bohecoding.com/mcp-auth',
|
|
10
|
+
logLevel: process.env.MCP_LOG_LEVEL || 'info',
|
|
11
|
+
};
|
|
12
|
+
export function validateConfig() {
|
|
13
|
+
const required = ['MCP_API_BASE_URL'];
|
|
14
|
+
const missing = required.filter((key) => !process.env[key]);
|
|
15
|
+
if (missing.length > 0) {
|
|
16
|
+
console.warn(`[MCP] Warning: Missing environment variables: ${missing.join(', ')}`);
|
|
17
|
+
console.warn(`[MCP] Using defaults. Set these in ~/.config/mcp-paike/.env or via Claude Code config.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,mCAAmC;AACnC,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;AAEzD,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,8BAA8B;IAC1E,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,uCAAuC;IACrF,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,MAAM;CAC9C,CAAC;AAEF,MAAM,UAAU,cAAc;IAC5B,MAAM,QAAQ,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAE5D,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,iDAAiD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpF,OAAO,CAAC,IAAI,CAAC,wFAAwF,CAAC,CAAC;IACzG,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|