Metadata-Version: 2.5
Name: neopat-mcp
Version: 0.1.1
Summary: MCP server for the NeoPAT campus placement portal.
Author: neopat-mcp contributors
License-Expression: MIT
License-File: LICENSE
Keywords: mcp,model-context-protocol,neopat,placement
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Education
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: mcp>=2.0.0
Requires-Dist: pydantic>=2.0
Description-Content-Type: text/markdown

# neopat-mcp

[![PyPI](https://img.shields.io/pypi/v/neopat-mcp.svg)](https://pypi.org/project/neopat-mcp/)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![ci](https://github.com/Udhay-Adithya/neopat-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Udhay-Adithya/neopat-mcp/actions/workflows/ci.yml)

An [MCP](https://modelcontextprotocol.io) server for the NeoPAT campus placement
portal. It lets an AI assistant browse your drives, fill in each company's
application form from your profile, and file applications — with your explicit
confirmation every time.

## Features

- **Stays signed in** — signs in with your credentials and renews the session
  itself, or works from a token you paste.
- **Browse and search drives** — filter by eligibility, open deadlines, or
  whether you have already applied.
- **Answers filled from your profile** — marks, backlogs, registration number,
  contact details and location are taken from the portal's own record of you,
  and each one reports where it came from.
- **Asks you for the rest** — on clients that support elicitation, the server
  prompts you directly for answers it cannot infer.
- **Two-step applications** — nothing is submitted without a confirmation token
  issued by a preview you have seen.
- **Interview schedule** — upcoming rounds across every drive you opted into.
- **Typed responses** — every tool publishes an output schema.

## Requirements

- Python 3.11 or newer
- [uv](https://docs.astral.sh/uv/)
- An account on a NeoPAT placement portal

## Installation

### Claude Desktop — one click

Download `neopat-mcp.mcpb` from the [latest release][releases] and open it.
Claude Desktop installs it and asks for your credentials, which it keeps in your
operating system's keychain rather than in a file.

### Everything else

No clone or checkout needed — `uvx` fetches and runs it:

```bash
uvx --from neopat-mcp neopat-mcp
```

Add it to any client with one of the configurations [below](#usage).

<details>
<summary><b>From source</b></summary>

```bash
git clone https://github.com/Udhay-Adithya/neopat-mcp.git
cd neopat-mcp
uv sync
```

Then point your client at `.venv/bin/python -m neopat_mcp.server`.
</details>

[releases]: https://github.com/Udhay-Adithya/neopat-mcp/releases/latest

## Configuration

Credentials go in your client's `env` block (shown under [Usage](#usage)), or
in a `.env.local` file when running from a checkout.

There are two ways to authenticate. Pick one.

### Sign in (recommended)

```ini
NEOPAT_EMAIL="you@example.com"
NEOPAT_PASSWORD="your-portal-password"
```

The server signs in once and caches the session in `.neopat-session.json`
(gitignored, mode `0600`). Access tokens last 24 hours; a refresh token renews
them automatically, so your password is used once and the session keeps itself
alive after that.

Repeated rejections stop further attempts rather than retrying in a loop —
that is how accounts get locked. Fix the credentials and call `sign_in`.

### Paste a token

```ini
NEOPAT_TOKEN="paste-auth_token-here"
```

From DevTools → **Application** → **Local Storage** → `auth_token`. No password
is stored, but the token expires after 24 hours and cannot renew itself, so you
replace it by hand. It takes precedence when both are configured.

This bookmarklet copies the current token in one click:

```js
javascript:(()=>{navigator.clipboard.writeText(localStorage.getItem('auth_token'));alert('Token copied');})()
```

### All settings

| Variable | Default | Purpose |
|---|---|---|
| `NEOPAT_EMAIL` | — | Portal email, for automatic sign-in. |
| `NEOPAT_PASSWORD` | — | Portal password. Used at sign-in, never stored. |
| `NEOPAT_TOKEN` | — | A token copied from the browser, instead of the above. |
| `NEOPAT_BASE_URL` | `https://api.neopat.ai/api/v1` | API root. |
| `NEOPAT_MIN_INTERVAL_MS` | `700` | Minimum gap between requests. |
| `NEOPAT_CONFIRM_TTL_SECONDS` | `600` | Confirmation token lifetime. |

The session is cached under your user data directory — `~/.local/share/neopat-mcp`
on Linux, `~/Library/Application Support/neopat-mcp` on macOS, `%LOCALAPPDATA%`
on Windows — with permissions `0600`. Set `NEOPAT_HOME` to move it.

When running from a checkout, `.env.local` is gitignored. Keep it that way: it
holds a live credential.

## Usage

[![Add to Cursor](https://img.shields.io/badge/Add%20to-Cursor-0b0b0b?logo=cursor)](cursor://anysphere.cursor-deeplink/mcp/install?name=neopat&config=eyJjb21tYW5kIjogInV2eCIsICJhcmdzIjogWyItLWZyb20iLCAibmVvcGF0LW1jcCIsICJuZW9wYXQtbWNwIl0sICJlbnYiOiB7Ik5FT1BBVF9FTUFJTCI6ICJ5b3VAZXhhbXBsZS5jb20iLCAiTkVPUEFUX1BBU1NXT1JEIjogInlvdXItcGFzc3dvcmQifX0=)
[![Add to VS Code](https://img.shields.io/badge/Add%20to-VS%20Code-007ACC?logo=visualstudiocode)](vscode:mcp/install?%7B%22name%22%3A%20%22neopat%22%2C%20%22type%22%3A%20%22stdio%22%2C%20%22command%22%3A%20%22uvx%22%2C%20%22args%22%3A%20%5B%22--from%22%2C%20%22neopat-mcp%22%2C%20%22neopat-mcp%22%5D%2C%20%22env%22%3A%20%7B%22NEOPAT_EMAIL%22%3A%20%22you%40example.com%22%2C%20%22NEOPAT_PASSWORD%22%3A%20%22your-password%22%7D%7D)

GitHub does not make badge links clickable in every view; the configurations
below are the same thing written out. Replace the placeholder credentials with
your own.

Every client runs the same command, so nothing below needs a checkout.

<details open>
<summary><b>Claude Code</b></summary>

```bash
claude mcp add neopat \
  --env NEOPAT_EMAIL=you@example.com \
  --env NEOPAT_PASSWORD=your-password \
  -- uvx --from neopat-mcp neopat-mcp
```
</details>

<details>
<summary><b>Claude Desktop</b></summary>

Edit `claude_desktop_config.json` (macOS:
`~/Library/Application Support/Claude/`, Windows: `%APPDATA%\Claude\`):

```json
{
  "mcpServers": {
    "neopat": {
      "command": "uvx",
      "args": ["--from", "neopat-mcp", "neopat-mcp"],
      "env": {
        "NEOPAT_EMAIL": "you@example.com",
        "NEOPAT_PASSWORD": "your-password"
      }
    }
  }
}
```
</details>

<details>
<summary><b>Cursor</b></summary>

`.cursor/mcp.json`, or `~/.cursor/mcp.json` for all projects:

```json
{
  "mcpServers": {
    "neopat": {
      "command": "uvx",
      "args": ["--from", "neopat-mcp", "neopat-mcp"],
      "env": {
        "NEOPAT_EMAIL": "you@example.com",
        "NEOPAT_PASSWORD": "your-password"
      }
    }
  }
}
```
</details>

<details>
<summary><b>VS Code (Copilot agent mode)</b></summary>

`.vscode/mcp.json`:

```json
{
  "servers": {
    "neopat": {
      "type": "stdio",
      "command": "uvx",
      "args": ["--from", "neopat-mcp", "neopat-mcp"],
      "env": {
        "NEOPAT_EMAIL": "you@example.com",
        "NEOPAT_PASSWORD": "your-password"
      }
    }
  }
}
```
</details>

<details>
<summary><b>Windsurf</b></summary>

`~/.codeium/windsurf/mcp_config.json`:

```json
{
  "mcpServers": {
    "neopat": {
      "command": "uvx",
      "args": ["--from", "neopat-mcp", "neopat-mcp"],
      "env": {
        "NEOPAT_EMAIL": "you@example.com",
        "NEOPAT_PASSWORD": "your-password"
      }
    }
  }
}
```
</details>

<details>
<summary><b>Zed</b></summary>

```json
{
  "context_servers": {
    "neopat": {
      "command": {
        "path": "uvx",
        "args": ["--from", "neopat-mcp", "neopat-mcp"],
        "env": {
          "NEOPAT_EMAIL": "you@example.com",
          "NEOPAT_PASSWORD": "your-password"
        }
      }
    }
  }
}
```

</details>

<details>
<summary><b>Cline / Roo</b></summary>

`cline_mcp_settings.json`:

```json
{
  "mcpServers": {
    "neopat": {
      "command": "uvx",
      "args": ["--from", "neopat-mcp", "neopat-mcp"],
      "env": {
        "NEOPAT_EMAIL": "you@example.com",
        "NEOPAT_PASSWORD": "your-password"
      },
      "disabled": false
    }
  }
}
```
</details>

<details>
<summary><b>Any other MCP client</b></summary>

A standard stdio server:

```bash
uvx --from neopat-mcp neopat-mcp
```

with `NEOPAT_EMAIL` and `NEOPAT_PASSWORD` in the environment.
</details>

Ask your assistant to run `health` to confirm the connection.

## Tools

### Reading

| Tool | Description |
|---|---|
| `health` | Token validity, expiry, and the portal's drive counts. |
| `auth_status` | How the server is authenticating and whether it can renew itself. |
| `sign_in` | Sign in with the configured email and password. |
| `sign_out` | Discard the cached session. |
| `token_status` | Time left on the token, without calling the portal. |
| `list_drives` | List drives. Filter by `eligible`, `only_open`, `exclude_applied`, `search`. |
| `search_drives` | The portal's own search — faster, and matches more fields. |
| `get_drive` | One drive in full, including its application questions. |
| `check_eligibility` | The portal's eligibility verdict for a drive, and why. |
| `my_profile` | Your profile, with marks and other common answers promoted. |
| `profile_completeness` | Which profile sections the portal considers finished. |
| `saved_resumes` | Resumes saved on the portal. |
| `code_profiles` | LeetCode/HackerRank status and whether your college requires them. |
| `my_schedule` | Interview rounds on drives you opted into. |
| `portal_features` | Which features your college has enabled. |
| `filter_options` | Drive types, domains and company categories in use. |
| `notifications` | Recent portal notifications. |

### Writing

| Tool | Description |
|---|---|
| `preview_application` | Build and validate the application. Submits nothing. |
| `apply_to_drive` | Submit the previewed application. |
| `preview_withdrawal` | Build a withdrawal. Submits nothing. |
| `withdraw_application` | Submit the withdrawal. |

### Prompts

| Prompt | Description |
|---|---|
| `find_drives_for_me` | Shortlist open drives that fit you, with reasons. |
| `apply_to` | Walk through applying to a named company. |
| `why_not_eligible` | Explain which criteria a drive rules you out on. |
| `readiness_check` | Check nothing blocks applying before a deadline rush. |
| `whats_next` | Upcoming interviews and deadlines about to pass. |

### Resources

| URI | Description |
|---|---|
| `neopat://profile` | Your profile, for grounding answers. |
| `neopat://drives/open` | Drives still open and not yet applied to. |

## How applying works

Applying takes two calls, and the second one cannot invent anything.

```
get_drive  ->  preview_application  ->  [you approve]  ->  apply_to_drive
```

`preview_application` reads the drive's questions, fills in what your profile
answers, asks you for the rest, and validates everything. It returns a
single-use confirmation token bound to that drive and to a hash of the exact
request body.

`apply_to_drive` takes no payload. It can only replay a body that was already
previewed. An assistant cannot apply as a side effect of browsing, cannot apply
to a drive other than the one you were shown, and cannot alter an answer between
your approval and the request.

No token is issued at all when something is wrong — you are ineligible, the
deadline has passed, a mandatory question is unanswered, an answer is not one of
the permitted options, or the portal already records you as applied.

Answers are keyed by question **id**, never by question text:

```json
{
  "drive_id": "...",
  "answers": { "8af2166d-8202-4932-b5a7-f3a01c80d7e9": "Development" }
}
```

Free-text questions about marks, backlogs, contact details and location are
filled from your profile automatically and reported in `auto_answered`.
Multiple-choice questions are never answered for you — picking an option is a
preference, not a lookup.

## Development

```bash
uv sync --all-groups
.venv/bin/python -m pytest
```

Tests run against a stubbed portal and never contact the real one.

```
src/neopat_mcp/
├── schemas.py       Typed models for every response
├── parsing.py       Portal payloads -> schemas
├── applications.py  Question answering, validation, write payloads
├── client.py        HTTP: auth, rate limiting, retries, pagination
├── confirm.py       Single-use confirmation tokens
├── config.py        Settings and the hot-reloaded token
└── server.py        MCP tools, prompts and resources
```

[`docs/api-notes.md`](docs/api-notes.md) documents the portal's API behaviour,
including several endpoints that fail in misleading ways.

## Disclaimer

Unofficial, and not affiliated with or endorsed by NeoPAT. It automates actions
you can already perform yourself, on your own account, with your own
credentials. Opting into a drive files a real application with a real company —
check your institution's placement policy before automating anything, and read
every preview before confirming it.

## License

[MIT](LICENSE)
