mcp-sis 0.1.0
MCP server for CUHKSZ SIS system
License: MIT
Requires Python: >=3.9

# CUHKSZ SIS MCP 服务器

一个基于 Model Context Protocol (MCP) 的香港中文大学（深圳）学生信息系统 (SIS) 接口服务，提供课程表查询、成绩查询、课程信息查询等功能。

## 📋 目录

- [项目架构](#项目架构)
- [实现方式与核心逻辑](#实现方式与核心逻辑)
- [部署指南](#部署指南)
- [测试说明](#测试说明)
- [API 接口](#api-接口)
- [故障排除](#故障排除)

## 🏗️ 项目架构

### 整体架构图

```
SIS-MCP/
├── src/mcp_server_sis/          # 核心源代码目录
│   ├── __init__.py              # 包初始化文件
│   ├── __main__.py              # 服务启动入口点
│   ├── sis.py                   # MCP 服务器主逻辑，工具定义
│   ├── sis_system.py            # SIS 系统交互核心模块
│   ├── requirements.txt         # Python 依赖包列表
│   └── Dockerfile               # Docker 容器构建文件
├── test/
│   └── test.py                  # 完整功能测试脚本
├── docker-compose.yml           # Docker Compose 配置
├── .env                         # 环境变量配置文件（需手动创建）
└── README.md                    # 项目文档
```

### 核心模块说明

#### 1. `sis.py` - MCP 服务器层
- **职责**: 定义 MCP 工具接口，处理异步请求，管理缓存和登录状态
- **核心功能**:
  - 全局登录状态管理（15分钟超时自动重登录）
  - 结果缓存机制（TTL 可配置，默认1小时）
  - 异步工具包装（将同步爬虫代码在线程池中执行）
  - 7个 MCP 工具的定义和实现

#### 2. `sis_system.py` - SIS 交互层
- **职责**: 直接与 CUHKSZ SIS 网站交互，处理登录认证和数据抓取
- **核心功能**:
  - 双阶段 ADFS OAuth2 登录流程
  - HTML 解析和数据提取
  - 会话管理和 Cookie 处理
  - 错误处理和重试机制

#### 3. `__main__.py` - 服务启动层
- **职责**: 服务器启动配置和端口绑定
- **传输协议**: Server-Sent Events (SSE)
- **默认端口**: 3000

### 技术栈

- **后端框架**: FastMCP (基于 FastAPI 的 MCP 实现)
- **HTTP 客户端**: requests + urllib3
- **HTML 解析**: lxml + XPath
- **SSL 处理**: pyOpenSSL
- **容器化**: Docker + Docker Compose
- **传输协议**: Server-Sent Events (SSE)

## 🔧 实现方式与核心逻辑

### 1. 登录认证流程

```python
# 双阶段 OAuth2 + ADFS 认证
def login(self) -> bool:
    # 阶段1: 获取授权码
    auth_url = "https://sts.cuhk.edu.cn/adfs/oauth2/authorize?..."
    data1 = {'UserName': f'cuhksz\\{username}', 'Password': password, ...}
    r1 = self.session.post(auth_url, data=data1, allow_redirects=True)
    code = extract_code_from_redirect(r1.url)
    
    # 阶段2: 使用授权码完成 SIS 登录
    login_url = "https://sis.cuhk.edu.cn/psp/csprd/"
    data2 = {'userid': 'CUSZ_SSO_LOGIN', 'pwd': random_string, ...}
    r2 = self.session.post(login_url, data=data2, params={'code': code})
    
    return verify_login_success(r2)
```

### 2. 缓存机制

```python
# 全局缓存，支持 TTL 过期
_global_cache: Dict[str, Dict] = {}

def _get_cached_or_fetch(cache_key: str, fetch_func, ttl: int = 300):
    current_time = time.time()
    
    # 检查缓存是否存在且未过期
    if (cache_key in _global_cache and 
        current_time - _global_cache[cache_key]['timestamp'] < ttl):
        return _global_cache[cache_key]['data']
    
    # 缓存未命中，重新获取数据
    data = fetch_func()
    _global_cache[cache_key] = {'data': data, 'timestamp': current_time}
    return data
```

### 3. 异步包装机制

```python
# 将同步爬虫代码包装为异步执行
@mcp.tool()
async def sis_get_schedule() -> str:
    def fetch_schedule():
        sis = _get_sis_instance()
        return sis.get_schedule()
    
    return await asyncio.get_event_loop().run_in_executor(
        None, lambda: _get_cached_or_fetch("schedule", fetch_schedule, ttl=3600)
    )
```

### 4. 数据抓取核心逻辑

每个数据抓取功能都遵循以下模式：
1. **状态令牌获取**: 解析页面中的 `ICSID` 和 `ICStateNum`
2. **表单提交**: 构造 POST 数据并提交查询请求
3. **HTML 解析**: 使用 XPath 选择器提取结构化数据
4. **结果格式化**: 将数据转换为可读的字符串格式

## 🚀 部署指南

### 环境要求

- 足够的Docker和Docker Compose版本
- 有效的 CUHKSZ 学号和密码

### 1. 克隆项目

```bash
git clone https://github.com/BetterAndBetterII/awesome-cuhksz-mcp.git
cd SIS-MCP
```

### 2. 创建环境变量文件

在项目根目录创建 `.env` 文件：

```bash
# .env 文件内容
SIS_USERNAME=你的学号
SIS_PASSWORD=你的密码
MCP_SERVER_PORT=3000
```

**⚠️ 安全提醒**: 
- 请确保 `.env` 文件不会被提交到版本控制系统
- 使用强密码并定期更换
- 在生产环境中考虑使用更安全的密钥管理方案

### 3. 构建和启动服务

```bash
# 构建并启动容器（首次运行或代码更新后）
docker-compose up --build

# 后台运行
docker-compose up -d --build

# 查看日志
docker-compose logs -f sis-mcp

# 停止服务
docker-compose down
```

### 4. 验证部署

服务启动后，你应该能看到类似以下的日志输出：

```
sis-mcp-server | INFO:     Started server process [1]
sis-mcp-server | INFO:     Waiting for application startup.
sis-mcp-server | INFO:     Application startup complete.
sis-mcp-server | INFO:     Uvicorn running on http://0.0.0.0:3000 (Press CTRL+C to quit)
```

服务将在 `http://localhost:3000` 上提供 MCP 接口。

### Docker Compose 配置说明

```yaml
services:
  sis-mcp:
    container_name: sis-mcp-server
    build:
      context: .                              # 构建上下文为项目根目录
      dockerfile: ./src/mcp_server_sis/Dockerfile
    ports:
      - "3000:3000"                          # 端口映射：主机3000 -> 容器3000
    env_file:
      - .env                                 # 环境变量文件
    restart: unless-stopped                  # 自动重启策略
```

## 🧪 测试说明

### 自动化测试

项目提供了完整的自动化测试脚本，可以测试所有 MCP 工具的功能。

#### 运行测试

```bash
# 确保服务已启动
docker-compose up -d --build

# 进入测试目录并运行测试
cd test
python test.py
```

#### 测试脚本功能

`test/test.py` 脚本会：

1. **连接验证**: 连接到 MCP 服务器 (`http://localhost:3000/sse`)
2. **工具发现**: 自动发现所有可用的 MCP 工具
3. **参数化测试**: 为需要参数的工具提供默认测试数据
4. **结果验证**: 调用每个工具并显示结果预览
5. **错误处理**: 捕获并报告任何调用错误

#### 测试输出示例

```
🚀 开始全面测试，正在连接到: http://localhost:3000/sse

✅ 连接成功！发现 7 个可用工具。将依次调用...

--- 正在调用工具: sis_get_schedule ---
   (无参数)

✅ sis_get_schedule 调用成功！结果预览:
---
Monday:
Course: CSC 3002 - L1Lecture
Type: Lecture
Duration: 09:00AM - 10:00AM
Location: Room 101
...
---

--- 正在调用工具: sis_get_course ---
   参数: {'course_code': 'CSC3002', 'term': '2410', 'open_only': False}

✅ sis_get_course 调用成功！结果预览:
---
Search Result: code=CSC3002, term=2410, open_only=False
Section: CSC3002-L1
Enrollment: 45/50
...
---

🏁 所有工具调用完毕，全面测试结束！
```

### 手动测试

你也可以使用任何支持 MCP 协议的客户端进行手动测试：

#### 使用 curl 测试

```bash
# 获取工具列表
curl -X POST http://localhost:3000/sse \
  -H "Content-Type: application/json" \
  -d '{"method": "tools/list"}'

# 调用具体工具
curl -X POST http://localhost:3000/sse \
  -H "Content-Type: application/json" \
  -d '{
    "method": "tools/call",
    "params": {
      "name": "sis_get_schedule"
    }
  }'
```

### 测试数据配置

在 `test/test.py` 中，你可以修改 `DEFAULT_INPUTS` 来自定义测试参数：

```python
DEFAULT_INPUTS = {
    "sis_get_course": {
        "course_code": "CSC3002",    # 修改为你想测试的课程代码
        "term": "2410",              # 修改为当前学期
        "open_only": False,
    },
    "sis_get_grades": {"term": "2024-25 Term 2"},
    "sis_get_course_outline": {"course_code": "CSC3002"},
}
```

## 📡 API 接口

### 可用工具列表

| 工具名称 | 描述 | 参数 | 缓存时间 |
|---------|------|------|----------|
| `sis_get_schedule` | 获取个人课程表 | 无 | 1小时 |
| `sis_get_course` | 查询课程信息 | `course_code`, `term`, `open_only` | 1小时 |
| `sis_get_grades` | 查询成绩 | `term` | 1小时 |
| `sis_get_course_outline` | 查询课程大纲 | `course_code` | 1小时 |
| `sis_get_academic_record` | 查询学术记录 | 无 | 1小时 |
| `sis_clear_cache` | 清除所有缓存 | 无 | - |
| `sis_force_relogin` | 强制重新登录 | 无 | - |

### 参数说明

- `course_code`: 课程代码，如 "CSC3002"
- `term`: 学期代码，如 "2410" 或学期名称如 "2024-25 Term 1"
- `open_only`: 布尔值，是否只显示开放课程

### 响应格式

所有工具都返回格式化的字符串，包含查询结果的详细信息。

## 🔧 故障排除

### 常见问题

#### 1. 容器启动失败

**症状**: `docker-compose up` 失败
**解决方案**:
```bash
# 检查 .env 文件是否存在
ls -la .env

# 检查 Docker 服务状态
docker --version
docker-compose --version

# 重新构建容器
docker-compose down
docker-compose up --build
```

#### 2. 登录失败

**症状**: 日志显示 "Failed to login to SIS system"
**解决方案**:
- 验证 `.env` 文件中的用户名和密码是否正确
- 确认账号没有被锁定
- 检查网络连接是否正常

#### 3. 测试连接失败

**症状**: `test.py` 无法连接到服务
**解决方案**:
```bash
# 检查容器是否运行
docker-compose ps

# 检查端口是否被占用
netstat -tulpn | grep 3000

# 查看容器日志
docker-compose logs sis-mcp
```

#### 4. 数据抓取失败

**症状**: 工具返回空结果或错误
**解决方案**:
- 检查 SIS 网站是否可访问
- 清除缓存：调用 `sis_clear_cache` 工具
- 强制重新登录：调用 `sis_force_relogin` 工具

### 日志分析

查看详细日志以诊断问题：

```bash
# 实时查看日志
docker-compose logs -f sis-mcp

# 查看最近的日志
docker-compose logs --tail=100 sis-mcp
```

### 性能优化

1. **缓存配置**: 根据数据更新频率调整 TTL
2. **并发限制**: 避免同时发起过多请求
3. **资源监控**: 监控容器内存和 CPU 使用情况

```bash
# 监控容器资源使用
docker stats sis-mcp-server
```

## 📄 许可证

本项目采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。

## 🤝 贡献

欢迎提交 Issue 和 Pull Request 来改进这个项目！

---

**⚠️ 免责声明**: 本项目仅供学习和研究使用。请遵守学校的相关政策和服务条款。