---
phase: 37-add-with-ui-support-to-typedsynctool
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/server/typed_tool.rs
  - src/server/wasm_typed_tool.rs
autonomous: true
requirements:
  - P37-01
  - P37-02
  - P37-03
  - P37-04

must_haves:
  truths:
    - "TypedSyncTool supports with_ui() builder method that sets _meta in metadata()"
    - "WasmTypedTool supports with_ui() builder method that sets _meta in info()"
    - "TypedSyncTool without with_ui() still returns _meta: None"
    - "WasmTypedTool without with_ui() still returns _meta: None"
  artifacts:
    - path: "src/server/typed_tool.rs"
      provides: "TypedSyncTool with ui_resource_uri field, with_ui() builder, _meta emission in metadata()"
      contains: "fn with_ui"
    - path: "src/server/wasm_typed_tool.rs"
      provides: "WasmTypedTool with ui_resource_uri field, with_ui() builder, _meta emission in info()"
      contains: "fn with_ui"
  key_links:
    - from: "src/server/typed_tool.rs"
      to: "src/types/ui.rs"
      via: "ToolUIMetadata::build_meta_map()"
      pattern: "ToolUIMetadata::build_meta_map"
    - from: "src/server/wasm_typed_tool.rs"
      to: "src/types/ui.rs"
      via: "ToolUIMetadata::build_meta_map()"
      pattern: "ToolUIMetadata::build_meta_map"
---

<objective>
Add `with_ui()` builder method to `TypedSyncTool` and `WasmTypedTool` so all typed tool variants can associate with UI resources for MCP Apps.

Purpose: Achieve API parity with `TypedTool` (async) -- currently only the async variant supports `with_ui()`, leaving sync and WASM tool authors unable to declare UI associations.

Output: Both structs gain `with_ui()` method, `_meta` emission in their respective info methods, and unit tests.
</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/37-add-with-ui-support-to-typedsynctool/37-CONTEXT.md
@.planning/phases/37-add-with-ui-support-to-typedsynctool/37-RESEARCH.md

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

From src/server/typed_tool.rs (reference implementation -- TypedTool):
```rust
// Field on struct (line 36):
ui_resource_uri: Option<String>,

// Builder method (line 205-208):
pub fn with_ui(mut self, ui_resource_uri: impl Into<String>) -> Self {
    self.ui_resource_uri = Some(ui_resource_uri.into());
    self
}

// Metadata emission (line 229-243):
fn metadata(&self) -> Option<ToolInfo> {
    let meta = self.ui_resource_uri.as_ref().map(|uri| {
        crate::types::ui::ToolUIMetadata::build_meta_map(uri)
    });
    Some(ToolInfo {
        name: self.name.clone(),
        description: self.description.clone(),
        input_schema: self.input_schema.clone(),
        annotations: self.annotations.clone(),
        _meta: meta,
        execution: None,
    })
}
```

From src/server/typed_tool.rs (TypedSyncTool -- MODIFY):
```rust
// Struct (line 247-258) -- needs ui_resource_uri field added:
pub struct TypedSyncTool<T, F> {
    name: String,
    description: Option<String>,
    input_schema: Value,
    annotations: Option<ToolAnnotations>,
    // ADD: ui_resource_uri: Option<String>,
    handler: F,
    _phantom: PhantomData<T>,
}

// Constructors that need ui_resource_uri: None:
// - new() at line 282 (cfg schema-generation)
// - new_with_schema() at line 298

// metadata() at line 369-378 -- needs _meta emission:
fn metadata(&self) -> Option<ToolInfo> {
    Some(ToolInfo { ..., _meta: None, ... })  // Change None to conditional
}
```

From src/server/wasm_typed_tool.rs (WasmTypedTool -- MODIFY):
```rust
// Struct (line 22-32) -- needs ui_resource_uri field added:
pub struct WasmTypedTool<T, F> {
    name: String,
    description: Option<String>,
    input_schema: Value,
    // ADD: ui_resource_uri: Option<String>,
    handler: F,
    _phantom: PhantomData<T>,
}

// Constructors that need ui_resource_uri: None:
// - new() at line 41 (cfg schema-generation)
// - new_with_schema() at line 56

// info() at line 95-104 -- needs _meta emission:
fn info(&self) -> ToolInfo {
    ToolInfo { ..., _meta: None, ... }  // Change None to conditional
}
```

