MCP Coverage Gap and Tool Build Queue

North star (PRINCIPLES.md #7): every action a human can take in the dashboard must have an MCP-tool equivalent. The product's main surface is the collaborative job-market matrix; MCP lets an agent operate that same workspace table instead of creating a parallel workflow. The practical target is: a human can ask an agent to add an internet job offer, enrich and evaluate it against the workspace criteria, inspect or adjust the CVL tree, tailor the application material, and keep the row state understandable from the dashboard.


1. Inventory of current MCP tools

Source: mcp/src/tools/ (registered in tools/index.ts).

# Tool One-line Health
1 add_job Create a job in a workspace (requires title or organization). Wraps job.create op. OK
2 update_job Patch fields on an existing job. Wraps job.update op. OK
3 delete_job Remove a job. Wraps job.delete op. OK
4 list_jobs List all jobs in a workspace. Reads live DO snapshot, falls back to D1. OK
5 get_job Fetch one job by id. OK
6 tailor Persist a tailored Node tree snapshot to a job via tailored.snapshot. Accepts branch_path to target CV or CL. Replaced tailor_cv/tailor_cl (commit e771e7c). CVL unification — no per-document-type tool twins. OK
7 get_cv_typst Return Typst source text for a workspace CV tree (quarry) or a tailored CV. Agents compile locally. Client-only PDF posture (no server Typst). Shipped commit c04203e. OK
8 evaluate_job Persist evaluation results (boolean/numeric criterion values) to a job. Persist-only. OK
9 add_node Insert a new Node into a CV/CL quarry tree. Wraps node.create op. Renamed from add_section (commit 2122c85). OK
10 update_node Patch mutable fields on a tree Node. Wraps node.update. Renamed from update_section (commit 2122c85). OK
11 move_node Move a Node between parents. Wraps node.move. Renamed from move_section (commit 2122c85). OK
16 delete_node Delete a Node from a CV/CL quarry tree. Wraps node.delete. Added commit 2122c85. OK
17 set_selection Update which children of a Node are selected for rendering and minCount/maxCount. Wraps selection.update. Added commit 2122c85. OK
18 translate Copy a node from one language tree to another with LLM translation. Wraps translate op. Added commit 15657aa. OK
12 set_score_criterion Insert/update a single boolean or numeric scoring criterion. Wraps settings.update. OK
13 list_views List view definitions + active view id. Reads D1. OK
14 set_active_view Switch the dashboard active view. Wraps settings.update. OK
15 get_workspace_summary Return id, slug, name, created_at, job_count, active_view_id. OK

1a. Status of the section.* → node.* rename, tailor unification, get_cv_typst, delete_node, set_selection, translate

All renames and missing tools have now landed:

Item Status Commit
add_sectionadd_node ✅ Shipped 2122c85
update_sectionupdate_node ✅ Shipped 2122c85
move_sectionmove_node ✅ Shipped 2122c85
tailor_cv + tailor_cltailor (CVL unification) ✅ Shipped e771e7c
get_cv_typst (Typst source for agents) ✅ Shipped c04203e
delete_node (wraps node.delete op) ✅ Shipped 2122c85
set_selection (wraps selection.update op) ✅ Shipped 2122c85
translate (cross-language node copy with LLM translation) ✅ Shipped 15657aa

Remaining gaps (not yet resolved):

  • tailored.promote is missing from MCP. Always adds as new variant; no mode arg.
  • cv_profile.replace / cl_profile.replace — no tool exposes per-language full-tree replace; needed to seed a fresh language from agent code.
  • mcp/test/tools.test.ts — confirm tests were updated alongside the renames; stale assertions against sections.create / sections.update op kinds would be a regression.

PDF posture is locked: client-only rendering. No analyze_pdf tool. No server-side Typst. Agents use get_cv_typst for source (compile locally), or download_file for cached PDF bytes from R2. See §5.1.


2. Inventory of human capabilities (v2 + v1 reference)

Walked through ui/src/routes, ui/src/lib/components/toolbar/modals/, ui/src/lib/components/cvl/, and ui/src/lib/components/dashboard/.

Capability areas:

A. Workspace lifecycle

A1. Create workspace (POST /api/workspaces). A2. List workspaces — v1 had a landing-page list; v2 home page is a stub. No GET /api/workspaces. A3. Rename workspace name / slug — v2 calls PUT /api/workspaces from layout but route does not exist (PARITY-AUDIT §3.1). A4. Delete workspace — DELETE /api/workspaces/:id not ported (PARITY-AUDIT 4.13). A5. Resolve / "switch" workspace by slug — GET /api/workspaces/:id accepts both id and slug; no list-and-pick tool for agents that don't already hold an id.

B. Job lifecycle

B1. Add by URL — POST /api/workspaces/:id/jobs with {url}. Pipeline auto-advance is unimplemented client-side (PARITY-AUDIT 4.1). B2. Add manually — same endpoint, with {title, organization, ...}. B3. Bulk import (CSV/JSON wizard) — UI is a 36-LoC stub (PARITY-AUDIT 4.10). B4. Bulk URL paste (JobImport modal) — stubbed, blocked on scrape.ts lift status. B5. JobRadar (job-board scraping) — stubbed, blocked on radar.ts lift. B6. Edit job fields — PATCH /api/workspaces/:id/jobs/:jobId. B7. Delete job — DELETE /api/workspaces/:id/jobs/:jobId. B8. Move status / phase — _statusOnly flag on PATCH; kanban DnD ports the gesture. B9. Archive job — represented by terminal status; no separate archive op.

C. PDF / file handling

C1. Upload CV / CL / misc — POST /api/workspaces/:id/files returns 501 (PARITY-AUDIT 4 / §3 row "File upload"). Op files.upload exists in catalog. C2. Download tailored CV/CL or attachment — GET /api/files/:fileId?workspace=… works. C3. Delete file — DELETE /api/files/:fileId?workspace=… works (ref-count + R2 cascade). C4. Render tailored CV/CL to PDF — happens client-side via Typst WASM (lib/domain/src/typst.ts + typst-client.ts); there is no server endpoint and no MCP tool. Agents cannot get PDF bytes. C5. PDF analysis (read-the-PDF) — no v1 or v2 surface; CV upload is treated as opaque bytes (this is consistent with how v1 worked).

D. CV / CL editing (the L0/L1/L2 tree)

D1. Personal info edits — pending design (PARITY-AUDIT §5.1: tree-of-choices says PersonalInfo collapses into a Node). D2. Header layout (drag chip bar, H toggles) — InfoEditor stub. D3. Format edits (font, colors, conventions, link styling, ortho, dateFormat) — FormatEditor stub. D4. Section / Node CRUD — node.create / update / delete / move. D5. Bullet / item CRUD — same Node ops at deeper depth. D6. Selection: which children render, in which order, max-count — selection.update op. D7. Variants: snapshot a tailored output, promote variant L2 → L1 / L0 — tailored.snapshot, tailored.promote. D8. Per-language replace (seed a new lang) — cv_profile.replace / cl_profile.replace. D9. Profile narrative (markdown blob injected into every tailoring prompt) — page is a stub; no Y.Doc root or REST path exists.

E. Pipeline

E1. Trigger extraction (URL → fields) — v1 /api/extract-fields endpoint, now 410 Gone with note "moved to MCP" — but no MCP tool actually exists. E2. Trigger enrichment (salary estimation, commute) — /api/commute lifted; salary.ts not lifted (PARITY-AUDIT 4.2). E3. Trigger evaluation — /api/evaluate 410s with note "moved to realtime worker"; not implemented in the worker either, no MCP tool. E4. Trigger tailoring — tailor_cv / tailor_cl only persist; no tool that actually runs the tailoring chain. (Per principle #8 LLM is client-side; the agent is the LLM here, so trigger semantics on MCP = "run my own LLM and call tailor_*". This is okay but the agent needs the input context — there is no tool that returns "everything tailoring needs": job + quarry + cascade chain + format + language + narrative.) E5. Monitor pipeline state — no read tool returns per-job stage / led status. E6. Cancel pipeline run — n/a, no run state on the server side.

F. Cascade / BYOK

F1. List providers / capabilities — v2 has PROVIDERS config in @cv/domain; no MCP read tool. F2. Set provider keys (BYOK) — PATCH /api/workspaces/:id/cloud-keys. Note: cloud_keys writes are blocked over the WebSocket op channel for secret-leak reasons (validate.ts); the cloud_keys.update op is reserved for service-binding (i.e. MCP) callers. F3. Read cascade chain — GET /api/chain?workspace=… returns unmasked keys. No MCP tool wraps it (and probably shouldn't, see "cross-cutting"). F4. Edit chain order / promote provider — Models tab UI; no REST endpoint yet (it patches cloud_keys.chains via the cloud-keys PATCH). F5. Health check a provider — POST/GET /api/health-log records timeline; no actual "ping this provider with my key" endpoint, no MCP tool.

G. Notes / scoring / annotations

G1. Workspace notes (collaborative ProseMirror) — v2 dropped the editor; /api/notes returns 410 (PARITY-AUDIT 4.6). G2. Set scoring criterion — set_score_criterion (existing). G3. Set evaluations — evaluate_job (existing). G4. Set scoring config (aggregation mode, weights) — settings.update via Y.Doc; no granular MCP tool.

H. Views

H1. List views — list_views (existing). H2. Set active view — set_active_view (existing). H3. Create / edit / delete a view (groups, rules, sort) — Views modal in UI; no MCP tool. H4. Column visibility, widths, layout — layout.update op exists; no MCP tool. H5. Kanban config (phases, statuses, swimlanes) — Kanban modal; no MCP tool. H6. Order state mutations (column / group / job manual order) — order_state.update op; no MCP tool.

I. Export

I1. Export to xlsx / csv — prepareExportData + exportToExcel in @cv/domain/export; UI button is a console.warn no-op (PARITY-AUDIT row "Export"). I2. Render PDF — see C4. I3. Raw Typst source — stackToTypst lifted; no endpoint or tool.

J. Multi-language

J1. Switch active lang — handled in format.lang; UI in CvGenerator chip list. J2. Add a new lang — UI uses a prompt() (PARITY-AUDIT row "Language switching"); no MCP tool. J3. See L1 deltas — implicit in tree structure; no read tool exposes "delta of lang X vs base." J4. Promote L2 → L1 / L0 — tailored.promote op exists, no MCP tool.


3. Gap matrix

Severity:

  • P0 — blocks the chat-first workflow ("paste URL → ingest → tailor → export PDF" must work end-to-end via MCP).
  • P1 — high-value editing or read capability the agent needs to be useful.
  • P2 — nice-to-have, used rarely or by humans only.
ID Capability MCP tool REST endpoint Notes Sev
A1 Create workspace MISSING POST /api/workspaces (works, but seeds empty defaults — PARITY-AUDIT 3.2) Agent has no way to make a workspace from chat. P0
A2 List workspaces MISSING REST MISSING Agent can't enumerate. Without this, "switch workspaces by slug" is the only path. P1
A3 Rename workspace MISSING REST MISSING (PUT route absent) PARITY-AUDIT 3.1. P2
A4 Delete workspace MISSING REST MISSING PARITY-AUDIT 4.13. P2
A5 Resolve workspace by slug (covered by get_workspace_summary — accepts slug or id via getWorkspace) GET /api/workspaces/:id accepts both Works but agent must already know the slug. Pair with A2. P1
B1 Add job by URL add_job exists POST /api/workspaces/:id/jobs exists Pipeline auto-advance does not run (PARITY-AUDIT 4.1) — adding by URL inserts the row but extraction never fires. Need a separate extract_fields tool or a "wait for extraction" semantics. P0
B2 Add job manually add_job works OK done
B3 Bulk CSV/JSON import MISSING REST MISSING UI stub. P1 because agents can loop add_job, but ergonomics matter. P2
B4 Bulk URL paste MISSING REST MISSING Same as B3 + needs scrape.ts. P2
B6 Edit job update_job PATCH /jobs/:id OK; check that _statusOnly / _salaryOnly etc. flags are exposable — currently they aren't surfaced in the tool's input schema. P1
B7 Delete job delete_job DELETE /jobs/:id OK done
B8 Move status covered by update_job (PATCH status_id) works A dedicated move_job_status taking a status name instead of status_id would be friendlier. P2
C1 Upload CV / file MISSING POST /files/ returns 501 Op files.upload exists. PDF ingestion is required for the agent vision — agent must be able to give CareerVector a CV PDF. Blocked on r2.ts domain lift. P0
C2 Download file MISSING (no download_file tool) GET /files/:id works Agents need to fetch tailored output bytes. P0
C3 Delete file MISSING DELETE /files/:id works Op files.delete exists. P1
C4 Render PDF MISSING REST MISSING PDF rendering is client-side Typst WASM today. No server-side render path exists in v1 or v2. Need a decision: lift Typst into a Worker, or have agents call get_typst_source and compile externally. See cross-cutting §5.1. P0
C5 Read uploaded PDF (vision/OCR) MISSING REST MISSING Not present in v1 either. The agent (Claude) already has vision; the right move is "give the agent the file URL, it reads it itself" rather than building OCR. P2
D1 Personal info edits partly (update_section once tree lands) n/a (Y.Doc) Pending design (PARITY-AUDIT 5.1). Block on tree-of-choices PersonalInfo decision before specifying tool. P1 (blocked)
D2 Header layout MISSING n/a Stub editor; depends on D1. P2
D3 Format edits MISSING n/a Stub editor. Op is settings.update patching format. P1
D4 Node CRUD: create add_section (rename → add_node) n/a Renaming pending. Op works. P1
D4 Node CRUD: update update_section (rename → update_node) n/a Renaming pending. P1
D4 Node CRUD: delete MISSING n/a node.delete op exists. Important — agents can create but not destroy. P0
D4 Node CRUD: move move_section (rename → move_node) n/a Renaming pending. P1
D6 Selection update (active children, minCount/maxCount) MISSING n/a selection.update op exists. Core to tree-of-choices loop. P0
D7 Tailored snapshot tailor (commit e771e7c) n/a Unified tool routes through tailored.snapshot, accepts branch_path for CV or CL. Replaces tailor_cv/tailor_cl. done
D7 Tailored promote MISSING n/a tailored.promote(path) op exists. Always adds as new variant; no mode arg. P1
D8 Per-lang full-tree replace MISSING n/a cv_profile.replace / cl_profile.replace ops exist. P1
D9 Profile narrative MISSING REST MISSING No Y.Doc root, no REST path (PARITY-AUDIT 4.7). The narrative is injected into every LLM tailoring prompt — without it, agent-driven tailoring degrades. P0
E1 Trigger extraction MISSING /api/extract-fields 410'd ("moved to MCP" — but the MCP tool was never built) The 410 message lies. P0
E2 Trigger salary enrichment MISSING n/a salary.ts not lifted. P1
E2 Trigger commute calc MISSING POST /api/commute works A compute_commute tool would just wrap the existing endpoint. P1
E3 Trigger evaluation MISSING /api/evaluate 410'd ("moved to realtime worker" — not implemented either) evaluate_job only persists results, doesn't run them. P1
E4 Trigger tailoring MISSING (only tailor_cv persist exists) n/a The agent IS the LLM; what's missing is a tool that hands the agent the full tailoring context (job + quarry + format + lang + narrative + cascade hint) in one call, so it can run its own model and POST back. Call it get_tailoring_context. P0
E5 Read pipeline state MISSING REST MISSING Per-job extraction-state / enrichment-state / tailored-state. P1
F1 List providers MISSING REST MISSING PROVIDERS is in @cv/domain. P2
F2 Set BYOK keys MISSING PATCH /cloud-keys works Sensitive — see cross-cutting §5.4. P1
F3 Read cascade chain MISSING GET /api/chain returns unmasked keys Do not expose unmasked-key endpoint via MCP. A list_chain tool that returns chain order + masked keys is fine. P1
F4 Edit chain MISSING (currently via cloud-keys PATCH) Wrap as set_chain_order. P1
F5 Provider health check MISSING REST MISSING /api/health-log is the timeline; no "ping" endpoint exists. P2
G1 Notes MISSING /api/notes 410'd v1 had a 1057-LoC editor; v2 dropped it. Decision pending. P2 (intent unclear)
G2 Scoring criterion set_score_criterion n/a OK done
H1 List views list_views n/a OK done
H2 Set active view set_active_view n/a OK done
H3 CRUD a view definition MISSING n/a settings.update op covers it. P1
H4 Layout / column visibility MISSING n/a layout.update op exists. P2
H5 Kanban phases / statuses MISSING n/a settings.update. P2
H6 Order state MISSING n/a order_state.update. P2
I1 Export xlsx / csv / JSON MISSING REST MISSING prepareExportData / exportToExcel lifted; Dashboard.svelte button is console.warn. Trivial to wrap. P1
I2 Render PDF LOCKED: not building PDF rendering is client-only. No server-side Typst, ever (PRINCIPLES.md #8, §5.1). Agents get PDF via download_file from R2 cached renders, or compile from get_cv_typst output. n/a
I3 Raw Typst source get_cv_typst (commit c04203e) Returns Typst source for quarry or tailored CV. This is the canonical agent path for offline PDF generation. done
J2 Add language MISSING n/a cv_profile.replace with new lang. P1
J4 Promote variant MISSING n/a tailored.promote. P1

Counts (updated): After 2122c85 and 15657aa, delete_node, set_selection, and translate are now "done". D4 delete (P0), D6 selection (P0), and translate (unlisted in original) are closed. P0 count drops to 10. Remaining P0 = 10, P1 = 21, P2 = 14.


4. Build queue (updated state)

Original top-10, with completed items marked. Ordered by north-star impact.

# Tool Status Notes
1 Rename add_section/update_section/move_sectionadd_node/update_node/move_node; add delete_node; add set_selection DONE (2122c85) All renames landed. delete_node and set_selection added in same commit.
translate tool (cross-language node copy with LLM translation) DONE (15657aa) Unlisted in original queue; shipped alongside the renames.
2 extract_fields OPEN scrape.ts is lifted; MCP wiring still missing. The 410 message on /api/extract-fields lies — it claims "moved to MCP" but no tool exists yet.
3 upload_file OPEN POST /api/workspaces/:id/files now works (c386fa3, 455ed27). MCP wrapper still missing.
4 download_file OPEN GET /api/files/:fileId works. Wrapper is a small S-effort item.
5 get_tailoring_context OPEN Profile narrative path still unresolved (OQ parked). Can ship with narrative: '' placeholder.
6 tailored_promote OPEN Op exists; no MCP tool yet.
7 render_pdf LOCKED — not building. Client-only PDF posture. See §5.1.
8 get_typst_source DONE as get_cv_typst (c04203e) Returns Typst source for quarry or tailored CV.
9 create_workspace + list_workspaces + get_workspace_by_slug OPEN POST /api/workspaces works; GET /api/workspaces (list) still needs a new endpoint.
10 update_selection DONE as set_selection (2122c85) Shipped in item #1 rename batch.

Implicit (deferred): set_chain_order + list_providers — important for BYOK flows, but agents will mostly receive keys out-of-band. Defer until P0 list is closed.


5. Cross-cutting concerns

5.1 PDF rendering boundary [LOCKED 2026-04-26]

Decision: client-only rendering. No server-side Typst on Cloudflare Workers.

Typst WASM (~25MB) loads from CDN, fonts preload into a virtual filesystem, stackToTypst() produces the source, $typst.pdf() produces bytes in the browser. Server-side Typst on Cloudflare Workers would burn CPU budget and fights PRINCIPLES.md #8 (client compute is the default).

Agent PDF access paths:

  • PDF bytes: agents pull from the most recent cached client render stored in R2 via download_file / GET /api/files/:fileId.
  • Typst source: agents call get_cv_typst which returns the source text directly. Agents can compile locally (e.g., typst CLI on the user's machine via Claude Code).

There is no render_pdf tool that produces bytes on-demand from the server. get_cv_typst (build queue #8, shipped at commit c04203e) is the correct approach for agent-driven PDF generation.

OCR / vision posture [LOCKED 2026-04-26]: CareerVector does NOT build OCR or PDF extraction. Agents (Claude, GPT-4o, Gemini, etc.) have vision built-in; when a user uploads a reference PDF, the agent reads it itself via the file URL. CareerVector exposes three pathways for the agent: structured Node tree, Typst source as text, and file bytes. Files are uniform — get_file(fileId) does not distinguish "rendered output" from "user-uploaded reference." A PDF is a PDF. There is no analyze_pdf tool and none will be added.

5.2 URL ingestion / scrape.ts

scrape.ts already lives at lib/domain/src/scrape.ts (with tests/scrape.test.ts). The lift is done. What's missing is the wiring: extract_fields tool, plus the client-side JobImport modal calling it. Build queue #2 is unblocked on the import side.

5.3 Output bytes — URL vs base64

MCP tools return text content. Both modes are legal:

  • base64 in structuredContent: simple, no URL plumbing, works without auth. Agents (Claude) handle base64 cleanly. Bad for files > a few MB (token cost in the agent's context window).
  • URL the agent fetches: forces the agent (or its host) to be allowed to make HTTP requests to our domain. Fine for Claude Code, less fine for sandboxed agents.

Recommendation: thresholded. For files ≤ 1 MB, return base64 in structuredContent. For larger (typical tailored PDFs are 50–200 KB so we're rarely there), return {url, expiresAt} and let the agent fetch. Document the threshold in the tool's description so agents can plan.

5.4 Streaming / long-running jobs

Tailoring takes 30–60 s. PDF compile is faster (<5 s on the client; unknown on the server). Options:

  1. Block. Tool waits until done, returns the result. Simple. Risks: MCP transport timeouts (varies by client), agent UX (no progress indicator).
  2. Job-id + poll. Tool returns {jobId}; agent polls get_job_status(jobId) until done. Familiar pattern; fits well with the singleflight compute_lock pattern already in v1. Adds infra (a job table or D1 row).
  3. Realtime fan-out. Mutation triggers a DO broadcast; agent subscribes via WebSocket. Powerful but MCP transports don't generally do WebSockets — the agent host would need an extra capability.

Recommendation: for tailoring, since the agent IS the LLM (per principle #8), the tool only persists the result — it's not waiting on a server-side LLM call. So tailoring tools (tailored_snapshot, tailor_cv) can block trivially (write completes in ms). For PDF rendering, recommend block (option 1) with a documented soft cap of ~30 s. For extract_fields: same logic — the agent runs the LLM, so the tool just persists, no wait. Job-id pattern not needed yet.

5.5 Workspace identity / agent default workspace

Per principle #5, possessing the workspace ID is the access credential. There is no default workspace and no auth.

Three patterns make sense:

  1. Agent connection pinned to one workspace. The MCP server URL includes the workspace id (?ws=<id> or as a header). Every tool call inherits it; tools could omit wsId from args.
  2. Tools always require wsId. Current state. Predictable but verbose.
  3. Hybrid: connection-level default, tool-level override. Best of both.

Recommendation: keep current behavior (tools take wsId). Add a MCP_DEFAULT_WORKSPACE env var on the worker for single-tenant deployments (per agent install). Document in tool descriptions: "If your MCP host is configured with a default workspace, you may omit wsId." list_workspaces (P1) is the resolution mechanism for new agents that don't know the slug.

For the BYOK keys sensitive case: the cloud-keys.update op is intentionally allowed only over the service binding (i.e. MCP, not the public WS) per validate.ts. So a set_byok_keys tool can write keys without the leak risk. Read paths must remain masked through MCP — never expose /api/chain (unmasked) via a tool.


6. Things needing user input before #1 starts

  1. Notes editor decision. v1 had a 1057-LoC ProseMirror editor; v2 dropped it. Is this intentional (= remove /api/notes + don't add an MCP tool) or a port miss? Not on the critical path either way, but a "decide once, document" item.
  2. Server-side PDF rendering posture. LOCKED 2026-04-26 — client-only. See §5.1. No action needed.
  3. Profile narrative storage. Where does the markdown blob live? A new Y.Doc root (profile.narrative), an extension of cv_profile's root Node, or a workspace settings field? Tree-of-choices is silent. Decide before #5 (get_tailoring_context) — without it, the tool returns an empty narrative and tailoring quality stays degraded.
  4. PDF analysis (vision). LOCKED 2026-04-26 — NOT building OCR/vision. Agent reads PDFs itself. No analyze_pdf tool. See §5.1.
  5. MCP per-host default workspace. Confirm pattern in §5.5 ("env var per worker install, tools still take wsId for explicitness") matches Julian's deploy model.

7. Out of scope (this doc)

  • Implementation. This is a plan.
  • New product features beyond v1 / v2 today. Greenfield restraint per task brief.
  • Touching files owned by other agents.

End of MCP-COVERAGE.md.

Source: wiki/content/architecture/MCP-COVERAGE.md