From 91f6027fed5490f0110a4009289b134a65ba0616 Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Mon, 21 Sep 2026 10:52:26 -0400 Subject: [PATCH] fix(schema): add zod 4 .def detection * development, zod 4 note * review-zod-integration skill, update stale notes, add report comparison * options.assertions, minor zod update * server.schema, zod 4 `.def` detection --- docs/development.md | 2 +- .../skills/review-zod-integration/SKILL.md | 1 + .../skills/review-zod-integration/reference.md | 17 ++++++++++------- src/options.assertions.ts | 2 +- src/server.schema.ts | 9 +++++++++ 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/development.md b/docs/development.md index b350ee8f..05751c57 100644 --- a/docs/development.md +++ b/docs/development.md @@ -422,7 +422,7 @@ These terms describe **how tools and their related properties are represented** - **`Tool Factory`**: A function wrapper `(options) => Tool` (internal). - **`Tool Module`**: The programmatic result of `createMcpTool`, representing a collection of tools. - **`JSON Schema` (`plain object`)**: A plain object intended as JSON Schema (for example `type`, `properties`, `required`). Converted toward Zod where possible via `jsonSchemaToZod` / `fromJSONSchema`. -- **`Zod schema`**: A Zod schema instance. Loosely detected with `isZodSchema`. +- **`Zod schema`**: A Zod schema instance. Loosely detected with `isZodSchema` (Zod 4 `_zod` / public `.def`, Zod 3 `_def`). - **`Raw Zod shape` (`ZodRawShapeCompat`)**: A **non-empty** plain object whose **values** are Zod schemas; keys are field names. An empty `{}` is **not** a raw Zod shape. Detected with `isZodRawShape`. ## Initial troubleshooting diff --git a/guidelines/skills/review-zod-integration/SKILL.md b/guidelines/skills/review-zod-integration/SKILL.md index eb503ee3..ae51959b 100644 --- a/guidelines/skills/review-zod-integration/SKILL.md +++ b/guidelines/skills/review-zod-integration/SKILL.md @@ -34,6 +34,7 @@ description: Reviews Zod dependency upgrades for PatternFly MCP—maps release n 4. **Impact matrix** - For **every** release-note bullet (breaking, fixes, performance, locales), add one row to the report tables. Columns: **Release note**, **Used in PF MCP?** (Yes / No / Indirect — cite file or grep), **Impact** (None / Low / Medium / High), **Priority**, **Recommended fix** (or `None`). Priority rules: [reference.md — Priority rules](reference.md#priority-rules-pf-mcp). - Always include [reference.md — Updated P2 recommendations (Recurring)](reference.md#updated-p2-recommendations-recurring) in the report **Recommended fixes** section, even when tests pass. + - In **Documentation and agent guidance**, compare `guidelines/agent_coding.md` Zod detection text to `isZodSchema` in `src/server.schema.ts`; note match or drift on the `guidelines/agent_coding.md` row (see [reference.md — Report template](reference.md#report-template)). - Row patterns: [reference.md — Example impact rows](reference.md#example-impact-rows-pf-mcp). 5. **Tests** diff --git a/guidelines/skills/review-zod-integration/reference.md b/guidelines/skills/review-zod-integration/reference.md index 15ea9c16..ffef26bc 100644 --- a/guidelines/skills/review-zod-integration/reference.md +++ b/guidelines/skills/review-zod-integration/reference.md @@ -20,12 +20,12 @@ | File | Zod role | |---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------| -| `src/server.schema.ts` | `isZodSchema`, `isZodRawShape`, `jsonSchemaToZod`, `normalizeInputSchema`, `zodToJsonSchema`; v3 `_def` / v4 `_zod` detection | +| `src/server.schema.ts` | `isZodSchema`, `isZodRawShape`, `jsonSchemaToZod`, `normalizeInputSchema`, `zodToJsonSchema`; v3 `_def`, v4 `_zod`, public `.def` detection | | `src/server.tools.ts` | `z.looseObject({})` Tools Host fallback | | `src/server.toolsHost.ts` | Normalize plugin schemas; manifest JSON Schema via `zodToJsonSchema` | | `src/server.toolsUser.ts` | `normalizeInputSchema` for inline/static tools | -| `src/options.assertions.ts` | `z.array(z.string().url().refine(...))` | -| `src/tool.patternFlyDocs.ts` | Raw Zod shape (`z.array`, `z.string`, `z.enum`, `.optional()`) | +| `src/options.assertions.ts` | `z.array(z.url().refine(...))` | +| `src/tool.patternFlyDocs.ts` | Raw Zod shape (`z.url()`, `z.array`, `z.string`, `z.enum`, `.optional()`) | | `src/tool.searchPatternFlyDocs.ts` | Same | | `src/tool.searchPatternFly.ts` | Same (experimental context-management search tool) | | `src/__tests__/server.schema.test.ts` | Conversion tests + `toJSONSchema` snapshots | @@ -61,7 +61,8 @@ If release notes only mention these, impact is usually **None**: | `toJSONSchema` | `zodToJsonSchema`, default `draft-2020-12` | | `z.object` / raw shapes | Built-in tools, `normalizeInputSchema` | | `z.looseObject` | `server.schema.ts`, `server.tools.ts`; Standard for "open" tool inputs in Zod 4.4+ | -| `z.string().url()` + `.refine` | `options.assertions.ts` | +| `z.url()` + `.refine` | `options.assertions.ts` | +| `z.url()` | `tool.patternFlyDocs.ts` (`urlList`) | | `z.enum`, `.optional()`, `.max()`, `.min()` | Tool input schemas | ## Dependencies @@ -164,11 +165,13 @@ Save as: **`reports/YYYYMMDD-HHMMSS-zod-{semver}-update-report.md`** (`YYYYMMDD- ## Documentation and agent guidance +Compare `guidelines/agent_coding.md` (Zod detection) to `isZodSchema` in `src/server.schema.ts`; record match or drift on the `guidelines/agent_coding.md` row. + | Asset | Status | |-------|--------| | `docs/development.md` | | | `docs/examples/*` | | -| `guidelines/agent_coding.md` | | +| `guidelines/agent_coding.md` | Match or drift vs `isZodSchema` | | `tests/e2e/` | | --- @@ -221,7 +224,7 @@ Use these as patterns when mapping **your** target release notes. Re-grep the co | Required keys with `z.undefined()` | No — tools use `.optional()` on keys | None | None | None | | `.merge()` throws when receiver has refinements | No — no `.merge()` | None | None | None | | `toJSONSchema()` strips redundant `id` in `$defs` | Yes — `zodToJsonSchema`, Tools Host manifest | Low — no code reads `$defs.id` | None | None; re-run `server.schema` snapshots if manifest output changed | -| `z.httpUrl()` stricter URL validation | No — uses `z.string().url()` + `refine` in `options.assertions.ts` | None | None | None | +| `z.httpUrl()` stricter URL validation | No — uses `z.url()` + `refine` in `options.assertions.ts` | None | None | None | | Floating-point accuracy (`multipleOf`) | No — `multipleOf` not used in schemas | None | None | None | ### Other fixes — sample rows @@ -289,6 +292,6 @@ These items MUST be included in the "Recommended Fixes (P2)" section of the repo - **DO**: Add detection for the public `.def` property (Zod 4.4+). - **DO**: Update `docs/development.md` to recommend JSON Schema or Zod 4 for *new* plugins. -- **DO**: Migrate `z.string().url()` to `z.url()` where applicable (Zod 4.4+). +- **DO**: Prefer `z.url()` over `z.string().url()` in new code (Zod 4.4+); grep `src/` for stragglers during reviews. - **DO NOT**: Remove the v3 `_def` branch in `isZodSchema`. - **DO NOT**: Remove the `passthrough()` fallback in `jsonSchemaToZod` while plugin/tool authors may supply Zod v3 schemas (even when the server pins Zod 4). Revisit only if PF MCP explicitly drops v3 plugin compatibility. diff --git a/src/options.assertions.ts b/src/options.assertions.ts index c4ab9055..232444f7 100644 --- a/src/options.assertions.ts +++ b/src/options.assertions.ts @@ -20,7 +20,7 @@ const assertProtocol = ( { codeOrError }: { codeOrError?: AssertCodeOrError } = {} ) => { const validate = z.array( - z.string().url().refine( + z.url().refine( url => { try { const urlScheme = new URL(url).protocol; diff --git a/src/server.schema.ts b/src/server.schema.ts index 312a264d..fb560806 100644 --- a/src/server.schema.ts +++ b/src/server.schema.ts @@ -31,6 +31,15 @@ const isZodSchema = (value: unknown): boolean => { } } + // Zod v4 public `.def` (when `_zod` is absent); keep additive alongside v3 `_def` below + if (has('def') && obj.def && typeof obj.def === 'object') { + const def = obj.def as Record; + + if (typeof def.type === 'string' && isFunc(obj.parse) && isFunc(obj.safeParse)) { + return true; + } + } + // Zod v3 detection: `_def` object with both parse and safeParse functions if (has('_def') && obj._def && typeof obj._def === 'object') { if (isFunc(obj.parse) && isFunc(obj.safeParse)) {