Metadata-Version: 2.4
Name: ninova-mcp
Version: 0.1.0
Summary: Credential-based MCP server for ITU Ninova: read courses, announcements, files, assignments, grades, attendance, and deadlines from Claude, Cursor, Codex, and other MCP clients.
Author: Hikmet Gultekin
License-Expression: MIT
Project-URL: Homepage, https://github.com/hikmedit/ninova-mcp
Project-URL: Repository, https://github.com/hikmedit/ninova-mcp
Project-URL: Issues, https://github.com/hikmedit/ninova-mcp/issues
Keywords: mcp,model-context-protocol,ninova,itu,claude,cursor
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Education
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: beautifulsoup4>=4.12.0
Requires-Dist: lxml>=5.0.0
Requires-Dist: mcp<2,>=1.27.0
Requires-Dist: requests>=2.31.0
Requires-Dist: starlette>=0.37.2
Requires-Dist: uvicorn>=0.30.0
Provides-Extra: playwright
Requires-Dist: playwright>=1.40; extra == "playwright"
Dynamic: license-file

# Ninova MCP

Ninova MCP is a credential-based Model Context Protocol server for ITU Ninova. It lets MCP-compatible AI clients such as Claude, OpenClaw, Cursor, and Codex read your own Ninova courses, announcements, files, assignments, grades, message boards, attendance, and deadlines.

It logs in with `NINOVA_USERNAME` and `NINOVA_PASSWORD`, opens its own temporary Ninova session, and exposes tools for:

- reading the dashboard
- listing courses
- resolving a course by code, title, path, or URL
- listing a course's direct Ninova sections/routes
- getting a course's announcements
- getting a course's class files
- getting a course's lesson files
- getting a course's assignments with detail pages
- getting a course's class information
- getting a course's grades
- getting a course's message board topics and threads
- getting a course's attendance
- getting a course's remote learning page
- building a combined course overview
- getting dashboard announcements
- getting dashboard assignments with detail pages
- reading any Ninova page
- crawling a course tree
- downloading files
- snapshotting pages
- comparing snapshots to track changes
- syncing all courses into a local tracking state
- listing detected updates
- listing upcoming deadlines

It does not read Chrome cookies or reuse browser profiles.

## Quick Start

