---
phase: 19-ship-examples-and-playwright-e2e
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - examples/mcp-apps-dataviz/Cargo.toml
  - examples/mcp-apps-dataviz/src/main.rs
  - examples/mcp-apps-dataviz/widgets/dashboard.html
  - examples/mcp-apps-dataviz/mock-data/sample.json
  - examples/mcp-apps-dataviz/README.md
  - Cargo.toml
  - justfile
  - crates/mcp-e2e-tests/Cargo.toml
  - crates/mcp-e2e-tests/src/lib.rs
files_deleted:
  - tests/playwright/ (entire directory: playwright.config.ts, fixtures/mock-mcp-bridge.ts, tests/chess-widget.spec.ts, serve.js, package.json, tsconfig.json)
  - examples/mcp-apps-chess/widget/ (legacy singular directory, if exists)
  - examples/mcp-apps-map/widget/ (legacy singular directory, if exists)
autonomous: true
requirements: [SHIP-01, SHIP-02]

must_haves:
  truths:
    - "Running `cd examples/mcp-apps-chess && cargo build` compiles the chess example without errors (examples are intentionally excluded from the workspace and built independently per RESEARCH.md Pitfall 3)"
    - "Running `cd examples/mcp-apps-map && cargo build` compiles the map example without errors (standalone build, not workspace member)"
    - "Running `cd examples/mcp-apps-dataviz && cargo build` compiles the data viz example without errors (standalone build, not workspace member)"
    - "Data viz example has an execute_query tool that accepts SQL and returns columns + rows as JSON"
    - "Data viz widget (dashboard.html) loads Chart.js from CDN and renders bar, line, or pie charts from tool call results"
    - "Data viz widget includes a sortable data table that populates from query results"
    - "Data viz widget has a chart type switcher (bar/line/pie)"
    - "Data viz widget uses these exact element IDs for E2E test contracts: `id=\"chart\"` (canvas), `id=\"chartType\"` (select), `id=\"dataTable\"` (table), `id=\"queryInput\"` (textarea)"
    - "A justfile exists at workspace root with recipes: run-chess, run-map, run-dataviz"
    - "`cargo check -p mcp-e2e-tests` compiles the placeholder crate successfully"
  artifacts:
    - path: "examples/mcp-apps-dataviz/Cargo.toml"
      provides: "Data viz example crate definition"
      contains: "mcp-apps-dataviz"
    - path: "examples/mcp-apps-dataviz/src/main.rs"
      provides: "Chinook-based data viz MCP server with execute_query tool and widget resource handler"
      contains: "execute_query"
    - path: "examples/mcp-apps-dataviz/widgets/dashboard.html"
      provides: "Interactive data visualization widget with Chart.js charts and data table"
      contains: "chart.js"
    - path: "examples/mcp-apps-dataviz/mock-data/sample.json"
      provides: "Canned query result for landing page mock bridge"
      contains: "columns"
    - path: "justfile"
      provides: "Just recipes for running examples and E2E tests"
      contains: "run-chess"
    - path: "Cargo.toml"
      provides: "Updated workspace members including mcp-e2e-tests"
      contains: "mcp-e2e-tests"
  key_links:
    - from: "examples/mcp-apps-dataviz/src/main.rs"
      to: "examples/mcp-apps-dataviz/widgets/dashboard.html"
      via: "WidgetDir reads HTML from widgets/ directory at runtime"
      pattern: "WidgetDir::new"
    - from: "justfile"
      to: "examples/mcp-apps-chess/src/main.rs"
      via: "just run-chess executes cd examples/mcp-apps-chess && cargo run"
      pattern: "run-chess"
---

<objective>
Ship three MCP App examples (chess, map, data visualization) that compile and run, create a justfile for ergonomic recipes, and prepare the workspace for the E2E test crate.

Purpose: Developers need working, diverse example apps demonstrating the MCP Apps widget pipeline. The chess and map examples already exist but need cleanup; the data viz example is new and extends the Chinook SQLite pattern. The justfile replaces ad-hoc scripts per the project convention (CLAUDE.md mandates justfile over Makefile).

