Commit graph

695 commits

Author SHA1 Message Date
Olivier Chafik
6d083cd69f Address review: MCPB build, guard the SDK's own handlers, doc fixes
- pdf-server build-mcpb.mjs packs the ext-apps build from this checkout
  into the staging dir instead of asking the registry for ^2.0.0, which
  is not published yet; CI's "Build MCPB bundle" step failed on ETARGET.
- Claim notifications/cancelled and notifications/progress in the
  MethodRegistry right after super() so a direct setNotificationHandler
  cannot silently replace the base Protocol's own handlers.
- Migration guide and add-app-to-server skill: custom methods use the
  three-argument setRequestHandler("m", { params }, handler) form (the
  two-argument form is only for spec methods); zod schemas live in
  @modelcontextprotocol/core, not client/server; it is
  toolInfo.tool.outputSchema that loosened in schema.json, and the
  recursive JSON-value def is nested under three definitions; core is a
  required peer that client already depends on.
- app-bridge.ts: fix "forthe"/"forresult" typos in @see links, update
  three JSDoc examples to the method-string setNotificationHandler form,
  move the UntypedHandlerSetter type below the import block (same in
  app.ts).
- wire-compat.test.ts: say the fixtures are 1.x-shaped, not captures.
2026-09-08 13:59:09 +01:00
Olivier Chafik
f722753c8f Finish .js import extensions and drop the zod 3 fallback test
Add explicit .js extensions to the relative imports in app.ts,
app-bridge.ts and their tests so the emitted declarations resolve under
NodeNext (#704). Replace the zod 3 JSON Schema fallback test, whose
code path was removed, with the remaining non-Standard-JSON-Schema
rejection case as its own test.
2026-09-08 13:59:09 +01:00
Olivier Chafik
cfdc0c725f fix: restore double-registration guard on App and AppBridge handlers
The base SDK Protocol silently replaces an existing request or
notification handler. v1's ProtocolWithEvents threw instead, so a stray
setRequestHandler / setNotificationHandler could not disconnect on*
handlers or addEventListener listeners. Restore that guard on both App
and AppBridge with a small MethodRegistry, keep replace semantics for the
on* setters via a protected replaceRequestHandler, and release methods
on removeRequestHandler / removeNotificationHandler.

Also:
- give oncreatesamplingmessage a getter, a replace warning and the same
  clearable shape as the other request setters
- let App register handlers for custom (non-spec) methods; the default
  branch of assertRequestHandlerCapability threw a misleading
  "No handler for method X registered" error
- move the RESOURCE_URI_META_KEY / RESOURCE_MIME_TYPE docs to
  src/constants.ts where the constants now live
- fix stale "inherited from Server" wording and point @see links at
  @modelcontextprotocol/client, which is what hosts install
2026-09-08 13:59:09 +01:00
Olivier Chafik
ea6b89c9e2 Add wire compatibility tests against captured 1.x traffic
Replay JSON-RPC messages recorded from ext-apps 1.7.x (sdk 1.x key
order and ids) through the 2.x App and AppBridge over a raw in-memory
transport and assert exactly what 2.x emits: the ui/initialize
handshake, params-less ui/notifications/initialized, tools/call in both
directions, and the -32002 to -32602 re-encoding on the host side. Also
check that AppBridge never sends an MCP initialize toward the View and
that the outer-client proxy works with a default (non-legacy) 2.x
Client/Server pair.
2026-09-08 13:58:37 +01:00
Olivier Chafik
c5b2543115 Document the 2.x install matrix and add a migration guide
Install instructions now list the split base MCP SDK packages at ^2.0.0
by role (core comes in transitively, server only for server authors),
require Node 20+, and state that the wire protocol is unchanged so 2.x
Views and 1.x hosts interoperate. The agent skills lose the exact
prerelease wording and add-app-to-server gains a v1 to v2 import
mapping. docs/migrate-to-2.md lists the breaking changes, the peer
matrix and the host-side error deltas.
2026-09-08 13:58:37 +01:00
Olivier Chafik
09bf2aae3c Drop the zod 3 JSON Schema fallback
The zod peer range is ^4.2.0, whose schemas implement
~standard.jsonSchema, so the lazy zod/v4 toJSONSchema fallback for
zod 3.25 is dead code. Re-export StandardSchemaWithJSON from the client
package instead of keeping a structurally identical local copy.
2026-09-08 13:58:37 +01:00
Olivier Chafik
0a477bb25d Use explicit .js extensions in relative imports
Consumers using moduleResolution NodeNext need explicit extensions in
the emitted declaration files. Covers every source file except app.ts,
app-bridge.ts and events.ts (and their tests), which are being reworked
separately.
2026-09-08 13:58:36 +01:00
Olivier Chafik
7f686ff2b4 Update dependency isolation check for the required client peer
The app-only consumer now also exercises the ./react entry. The
server-only consumer no longer asserts that client is absent from
node_modules (npm auto-installs required peers) but still checks that
the server entry does not bundle it. Surface npm and esbuild errors
instead of swallowing them.
2026-09-08 13:58:36 +01:00
Olivier Chafik
d6ebcca9a0 Accept raw zod shapes in registerAppTool as a deprecated overload
SDK v2 registerTool takes Standard JSON Schema objects (z.object(...))
and keeps a deprecated overload for the v1 raw-shape form. Mirror that
here so existing servers passing { field: z.string() } keep compiling;
the call is forwarded to the SDK, which auto-wraps the shape. Tests
exercise both forms end to end through a real McpServer and Client.
2026-09-08 13:58:36 +01:00
Olivier Chafik
040942b3af Import shared wire types from @modelcontextprotocol/client
spec.types.ts previously inferred CallToolResult, ContentBlock and
friends from core's Zod schemas via a local core-types module so that
the type surface stayed free of a client package edge. Now that client
is a required peer, take the types from it directly and delete the
indirection. Generated schemas are unchanged.
2026-09-08 13:58:22 +01:00
Olivier Chafik
50eb30ec8d Bump to 2.0.0 and widen SDK peer ranges to ^2.0.0
The client, core and server packages become ^2.0.0 peers and client is
now a required peer (App and AppBridge extend its Protocol class);
server stays optional for View-only consumers. Examples move to
zod ^4.2.0 to match the library peer range.

The root package is not yet published at 2.0.0, so the lockfile now
links the examples' @modelcontextprotocol/ext-apps dependency to the
repository root instead of a registry copy. This drops the stale 1.x
tarball (and its sdk 1.x peer) from the lockfile and makes
scripts/link-self.mjs a no-op.
2026-09-08 13:58:22 +01:00
Olivier Chafik
f28f5ddb3c Harden dependency isolation check and run it in CI
npm pack runs the prepare script for directory specs even with
--ignore-scripts and its output can pollute stdout, so locate the packed
tarball on disk instead of parsing --json output. Run npm with a minimal
environment so caller npm config cannot leak into the synthetic
consumers, and require exact versions for the consumers' dependencies.
Add a CI job that runs the check on Node 22.
2026-09-08 13:58:21 +01:00
Olivier Chafik
90123733bb test: regenerate lockfile (re-add nested @types/node entries) 2026-09-08 13:58:21 +01:00
Olivier Chafik
f9c671c323
Add explicit .js extensions to relative imports for NodeNext consumers (#705)
Emitted declarations used extensionless relative imports, so types failed to resolve under moduleResolution NodeNext/Node16 (#704).

Closes #704
2026-09-08 13:57:46 +01:00
Ken Jo
264d9cae44 fix: add explicit .js extensions to relative imports so declarations resolve under NodeNext/Node16
The emitted .d.ts copy relative specifiers verbatim, and the extensionless form
(e.g. `from "./events"`) doesn't resolve under Node16/NodeNext, hiding inherited
members such as `addEventListener` (TS2339). Appending `.js` matches the existing
SDK-import style and works with both bundler-mode type-checking and Bun.build.
Types-only; runtime is unaffected.

Fixes #704
2026-09-08 13:40:58 +01:00
Olivier Chafik
1a713173cc
Migrate to MCP SDK 2.0 with official Protocol and isolated role peers (#720)
App and AppBridge extend the public Protocol from @modelcontextprotocol/client; the iframe wire protocol (ui/initialize only) is unchanged. Peers move to the split SDK 2.0 packages with client and server as optional role peers. Fixups (lockfile, peer ranges, restored handler guards, docs, wire-compat tests) follow in a separate PR.

Co-authored-by: Andrew Khadder <andrew@manufact.com>
Co-authored-by: Felix Weinberger <fweinberger@anthropic.com>
Closes #702
2026-09-08 12:44:49 +01:00
Quentin Churet
10195ad918
docs: add Alpic Playground to supported clients (#729)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-12 18:04:54 +03:00
Enrico
4e4886c5ff chore: update MCP SDK packages to stable 2.0.0 2026-07-28 15:02:48 -07:00
Olivier Chafik
92f46a5745
chore: bump ext-apps to 1.7.5 (#724) v1.7.5 2026-07-23 12:25:07 +01:00
Enrico
c847bc0887 docs: align Protocol and role-specific setup 2026-07-22 15:37:22 -07:00
Enrico
e7ffe7c738 chore: update MCP SDK packages to beta.5 2026-07-22 15:24:49 -07:00
Olivier Chafik
7d498079e8
chore: clear npm audit findings (15 -> 3) (#725)
Non-breaking transitive updates via `npm audit fix`, plus a `sharp` major
bump. Takes `npm audit` from 15 findings (2 critical, 7 high, 4 moderate,
2 low) to 3 moderate.

Critical / high:

- shell-quote 1.8.3 -> 1.9.0 (GHSA-w7jw-789q-3m8p, GHSA-395f-4hp3-45gv)
- @babel/core 7.28.6 -> 7.29.7 (GHSA-4x5r-pxfx-6jf8)
- sharp ^0.34.5 -> ^0.35.3 (GHSA-f88m-g3jw-g9cj; libvips CVE-2026-33327,
  CVE-2026-33328, CVE-2026-35590, CVE-2026-35591)
- brace-expansion 1.1.16 / 2.1.2 (GHSA-3jxr-9vmj-r5cp)
- fast-uri 3.1.4 (GHSA-v2hh-gcrm-f6hx, GHSA-4c8g-83qw-93j6)
- hono 4.12.31 (8 advisories, incl. GHSA-88fw-hqm2-52qc, GHSA-hvrm-45r6-mjfj)
- linkify-it 5.0.2 (GHSA-22p9-wv53-3rq4, GHSA-v245-v573-v5vm)

Plus markdown-it 14.3.0, body-parser 2.3.0, type-is 2.1.0, undici 7.28.0
and vite 6.4.3.

sharp is a devDependency used only by tests/e2e/generate-grid-screenshots,
which is testIgnore'd unless GENERATE_SCREENSHOTS=1. The only breaking
change in 0.35 is the Node floor moving to >=20.9.0, and CI runs Node 22;
the API used (png / resize / toFile) is unchanged.

The 3 remaining findings are all the same advisory (GHSA-frvp-7c67-39w9,
path traversal in @hono/node-server's serve-static on Windows), reported
once each for @hono/node-server, @modelcontextprotocol/sdk and
@modelcontextprotocol/ext-apps. The fix is in @hono/node-server 2.0.5 but
sdk 1.29.0 pins ^1.19.9, so the range cannot reach it; that needs an
upstream bump. It is not reachable here either way — the SDK imports only
getRequestListener and serve from that package, never serve-static.
2026-07-22 19:34:30 +01:00
victor-openai
c55a3a231f
fix: preserve experimental capability settings (#721) 2026-07-22 19:28:57 +03:00
Enrico
7664bb1010 test: isolate optional SDK role dependencies 2026-07-20 11:29:43 -07:00
Felix Weinberger
43eed45404 Point SDK packages at the pkg.pr.new preview of typescript-sdk#2501
The Protocol-based migration needs the Protocol class exported from the
package roots, which lands in modelcontextprotocol/typescript-sdk#2501.
Pin the preview builds so the branch is installable; swap to the next
published beta once that PR is released.
2026-07-20 11:14:56 -07:00
Felix Weinberger
ccae093d1a Port App and AppBridge to the public v2 Protocol base class
Replaces the Client/Server subclassing from the v2 migration with direct
Protocol extension, now that the SDK exports Protocol from the client
package root. This restores the v1 wire contract on the iframe channel:
ui/initialize is the first and only handshake, so deployed hosts keep
working unchanged.

Deleted with the double handshake: the inner MCP initialize round-trip
and its pinned protocol version, the contentless inner capabilities and
their retro-registration plumbing, the oninitialized defineProperty
shadow, and the getCapabilities/getHostCapabilities split (v1
getCapabilities restored). Tests pin the wire order and assert no MCP
initialize is sent in either direction.

Note: package.json overrides point the SDK packages at locally built
tarballs carrying the Protocol export; replace with published versions
before any upstream PR.
2026-07-20 11:14:56 -07:00
Dominic Farolino
2ca6a59d2f
spec: sync object-src with default-src (#715) 2026-07-17 03:15:10 +03:00
Mel Ludowise
9778cf97a2
Make lazy-auth-server usable on mobile (#714)
The consent page shipped without a viewport meta tag, so mobile browsers
laid it out at a desktop width. Its Approve/Deny buttons were 37px tall
and nested a <button> inside an <a>, which is invalid HTML — interactive
content is not permitted inside a link.

- Add a viewport meta tag to the consent page
- Replace the <a><button> nesting with anchors styled as buttons
  (role="button" plus a Space-key handler to keep button keyboard
  semantics)
- Lay the actions out as a wrapping flex row with equal 56px tap targets
- Add touch-action: manipulation so a tap resolves to a click immediately
  rather than waiting on the double-tap-to-zoom gesture
- Suppress long-press text selection and the iOS callout menu on the
  buttons, scoped so the card's client/redirect details stay copyable
- Give buttons an :active style since removing the native tap highlight
  otherwise leaves no press feedback
- Apply the same 56px minimum and touch handling to the in-app view's
  buttons (the fullscreen toggle was ~33x33)
2026-07-16 17:00:17 +01:00
Andrew Khadder
f50ec6ebfb
fix: regenerate lockfile for npm 10 2026-07-14 17:16:27 -07:00
Andrew Khadder
f94cb38b0b
refactor: finish MCP SDK v2 migration 2026-07-14 17:07:30 -07:00
Andrew Khadder
a76e3a7691
refactor: migrate shared SDK surfaces to v2 2026-07-14 15:46:56 -07:00
Andrew Khadder
d19a1fb83a
refactor: migrate AppBridge to SDK v2 server 2026-07-14 15:34:33 -07:00
Andrew Khadder
59e6ffc6d8
refactor: migrate App to SDK v2 client 2026-07-14 15:02:25 -07:00
Andrew Khadder
209cfc6241
refactor: compose protocol-neutral event dispatch 2026-07-14 13:49:10 -07:00
Andrew Khadder
b383847bf2
chore: prepare SDK v2 dependencies 2026-07-14 13:35:51 -07:00
Copilot
cf87f2a2c2
ci: fix README prettier build failure (#698)
* Initial plan

* fix: apply Prettier formatting to README table row

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-07 21:29:16 +03:00
Andrew Khadder
cd969dbc31
docs: add mcp-use inspector badge to supported clients (#650) 2026-07-07 21:05:17 +03:00
Cameron Yick
f0d9582eaa
spec: add missing HostCapabilities fields (#653) 2026-07-07 21:02:07 +03:00
Ensar Gök
ea8775dcb5
docs: API Docs link for ext-apps/server (#697)
Updated API documentation link for ext-apps/server.
The link `/server.html` does not exists.
2026-07-07 20:43:37 +03:00
Aqil Aziz
fa12744908
docs: fix map-server readme and code of conduct links (#657) 2026-06-17 18:51:57 +03:00
Olivier Chafik
ca1d29894f
chore: bump ext-apps to 1.7.4 (#685) v1.7.4
* chore: bump ext-apps to 1.7.4

* fix: resolve all npm audit vulnerabilities

- Remove unused vitest devDependency from basic-host (critical advisory,
  package was never referenced by any test or config)
- systeminformation ^5.31.5 -> ^5.31.6 (command injection, GHSA-hvx9-hwr7-wjj9)
- Lockfile-only bumps: devalue 5.8.1, fast-uri 3.1.2, hono 4.12.23,
  ip-address 10.2.0, express-rate-limit 8.5.2, postcss 8.5.15, qs 6.15.2,
  svelte 5.56.1

npm audit now reports 0 vulnerabilities.
2026-06-04 22:10:06 +01:00
Olivier Chafik
0ffe16781e
lazy-auth-server: support mounting under a base path (#683)
* lazy-auth-server: support mounting under a base path

createApp() previously built every advertised URL (AS metadata
endpoints, PRM resource, WWW-Authenticate resource_metadata, consent
approve link, elicitation callbacks) from the bare origin, so mounting
the app inside another Express app — app.use("/lazy-auth",
createApp()) — advertised URLs that pointed at the host's root and
collided with the host's own OAuth endpoints.

Derive the public base URL from Express's req.baseUrl instead (empty
when standalone, so standalone behavior is unchanged), and treat
PUBLIC_URL as including any mount path. Document the mounting pattern,
including the root well-known rewrite hosts must add for RFC 8414/9728
path-insertion discovery.

* lazy-auth-server: derive DIST_DIR from import.meta.url

import.meta.filename and import.meta.dirname are undefined in some
module-VM contexts (e.g. importing this package from jest), which made
createApp() throw at import time. import.meta.url is defined
everywhere; derive the dist directory from it instead.
2026-06-04 19:47:57 +01:00
Mel Ludowise
a990780293
Require PKCE and bind redirect_uri in lazy-auth-server token exchange (#681)
* Require PKCE and bind redirect_uri in lazy-auth-server token exchange

Hardens the example's mock authorization server:

- /authorize now requires a PKCE S256 code_challenge (the MCP auth
  spec mandates PKCE for clients; previously a code issued without a
  challenge skipped verification at the token endpoint)
- /token rejects authorization-code exchanges where a provided
  redirect_uri does not match the authorization request (RFC 6749
  §4.1.3); OAuth 2.1 clients that omit it still work, relying on the
  now-mandatory PKCE binding

* Enforce single-use authorization codes

Track redeemed code IDs (jti) in memory until the code's own 5-minute
expiry; replaying a code at the token endpoint now fails with
invalid_grant (RFC 6749 §4.1.2). Also documents why wildcard CORS is
intentional for this demo (browser-based hosts must read
WWW-Authenticate; no ambient credentials exist to protect).
2026-06-02 13:30:49 +01:00
Olivier Chafik
7d4434e991
chore: bump ext-apps to 1.7.3 (#680) v1.7.3
No SDK API changes since 1.7.2.

Examples:
- Add lazy-auth-server example (#679)
- Publish lazy-auth-server to npm (added to publish workflows)

Also syncs example dependency ranges to ^1.7.0 (output of npm run bump).
2026-06-01 12:10:47 +01:00
Mel Ludowise
fcc4b03bde
Add lazy-auth-server example (#679)
Adds an example MCP App server demonstrating lazy (on-demand) OAuth:
the server connects and lists tools without authentication, and only
prompts for auth when a protected tool is called, by responding 401
with a WWW-Authenticate header pointing at protected-resource metadata.

- Public `show_auth_button` app calls the protected `get_secret` tool
  via callServerTool; the host runs the OAuth flow on 401 and retries
- Embedded mock authorization server (authorization-code + PKCE,
  short-lived HS256 tokens, refresh + session revocation) so the whole
  flow runs from a single process
- TTL-scoped endpoint paths (/ttl/<seconds>/mcp) let slow or automated
  clients request longer-lived tokens per connection, threaded through
  OAuth via RFC 8707 resource indicators
- `elicit_url` / `elicit_by_error` tools demonstrating URL elicitation
2026-06-01 11:19:53 +01:00
Olivier Chafik
9a37ad7182
chore: bump ext-apps to 1.7.2 (#660) v1.7.2
No SDK API changes since 1.7.1.

Examples:
- pdf-server: switch pdf-lib to maintained @cantoo/pdf-lib fork (#651)
- bump example transitives to latest patches (#658)
2026-05-15 19:01:25 +01:00
Olivier Chafik
1d091e2132
chore: bump example transitives to latest patches (#658)
- vite-plugin-solid 2.11.10 -> 2.11.12 (basic-server-solid)
- babel-preset-solid 1.9.10 -> 1.9.12
- babel-plugin-jsx-dom-expressions 0.40.3 -> 0.40.6
- vite-prerender-plugin 0.5.12 -> 0.5.13 (basic-server-preact)
- @modelcontextprotocol/ext-apps 1.0.0 -> 1.7.1 (lockfile had a stale
  registry pin of the repo's own published package that was never bumped
  alongside package.json's version field)

Tighten the basic-server-solid range to ^2.11.12 to track the patch
series with current Solid HMR fixes.
2026-05-08 15:55:06 +01:00
Olivier Chafik
3cf5b588a1
chore(pdf-server): switch pdf-lib to maintained @cantoo/pdf-lib fork (#651)
The original pdf-lib package has been unmaintained since Nov 2021.
@cantoo/pdf-lib is an actively maintained, API-compatible fork with
ongoing releases and accumulated bugfixes.

Also updates test assertions from the deprecated PDFNumber.value() to
PDFNumber.asNumber() per the fork's API guidance.
2026-05-08 13:01:54 +01:00
Olivier Chafik
0008d3b78f
chore: bump ext-apps to 1.7.1 (#643) v1.7.1 2026-04-27 16:16:33 +01:00
Olivier Chafik
ba31be1f91
perf(pdf-server): lazy form extraction via range transport + incremental viewer scans (#639)
* perf(pdf-server): lazy form extraction via range transport + incremental viewer scans

Server: display_pdf now opens the document via PDFDataRangeTransport
(disableAutoFetch) and only runs the per-page form/annotation walk when
getFieldObjects() is non-empty, so form-free PDFs are probed with ~10-25%
of bytes instead of a full download. The unused viewFieldInfo Map is
removed.

Viewer: getDocument sets disableAutoFetch/disableStream; baseline
annotation scan and field-name mapping run lazily per rendered page
instead of walking every page after load, so first paint no longer
schedules a full-file pull.

E2E: new range-counting HTTPS fixture (W-9 for forms, generated
text+image PDF for no-forms) with stallAfterBytes control, and four
regression tests asserting form fields are returned, <30% served on
no-forms display_pdf, first page renders while later ranges are stalled,
and overlap stays bounded.

* fix(pdf-server): handle separated field/widget trees in extractFormSchema

pdfjs getFieldObjects() returns the full field-tree array. For PDFs with
a separated structure (pdf-lib, some authoring tools) the typed widget
sits at fields[1+] behind a typeless container at fields[0]; the previous
code only inspected fields[0] and skipped them all. Pick the first entry
with a non-empty type instead.

Makes the e2e forms.pdf fixture fully generated (no checked-in third-party
asset on the hot path); fw9.pdf stays as a unit-test fixture for the
hierarchical/XFA case.

* fix(pdf-server): address range-transport hangs and lazy-scan tombstone loss

PdfCacheRangeTransport:
- abort() is a no-op stub on PDFDataRangeTransport (it's the hook pdfjs
  calls *on* the transport, not an upstream error channel). Expose a
  .failed promise that rejects on the first fetch error and race every
  pdfjs await against it in display_pdf, so transient network errors
  surface into the existing catch instead of hanging the tool call.
- pdfjs coalesces adjacent missing chunks into one unbounded
  requestDataRange; readPdfRange clamps each call to MAX_CHUNK_BYTES.
  Loop and deliver in slices so every requested chunk is marked loaded.

Viewer (mcp-app.ts):
- The lazy per-page baseline scan left pdfBaselineAnnotations incomplete,
  so persistAnnotations and getAnnotatedPdfBytes silently dropped
  restoredRemovedIds tombstones for unvisited pages. Union those ids into
  the computed diff and removedRefs.

Test fixture: release stalled handlers before resetStats/close so a
failing stalled test doesn't hang afterAll; fail fast if started with
NODE_ENV=production. NODE_TLS_REJECT_UNAUTHORIZED scope documented (full
per-process scoping needs a validateUrl localhost allow, tracked
separately).

* test(pdf-server): bridge coverage gaps and switch fixture to gated loopback HTTP

PdfCacheRangeTransport.deliver(): pdf.js's reader is keyed by the
original begin and removed after one delivery, so accumulate slices and
call onDataRange once with the full buffer (the previous multi-call
approach threw inside pdfjs). Covered by a new integration test that
drives getDocument()/getPage(1) on a >1MB PDF through a clamping
in-memory readPdfRange.

validateUrl: allow http://127.0.0.1|localhost|[::1] only when
PDF_SERVER_ALLOW_LOOPBACK_HTTP is set, so a remote deploy can't be made
to probe its own ports. Covered by env-on/off unit tests.

Fixture switched to plain HTTP (no openssl, no
NODE_TLS_REJECT_UNAUTHORIZED). Adds /error.pdf (500s after 50KB) and two
e2e tests: page 2 renders after stall release (>512KB object path), and
display_pdf returns gracefully on mid-load 500. Existing <30% test now
samples stats before the iframe loads.

New unit tests: display_pdf returns (not hangs) on mid-load fetch
failure via in-memory MCP client; computeDiff/serializeDiff contract
tests pinning the restoredRemovedIds tombstone-preservation behaviour.

* test(pdf-server): e2e for restoredRemovedIds tombstone preservation through viewer

Adds /with-native-annot.pdf fixture (2 pages, native /Text annot on
page 2) and a Playwright test that drives delete on page 2 → iframe
reload → interact add_annotations on page 1 (persist with page 2
unscanned) → assert localStorage diff still contains the removed id →
page 2 still shows it gone. Covers the mcp-app.ts glue the unit tests
can't reach.

* refactor(pdf-server): simplify per round-2 review and fix false-pass test

server.ts: extract probeFormFields() so display_pdf calls one helper
instead of inlining 40 lines of transport/orFail plumbing; make
extractFormSchema's fieldObjects param required (the optional branch
only existed for a test helper).

server.test.ts: the >1MB integration test now forces the image XObject
fetch via getOperatorList() and asserts max requestDataRange span >
MAX_CHUNK_BYTES, so it can't go vacuous. Dedupe makeRandomJpeg by
importing from the fixture.

mcp-app.ts: restore per-annotation try/catch isolation in the lazy
baseline scan (a throw was skipping AnnotationLayer.render); only
carry forward restoredRemovedIds while the baseline scan is incomplete
so a stale id can't pin dirty=true once every page is scanned.

tests: bump fixture JPEG to 1.1MB; consolidate 7 e2e tests to 4 (merge
overlap into the byte-budget test, merge stall+page-2, drop the
error-e2e duplicate of the unit integration test); delete
run-fixture.mjs; drop /error.pdf and ERROR_AFTER_BYTES; rename
tombstone test's describe and clarify page-2 covers the viewer
transport.

* fix(pdf-server): preserve partial probeFormFields results when extractFormFieldInfo throws

* test(pdf-server): fix tombstone e2e — deleted native annotations become a cleared card, not removed from DOM

* test(pdf-server): mark tombstone e2e as fixme — needs basic-host iframe-reload replay support

* test(pdf-server): link tombstone fixme to tracking issue #642
2026-04-27 15:32:53 +01:00