Repository navigation
Conversation
Apps that produce or edit documents need a way to hand their current output file back to the model. This adds the pattern to the pdf-server example: the viewer registers a get_current_pdf view tool that returns the current document — with annotations and form fills applied — as an embedded PDF resource in the tool result, so the file can be retrieved without a save to disk. Also replaces the edited-document heuristic shared with the Download button: annotationMap/formFieldValues are seeded with baseline state from the PDF, so a pristine document with native annotations or form values was named "<title> - edited.pdf". The check now mirrors the diff getAnnotatedPdfBytes actually bakes in. The e2e test drives the tool over the view's PostMessageTransport from the sandbox frame — the first direct coverage of a view-registered tool.
@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: |
Hosts that only drive server tools (e.g. Claude Desktop over stdio) could not reach the view-registered get_current_pdf. This adds it as an interact action using the same request-response bridge as get_pages and save_as: the server enqueues a get_current_pdf command, the viewer builds the bytes and posts them back through the app-only submit_current_pdf tool (base64 + file name + edit status), and the blocking interact call returns the same text summary + embedded PDF resource as the app tool. In batches the embedded resource is kept as the command's content slot; a single-action call keeps both the summary text and the resource. Also raises the HTTP transport's JSON body limit (express default: 100kb), which rejected the viewer's multi-MB submits — a latent issue for submit_save_data and screenshot-bearing submit_page_data too.
This branch has not been deployed
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.
Motivation
Apps that produce or edit documents (editors, annotators, generators) need a way to hand their current output file back to the model.
ui/download-filesaves to the user's disk without the model ever seeing the bytes, and waiting for a tool call to finish doesn't fit a long-running editor whose output materializes when the user is done editing.A simple pattern covers this: the app exposes a "get output file" tool that returns the current document as an embedded resource in the tool result. The model learns of the file through a call it made itself, and gets the bytes in the same result. This PR adds that pattern to the pdf-server example — on both tool surfaces, so it works with hosts that call view-registered tools directly and with hosts that only drive server tools (e.g. Claude Desktop over stdio).
Changes
get_current_pdfapp tool (view-registered viaapp.registerTool): returns the currently displayed PDF — with all in-viewer annotations and form fills baked in via the existinggetAnnotatedPdfBytes()path — as an embeddedapplication/pdfresource, plus a text block with the file name, size, and whether it includes unsaved edits.get_current_pdfinteract action (server-mediated): same result shape through the existing request-response bridge —interactenqueues aget_current_pdfcommand, the viewer builds the bytes and posts them back through the new app-onlysubmit_current_pdftool (base64 + file name + edit status), and the blockinginteractcall returns the text summary + embedded resource. In acommandsbatch, the embedded resource is kept as the command's 1:1 content slot; a single-action call keeps both parts.jsonLimit: "64mb"): express's default 100 kB JSON limit rejected the viewer's multi-MB submits. This was latent forsubmit_save_dataand screenshot-bearingsubmit_page_dataas well.<title> - edited.pdfwhenever the PDF had native annotations or form values, becauseannotationMap/formFieldValuesare seeded with baseline state. The newhasInViewerEdits()mirrors the diffgetAnnotatedPdfBytesactually applies and is shared by the Download path and both tool surfaces.tools/list/tools/callJSON-RPC into the app iframe from the sandbox frame; asserts a pristine%PDFexport (not marked edited), then adds an annotation and asserts the re-export differs and is marked edited.Testing
bun test src examples— 443 tests, 0 failnpx tsc -p examples/pdf-server/tsconfig.json --noEmitandtsconfig.server.json— cleannpx playwright test tests/e2e/pdf-get-current-pdf.spec.ts— 2 passed (app-tool path exports the ~1.9 MB arXiv PDF and round-trips an annotation; interact path returns the same resource through the bridge)npx playwright test tests/e2e/pdf-annotations.spec.ts tests/e2e/pdf-viewer-zoom.spec.ts tests/e2e/pdf-incremental-load.spec.ts— all passNotes
uriisfile:///<URI-encoded file name>for consistency with the Download path'sdownloadFilepayload.Generated by Claude Code