Fix MCP saved keyword metrics (#483)
This commit is contained in:
parent
72eaa09c32
commit
db45a0dd6d
@ -10,6 +10,7 @@ import {
|
||||
locationCodeSchema,
|
||||
projectIdSchema,
|
||||
} from "@/server/mcp/schemas";
|
||||
import { savedKeywordMetricSchema } from "@/types/schemas/keywords";
|
||||
|
||||
const inputSchema = {
|
||||
projectId: projectIdSchema,
|
||||
@ -18,6 +19,13 @@ const inputSchema = {
|
||||
.min(1)
|
||||
.max(100)
|
||||
.describe("Keywords to save (1-100)."),
|
||||
metrics: z
|
||||
.array(savedKeywordMetricSchema)
|
||||
.max(100)
|
||||
.optional()
|
||||
.describe(
|
||||
"Optional metrics for the saved keywords. Copy keyword, searchVolume, keywordDifficulty, cpc, competition, and intent from research_keywords rows; map each row's trend to monthlySearches. Match each metric using its keyword field.",
|
||||
),
|
||||
tags: z
|
||||
.array(z.string().min(1).max(64))
|
||||
.max(20)
|
||||
@ -70,6 +78,7 @@ export const saveKeywordsTool = {
|
||||
await KeywordResearchService.saveKeywords({
|
||||
projectId: args.projectId,
|
||||
keywords: args.keywords,
|
||||
metrics: args.metrics,
|
||||
tags: args.tags,
|
||||
tagMode: args.tagMode ?? "append",
|
||||
locationCode,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { listSavedKeywordsTool } from "./list-saved-keywords";
|
||||
import { saveKeywordsTool } from "./save-keywords";
|
||||
@ -63,6 +64,43 @@ describe("saved keyword MCP tools", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts and passes keyword metrics through save_keywords", async () => {
|
||||
mocks.saveKeywords.mockResolvedValue({
|
||||
success: true,
|
||||
savedKeywordIds: ["saved_1"],
|
||||
});
|
||||
const metrics = [
|
||||
{
|
||||
keyword: "technical seo",
|
||||
searchVolume: 120,
|
||||
keywordDifficulty: 18,
|
||||
cpc: 2.5,
|
||||
competition: 0.42,
|
||||
intent: "commercial" as const,
|
||||
monthlySearches: [
|
||||
{ year: 2026, month: 7, searchVolume: 110 },
|
||||
{ year: 2026, month: 8, searchVolume: 120 },
|
||||
],
|
||||
},
|
||||
];
|
||||
const args = z.object(saveKeywordsTool.config.inputSchema).parse({
|
||||
projectId: "project_1",
|
||||
keywords: ["technical seo"],
|
||||
metrics,
|
||||
});
|
||||
|
||||
await saveKeywordsTool.handler(args, toolContext);
|
||||
|
||||
expect(mocks.saveKeywords).toHaveBeenCalledWith({
|
||||
projectId: "project_1",
|
||||
keywords: ["technical seo"],
|
||||
metrics,
|
||||
tagMode: "append",
|
||||
locationCode: 2840,
|
||||
languageCode: "en",
|
||||
});
|
||||
});
|
||||
|
||||
it("replaces tags through save_keywords when requested", async () => {
|
||||
mocks.saveKeywords.mockResolvedValue({
|
||||
success: true,
|
||||
|
||||
@ -31,28 +31,12 @@ export const researchKeywordsSchema = z.object({
|
||||
clickstream: z.boolean().optional().default(false),
|
||||
});
|
||||
|
||||
export const saveKeywordsSchema = z
|
||||
.object({
|
||||
projectId: z.string().min(1),
|
||||
keywords: z.array(z.string().min(1)).min(1).max(500),
|
||||
locationCode: z.number().int().positive().optional(),
|
||||
languageCode: z.string().min(2).max(8).optional(),
|
||||
tags: z.array(savedKeywordTagSchema).max(20).optional(),
|
||||
tagMode: z.enum(["append", "replace"]).optional(),
|
||||
metrics: z
|
||||
.array(
|
||||
z.object({
|
||||
export const savedKeywordMetricSchema = z.object({
|
||||
keyword: z.string().min(1),
|
||||
searchVolume: z.number().int().nonnegative().nullable().optional(),
|
||||
cpc: z.number().nonnegative().nullable().optional(),
|
||||
competition: z.number().min(0).max(1).nullable().optional(),
|
||||
keywordDifficulty: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.max(100)
|
||||
.nullable()
|
||||
.optional(),
|
||||
keywordDifficulty: z.number().int().min(0).max(100).nullable().optional(),
|
||||
intent: z
|
||||
.enum([
|
||||
"informational",
|
||||
@ -72,10 +56,17 @@ export const saveKeywordsSchema = z
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
.max(500)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const saveKeywordsSchema = z
|
||||
.object({
|
||||
projectId: z.string().min(1),
|
||||
keywords: z.array(z.string().min(1)).min(1).max(500),
|
||||
locationCode: z.number().int().positive().optional(),
|
||||
languageCode: z.string().min(2).max(8).optional(),
|
||||
tags: z.array(savedKeywordTagSchema).max(20).optional(),
|
||||
tagMode: z.enum(["append", "replace"]).optional(),
|
||||
metrics: z.array(savedKeywordMetricSchema).max(500).optional(),
|
||||
})
|
||||
.refine(
|
||||
(value) => value.tagMode !== "replace" || (value.tags?.length ?? 0) > 0,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user