fix(mcp): classify project input validation (#455)
This commit is contained in:
parent
7f9d73102e
commit
b592dc5043
@ -75,7 +75,23 @@ describe("create_project MCP tool", () => {
|
|||||||
{ name: "Bad market", languageCode: "en" },
|
{ name: "Bad market", languageCode: "en" },
|
||||||
toolContext,
|
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();
|
expect(mocks.createProject).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ProjectService } from "@/server/features/projects/services/ProjectService";
|
import { ProjectService } from "@/server/features/projects/services/ProjectService";
|
||||||
|
import { AppError } from "@/server/lib/errors";
|
||||||
import { mcpResponse } from "@/server/mcp/formatters";
|
import { mcpResponse } from "@/server/mcp/formatters";
|
||||||
import { type ToolContext } from "@/server/mcp/context";
|
import { type ToolContext } from "@/server/mcp/context";
|
||||||
import { optionalMetaOutputSchema } from "@/server/mcp/output-schemas";
|
import { optionalMetaOutputSchema } from "@/server/mcp/output-schemas";
|
||||||
@ -65,9 +66,17 @@ export const createProjectTool = {
|
|||||||
handler: async (args: Args, context: ToolContext) => {
|
handler: async (args: Args, context: ToolContext) => {
|
||||||
const { baseUrl, ...auth } = context.auth;
|
const { baseUrl, ...auth } = context.auth;
|
||||||
// Reuse the app's create schema so the market pair rule (a languageCode
|
// Reuse the app's create schema so the market pair rule (a languageCode
|
||||||
// requires a locationCode) is enforced identically to the dashboard, and
|
// requires a locationCode) and domain normalization match the dashboard.
|
||||||
// the domain is normalized the same way.
|
// A rejection is bad caller input, not a fault: VALIDATION_ERROR keeps it
|
||||||
const input = createProjectSchema.parse(args);
|
// 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(
|
const project = await ProjectService.createProject(
|
||||||
auth.organizationId,
|
auth.organizationId,
|
||||||
input,
|
input,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user