Output: Three standalone example apps that compile and run independently, a workspace-root justfile, and workspace configuration updated for the upcoming E2E test crate.
</objective>

<execution_context>
@./.claude/get-shit-done/workflows/execute-plan.md
@./.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/19-ship-examples-and-playwright-e2e/19-CONTEXT.md
@.planning/phases/19-ship-examples-and-playwright-e2e/19-RESEARCH.md

<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->

From examples/mcp-apps-chess/Cargo.toml (existing pattern to follow for dataviz):
```toml
[package]
name = "mcp-apps-chess"
version = "0.1.0"
edition = "2021"
description = "Chess MCP Server with interactive UI widget"
publish = false

[[bin]]
name = "mcp-apps-chess"
path = "src/main.rs"

[dependencies]
pmcp = { path = "../..", features = ["full"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
schemars = "1.0"
async-trait = "0.1"
```

From examples/mcp-apps-chess/src/main.rs (MCP App server pattern):
```rust
use pmcp::server::mcp_apps::{ChatGptAdapter, UIAdapter, WidgetDir};
use pmcp::server::streamable_http_server::{StreamableHttpServer, StreamableHttpServerConfig};
use pmcp::server::ServerBuilder;
use pmcp::types::mcp_apps::{ExtendedUIMimeType, WidgetMeta};
// ... tool_typed_sync_with_description for each tool
// ... WidgetDir::new(widgets_path) for file-based widget serving
// ... StreamableHttpServer::with_config for HTTP serving
```

From cargo-pmcp/src/templates/sqlite_explorer.rs (Chinook SQL pattern to reference):
```rust
// The data viz example follows this pattern:
// - Accept SQL query as tool input
// - Open SQLite database (rusqlite)
// - Execute query, collect column names + rows
// - Return as JSON { columns: [...], rows: [[...], ...] }
// Developer downloads Chinook.db manually via curl (documented in README)
```

From root Cargo.toml workspace section (current state):
```toml
[workspace]
members = ["pmcp-macros", "crates/mcp-tester", "crates/mcp-preview", "crates/pmcp-tasks", "examples/25-oauth-basic", "examples/test-basic", "cargo-pmcp"]
exclude = ["fuzz", "examples/cloudflare-worker-mcp", "examples/26-server-tester", "examples/27-course-server-minimal", "examples/wasm-client", "examples/mcp-apps-chess", "examples/mcp-apps-map"]
```

From tests/playwright/serve.js (legacy URL mappings to note):
```javascript
const urlMappings = {
    '/chess/board.html': '/mcp-apps-chess/widget/board.html',  // Note: singular "widget" is legacy
    '/map/explorer.html': '/mcp-apps-map/widget/map.html',      // Note: singular "widget" is legacy
};
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create data visualization MCP App example</name>
  <files>
    examples/mcp-apps-dataviz/Cargo.toml
    examples/mcp-apps-dataviz/src/main.rs
    examples/mcp-apps-dataviz/widgets/dashboard.html
    examples/mcp-apps-dataviz/mock-data/sample.json
    examples/mcp-apps-dataviz/README.md
  </files>
  <action>
Create a new `examples/mcp-apps-dataviz/` directory with the full MCP App example structure.

**Cargo.toml:**
```toml
[package]
name = "mcp-apps-dataviz"
version = "0.1.0"
edition = "2021"
description = "Data Visualization MCP Server with interactive Chart.js widget (Chinook SQLite Explorer)"
publish = false

[[bin]]
name = "mcp-apps-dataviz"
path = "src/main.rs"

