---
phase: 15-wasm-widget-bridge
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - examples/wasm-client/src/lib.rs
  - crates/mcp-preview/src/wasm_builder.rs
  - crates/mcp-preview/src/handlers/wasm.rs
  - crates/mcp-preview/src/handlers/mod.rs
  - crates/mcp-preview/src/server.rs
  - crates/mcp-preview/src/lib.rs
autonomous: true
requirements: [WASM-01, WASM-03, WASM-04]

must_haves:
  truths:
    - "WASM client uses unique request IDs for each MCP call (no concurrent call corruption)"
    - "Preview server can trigger wasm-pack build and cache artifacts"
    - "Preview server serves WASM artifacts (.js, .wasm) at /wasm/* with correct MIME types"
    - "WASM build status endpoint reports NotBuilt, Building, Ready, or Failed"
    - "Missing wasm-pack returns clear error message with install instructions"
  artifacts:
    - path: "examples/wasm-client/src/lib.rs"
      provides: "WasmClient with AtomicU64 request ID counter"
      contains: "AtomicU64"
    - path: "crates/mcp-preview/src/wasm_builder.rs"
      provides: "WasmBuilder with build orchestration and caching"
      contains: "WasmBuilder"
    - path: "crates/mcp-preview/src/handlers/wasm.rs"
      provides: "API endpoints for WASM build trigger and artifact serving"
      contains: "trigger_build"
  key_links:
    - from: "crates/mcp-preview/src/handlers/wasm.rs"
      to: "crates/mcp-preview/src/wasm_builder.rs"
      via: "AppState.wasm_builder"
      pattern: "state\\.wasm_builder"
    - from: "crates/mcp-preview/src/server.rs"
      to: "crates/mcp-preview/src/handlers/wasm.rs"
      via: "axum route registration"
      pattern: "/wasm"
---

<objective>
Fix the WASM client's hardcoded request IDs and build the preview server infrastructure for WASM bridge mode: build automation, caching, artifact serving, and API endpoints.

Purpose: The WASM client currently corrupts concurrent calls due to hardcoded request IDs (known issue in STATE.md). The preview server needs to automate wasm-pack builds, cache output, and serve WASM artifacts before the frontend can offer a WASM bridge toggle.

