Skip to main content
Glama
ivanprytula

cv-rest-mcp-server

by ivanprytula

CV REST/MCP Server

FastAPI + FastMCP service rendering a CV as JSON, HTML, themed PDFs, and MCP tools. Operator login guards mutating/authed endpoints; the same JD you're applying to can be tailored against your skill bank and checked for gaps. See docs/using-gap-analysis.md for the "what should I learn next?" workflow.

Architecture

Two services, one codebase:

Service

Role

Deployed as

API

FastAPI + FastMCP; renders CV; validates operators

api-core image

SPA

React 19 + Vite; operator login & dashboard

spa-origin image

Locally: Both run in dev mode (API on 8080, SPA on 5173). just dev-local starts the API with hot reload; just dev-spa starts the SPA (separate terminal).

Cloud Run: Separate services. SPA nginx serves static assets (Vite-hashed); API is JSON-only. A reverse proxy (or CDN) routes /app.domain → SPA and /api.domain → API.

Related MCP server: cv-builder

Authentication

Operators sign in at /login (served by the SPA) with username/password. API issues an HS256 JWT access token plus a refresh token; both are Postgres-backed (DATABASE_URL, required, no default — see CLAUDE.md).

Unauthenticated requests:

  • GET /, GET /health — allowed

  • GET /cv, /cv/html, /cv/preview, /cv/pdf (live CV, no tailored selector) — allowed

  • Everything under /api/v1/*, plus a ?tailored= selector on the public CV routes — JWT required

See CLAUDE.md for env vars (JWT_SIGNING_KEY, FIRST_ADMIN_USERNAME, etc.).

Getting Started

Just want to see the CV locally?

just setup && just dev-local

Open http://localhost:8080. just dev-spa (separate terminal) starts the operator SPA on 5173 for login and the tailoring/gap-analysis UI.

Want to modify the CV data?

Edit data/cv.json (validated against CVData in services/portfolio/cv_data.py). Restart the API server.

Set CV_DATA_PATH env var to point to a different JSON file. See data/cv.example.json for the full schema.

Want to try job-description gap analysis?

See docs/using-gap-analysis.md.

Ready to deploy to GCP?

See Deployment below.

API

REST Endpoints

Public (no auth):

Method

Path

Description

GET

/

Landing page

GET

/health

Health check

GET

/cv

Live CV as JSON

GET

/cv/html?theme=<name>

Live CV rendered as HTML

GET

/cv/preview?theme=<name>

Preview page with toolbar

GET

/cv/pdf?theme=<name>

Live CV as PDF (attachment)

Operator-only (JWT required — cv:read / cv:manage scopes, see AGENTS.md):

Method

Path

Description

GET

/api/v1/cv

Operator CV as JSON (supports tailored revisions)

GET

/api/v1/cv/pdf

Operator CV as PDF

POST

/api/v1/cv/tailor

Tailor the CV against a job description

GET

/api/v1/revisions

List saved tailored revisions

POST

/api/v1/postings

Store a job posting

GET

/api/v1/postings

List stored postings

POST

/api/v1/postings/{id}/analyze

Run gap analysis on a posting

GET

/api/v1/postings/{id}

Read a stored gap report

GET

/api/v1/gaps/roadmap

Ranked "what to learn next" roadmap

*

/api/v1/tracked-boards*

CRUD for ATS boards under continuous monitoring

*

/api/v1/documents/{kind}

CRUD for operator documents (CV / skill bank / JD vocabulary)

See docs/using-gap-analysis.md for how the gap-analysis endpoints fit together, and docs/api.md for the full contract.

Interactive OpenAPI docs (Swagger UI): /docs

MCP Tools

Mounted at /mcp via HTTP JSON-RPC transport. Any MCP client (Claude Desktop, Cursor, VS Code, Windsurf) can connect:

{
  "mcpServers": {
    "cv-rest-mcp-server": {
      "url": "http://localhost:8080/mcp"
    }
  }
}

For deployed: replace http://localhost:8080 with your public API URL.

Available tools:

Tool

Parameters

Returns

get_cv

JSON object with full CV data

get_available_themes

list[str] of theme names

generate_cv_pdf_tool

theme: str

Base64-encoded PDF bytes

match_job_posting

posting_text: str, title: str = ""

Tailored CV JSON matched against a posting

Themes

Four built-in themes: classic, minimal, modern, original. Defined in services/portfolio/themes/<name>.py as CSS strings.

Add a new theme: create services/portfolio/themes/yourname.py with a CSS constant.

Rate Limiting

Per-IP (X-Forwarded-For-aware behind a trusted proxy), in-memory. Loopback peers exempt in local dev. Every limited endpoint stacks a per-minute burst cap with a per-hour sustained cap; a sample:

Endpoint

Limit

/

30/min, 120/hour

/health

60/min

/cv, /api/v1/cv

30/min, 600/hour

/cv/html, /cv/preview

30/min, 300/hour

/cv/pdf, /api/v1/cv/pdf

5/15min, 15/hour

/api/v1/cv/tailor

10/min, 60/hour

Development

All commands use uv for dependency management:

uv sync --group dev     # Install dependencies (first time)
uv run pytest           # Run tests (522 total)
uv run pytest -x        # Stop on first failure
just code-quality       # Ruff + type check
just dev-local          # Start the API with hot reload
just dev-spa            # Start the SPA (separate terminal)
npm run css             # Rebuild Tailwind (after template changes)

See CLAUDE.md for full command reference.

Docker

Build and run both services locally in a container:

just build && just run

Or build images manually for a GCP project:

just build-images <your-gcp-project>

This builds api-core, spa-origin, and any other service images and pushes them to the cv-images Artifact Registry repo (<region>-docker.pkg.dev/<project>/cv-images/<service>).

Deployment

New to DNS, load balancers, or Terraform? docs/infrastructure.md explains how a browser request reaches your code — DNS delegation, SSL certificates, the load balancer, Cloud Run, and how Terraform ties them together — using this project's real resources.

Local Development

just dev-local   # API, port 8080
just dev-spa     # SPA, port 5173 (separate terminal)

Both start with hot reload. No setup required beyond just setup.

Cloud Run (GCP)

One-time bootstrap (idempotent):

export GCP_PROJECT=<your-gcp-project>
just deploy bootstrap          # Enable APIs, create CV bucket, set IAM
just deploy bootstrap-state    # Create Terraform state bucket
just deploy bootstrap-secrets  # Create JWT signing key + refresh token pepper
cd terraform && terraform plan && terraform apply  # Deploy all services
just deploy upload-cv          # Upload your CV data to GCS

See CLAUDE.md for the full step-by-step with configuration details.

Cost Estimation (Infracost)

Before deploying, estimate monthly GCP costs:

# Local estimate (requires Infracost CLI installed)
infracost breakdown --path terraform/

# Set up CI/CD cost estimates on PRs (requires INFRACOST_API_KEY secret)
# See CLAUDE.md#cost-estimation for details

Budget: $100/month. Pre-commit hook and CI/CD automatically warn if costs trend high.

Subsequent releases:

Two path-filtered workflows split infrastructure from application releases — Terraform owns the platform, gcloud run deploy ships the code:

You changed

Workflow

What runs

services/, frontend/, Dockerfiles

deploy-app.yml

lint + tests → build images to Artifact Registry → gcloud run deploy → verify

terraform/

ci-cd.yml

tflint + checkov → Infracost → terraform planapproval gateterraform apply

An app-only commit never runs Terraform, and an infra-only commit never rebuilds images. Use [skip deploy] in a commit message to run checks without deploying.

Infra applies are gated on the production GitHub Environment: terraform plan posts its output to the PR, and the apply job waits for a required reviewer before running the exact reviewed plan. Application rollbacks don't need Terraform — redeploy a previously built image tag.

The deployed URL is printed by the verify job and visible in Google Cloud Console under Cloud Run.

Testing

uv run pytest               # Full suite (522 tests)
uv run pytest services/portfolio/tests/test_auth.py -xvs  # Auth tests with verbose output
uv run pytest --cov         # Coverage report (96% target)

Tests run against a real, ephemeral Postgres via testcontainers (Docker required). See CLAUDE.md for auth fixture details.

Stack

  • Backend: FastAPI, FastMCP v3, SQLAlchemy (asyncpg/psycopg), Alembic, WeasyPrint, Jinja2

  • Frontend: React 19, TypeScript, Vite, TanStack Query (React Query), Tailwind CSS

  • Database: Cloud SQL Postgres (Auth Proxy), Alembic-migrated

  • Deployment: Cloud Run, Terraform, GitHub Actions CI/CD

  • Design: Stateless, $PORT-aware, 12-factor config (env vars)

Project Status

Phase 2: Postgres-backed users, revisions, and gap analysis; JD tailoring against a skill bank; continuous ATS-board monitoring feeding a ranked learning roadmap. 522 tests passing. See CLAUDE.md for key files and non-obvious patterns.


For questions or contributions, see AGENTS.md (detailed setup and troubleshooting) or CLAUDE.md (internal patterns and commands).

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    B
    maintenance
    Provides MCP tools for managing a CV as structured content, including a SQLite-backed snippet library with tools to list, create, update, and match job postings, compose role-tailored CV variants, and manage drafts.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to query a person's CV and portfolio content via MCP tools and resources, returning grounded answers from local markdown data instead of relying on resume parsing.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to create, retrieve, update, delete, and export CVs as PDFs through MCP, sharing documents with the web editor.
    MIT