feat(mcp): add cover-letter PDF downloads (#3304)

* feat(mcp): add cover-letter PDF downloads

* fix(mcp): bind signed PDF targets

* test(mcp): cover unavailable cover letters

* fix(api): accept legacy PDF download targets

* fix(server): limit legacy PDF tokens to resumes

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
This commit is contained in:
ignaciocarre
2026-08-13 23:09:56 +02:00
committed by GitHub
co-authored by Amruth Pillai
parent 45303fb465
commit 6d9ebccc63
12 changed files with 210 additions and 29 deletions
+30 -21
View File
@@ -8,6 +8,7 @@ import { Buffer } from "node:buffer";
import { resolveUserFromRequestHeaders } from "@reactive-resume/api/context";
import { createResumePdfDownloadUrl } from "@reactive-resume/api/features/resume/export";
import { env } from "@reactive-resume/env/server";
import { resumeHasCoverLetter } from "@reactive-resume/resume/export-sections";
import { resumeDataSchema } from "@reactive-resume/schema/resume/data";
import { MCP_TOOL_NAME } from "./mcp-tool-names";
import { TOOL_META } from "./tool-meta";
@@ -157,32 +158,40 @@ export function registerTools(server: McpServer, client: RouterClient<typeof rou
}),
);
// ── Download Resume PDF ──────────────────────────────────────
// ── Download Resume or Cover Letter PDF ───────────────────────
server.registerTool(
T.downloadResumePdf,
TOOL_META[T.downloadResumePdf],
withErrorHandling("creating PDF download URL", async ({ id }: { id: string }) => {
const resume = await client.resume.getById({ id });
const user = await resolveUserFromRequestHeaders(requestHeaders);
if (!user) throw new Error("Unauthorized");
withErrorHandling(
"creating PDF download URL",
async ({ id, target }: { id: string; target?: "resume" | "cover-letter" }) => {
const resume = await client.resume.getById({ id });
const user = await resolveUserFromRequestHeaders(requestHeaders);
if (!user) throw new Error("Unauthorized");
const signedUrl = createResumePdfDownloadUrl({ resumeId: id, userId: user.id });
const documentTarget = target ?? "resume";
if (documentTarget === "cover-letter" && !resumeHasCoverLetter(resume.data))
throw new Error("No visible cover letter found for this resume.");
return text(
JSON.stringify(
{
resumeId: id,
name: resume.name,
downloadUrl: signedUrl.url,
expiresAt: signedUrl.expiresAt,
expiresInSeconds: signedUrl.expiresInSeconds,
contentType: "application/pdf",
},
null,
2,
),
);
}),
const signedUrl = createResumePdfDownloadUrl({ resumeId: id, userId: user.id, target: documentTarget });
return text(
JSON.stringify(
{
resumeId: id,
target: documentTarget,
name: documentTarget === "cover-letter" ? `${resume.name} Cover Letter` : resume.name,
downloadUrl: signedUrl.url,
expiresAt: signedUrl.expiresAt,
expiresInSeconds: signedUrl.expiresInSeconds,
contentType: "application/pdf",
},
null,
2,
),
);
},
),
);
// ── Create Resume ─────────────────────────────────────────────