Repository navigation
pdf-server: validate annotations with per-type schemas (fixes stamps rendering as "undefined") - #832
Merged
Merged
Conversation
Stamps often rendered as the literal text "undefined": the annotation
arrays were typed z.record(z.string(), z.any()), so a stamp sent with
content (note/freetext's field) instead of label sailed through, and
every render path coerced the missing label — textContent and fillText
drew "undefined" and the save path baked it into the PDF bytes.
Replace the loose shape with strict per-type zod schemas shared by both
tool surfaces (new src/annotation-schemas.ts): a discriminated union for
add_annotations (stamp requires label, note/freetext require content,
image requires imageUrl or imageData, unknown keys are errors rather
than silent strips) and a partial union for update_annotations (id and
type required, text fields clearable to "", imported annotations accept
position patches). The app tools enforce them through their input
schemas; the server-mediated interact validates per def and names the
failing index, type and field. Compile-time guards assert each schema's
output against its pdf-annotations.ts interface so the two cannot
drift silently.
The render and save paths also get explicit fallbacks (label ?? STAMP,
content ?? "") so annotations persisted before this validation no
longer draw "undefined" or throw while saving. Tool descriptions fix
the rects shorthand ({x,y,w,h} -> {x,y,width,height}) that the schemas
now enforce.
Co-authored-by: ochafik <ochafik@anthropic.com>
@modelcontextprotocol/ext-apps
@modelcontextprotocol/server-basic-preact
@modelcontextprotocol/server-basic-react
@modelcontextprotocol/server-basic-solid
@modelcontextprotocol/server-basic-svelte
@modelcontextprotocol/server-basic-vanillajs
@modelcontextprotocol/server-basic-vue
@modelcontextprotocol/server-budget-allocator
@modelcontextprotocol/server-cohort-heatmap
@modelcontextprotocol/server-customer-segmentation
@modelcontextprotocol/server-debug
@modelcontextprotocol/server-lazy-auth
@modelcontextprotocol/server-map
@modelcontextprotocol/server-pdf
@modelcontextprotocol/server-scenario-modeler
@modelcontextprotocol/server-shadertoy
@modelcontextprotocol/server-sheet-music
@modelcontextprotocol/server-system-monitor
@modelcontextprotocol/server-threejs
@modelcontextprotocol/server-transcript
@modelcontextprotocol/server-video-resource
@modelcontextprotocol/server-wiki-explorer
commit: |
ochafik
added a commit
that referenced
this pull request
Oct 27, 2026
Minor release: the SDK gains WebMCP interoperability — tools registered with App.registerTool are mirrored into document.modelContext and tools the page registers there are served over the Apps tools/list channel, behind a new webMcpBridge option (default on), with installWebMcpPolyfill for views that need a drop-in model context (#829). The pdf-server example validates annotations with strict per-type schemas, fixing stamps rendering as the literal text "undefined" (#832). Workspace dependency ranges move to ^2.1.0 per the bump script. Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stamps often rendered as the literal text "undefined". Root cause: the
annotationsarrays were typedz.record(z.string(), z.any()), so a stamp sent withcontent(note/freetext's field) instead oflabelsailed straight through — and every render path then coerced the missing label (el.textContent = undefinedandctx.fillText(undefined, …)draw"undefined", and the save path baked it into the PDF bytes viaPDFHexString.fromText).Rather than normalizing aliases, this tightens the contract:
src/annotation-schemas.ts, shared by both tool surfaces: a strict discriminated union per annotation type foradd_annotations(stamp requireslabel, note/freetext requirecontent, highlight/underline/strikethrough requirerects, image requiresimageUrlorimageData; unknown or misspelled keys are errors, not silent strips) and a partial union forupdate_annotations(id+typerequired, text fields clearable to"",importedannotations accept position patches).interactvalidates per def, naming the failing index, type and field — e.g.add_annotations: annotations[0] (stamp): label: Invalid input: expected string, received undefined.pdf-annotations.tsinterface, so a field added there without updating the schema becomes a compile error instead of a silently dropped key.label ?? "STAMP",content ?? "") so annotations persisted to localStorage before this validation no longer draw"undefined"— and no longer throw in the appearance-stream builder, which calleddef.label.replace(...)unguarded.rectsshorthand ({x,y,w,h}→{x,y,width,height}) that the schemas now enforce, and mark the stamp'slabelas required.Test plan
bun test examples/pdf-server, 201 pass): new cases for the server-side validation — stamp withoutlabelrejected naming the field, valid stamp queued, unknown type rejected, update partials accepted while still requiringid.pdf-app-interact.spec.tsdriving the view'sinteractwith the classiccontent-instead-of-labelstamp and asserting the error nameslabel; the full annotation CRUD suite (pdf-annotations.spec.ts) passes under the strict schemas. 12/13 pdf e2e locally — the one failure (display_pdf result includes viewUUID and interactEnabled meta) reproduces identically on cleanmain.tsc --noEmiton both configs, prettier clean.Generated by Claude Code