refactor(applications): autofill from a pasted posting instead of a URL

Fetching an arbitrary job URL server side meant owning SSRF defence, redirect
and size limits, and per-site scraping quirks. The autofill tool now takes only
pasted text, so the URL input, the fetch path and its MCP annotation are gone.

The sheet gates the call behind a tested AI provider and a minimum paste length
so a stray snippet does not spend an AI call.
This commit is contained in:
Amruth Pillai
2026-08-17 22:32:32 +02:00
parent 7a14b0dfbc
commit da2f1f8244
6 changed files with 114 additions and 551 deletions
+8 -3
View File
@@ -94,16 +94,21 @@ describe("buildMcpServerCard", () => {
it("accepts only http/https application source URLs", () => {
const create = TOOL_META[MCP_TOOL_NAME.createApplication].inputSchema;
const autofill = TOOL_META[MCP_TOOL_NAME.autofillApplicationFromJob].inputSchema;
expect(create.safeParse({ company: "Acme", role: "Engineer", sourceUrl: "https://example.com/job" }).success).toBe(
true,
);
expect(autofill.safeParse({ sourceUrl: "http://example.com/job" }).success).toBe(true);
const invalidUrl = create.safeParse({ company: "Acme", role: "Engineer", sourceUrl: "ftp://example.com/job" });
expect(invalidUrl.success).toBe(false);
if (!invalidUrl.success) expect(invalidUrl.error.issues[0]?.message).toBe("URL must use http or https.");
expect(autofill.safeParse({ sourceUrl: "javascript:alert(1)" }).success).toBe(false);
});
it("requires a pasted job posting to autofill an application", () => {
const autofill = TOOL_META[MCP_TOOL_NAME.autofillApplicationFromJob].inputSchema;
expect(autofill.safeParse({ jobDescription: "Senior Engineer at Acme" }).success).toBe(true);
expect(autofill.safeParse({ sourceUrl: "https://example.com/job" }).success).toBe(false);
expect(autofill.safeParse({ jobDescription: " " }).success).toBe(false);
});
it("rejects application document payloads above 10MB decoded", () => {
+2 -7
View File
@@ -102,14 +102,9 @@ describe("tool annotations", () => {
}
});
it("marks only job-posting autofill as open-world", () => {
expect(TOOL_META[MCP_TOOL_NAME.autofillApplicationFromJob].annotations.openWorldHint).toBe(true);
});
it("declares no tools as open-world by default", () => {
it("declares no tools as open-world", () => {
for (const [name, { annotations }] of Object.entries(TOOL_META)) {
if (name === MCP_TOOL_NAME.autofillApplicationFromJob) continue;
expect(annotations.openWorldHint).toBe(false);
expect(annotations.openWorldHint, name).toBe(false);
}
});
});
+3 -13
View File
@@ -21,12 +21,6 @@ const READ_NON_IDEMPOTENT: ToolAnnotations = {
idempotentHint: false,
openWorldHint: false,
};
const READ_OPEN_WORLD_NON_IDEMPOTENT: ToolAnnotations = {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true,
};
const WRITE_NON_IDEMPOTENT: ToolAnnotations = {
readOnlyHint: false,
destructiveHint: false,
@@ -458,13 +452,9 @@ export const TOOL_META = {
},
[T.autofillApplicationFromJob]: {
title: "Autofill Application From Job",
description:
"Use AI to extract company, role, location, salary, and job description from a job URL or pasted posting.",
inputSchema: z.object({
sourceUrl: httpUrlSchema.optional(),
jobDescription: z.string().max(20_000).optional(),
}),
annotations: READ_OPEN_WORLD_NON_IDEMPOTENT,
description: "Use AI to extract company, role, location, and salary from a pasted job posting.",
inputSchema: z.object({ jobDescription: z.string().trim().min(1).max(20_000) }),
annotations: READ_NON_IDEMPOTENT,
},
[T.scoreApplicationMatch]: {
title: "Score Application Match",