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:
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.
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:
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.
- The client registers itself. cvgen supports dynamic client registration, so there is no developer portal and no client id for you to create.
- Your browser opens on cvgen's sign-in page if you are not already signed in.
- A consent screen names the client and the access it is asking for. You approve or decline.
- 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:
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:
Cursor and any other client that reads a JSON config take a headers object:
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_resumesLists 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_resumeFetches 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_resumeCreates 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_resumeDeep-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_resumeChanges a resume's title. Nothing else is touched.
- id string
- title string
patch_resumeThe 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_folderMoves 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_resumeDeletes a resume permanently and cleans up its preview and profile images.
- id string
There is no undo and no trash.
create_folderCreates 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.
contentOpspaths start insidecontent,customizationOpsinsidecustomization. Neither includes a leading/contentor/customizationsegment. - 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
idwhen adding an item; address existing items by path (/experiences/2). Everything else the schema requires you still supply, notablyisVisibleon 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_resumeFills 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_resumeAdapts 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:
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:
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 theBearerprefix, 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.