From src/types/ui.rs (canonical builder -- DO NOT MODIFY):
```rust
// ToolUIMetadata::build_meta_map() at line 276-289
// Returns Map<String, Value> with nested ui.resourceUri + openai/outputTemplate
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Add with_ui() to TypedSyncTool</name>
  <files>src/server/typed_tool.rs</files>
  <behavior>
    - Test: TypedSyncTool with with_ui("ui://widgets/chart.html") has _meta with nested ui.resourceUri and openai/outputTemplate
    - Test: TypedSyncTool without with_ui() has _meta: None in metadata()
  </behavior>
  <action>
    Mirror TypedTool::with_ui() exactly for TypedSyncTool:

    1. Add `ui_resource_uri: Option<String>` field to TypedSyncTool struct (after `annotations` field, before `handler`).

    2. Initialize `ui_resource_uri: None` in both constructors:
       - `new()` (cfg schema-generation, ~line 287)
       - `new_with_schema()` (~line 299)

    3. Add `with_ui()` builder method to the `impl TypedSyncTool` block (after `open_world()`, before closing brace ~line 351). Copy exact doc comment pattern from TypedTool::with_ui() but adapt example to use TypedSyncTool:
       ```rust
       pub fn with_ui(mut self, ui_resource_uri: impl Into<String>) -> Self {
           self.ui_resource_uri = Some(ui_resource_uri.into());
           self
       }
       ```

    4. Update `metadata()` in the `ToolHandler for TypedSyncTool` impl (~line 369) to emit `_meta` conditionally:
       ```rust
       fn metadata(&self) -> Option<ToolInfo> {
           let meta = self.ui_resource_uri.as_ref().map(|uri| {
               crate::types::ui::ToolUIMetadata::build_meta_map(uri)
           });
           Some(ToolInfo {
               name: self.name.clone(),
               description: self.description.clone(),
               input_schema: self.input_schema.clone(),
               annotations: self.annotations.clone(),
               _meta: meta,
               execution: None,
           })
       }
       ```

    5. Add two unit tests in the existing `mod tests` block at end of file (after the existing TypedTool tests):
       - `test_typed_sync_tool_metadata_with_ui_has_openai_output_template` -- uses `new_with_schema`, calls `.with_ui("ui://widgets/chart.html")`, asserts `_meta` has `ui.resourceUri` and `openai/outputTemplate`
       - `test_typed_sync_tool_metadata_without_ui_has_no_meta` -- uses `new_with_schema` without `.with_ui()`, asserts `_meta.is_none()`

    Follow exact test pattern from existing `test_typed_tool_metadata_with_ui_has_openai_output_template` test (line 694-715).
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo test --lib -- typed_tool::tests::test_typed_sync_tool 2>&1</automated>
  </verify>
  <done>TypedSyncTool has with_ui() builder, metadata() emits _meta when set, both tests pass</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Add with_ui() to WasmTypedTool</name>
  <files>src/server/wasm_typed_tool.rs</files>
  <behavior>
    - Test: WasmTypedTool with with_ui("ui://widgets/chart.html") has _meta with nested ui.resourceUri and openai/outputTemplate in info()
    - Test: WasmTypedTool without with_ui() has _meta: None in info()
  </behavior>
  <action>
    Mirror the same pattern for WasmTypedTool:

    1. Add `ui_resource_uri: Option<String>` field to WasmTypedTool struct (after `input_schema`, before `handler`, ~line 29).

    2. Initialize `ui_resource_uri: None` in both constructors:
       - `new()` (cfg schema-generation, ~line 46)
       - `new_with_schema()` (~line 57)

    3. Add `with_ui()` builder method to the `impl WasmTypedTool` block (after `with_schema_from()`, before closing brace ~line 78):
       ```rust
       /// Associate this tool with a UI resource (MCP Apps Extension).
       ///
       /// Sets `_meta.ui.resourceUri` and `openai/outputTemplate` in the tool's
       /// `ToolInfo` metadata for MCP and ChatGPT host compatibility.
       pub fn with_ui(mut self, ui_resource_uri: impl Into<String>) -> Self {
           self.ui_resource_uri = Some(ui_resource_uri.into());
           self
       }
       ```

    4. Update `info()` in the `WasmTool for WasmTypedTool` impl (~line 95) to emit `_meta` conditionally:
       ```rust
       fn info(&self) -> ToolInfo {
           let meta = self.ui_resource_uri.as_ref().map(|uri| {
               crate::types::ui::ToolUIMetadata::build_meta_map(uri)
           });
           ToolInfo {
               name: self.name.clone(),
               description: self.description.clone(),
               input_schema: self.input_schema.clone(),
               annotations: None,
               _meta: meta,
               execution: None,
           }
       }
       ```

    5. Add two unit tests in the existing `mod tests` block (~line 360):
       - `test_wasm_typed_tool_info_with_ui_has_openai_output_template` -- uses `new_with_schema`, calls `.with_ui("ui://widgets/chart.html")`, asserts `info()._meta` has `ui.resourceUri` and `openai/outputTemplate`
       - `test_wasm_typed_tool_info_without_ui_has_no_meta` -- uses `new_with_schema` without `.with_ui()`, asserts `info()._meta.is_none()`

    Note: WasmTypedTool uses `info()` not `metadata()` -- it implements WasmTool trait, not ToolHandler.
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo test --lib -- wasm_typed_tool::tests::test_wasm_typed_tool_info 2>&1</automated>
  </verify>
  <done>WasmTypedTool has with_ui() builder, info() emits _meta when set, both tests pass</done>
</task>

</tasks>

<verification>
```bash
# Full test suite for both modified files
cargo test --lib -- typed_tool wasm_typed_tool

# Clippy clean
cargo clippy -- -D warnings

# Build check
cargo build
```
</verification>

<success_criteria>
- TypedSyncTool.with_ui("uri") causes metadata() to return ToolInfo with _meta containing ui.resourceUri and openai/outputTemplate
- WasmTypedTool.with_ui("uri") causes info() to return ToolInfo with _meta containing ui.resourceUri and openai/outputTemplate
- Both structs without with_ui() return _meta: None (backward compatible)
- All 4 new tests pass
- Zero clippy warnings
- Existing tests unaffected
</success_criteria>

<output>
After completion, create `.planning/phases/37-add-with-ui-support-to-typedsynctool/37-01-SUMMARY.md`
</output>
