---
phase: 44-improving-mcp-preview-to-support-chatgpt-version
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - crates/mcp-preview/src/server.rs
  - crates/mcp-preview/src/proxy.rs
  - crates/mcp-preview/src/handlers/api.rs
  - crates/mcp-preview/src/lib.rs
  - cargo-pmcp/src/main.rs
  - cargo-pmcp/src/commands/preview.rs
autonomous: true
requirements:
  - P44-MODE
  - P44-CONFIG
  - P44-RESOURCEMETA

must_haves:
  truths:
    - "PreviewMode enum exists with Standard and ChatGpt variants"
    - "PreviewConfig has a mode field that defaults to Standard"
    - "CLI accepts --mode standard or --mode chatgpt flag"
    - "GET /api/config returns mode, descriptor_keys, and invocation_keys"
    - "ResourceInfo and ResourceContentItem deserialize _meta from MCP server"
    - "Terminal banner displays active mode prominently"
  artifacts:
    - path: "crates/mcp-preview/src/server.rs"
      provides: "PreviewMode enum, PreviewConfig.mode field, mode in startup banner"
      contains: "PreviewMode"
    - path: "crates/mcp-preview/src/proxy.rs"
      provides: "ResourceInfo._meta and ResourceContentItem._meta fields"
      contains: "pub meta: Option<Value>"
    - path: "crates/mcp-preview/src/handlers/api.rs"
      provides: "ConfigResponse with mode, descriptor_keys, invocation_keys"
      contains: "descriptor_keys"
    - path: "cargo-pmcp/src/main.rs"
      provides: "--mode CLI argument on Preview command"
      contains: "mode"
    - path: "cargo-pmcp/src/commands/preview.rs"
      provides: "Mode threading to PreviewConfig"
      contains: "PreviewMode"
  key_links:
    - from: "cargo-pmcp/src/main.rs"
      to: "cargo-pmcp/src/commands/preview.rs"
      via: "mode string passed to execute()"
      pattern: "mode"
    - from: "cargo-pmcp/src/commands/preview.rs"
      to: "crates/mcp-preview/src/server.rs"
      via: "PreviewConfig.mode field"
      pattern: "config\\.mode"
    - from: "crates/mcp-preview/src/server.rs"
      to: "crates/mcp-preview/src/handlers/api.rs"
      via: "AppState.config.mode read in get_config handler"
      pattern: "state\\.config\\.mode"
---

<objective>
Add PreviewMode enum and --mode CLI flag to mcp-preview, extend ConfigResponse to expose mode and ChatGPT descriptor keys to the browser, add _meta fields to proxy structs, and display mode in terminal banner.

Purpose: Establishes the Rust-side plumbing that Plan 02 (browser-side) depends on -- mode awareness, key constants served via API, and _meta passthrough from MCP server responses.

Output: All Rust files updated and compiling. Browser can read mode and validation keys from /api/config. Resources properly forward _meta from MCP server.
</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/44-improving-mcp-preview-to-support-chatgpt-version/44-CONTEXT.md
@.planning/phases/44-improving-mcp-preview-to-support-chatgpt-version/44-RESEARCH.md

@crates/mcp-preview/src/server.rs
@crates/mcp-preview/src/proxy.rs
@crates/mcp-preview/src/handlers/api.rs
@crates/mcp-preview/src/lib.rs
@cargo-pmcp/src/main.rs
@cargo-pmcp/src/commands/preview.rs

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

From src/types/ui.rs:
```rust
pub const CHATGPT_DESCRIPTOR_KEYS: &[&str] = &[
    "openai/outputTemplate",
    "openai/toolInvocation/invoking",
    "openai/toolInvocation/invoked",
    "openai/widgetAccessible",
];
```

From crates/mcp-preview/src/server.rs:
```rust
pub struct PreviewConfig {
    pub mcp_url: String,
    pub port: u16,
    pub initial_tool: Option<String>,
    pub theme: String,
    pub locale: String,
    pub widgets_dir: Option<PathBuf>,
}
```

From crates/mcp-preview/src/lib.rs:
```rust
pub use server::{PreviewConfig, PreviewServer};
```

From cargo-pmcp/src/main.rs (Preview variant of Commands enum, lines 181-207):
```rust
Preview {
    #[arg(long)] url: String,
    #[arg(long, default_value = "8765")] port: u16,
    #[arg(long)] open: bool,
    #[arg(long)] tool: Option<String>,
    #[arg(long, default_value = "light")] theme: String,
    #[arg(long, default_value = "en-US")] locale: String,
    widgets_dir: Option<String>,
},
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add PreviewMode enum, extend PreviewConfig and proxy structs</name>
  <files>crates/mcp-preview/src/server.rs, crates/mcp-preview/src/proxy.rs, crates/mcp-preview/src/lib.rs</files>
  <action>
1. In `server.rs`, add a `PreviewMode` enum above `PreviewConfig`:
   ```rust
   #[derive(Debug, Clone, PartialEq)]
   pub enum PreviewMode {
       Standard,
       ChatGpt,
   }

   impl Default for PreviewMode {
       fn default() -> Self { Self::Standard }
   }

   impl std::fmt::Display for PreviewMode {
       fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
           match self {
               Self::Standard => write!(f, "standard"),
               Self::ChatGpt => write!(f, "chatgpt"),
           }
       }
   }
   ```

2. Add `pub mode: PreviewMode` field to `PreviewConfig` (after `widgets_dir`). Update `Default` impl to set `mode: PreviewMode::default()`.

3. In the startup banner in `PreviewServer::start()`, add a Mode line after the Widgets Dir conditional block (before the closing bar):
   ```rust
   println!(
       "\x1b[1;36m||\x1b[0m  Mode:       {:<30}   \x1b[1;36m||\x1b[0m",
       match config.mode {
           PreviewMode::ChatGpt => "\x1b[1;31mChatGPT Strict\x1b[0m",
           PreviewMode::Standard => "\x1b[1;32mStandard\x1b[0m",
       }
   );
   ```

4. In `lib.rs`, update the pub use to also export `PreviewMode`:
   ```rust
   pub use server::{PreviewConfig, PreviewMode, PreviewServer};
   ```

5. In `proxy.rs`, add `_meta` field to `ResourceInfo`:
   ```rust
   #[serde(rename = "_meta", skip_serializing_if = "Option::is_none", default)]
   pub meta: Option<Value>,
   ```
   NOTE: Do NOT add `rename_all = "camelCase"` to the serde attribute since it already has it -- just add the field. The `_meta` rename is explicit and overrides camelCase.

6. In `proxy.rs`, add `_meta` field to `ResourceContentItem` with the same serde attributes.

7. In `proxy.rs`, add `_meta` to `ResourceReadResult` -- actually no, `ResourceReadResult` just wraps `contents: Vec<ResourceContentItem>` which already gets the field. But also add a top-level meta field:
   ```rust
   #[serde(rename = "_meta", skip_serializing_if = "Option::is_none", default)]
   pub meta: Option<Value>,
   ```
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo check -p mcp-preview 2>&1 | tail -5</automated>
  </verify>
  <done>PreviewMode enum exists and is exported. PreviewConfig has mode field. ResourceInfo and ResourceContentItem have _meta fields. Startup banner shows mode. All compiles.</done>
</task>

<task type="auto">
  <name>Task 2: Add --mode CLI flag and extend ConfigResponse with mode and keys</name>
  <files>cargo-pmcp/src/main.rs, cargo-pmcp/src/commands/preview.rs, crates/mcp-preview/src/handlers/api.rs</files>
  <action>
1. In `cargo-pmcp/src/main.rs`, add a `mode` field to the `Preview` variant of the `Commands` enum:
   ```rust
   /// Preview mode: standard (default) or chatgpt (strict ChatGPT protocol validation)
   #[arg(long, default_value = "standard")]
   mode: String,
   ```

2. In the match arm for `Commands::Preview` (around line 374), add `mode` to the destructuring and pass it to `preview::execute()`.

3. In `cargo-pmcp/src/commands/preview.rs`, update the `execute()` function signature to accept `mode: String`. Inside the function:
   - Parse the mode string: `let preview_mode = if mode == "chatgpt" { mcp_preview::PreviewMode::ChatGpt } else { mcp_preview::PreviewMode::Standard };`
   - Set `mode: preview_mode` on the `PreviewConfig` struct.
   - In the `should_output()` block, add a line showing the mode (e.g., "Mode: ChatGPT Strict" or "Mode: Standard").

4. In `crates/mcp-preview/src/handlers/api.rs`, extend `ConfigResponse` with three new fields:
   ```rust
   pub mode: String,
   pub descriptor_keys: Vec<String>,
   pub invocation_keys: Vec<String>,
   ```

5. Update `get_config()` handler to populate these fields:
   - `mode: state.config.mode.to_string()` (uses the Display impl)
   - `descriptor_keys`: Hard-code the 4 ChatGPT descriptor keys as `vec!["openai/outputTemplate".into(), "openai/toolInvocation/invoking".into(), "openai/toolInvocation/invoked".into(), "openai/widgetAccessible".into()]`. NOTE: mcp-preview does not depend on the pmcp crate directly, so we cannot import `CHATGPT_DESCRIPTOR_KEYS`. Hard-code with a comment: `// Mirror of pmcp::types::ui::CHATGPT_DESCRIPTOR_KEYS`
   - `invocation_keys`: `vec!["openai/toolInvocation/invoking".into(), "openai/toolInvocation/invoked".into()]`

6. In `list_resources()` handler, update the proxy resource serialization to include `_meta`:
   Change the `json!({...})` for proxy resources to include `"_meta": r.meta`.

7. In `read_resource()` handler, update the proxy fallback to include `_meta` from `ResourceContentItem`:
   Change `json!({ "contents": result.contents })` to serialize the full struct (which now includes _meta via serde). This already works since `result.contents` is a `Vec<ResourceContentItem>` with Serialize derived.
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo check -p cargo-pmcp 2>&1 | tail -5</automated>
  </verify>
  <done>--mode chatgpt flag accepted by CLI. ConfigResponse returns mode, descriptor_keys, and invocation_keys. Resource handlers pass through _meta. Full workspace compiles.</done>
</task>

</tasks>

<verification>
- `cargo check -p mcp-preview` compiles with no errors
- `cargo check -p cargo-pmcp` compiles with no errors
- `cargo pmcp preview --help` shows `--mode` flag with default "standard"
- `cargo clippy -p mcp-preview -p cargo-pmcp -- -D warnings` passes
</verification>

<success_criteria>
- PreviewMode enum with Standard/ChatGpt variants is exported from mcp-preview
- PreviewConfig.mode field defaults to Standard
- CLI --mode flag accepts "standard" (default) and "chatgpt"
- GET /api/config response includes mode, descriptor_keys (4 keys), invocation_keys (2 keys)
- ResourceInfo and ResourceContentItem structs deserialize _meta from MCP server responses
- Terminal banner displays active mode
- Workspace compiles cleanly (cargo check, clippy)
</success_criteria>

<output>
After completion, create `.planning/phases/44-improving-mcp-preview-to-support-chatgpt-version/44-01-SUMMARY.md`
</output>