[dependencies]
pmcp = { path = "../..", features = ["full"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
schemars = "1.0"
async-trait = "0.1"
rusqlite = { version = "0.34", features = ["bundled"] }
```

**src/main.rs** -- Chinook SQLite Explorer MCP server:

1. Define tool input types:
   - `ExecuteQueryInput` with fields: `sql: String` (the SQL query to execute).
   - `ListTablesInput` with no fields (empty struct).
   - `DescribeTableInput` with field: `table_name: String`.

2. Implement tool handlers following the chess/map pattern (`fn handler(input: T, _extra: RequestHandlerExtra) -> Result<Value>`):
   - `execute_query_handler`: Opens Chinook.db from `CARGO_MANIFEST_DIR` (with helpful error if file not found telling user to download it). Prepares the SQL statement. Collects column names from `stmt.column_names()`. Iterates rows collecting each value as `serde_json::Value` (handle Text, Integer, Real, Null). Returns `json!({ "columns": columns, "rows": rows, "row_count": rows.len() })`.
   - `list_tables_handler`: Executes `SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`. Returns `json!({ "tables": table_names })`.
   - `describe_table_handler`: Executes `PRAGMA table_info({table_name})` (validate table name contains only alphanumeric/underscore to prevent injection). Returns `json!({ "table_name": name, "columns": [{ "name": ..., "type": ..., "nullable": ..., "primary_key": ... }] })`.

3. Implement `DataVizResources` struct following the exact pattern from chess/map examples:
   - Constructor takes `widgets_path: PathBuf`, creates `WidgetDir::new(widgets_path)` and `ChatGptAdapter::new().with_widget_meta(...)`.
   - Implement `ResourceHandler` with `read()` and `list()` methods matching the chess example.

4. Main function:
   - Port 3002 default (chess=3000, map=3001).
   - Register three tools: `execute_query`, `list_tables`, `describe_table`.
   - Print "Data Viz MCP Server running at http://{addr}" with available tools list.
   - Follow the exact `StreamableHttpServer::with_config` pattern from chess/map.

**widgets/dashboard.html** -- Interactive data visualization widget:

**CRITICAL -- E2E Selector Contract (cross-referenced with 19-02-PLAN Task 2):**
The following element IDs are contractual -- E2E tests depend on them. Do NOT rename.
- `id="chart"` -- `<canvas>` element for Chart.js rendering
- `id="chartType"` -- `<select>` element for chart type switching (bar/line/pie)
- `id="dataTable"` -- `<table>` element with `<thead>` and `<tbody>` for tabular results
- `id="queryInput"` -- `<textarea>` element for SQL query input
- `id="runQueryBtn"` -- `<button>` element to execute the query
- `id="loading"` -- loading indicator div (matches chess/map pattern)

1. HTML structure (required IDs listed above MUST be used exactly):
   - Header with title "Data Explorer" and a SQL input area: `<textarea id="queryInput">`.
   - Pre-filled default query: `SELECT g.Name AS Genre, COUNT(t.TrackId) AS TrackCount FROM Genre g JOIN Track t ON g.GenreId = t.GenreId GROUP BY g.Name ORDER BY TrackCount DESC LIMIT 10`
   - "Run Query" button: `<button id="runQueryBtn">`.
   - Chart type switcher: `<select id="chartType">` with options: Bar, Line, Pie.
   - `<canvas id="chart">` for Chart.js rendering.
   - `<table id="dataTable">` with `<thead>` and `<tbody>` for tabular results.
   - Column headers in the data table should be clickable for sorting (ascending/descending toggle).
   - Loading indicator: `<div id="loading" class="loading">` matching chess/map pattern.

2. JavaScript:
   - Load Chart.js via CDN: `<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>`.
   - On init: call `list_tables` tool to populate a table list, then call `execute_query` with the default query.
   - `renderChart(columns, rows, chartType)`: Destroy existing chart if any (`chart.destroy()`). Create new `Chart()` with the selected type. Use first column as labels, second column as data values. Apply a pleasant color palette.
   - `renderTable(columns, rows)`: Clear table, create `<th>` for each column name (with click handler for sorting), populate `<tr>/<td>` for each row.
   - Chart type switcher: On change, call `renderChart()` with existing data and new chart type (no new tool call needed).
   - Sorting: Track sort column index and direction (asc/desc). On header click, sort rows array, re-render table body.
   - Bridge integration: Use `window.mcpBridge?.callTool(name, args)` matching chess/map pattern. Save/load state via `mcpBridge.setState/getState`.

3. Styling: Match the chess/map dark theme (`background: #1a1a2e`, `color: #eee`, same font stack, same button styles). The chart canvas should fill available width. The data table should have alternating row colors and be horizontally scrollable on narrow viewports.

**mock-data/sample.json** -- Canned data for landing page mock bridge:
```json
{
  "execute_query": {
    "columns": ["Genre", "TrackCount"],
    "rows": [
      ["Rock", 1297],
      ["Latin", 579],
      ["Metal", 374],
      ["Alternative & Punk", 332],
      ["Jazz", 130],
      ["Blues", 81],
      ["Classical", 74],
      ["R&B/Soul", 61],
      ["Reggae", 58],
      ["Pop", 48]
    ],
    "row_count": 10
  },
  "list_tables": {
    "tables": ["Album", "Artist", "Customer", "Employee", "Genre", "Invoice", "InvoiceLine", "MediaType", "Playlist", "PlaylistTrack", "Track"]
  }
}
```

**README.md** -- Short README:
- Title: "Data Visualization MCP App"
- Prerequisites: Download Chinook.db: `curl -L -o Chinook.db https://github.com/lerocha/chinern-database/releases/download/v1.4.5/Chinook_Sqlite.sqlite`
  (Note: verify URL at implementation time; if the exact URL differs, use the closest working download link and rename to `Chinook.db`)
- Running: `cargo run` (prints URL)
- Available tools: execute_query, list_tables, describe_table
- Widget: Interactive dashboard with Chart.js charts and sortable data table
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk/examples/mcp-apps-dataviz && cargo check 2>&1 | tail -10</automated>
  </verify>
  <done>Data viz example directory exists with Cargo.toml, src/main.rs, widgets/dashboard.html, mock-data/sample.json, and README.md; cargo check compiles without errors (database file not needed for compilation check)</done>
</task>

<task type="auto">
  <name>Task 2: Clean up legacy files, update workspace, and create justfile</name>
  <files>
    Cargo.toml
    justfile
  </files>
  <action>
**Remove legacy directories and files:**

1. Delete `tests/playwright/` directory entirely (6 files: playwright.config.ts, fixtures/mock-mcp-bridge.ts, tests/chess-widget.spec.ts, serve.js, package.json, tsconfig.json). These are replaced by the Rust E2E test crate in plan 19-02.

2. Check if `examples/mcp-apps-chess/widget/` (singular) directory exists alongside `widgets/` (plural). If it does, remove the singular one. The canonical location is `widgets/` per Phase 17 convention.

3. Check if `examples/mcp-apps-map/widget/` (singular) directory exists alongside `widgets/` (plural). If it does, remove the singular one.

**Update root Cargo.toml workspace configuration:**

1. Add `"crates/mcp-e2e-tests"` to the `[workspace] members` list. This prepares for plan 19-02 where the crate is created. The build will fail if the directory doesn't exist yet, but we need the entry present so plan 19-02 doesn't have to modify Cargo.toml. To handle this cleanly: create a minimal `crates/mcp-e2e-tests/Cargo.toml` and `crates/mcp-e2e-tests/src/lib.rs` placeholder now.

   Minimal Cargo.toml for the placeholder:
   ```toml
   [package]
   name = "mcp-e2e-tests"
   version = "0.1.0"
   edition = "2021"
   publish = false
   description = "End-to-end browser tests for MCP App widgets"

   [dependencies]
   ```

   Minimal src/lib.rs:
   ```rust
   //! E2E browser tests for MCP App widgets.
   //!
   //! Uses chromiumoxide (CDP) for headless browser automation
   //! and axum for embedded widget file serving.
   ```

2. Add `"examples/mcp-apps-dataviz"` to the workspace `exclude` list (alongside existing chess/map exclusions). All three example apps are standalone binaries compiled separately, not as workspace members.

**Create justfile at workspace root:**

Create a `justfile` (lowercase, no extension) with recipes. Use `just` syntax (not Make syntax). Key differences from Makefile: no `.PHONY`, no `$(SHELL)`, use backticks for commands, `@` prefix suppresses echo.

```just
# PMCP SDK development recipes

# Default recipe: show available recipes
default:
    @just --list

# === Examples ===

# Run the chess MCP App example
run-chess:
    cd examples/mcp-apps-chess && cargo run

# Run the map MCP App example
run-map:
    cd examples/mcp-apps-map && cargo run

# Run the data visualization MCP App example
run-dataviz:
    cd examples/mcp-apps-dataviz && cargo run

# === E2E Tests ===

# Run all E2E widget tests
test-e2e:
    cargo test -p mcp-e2e-tests -- --test-threads=1

# Run chess widget E2E tests
test-e2e-chess:
    cargo test -p mcp-e2e-tests chess -- --test-threads=1

# Run map widget E2E tests
test-e2e-map:
    cargo test -p mcp-e2e-tests map -- --test-threads=1

# Run data viz widget E2E tests
test-e2e-dataviz:
    cargo test -p mcp-e2e-tests dataviz -- --test-threads=1

# Pre-download Chromium for E2E tests (useful for CI)
setup-e2e:
    cargo test -p mcp-e2e-tests --no-run

# === Quality ===

# Run quality gate (format + clippy + build + test)
quality-gate:
    cargo fmt --check
    cargo clippy -- -D warnings
    cargo build
    cargo test --lib --tests -- --test-threads=1

# Run all tests
test:
    cargo test -- --test-threads=1

# Format code
fmt:
    cargo fmt

# Run clippy
clippy:
    cargo clippy -- -D warnings
```

After creating justfile, verify it works: `just --list` should show all recipes.
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && just --list 2>&1 | head -20 && cargo check -p mcp-e2e-tests 2>&1 | tail -5</automated>
  </verify>
  <done>tests/playwright/ removed; legacy singular widget/ directories removed if they existed; workspace members includes mcp-e2e-tests placeholder; workspace exclude includes mcp-apps-dataviz; justfile exists with all required recipes; just --list shows recipes; cargo check -p mcp-e2e-tests succeeds on placeholder</done>
</task>

</tasks>

<verification>
1. `cd examples/mcp-apps-chess && cargo check` compiles without errors (standalone, not workspace member)
2. `cd examples/mcp-apps-map && cargo check` compiles without errors (standalone, not workspace member)
3. `cd examples/mcp-apps-dataviz && cargo check` compiles without errors (standalone, not workspace member)
4. `test ! -d tests/playwright` (playwright directory removed)
5. `test ! -d examples/mcp-apps-chess/widget` (legacy singular widget/ removed, only widgets/ remains)
6. `test ! -d examples/mcp-apps-map/widget` (legacy singular widget/ removed, only widgets/ remains)
7. `test -f justfile` (justfile exists at root)
8. `just --list` shows run-chess, run-map, run-dataviz, test-e2e recipes
9. `cargo check -p mcp-e2e-tests` compiles placeholder crate
10. `grep 'mcp-e2e-tests' Cargo.toml` confirms workspace membership
11. `grep 'mcp-apps-dataviz' Cargo.toml` confirms workspace exclusion
12. Dataviz widget contains contractual element IDs: `grep -c 'id="chart"\|id="chartType"\|id="dataTable"\|id="queryInput"' examples/mcp-apps-dataviz/widgets/dashboard.html` returns 4+
</verification>

<success_criteria>
- All three MCP App examples (chess, map, dataviz) compile independently via `cd examples/mcp-apps-X && cargo check`
- Data viz example has execute_query, list_tables, and describe_table tools with a Chart.js + data table widget
- The tests/playwright/ directory no longer exists
- A justfile at workspace root provides `just run-chess`, `just run-map`, `just run-dataviz` recipes
- Workspace is configured with mcp-e2e-tests as a member (placeholder crate) and mcp-apps-dataviz in exclude list
</success_criteria>

<output>
After completion, create `.planning/phases/19-ship-examples-and-playwright-e2e/19-01-SUMMARY.md`
</output>
