cvgen — mcp server

MCP server

cvgen implements the Model Context Protocol, so an MCP client — Claude Code, Claude Desktop, Cursor, ChatGPT — can read and edit the resumes in your account. This page documents the endpoint, both authentication methods, and every tool, prompt and resource the server exposes.

  • 9 tools
  • 2 prompts
  • OAuth 2.1
  • API keys

01 / overview

What the server does

The server operates on the same records the editor does — there is no import or export step. An agent can list your resumes, read one, duplicate it, edit any part of the content or styling, manage folders, and write a cover letter.

There is one endpoint, and every request to it must be authenticated:

https://cvgen.dev/api/mcp

Nine tools, two guided prompts, two schema resources. The tools cover listing, reading, creating, duplicating, renaming, moving, deleting, and one patch tool that handles every edit to a resume's body. The prompts drive the two flows worth scripting — building a resume from scratch and tailoring one to a job description. The resources hand the agent the JSON Schema for the resume shape so it does not have to guess field names.

Streamable HTTP, stateless. The route serves POST and DELETE, responds with JSON rather than an SSE stream, and keeps no session between requests. Configure cvgen as Streamable HTTP, not SSE.

An open editor picks up agent edits on its own. Every write tool expires the cache entry behind the dashboard, so the new state is there the next time it loads. An editor left open on the patched resume polls for a newer timestamp and pulls the change in within about fifteen seconds, rather than quietly overwriting it with what was on screen.

02 / connect

Connecting a client

The endpoint is the same everywhere; only the place you paste it differs. Claude Code takes a CLI command, Cursor reads a JSON file, and Claude Desktop and ChatGPT register remote servers through their own settings UI — their connectors dial the endpoint from the vendor's cloud rather than from your machine, which is why there is no local file to edit.

Add the server, then authenticate from inside a session.

claude mcp add --transport http cvgen https://cvgen.dev/api/mcp

Run /mcp in Claude Code and pick cvgen. A browser opens, you sign in, you approve the request, and the token is stored for you. claude mcp list should then show cvgen as connected.

That command registers the server for the current project only. To have it everywhere, add the user scope:

claude mcp add --transport http cvgen https://cvgen.dev/api/mcp --scope user

03 / authentication

Authentication

Every request needs an Authorization: Bearer header. cvgen accepts two kinds of token and decides which is which by the prefix: a bearer starting with cvgen_live_ is verified as an API key, anything else is verified as an OAuth access token. There is no anonymous access and no read-only mode — a token that can read your resumes can also change them.

OAuth 2.1 — the default

Use this for anything with a browser: Claude Code, Claude Desktop, Cursor, ChatGPT. You never copy a credential, and you can revoke the grant later without touching your other clients.

  1. The client registers itself. cvgen supports dynamic client registration, so there is no developer portal and no client id for you to create.
  2. Your browser opens on cvgen's sign-in page if you are not already signed in.
  3. A consent screen names the client and the access it is asking for. You approve or decline.
  4. The client receives an access token bound to this endpoint as its audience, plus a refresh token so it does not have to ask again.

Clients that speak OAuth discover all of this on their own from two metadata documents — you should never need to enter them by hand:

https://cvgen.dev/.well-known/oauth-protected-resource/api/mcp
  → names https://cvgen.dev/api/mcp as a protected resource and points at the auth server

https://cvgen.dev/.well-known/oauth-authorization-server/api/auth
  → the authorization, token and registration endpoints

API keys — for headless clients

A cron job, a CI step or a script on a server cannot complete a browser redirect. Generate a key in API key settings instead, then send it as the bearer token. Keys carry the same access as your session, so treat one like a password: keep it in an environment variable or a secret store, never in a committed file.

Claude Code takes the header as a flag:

claude mcp add --transport http cvgen https://cvgen.dev/api/mcp \
  --header "Authorization: Bearer cvgen_live_YOUR_KEY"

Cursor and any other client that reads a JSON config take a headers object:

{
  "mcpServers": {
    "cvgen": {
      "url": "https://cvgen.dev/api/mcp",
      "headers": {
        "Authorization": "Bearer cvgen_live_YOUR_KEY"
      }
    }
  }
}

Deleting a key takes effect on the next request. The two paths are independent: revoking an OAuth grant leaves your keys working, and deleting a key signs no client out.

04 / tools

Tool reference

Nine tools, each scoped to the authenticated user — an id belonging to somebody else reads as missing rather than forbidden. Your client reads the full input schemas off the server, so the types below are a summary. A trailing ? marks an optional parameter.

list_resumes

Lists the resumes and folders you own. The entry point — every other tool takes an id from here.

  • folderId? string | null

folderId has three states: omit for everything, null for the root only, or an id for one folder.

