diff --git a/README.md b/README.md index 3010859a..55f69044 100644 --- a/README.md +++ b/README.md @@ -56,19 +56,28 @@ Or edit your `package.json` manually: ## Examples -Start with these foundational examples to learn the SDK: - -- [`examples/basic-server-vanillajs`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs) — MCP server + MCP App using vanilla JS -- [`examples/basic-server-react`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) — MCP server + MCP App using [React](https://github.com/facebook/react) -- [`examples/basic-server-vue`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vue) — MCP server + MCP App using [Vue](https://github.com/vuejs/vue) -- [`examples/basic-server-svelte`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-svelte) — MCP server + MCP App using [Svelte](https://github.com/sveltejs/svelte) -- [`examples/basic-server-preact`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-preact) — MCP server + MCP App using [Preact](https://github.com/preactjs/preact) -- [`examples/basic-server-solid`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-solid) — MCP server + MCP App using [Solid](https://github.com/solidjs/solid) -- [`examples/basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host) — MCP host application supporting MCP Apps + +| | | | +|:---:|:---:|:---:| +| [![Map](examples/map-server/grid-cell.png "Interactive 3D globe viewer using CesiumJS")](examples/map-server) | [![Three.js](examples/threejs-server/grid-cell.png "Interactive 3D scene renderer")](examples/threejs-server) | [![ShaderToy](examples/shadertoy-server/grid-cell.png "Real-time GLSL shader renderer")](examples/shadertoy-server) | +| [**Map**](examples/map-server) | [**Three.js**](examples/threejs-server) | [**ShaderToy**](examples/shadertoy-server) | +| [![Sheet Music](examples/sheet-music-server/grid-cell.png "ABC notation to sheet music")](examples/sheet-music-server) | [![Wiki Explorer](examples/wiki-explorer-server/grid-cell.png "Wikipedia link graph visualization")](examples/wiki-explorer-server) | [![Cohort Heatmap](examples/cohort-heatmap-server/grid-cell.png "Customer retention heatmap")](examples/cohort-heatmap-server) | +| [**Sheet Music**](examples/sheet-music-server) | [**Wiki Explorer**](examples/wiki-explorer-server) | [**Cohort Heatmap**](examples/cohort-heatmap-server) | +| [![Scenario Modeler](examples/scenario-modeler-server/grid-cell.png "SaaS business projections")](examples/scenario-modeler-server) | [![Budget Allocator](examples/budget-allocator-server/grid-cell.png "Interactive budget allocation")](examples/budget-allocator-server) | [![Customer Segmentation](examples/customer-segmentation-server/grid-cell.png "Scatter chart with clustering")](examples/customer-segmentation-server) | +| [**Scenario Modeler**](examples/scenario-modeler-server) | [**Budget Allocator**](examples/budget-allocator-server) | [**Customer Segmentation**](examples/customer-segmentation-server) | +| [![System Monitor](examples/system-monitor-server/grid-cell.png "Real-time OS metrics")](examples/system-monitor-server) | [![Transcript](examples/transcript-server/grid-cell.png "Live speech transcription")](examples/transcript-server) | [![Video Resource](examples/video-resource-server/grid-cell.png "Binary video via MCP resources")](examples/video-resource-server) | +| [**System Monitor**](examples/system-monitor-server) | [**Transcript**](examples/transcript-server) | [**Video Resource**](examples/video-resource-server) | + +### Starter Templates + +| | | +|:---:|:---| +| [![Basic](examples/basic-server-react/grid-cell.png "Starter template")](examples/basic-server-react) | The same app built with different frameworks — pick your favorite!