Install with [pipx](https://pipx.pypa.io) (recommended):

```bash
pipx install ninova-mcp
```

Or with [uv](https://docs.astral.sh/uv/):

```bash
uv tool install ninova-mcp
```

Or with plain pip:

```bash
pip install ninova-mcp
```

All three give you a `ninova-mcp` command on your PATH. You will need Python 3.11+.

Then add the server to your MCP client. The minimal config is:

```json
{
  "mcpServers": {
    "ninova": {
      "command": "ninova-mcp",
      "env": {
        "NINOVA_USERNAME": "your_itu_username",
        "NINOVA_PASSWORD": "your_itu_password"
      }
    }
  }
}
```

See the full guide:

- [Installation guide](docs/installation.md)
- [Security notes](docs/security.md)
- [Example configs](examples/)

Supported priority clients:

- Claude Desktop / Claude Code
- OpenClaw
- Cursor
- Codex CLI

## Security Notice

This project uses credentials that you provide locally. Never commit `.env`, downloads, course files, submissions, screenshots, or tracking state. Prefer local stdio MCP. If you deploy the remote HTTP server for Claude.ai custom connectors, use HTTPS and a long random private MCP path.

## Request Model

Ninova appears to be classic ASP.NET WebForms. In practice this server now prefers request-level access to Ninova's own routes such as:

- `/Sinif/<id>.<id>/Notlar`
- `/Sinif/<id>.<id>/MesajPanosu`
- `/Sinif/<id>.<id>/Yoklama`
- `/Sinif/<id>.<id>/UzaktanEgitim`

For interactive actions Ninova uses same-page form `POST` requests with `__VIEWSTATE`, `__EVENTTARGET`, and related WebForms fields rather than a clean public JSON API.

## Requirements

- Python 3.11+
- `requests`
- `beautifulsoup4`
- `lxml`
- `mcp`
- `uvicorn`
- Optional: `playwright` for login fallback when the form flow changes

## Configuration

Only two variables are required:

```dotenv
NINOVA_USERNAME=your_username
NINOVA_PASSWORD=your_password
```

The server reads them from the process environment. If a `.env` file exists in the current working directory (or any parent), `NINOVA_*` variables are loaded from it without overriding variables already set in the environment.

### Advanced (optional)

These are not needed for normal local use. Set them only if you need to override defaults or run the remote HTTP transport.

Local overrides:

```bash
export NINOVA_BASE_URL="https://ninova.itu.edu.tr"   # default
export NINOVA_STATE_DIR="/absolute/path/to/.ninova_state"
export NINOVA_DISABLE_PLAYWRIGHT_FALLBACK="1"
export NINOVA_ENV_FILE="/absolute/path/to/.env"
```

Remote HTTP server (only for self-hosted Claude.ai custom connectors):

```bash
export NINOVA_REMOTE_HOST="0.0.0.0"
export NINOVA_REMOTE_PORT="8000"
export NINOVA_REMOTE_MCP_PATH="/mcp-choose-a-long-random-secret"
export NINOVA_PUBLIC_BASE_URL="https://your-public-domain.example.com"
export NINOVA_ALLOWED_HOSTS="your-public-domain.example.com"
export NINOVA_ALLOWED_ORIGINS="https://claude.ai,https://claude.com"
```

## Run

After installing the package:

```bash
ninova-mcp
```

This starts the stdio MCP server. It is normal for it to wait silently because your MCP client talks to it over stdin/stdout.

To run from a source checkout without installing:

```bash
PYTHONPATH=src python3 -m ninova_mcp
```

## Run Remote HTTP Server

To expose the server for Claude Cowork / Claude.ai, run the remote Streamable HTTP transport:

```bash
ninova-mcp-remote
```

By default this starts an HTTP server on `0.0.0.0:8000`.

Useful endpoints:

- `GET /healthz`: health check
- `POST/GET <NINOVA_REMOTE_MCP_PATH>`: MCP endpoint for remote connectors

Example:

```bash
export NINOVA_REMOTE_MCP_PATH="/mcp-6f8f94c5b6bb4b8f8f6d2f8b20f28e9f"
export NINOVA_PUBLIC_BASE_URL="https://ninova-mcp.example.com"
export NINOVA_ALLOWED_HOSTS="ninova-mcp.example.com"
export NINOVA_ALLOWED_ORIGINS="https://claude.ai,https://claude.com"
ninova-mcp-remote
```

Then your connector URL is:

```text
https://ninova-mcp.example.com/mcp-6f8f94c5b6bb4b8f8f6d2f8b20f28e9f
```

This server currently uses an authless remote MCP setup, so for personal use you should choose a long random `NINOVA_REMOTE_MCP_PATH` and keep the full URL private. OAuth is not implemented yet.

## Docker

Build and run:

```bash
docker build -t ninova-mcp .
docker run --rm -p 8000:8000 \
  -e NINOVA_USERNAME="your_username" \
  -e NINOVA_PASSWORD="your_password" \
  -e NINOVA_REMOTE_MCP_PATH="/mcp-choose-a-long-random-secret" \
  -e NINOVA_PUBLIC_BASE_URL="https://your-public-domain.example.com" \
  -e NINOVA_ALLOWED_HOSTS="your-public-domain.example.com" \
  ninova-mcp
```

## Claude Cowork / Claude.ai

Anthropic's remote MCP support requires a publicly reachable HTTP server; local stdio servers are not available in Cowork.

To use this connector in Claude Cowork:

1. Deploy this repo to a public host.
2. Set `NINOVA_USERNAME`, `NINOVA_PASSWORD`, `NINOVA_REMOTE_MCP_PATH`, `NINOVA_PUBLIC_BASE_URL`, and `NINOVA_ALLOWED_HOSTS`.
3. Confirm `https://your-domain/healthz` returns `200`.
4. In Claude, add a custom connector and paste the full remote MCP URL:
   `https://your-domain<your-random-mcp-path>`
5. Enable the tools you want to expose.

Anthropic references:

- Remote connectors overview: [Anthropic Help](https://support.anthropic.com/en/articles/11175166-about-custom-integrations-using-remote-mcp)
- Remote MCP transport support: [Anthropic Help](https://support.anthropic.com/en/articles/11503834-building-custom-connectors-via-remote-mcp-servers)
- Streamable HTTP requirement: [Anthropic MCP connector docs](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector)

## Example MCP Config

After `pipx install ninova-mcp`, the minimal local stdio entry is:

```json
{
  "mcpServers": {
    "ninova": {
      "command": "ninova-mcp",
      "env": {
        "NINOVA_USERNAME": "your_username",
        "NINOVA_PASSWORD": "your_password"
      }
    }
  }
}
```

## Exposed Tools

- `auth_status`: Check whether credentials exist and whether a fresh Ninova session can be created.
- `refresh_session`: Force a new login with the configured credentials.
- `get_dashboard`: Read `/Kampus1` and return dashboard sections, recent items, and course list.
- `list_courses`: Return the discovered courses from the dashboard.
- `get_courses`: Alias for `list_courses`.
- `get_course_announcements`: Return announcements for a given course. Accepts course code, title, path, or full URL.
- `get_course_class_files`: Return structured entries from `Sınıf Dosyaları`, optionally recursively.
- `get_course_lesson_files`: Return structured entries from `Ders Dosyaları`, optionally recursively.
- `get_course_assignments`: Return assignments for a course and fetch each assignment's detail page.
- `get_course_info`: Return structured data from `Sınıf Bilgileri`.
- `get_course_sections`: Return the course routes exposed on the course home page.
- `get_course_grades`: Return structured data from `Notlar`.
- `get_course_message_board`: Return structured topics from `Mesaj Panosu`, optionally with thread details.
- `get_course_attendance`: Return structured data from `Yoklama`.
- `get_course_remote_learning`: Return structured data from `Uzaktan Eğitim`.
- `get_course_overview`: Return a combined live or tracked overview of a course.
- `get_dashboard_announcements`: Return the announcements listed in `/Kampus?1/Duyurular`.
- `get_dashboard_assignments`: Return the assignments listed in `/Kampus?1/Odevler` and fetch each detail page.
- `sync_all_courses`: Save a local snapshot for every visible course and detect changes since the previous sync.
- `get_updates`: Return recently detected changes from the local tracking state.
- `get_upcoming_deadlines`: Return upcoming assignment deadlines from the local tracking state.
- `read_page`: Read and structure any Ninova HTML page.
- `crawl_course`: Inventory internal pages and downloadable resources inside a course.
- `download_resource`: Download a file or page response to disk.
- `snapshot_page`: Save a structured snapshot of a page for later tracking.
- `diff_snapshot`: Compare the current page against a stored snapshot.

## Notes

- Sessions are created by normal login flow and held in process memory.
- The server automatically retries once with a fresh login if a session expires.
- Downloaded files are stored inside the requested output directory. The default is `./downloads`.
- Snapshots are stored under `./.ninova_state/snapshots` unless `NINOVA_STATE_DIR` is set.
- Course tracking state is stored under `./.ninova_state/tracking-state.json` unless `NINOVA_STATE_DIR` is set.
- The remote HTTP entrypoint is implemented with the official Python MCP SDK's Streamable HTTP server support.

## Tests

```bash
PYTHONPATH=src python3 -m unittest discover -s tests -v
```
