mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-24 23:32:19 +10:00
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:
@@ -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", () => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user