[React](examples/basic-server-react) · [Vue](examples/basic-server-vue) · [Svelte](examples/basic-server-svelte) · [Preact](examples/basic-server-preact) · [Solid](examples/basic-server-solid) · [Vanilla JS](examples/basic-server-vanillajs) | + The [`examples/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples) directory contains additional demo apps showcasing real-world use cases. -To run all examples together: +To run all examples: ```bash npm install diff --git a/examples/basic-server-react/README.md b/examples/basic-server-react/README.md index eb1864a7..c5be8e9d 100644 --- a/examples/basic-server-react/README.md +++ b/examples/basic-server-react/README.md @@ -1,5 +1,7 @@ # Example: Basic Server (React) +![Screenshot](screenshot.png) + An MCP App example with a React UI. > [!TIP] diff --git a/examples/basic-server-react/grid-cell.png b/examples/basic-server-react/grid-cell.png new file mode 100644 index 00000000..926846fe Binary files /dev/null and b/examples/basic-server-react/grid-cell.png differ diff --git a/examples/basic-server-react/screenshot.png b/examples/basic-server-react/screenshot.png new file mode 100644 index 00000000..1c9df5ad Binary files /dev/null and b/examples/basic-server-react/screenshot.png differ diff --git a/examples/budget-allocator-server/grid-cell.png b/examples/budget-allocator-server/grid-cell.png new file mode 100644 index 00000000..dee5a313 Binary files /dev/null and b/examples/budget-allocator-server/grid-cell.png differ diff --git a/examples/budget-allocator-server/screenshot.png b/examples/budget-allocator-server/screenshot.png new file mode 100644 index 00000000..1bd9783c Binary files /dev/null and b/examples/budget-allocator-server/screenshot.png differ diff --git a/examples/cohort-heatmap-server/grid-cell.png b/examples/cohort-heatmap-server/grid-cell.png new file mode 100644 index 00000000..c1fe7041 Binary files /dev/null and b/examples/cohort-heatmap-server/grid-cell.png differ diff --git a/examples/cohort-heatmap-server/screenshot.png b/examples/cohort-heatmap-server/screenshot.png new file mode 100644 index 00000000..a8ad0e77 Binary files /dev/null and b/examples/cohort-heatmap-server/screenshot.png differ diff --git a/examples/customer-segmentation-server/grid-cell.png b/examples/customer-segmentation-server/grid-cell.png new file mode 100644 index 00000000..c0c19bf5 Binary files /dev/null and b/examples/customer-segmentation-server/grid-cell.png differ diff --git a/examples/customer-segmentation-server/screenshot.png b/examples/customer-segmentation-server/screenshot.png new file mode 100644 index 00000000..d891fbc6 Binary files /dev/null and b/examples/customer-segmentation-server/screenshot.png differ diff --git a/examples/map-server/README.md b/examples/map-server/README.md index c504eeab..ad9223dc 100644 --- a/examples/map-server/README.md +++ b/examples/map-server/README.md @@ -1,5 +1,7 @@ # Example: Interactive Map +![Screenshot](screenshot.png) + Interactive 3D globe viewer using CesiumJS with OpenStreetMap tiles. Demonstrates geocoding integration and full MCP App capabilities. ## Features diff --git a/examples/map-server/grid-cell.png b/examples/map-server/grid-cell.png new file mode 100644 index 00000000..a5a85b9a Binary files /dev/null and b/examples/map-server/grid-cell.png differ diff --git a/examples/map-server/screenshot.png b/examples/map-server/screenshot.png new file mode 100644 index 00000000..706d71e7 Binary files /dev/null and b/examples/map-server/screenshot.png differ diff --git a/examples/scenario-modeler-server/grid-cell.png b/examples/scenario-modeler-server/grid-cell.png new file mode 100644 index 00000000..d04569cf Binary files /dev/null and b/examples/scenario-modeler-server/grid-cell.png differ diff --git a/examples/scenario-modeler-server/screenshot.png b/examples/scenario-modeler-server/screenshot.png new file mode 100644 index 00000000..0f4cf849 Binary files /dev/null and b/examples/scenario-modeler-server/screenshot.png differ diff --git a/examples/shadertoy-server/README.md b/examples/shadertoy-server/README.md index 0e85d47c..5f965cb8 100644 --- a/examples/shadertoy-server/README.md +++ b/examples/shadertoy-server/README.md @@ -50,7 +50,10 @@ _Tool input:_ ```json { - "fragmentShader": "void mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 uv = fragCoord / iResolution.xy;\n fragColor = vec4(uv, 0.5 + 0.5*sin(iTime), 1.0);\n}" + "fragmentShader": "void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + fragColor = vec4(uv, 0.5 + 0.5*sin(iTime), 1.0); +}" } ``` @@ -79,7 +82,22 @@ _Tool input:_ ```json { - "fragmentShader": "void mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;\n float segments = 6.0;\n float zoom = 1.0 + 0.3 * sin(iTime * 0.2);\n float angle = atan(uv.y, uv.x) + iTime * 0.3;\n float r = length(uv) * zoom;\n angle = mod(angle, 6.28 / segments);\n angle = abs(angle - 3.14 / segments);\n vec2 p = vec2(cos(angle), sin(angle)) * r;\n p += iTime * 0.1;\n float v = sin(p.x * 10.0) * sin(p.y * 10.0);\n v += sin(length(p) * 15.0 - iTime * 2.0);\n v += sin(p.x * 5.0 + p.y * 7.0 + iTime);\n vec3 col = 0.5 + 0.5 * cos(v * 2.0 + vec3(0.0, 2.0, 4.0) + iTime);\n fragColor = vec4(col, 1.0);\n}" + "fragmentShader": "void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y; + float segments = 6.0; + float zoom = 1.0 + 0.3 * sin(iTime * 0.2); + float angle = atan(uv.y, uv.x) + iTime * 0.3; + float r = length(uv) * zoom; + angle = mod(angle, 6.28 / segments); + angle = abs(angle - 3.14 / segments); + vec2 p = vec2(cos(angle), sin(angle)) * r; + p += iTime * 0.1; + float v = sin(p.x * 10.0) * sin(p.y * 10.0); + v += sin(length(p) * 15.0 - iTime * 2.0); + v += sin(p.x * 5.0 + p.y * 7.0 + iTime); + vec3 col = 0.5 + 0.5 * cos(v * 2.0 + vec3(0.0, 2.0, 4.0) + iTime); + fragColor = vec4(col, 1.0); +}" } ``` @@ -108,7 +126,22 @@ _Tool input:_ ```json { - "fragmentShader": "void mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y * 2.5;\n vec2 mouse = (iMouse.xy / iResolution.xy - 0.5) * 2.0;\n vec2 c = mouse;\n vec2 z = uv;\n float iter = 0.0;\n for (int i = 0; i < 100; i++) {\n z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;\n if (dot(z, z) > 4.0) break;\n iter++;\n }\n float t = iter / 100.0;\n vec3 col = 0.5 + 0.5 * cos(3.0 + t * 6.28 * 2.0 + vec3(0.0, 0.6, 1.0));\n if (iter == 100.0) col = vec3(0.0);\n fragColor = vec4(col, 1.0);\n}" + "fragmentShader": "void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y * 2.5; + vec2 mouse = (iMouse.xy / iResolution.xy - 0.5) * 2.0; + vec2 c = mouse; + vec2 z = uv; + float iter = 0.0; + for (int i = 0; i < 100; i++) { + z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; + if (dot(z, z) > 4.0) break; + iter++; + } + float t = iter / 100.0; + vec3 col = 0.5 + 0.5 * cos(3.0 + t * 6.28 * 2.0 + vec3(0.0, 0.6, 1.0)); + if (iter == 100.0) col = vec3(0.0); + fragColor = vec4(col, 1.0); +}" } ``` diff --git a/examples/shadertoy-server/grid-cell.png b/examples/shadertoy-server/grid-cell.png new file mode 100644 index 00000000..4bed456a Binary files /dev/null and b/examples/shadertoy-server/grid-cell.png differ diff --git a/examples/shadertoy-server/screenshot.png b/examples/shadertoy-server/screenshot.png new file mode 100644 index 00000000..9d90a4c8 Binary files /dev/null and b/examples/shadertoy-server/screenshot.png differ diff --git a/examples/sheet-music-server/README.md b/examples/sheet-music-server/README.md index 0eba9ff8..8f2f990c 100644 --- a/examples/sheet-music-server/README.md +++ b/examples/sheet-music-server/README.md @@ -38,7 +38,12 @@ When calling the `play-sheet-music` tool, provide ABC notation: ```json { - "abcNotation": "X:1\nT:C Major Scale\nM:4/4\nL:1/4\nK:C\nC D E F | G A B c |" + "abcNotation": "X:1 +T:C Major Scale +M:4/4 +L:1/4 +K:C +C D E F | G A B c |" } ``` diff --git a/examples/sheet-music-server/grid-cell.png b/examples/sheet-music-server/grid-cell.png new file mode 100644 index 00000000..b76374bb Binary files /dev/null and b/examples/sheet-music-server/grid-cell.png differ diff --git a/examples/sheet-music-server/screenshot.png b/examples/sheet-music-server/screenshot.png new file mode 100644 index 00000000..046f1279 Binary files /dev/null and b/examples/sheet-music-server/screenshot.png differ diff --git a/examples/system-monitor-server/grid-cell.png b/examples/system-monitor-server/grid-cell.png new file mode 100644 index 00000000..b0ca8a10 Binary files /dev/null and b/examples/system-monitor-server/grid-cell.png differ diff --git a/examples/system-monitor-server/screenshot.png b/examples/system-monitor-server/screenshot.png new file mode 100644 index 00000000..e83d0ef9 Binary files /dev/null and b/examples/system-monitor-server/screenshot.png differ diff --git a/examples/threejs-server/README.md b/examples/threejs-server/README.md index 90a336fb..41e6d2f0 100644 --- a/examples/threejs-server/README.md +++ b/examples/threejs-server/README.md @@ -1,12 +1,8 @@ # Example: Three.js App -Interactive 3D scene renderer using Three.js. Demonstrates streaming code preview and full MCP App integration. +![Screenshot](screenshot.png) - - - - -
Three.js scene
+Interactive 3D scene renderer using Three.js. Demonstrates streaming code preview and full MCP App integration. ## Features diff --git a/examples/threejs-server/grid-cell.png b/examples/threejs-server/grid-cell.png new file mode 100644 index 00000000..438a6d17 Binary files /dev/null and b/examples/threejs-server/grid-cell.png differ diff --git a/examples/threejs-server/screenshot.png b/examples/threejs-server/screenshot.png new file mode 100644 index 00000000..7df32b76 Binary files /dev/null and b/examples/threejs-server/screenshot.png differ diff --git a/examples/threejs-server/src/threejs-app.tsx b/examples/threejs-server/src/threejs-app.tsx index 7877bd31..3fb50160 100644 --- a/examples/threejs-server/src/threejs-app.tsx +++ b/examples/threejs-server/src/threejs-app.tsx @@ -28,10 +28,19 @@ const cube = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshStandardMaterial({ color: 0x00ff88 }) ); +// Start with an isometric-ish rotation to show 3 faces +cube.rotation.x = 0.5; +cube.rotation.y = 0.7; scene.add(cube); -scene.add(new THREE.DirectionalLight(0xffffff, 1)); -scene.add(new THREE.AmbientLight(0x404040)); +// Better lighting: key light + fill light + ambient +const keyLight = new THREE.DirectionalLight(0xffffff, 1.2); +keyLight.position.set(1, 1, 2); +scene.add(keyLight); +const fillLight = new THREE.DirectionalLight(0x8888ff, 0.4); +fillLight.position.set(-1, 0, -1); +scene.add(fillLight); +scene.add(new THREE.AmbientLight(0x404040, 0.5)); camera.position.z = 3; diff --git a/examples/transcript-server/README.md b/examples/transcript-server/README.md index 057bc4fa..dae22280 100644 --- a/examples/transcript-server/README.md +++ b/examples/transcript-server/README.md @@ -1,5 +1,7 @@ # Transcript Server +![Screenshot](screenshot.png) + An MCP App Server for live speech transcription using the Web Speech API. ## Features diff --git a/examples/transcript-server/grid-cell.png b/examples/transcript-server/grid-cell.png new file mode 100644 index 00000000..6895b337 Binary files /dev/null and b/examples/transcript-server/grid-cell.png differ diff --git a/examples/transcript-server/screenshot.png b/examples/transcript-server/screenshot.png new file mode 100644 index 00000000..3785b780 Binary files /dev/null and b/examples/transcript-server/screenshot.png differ diff --git a/examples/video-resource-server/README.md b/examples/video-resource-server/README.md index 4c6fbb57..252e6bbf 100644 --- a/examples/video-resource-server/README.md +++ b/examples/video-resource-server/README.md @@ -1,5 +1,7 @@ # Video Resource Server +![Screenshot](screenshot.png) + Demonstrates serving binary content (video) via MCP resources using the base64 blob pattern. ## Quick Start diff --git a/examples/video-resource-server/grid-cell.png b/examples/video-resource-server/grid-cell.png new file mode 100644 index 00000000..9fb01f6a Binary files /dev/null and b/examples/video-resource-server/grid-cell.png differ diff --git a/examples/video-resource-server/screenshot.png b/examples/video-resource-server/screenshot.png new file mode 100644 index 00000000..1e6e169d Binary files /dev/null and b/examples/video-resource-server/screenshot.png differ diff --git a/examples/wiki-explorer-server/grid-cell.png b/examples/wiki-explorer-server/grid-cell.png new file mode 100644 index 00000000..b6d8fa59 Binary files /dev/null and b/examples/wiki-explorer-server/grid-cell.png differ diff --git a/examples/wiki-explorer-server/screenshot.png b/examples/wiki-explorer-server/screenshot.png new file mode 100644 index 00000000..291f1c12 Binary files /dev/null and b/examples/wiki-explorer-server/screenshot.png differ diff --git a/package-lock.json b/package-lock.json index 379526fe..d1232523 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "prettier": "^3.6.2", "react": "^19.2.0", "react-dom": "^19.2.0", + "sharp": "^0.34.5", "ts-to-zod": "^5.1.0", "tsx": "^4.21.0", "typedoc": "^0.28.14", @@ -70,7 +71,7 @@ }, "examples/basic-host": { "name": "@modelcontextprotocol/ext-apps-basic-host", - "version": "1.1.0", + "version": "0.4.0", "dependencies": { "@modelcontextprotocol/ext-apps": "../..", "@modelcontextprotocol/sdk": "^1.24.0", @@ -851,7 +852,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -1214,6 +1214,17 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@epic-web/invariant": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", @@ -1689,6 +1700,496 @@ "hono": "^4" } }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2566,7 +3067,6 @@ "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", @@ -2773,7 +3273,6 @@ "integrity": "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -2798,7 +3297,6 @@ "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3201,7 +3699,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3527,7 +4024,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4239,7 +4735,6 @@ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -4363,6 +4858,16 @@ "node": ">= 0.8" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/devalue": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz", @@ -4714,7 +5219,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -6256,7 +6760,6 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz", "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -6352,7 +6855,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6451,7 +6953,6 @@ "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -6573,7 +7074,6 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -6615,6 +7115,51 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6819,7 +7364,6 @@ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz", "integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", @@ -6998,7 +7542,6 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.46.3.tgz", "integrity": "sha512-Y5juST3x+/ySty5tYJCVWa6Corkxpt25bUZQHqOceg9xfMUtDsFx6rCsG6cYf1cA6vzDi66HIvaki0byZZX95A==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -7326,7 +7869,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8076,7 +8618,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8170,7 +8711,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -8347,7 +8887,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8466,7 +9005,6 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz", "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.26", "@vue/compiler-sfc": "3.5.26", @@ -8639,7 +9177,6 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -8690,7 +9227,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index ebe9102f..e0b4dbba 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "prepare": "node scripts/setup-bun.mjs && npm run build && husky", "docs": "typedoc", "docs:watch": "typedoc --watch", + "generate:screenshots": "docker run --rm -v $(pwd):/work -w /work mcr.microsoft.com/playwright:v1.57.0-noble sh -c 'npm i -g bun && npm ci && npx playwright test tests/e2e/generate-grid-screenshots.spec.ts'", "prettier": "prettier -u \"**/*.{js,jsx,ts,tsx,mjs,json,md,yml,yaml}\" --check", "prettier:fix": "prettier -u \"**/*.{js,jsx,ts,tsx,mjs,json,md,yml,yaml}\" --write", "check:versions": "node scripts/check-versions.mjs" @@ -85,6 +86,7 @@ "prettier": "^3.6.2", "react": "^19.2.0", "react-dom": "^19.2.0", + "sharp": "^0.34.5", "ts-to-zod": "^5.1.0", "tsx": "^4.21.0", "typedoc": "^0.28.14", diff --git a/tests/e2e/generate-grid-screenshots.spec.ts b/tests/e2e/generate-grid-screenshots.spec.ts new file mode 100644 index 00000000..c6863044 --- /dev/null +++ b/tests/e2e/generate-grid-screenshots.spec.ts @@ -0,0 +1,177 @@ +/** + * Generate 300x300 grid-cell.png screenshots for each example server. + * + * Usage: + * npm run generate:screenshots + * + * Output: examples//grid-cell.png (300x300, cropped top-aligned) + * + * For basic-server-* variants, only basic-server-react is included. + * integration-server is excluded (it's for E2E testing, same UI as basic-server-react). + */ + +import { test, type Page } from "@playwright/test"; +import * as path from "path"; +import * as fs from "fs"; +import sharp from "sharp"; + +const OUTPUT_SIZE = 300; +const APP_WIDTH = 500; +const DEFAULT_WAIT_MS = 5000; + +// Extra wait time for slow-loading servers (tiles, etc.) +const EXTRA_WAIT_MS: Record = { + "map-server": 45000, // CesiumJS needs time for map tiles +}; + +// Servers to skip (screenshots maintained manually) +const SKIP_SERVERS = new Set([ + "video-resource", // Uses custom screenshot from PR comment +]); + +// Server configurations (excludes integration-server which is for E2E testing) +const SERVERS = [ + { + key: "basic-react", + name: "Basic MCP App Server (React)", + dir: "basic-server-react", + }, + { + key: "budget-allocator", + name: "Budget Allocator Server", + dir: "budget-allocator-server", + }, + { + key: "cohort-heatmap", + name: "Cohort Heatmap Server", + dir: "cohort-heatmap-server", + }, + { + key: "customer-segmentation", + name: "Customer Segmentation Server", + dir: "customer-segmentation-server", + }, + { key: "map-server", name: "CesiumJS Map Server", dir: "map-server" }, + { + key: "scenario-modeler", + name: "SaaS Scenario Modeler", + dir: "scenario-modeler-server", + }, + { key: "shadertoy", name: "ShaderToy Server", dir: "shadertoy-server" }, + { + key: "sheet-music", + name: "Sheet Music Server", + dir: "sheet-music-server", + }, + { + key: "system-monitor", + name: "System Monitor Server", + dir: "system-monitor-server", + }, + { key: "threejs", name: "Three.js Server", dir: "threejs-server" }, + { key: "transcript", name: "Transcript Server", dir: "transcript-server" }, + { + key: "video-resource", + name: "Video Resource Server", + dir: "video-resource-server", + }, + { key: "wiki-explorer", name: "Wiki Explorer", dir: "wiki-explorer-server" }, +]; + +/** + * Wait for the MCP App to load inside nested iframes. + */ +async function waitForAppLoad(page: Page) { + const outerFrame = page.frameLocator("iframe").nth(0); + await outerFrame + .locator("iframe") + .waitFor({ state: "visible", timeout: 60000 }); +} + +/** + * Load a server by selecting it from dropdown and clicking Call Tool. + */ +async function loadServer(page: Page, serverName: string) { + await page.goto("/"); + await page + .locator("select") + .nth(0) + .waitFor({ state: "visible", timeout: 30000 }); + await page.waitForTimeout(500); + await page.locator("select").nth(0).selectOption({ label: serverName }); + await page.click('button:has-text("Call Tool")'); + await waitForAppLoad(page); +} + +/** + * Capture the app iframe content and save both: + * - screenshot.png: full-size raw screenshot of the iframe + * - grid-cell.png: 300x300 cropped thumbnail (top-aligned) + */ +async function captureAppScreenshot(page: Page, outputDir: string) { + // Get the inner app iframe element + const outerFrame = page.frameLocator("iframe").nth(0); + const innerIframe = outerFrame.locator("iframe").nth(0); + + // Screenshot the inner iframe element + const screenshot = await innerIframe.screenshot(); + + // Save full-size screenshot + const screenshotPath = path.join(outputDir, "screenshot.png"); + await sharp(screenshot).png().toFile(screenshotPath); + + // Save 300x300 grid cell thumbnail (crop to fill, align top) + const gridCellPath = path.join(outputDir, "grid-cell.png"); + await sharp(screenshot) + .resize(OUTPUT_SIZE, OUTPUT_SIZE, { + fit: "cover", + position: "top", + }) + .png() + .toFile(gridCellPath); + + return { screenshotPath, gridCellPath }; +} + +// Use a constrained viewport width for consistent app rendering +test.use({ viewport: { width: APP_WIDTH, height: 600 } }); + +// Increase test timeout for slow servers +test.setTimeout(120000); + +// Generate screenshots for each server +for (const server of SERVERS) { + test(`Generate grid-cell.png for ${server.dir}`, async ({ page }) => { + const examplesDir = path.join(process.cwd(), "examples"); + const outputDir = path.join(examplesDir, server.dir); + + // Skip if directory doesn't exist + if (!fs.existsSync(outputDir)) { + console.log(`⚠️ Skipping ${server.dir}: directory not found`); + test.skip(); + return; + } + + // Skip servers with manually maintained screenshots + if (SKIP_SERVERS.has(server.key)) { + console.log(`⏭️ Skipping ${server.dir}: manually maintained screenshot`); + test.skip(); + return; + } + + // Load the server + await loadServer(page, server.name); + + // Wait for app to fully load (extra time for slow servers) + const waitMs = EXTRA_WAIT_MS[server.key] ?? DEFAULT_WAIT_MS; + console.log(`⏳ Waiting ${waitMs / 1000}s for ${server.dir}...`); + await page.waitForTimeout(waitMs); + + // Capture and save both screenshot.png and grid-cell.png + const { screenshotPath, gridCellPath } = await captureAppScreenshot( + page, + outputDir, + ); + console.log(`✅ Saved ${screenshotPath} + ${gridCellPath}`); + }); +}