TPCU MCP Server
by alaner652
README.md
# tpcu-mcp
[](https://github.com/alaner652/tpcu-mcp/actions/workflows/ci.yml)
[](LICENSE)
[](package.json)
TPCU(臺北城市科技大學)校務系統的 [MCP](https://modelcontextprotocol.io) Server。
把課表、缺曠、成績、假單、工讀考勤包成 MCP 工具,讓支援 MCP 的 AI 應用(Claude Code、Claude Desktop、Codex CLI、Gemini CLI 等)可以在對話中直接查詢與操作:
> 「我今天有什麼課」「最近有沒有缺曠」「幫我把 10/12 的缺曠請事假」「幫我排這個月的工讀班表」
- TypeScript + 官方 `@modelcontextprotocol/sdk`,stdio transport,零額外後端。
- 直接對 `siw.tpcu.edu.tw` 發 HTTP、解析 HTML。
- 帳密由 AI 在對話中以 `login` 工具向使用者索取,只存在記憶體;session 用完保留,不落地、不記錄。
> **非官方工具**:本專案與臺北城市科技大學無關,純屬學生自用。使用前請閱讀[安全政策](SECURITY.md),自行評估把帳密交給 AI 應用的風險。
## 安裝
```bash
git clone https://github.com/alaner652/tpcu-mcp.git
cd tpcu-mcp
npm install
npm run build # 輸出 dist/
```
需要 Node.js 20 以上。開發用指令:
```bash
npm run dev # tsx 直接跑 src/index.ts
npm test # vitest:parsers、排班、整合測試
npm run inspect # MCP Inspector,手動呼叫工具
```
## 接到 MCP Client
以下把 `/absolute/path/to/tpcu-mcp` 換成你 clone 的路徑。
### Claude Code
```bash
claude mcp add --scope user tpcu -- node /absolute/path/to/tpcu-mcp/dist/index.js
```
### Claude Desktop
`~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"tpcu": {
"command": "node",
"args": ["/absolute/path/to/tpcu-mcp/dist/index.js"]
}
}
}
```
### Codex CLI
`~/.codex/config.toml`:
```toml
[mcp_servers.tpcu]
command = "node"
args = ["/absolute/path/to/tpcu-mcp/dist/index.js"]
```
### Gemini CLI
`~/.gemini/settings.json`:
```json
{
"mcpServers": {
"tpcu": {
"command": "node",
"args": ["/absolute/path/to/tpcu-mcp/dist/index.js"]
}
}
}
```
開發時可以把 `node dist/index.js` 換成 `npx tsx src/index.ts`,省掉 build。改了 `src/` 之後記得重新 build 並讓 client 重新連線。
## 對話範例
```
你:看我最近有沒有缺曠
AI:(get_session_status → 未登入 → 向你要學號密碼 → login → fetch_absence)
最近 30 天有 7 節缺曠:10/12(一)6-8 節、10/14(三)3-6 節 …
你:幫我把 10/12 的請事假,原因家裡有事
AI:(get_leave_form 確認當天節次 → 逐項列出日期 / 節次 / 假別 / 原因請你確認)
你:確認送出
AI:(apply_leave)已送出,等待導師線上審核。
```
## 環境變數(皆選填)
| 變數 | 預設 | 說明 |
| --- | --- | --- |
| `SCHOOL_BASE_URL` | `https://siw.tpcu.edu.tw` | 學校系統端點 |
| `UPSTREAM_TIMEOUT_MS` | `30000` | 對學校的單次請求逾時 |
| `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error`,日誌只寫 stderr |
| `TPCU_ATTACHMENT_DIR` | 無 | 設定後 `apply_leave` 的附件必須位於此目錄內 |
設定錯誤時 server 會在啟動時直接失敗並把原因寫到 stderr(不含值)。
## 工具清單
| 工具 | 需登入 | 副作用 | 用途 |
| --- | :-: | :-: | --- |
| `login` | — | — | 以學號 / 密碼登入,回傳姓名、學號、目前學期(代碼 + 標籤) |
| `logout` | — | — | 清除記憶體 session(只在使用者要求時) |
| `get_session_status` | — | — | 目前是否已登入、目前學期;需登入的操作前先呼叫 |
| `get_current_date` | — | — | 今天(台北時間;西元 + 民國 compact) |
| `get_semester_options` | ✓ | — | 可查詢的學期清單 |
| `fetch_schedule` | ✓ | — | 整學期課表(`semester_value` 省略 = 目前學期) |
| `get_day_schedule` | ✓ | — | 某一天的課(省略 = 今天),依時間排序 |
| `get_next_class` | ✓ | — | 現在正在上 / 下一堂課(台北時間) |
| `fetch_absence` | ✓ | — | 日期範圍缺曠(學期省略 = 目前學期;預設近 30 天) |
| `fetch_grades` | ✓ | — | 歷年成績 |
| `get_leaves` | ✓ | — | 指定日期範圍的假單與審核狀態 |
| `get_leave_form` | ✓ | — | 指定日期的節次順序與有課節次 |
| `apply_leave` | ✓ | 新增 | 送出請假(公假需附件) |
| `delete_leave` | ✓ | **不可逆** | 撤回待審假單 |
| `get_workstudy_master` | ✓ | — | 工讀月份主檔(unit_id / kind_id / 核銷狀態) |
| `get_workstudy_month` | ✓ | — | 某月既有出勤列 |
| `plan_workstudy` | 選填 | — | 依固定班表攤出當月出勤清單(可用課表防呆) |
| `save_workstudy` | ✓ | **整月覆蓋** | 送出某月工讀考勤 |
每個工具都帶 MCP `annotations`(`readOnlyHint` / `destructiveHint`),有副作用的工具描述中要求 AI 先向使用者確認。
目前「向使用者確認」由 Host 端的模型負責;server 以註解與描述提供足夠訊號。
## 領域速查
- **日期**:工具參數一律民國 compact `YYYMMDD`,如 `1150521` = 2026/05/21。西元 = 民國 + 1911。
- **學期代碼**:`114,2` = 114 學年度第 2 學期。
- **節次順序**:朝會 → 早自習 → 1 … 9 → K → A → B → C → D → E。
- **假別代碼**:21 事假、22 病假、23 公假(限兵役 / 法院傳訴 / 國家考試 / 系科公假,需 JPEG 或 PDF 附件)、24 喪假、25 婚假、26 孕(產)假、27 哺育假、28 防疫假、29 生理假、31 原住民假。
- **工讀**:每日 7.5、每週 8 小時上限;存檔為整月覆蓋,要保留的既有列(含 `seq`)必須一起送。
## 錯誤格式
工具失敗時回 `isError: true`,內容為:
```json
{ "error": { "code": "SESSION_EXPIRED", "message": "學校系統 session 已過期", "hint": "請重新呼叫 login 工具。" } }
```
| code | 意義 |
| --- | --- |
| `INVALID_INPUT` | 參數格式錯誤(schema 層通常已先擋下) |
| `NOT_LOGGED_IN` | 尚未 `login` |
| `LOGIN_FAILED` | 帳密錯誤或學校忙線 |
| `SESSION_EXPIRED` | 學校判定 session 過期;server 已清除狀態,需重新登入 |
| `UPSTREAM_ERROR` | 學校系統連線 / HTTP 錯誤 |
| `PRECONDITION_FAILED` | 缺前置資料(如該月無 pay_seqid) |
| `LEAVE_CONFLICT` / `MISSING_ATTACHMENT` / `LEAVE_APPROVED` | 請假 / 刪假的業務錯誤(出現在 `error_code` 欄) |
| `INTERNAL` | 未預期例外;細節只在 stderr |
## 安全注意
- 日誌只寫 stderr,欄位名含 `jsessionid` / `pwd` / `cookie` 等一律遮蔽。
- 學校伺服器只送 leaf 憑證,server 內嵌 TWCA 中繼憑證完成鏈驗證,不關閉 TLS 驗證。
- 學校對未登入請求會 302 到 `http://localhost`,HTTP 層只跟同源轉址。
- 附件僅接受 `.jpg` / `.jpeg` / `.pdf`;設 `TPCU_ATTACHMENT_DIR` 可把可讀路徑限制在單一目錄(解析 symlink 後比對)。
## 專案結構
```
src/
├── index.ts # 進入點:設定驗證 → server → stdio
├── server.ts # 建 McpServer、註冊工具(可注入 client 供測試)
├── config.ts # 環境變數讀取與驗證
├── adapters/tpcu-client.ts # 對學校系統的 HTTP 層(cookie、TLS、redirect、解碼)
├── parsers/ # HTML → 結構化資料(純函式,不碰網路)
├── services/ # 業務邏輯(登入狀態、各功能、工讀排班)
├── tools/ # MCP 工具定義 + 統一註冊 / 錯誤處理
└── shared/ # errors、logger、date、alert、zod 片段
tests/
├── unit/ # parsers、日期、排班、config
├── integration/ # InMemoryTransport + stub upstream 的端對端
└── fixtures/html.ts # 校務系統頁面樣本(課表、登入頁為去識別化的真實快照,其餘合成)
```
設計細節與決策紀錄見 [ARCHITECTURE.md](ARCHITECTURE.md)。
## 貢獻
學校頁面改版、parser 壞掉、想加新工具,都歡迎開 issue 或 PR,流程見 [CONTRIBUTING.md](CONTRIBUTING.md)。
提交前請確認沒有真實學號、姓名或頁面快照。
## 來源與授權
業務邏輯(頁面流程、欄位名稱、解析規則、排班原則)參考自同作者的 Agora-AI 專案(Python / FastAPI),
本專案以 TypeScript 重新實作為獨立 MCP server。
[MIT License](LICENSE) © 2026 small R (alaner652)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues