---
phase: 43-chatgpt-mcp-apps-alignment
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/types/protocol.rs
  - src/server/simple_resources.rs
  - src/server/core.rs
  - src/server/core_tests.rs
  - src/server/resource_watcher.rs
  - src/server/wasm_server_tests.rs
  - tests/server_subscriptions.rs
  - tests/typescript_interop.rs
  - tests/test_batch_requests.rs
  - examples/04_server_resources.rs
  - examples/18_resource_watcher.rs
  - examples/54_hybrid_workflow_execution.rs
  - examples/59_dynamic_resource_workflow.rs
  - examples/60_resource_only_steps.rs
  - examples/27-course-server-minimal/src/main.rs
  - examples/mcp-apps-dataviz/src/main.rs
  - examples/mcp-apps-chess/src/main.rs
  - examples/mcp-apps-map/src/main.rs
  - cargo-pmcp/src/templates/mcp_app.rs
autonomous: true
requirements: []

must_haves:
  truths:
    - "ResourceInfo includes _meta field serialized as JSON _meta key"
    - "tools/call responses contain only openai/toolInvocation/* keys in _meta"
    - "URI-to-tool-meta index is built at ServerCore construction time"
    - "All existing code compiles after ResourceInfo field addition"
  artifacts:
    - path: "src/types/protocol.rs"
      provides: "ResourceInfo with _meta field, filtered with_widget_enrichment"
      contains: "pub meta: Option<serde_json::Map"
    - path: "src/server/core.rs"
      provides: "uri_to_tool_meta field and builder on ServerCore"
      contains: "uri_to_tool_meta"
  key_links:
    - from: "src/types/protocol.rs"
      to: "src/server/core.rs"
      via: "ResourceInfo struct used in handler results"
      pattern: "ResourceInfo"
    - from: "src/server/core.rs"
      to: "src/types/protocol.rs"
      via: "with_widget_enrichment called with ToolInfo"
      pattern: "with_widget_enrichment"
---

<objective>
Add _meta field to ResourceInfo, build URI-to-tool-meta index on ServerCore, and filter tools/call _meta to only openai/toolInvocation/* keys.

Purpose: Foundation for ChatGPT MCP Apps alignment -- the ResourceInfo field, the index, and the filtering are prerequisites for the propagation logic in Plan 02.
Output: Compiling codebase with new ResourceInfo._meta field, URI-to-tool-meta index on ServerCore, and filtered with_widget_enrichment.
</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/43-chatgpt-mcp-apps-alignment/43-CONTEXT.md
@.planning/phases/43-chatgpt-mcp-apps-alignment/43-RESEARCH.md

@src/types/protocol.rs
@src/server/core.rs
@src/server/simple_resources.rs

<interfaces>
<!-- Key types the executor needs -->

From src/types/protocol.rs:
```rust
// ResourceInfo (line 898) -- adding meta field here
pub struct ResourceInfo {
    pub uri: String,
    pub name: String,
    pub description: Option<String>,
    pub mime_type: Option<String>,
}

// with_widget_enrichment (line 594) -- filtering _meta here
pub fn with_widget_enrichment(self, info: &ToolInfo, structured_value: Value) -> Self {
    if let Some(meta) = info.widget_meta() {
        self.with_structured_content(structured_value)
            .with_meta(meta.clone())
    } else {
        self
    }
}

// ToolInfo::widget_meta (line 407) -- used to detect widget tools
pub fn widget_meta(&self) -> Option<&serde_json::Map<String, Value>> { ... }
```

From src/server/core.rs:
```rust
// ServerCore fields (line 109-176) -- adding uri_to_tool_meta field
pub struct ServerCore {
    tool_infos: HashMap<String, ToolInfo>,
    // ... other fields
}

// ServerCore::new (line 181) -- building index from tool_infos here
pub fn new(
    info: Implementation,
    capabilities: ServerCapabilities,
    tools: HashMap<String, Arc<dyn ToolHandler>>,
    prompts: HashMap<String, Arc<dyn PromptHandler>>,
    tool_infos: HashMap<String, ToolInfo>,
    // ... more params
) -> Self { ... }
```

From src/types/ui.rs:
```rust
// deep_merge -- reusable for Plan 02
pub fn deep_merge(base: &mut serde_json::Map<String, Value>, overlay: serde_json::Map<String, Value>);
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add _meta field to ResourceInfo, filter with_widget_enrichment, build URI-to-tool-meta index</name>
  <files>src/types/protocol.rs, src/server/core.rs</files>
  <action>
**In `src/types/protocol.rs`:**

1. Add `meta` field to `ResourceInfo` struct (after `mime_type`):
```rust
/// Optional metadata (e.g., widget descriptor keys for ChatGPT MCP Apps)
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<serde_json::Map<String, serde_json::Value>>,
```
Follow the Phase 41 pattern: field named `meta`, serde renames to `_meta`.

2. Modify `with_widget_enrichment()` (line 594) to filter _meta to only `openai/toolInvocation/*` keys before applying:
```rust
pub fn with_widget_enrichment(self, info: &ToolInfo, structured_value: Value) -> Self {
    if let Some(meta) = info.widget_meta() {
        let filtered: serde_json::Map<String, Value> = meta
            .iter()
            .filter(|(k, _)| k.starts_with("openai/toolInvocation/"))
            .map(|(k, v)| (k.clone(), v.clone()))
            .collect();
        self.with_structured_content(structured_value)
            .with_meta(filtered)
    } else {
        self
    }
}
```
Per user decision: always filter regardless of host type. Use `starts_with` prefix match (Claude's discretion from CONTEXT.md).

3. Update the `test_resource_types` test (line 1631) to include `meta: None`.

**In `src/server/core.rs`:**

4. Add `uri_to_tool_meta` field to `ServerCore` struct (after `tool_infos`):
```rust
/// Cached URI-to-tool-meta index for widget resource _meta propagation.
/// Maps resource URIs to their linked tool's openai/* _meta keys.
uri_to_tool_meta: HashMap<String, serde_json::Map<String, serde_json::Value>>,
```

5. Add a private helper function `build_uri_to_tool_meta` (before `impl ServerCore`):
```rust
fn build_uri_to_tool_meta(
    tool_infos: &HashMap<String, ToolInfo>,
) -> HashMap<String, serde_json::Map<String, serde_json::Value>> {
    let mut map = HashMap::new();
    for info in tool_infos.values() {
        if let Some(meta) = info.widget_meta() {
            if let Some(serde_json::Value::String(uri)) = meta.get("openai/outputTemplate") {
                // First tool registered wins (per user decision)
                map.entry(uri.clone()).or_insert_with(|| {
                    meta.iter()
                        .filter(|(k, _)| k.starts_with("openai/"))
                        .map(|(k, v)| (k.clone(), v.clone()))
                        .collect()
                });
            }
        }
    }
    map
}
```

6. In `ServerCore::new()`, build the index from `tool_infos` and store it:
```rust
let uri_to_tool_meta = build_uri_to_tool_meta(&tool_infos);
// ... in Self { ... }
uri_to_tool_meta,
```
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo check 2>&1 | head -30</automated>
  </verify>
  <done>ResourceInfo has _meta field, with_widget_enrichment filters to openai/toolInvocation/* only, ServerCore has uri_to_tool_meta index built from tool_infos. Code compiles (will have errors from struct literals in other files -- fixed in Task 2).</done>
</task>

<task type="auto">
  <name>Task 2: Update all ResourceInfo struct literals across codebase</name>
  <files>src/server/simple_resources.rs, src/server/core_tests.rs, src/server/resource_watcher.rs, src/server/wasm_server_tests.rs, tests/server_subscriptions.rs, tests/typescript_interop.rs, tests/test_batch_requests.rs, examples/04_server_resources.rs, examples/18_resource_watcher.rs, examples/54_hybrid_workflow_execution.rs, examples/59_dynamic_resource_workflow.rs, examples/60_resource_only_steps.rs, examples/27-course-server-minimal/src/main.rs, examples/mcp-apps-dataviz/src/main.rs, examples/mcp-apps-chess/src/main.rs, examples/mcp-apps-map/src/main.rs, cargo-pmcp/src/templates/mcp_app.rs</files>
  <action>
Add `meta: None` to every `ResourceInfo { ... }` struct literal that is missing it. The exact locations (from grep):

**src/server/simple_resources.rs:**
- Line 87 (StaticResource::info) -- add `meta: None`
- Line 276 (UI resources in list()) -- add `meta: None`
- Line 286 (dynamic templates in list()) -- add `meta: None`

**src/server/core_tests.rs:**
- Line 112 (MockResourceHandler::new) -- add `meta: None`

**src/server/resource_watcher.rs:**
- Line 509 (test resource) -- add `meta: None`

**src/server/wasm_server_tests.rs:**
- Lines 230, 240 (paginated resources) -- add `meta: None`

**tests/server_subscriptions.rs:**
- Line 74 (map closure) -- add `meta: None`

**tests/typescript_interop.rs:**
- Line 67 -- add `meta: None`

**tests/test_batch_requests.rs:**
- Lines 46, 52 -- add `meta: None`

**examples/04_server_resources.rs:**
- Lines 82, 147, 153 -- add `meta: None`

**examples/18_resource_watcher.rs:**
- Line 97 -- add `meta: None`

**examples/54_hybrid_workflow_execution.rs:**
- Line 125 -- add `meta: None`

**examples/59_dynamic_resource_workflow.rs:**
- Lines 71, 77 -- add `meta: None`

**examples/60_resource_only_steps.rs:**
- Lines 128, 134, 140 -- add `meta: None`

**examples/27-course-server-minimal/src/main.rs:**
- Line 354 -- add `meta: None`

**examples/mcp-apps-dataviz/src/main.rs:**
- Line 291 -- add `meta: None`

**examples/mcp-apps-chess/src/main.rs:**
- Line 484 -- add `meta: None`

**examples/mcp-apps-map/src/main.rs:**
- Line 390 -- add `meta: None`

**cargo-pmcp/src/templates/mcp_app.rs:**
- Line 205 -- add `meta: None` (note: this is a template with `{{` braces, so the literal None does not need escaping)

**IMPORTANT:** Do NOT modify `src/server/workflow/conversion.rs` -- it has a DIFFERENT `ResourceInfo` struct (see Research Pitfall 2).

**Also:** Check `src/server/mod.rs` line 2419 -- this is inside a doc comment (`///`), NOT code. Only update if it would cause a doctest failure; otherwise leave it since doctests for commented-out code don't run.

After all updates, run `cargo check` to confirm compilation. If any additional struct literal sites surface from compilation errors, fix those too.
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo check --all-targets 2>&1 | tail -5</automated>
  </verify>
  <done>All ResourceInfo struct literals include meta: None. Full workspace compiles with `cargo check --all-targets`. Zero compilation errors.</done>
</task>

</tasks>

<verification>
```bash
# Full compilation check including all targets
cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo check --all-targets

# Verify ResourceInfo serializes _meta correctly
cargo test test_resource_types -- --nocapture

# Verify no clippy warnings
cargo clippy --all-targets -- -D warnings 2>&1 | tail -10
```
</verification>

<success_criteria>
- ResourceInfo has `meta` field that serializes as `_meta` in JSON
- with_widget_enrichment filters _meta to only `openai/toolInvocation/*` keys
- ServerCore has `uri_to_tool_meta` HashMap built from `tool_infos` at construction
- All code compiles with zero errors and zero clippy warnings
- Existing tests pass
</success_criteria>

<output>
After completion, create `.planning/phases/43-chatgpt-mcp-apps-alignment/43-01-SUMMARY.md`
</output>
