---
phase: 26-add-oauth-support-to-load-testing
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - Cargo.toml
  - src/client/oauth.rs
  - src/client/mod.rs
autonomous: true
requirements:
  - OAUTH-01
  - OAUTH-02

must_haves:
  truths:
    - "OAuthHelper, OAuthConfig, and default_cache_path are available at pmcp::client::oauth behind an 'oauth' feature flag"
    - "OAuthHelper::create_middleware_chain() returns Arc<HttpMiddlewareChain> using the existing BearerToken + OAuthClientMiddleware types"
    - "A convenience function create_oauth_middleware(config) is exported at module level"
    - "All colored terminal output is replaced with tracing calls (info, debug, warn) -- no colored crate dependency"
    - "Error types use pmcp::error::Error (anyhow::Result replaced with pmcp::error::Result)"
    - "cargo build -p pmcp --features oauth compiles cleanly"
  artifacts:
    - path: "src/client/oauth.rs"
      provides: "OAuthHelper, OAuthConfig, TokenCache, default_cache_path, create_oauth_middleware"
      exports: ["OAuthHelper", "OAuthConfig", "default_cache_path", "create_oauth_middleware"]
    - path: "src/client/mod.rs"
      provides: "pub mod oauth re-export gated behind #[cfg(feature = \"oauth\")]"
      contains: "pub mod oauth"
    - path: "Cargo.toml"
      provides: "oauth feature flag with deps: webbrowser, dirs, rand"
      contains: "oauth"
  key_links:
    - from: "src/client/oauth.rs"
      to: "src/client/oauth_middleware.rs"
      via: "imports BearerToken and OAuthClientMiddleware to build middleware chain"
      pattern: "use crate::client::oauth_middleware"
    - from: "src/client/oauth.rs"
      to: "src/client/http_middleware.rs"
      via: "imports HttpMiddlewareChain to return from create_middleware_chain"
      pattern: "use crate::client::http_middleware::HttpMiddlewareChain"
    - from: "src/client/oauth.rs"
      to: "src/client/auth"
      via: "imports OidcDiscoveryClient, TokenExchangeClient for OAuth flows"
      pattern: "use crate::client::auth"
---

<objective>
Move OAuthHelper from crates/mcp-tester/src/oauth.rs into the core pmcp SDK at src/client/oauth.rs, feature-gated behind an `oauth` feature flag.

Purpose: OAuthHelper is currently trapped in the mcp-tester application crate. Moving it to the core SDK makes it reusable by any tool (mcp-tester, cargo-pmcp loadtest, future tools) with minimal wiring. This is the foundation that enables plans 02, 03, and 04.

Output: `src/client/oauth.rs` with OAuthHelper, OAuthConfig, default_cache_path, and create_oauth_middleware. Feature-gated behind `oauth` in Cargo.toml.
</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/26-add-oauth-support-to-load-testing/26-CONTEXT.md

<interfaces>
<!-- Source code being moved: crates/mcp-tester/src/oauth.rs -->
<!-- Target: src/client/oauth.rs (new file in pmcp crate) -->

From crates/mcp-tester/src/oauth.rs (SOURCE -- to be adapted):
```rust
// Current imports that use anyhow and colored:
use anyhow::{Context, Result};
use colored::*;
use pmcp::client::auth::{OidcDiscoveryClient, TokenExchangeClient};
use pmcp::client::http_middleware::HttpMiddlewareChain;
use pmcp::client::oauth_middleware::{BearerToken, OAuthClientMiddleware};
use pmcp::server::auth::oauth2::OidcDiscoveryMetadata;
// ... plus base64, sha2, rand, url, webbrowser, dirs, tokio, serde

pub struct OAuthConfig { /* 6 fields */ }
pub struct OAuthHelper { config: OAuthConfig, client: reqwest::Client }
impl OAuthHelper {
    pub fn new(config: OAuthConfig) -> Result<Self>;
    pub async fn get_access_token(&self) -> Result<String>;
    pub async fn create_middleware_chain(&self) -> Result<Arc<HttpMiddlewareChain>>;
}
pub fn default_cache_path() -> PathBuf;
```

From src/client/mod.rs (TARGET -- add pub mod oauth):
```rust
#[cfg(not(target_arch = "wasm32"))]
pub mod auth;
pub mod http_logging_middleware;
pub mod http_middleware;
pub mod oauth_middleware;
pub mod transport;
```

