From 1e10570c2127c62c83aebf03a0d33849cdf0a5a4 Mon Sep 17 00:00:00 2001 From: Jayson Jacobs Date: Sun, 19 Apr 2026 02:33:15 -0600 Subject: [PATCH] loosen validation of get_acf_schema --- package.json | 2 +- src/tools/acf.ts | 29 +++++++++++++++++++-- test/acf.test.ts | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 test/acf.test.ts diff --git a/package.json b/package.json index a186f8e..b8aa594 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@missionsquad/mcp-wordpress", - "version": "0.2.3", + "version": "0.2.4", "description": "A Model Context Protocol server for interacting with WordPress.", "type": "module", "main": "./build/server.js", diff --git a/src/tools/acf.ts b/src/tools/acf.ts index 9f84070..fac7f7b 100644 --- a/src/tools/acf.ts +++ b/src/tools/acf.ts @@ -16,7 +16,32 @@ type ToolWithZodSchema = Tool & { export const getAcfSchemaSchema = z .object({ target: z - .enum(['content', 'term', 'user']) + .preprocess((value) => { + if (typeof value !== 'string') { + return value + } + + const normalized = value.trim().toLowerCase() + const aliases: Record = { + post: 'content', + posts: 'content', + page: 'content', + pages: 'content', + cpt: 'content', + custom_post_type: 'content', + custom_post: 'content', + taxonomy: 'term', + terms: 'term', + category: 'term', + categories: 'term', + tag: 'term', + tags: 'term', + users: 'user', + } + + return aliases[normalized] ?? normalized + }, z.enum(['content', 'term', 'user'])) + .default('content') .describe('Schema target. Use content for posts/pages/CPTs, term for taxonomy terms, and user for users.'), content_type: z .preprocess((value) => (typeof value === 'string' && value.trim() === '' ? undefined : value), z.string().default('post')) @@ -42,7 +67,7 @@ export const getAcfSchemaSchema = z .optional() .describe('Optional target ID. For users, this may also be "me". Omit to inspect the collection schema.'), }) - .strict() + .passthrough() type GetAcfSchemaParams = | { target: 'content'; content_type: string; id?: number } diff --git a/test/acf.test.ts b/test/acf.test.ts new file mode 100644 index 0000000..c86ea94 --- /dev/null +++ b/test/acf.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from 'vitest' +import { getAcfSchemaSchema } from '../src/tools/acf.js' + +describe('getAcfSchemaSchema', () => { + it('defaults a target-only content schema request to posts', () => { + const parsed = getAcfSchemaSchema.parse({ + target: 'content', + }) + + expect(parsed).toEqual({ + target: 'content', + content_type: 'post', + taxonomy: 'category', + }) + }) + + it('accepts blank optional form fields for a content schema request', () => { + const parsed = getAcfSchemaSchema.parse({ + target: 'content', + content_type: 'post', + taxonomy: '', + id: '', + }) + + expect(parsed).toEqual({ + target: 'content', + content_type: 'post', + taxonomy: 'category', + id: undefined, + }) + }) + + it('coerces numeric string ids from form inputs', () => { + const parsed = getAcfSchemaSchema.parse({ + target: 'content', + content_type: 'post', + id: '123', + }) + + expect(parsed.id).toBe(123) + }) + + it('accepts common target aliases and extra llm-supplied fields', () => { + const parsed = getAcfSchemaSchema.parse({ + target: 'posts', + content_type: 'page', + reason: 'inspect ACF fields', + }) + + expect(parsed).toMatchObject({ + target: 'content', + content_type: 'page', + taxonomy: 'category', + reason: 'inspect ACF fields', + }) + }) + + it('defaults an omitted target to content', () => { + const parsed = getAcfSchemaSchema.parse({}) + + expect(parsed).toMatchObject({ + target: 'content', + content_type: 'post', + taxonomy: 'category', + }) + }) +})