metu-sais-mcp
# METU SAIS API & Multi-Tenant MCP Server
A modern **Model Context Protocol (MCP)** server and **FastAPI** service for Middle East Technical University (METU / ODTÜ) Student Affairs Information System (SAIS / `student.metu.edu.tr`).
Connects your AI assistants (such as **OpenCode**, **Claude Desktop**, and **Cursor**) directly to METU SAIS with real authentication, structured data parsing, and **stateless multi-tenant remote streaming**.
---
## Features
- **Multi-Tenant Architecture**: Supports both local single-user runs and remote multi-tenant serving over Server-Sent Events (SSE) / HTTP. Remote users pass their own credentials via headers or query parameters without storing any passwords on the server.
- **SSO Authentication**: Handles the real METU SSO login flow (`/sso/backend/request/user/signin`) and session token validation.
- **Proxy Scraper & HTML Parser**: Automatically establishes authenticated proxy sessions (`/proxy/Student_Information/`) and parses student records into structured JSON.
- **MCP Server (MCP SDK)**: Exposes typed tools over standard I/O (`stdio`), Server-Sent Events (`sse`), or Streamable HTTP.
- **FastAPI REST Service**: Alternative HTTP REST backend for API integrations.
- **Privacy & Security**: Zero credential logging or exposure.
---
## Available MCP Tools
| Tool | Description |
|---|---|
| `get_student_info` | Fetches student ID, name, department, semester, GPA/CGPA, academic status, and advisor. |
| `get_schedule` | Fetches weekly class schedule (days, hours, course codes, course names, classrooms, instructors). |
| `get_transcript` | Fetches academic transcript across all semesters with courses, credits, grades, and semester GPA statistics. |
| `get_announcements` | Fetches active portal announcements. |
| `create_connection` | Creates an in-memory connection record. |
| `list_connections` | Lists all saved connection records. |
---
## Installation & Setup
### 1. Prerequisites
- Python >= 3.11
- [uv](https://github.com/astral-sh/uv) (recommended) or `pip`
### 2. Install Dependencies
```bash
# Using uv (recommended)
uv sync
# Or using pip
pip install -e .
```
### 3. Local Configuration (Optional for Single-User Runs)
For local single-user runs, copy `.env.example` to `.env`:
```bash
cp .env.example .env
```
Edit `.env`:
```ini
SAIS_USERNAME=your_metu_username
SAIS_PASSWORD=your_metu_password
LOCALE=tr
PORT=8300
```
> [!NOTE]
> `.env` is listed in `.gitignore` and will never be committed. Remote multi-tenant users do **not** need a `.env` file on the server because they supply their credentials per-session via request headers or URL parameters.
---
## Running Multi-Tenant Remote MCP (Online)
### Start the Server with SSE Transport
```bash
# Direct MCP SSE server:
uv run metu-sais-mcp --transport sse --host 0.0.0.0 --port 8300
# Or via FastAPI (serves both REST API and MCP SSE at /sse and /messages/):
uv run uvicorn api.main:app --host 0.0.0.0 --port 8300
```
### Connecting Remote Clients (Claude Desktop / Cursor / OpenCode)
#### Option 1: Custom Headers (Recommended)
```json
{
"mcpServers": {
"metu-sais": {
"url": "https://mcp.yourdomain.com/sse",
"headers": {
"X-SAIS-Username": "e123456",
"X-SAIS-Password": "student_secret_password"
}
}
}
}
```
#### Option 2: Basic Authentication Header
```json
{
"mcpServers": {
"metu-sais": {
"url": "https://mcp.yourdomain.com/sse",
"headers": {
"Authorization": "Basic <base64(username:password)>"
}
}
}
}
```
#### Option 3: URL Query Parameters
```json
{
"mcpServers": {
"metu-sais": {
"url": "https://mcp.yourdomain.com/sse?u=e123456&p=student_secret_password"
}
}
}
```
---
## Local Usage with OpenCode & Claude Desktop
### Project-Level Usage with OpenCode
When starting OpenCode inside this repository:
```bash
opencode
```
OpenCode will automatically connect to the `metu-sais` MCP server defined in [`opencode.json`](./opencode.json).
### Local Stdio Standalone Run
```bash
uv run metu-sais-mcp
```
---
## REST API
To start the FastAPI server:
```bash
uv run uvicorn api.main:app --port 8300 --reload
```
Interactive API documentation will be available at:
- **Swagger UI**: [http://localhost:8300/docs](http://localhost:8300/docs)
- **Health check**: [http://localhost:8300/healthz](http://localhost:8300/healthz)
### REST Endpoints
- `GET /api/sais/info` — Student information
- `GET /api/sais/schedule` — Class schedule
- `GET /api/sais/transcript` — Transcript history
- `GET /api/sais/announcements` — Portal announcements
- `GET|POST|PUT|DELETE /api/connections` — Connection management CRUD
*(In multi-tenant mode, pass `X-SAIS-Username` and `X-SAIS-Password` headers or `?u=...&p=...` query parameters to any REST endpoint).*
---
## Testing
Run the automated test suite with pytest:
```bash
uv run pytest -v
```
The test suite includes:
- Unit tests for HTML parsers (`parse_student_info`, `parse_schedule`, `parse_transcript`, `parse_announcements`).
- MCP tool registration and tool calling tests.
- Multi-tenant credential extraction and session isolation tests.
- FastAPI REST endpoint tests.
- Live integration tests against METU SAIS when credentials are configured.
---
## Project Structure
```text
├── sais/
│ ├── auth.py # Multi-tenant credential resolver and session store
│ ├── connector.py # Core SAIS authentication, proxy scraping, and HTML parsers
│ └── connections.py # Shared in-memory connection store
├── sais_mcp/
│ └── server.py # MCP multi-tenant server (stdio, sse, streamable-http)
├── api/
│ └── main.py # FastAPI REST API + mounted MCP SSE app
├── tests/
│ ├── test_connector.py # Scraper and HTML parser tests
│ ├── test_mcp_server.py# MCP server and multi-tenant SSE isolation tests
│ └── test_api.py # FastAPI tests
├── .env.example # Environment variable template
├── opencode.json # OpenCode MCP configuration
├── pyproject.toml # Package dependencies and CLI entrypoints
└── README.md # Project documentation
```
---
## License
MIT License.
TDQS
Scored across 6 tools
Each tool targets a clearly distinct resource or action: student info, schedule, transcript, announcements, and connection records. There is no meaningful overlap that would cause an agent to select the wrong tool.
Tool names follow a consistent snake_case verb_noun pattern: get_student_info, get_schedule, get_transcript, get_announcements, create_connection, list_connections. The only minor variation is singular vs plural nouns, but the pattern is predictable.
Six tools is a well-scoped set for a student portal MCP server. It covers the core read operations and adds connection management without bloating the surface.
The student-facing read operations are solid, but connection management is incomplete: create_connection and list_connections have no corresponding update or delete operation, creating a lifecycle dead end. Minor gaps like course-level detail or exam results are less critical but still absent.