Output: Fixed WASM client with AtomicU64 IDs, WasmBuilder module for build orchestration, and /wasm/* routes for artifact serving and build management.
</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/14-preview-bridge-infrastructure/14-01-SUMMARY.md
@.planning/phases/15-wasm-widget-bridge/15-RESEARCH.md
@examples/wasm-client/src/lib.rs
@crates/mcp-preview/src/server.rs
@crates/mcp-preview/src/handlers/mod.rs
@crates/mcp-preview/src/lib.rs
</context>

<tasks>

<task type="auto">
  <name>Task 1: Fix WASM client hardcoded request IDs and add AtomicU64 counter</name>
  <files>examples/wasm-client/src/lib.rs</files>
  <action>
Fix the known hardcoded request ID bug in the WASM client that causes concurrent call corruption.

In `examples/wasm-client/src/lib.rs`:

1. Add import: `use std::sync::atomic::{AtomicU64, Ordering};`
2. Add `next_request_id: AtomicU64` field to the `WasmClient` struct
3. Initialize in `WasmClient::new()`: `next_request_id: AtomicU64::new(1)`
4. Add helper method: `fn next_id(&self) -> i64 { self.next_request_id.fetch_add(1, Ordering::Relaxed) as i64 }`
5. Replace ALL hardcoded `id: 1i64.into()`, `id: 2i64.into()`, `id: 3i64.into()` occurrences with `id: self.next_id().into()`
6. Search exhaustively for any remaining hardcoded integer `.into()` patterns used as request IDs

This is a targeted bug fix -- do not refactor other parts of the WASM client. The client's connect(), list_tools(), and call_tool() methods all need unique IDs.
  </action>
  <verify>
`cargo check -p mcp-wasm-client` (or the actual package name in examples/wasm-client/Cargo.toml) compiles without errors. Grep for `1i64.into()`, `2i64.into()`, `3i64.into()` in the file -- should return zero matches. Grep for `next_id()` -- should match at least 3 call sites (connect, list_tools, call_tool).
  </verify>
  <done>Every MCP request in WasmClient uses a unique auto-incrementing ID from AtomicU64. No hardcoded request ID literals remain in the file.</done>
</task>

<task type="auto">
  <name>Task 2: Add WasmBuilder module and WASM API routes to preview server</name>
  <files>
    crates/mcp-preview/src/wasm_builder.rs
    crates/mcp-preview/src/handlers/wasm.rs
    crates/mcp-preview/src/handlers/mod.rs
    crates/mcp-preview/src/server.rs
    crates/mcp-preview/src/lib.rs
  </files>
  <action>
Create the WASM build orchestration and artifact serving infrastructure in mcp-preview.

**1. Create `crates/mcp-preview/src/wasm_builder.rs`:**

```rust
pub struct WasmBuilder {
    cache_dir: PathBuf,      // target/wasm-bridge/pkg/
    source_dir: PathBuf,     // examples/wasm-client/
    build_status: RwLock<BuildStatus>,
}

pub enum BuildStatus {
    NotBuilt,
    Building,
    Ready(PathBuf),  // Path to pkg/ directory with .js and .wasm files
    Failed(String),
}
```

Key behaviors:
- `WasmBuilder::new(source_dir, cache_dir)` -- constructor accepting paths
- `ensure_built() -> Result<PathBuf>` -- returns cached path if Ready, triggers build if NotBuilt, waits if Building, retries if Failed
- `build() -> Result<PathBuf>` -- runs `wasm-pack build --target web --out-name mcp_wasm_client --no-opt --out-dir {cache_dir}/pkg` with `current_dir(source_dir)`. Sets status to Building before start, Ready on success, Failed(error) on failure.
- `status() -> String` -- returns current build status as string ("not_built", "building", "ready", "failed: {msg}")
- `artifact_dir() -> Option<PathBuf>` -- returns Some(pkg_path) if Ready, None otherwise
- Check for `wasm-pack` binary existence before attempting build (use `which` crate or `Command::new("wasm-pack").arg("--version")`). If not found, set Failed with message: "WASM mode requires wasm-pack. Install with: cargo install wasm-pack && rustup target add wasm32-unknown-unknown"
- Use `tokio::sync::RwLock` for async-safe build locking. When a build is in progress, subsequent `ensure_built()` calls poll status until ready (loop with short sleep, not blocking wait).
- Also set env var `CARGO_PROFILE_RELEASE_LTO=false` during wasm-pack build for faster compilation.
- Cache check: if `{cache_dir}/pkg/mcp_wasm_client.js` and `{cache_dir}/pkg/mcp_wasm_client_bg.wasm` already exist on disk at startup, initialize status as `Ready`.

**2. Create `crates/mcp-preview/src/handlers/wasm.rs`:**

Three handler functions:

- `pub async fn trigger_build(State(state): State<Arc<AppState>>) -> impl IntoResponse` -- calls `state.wasm_builder.ensure_built()`, returns JSON `{"status": "ready"|"error", ...}`
- `pub async fn build_status(State(state): State<Arc<AppState>>) -> impl IntoResponse` -- returns JSON `{"status": state.wasm_builder.status()}`
- `pub async fn serve_artifact(State(state): State<Arc<AppState>>, Path(filename): Path<String>) -> impl IntoResponse` -- reads file from `wasm_builder.artifact_dir()/{filename}`, sets Content-Type based on extension (`.wasm` -> `application/wasm`, `.js` -> `application/javascript`, `.d.ts` -> `application/typescript`). Returns 404 if not built or file not found. This is critical for WASM-03 (CORS is handled at the MCP server level, but correct MIME types are needed for WASM streaming compilation).

**3. Update `crates/mcp-preview/src/handlers/mod.rs`:**
Add `pub mod wasm;`

**4. Update `crates/mcp-preview/src/server.rs`:**
- Add `wasm_builder: Arc<WasmBuilder>` field to `AppState`
- Initialize WasmBuilder in server setup: source_dir = detect `examples/wasm-client/` relative to project root (use the `mcp_server_url` config to determine workspace root, or accept as config), cache_dir = `target/wasm-bridge/`
- Register routes:
  - `POST /api/wasm/build` -> `handlers::wasm::trigger_build`
  - `GET /api/wasm/status` -> `handlers::wasm::build_status`
  - `GET /wasm/:filename` -> `handlers::wasm::serve_artifact`

**5. Update `crates/mcp-preview/src/lib.rs`:**
Add `pub mod wasm_builder;` to module declarations.

The WasmBuilder source_dir should default to finding `examples/wasm-client/` by walking up from the current working directory to find the workspace root (look for the workspace Cargo.toml). If not found, the builder gracefully reports "WASM client source not found" as a Failed status.

For WASM-04 (server URL injection): the preview server's existing `config.mcp_url` will be used by the frontend (Plan 02) to inject the URL into the WASM bridge. The backend just needs to expose the MCP URL via the existing `/api/config` endpoint (which it already does).
  </action>
  <verify>
`cargo check -p mcp-preview` compiles without errors. `cargo clippy -p mcp-preview -- -D warnings` passes with zero warnings. Verify wasm_builder.rs, handlers/wasm.rs exist and contain the expected types (WasmBuilder, BuildStatus, trigger_build, serve_artifact). Verify server.rs registers the /api/wasm/* and /wasm/* routes.
  </verify>
  <done>Preview server has WasmBuilder with build orchestration and caching, three WASM API endpoints (build trigger, status, artifact serving), and correct MIME type handling for .wasm files. AppState includes wasm_builder field. All routes registered.</done>
</task>

</tasks>

<verification>
1. `cargo check -p mcp-preview` -- full compilation succeeds
2. `cargo clippy -p mcp-preview -- -D warnings` -- zero warnings
3. Grep `examples/wasm-client/src/lib.rs` for hardcoded IDs: `grep -c "1i64\|2i64\|3i64" examples/wasm-client/src/lib.rs` returns 0
4. Grep for AtomicU64 usage: `grep -c "AtomicU64" examples/wasm-client/src/lib.rs` returns at least 1
5. Grep for WasmBuilder in server.rs: `grep "wasm_builder" crates/mcp-preview/src/server.rs` shows field in AppState
6. Grep for route registration: `grep "/wasm" crates/mcp-preview/src/server.rs` shows route mounts
</verification>

<success_criteria>
- WASM client request IDs are unique per call (AtomicU64 counter, no hardcoded values)
- WasmBuilder can detect wasm-pack, trigger builds, and cache results
- Preview server serves WASM artifacts with correct MIME types at /wasm/*
- Build trigger and status endpoints work at /api/wasm/build and /api/wasm/status
- Missing wasm-pack produces a clear human-readable error with install instructions
</success_criteria>

<output>
After completion, create `.planning/phases/15-wasm-widget-bridge/15-01-SUMMARY.md`
</output>
