It's a vibe coded experiment that helped me personally building my CVs as I want instead of paying 30$ for some lame subscription. You can self host it locally or on VPS and connect your agent to it, both REST or MCP supported.
Use it as you wish. There is no contribution expected. And of course this readme was not meant to be read by humans, let your agent read and explain it for you.
A self-hosted CV editor, PDF renderer, REST API, and Model Context Protocol (MCP) server. CVs are stored by the server, so the browser UI and multiple AI agents work on the same documents.
- Web editor:
/ - REST API:
/api/v1 - OpenAPI description:
/api/v1/openapi.json - Streamable HTTP MCP:
/mcp - Health check:
/api/v1/health - MCP tools:
list_cvs,get_cv,create_cv,update_cv,delete_cv, andexport_cv - OAuth protected-resource discovery:
/.well-known/oauth-protected-resource/mcp
PDFs are rendered on the server. export_cv returns an MCP PDF resource, while
GET /api/v1/cvs/{id}/pdf returns a normal downloadable file.
PDF page count is content-driven rather than fixed by the template. A short CV can render as one A4 page, while longer content automatically flows to three or more pages. The bundled example's two-page check is only a visual-layout regression for that specific fixture.
Requirements: Node.js 22 or newer and npm.
npm install
npm run devOpen http://127.0.0.1:5173. Development uses a durable SQLite database at
data/cv-builder.sqlite and starts with an empty workspace. Set a token before exposing the
process to any network:
CV_BUILDER_API_TOKEN="$(openssl rand -hex 32)" npm run devThe macOS start.command and generated Dock launcher still work. The server owns the data now;
browser localStorage is not used as a database.
To load the fictional example into a new database, set
CV_BUILDER_SEED_FILE=examples/sample-cv.md on the first start.
Create local secrets once:
cp .env.example .env
openssl rand -hex 32Paste the generated value into CV_BUILDER_API_TOKEN in .env, then start the SQLite version:
docker compose up -d --buildThe application is available at http://localhost:5173. SQLite is stored in the named
cv_builder_data volume and uses WAL mode. Back up that volume or the database file.
For PostgreSQL, also set a different POSTGRES_PASSWORD and run:
docker compose -f compose.yaml -f compose.postgres.yaml up -d --buildPostgreSQL is recommended when the service will have multiple replicas, external backups, or several active users. The schema is initialized automatically.
Use compose.coolify.yaml as a self-contained CV Builder and PostgreSQL stack. It intentionally
has no Caddy service and no host port mappings: Coolify supplies the private network, reverse
proxy, domain routing, and HTTPS.
-
Create a Coolify resource from this GitHub repository.
-
Select the Docker Compose build pack, use
/as the base directory, and set the Compose location to/compose.coolify.yaml. -
Add the runtime variables documented in
docs/mobile-plugin-deployment.md. The complete set for ChatGPT mobile is:CV_BUILDER_API_TOKEN=<output of: openssl rand -hex 32> POSTGRES_PASSWORD=<different output of: openssl rand -hex 32> CV_BUILDER_PUBLIC_URL=https://cv.example.com CV_BUILDER_OAUTH_ISSUER=https://YOUR_AUTH0_DOMAIN/ CV_BUILDER_OAUTH_AUDIENCE=https://cv.example.com/mcp CV_BUILDER_OAUTH_JWKS_URL=https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json CV_BUILDER_OAUTH_ALLOWED_SUBJECTS=auth0|OWNER_USER_ID
For a staged rollout, the OAuth variables may remain empty during the first deployment. Existing browser and static-token access continues working, but ChatGPT mobile remains unavailable until all OAuth variables are configured together and the service is redeployed.
-
Assign
https://cv.example.com:8080to thecv-builderservice. The:8080suffix tells Coolify which internal container port to proxy; the public URL still uses normal HTTPS. -
Deploy, then open
/api/v1/healthand confirm that storage ispostgresand authentication is required.
The Compose stack limits the app to 512 MB RAM and PostgreSQL to 1 GB. Those are safety ceilings, not expected steady usage. The Node and PostgreSQL images support both AMD64 and ARM64 hosts.
The authentication and storage model is one private workspace per deployment. For Maxim and Valeriia, deploy this repository twice with distinct domains, OAuth audiences/subjects, access tokens, PostgreSQL passwords, and stack volumes. Follow the linked backup, Auth0, installation, test, recovery, and rollback runbook before enabling mobile access.
Point a DNS A/AAAA record at the VPS, set CV_BUILDER_DOMAIN in .env, and start the PostgreSQL
and Caddy overlays:
docker compose \
-f compose.yaml \
-f compose.postgres.yaml \
-f compose.remote.yaml \
up -d --buildCaddy obtains and renews TLS certificates. Only ports 80 and 443 need to be public; the direct app
port is bound to 127.0.0.1. The agent endpoint is then https://YOUR_DOMAIN/mcp.
Keep .env off the server's public filesystem, restrict SSH access, and arrange automated
PostgreSQL backups before treating the service as the only copy of a CV.
Every agent belonging to one workspace uses that deployment's MCP URL and bearer token. REST-only agents can use the API instead. Agents for another person should use that person's separate deployment and token.
Export the token in the environment that launches Codex/ChatGPT:
export CV_BUILDER_TOKEN="the-value-from-CV_BUILDER_API_TOKEN"Add this to ~/.codex/config.toml (or project-local .codex/config.toml):
[mcp_servers.cv_builder]
url = "https://YOUR_DOMAIN/mcp"
bearer_token_env_var = "CV_BUILDER_TOKEN"Codex CLI/IDE and compatible local desktop agents can keep using this migration path. Restart the client after adding it. Hosted ChatGPT plugins should use OAuth instead.
Use the hosted OAuth connection and private plugin package described in
docs/mobile-plugin-deployment.md. ChatGPT authenticates in a
browser with Authorization Code + PKCE and then calls the hosted /mcp URL directly; no Mac or
manually copied API token is involved after installation.
Add a Streamable HTTP MCP server with:
- URL:
https://YOUR_DOMAIN/mcp - Header:
Authorization: Bearer YOUR_TOKEN
Hermes supports the hosted OAuth path directly:
hermes mcp add --url https://YOUR_DOMAIN/mcp --auth oauth cv-builder
hermes mcp test cv-builderThe built-in static token remains intended for personal desktop/server-side agents and browser login, not ChatGPT mobile.
A reliable CV-preparation run looks like this:
- Call
list_cvsand choose a source CV. - Call
get_cvand keep itsrevision. - Tailor the structured
resumefields or generated Markdown for the requested role. - Call
create_cvfor a new named variant, orupdate_cvwithexpectedRevision. - Call
export_cvwithformat: "pdf"and return the PDF resource to the user.
Revision checks prevent one agent from silently overwriting another agent's edit.
export CV_URL="https://YOUR_DOMAIN"
export CV_TOKEN="the-value-from-CV_BUILDER_API_TOKEN"
curl -fsS "$CV_URL/api/v1/cvs" \
-H "Authorization: Bearer $CV_TOKEN"
curl -fsS "$CV_URL/api/v1/cvs" \
-H "Authorization: Bearer $CV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Product Designer — Acme","markdown":"# Candidate Name\n\n## Product Designer\n\n..."}'
curl -fsS "$CV_URL/api/v1/cvs/CV_ID/pdf" \
-H "Authorization: Bearer $CV_TOKEN" \
-o tailored-cv.pdfThe complete machine-readable operation list is at /api/v1/openapi.json.
| Variable | Default | Purpose |
|---|---|---|
CV_BUILDER_API_TOKEN |
unset | Protects the UI session, REST API, and MCP endpoint. Required for remote use; minimum 6 characters. A long random token remains strongly recommended. |
CV_BUILDER_OAUTH_ISSUER |
unset | Exact trusted authorization-server issuer. |
CV_BUILDER_OAUTH_AUDIENCE |
<public URL>/mcp |
Exact resource/audience required in OAuth access tokens. |
CV_BUILDER_OAUTH_JWKS_URL |
unset | HTTPS JWKS used to verify OAuth JWT signatures. |
CV_BUILDER_OAUTH_ALLOWED_SUBJECTS |
unset | Comma-separated immutable OAuth sub values allowed in this deployment. Use one owner in production. |
CV_BUILDER_OAUTH_ALGORITHMS |
RS256 |
Comma-separated JWT signature algorithm allowlist. |
CV_BUILDER_OAUTH_MAX_TOKEN_LIFETIME_SECONDS |
600 |
Reject OAuth tokens whose exp - iat exceeds this short-lived-token ceiling. |
CV_BUILDER_OAUTH_REVOKED_TOKEN_IDS |
unset | Emergency comma-separated JWT jti denylist. |
CV_BUILDER_OAUTH_REVOKED_BEFORE |
unset | Emergency Unix timestamp invalidating tokens issued at or before it. |
CV_BUILDER_OAUTH_INTROSPECTION_* |
unset | Optional RFC 7662 endpoint and server-side credentials; configure all three together. |
DATABASE_URL |
sqlite:./data/cv-builder.sqlite |
SQLite path or PostgreSQL connection URL. |
CV_BUILDER_PUBLIC_URL |
local server URL | Canonical HTTPS origin used in MCP download links. |
CV_BUILDER_SEED_FILE |
unset | Optional initial Markdown document used only when the database is empty. |
HOST |
127.0.0.1 locally, 0.0.0.0 in production |
Listen address. |
PORT / CV_BUILDER_PORT |
5173 |
Listen port. |
CV_BUILDER_ALLOW_QUIT |
enabled in development | Allows the local macOS launcher to stop the process. |
The optional Markdown input keeps the existing hierarchy:
#candidate name- the first
##professional title - contact lines with
mailto:andtel:links ## Profile,## Employment History,## Education,## Skills, and## Languages###for each employment or education entry- a bold date line followed by
|and its location for employment entries - language values in
Language: value/maximumform
Structured fields remain the source of truth after parsing.
The repository includes examples/sample-cv.md, which uses fictional identity and employment
data. Personal source files, local databases, generated PDFs, .env, and local Codex hooks are
excluded from Git and the Docker build context.
npm test
npm run build
npm run verify:pdfTests cover OAuth discovery; signature, issuer, audience, expiry, subject, revocation, and scope enforcement; authenticated sessions; CRUD and revision conflicts; every CV MCP tool; server and MCP PDF export; a real Streamable HTTP MCP handshake; PDF geometry, text, links, and content-driven pagination in both directions.
CV Builder is available under the MIT License. The bundled Arimo document fonts and JetBrains Mono
interface fonts remain under the SIL Open Font License 1.1; their respective copyright and license
texts are included in public/fonts/ARIMO-OFL.txt and public/fonts/JETBRAINS-MONO-OFL.txt.