diff --git a/src/server/mcp/tools/create-project.test.ts b/src/server/mcp/tools/create-project.test.ts index f76ba76..3faeef6 100644 --- a/src/server/mcp/tools/create-project.test.ts +++ b/src/server/mcp/tools/create-project.test.ts @@ -75,7 +75,23 @@ describe("create_project MCP tool", () => { { name: "Bad market", languageCode: "en" }, toolContext, ), - ).rejects.toThrow(); + ).rejects.toMatchObject({ code: "VALIDATION_ERROR" }); + expect(mocks.createProject).not.toHaveBeenCalled(); + }); + + it("rejects an unsupported location as a readable validation error", async () => { + const call = () => + createProjectTool.handler( + { name: "Bad location", locationCode: 999999 }, + toolContext, + ); + + await expect(call()).rejects.toMatchObject({ code: "VALIDATION_ERROR" }); + // The message must name the offending field so the calling agent can + // retry with a supported code, not just repeat the bare error code. + await expect(call()).rejects.toThrow( + "Unsupported DataForSEO location code", + ); expect(mocks.createProject).not.toHaveBeenCalled(); }); }); diff --git a/src/server/mcp/tools/create-project.ts b/src/server/mcp/tools/create-project.ts index f3e7f2e..55101bc 100644 --- a/src/server/mcp/tools/create-project.ts +++ b/src/server/mcp/tools/create-project.ts @@ -1,4 +1,5 @@ import { ProjectService } from "@/server/features/projects/services/ProjectService"; +import { AppError } from "@/server/lib/errors"; import { mcpResponse } from "@/server/mcp/formatters"; import { type ToolContext } from "@/server/mcp/context"; import { optionalMetaOutputSchema } from "@/server/mcp/output-schemas"; @@ -65,9 +66,17 @@ export const createProjectTool = { handler: async (args: Args, context: ToolContext) => { const { baseUrl, ...auth } = context.auth; // Reuse the app's create schema so the market pair rule (a languageCode - // requires a locationCode) is enforced identically to the dashboard, and - // the domain is normalized the same way. - const input = createProjectSchema.parse(args); + // requires a locationCode) and domain normalization match the dashboard. + // A rejection is bad caller input, not a fault: VALIDATION_ERROR keeps it + // out of error reporting while still naming the bad field. + const parsedInput = createProjectSchema.safeParse(args); + if (!parsedInput.success) { + throw new AppError( + "VALIDATION_ERROR", + z.prettifyError(parsedInput.error), + ); + } + const input = parsedInput.data; const project = await ProjectService.createProject( auth.organizationId, input,