get_resume

Fetches one full resume, including content, customization and cover letter.

  • id string

Read this before patching — the paths you patch are the paths you see here.

create_resume

Creates a resume prefilled with the default content and customization.

  • title string
  • folderId? string | null

It arrives with a full sectionsOrder, so the next step is patching values in, not building structure.

duplicate_resume

Deep-copies a resume into the same folder as the source.

  • id string
  • title? string

The preview and profile images are copied as new objects, so the copy never mutates the original's files.

rename_resume

Changes a resume's title. Nothing else is touched.

  • id string
  • title string
patch_resume

The only write path into a resume's body. Applies RFC 6902 JSON Patch operations to content and customization, and replaces the cover letter.

  • id string
  • contentOps? Operation[]
  • customizationOps? Operation[]
  • coverLetter? string

At least one of contentOps, customizationOps or coverLetter is required.

move_to_folder

Moves a resume between folders, or to the root with null.

  • id string
  • folderId string | null

Moving a resume to the folder it is already in is an error, not a no-op.

delete_resume

Deletes a resume permanently and cleans up its preview and profile images.

  • id string

There is no undo and no trash.

create_folder

Creates a folder. The slug is generated from the name if omitted.

  • name string
  • slug? string

Writing patches

Connected clients receive the full patching conventions from the server, so an agent does not need this. It matters if you are calling patch_resume yourself with an API key:

  • Paths are rooted at the field. contentOps paths start inside content, customizationOps inside customization. Neither includes a leading /content or /customization segment.
  • A patch is all or nothing. Operations are applied to a copy and validated against the schema before anything is written. If one path does not exist, nothing persists.
  • Ids are assigned by the server. Omit id when adding an item; address existing items by path (/experiences/2). Everything else the schema requires you still supply, notably isVisible on collection items.

Call get_resume first and build paths from what it returns.

06 / prompts

Prompts and resources

Two registered prompts. Each is a starting message that sets the order of operations for a flow; the conversation stays yours after it runs.

build_resume

Fills a new resume section by section. It creates the resume, reads the template's sectionsOrder to learn which sections exist, then walks them one at a time, offering the optional sections only once the rest are done.

no arguments

tailor_resume

Adapts a resume to one posting. It duplicates the source first and works only on the copy, so the original is never edited. It asks clarifying questions, presents a change plan, and applies nothing until you confirm — then as one atomic patch. It will not invent skills, metrics, titles, dates or employers.

resumeId · jobDescription

Schema resources

Two resources expose the resume shape as JSON Schema, so an agent can check a field name instead of guessing at one:

cvgen://schema/resume-content
  the `content` field — every section, every item, every constraint

cvgen://schema/customization
  the `customization` field — fonts, colors, template, theme, sectionsOrder

These are the schemas the app validates against, with one difference: every id is marked optional and annotated as server-assigned, which is what stops an agent inventing UUIDs.

07 / troubleshooting

Errors and what they mean

401 Unauthorized

Every rejected request looks the same, whichever token you sent. The response carries the header an OAuth-aware client needs to recover on its own:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://cvgen.dev/.well-known/oauth-protected-resource/api/mcp"

{
  "jsonrpc": "2.0",
  "error": { "code": -32000, "message": "Unauthorized" },
  "id": null
}

A well-behaved client reads resource_metadata, discovers the authorization server from it, and starts the sign-in flow — so a 401 on first connect is normal and not a problem. A 401 that keeps repeating usually means one of these:

  • No header at all. There is no anonymous access. If your client saved the server without completing sign-in, it is sending nothing.
  • The access token expired and the client has no refresh token, usually because the grant was revoked. Re-run the sign-in flow — in Claude Code, /mcp.
  • The token was issued for something else. Tokens are bound to this endpoint as their audience and to cvgen as their issuer. A token minted for a different resource fails verification, silently and by design.
  • The API key was deleted. Keys are verified on every request; there is no grace period.
  • The key was mangled in transit. A key must arrive as Authorization: Bearer cvgen_live_…. Sent under any other header name, or without the Bearer prefix, it is not seen at all.

The response never says which of these it was. It is always a bare Unauthorized, deliberately, so the endpoint cannot be used to probe which tokens exist.

405 Method Not Allowed on GET

The route answers POST and DELETE only, so aGET comes back 405. That is intentional and it does not stop the server working — responses are plain JSON, so no server-sent stream is needed. Configure cvgen as Streamable HTTP (some clients call it http, some streamable-http) and not as SSE.

Tool errors

Failures inside a tool come back as tool errors carrying a plain-text message, not as HTTP failures. A failed patch_resume writes nothing, so the resume is left as it was — call get_resume and rebuild the operations from what the server actually holds.