feat(examples): add say-server - streaming Pocket TTS with karaoke highlighting (#304)
* Add say-server: streaming TTS MCP App with karaoke UI - Streaming TTS MCP App with real-time word highlighting - React widget with pause/resume controls - Debounced model context updates - Self-contained server.py for standalone uv run Co-authored-by: Claude <noreply@anthropic.com> * Update server.py * fix(say-server): fix React/Zod version conflicts in widget - Pin Zod to v3.25.1 via esm.sh deps param (fixes .custom() error) - Add external=react,react-dom to prevent duplicate React instances - Update React from 19.1.0 to 19.2.0 - Import React for Babel's classic JSX transform - Consolidate imports: only use @modelcontextprotocol/ext-apps/react - Use Babel from unpkg.com instead of esm.sh - Simplify resource decorator with meta= parameter - Update CSP to allow both esm.sh and unpkg.com * fix(say-server): show play button when paused, restart when finished * feat(say-server): add fullscreen button, remove inline scroll - Add fullscreen toggle button (appears on hover when available) - Remove max-height/scroll in inline mode for cleaner display - Enable scrolling only in fullscreen mode - Track displayMode and fullscreenAvailable state - Handle onHostContextChanged to detect fullscreen support * fix(say-server): fix fullscreen button visibility and prevent multiple audio streams - Check initial host context for fullscreen availability (not just updates) - Add audioOperationInProgressRef guard to prevent concurrent audio operations - Prevents race conditions from rapid clicks/double-clicks * fix(qr-server): use meta parameter instead of manual handler patching Simplified CSP metadata handling to match say-server's approach: - Use meta parameter on @mcp.resource decorator directly - Removed custom _read_resource_with_meta handler and handler replacement * fix(say-server): simplify controls to prevent multiple audio streams - Remove onClick from text display (was conflicting with double-click) - Always show overlay button (with pause icon when playing) - Lower opacity (0.3) when playing, full on hover - Button shows: ▶️ (play), ⏸️ (pause), 🔄 (restart) - Double-click text to restart still works - Eliminates click/dblclick race condition * fix(say-server): only show pause button on hover - Play/restart buttons visible normally - Pause button hidden until hover (doesn't block karaoke text) * fix(say-server): fix partial input handling and session management - Add promise lock to initTTSQueue to prevent concurrent initialization - Reset queueIdRef and lastTextRef in ontoolresult for next tool call - Close existing AudioContext when starting new session - Reset all playback state (chunks, timings, position) on new session - Fixes: text deltas not sent when new tool call has shorter text * fix(say-server): detect new sessions by text comparison, not queueIdRef reset - Don't reset queueIdRef in ontoolresult - audio should keep playing - Detect new sessions in ontoolinputpartial/ontoolinput by checking if new text starts with lastTextRef (continuation) or not (new session) - Only reset and create new queue when genuinely new session detected - Fixes: pause/play was creating new streams because queueIdRef was reset * feat(say-server): add theming, floating button, and keyboard shortcuts Theming: - Use CSS variables (--font-sans, --color-text-primary/secondary) with fallbacks - Apply host fonts via applyHostFonts() on connect and context changes - Apply host style variables via applyHostStyleVariables() - Apply document theme via applyDocumentTheme() Floating button: - Button follows cursor position (below current line being spoken) - Uses invisible cursor marker element for accurate positioning - Shows ⏸️ when playing, ▶️ when paused, 🔄 when finished - Low opacity when playing, full on hover Keyboard shortcuts: - Space: toggle play/pause - Enter: toggle fullscreen (when available) UI changes: - Large overlay only shown for initial play (idle state) - Floating button used during playback for less intrusive controls * docs(say-server): add TODO items for future improvements * fix(say-server): simplify to fixed play button at top right - Remove floating cursor-following button (was too complex) - Fixed play button at top-right corner - Low opacity when playing, full on hover - Keep theming and keyboard shortcuts (Space/Enter) - Add say-server to E2E tests * test(e2e): add say-server to tests but skip in CI Say-server requires Pocket TTS model (~500MB) and GPU support, which isn't available in the Docker CI environment. Skip it for now. * test(e2e): add say-server and qr-server to screenshot generation - Remove say-server from SKIP_SERVERS (widget works without TTS model) - Add say-server masks for play buttons - Add both say-server and qr-server to grid screenshot generator * fix(say-server): restore working play/pause overlay, keep theming and keyboard shortcuts Reverted to working commit 413beca and carefully reapplied: - CSS variables for theming (--font-sans, --color-text-primary/secondary) - Dark mode support via @media (prefers-color-scheme) - Host theming via applyHostStyleVariables/applyHostFonts/applyDocumentTheme - Keyboard shortcuts: Space = play/pause, Enter = fullscreen Preserved working behavior: - Play overlay visible when not playing, hover to show when playing - Pause on click works correctly - Karaoke highlighting works * fix(say-server): minimal CSS-only theming, no JS changes from working version Only CSS changes from working commit 413beca: - Added CSS variables for theming foundation - No JavaScript changes (reverted theming calls and keyboard shortcuts) This isolates whether the issue is CSS or JS related. * feat(say-server): add autoPlay param and Reset button - Add autoPlay parameter (default: true) with Pydantic Field description - Description visible in MCP tool schema: 'browsers may block autoplay' - Add Reset button next to Fullscreen button (bottom right) - Both buttons share controlBtn base styles, shown on hover - Widget reads autoPlay from tool input params * fix(say-server): update FastMCP API for new SDK version Python SDK refactored transport params from constructor to run(): - Remove port, stateless_http from FastMCP() constructor - Pass stateless_http=True to streamable_http_app() instead * debug(say-server): add console logs to trace audio stream issue Logging added to: - togglePlayPause (all branches) - initTTSQueue (entry and AudioContext creation) - restartPlayback (entry) - ontoolinputpartial/ontoolinput (entry and new session detection) Open browser DevTools console and reproduce the issue - logs will show what's being called. * feat(say-server): add play/pause buttons in top-left and bottom-right corners - Top left: playPauseTopBtn for quick access - Bottom right: playPauseBtn alongside reset and fullscreen - Both show on hover with same styling as other control buttons - Icons: ▶️ (play), ⏸️ (pause), 🔄 (restart when finished) * fix(say-server): simplify UI and fix pause/resume audio duplication - Remove central play overlay, use single toolbar in top-right - Fix audio duplication on pause/resume (only defer to pendingChunks for initial autoplay) - Remove double-click to restart (use toolbar restart button) - Sync displayMode when host changes it externally - Click on text to play/pause * feat(say-server): add voice selection support - Add list_voices tool to show available voices - Add voice parameter to say tool (predefined names, HuggingFace URLs, or local paths) - Predefined voices: alba, marius, javert, jean, cosette, eponine, azelma, fantine - Update README with voice documentation * docs(say-server): improve tool description for better LLM triggering - Add English-only constraint prominently in first line and note - List common trigger patterns (say, speak, read aloud, etc.) - Update text parameter description to mention English - Keep description concise for quick LLM scanning * feat(say-server): add speak lock for multi-widget coordination When multiple TTS widgets exist, only one plays at a time: - Widget announces to localStorage when starting playback - Playing widgets poll localStorage every 200ms - If another widget starts, previous one pauses automatically - Lock is cleared on pause/finish/teardown Uses _meta.widgetUUID in tool result for widget identification. * update package.json of say * feat(say-server): add screenshots and update README gallery - Add say-server to demo gallery with 300x300 grid-cell.png - Fix qr-server grid-cell.png dimensions (was 672x668, now 300x300) - Add golden snapshot for say-server E2E tests - Add full screenshot and grid-cell thumbnail for say-server 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * style(say-server): apply prettier formatting - Add blank lines in README for better readability - Add trailing newline to package.json 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(say-server): add Escape key to exit fullscreen and update docs - Add onKeyDown handler to main element for Escape key - Update README with full list of MCP App features: - Model context updates - Native theming - Fullscreen mode - Multi-widget speak lock - Update server.py docstring with feature documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(say-server): add queue timeout to prevent infinite polling The TTS queue could hang forever if: - The LLM stops generating mid-stream (no end signal) - User cancels before tool completes - Network interruption prevents end_tts_queue call Changes: - Add last_activity timestamp to track queue activity - Add 30-second timeout (QUEUE_TIMEOUT_SECONDS) - Poll with 5-second timeout, checking for stale queues - Mark stale queues as "error" so widget stops polling - Return error message in poll_tts_audio response - Add error logging in widget polling loop This prevents tool calls from running forever when queues are abandoned. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * debug(say-server): add logging to track queue lifecycle Add console logging to debug why end_tts_queue might not be called: Widget-side: - Log queueId in ontoolinputpartial, ontoolinput, ontoolresult - Log when new session is detected (queue reset) - Log when initTTSQueue fails - Log when end_tts_queue is called/not called Server-side: - Log when end_tts_queue is called - Log warnings for unknown queues - Log info for already-ended queues This will help identify if the issue is: - User interruption (ontoolresult never called) - Session reset (queueId becomes null) - Host bug (callback dropped) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * perf(say-server): add initial delay before polling Add 150ms delay before first poll to give TTS model time to generate the first audio chunk. This reduces unnecessary polling and server load. Current backoff after that: - 30ms if chunks received (keep streaming fast) - 80ms if no chunks (wait longer for generation) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(say-server): use adaptive polling instead of fixed initial delay Previous approach: Always wait 150ms before first poll Problem: Even when chunks are ready, we wait unnecessarily New approach: Adaptive polling - Start polling immediately (no initial delay) - 20ms backoff when receiving chunks (fast streaming) - Exponential backoff when no chunks: 50ms → 100ms → 150ms max - Reset backoff when chunks start flowing This reduces latency when TTS is fast while being polite when it's slow. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat(say-server): add speaker icon to server info Uses SVG data URI for a speaker with sound waves icon. * docs(say-server): add comprehensive third-party licensing information - Expand License section in README with component attribution table - Add voice collection license matrix with commercial use indicators - Add warning about non-commercial expresso/ears voice collections - Clarify NON-COMMERCIAL restriction in list_voices() output - Use consistent CC-BY formatting (hyphenated) * Update last activity to prevent timeout during active polling * feat(say-server): add info button, SVG icons, and UI fixes - Replace emoji play/pause buttons with neutral white SVG icons - Add (i) info button in bottom-right with attribution popup - Use app.openLink() API for external links in popup - Add dynamic padding to fit popup when shown - Make create_tts_queue async to fix AnyIO event loop error - Add better error logging for TTS queue initialization - Update README with openLink feature documentation - Remove unnecessary HuggingFace login prerequisites * docs(say-server): document multi-widget speak lock mechanism * prettier:fix --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f3b3c48a83
commit
e6983b36d0
13 changed files with 1713 additions and 2 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -11,3 +11,5 @@ intermediate-findings/
|
|||
# Playwright
|
||||
playwright-report/
|
||||
test-results/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
|
@ -67,8 +67,8 @@ Or edit your `package.json` manually:
|
|||
| [**Scenario Modeler**](examples/scenario-modeler-server) | [**Budget Allocator**](examples/budget-allocator-server) | [**Customer Segmentation**](examples/customer-segmentation-server) |
|
||||
| [](examples/system-monitor-server) | [](examples/transcript-server) | [](examples/video-resource-server) |
|
||||
| [**System Monitor**](examples/system-monitor-server) | [**Transcript**](examples/transcript-server) | [**Video Resource**](examples/video-resource-server) |
|
||||
| [](examples/pdf-server) | [](examples/qr-server) | |
|
||||
| [**PDF Server**](examples/pdf-server) | [**QR Code (Python)**](examples/qr-server) | |
|
||||
| [](examples/pdf-server) | [](examples/qr-server) | [](examples/say-server) |
|
||||
| [**PDF Server**](examples/pdf-server) | [**QR Code (Python)**](examples/qr-server) | [**Say Demo**](examples/say-server) |
|
||||
|
||||
### Starter Templates
|
||||
|
||||
|
|
|
|||
2
examples/say-server/.gitignore
vendored
Normal file
2
examples/say-server/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
dist/
|
||||
175
examples/say-server/README.md
Normal file
175
examples/say-server/README.md
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
# Say Server - Streaming TTS MCP App
|
||||
|
||||
A real-time text-to-speech MCP App with karaoke-style text highlighting, powered by [Kyutai's Pocket TTS](https://github.com/kyutai-labs/pocket-tts).
|
||||
|
||||
## MCP App Features Demonstrated
|
||||
|
||||
This example showcases several MCP App capabilities:
|
||||
|
||||
- **Single-file executable**: Python server with embedded React UI - no build step required
|
||||
- **Partial tool inputs** (`ontoolinputpartial`): Widget receives streaming text as it's being generated
|
||||
- **Queue-based streaming**: Demonstrates how to stream text out and audio in via a polling tool (adds text to an input queue, retrieves audio chunks from an output queue)
|
||||
- **Model context updates**: Widget updates the LLM with playback progress ("Playing: ...snippet...")
|
||||
- **Native theming**: Uses CSS variables for automatic dark/light mode adaptation
|
||||
- **Fullscreen mode**: Toggle fullscreen via `requestDisplayMode()` API, press Escape to exit
|
||||
- **Multi-widget speak lock**: Coordinates multiple TTS widgets via localStorage so only one plays at a time
|
||||
- **Hidden tools** (`visibility: ["app"]`): Private tools only accessible to the widget, not the model
|
||||
- **External links** (`openLink`): Attribution popup uses `app.openLink()` to open external URLs
|
||||
- **CSP metadata**: Resource declares required domains (`esm.sh`) for in-browser transpilation
|
||||
|
||||
## Features
|
||||
|
||||
- **Streaming TTS**: Audio starts playing as text is being generated
|
||||
- **Karaoke highlighting**: Words are highlighted in sync with speech
|
||||
- **Interactive controls**: Click to pause/resume, double-click to restart
|
||||
- **Low latency**: Uses a polling-based queue for minimal delay
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [uv](https://docs.astral.sh/uv/getting-started/installation/) - fast Python package manager
|
||||
- A CUDA GPU (recommended) or CPU with sufficient RAM (~2GB for model)
|
||||
|
||||
## Quick Start
|
||||
|
||||
The server is a single self-contained Python file that can be run directly with `uv`:
|
||||
|
||||
```bash
|
||||
# Run directly (uv auto-installs dependencies)
|
||||
uv run examples/say-server/server.py
|
||||
```
|
||||
|
||||
The server will be available at `http://localhost:3109/mcp`.
|
||||
|
||||
## Running with Docker
|
||||
|
||||
Run directly from GitHub using the official `uv` Docker image. Mount your HuggingFace cache to avoid re-downloading the model:
|
||||
|
||||
```bash
|
||||
docker run --rm -it \
|
||||
-p 3109:3109 \
|
||||
-v ~/.cache/huggingface:/root/.cache/huggingface \
|
||||
-e HF_HOME=/root/.cache/huggingface \
|
||||
ghcr.io/astral-sh/uv:debian \
|
||||
uv run https://raw.githubusercontent.com/modelcontextprotocol/ext-apps/main/examples/say-server/server.py
|
||||
```
|
||||
|
||||
For GPU support, add `--gpus all` (requires [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html)).
|
||||
|
||||
## Usage
|
||||
|
||||
### With Claude Desktop
|
||||
|
||||
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"say": {
|
||||
"command": "uv",
|
||||
"args": ["run", "server.py", "--stdio"],
|
||||
"cwd": "/path/to/examples/say-server"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### With MCP Clients
|
||||
|
||||
Connect to `http://localhost:3109/mcp` and call the `say` tool:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "say",
|
||||
"arguments": {
|
||||
"text": "Hello, world! This is a streaming TTS demo."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Available Voices
|
||||
|
||||
The default voice is `cosette`. Use the `list_voices` tool or pass a `voice` parameter to `say`:
|
||||
|
||||
### Predefined Voices
|
||||
|
||||
- `alba`, `marius`, `javert`, `jean` - from [alba-mackenna](https://huggingface.co/kyutai/tts-voices/tree/main/alba-mackenna) (CC BY 4.0)
|
||||
- `cosette`, `eponine`, `azelma`, `fantine` - from [VCTK dataset](https://huggingface.co/kyutai/tts-voices/tree/main/vctk) (CC BY 4.0)
|
||||
|
||||
### Custom Voices
|
||||
|
||||
You can also use HuggingFace URLs or local file paths:
|
||||
|
||||
```json
|
||||
{"text": "Hello!", "voice": "hf://kyutai/tts-voices/voice-donations/alice.wav"}
|
||||
{"text": "Hello!", "voice": "/path/to/my-voice.wav"}
|
||||
```
|
||||
|
||||
See the [kyutai/tts-voices](https://huggingface.co/kyutai/tts-voices) repository for more voice collections
|
||||
|
||||
## Architecture
|
||||
|
||||
The entire server is contained in a single `server.py` file:
|
||||
|
||||
1. **`say` tool**: Public tool that triggers the widget with text to speak
|
||||
2. **Private tools** (`create_tts_queue`, `add_tts_text`, `poll_tts_audio`, etc.): Hidden from the model, only callable by the widget
|
||||
3. **Embedded React widget**: Uses [Babel standalone](https://babeljs.io/docs/babel-standalone) for in-browser JSX transpilation - no build step needed
|
||||
4. **TTS backend**: Manages per-request audio queues using Pocket TTS
|
||||
|
||||
The widget communicates with the server via MCP tool calls:
|
||||
|
||||
- Receives streaming text via `ontoolinputpartial` callback
|
||||
- Incrementally sends new text to the server as it arrives (via `add_tts_text`)
|
||||
- Polls for generated audio chunks while TTS runs in parallel
|
||||
- Plays audio via Web Audio API with synchronized text highlighting
|
||||
|
||||
## Multi-Widget Speak Lock
|
||||
|
||||
When multiple TTS widgets exist in the same browser (e.g., multiple chat messages each with their own say widget), they coordinate via localStorage to ensure only one plays at a time:
|
||||
|
||||
1. **Unique Widget IDs**: Each widget receives a UUID via `toolResult._meta.widgetUUID`
|
||||
2. **Announce on Play**: When starting, a widget writes `{uuid, timestamp}` to `localStorage["mcp-tts-playing"]`
|
||||
3. **Poll for Conflicts**: Every 200ms, playing widgets check if another widget took the lock
|
||||
4. **Yield Gracefully**: If another widget started playing, pause and yield
|
||||
5. **Clean Up**: On pause/finish, clear the lock (only if owned)
|
||||
|
||||
This "last writer wins" protocol ensures a seamless experience: clicking play on any widget immediately pauses others, without requiring cross-iframe postMessage coordination.
|
||||
|
||||
## TODO
|
||||
|
||||
- Persist caret position in localStorage (resume from where you left off)
|
||||
- Click anywhere in text to move the cursor/playback position
|
||||
|
||||
## Credits
|
||||
|
||||
This project uses [Pocket TTS](https://github.com/kyutai-labs/pocket-tts) by [Kyutai](https://kyutai.org/) - a fantastic open-source text-to-speech model. Thank you to the Kyutai team for making this technology available!
|
||||
|
||||
The server includes modified Pocket TTS code to support streaming text input (text can be fed incrementally while audio generation runs in parallel). A PR contributing this functionality back to the original repo is planned.
|
||||
|
||||
## License
|
||||
|
||||
This example is MIT licensed.
|
||||
|
||||
### Third-Party Licenses
|
||||
|
||||
This project uses the following open-source components:
|
||||
|
||||
| Component | License | Link |
|
||||
| --------------------------------------------------------------------- | ----------------- | ---------------------------- |
|
||||
| [pocket-tts](https://github.com/kyutai-labs/pocket-tts) | MIT | Python TTS library |
|
||||
| [Kyutai TTS model](https://huggingface.co/kyutai/tts-0.75b-en-public) | CC-BY 4.0 | Text-to-speech model weights |
|
||||
| [kyutai/tts-voices](https://huggingface.co/kyutai/tts-voices) | Mixed (see below) | Voice prompt files |
|
||||
|
||||
### Voice Collection Licenses
|
||||
|
||||
The predefined voices in this example use **CC-BY 4.0** licensed collections:
|
||||
|
||||
| Collection | License | Commercial Use |
|
||||
| --------------- | ------------------- | ------------------------- |
|
||||
| alba-mackenna | CC-BY 4.0 | ✅ Yes (with attribution) |
|
||||
| vctk | CC-BY 4.0 | ✅ Yes (with attribution) |
|
||||
| cml-tts/fr | CC-BY 4.0 | ✅ Yes (with attribution) |
|
||||
| voice-donations | CC0 (Public Domain) | ✅ Yes |
|
||||
| **expresso** | CC-BY-NC 4.0 | ❌ Non-commercial only |
|
||||
| **ears** | CC-BY-NC 4.0 | ❌ Non-commercial only |
|
||||
|
||||
⚠️ **Note**: If you use voices from the `expresso/` or `ears/` collections, your use is restricted to non-commercial purposes.
|
||||
BIN
examples/say-server/grid-cell.png
Normal file
BIN
examples/say-server/grid-cell.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
14
examples/say-server/mcp-app.html
Normal file
14
examples/say-server/mcp-app.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<title>Say Widget</title>
|
||||
<link rel="stylesheet" href="/src/global.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/mcp-app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
17
examples/say-server/package.json
Normal file
17
examples/say-server/package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "@modelcontextprotocol/server-say",
|
||||
"version": "0.4.1",
|
||||
"private": true,
|
||||
"description": "Streaming TTS MCP App Server with karaoke-style text highlighting",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/modelcontextprotocol/ext-apps",
|
||||
"directory": "examples/say-server"
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "uv run server.py",
|
||||
"dev": "uv run server.py",
|
||||
"build": "echo 'No build step needed for Python server'"
|
||||
}
|
||||
}
|
||||
BIN
examples/say-server/screenshot.png
Normal file
BIN
examples/say-server/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
1457
examples/say-server/server.py
Executable file
1457
examples/say-server/server.py
Executable file
File diff suppressed because it is too large
Load diff
42
package-lock.json
generated
42
package-lock.json
generated
|
|
@ -617,6 +617,44 @@
|
|||
"name": "@modelcontextprotocol/server-qr",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"examples/say-server": {
|
||||
"name": "@modelcontextprotocol/server-say",
|
||||
"version": "0.4.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/ext-apps": "^0.4.1",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"concurrently": "^9.2.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^6.0.0",
|
||||
"vite-plugin-singlefile": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"examples/say-server/node_modules/@types/node": {
|
||||
"version": "22.19.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz",
|
||||
"integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"examples/say-server/node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"examples/scenario-modeler-server": {
|
||||
"name": "@modelcontextprotocol/server-scenario-modeler",
|
||||
"version": "0.4.1",
|
||||
|
|
@ -2480,6 +2518,10 @@
|
|||
"resolved": "examples/qr-server",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/server-say": {
|
||||
"resolved": "examples/say-server",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/server-scenario-modeler": {
|
||||
"resolved": "examples/scenario-modeler-server",
|
||||
"link": true
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ const ALL_SERVERS = [
|
|||
{ key: "map-server", name: "Map Server", dir: "map-server" },
|
||||
{ key: "pdf-server", name: "PDF Server", dir: "pdf-server" },
|
||||
{ key: "qr-server", name: "QR Code Server", dir: "qr-server" },
|
||||
{ key: "say-server", name: "Say Demo", dir: "say-server" },
|
||||
{
|
||||
key: "scenario-modeler",
|
||||
name: "SaaS Scenario Modeler",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const DYNAMIC_MASKS: Record<string, string[]> = {
|
|||
"basic-vue": ["#server-time"], // Server time display
|
||||
"cohort-heatmap": ['[class*="heatmapWrapper"]'], // Heatmap grid (random data)
|
||||
"customer-segmentation": [".chart-container"], // Scatter plot (random data)
|
||||
"say-server": [".playBtn", ".playOverlayBtn"], // Play buttons may have different states
|
||||
shadertoy: ["#canvas"], // WebGL shader canvas (animated)
|
||||
"system-monitor": [
|
||||
".chart-container", // CPU chart (highly dynamic)
|
||||
|
|
|
|||
BIN
tests/e2e/servers.spec.ts-snapshots/say-server.png
Normal file
BIN
tests/e2e/servers.spec.ts-snapshots/say-server.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Loading…
Reference in a new issue