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.
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).
* ci: automate release process with workflow-driven version bumps and PR flow
Adds a Release workflow that bumps all package versions, generates release
notes, and opens a PR. Merging the PR auto-tags and creates the GitHub
Release, which triggers npm-publish. Also adds `npm run bump` for local use
and a release.yml config for categorized auto-generated notes.
* ci: simplify release workflow and flatten npm-publish approval chain
- Drop auto-generated RELEASES.md prepending (notes are written manually in PR)
- Flatten npm-publish job dependencies so all publish jobs wait for approval
together instead of sequentially, reducing 3 approval clicks to 1
- Remove Release environment from publish-mcpb (only needs GH_TOKEN)
- Use --generate-notes for GitHub Release creation
* fix: address review findings in release automation
- release.yml: use RELEASE_TOKEN PAT for gh release create so the
release:published event triggers npm-publish (GITHUB_TOKEN events
don't cascade to other workflows)
- release.yml: checkout merge_commit_sha in tag job instead of ref:main
to avoid tagging later commits
- release.yml: checkout ref:main in prepare job so dispatching from a
feature branch doesn't pollute the release PR
- release.yml: constrain preid to type:choice[beta] to match
npm-publish.yml's tag detection
- release.yml: drop -f on push so re-runs fail loudly instead of
clobbering manual RELEASES.md edits; drop pr-edit fallback
- release.yml: ensure release label exists before gh pr create
- release.yml: make tag push idempotent for re-runs after partial
failure
- bump-version.mjs: update workspace dependency ranges so major bumps
don't leave examples pointing at the old major
- bump-version.mjs: run npm install --package-lock-only so npm ci
on the release PR doesn't fail on lockfile mismatch
- npm-publish.yml: add npm-tag detection to publish-examples so beta
example releases don't overwrite latest
- .github/release.yml: use [bot] suffix in author excludes so
dependabot PRs are actually filtered from release notes
- CONTRIBUTING.md: document RELEASE_TOKEN setup; fix npm run -- syntax
* style: auto-fix prettier formatting
* build: add npm run bump; parallel npm-publish approval
- scripts/bump-version.mjs (+ npm run bump): bumps root and all workspace
package versions, updates examples' ^X.Y.0 dependency range on the root
package (matters on major bumps), and refreshes package-lock so npm ci
accepts the release PR.
- npm-publish.yml: all publish jobs now depend directly on [build, test]
instead of chaining, so they enter "waiting for approval" together and a
single Review-deployments click approves the lot. Prerelease versions
(any X.Y.Z-…) publish under --tag beta so they don't take latest.
- CONTRIBUTING.md: release steps now use npm run bump.
Dropped from the original change: the workflow_dispatch release.yml
(required a long-lived PAT to cascade events), label-based
.github/release.yml auto-notes (PRs aren't labeled today), and the
release-X.Y npm-tag for non-main branches (no backport branches yet).
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* fix(pdf-server): stub DOMMatrix/ImageData/Path2D for npx without @napi-rs/canvas
pdfjs-dist/legacy/build/pdf.mjs does `new DOMMatrix()` at module scope.
Its own polyfill loads @napi-rs/canvas (an optionalDependency), but npx
frequently misses the platform-native binary (npm/cli#4828), so the
server crashed on `npx @modelcontextprotocol/server-pdf --stdio`:
ReferenceError: DOMMatrix is not defined
at pdfjs-dist/legacy/build/pdf.mjs:17078
The server only uses pdfjs for text/metadata/form-field extraction —
never canvas rendering — so no-op stubs suffice and avoid shipping
~130MB of native binaries.
The polyfill is a separate ESM module kept --external in the bun bundle
so it executes before pdfjs's hoisted static import; inlined body code
would run too late.
Broken since 1.3.0 (#506). Thanks to Bryan Thompson for the analysis
and fix shape from the npm-to-mcpb pipeline.
* fix(pdf-server): MCPB bundle missing runtime deps + display_name with spaces
`mcpb pack` zips whatever is on disk; in this monorepo all runtime deps
are hoisted to root node_modules, so the published .mcpb shipped without
pdfjs-dist/ajv/express/etc. and crashed on import in Claude Desktop.
build-mcpb.mjs stages dist/ + manifest into a clean temp dir, runs a
non-workspace `npm install --omit=dev --omit=optional` (the new
DOMMatrix polyfill makes @napi-rs/canvas's ~130MB of native binaries
unnecessary), syncs manifest.version to package.json, then packs.
Result: 12.3MB / 38.8MB unpacked, full MCP handshake + tools/list pass.
Also rename display_name "PDF (By Anthropic)" → "pdf-viewer" — spaces
in display_name break MCP App UI rendering for MCPBs in Claude Desktop.
CI now smoke-tests the extracted bundle starts (`dist/index.js --stdio`
prints "Ready") so a deps-less bundle can't ship again.
* fix(pdf-server): list interact in MCPB manifest tools (was stale since #491)
* fix(readme): use picture element for theme-aware logo
The #gh-light-mode-only / #gh-dark-mode-only URL fragments only work
on github.com. On npm, VS Code preview, and other markdown renderers
both img tags render, showing two stacked logos.
Switch to <picture> with prefers-color-scheme, which GitHub recommends
and which degrades gracefully to a single logo elsewhere.
* ci: add Cloudflare Pages preview for docs PRs
Deploys a per-PR preview of the TypeDoc site to Cloudflare Pages project
mcp-ext-apps-docs-preview when docs, src JSDoc, README, or typedoc config
change. Uses the shared modelcontextprotocol/actions composite action that
injects noindex headers, posts a sticky PR comment with preview URLs, and
cleans up deployments on PR close. Fork PRs are skipped (no secret access).
* fix(docs): sync README logo with TypeDoc theme switcher
The README <picture> logo uses prefers-color-scheme, which only follows
the OS setting. TypeDoc's theme switcher sets data-theme on <html>, so
manually picking Light/Dark would show the wrong logo (e.g. white logo
on light background if OS is dark but user picked Light).
Rewrite the <picture> in the generated docs into two class-tagged <img>s
via the mcpstyle plugin, and hide the inactive one via CSS keyed on both
data-theme (explicit selection) and prefers-color-scheme (OS mode). The
README source is unchanged so GitHub/npm still get <picture>.
Also: prettier normalized mcp-theme.css (tabs to spaces, quote style).
The issue_comment trigger previously ran for any commenter on a PR.
On a public repo this lets drive-by users trigger a job with
contents:write that checks out the PR branch, runs playwright
--update-snapshots, and pushes a [skip ci] commit.
Gate the issue_comment path on author_association being OWNER,
MEMBER or COLLABORATOR. workflow_dispatch is unchanged (already
requires repo write access).
- Switch npm install -> npm ci in ci.yml (build, build-wsl) and docs.yml
to fail on lockfile drift rather than silently tolerating it
- Add cache: npm to all setup-node steps that have a lockfile to hash
(skipped test-git-install which has no checkout, and WSL which has no
setup-node action)
- Add TypeDoc link validation step after build (Linux x64 only, matching
existing single-OS gate pattern for MCPB). Verified passing on main.
- Align all workflows on Node 22 (active LTS) — previously CI used 20
but publish workflows used 22, so releases were tested on a different
Node than PRs
- Add engines.node >=20 to package.json so consumers on Node 18 get a
warning
Pre-commit hook:
- Stage all files modified by prettier:fix, not just src/generated/
CI (GitHub Actions):
- Add prettier-fix job that runs on PRs from the same repo
- Auto-commits formatting fixes back to the PR branch
- Skips fork PRs (no write access to fork branches)
Co-authored-by: Claude <noreply@anthropic.com>
Update all documentation links from `modelcontextprotocol.github.io/ext-apps`
to the new custom domain across README.md, docs/, and example READMEs.
Add `cname` to the `peaceiris/actions-gh-pages` deploy action in
`.github/workflows/docs.yml` so the CNAME file persists across deploys.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add basic-server-preact, basic-server-solid, basic-server-svelte,
basic-server-vue, and debug-server to the publish workflows so they
are included in pkg-pr-new and npm deployments.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Adding a simple (bad) pdf icon for directory (#330)
* chore: bump version to 0.4.2
- Update package version from 0.4.1 to 0.4.2
- Pin @types/node to 22.10.0 for dev compatibility
- Override seroval to ^1.4.1 to fix security vulnerabilities
- Use '*' for workspace SDK dependency (works with unpublished versions)
* fix(e2e): update Map Server display name to CesiumJS Map Server (#324)
* fix(test): update map-server test to expect 'CesiumJS Map Server'
The server name was renamed but the E2E test config was not updated.
* fix: change in another file
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
* feat(server): add getUiCapability helper for capability negotiation (#313)
* feat(server): add hasUiSupport/getUiCapability for experimental+extensions
Support double-tagging for MCP Apps capability negotiation:
- Check both experimental and extensions fields in client capabilities
- Add hasUiSupport() to easily check if client supports MCP Apps
- Add getUiCapability() to retrieve the capability settings
- Update spec to document both capability locations
This enables forward compatibility as MCP transitions from experimental
to the extensions field (SEP-1724).
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0
* fix: update @types/node version in package-lock.json
Replace invalid 22.19.5/22.19.6/22.19.7 versions with valid 22.19.3
* docs(spec): clarify hosts SHOULD double-tag, servers SHOULD check both
- Hosts SHOULD advertise capabilities in both experimental and extensions
- Servers SHOULD check both locations, preferring extensions when present
- Updated example to show double-tagging pattern
* chore: remove double-tagging recommendation, keep hasUiSupport helper
Reverts the spec changes that recommended clients double-tag capabilities
in both experimental and extensions fields. The helper functions remain
as they're useful for checking capability in either location.
* refactor(server): simplify to only getUiCapability, remove hasUiSupport
- Remove hasUiSupport function (use getUiCapability directly)
- Remove double-tagging logic (only check extensions field)
- Add ClientCapabilitiesWithExtensions type using SDK's ClientCapabilities
- Update spec to use getUiCapability
- Simplify tests
* fix: update @see link to getUiCapability
* refactor(server): inline ClientCapabilitiesWithExtensions type in getUiCapability
* refactor: move McpUiClientCapabilities to spec.types.ts
* fix: update playwright docker image to v1.58.0
* fix: use -i instead of -it for docker commands (no TTY needed)
* fix: Python server dependency resolution and Docker host validation (#333)
* fix(say-server): add --index flag to uv run for PyPI resolution
The pocket-tts dependency wasn't resolving without explicit --index flag
when invoked via npm scripts.
* fix(qr-server): allow Docker bridge IP in transport security
Add 172.17.0.1:* to allowed_hosts to fix 421 Misdirected Request errors
when the server is accessed from Docker containers.
* feat(basic-host): Add theme toggle and MCP style variables (#336)
* feat(basic-host): add theme toggle and MCP style variables
Add theme support to basic-host example:
- theme.ts: Global theme manager with light/dark toggle
- host-styles.ts: Full set of MCP CSS variables using light-dark()
- ThemeToggle component with sun/moon icons
- Theme-aware CSS using CSS custom properties
- Pass theme + styles to apps via hostContext
- Notify apps when theme changes via sendHostContextChange
Apps now receive:
- hostContext.theme: 'light' | 'dark'
- hostContext.styles.variables: All 60+ MCP CSS variables
- onhostcontextchanged notifications when theme changes
* test: update e2e goldens for theme changes
* [MCP Apps] Add appCapabilities.availableDisplayModes documentation to spec (#331)
* Add appCapabilities.availableDisplayModes documentation to spec
Document the availableDisplayModes field in McpUiAppCapabilities that allows
apps to declare which display modes they support. This enables hosts to
compute the intersection of host and app capabilities and only offer
appropriate display mode controls.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Address PR review feedback
- Change "Guest UI" to "View" terminology
- Add "View" prefix to availableDisplayModes heading for disambiguation
- Remove intersection logic - host only checks its own supported modes
- Remove assumption that apps must support inline mode
- Simplify host behavior to honor/reject based on its own capabilities
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add appCapabilities.availableDisplayModes to types
- Add `availableDisplayModes` field to `McpUiAppCapabilities` so apps can
declare which display modes they support
- Change `HostContext.availableDisplayModes` type from `string[]` to
`McpUiDisplayMode[]` for type safety
- Regenerate schemas
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Address Olivier's feedback - consolidate display mode docs
- Remove separate "App Capabilities" section for availableDisplayModes
- Add display mode requirements to existing ui/request-display-mode section:
- Host MUST NOT switch View to mode not in appCapabilities.availableDisplayModes
- Host MAY decline requests from Views that didn't declare modes
- View MUST declare supported modes during initialization
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Restore McpUiAppCapabilities docs and fix display mode clause
- Restore McpUiAppCapabilities interface documentation with availableDisplayModes
- Restore "View MUST check if the requested mode is in availableDisplayModes
from host context" clause that was accidentally removed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Create unified Display Modes section
Consolidate all display mode documentation into a single section:
- Display mode types (inline, fullscreen, pip)
- Declaring support from View (appCapabilities.availableDisplayModes)
- Declaring support from Host (HostContext.availableDisplayModes)
- Requesting changes (ui/request-display-mode)
- Notifying changes (ui/notifications/host-context-changed)
- All MUST/SHOULD requirements for View and Host behavior
The ui/request-display-mode in Requests section now references
the Display Modes section for behavior details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove mode coercion clause from display modes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Regenerate schemas with correct version
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(say-server): add light/dark theme support (#338)
- Add importmap entry for main SDK exports (applyDocumentTheme, etc.)
- Import and apply host theme and styles on context changes
- Apply initial host context theme/styles when app connects
- Update CSS to use [data-theme="dark"] selector for host-provided themes
- Keep @media prefers-color-scheme as fallback for standalone mode
- Add color-scheme meta tag for native element theming
* fix: followup app -> view terminology (#339)
* chore: husky env fix, version pins, update-lock:docker script (#341)
* fix(husky): load Node.js env for GUI apps + add update-lock:docker script
Cherry-picked non-e2e changes from ochafik/fix-e2e-flaky-tests:
- Load nvm/fnm/volta/homebrew paths in pre-commit for GitHub Desktop
- Add update-lock:docker script to regenerate lockfile with public registry
* pin package versions for dev compatibility
- solid-js: 1.9.10
- caniuse-lite: 1.0.30001763
- cheerio: 1.1.2
- electron-to-chromium: 1.5.267
- @playwright/test: 1.57.0
- playwright: 1.57.0
- playwright-core: 1.57.0
* regen
* fix: pin @types/node to existing version and regenerate lockfile
- Fix @types/node 20.19.28 -> 20.19.27 (typo, version didn't exist)
- Regenerate package-lock.json to resolve versions available on both
public npm and Anthropic artifactory registries
* fix(basic-server-solid): bump solid-js 1.9.10 -> 1.9.11 to fix seroval vulnerabilities
* fix: pin seroval to 1.4.1 for dev compatibility
* Refactor server startup functions
Rename `startServer` to `startStreamableHTTPServer` and add a new
`startStdioServer` helper to make the transport type explicit. Remove
the `ServerOptions` interface in favor of reading `PORT` directly from
the environment inside the function (defaulting to `3001`).
Also update Python examples to default to port `3001` for consistency.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Support full-file inclusion in synced code snippets
Previously, the script only supported extracting regions from `.ts/.tsx`
files using the `#regionName` syntax. Now it also supports including
entire files without a region specifier, enabling sync of any file type
(JSON, YAML, shell scripts, etc.) into documentation.
Changes:
- Make `#regionName` optional in the `source=""` attribute
- Accept any fence language (not just `ts`/`tsx`)
- Simplify cache to flat map with composite keys
- Add validation error for region extraction on non-TypeScript files
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Rewrite Quickstart guide with type-checked code examples
Rewrites the Quickstart tutorial to provide a more focused, step-by-step
introduction to building MCP Apps. The guide now uses synced code fences
that pull from `examples/quickstart/`, ensuring all code shown in the
documentation is type-checked and tested.
Key improvements:
- Clearer project setup instructions with explicit commands
- Streamlined explanations of the tool + resource registration pattern
- Added screenshot showing the completed app
- E2E test coverage validates the example works end-to-end
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add MCP Apps Overview document
Adds `docs/overview.md` covering the core concepts: architecture,
lifecycle, UI resources, bidirectional communication, theming, display
modes, and security. Includes mermaid flowchart and sequence diagrams.
Also adds the `@boneskull/typedoc-plugin-mermaid` TypeDoc plugin for
mermaid diagram support. However, the plugin handles HTML entities from
TypeDoc incorrectly, so `scripts/typedoc-plugin-fix-mermaid-entities.mjs`
decodes HTML entities before the plugin processes them.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Agent Skills installation guide
Add `docs/agent-skills.md` with installation instructions for multiple AI
coding agents including Claude Code, VS Code/Copilot, Gemini CLI, Cline,
and Goose. The guide provides three installation methods: Claude Code
plugin, Vercel Skills CLI (`npx skills add`), and manual installation.
Update `README.md` to link to the new guide and reframe the plugin
section around the broader "Agent Skills" concept.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Polish OpenAI migration reference for readability
Add framing prose to the migration guide to improve navigation:
- Expanded intro describing document purpose and how to use it
- Added note about features marked "Not yet implemented"
- Added brief transition sentences before Server-Side and Client-Side sections
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix mermaid diagram arrowheads not rendering
The `@boneskull/typedoc-plugin-mermaid` creates both dark and light theme
variants of each diagram, each with identical marker IDs (e.g., `#arrowhead`).
When mermaid renders SVGs at runtime, lines reference `url(#arrowhead)`, but
the browser resolves this to the first match in document order — the hidden
dark-theme SVG — causing arrows to be invisible.
The fix removes dark-theme `<div class="mermaid dark">` elements at build time,
leaving only one SVG per diagram with unique marker IDs. A CSS filter
(`filter: invert(1) hue-rotate(180deg)`) handles dark mode styling instead.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(docs): improve OpenAI migration guide accuracy (#349)
- Add missing setOpenInAppUrl API to 'Not Yet in MCP Apps' section
- Clarify Event Handling table: specify exact OpenAI properties
(toolInput/toolOutput) instead of vague 'window.openai.*'
- Improve notes to distinguish sync property reads vs callbacks
* Add Testing MCP Apps guide
Document how to test MCP Apps using `basic-host` and MCP Apps-compatible
hosts like Claude.ai and VS Code Copilot. Cover debugging features for
`basic-host`, and using `cloudflared` to expose local servers for remote
hosts.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix `onhostcontextchanged` handler invocation in examples
`App.onhostcontextchanged` is a setter-only property with no getter.
TypeScript doesn't prevent reading from setter-only properties, so
`app.onhostcontextchanged?.(ctx)` compiles but silently returns
`undefined` at runtime.
Extract the handler into a named `handleHostContextChanged()` function
and call it directly after `connect()`, matching the pattern used in
`basic-server-vanillajs` and other examples.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Expand Agent Skills guide
Add intro explaining what Agent Skills are and link to the two skill
files (`create-mcp-app`, `migrate-oai-app`). Reorganize headings and add
"Invoke a Skill" and "Test Your App" sections. Update `README.md` link
text and add tip to quickstart suggesting agent skills as an alternative.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(e2e): improve test stability and developer experience (#337)
* fix(e2e): add masks for dynamic content and increase timeouts for slow servers
- Add HOST_MASKS for Tool Input/Result panels with dynamic timestamps
- Add say-server to SLOW_SERVERS (10s) for TTS model download
- Add say-server to EXTRA_WAIT_MS (30s) in generate-grid-screenshots
* feat: add update-lock:docker script to regenerate lockfile with public registry
* pin package versions for dev compatibility
* feat(basic-host): add ?theme=hide query param to hide theme toggle in screenshots
- Add hideThemeToggle param to getQueryParams()
- Conditionally render ThemeToggle based on ?theme=hide
- Update e2e tests to use ?theme=hide for consistent screenshots
* update screenshots
* update screenshots
* fix(husky): load Node.js environment for GUI apps like GitHub Desktop
* fix(e2e): increase map-server wait time to 15s for tile loading
* fix(e2e): increase waitForAppLoad timeout to 30s for nested iframe loading
* ci: add workflow to update e2e snapshots
* ci: trigger snapshot update on push to this branch
* chore: update e2e snapshots [skip ci]
* ci: trigger CI after snapshot update
* style: fix prettier formatting in update-snapshots workflow
* fix: pin seroval, seroval-plugins, and solid-js for dev compatibility
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* Improve README structure and clarity
Add "Why MCP Apps?" section to explain the motivation upfront. Reorganize
the "How It Works" section to lead with tool definition. Rename "Overview"
to "Using the SDK" and add "For" prefix to audience headings.
Restructure the Examples section with clearer subsections for running
examples with `basic-host` vs MCP clients using stdio transport. Move the
`qr` server note outside the collapsed details block.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Codex and Goose to documentation
Add Codex to the list of agents supporting skills in `agent-skills.md`.
Add Goose to the list of supported clients in `testing-mcp-apps.md` and
rename "VS Code Copilot" to "VS Code (Insiders)" for accuracy.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: regenerate lockfile with public registry, use ^0.4.2 for ext-apps deps
* fix: update quickstart example to use workspace reference
* chore: remove internal registry references from lockfile
* feat(ci): add /update-snapshots comment trigger for PRs
- Comment '/update-snapshots' on any PR to update E2E snapshots
- Adds rocket reaction to acknowledge the command
- Comments back with result (updated or no changes needed)
- Removes hardcoded branch trigger
- Keeps workflow_dispatch for manual runs
* docs: document /update-snapshots PR comment trigger
* fix: pin @hono/node-server to 1.19.7 for dev compatibility
SDK 1.25.3 requires @hono/node-server@^1.19.9 which doesn't exist
on the public npm registry (latest is 1.19.7).
* fix: pin @modelcontextprotocol/sdk to 1.25.2
SDK 1.25.3 has broken dependency on @hono/node-server@^1.19.9
which doesn't exist on public npm registry.
* fix: restore flexible SDK peerDep range (^1.24.0)
devDependencies stays pinned to 1.25.2 for our builds,
but peerDependencies should allow users flexibility.
* fix: pin @types/node to avoid unpublished versions
- Pin quickstart @types/node to 22.19.5 (latest public 22.x)
- Add @types/node override to root package.json
- Regenerate lockfile with public registry only
---------
Co-authored-by: Tobin South <tobin.south@gmail.com>
Co-authored-by: Liad Yosef <liady@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: martinalong <martinalong10@gmail.com>
Co-authored-by: Ido Salomon <idosalomon@gmail.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* fix(e2e): add masks for dynamic content and increase timeouts for slow servers
- Add HOST_MASKS for Tool Input/Result panels with dynamic timestamps
- Add say-server to SLOW_SERVERS (10s) for TTS model download
- Add say-server to EXTRA_WAIT_MS (30s) in generate-grid-screenshots
* feat: add update-lock:docker script to regenerate lockfile with public registry
* pin package versions for dev compatibility
* feat(basic-host): add ?theme=hide query param to hide theme toggle in screenshots
- Add hideThemeToggle param to getQueryParams()
- Conditionally render ThemeToggle based on ?theme=hide
- Update e2e tests to use ?theme=hide for consistent screenshots
* update screenshots
* update screenshots
* fix(husky): load Node.js environment for GUI apps like GitHub Desktop
* fix(e2e): increase map-server wait time to 15s for tile loading
* fix(e2e): increase waitForAppLoad timeout to 30s for nested iframe loading
* ci: add workflow to update e2e snapshots
* ci: trigger snapshot update on push to this branch
* chore: update e2e snapshots [skip ci]
* ci: trigger CI after snapshot update
* style: fix prettier formatting in update-snapshots workflow
* fix: pin seroval, seroval-plugins, and solid-js for artifactory compatibility
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Migrate JSDoc and markdown documentation from `{@includeCode ./file#region}`
tags to inline code fences with `source="./file#region"` attributes.
Code examples are now visible directly in source files while remaining
type-checked via their `.examples.ts` source files.
Add `scripts/sync-snippets.ts` to extract code from `//#region` blocks
and sync it into labeled fences. Run with `npm run sync:snippets`.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace requirements.txt with inline script dependencies in server.py
- Add package.json with npm scripts for run-all.ts integration
- Update CI workflow to install uv via astral-sh/setup-uv@v5
- Update Docker commands to install uv for e2e tests
- Add Playwright golden screenshot for qr-server
- Update README with uv installation and usage instructions
* feat(pdf-server): Interactive PDF viewer example
PDF viewer with PDF.js featuring:
- Chunked binary loading with progress bar
- Text extraction for AI context
- arXiv paper support (fetch by ID)
- Page navigation with keyboard shortcuts
- Zoom controls (including Ctrl+0 reset)
- Fullscreen mode support
- Horizontal swipe for page changes (disabled when zoomed)
- Page persistence in localStorage
- Text selection via PDF.js TextLayer
- Clickable title link to source URL
- Rounded corners and subtle border styling
* chore: Add pdf-server to screenshot generation list
* refactor(pdf-server): Simplify and generalize PDF loading
- Accept any HTTP(s) URLs instead of ArXiv-only
- Use HTTP Range requests for chunked binary loading
- Remove ArXiv-specific code (arxiv.ts, metadata fetching)
- Remove CLAUDE.md index generation
- Flatten hierarchical folder structure to simple entries list
- Remove dead code: getPdfSummary, httpFileSizes
- Simplify base64 encoding using Buffer
- Simplify chunk extraction using slice()
- Consolidate DEFAULT_PDF_URL constant
The server now works with any PDF URL, not just arXiv papers.
HTTP Range requests stream chunks on-demand when supported.
* feat(pdf-server): Include title and selection in model context
- Add pdfTitle to updateModelContext structuredContent
- Include selection position (text, start, end) when text is selected
- Add debounced selectionchange listener to update context on selection
* fix(pdf-server): Restore default URL in view_pdf schema
The UI needs the default value in the schema to show it properly.
* refactor(pdf-server): Further simplifications
- Remove hard-coded test paths from main()
- Remove unused resources: pdfs://metadata/{pdfId}, pdfs://content/{pdfId}
- Remove unused metadata fields: subject, creator, producer, creationDate, modDate
- Remove unused entry fields: relativePath, estimatedTextSize
- Remove filterEntriesByFolder and folder filter from list_pdfs
- Remove redundant output schema validation (trust typed returns)
- Simplify scanDirectory and createLocalEntry signatures
Total: 1836 → 1666 lines (-170 lines, -9%)
* refactor(pdf-server): Major simplification for didactic focus
Simplified the example to focus on key MCP Apps SDK patterns:
- Chunked data through size-limited tool calls
- Model context updates (page text + selection)
- Display modes (fullscreen vs inline)
- External links (openLink)
Changes:
- Remove local file support (HTTP URLs only)
- Restrict dynamic URLs to arxiv.org for security
- Simplify types: url instead of sourcePath/sourceType
- Simplify indexer: 168 → 44 lines
- Simplify loader: 318 → 171 lines
- Simplify server: 337 → 233 lines
- Fix selection text normalization
- Rewrite README with didactic focus
Total: 1836 → 1236 lines (-33%)
* feat(pdf-server): Add file:// URL support for local files
- Local paths are converted to file:// URLs on startup
- file:// URLs must be in the initial list (strict validation)
- Dynamic URLs still restricted to arxiv.org only
- Updated README with local file examples
* fix(pdf-server): Improve selection detection with logging
- Add logging to selectionchange handler to verify it fires
- Add fallback matching without spaces (TextLayer spans may lack spaces)
- Log selection detection success/failure for debugging
The issue: PDF.js TextLayer renders text as positioned spans without
space characters between them. When selecting across spans:
- pageText has spaces (items joined with ' ')
- sel.toString() may not have spaces
- indexOf fails to match
The fix tries exact match first, then falls back to spaceless matching.
* feat(pdf-server): Format model context as markdown with front matter
Model context now looks like:
```markdown
---
url: https://arxiv.org/pdf/...
page: 5/144
---
Page text with <pdf-selection>selected text</pdf-selection> inline.
```
This is cleaner for the model to parse and includes the source URL.
* refactor(pdf-server): Extract smart truncation helpers
Added two well-designed helpers:
formatPageContent(text, maxLength, selection?)
- Centers truncation window around selection if present
- Adds <truncated-content/> markers at elision points
- Wraps selection in <pdf-selection> tags
- Allocates 60% context before, 40% after for readability
findSelectionInText(pageText, selectedText)
- Tries exact match first
- Falls back to spaceless match for TextLayer quirks
- Returns { start, end } or undefined
Example output with selection:
```
<truncated-content/>
...context before... <pdf-selection>selected text</pdf-selection> ...context after...
<truncated-content/>
```
* fix(pdf-server): Truncate inside selection tags when selection too long
When selection is too large for the budget:
<truncated-content/><pdf-selection><truncated-content/>start...end<truncated-content/></pdf-selection><truncated-content/>
This keeps the selection structure intact while showing beginning and end.
* refactor(pdf-server): Remove unused read_pdf_text, use Attention paper as default
- Remove read_pdf_text tool (viewer extracts text client-side with pdfjs)
- Remove PdfTextChunk and ReadPdfTextInput types
- Remove loadPdfTextChunk from pdf-loader
- Change default PDF to 'Attention Is All You Need' (1706.03762)
- Update README with modest language
* refactor(pdf-server): Simplify to use URL as ID, rename view_pdf to display_pdf
Major simplifications:
- Use URL directly as identifier (no hashing)
- Remove displayName - show elided URL with full URL as tooltip
- Rename view_pdf to display_pdf with better description
- Update all references from pdfId to url
- Simplify storage key and model context
The tool description now explains it displays an interactive viewer in the chat.
* feat(pdf-server): Normalize arxiv URLs to PDF format
arxiv.org/abs/... -> arxiv.org/pdf/...
Applied both at startup and when loading dynamic URLs.
* docs(pdf-server): Add prompt engineering to display_pdf description
* fix(pdf-server): Sharp rendering on retina displays
Account for devicePixelRatio when rendering canvas:
- Scale canvas dimensions by dpr
- Scale context by dpr
- Keep CSS size at logical pixels
* fix(pdf-server): Normalize arxiv URLs in read_pdf_bytes too
* add to e2e spec
* add to e2e spec
* add to e2e spec
* add to e2e spec
* regen
* chore: regenerate package-lock.json and fix hono vulnerability
* docs: add pdf-server screenshot to READMEs
* regen
* ci: add missing examples to pkg-pr-new publish
* ci: add pdf-server to npm publish examples
* Update README.md
* pdf-server: improve tool response text for better model context
* revert unrelated screenshot changes
* pdf-server: dynamically add arxiv URLs in read_pdf_bytes
Fixes 'PDF not found' error when server restarts between display_pdf
(which adds the entry) and read_pdf_bytes (which previously only looked
up existing entries). Now read_pdf_bytes mirrors display_pdf's logic
and dynamically adds arxiv URLs to the index.
* chore: release 0.4.0
* chore: add new examples to npm publish workflow
Add map-server, shadertoy-server, and transcript-server to the
publish-examples matrix.
* fix: ensure lib is built before examples:build
Add preexamples:build hook to automatically run `npm run build` before
`examples:build`. This ensures examples always type-check against the
latest library code, catching breaking changes during development.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: add version consistency check for examples
Add scripts/check-versions.mjs to verify that example package.json files
reference the correct version of @modelcontextprotocol/ext-apps.
- Allows "../.." (local dev) or "^{root-version}" as valid dependencies
- Runs in CI before npm install to catch mismatches early
- Available locally via `npm run check:versions`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: regenerate package-lock.json for cross-platform compatibility
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: publish example servers to npm
Make example MCP App servers publishable to npm under the
@modelcontextprotocol scope:
- server-basic-react
- server-basic-vanillajs
- server-budget-allocator
- server-cohort-heatmap
- server-customer-segmentation
- server-scenario-modeler
- server-system-monitor
- server-threejs
- server-wiki-explorer
Changes:
- Remove `private: true` from example package.json files
- Add proper npm metadata (description, repository, license, files)
- Update ext-apps dependency from relative to ^0.2.2
- Copy server-utils.ts into each package for standalone use
- Add publish-examples job to npm-publish.yml workflow
The examples will be published automatically when a GitHub release
is created, after the main SDK package is published.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: publish examples with pkg-pr-new for PR testing
* feat: export createServer from example servers
* style: format integration-server/server.ts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: update e2e snapshots for basic-server examples
Update golden screenshots for basic-react and basic-vanillajs after
the tool output was simplified from JSON to plain text in #182.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* merge
* refactor: update sheet-music-server and video-resource-server to new publishing pattern
- Move server-utils.ts from src/ to root level
- Add npm publishing metadata (scoped name, repository, license, files)
- Change ext-apps dependency from local to versioned ^0.2.2
- Update server.ts to handle stdio mode inline
* update package-lock
* fix: update package-lock.json for renamed server packages
* feat: add NASA public domain video to video-resource-server
Added 'nasa-earth' option - NASA ISS Earth observations (6.4MB).
Source: archive.org/details/NASA-Ultra-High-Definition (public domain)
* fix: add default videoId for video-resource-server
* fix: use existing bunny-1mb as default video, remove nasa-earth
* refactor: update new framework examples to publishing pattern
Add publishing metadata for:
- @modelcontextprotocol/server-basic-preact
- @modelcontextprotocol/server-basic-solid
- @modelcontextprotocol/server-basic-svelte
- @modelcontextprotocol/server-basic-vue
Changes:
- Move server-utils.ts from src/ to root
- Add npm publishing metadata
- Update ext-apps dep to versioned ^0.2.2
- Add stdio mode support in main()
* fix: add missing @rollup/rollup-win32-arm64-msvc to optionalDependencies
This fixes the Windows ARM64 CI build failure caused by npm's optional
dependency bug. The package.json was missing this platform-specific
rollup binding.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
* Update `package.json`
* Add TypeScript type checking to example build scripts
Add `tsc --noEmit &&` before `vite build` in all example `package.json`
files to catch type errors during builds. Previously, type errors
in `server.ts` files were not detected because Vite/Bun transpile
without type checking.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Make `_meta` optional in `McpUiAppResourceConfig`
The `_meta` and `ui` properties were required, causing TypeScript errors
when calling `registerAppResource()` without providing metadata.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix Zod type compatibility in `registerAppTool()`
Use `ZodRawShapeCompat | AnySchema` from SDK's `zod-compat` module
instead of raw `ZodRawShape` to resolve type errors when examples
pass `ZodObject` schemas to `registerAppTool()`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove unused imports in example apps
- `basic-server-react`: Remove unused `McpUiResourceTeardownResult`
- `customer-segmentation-server`: Remove unused `PostMessageTransport`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix CI git install test for fork PRs
Use `github.event.pull_request.head.repo.full_name` to correctly
reference the fork repository when testing git installs from PRs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: Windows compatibility for npm scripts and cross-platform CI
Fixes#142 - Windows PowerShell install bug
Changes:
- Replace single quotes with escaped double quotes in generate:schemas
(cmd.exe passes single quotes literally, breaking prettier glob matching)
- Simplify prettier scripts: remove redundant --ignore-path flags
(prettier auto-loads .gitignore and .prettierignore by default)
- Remove $(pwd) from prettier scripts (bash-only syntax)
- Add cross-platform CI matrix: Linux x64, Windows x64, macOS ARM64, macOS x64
- Use shell: bash for grep commands in CI (not available in Windows cmd.exe)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: remove bun npm package from all workspaces
The bun npm package tries to install platform-specific binaries which
fails on Windows CI. Since bun is installed via oven-sh/setup-bun action
in CI (and developers install it globally), we don't need the npm package.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: skip examples:build on Windows (uses Unix-only INPUT=... syntax)
The examples use `INPUT=mcp-app.html vite build` which is bash syntax
for setting environment variables. This doesn't work on Windows cmd.exe.
Since examples aren't published to npm, we can skip building them on Windows.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: force LF line endings for cross-platform prettier consistency
Git on Windows auto-converts line endings to CRLF, causing prettier to
report formatting issues. Force LF for all text files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: add test for git-based npm install (cross-platform)
This verifies that `npm install git+https://...` works on all platforms,
which triggers the prepare script and requires bun to be available.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: add Linux ARM64 and Windows ARM64 to CI matrix
- Added ubuntu-24.04-arm (Linux ARM64)
- Added windows-11-arm (Windows ARM64)
Note: WSL is not available on GitHub-hosted runners (requires nested
virtualization which isn't supported).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: disable Windows ARM64 (Bun lacks stable ARM64 binaries)
The oven-sh/setup-bun action fails with 404 when trying to download
Bun for Windows ARM64. Re-enable when Bun adds proper support.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: add Windows WSL build test
Uses Vampire/setup-wsl action to install Ubuntu-24.04 in WSL and run
the full build/test cycle. This tests the Linux-in-Windows scenario
that was reported in issue #142.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: fix WSL build - install unzip for Bun installer
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: add setup-bun to publish.yml workflow
The pkg-pr-new publish workflow was missing bun setup, causing
"bun: not found" errors during npm ci (which triggers prepare script).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: enable Windows ARM64 with x64-baseline Bun (via emulation)
Windows ARM64 can run x64 binaries via emulation. Use bun-download-url
to force x64-baseline build since native ARM64 Bun isn't available yet.
Reference: https://github.com/oven-sh/bun/issues/9824🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: remove macOS x64 (macos-13 runner retired)
The macos-13 based runner images are now retired.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: add Bun installation instructions per platform
Documents that Bun is required for development, with platform-specific
install commands in collapsible sections.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: note Bun requirement in README examples section
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: add bun as optional dependency (no global install required)
- Add @oven/bun-* platform packages as optionalDependencies
- Create postinstall script to set up bun binary from optional deps
- Handle Windows ARM64 by downloading x64-baseline for emulation
- Remove setup-bun from CI workflows (npm install handles it now)
- Remove manual Bun install instructions from docs
This alternative approach eliminates the need for global Bun installation
or setup-bun action in CI. npm install automatically installs the
platform-appropriate bun binary.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: add debug logging to setup-bun script
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: force stdout flush in setup-bun for CI debugging
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: add more debugging to setup-bun
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: remove accidental test artifacts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: run setup-bun in prepare script (before build)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: use cross-env for Windows-compatible example builds
- Add cross-env to devDependencies
- Update all example package.json scripts to use cross-env for INPUT and NODE_ENV
- Enable examples:build on Windows in CI (no longer Unix-only)
- Add examples:build to WSL CI job
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: remove unused imports in setup-bun.mjs
Address github-code-quality bot comments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: use run-bun.mjs wrapper for prepare hook compatibility
During npm install's prepare hook, node_modules/.bin is not in PATH,
so calling 'bun' directly fails. The new run-bun.mjs wrapper finds
bun at node_modules/.bin/bun and runs it with the correct path.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: restore setup-bun action for build jobs
- Add setup-bun action to build and test-git-install jobs
- Disable Windows ARM64 (Bun doesn't support this platform)
- Add bun installation to WSL build
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: remove bun devDep, add libc detection for Linux ARM64
- Remove 'bun' from devDependencies (its postinstall fails on Windows ARM64)
- Add libc detection (glibc vs musl) to setup-bun.mjs
- Only try compatible binaries based on detected libc
- Re-enable Windows ARM64 in CI (now uses x64 emulation fallback)
- Remove setup-bun action from CI (no longer needed)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: simplify WSL build - let setup-bun.mjs handle bun installation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Add Playwright E2E tests with screenshot golden testing
- Add E2E tests for all 8 MCP server examples
- Screenshot golden images for visual regression testing
- CI workflow for running E2E tests
- npm scripts: test:e2e, test:e2e:update, test:e2e:ui
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add default demo code for Three.js example
When no code is provided, show a rotating green cube demo.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add E2E testing documentation to CONTRIBUTING.md
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Update Three.js golden screenshot with 3D cube
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add explicit permissions to CI workflow
Set minimal read-only permissions for security best practices.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix test.setTimeout() to be inside describe blocks
Playwright requires setTimeout to be called within a test or describe block.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Simplify E2E tests - remove excessive timeouts
- Remove explicit setTimeout calls (default 30s is sufficient)
- Replace waitForTimeout(5000/6000) with proper waitForAppLoad()
- Wait for inner iframe visibility instead of fixed delays
- Keep only 500ms stabilization for screenshot animations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Auto-generate missing snapshots in CI
Use updateSnapshots: 'missing' in CI to handle cross-platform
screenshot differences (macOS vs Linux).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Use platform-agnostic golden screenshots
- Remove -chromium-darwin suffix from snapshot filenames
- Configure snapshotPathTemplate for cross-platform compatibility
- Increase tolerance to 5% for rendering differences between platforms
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Refactor tests to use forEach instead of for-of loop
May help with Playwright test discovery issues in CI.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Use npm ci and list reporter in CI
- Use npm ci for exact package versions
- Use --reporter=list instead of html to avoid potential re-evaluation issues
- Only upload test-results on failure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Exclude e2e tests from bun test
Run bun test only on src/ directory to avoid running Playwright tests
with Bun's test runner.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix prettier formatting
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add interaction tests for basic server apps
Test that clicking buttons in the MCP App triggers the corresponding
host callbacks:
- Send Message → host logs "Message from MCP App"
- Send Log → host logs "Log message from MCP App"
- Open Link → host logs "Open link request"
Tests both React and Vanilla JS implementations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Mask dynamic content in E2E screenshot tests
Add Playwright mask option to handle servers with dynamic/random content:
- basic-react/vanillajs: mask server time display
- system-monitor: mask CPU chart, memory stats, uptime
- cohort-heatmap: mask heatmap grid (random data)
- customer-segmentation: mask scatter chart (random data)
This addresses PR feedback about handling examples with non-deterministic
output. Masking replaces the previous 10% tolerance with proper exclusion
of dynamic elements, allowing tighter 1% tolerance for the rest of the UI.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add wiki-explorer to E2E tests with default URL param
- Add default URL "https://en.wikipedia.org/wiki/Model_Context_Protocol" to
wiki-explorer-server's get-first-degree-links tool inputSchema
- Update basic-host to automatically populate input field with tool defaults
extracted from inputSchema.properties
- Add wiki-explorer-server to E2E test suite with dynamic masking for the
force-directed graph
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Use .default() for threejs tool schema instead of .optional()
This exposes the default code and height values in the JSON Schema,
allowing basic-host to auto-populate the input field with defaults.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Enable parallel E2E tests with timeouts and canvas masking
- Enable fullyParallel with 4 workers locally (2 in CI)
- Add 30s timeout per test
- Mask threejs canvas for stable screenshots
- Update snapshots to reflect default values in input fields
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add pre-commit check for private registry URLs in package-lock.json
Same check as in CI to catch issues before push.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Increase screenshot diff tolerance to 6% for cross-platform rendering
Font rendering differs between macOS and Linux, causing ~5% pixel differences.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Revert pre-commit artifactory check (moved to separate PR #133)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Format threejs server.ts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Update CONTRIBUTING.md to reflect platform-agnostic screenshots
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(e2e): use factory pattern for MCP servers to support parallel connections
McpServer only supports one transport at a time. When multiple browser
contexts connected in parallel, calling server.connect(transport) for
each session overwrote the previous transport's callbacks, causing
connection corruption and test timeouts.
Changes:
- server-utils.ts: accept factory function instead of single instance
- All 9 example servers: wrap server creation in createServer() function
- playwright.config.ts: re-enable parallel execution (4 workers)
- servers.spec.ts: add toBeEnabled wait for server connection
- Update screenshot baselines for basic-vanillajs and cohort-heatmap
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: prettify generated files on the fly
- Add prettier --write to generate:schemas script to format generated
files immediately after generation
- Remove redundant prettier:fix from CI schema verification step
(now that generation includes formatting)
This fixes the release CI failure where generated files weren't
formatted. Previously, PR CI masked this by running prettier:fix
before the final prettier check, while release CI ran the check
directly on freshly-generated (unformatted) files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* format
---------
Co-authored-by: Claude <noreply@anthropic.com>
Replace `force_orphan` with `keep_files` in `peaceiris/actions-gh-pages`
to prevent wiping manually-added files (like screenshots) when deploying
generated documentation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: add ts-to-zod schema generation from spec.types.ts
Add automated Zod schema generation using ts-to-zod:
- New npm script `generate:schemas` to regenerate schemas from spec.types.ts
- Post-processing script for Zod v4 compatibility and MCP SDK type imports
- src/spec.types.ts: Pure TypeScript interface definitions (source of truth)
- src/schemas.generated.ts: Auto-generated Zod schemas
- src/schemas.ts: Re-exports with PascalCase naming for backwards compatibility
The generated schemas provide a simpler alternative to the hand-written schemas
in types.ts, useful for lighter validation or reference implementations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: types.ts now re-exports from spec.types.ts and schemas.generated.ts
- types.ts imports and re-exports types from spec.types.ts (source of truth)
- types.ts imports and re-exports schemas from schemas.generated.ts
- Added compile-time VerifySchemaMatches checks to flag mismatches between
interfaces and generated schemas
- Removed redundant schemas.ts (types.ts now handles all re-exports)
- Applied prettier formatting to schemas.generated.ts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: use ts-to-zod as library with integration tests
Replace CLI-based schema generation with library API:
- scripts/generate-schemas.ts: New script using ts-to-zod library API
- Detailed documentation of why each post-processing step is needed
- Generates integration tests via getIntegrationTestFile()
- Better error reporting and control
- src/schemas.generated.test.ts: Auto-generated integration tests
- Compile-time verification that schemas match TypeScript types
- Uses expectType<> pattern for bidirectional type checking
- Removed scripts/postprocess-schemas.ts (merged into generate-schemas.ts)
- Fixed McpUiResourceTeardownResult to include index signature for MCP SDK
Protocol compatibility (detected by the new integration tests)
Post-processing rationale (documented in generate-schemas.ts):
1. zod → zod/v4: Explicit v4 subpath for version clarity
2. z.any() → MCP SDK schemas: External types need real validation
3. z.record().and() → z.looseObject(): Index signatures need Zod v4 idiom
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: regenerate schemas during build + CI check
- build script now runs generate:schemas before bun build
- CI workflow verifies generated schemas are up-to-date
(fails if spec.types.ts changed but schemas weren't regenerated)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: generate PascalCase schemas, move verification to tests
- Add getSchemaName option to generate PascalCase schema names directly
(McpUiOpenLinkRequestSchema instead of mcpUiOpenLinkRequestSchema)
- Remove redundant compile-time verification from types.ts
(already handled by generated test file's bidirectional expectType checks)
- Simplify types.ts from 235 to 57 lines (just re-exports)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: add missing csp field, simplify schema generation
- Add missing csp field to McpUiSandboxResourceReadyNotification (regression from merge)
- Simplify generate-schemas.ts - remove complex .describe() conversion
- Document that ts-to-zod supports @description JSDoc tags for .describe()
- Improve z.looseObject() replacement with brace-counting for nested objects
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: add @description JSDoc tags for .describe() on schemas
Added @description tags to all interfaces and fields in spec.types.ts.
ts-to-zod now generates .describe() calls on all schema fields, making
descriptions available at runtime for introspection and JSON Schema generation.
Example:
/** @description URL to open in the host's browser */
url: string;
Generates:
url: z.string().describe("URL to open in the host's browser")
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: generate JSON Schema from Zod schemas
Added JSON Schema generation to the schema generation pipeline:
- Uses Zod v4's built-in toJSONSchema() to convert schemas
- Generates src/schema.json with all message types in $defs
- Includes descriptions from @description JSDoc tags
- Added export "./schema.json" to package.json
- Updated CI to verify schema.json is up-to-date
Use cases:
- Language-agnostic validation (Python, Go, Rust)
- Documentation generation (Redoc, Swagger UI)
- Code generation (quicktype)
- IDE autocomplete for JSON/YAML
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: rename schema.json to schema.generated.json
Renamed to match other generated files naming convention.
Export path unchanged: ./schema.json -> ./dist/src/schema.generated.json
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: rename schemas.generated.* to schema.generated.* (singular)
Consistent singular naming for all generated files:
- schema.generated.ts
- schema.generated.test.ts
- schema.generated.json
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: rename schemas.generated.* to schema.generated.* (singular)
Consistent singular naming for all generated files:
- schema.generated.ts
- schema.generated.test.ts
- schema.generated.json
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: move generated files to src/generated/ with simpler names
Moved:
- src/schema.generated.ts → src/generated/schema.ts
- src/schema.generated.test.ts → src/generated/schema.test.ts
- src/schema.generated.json → src/generated/schema.json
Benefits:
- Cleaner organization (generated files isolated)
- Simpler names without .generated suffix
- Easier to gitignore if desired later
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: add missing type exports for backwards compatibility
Added missing exports that existed in main:
- McpUiTheme, McpUiThemeSchema
- McpUiDisplayMode, McpUiDisplayModeSchema
- McpUiResourceCsp, McpUiResourceCspSchema
- McpUiResourceMeta, McpUiResourceMetaSchema
Also fixed theme/displayMode to match original values:
- theme: 'light' | 'dark' (removed 'system')
- displayMode: 'inline' | 'fullscreen' | 'pip' (removed 'carousel')
Verified: all 47 exports from main are preserved with compatible types.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: stage generated files after prettier in pre-commit hook
The pre-commit hook runs build (which generates schemas) then prettier
(which formats them). The formatted generated files weren't being staged,
causing CI to fail with 'Generated schemas are out of date'.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* update generated files
* fix: run prettier after regenerating schemas in CI verification
CI was comparing unformatted generated output against formatted committed
files, causing false 'out of date' failures.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Automatically publish documentation when a new release is created,
ensuring docs stay in sync with published package versions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add npm publish workflow
- Add .github/workflows/npm-publish.yml triggered on GitHub releases
- Runs build and test jobs before publishing
- Uses NPM_TOKEN secret with OIDC provenance
- Smart npm tagging: latest, beta, or release-X.Y
- Add prepack hook to package.json for automatic builds
- Move bun from dependencies to devDependencies
- Add npm test step to CI workflow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add CONTRIBUTING.md with maintainer publishing guide
- Getting started and development process for contributors
- Pull request guidelines
- Maintainer section with:
- Repository setup (NPM_TOKEN, release environment)
- Step-by-step release publishing instructions
- npm tag documentation (latest, beta, release-X.Y)
- Maintenance release workflow
- Pre-release testing via pkg-pr-new
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* ...
* Potential fix for code scanning alert no. 9: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* Fix compatibility with @modelcontextprotocol/sdk 1.23+
Implement required abstract methods `assertTaskCapability` and
`assertTaskHandlerCapability` in App and AppBridge classes. These
were added to the Protocol base class in SDK 1.23+.
Also regenerates package-lock.json with valid npm registry versions
(previously had non-existent vite@6.4.1).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add pkg-pr-new workflow for PR/commit publishing
Enables automatic publishing of any commit or PR to pkg.pr.new,
allowing consumers to install directly from git commits:
npm install https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/ext-apps@<commit-or-pr>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Introduce new example implementations to replace simple-host and
simple-server:
- basic-host: Reference host implementation with double-iframe sandbox
pattern for secure UI isolation (outer iframe validates and relays
messages, inner iframe renders untrusted tool UI via srcdoc)
- basic-server-react: MCP server example demonstrating tool registration
with linked UI resources and React UI using the useApp() hook
Build infrastructure updates:
- Add build:all script and simplify CI/pre-commit to use it
- Add examples:dev for watch-mode development workflow
Also change PostMessageTransport logging from info to debug level.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Track package-lock.json for deterministic builds
- Remove package-lock.json from .gitignore
- Add CI check to verify no private registry URLs in package-lock.json
- Add fresh package-lock.json from registry.npmjs.org
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Mark package-lock.json as generated in .gitattributes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Refactor types.ts to use explicit interfaces with JSDoc
Replace z.infer<typeof Schema> patterns with explicit interface
definitions for all MCP UI types. Add VerifySchemaMatches helper
type to ensure compile-time verification that schemas match their
interfaces.
Add comprehensive JSDoc documentation to all exported types including:
- Clear descriptions of purpose and usage
- @see references to related types and methods
- Code examples where helpful
- @internal markers for implementation details
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add JSDoc documentation and TypeDoc tooling
Add comprehensive JSDoc documentation to:
- AppBridge class and all methods
- App class and all methods
- PostMessageTransport class and callbacks
- React hooks (useApp, useAutoResize)
- React module index
Set up TypeDoc for API documentation generation:
- Add typedoc.json configuration
- Add docs and docs:watch npm scripts
- Add typedoc dev dependency
- Update .gitignore to exclude generated docs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add JSDoc documentation for callback handler setters
Document all notification and request handler setters in AppBridge
and App classes with comprehensive JSDoc comments including:
- Clear descriptions of when handlers are called
- Parameter documentation and types
- Practical code examples with error handling
- Cross-references to related types and methods
- Security considerations where relevant
Also update types.ts to document optional isError fields in result
types and make McpUiInitializedNotification params optional.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add JSDoc for new App methods and fix result type compatibility
Document new public APIs introduced in merge commit 74a7b1f:
- getHostCapabilities() - getter for host capabilities after connect
- getHostVersion() - getter for host implementation info after connect
- oncalltool setter - convenience handler for tool call requests
- onlisttools setter - convenience handler for tool listing requests
- RESOURCE_URI_META_KEY constant - metadata key for resource URIs
Add index signatures to McpUiMessageResult and McpUiOpenLinkResult
for MCP SDK Protocol class compatibility, matching the existing
pattern in McpUiInitializeResult.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add comprehensive JSDoc documentation across library
- Document App methods with detailed descriptions and examples
- Add JSDoc to AppBridge methods including getters and setters
- Document PostMessageTransport callback properties
- Improve React hook documentation (useApp, useAutoResize)
- Add documentation for types.ts result and notification types
- Add @see cross-references between related methods
- Fix code examples to use correct syntax and patterns
- Clarify error handling behavior in method descriptions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix TypeDoc cross-module link resolution warnings
Replace unqualified @link references with module-qualified syntax to fix
"Failed to resolve link" warnings during docs generation. TypeDoc cannot
resolve links like {@link App.sendOpenLink} without imports, so use the
module-qualified format {@link app.App.sendOpenLink} instead.
Changes:
- types.ts: Fix 9 cross-module links to App and AppBridge methods
- app-bridge.ts: Fix 2 references to App.sendSizeChange
- message-transport.ts: Fix 2 references to App.connect and AppBridge.connect
This eliminates all "Failed to resolve link" warnings without requiring
circular imports or removing documentation cross-references.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove TypeDoc links to undocumented external types
Remove @link references to types that are either:
- Not exported (AppOptions) - internal implementation detail
- From external packages (@modelcontextprotocol/sdk) - not included in docs
This eliminates warnings about resolved but undocumented types. The type
information is still available through TypeScript's type system and parameter
documentation, but without broken links in the generated documentation.
Removed links to:
- AppOptions (internal type)
- Implementation, CallToolRequest, CallToolResult (MCP SDK types)
- ListToolsRequest, LoggingMessageNotification (MCP SDK types)
Reduces warnings from 12 to 4 (remaining are about missing example files).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Bump @modelcontextprotocol/sdk to ^1.23.0
Update SDK dependency to fix CI build failures caused by version
mismatch. CI was installing 1.23.0 (latest compatible with ^1.22.0)
while local builds used 1.22.0.
Note: package-lock.json is gitignored, which prevents reproducible
builds. Consider committing it to ensure CI uses the same versions
as local development.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix Zod type compatibility with @modelcontextprotocol/sdk
The SDK imports Zod from 'zod/v4' while this package imported from
'zod'. Although both resolve to the same version (3.25.x), TypeScript
sees them as separate modules with incompatible type declarations.
This caused type errors when:
- Extending SDK schemas like RequestSchema.extend()
- Passing SDK schemas to setRequestHandler/setNotificationHandler
- Using ZodObject/ZodLiteral types from different import paths
Changes:
- Import zod from 'zod/v4' to match SDK import path
- Update z.record() calls to use Zod v4 signature (requires both
key and value schemas)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add GitHub Actions workflow to deploy docs to gh-pages
Adds a workflow with manual trigger (workflow_dispatch) that generates
TypeDoc documentation and publishes it to the gh-pages branch using
peaceiris/actions-gh-pages.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add docs index.html redirect to API documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Revise README for clarity and audience focus
- Add API documentation badge linking to GitHub Pages
- Reorganize content around two audiences: App Developers and Host Developers
- Convert relative links to absolute GitHub URLs for better portability
- Simplify installation and examples sections
- Add Resources section with links to docs, spec, and discussion
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Update src/app-bridge.ts
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>