From src/client/oauth_middleware.rs (already in SDK -- reuse):
```rust
pub struct BearerToken { pub token: String, pub token_type: String, pub expires_at: Option<SystemTime> }
pub struct OAuthClientMiddleware { /* ... */ }
impl OAuthClientMiddleware { pub fn new(token: BearerToken) -> Self; }
```

From src/client/auth (already in SDK -- reuse):
```rust
pub struct OidcDiscoveryClient;
impl OidcDiscoveryClient { pub fn new() -> Self; pub async fn discover(&self, url: &str) -> ...; }
pub struct TokenExchangeClient;
impl TokenExchangeClient { pub fn new() -> Self; pub async fn exchange_code(...) -> ...; }
pub struct TokenResponse { pub access_token: String, pub refresh_token: Option<String>, pub expires_in: Option<u64>, pub token_type: String }
```

From Cargo.toml (existing oauth-adjacent deps already present):
```toml
sha2 = "0.10"
base64 = "0.22"
url = "2.5"
rand is NOT in pmcp deps -- must add under oauth feature
webbrowser is NOT in pmcp deps -- must add under oauth feature
dirs is NOT in pmcp deps -- must add under oauth feature
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add oauth feature flag and dependencies to Cargo.toml</name>
  <files>Cargo.toml</files>
  <action>
Add the following dependencies to pmcp's Cargo.toml, gated behind the `oauth` feature:

1. Add to `[dependencies]` section (as optional deps):
```toml
# OAuth CLI helper dependencies (feature-gated)
webbrowser = { version = "1", optional = true }
dirs = { version = "6", optional = true }
rand = { version = "0.10", optional = true }
```

2. Add to `[features]` section:
```toml
oauth = ["dep:webbrowser", "dep:dirs", "dep:rand"]
```

Note: `sha2`, `base64`, `url`, `tokio`, `reqwest`, `serde` are already unconditional dependencies of pmcp. No need to add them. The `colored` crate is NOT added -- all terminal output uses `tracing` instead.
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo check -p pmcp --features oauth 2>&1 | tail -5</automated>
  </verify>
  <done>Cargo.toml has `oauth` feature with `webbrowser`, `dirs`, `rand` as optional deps. `cargo check -p pmcp --features oauth` succeeds.</done>
</task>

<task type="auto">
  <name>Task 2: Create src/client/oauth.rs and wire into mod.rs</name>
  <files>src/client/oauth.rs, src/client/mod.rs</files>
  <action>
**Create src/client/oauth.rs** by adapting crates/mcp-tester/src/oauth.rs with these changes:

1. **Module doc comment**: Keep the existing doc comment explaining the OAuth flows (PKCE, device code, auto-discovery).

2. **Imports -- replace external with internal crate paths**:
   - `anyhow::{Context, Result}` -> `use crate::error::{Error, Result};`
   - Remove `use colored::*;` entirely
   - `use pmcp::client::auth::*` -> `use crate::client::auth::*`
   - `use pmcp::client::http_middleware::*` -> `use crate::client::http_middleware::*`
   - `use pmcp::client::oauth_middleware::*` -> `use crate::client::oauth_middleware::*`
   - `use pmcp::server::auth::oauth2::*` -> `use crate::server::auth::oauth2::*`
   - Keep all other imports (base64, sha2, rand, url, webbrowser, tokio, serde, dirs)

3. **Replace all anyhow error handling with pmcp Error**:
   - `anyhow::bail!("msg")` -> `return Err(Error::internal(format!("msg")))`
   - `.context("msg")?` -> `.map_err(|e| Error::internal(format!("msg: {e}")))?`
   - Return types: `Result<T>` (which is now `crate::error::Result<T>`)

4. **Replace ALL colored terminal output with tracing**:
   - `eprintln!("{}", "message".green())` -> `tracing::info!("message")`
   - `eprintln!("{}", "message".cyan())` -> `tracing::info!("message")`
   - `eprintln!("{}", "message".yellow())` -> `tracing::warn!("message")`
   - `eprintln!("{}", "message".dimmed())` -> `tracing::debug!("message")`
   - `eprintln!("{}", "message".red())` -> `tracing::error!("message")`
   - For multi-line formatted boxes (the "OAuth Authentication Required" banner), convert to simple tracing::info! calls:
     ```rust
     tracing::info!("OAuth Authentication Required");
     tracing::info!("Opening browser for authentication...");
     tracing::info!("If the browser doesn't open, visit: {}", auth_url.as_str());
     ```
   - For device code flow display:
     ```rust
     tracing::info!("OAuth device code flow");
     tracing::info!("1. Visit: {}", device_auth.verification_uri);
     tracing::info!("2. Enter code: {}", device_auth.user_code);
     ```
   - The `print!("  Waiting for authorization...");` + flush -> `tracing::info!("Waiting for authorization...")`
   - The `eprintln!("\r{}", "Authorization code received!".green())` -> `tracing::info!("Authorization code received")`

5. **Keep all public types as-is** (they are the API contract):
   - `pub struct OAuthConfig` (all 6 fields, Debug + Clone derives)
   - `pub struct OAuthHelper` (config + client fields)
   - All impl methods on OAuthHelper (new, get_access_token, create_middleware_chain)
   - `pub fn default_cache_path() -> PathBuf`

6. **Keep all private types as-is**: `TokenCache`, `DeviceAuthResponse`, `TokenResponse`

7. **Add convenience function at module level**:
```rust
/// Create an OAuth middleware chain from configuration.
///
/// This is a one-liner convenience for tools that just need a middleware chain:
/// ```no_run
/// # use pmcp::client::oauth::{OAuthConfig, create_oauth_middleware};
/// # async fn example() -> pmcp::Result<()> {
/// let config = OAuthConfig { /* ... */ };
/// let chain = create_oauth_middleware(config).await?;
/// // Pass chain to HttpClient or transport
/// # Ok(())
/// # }
/// ```
pub async fn create_oauth_middleware(config: OAuthConfig) -> Result<Arc<HttpMiddlewareChain>> {
    let helper = OAuthHelper::new(config)?;
    helper.create_middleware_chain().await
}
```

8. **Gate the entire module** -- the module is only compiled when the `oauth` feature is enabled. This is handled by the `#[cfg(feature = "oauth")]` on the `pub mod oauth` line in mod.rs.

**Update src/client/mod.rs**:

Add after the existing `pub mod oauth_middleware;` line:
```rust
#[cfg(all(not(target_arch = "wasm32"), feature = "oauth"))]
pub mod oauth;
```

The `not(target_arch = "wasm32")` guard is needed because `oauth` uses `tokio::net::TcpListener`, `webbrowser`, and `dirs` which are not available on WASM. This matches the existing `auth` module guard.
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo build -p pmcp --features oauth 2>&1 | tail -10 && cargo test -p pmcp --features oauth --lib client::oauth -- --test-threads=1 2>&1 | tail -5</automated>
  </verify>
  <done>src/client/oauth.rs exists with OAuthHelper, OAuthConfig, default_cache_path, and create_oauth_middleware. Module is feature-gated behind `oauth`. `cargo build -p pmcp --features oauth` compiles without warnings. No colored crate dependency -- all output via tracing. Error types use pmcp::error::Error.</done>
</task>

</tasks>

<verification>
```bash
# Feature-gated build
cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk
cargo build -p pmcp --features oauth

# Verify no colored dependency
cargo tree -p pmcp --features oauth | grep -i colored
# Expected: no output (colored is NOT a dep)

# Verify new deps are present
cargo tree -p pmcp --features oauth | grep -E "webbrowser|dirs"

# Default build still works (no oauth feature)
cargo build -p pmcp

# Clippy clean
cargo clippy -p pmcp --features oauth -- -D warnings
```
</verification>

<success_criteria>
1. `cargo build -p pmcp --features oauth` succeeds
2. `cargo build -p pmcp` (without oauth) still succeeds (feature is additive)
3. `pmcp::client::oauth::OAuthHelper`, `OAuthConfig`, `default_cache_path`, `create_oauth_middleware` are publicly accessible
4. No `colored` crate in pmcp's dependency tree
5. All terminal output uses `tracing` calls
6. Error types are `pmcp::error::Error` not `anyhow::Error`
</success_criteria>

<output>
After completion, create `.planning/phases/26-add-oauth-support-to-load-testing/26-01-SUMMARY.md`
</output>
