Job Application MCP
by Mzaq1559
README.md



# Job Application MCP
A remote [MCP](https://modelcontextprotocol.io) server that turns an AI assistant into a structured job-application workspace. It manages your profile, resumes, job analysis, application tracking, and interview history, with **Claude Web** as the primary documented client.
The project is designed as a serious, extensible platform rather than a one-off personal script. The core repository is source-available for learning and contribution, while **commercial use, hosted use, and use inside paid products require a separate commercial license**. See [LICENSE](LICENSE).
It does **not** scrape or automate any job platform — job descriptions are supplied by the user (pasted or typed), and the final submission is always done by the user, manually.
> **v1.0.0:** the core server is implemented and deployed to Azure Container Apps with 21 MCP tools, Streamable HTTP, OAuth 2.1 resource-server authentication, Docker, GitHub Actions CI/CD, and Auth0 integration. The public deployment is configured for Claude Web custom connectors.
## Table of contents
- [Why no scraping or automation](#why-no-scraping-or-automation)
- [Features](#features)
- [Architecture](#architecture)
- [MCP tools](#mcp-tools)
- [Tech stack](#tech-stack)
- [Project structure](#project-structure)
- [Getting started](#getting-started)
- [Environment variables](#environment-variables)
- [Database](#database)
- [Running tests](#running-tests)
- [Docker](#docker)
- [Deployment](#deployment)
- [Connecting to Claude Web](#connecting-to-claude-web)
- [Example prompts](#example-prompts)
- [Security](#security)
- [Limitations](#limitations)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)
## Why no scraping or automation
Platforms like LinkedIn explicitly prohibit automated scraping and bot-driven activity in their Terms of Service, and have pursued account suspensions and legal action over it. This project deliberately stays on the right side of that line: it only ever works from information *you* provide directly (pasted job descriptions, uploaded resumes), and it never logs into, scrapes, or automates form submission on a third-party site. You stay in control of every application you actually send.
## Features
- **Profile management** — one structured profile (education, skills, projects, experience, research interests, certifications, achievements) that every other tool reads from. Nothing about you is hard-coded into the source.
- **Resume/CV management** — upload multiple resumes (PDF, DOCX, TXT, MD), keep multiple versions of each, and get a transparent, keyword-based recommendation for which resume fits a given job description.
- **Job tracking** — save jobs with full description/requirements text, automatic duplicate detection (by URL, then by company+title), and a status workflow (`saved → analyzing → ready_to_apply → applied → screening → interview → offer`, with `rejected`/`withdrawn` reachable from most states).
- **Transparent job analysis** — literal keyword comparison between a job's text and your stored skills. No fabricated "hireability score" — matches and gaps are reported plainly so you can judge fit yourself.
- **Application tracking** — persistent records per job, full event history, and duplicate-application prevention. Nothing is ever marked `applied` except in response to *you* telling Claude you submitted it — the tools never submit anything themselves.
- **Interview tracking** — scheduled interviews, prep/post-interview notes, linked to the application they belong to.
## Architecture
The system is split into transport/authentication, MCP tools, business services, persistence, and external identity infrastructure so new clients and features can be added without rewriting the core domain logic.
```mermaid
flowchart TB
CLIENT["AI Client<br/>Claude Web / MCP Client"]
CLIENT -->|"Streamable HTTP + OAuth 2.1"| SERVER["Remote MCP Server<br/>Starlette + MCP SDK"]
SERVER --> AUTH["OAuth 2.1 Resource Server<br/>JWT/JWKS validation"]
AUTH --> AUTH0["Auth0"]
SERVER --> TOOLS["MCP Tool Layer<br/>21 tools"]
TOOLS --> SERVICES["Application Services"]
SERVICES --> PROFILE["Profile Service"]
SERVICES --> RESUME["Resume / CV Service"]
SERVICES --> JOB["Job Service<br/>duplicate detection + analysis"]
SERVICES --> APP["Application Service<br/>pipeline + event history"]
SERVICES --> INTERVIEW["Interview Service"]
PROFILE --> DB["SQLAlchemy Async"]
RESUME --> DB
JOB --> DB
APP --> DB
INTERVIEW --> DB
DB --> SQLITE["SQLite<br/>development"]
DB --> POSTGRES["PostgreSQL<br/>production"]
SERVER --> FILES["Resume File Storage"]
```
### Design principles
- **AI-client agnostic:** MCP is the interface; Claude Web is the first documented client, not a hard dependency of the domain layer.
- **User-controlled applications:** the server prepares and tracks applications but never submits them to third-party job platforms.
- **Transparent analysis:** current job/resume matching is deliberately inspectable keyword-based logic rather than an opaque hiring score.
- **Authentication at the edge:** OAuth 2.1 protects the remote MCP endpoint while application services remain focused on business logic.
- **Extensible domain services:** contributors can add deeper AI analysis, cover-letter generation, integrations, migrations, durable storage, and other capabilities without replacing the existing architecture.
## MCP tools
| Domain | Tools |
|---|---|
| Profile | `profile_get`, `profile_update`, `profile_summary` |
| Resume | `resume_list`, `resume_get`, `resume_upload`, `resume_update`, `resume_delete`, `resume_select_for_job` |
| Job | `job_create`, `job_get`, `job_list`, `job_analyze`, `job_update_status` |
| Application | `application_create`, `application_get`, `application_list`, `application_update_status`, `application_history`, `application_delete` |
| Interview | `interview_create`, `interview_list`, `interview_update`, `interview_notes` |
Every tool's description is explicit about what it does and doesn't do. For example: `job_create` states it never fetches or scrapes a URL itself — the job description must come from you; `application_update_status` states that moving an application to `applied` only records what you report, it never submits anything on your behalf; destructive tools (`resume_delete`, `application_delete`) require an explicit `confirm=true` flag.
## Tech stack
- **Python 3.12+**, official [`mcp`](https://github.com/modelcontextprotocol/python-sdk) SDK v2.x (`MCPServer`, Streamable HTTP transport)
- **Starlette** for the ASGI app, **Pydantic** for validation, **SQLAlchemy** (async) + **Alembic** for the database
- **SQLite** in development, **PostgreSQL** in production — swap by changing `DATABASE_URL` only, no code changes
- **pypdf** / **python-docx** for resume text extraction
- **Docker** for packaging; **pytest** + **ruff** for tests and linting
## Project structure
```text
job-application-mcp/
├── src/job_application_mcp/
│ ├── server.py # ASGI app: health/ready routes, OAuth auth, uvicorn entrypoint
│ ├── mcp_app.py # shared MCPServer instance
│ ├── config/settings.py # env-driven configuration
│ ├── database/ # SQLAlchemy engine/session + models
│ ├── models/ # Pydantic schemas at the MCP tool boundary
│ ├── services/ # business logic (profile, resume, job, application, interview)
│ ├── mcp/tools/ # MCP tool definitions, one module per domain
│ └── utils/ # document text extraction, upload validation
├── tests/unit/ # service-layer unit tests
├── Dockerfile, docker-compose.yml
├── pyproject.toml
└── .env.example
```
## Getting started
Requires Python 3.12+.
```bash
git clone https://github.com/Mzaq1559/job-application-mcp.git
cd job-application-mcp
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env
# edit .env: at minimum, set MCP_AUTH_TOKENS to a random secret
python -c "import secrets; print(secrets.token_urlsafe(32))"
# For local development, configure the OAuth variables in .env
python -m job_application_mcp.server
```
The server starts on `http://0.0.0.0:8000` by default. Check it's alive:
```bash
curl http://localhost:8000/health
# {"status": "ok"}
```
## Environment variables
See [`.env.example`](.env.example) for the full, current list. The essentials for the deployed OAuth configuration are:
| Variable | Purpose | Default |
|---|---|---|
| `DATABASE_URL` | SQLAlchemy async connection string | `sqlite+aiosqlite:///./data/app.db` |
| `OAUTH_ISSUER_URL` | Auth0 issuer URL | *(required)* |
| `OAUTH_AUDIENCE` | Auth0 API Identifier / JWT audience | *(required)* |
| `OAUTH_RESOURCE_URL` | Exact public `/mcp` URL used for RFC 9728 metadata | *(required)* |
| `OAUTH_JWKS_URL` | Optional Auth0 JWKS URL | derived from issuer |
| `OAUTH_REQUIRED_SCOPE` | Scope required on MCP access tokens | `mcp:access` |
| `MCP_HOST` / `MCP_PORT` | Bind address | `0.0.0.0` / `8000` |
| `UPLOAD_DIR` | Where resume files are stored on disk | `./uploads` |
| `AI_PROVIDER` / `AI_API_KEY` / `AI_MODEL` | Reserved for the AI-assisted analysis layer (not yet implemented) | — |
## Database
Development uses SQLite and creates tables automatically on server startup — nothing to run manually. Production should point `DATABASE_URL` at PostgreSQL; Alembic migrations for that path are on the [roadmap](#roadmap) but not yet implemented (the auto-create-on-startup behavior works against Postgres too in the meantime).
## Running tests
```bash
pip install -e ".[dev]"
pytest -q # 16 tests, run against an isolated per-test SQLite database
ruff check . # lint
```
## Docker
```bash
docker compose up --build
```
This starts the app plus a PostgreSQL container. The compose file is intended for local development; configure the OAuth variables in your local `.env` when testing authenticated MCP requests.
To build and run standalone (SQLite, no Postgres container):
```bash
docker build -t job-application-mcp .
docker run -p 8000:8000 job-application-mcp
```
## Deployment
Any platform that runs a long-lived container works — the server just needs a public HTTPS URL and a couple of environment variables. Options that fit a personal, single-user deployment like this one:
- **Render** or **Railway** — connect the GitHub repo, they build the `Dockerfile` automatically, set env vars in their dashboard, get a public HTTPS URL.
- **Azure Container Apps** — a good fit if you have Azure for Students credit; deploy the same `Dockerfile` via `az containerapp up` or the portal's "deploy from GitHub" flow.
- **Fly.io** — `fly launch` against this repo's `Dockerfile`.
For the current Azure deployment, configure the OAuth environment variables shown above. Do not add an Auth0 client secret to the repository or GitHub Actions. Azure stores deployment configuration separately from source control.
The production deployment currently runs on Azure Container Apps. GitHub Actions builds the Docker image, pushes it to Azure Container Registry, updates the Container App to the exact Git SHA image, and verifies `/health` after deployment.
## Connecting to Claude Web
Once deployed, add it in Claude Web as a **custom remote MCP connector**, pointing at the public `/mcp` endpoint:
```text
https://<your-deployed-domain>/mcp
```
Authentication is OAuth 2.1 through Auth0; Claude signs in through the OAuth authorization flow rather than receiving a shared bearer secret. The Auth0 API requires the `mcp:access` scope. The Claude Web application uses the callback `https://claude.ai/api/mcp/auth_callback`. See [`docs/claude-web.md`](docs/claude-web.md) for the setup details.
## Example prompts
Once connected, things like:
- *"Here's a job description I found — [paste text]. Save it and tell me how well I match it."*
- *"Which of my resumes fits this job best?"*
- *"I just submitted the ML intern application at Example Co — mark it as applied."*
- *"What applications do I have in the interview stage?"*
## Security
- The `/mcp` endpoint is protected by OAuth 2.1 bearer access tokens issued by Auth0. JWT signatures, issuer, audience, expiry, and required scope are verified before MCP requests are served.
- No secrets, `.env` files, or database files are committed — see `.gitignore` / `.dockerignore`.
- File uploads are validated by extension and size, and filenames are checked for path traversal before being written to disk.
- The Docker image runs as a non-root user.
- This is a single-user server by design — see [Limitations](#limitations) for what that means for auth.
## Limitations
- **Personal deployment.** Authentication is delegated to one Auth0 tenant and the MCP tools operate on the database attached to this deployment. Add application-level authorization rules before turning this into a multi-user service.
- **No AI-assisted analysis yet.** Resume selection and job analysis are transparent keyword-matching heuristics, not LLM-based reasoning — by design, so there's nothing to audit for fabrication yet. A proper AI-assisted layer (with explicit no-fabrication prompt rules) is on the roadmap.
- **No migrations yet.** Schema changes currently mean dropping and recreating tables in development; Alembic migrations for safe production upgrades aren't wired up yet.
- **Never submits anything.** By design, not a bug — this project prepares and tracks applications; you always click submit yourself.
## Roadmap
- [x] Repo scaffold, license, `.gitignore`, `pyproject.toml`
- [x] Settings (env-driven config)
- [x] Database models (Profile, Resume, ResumeVersion, Job, Application, ApplicationDocument, ScreeningQuestion, Interview, InterviewNote, ApplicationEvent)
- [x] Service layer (profile, resume, job, application, interview)
- [x] MCP server + tools, verified end-to-end over HTTP (health check, bearer auth, `initialize`, `tools/list`)
- [x] Unit tests (16 passing) + clean `ruff` lint
- [x] Docker + docker-compose, verified with a production-equivalent install and boot
- [x] CI/CD (GitHub Actions for lint + tests + Docker build/push + Azure deployment)
- [x] Public Azure Container Apps deployment
- [ ] Alembic migrations
- [ ] AI provider abstraction + prompts (no-fabrication rules) for deeper job analysis and cover-letter generation
- [ ] Integration tests against the running HTTP server
- [x] Azure Container Apps deployment documentation
- [x] Public Azure deployment
- [x] OAuth 2.1 resource-server authentication for Claude Web
- [ ] Durable resume-file storage
## Contributing
Community development happens on the **`community` branch**. Contributors should fork the repository, create a focused branch from `community`, and open pull requests back into `community`. The `main` branch is reserved for reviewed, release-ready changes.
Before contributing, read:
- [CONTRIBUTING.md](CONTRIBUTING.md) — development workflow, coding guidelines, and PR checklist
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) — community standards
- [SECURITY.md](SECURITY.md) — vulnerability reporting
Contributions are especially useful around the AI-assisted analysis layer, Alembic migrations, integrations, durable storage, testing, observability, authentication/authorization, multi-user support, and deployment.
If you want to build on this project commercially, host it for customers, or include it in a paid product, contact the maintainer for commercial licensing.
This started as a personal project, but issues and PRs are welcome. Contributions are especially useful around the AI-assisted analysis layer, Alembic migrations, integrations, durable storage, testing, observability, and additional ATS-adjacent tooling that does not involve scraping or unauthorized automation.
If you want to build on this project commercially, host it for customers, or include it in a paid product, contact the maintainer for commercial licensing.
## License
This project is **source-available, not open-source under an OSI-approved license**. Personal evaluation, learning, and non-commercial contribution are permitted under the project license. Commercial use, hosted/SaaS use, and inclusion in paid products require a separate commercial license.
See [LICENSE](LICENSE) for the full terms.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues