pmcp 1.9.2

High-quality Rust SDK for Model Context Protocol (MCP) with full TypeScript SDK compatibility
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
# GitHub Issue Comments

## Issue #82: Add HttpMiddleware trait for transport-level HTTP concerns

**Status: RESOLVED ✅**

---

### Closing Comment

This issue has been fully resolved! 🎉

## What Was Delivered

### Core HttpMiddleware Infrastructure

✅ **`HttpMiddleware` trait** with comprehensive lifecycle hooks:
```rust
#[async_trait]
pub trait HttpMiddleware: Send + Sync {
    async fn on_request(&self, request: &mut HttpRequest, context: &HttpMiddlewareContext) -> Result<()>;
    async fn on_response(&self, response: &mut HttpResponse, context: &HttpMiddlewareContext) -> Result<()>;
    async fn on_error(&self, error: &Error, context: &HttpMiddlewareContext) -> Result<()>;
    fn priority(&self) -> i32;
    async fn should_execute(&self, context: &HttpMiddlewareContext) -> bool;
}
```

✅ **`HttpRequest` and `HttpResponse` abstractions**:
- Case-insensitive header operations (HTTP RFC 7230 compliant)
- Clean API: `add_header()`, `get_header()`, `has_header()`, `remove_header()`
- Works with `HashMap<String, String>` internally

✅ **`HttpMiddlewareContext`**:
- Request metadata (url, method, attempt number)
- Shareable metadata store for middleware coordination
- Request ID correlation support

✅ **`HttpMiddlewareChain`**:
- Priority-based execution ordering
- Short-circuit error semantics
- Reverse order for response processing
- Comprehensive error handling with `on_error` hooks

### Integration with StreamableHttpTransport

✅ **Automatic middleware invocation** in `StreamableHttpTransport`:
- `build_request_with_middleware()` - Applied before HTTP send (lines 339-435)
- `apply_response_middleware()` - Applied after HTTP receive (lines 437-470)
- Fast path optimization: Skips middleware when none configured
- OAuth precedence: `auth_provider > HttpMiddleware > extra_headers`

✅ **Configuration API**:
```rust
let mut http_chain = HttpMiddlewareChain::new();
http_chain.add(Arc::new(OAuthClientMiddleware::new(token)));

let config = StreamableHttpTransportConfig {
    url: Url::parse("http://localhost:8080").unwrap(),
    http_middleware_chain: Some(Arc::new(http_chain)), // ← Integration point
    // ...
};

let transport = StreamableHttpTransport::new(config);
let client = ClientBuilder::new(transport).build();

// Middleware runs automatically - no manual calls needed!
```

## Use Cases Demonstrated

### 1. Header Injection ✅
See `examples/40_middleware_demo.rs` - `CorrelationHeaderMiddleware`

### 2. Request/Response Logging ✅
Built-in with comprehensive header tracking

### 3. OAuth Token Injection ✅
`OAuthClientMiddleware` (see issue #83)

### 4. Status Code Handling ✅
`on_response` hook with access to status codes

## Testing

✅ **23 comprehensive integration tests**:
- 14 HTTP middleware integration tests (`tests/http_middleware_integration.rs`)
  - Middleware ordering/priority
  - OAuth flows (no provider, expired token, duplicate headers)
  - Concurrency and state isolation
  - Error handling and short-circuit behavior
  - Header case-insensitivity
  - Double-retry protection

- 4 SSE middleware tests (`tests/sse_middleware_integration.rs`)
  - Middleware on SSE GET requests
  - Last-Event-ID header tracking
  - Multiple HTTP methods
  - Response status tracking

- 5 OAuth integration tests (`tests/streamable_http_oauth_integration.rs`)
  - Real client-server interaction
  - Token injection and precedence
  - Token expiry error handling

## Documentation

✅ **Chapter 11: Middleware** in pmcp-book
- HTTP-Level Middleware section with architecture diagram
- Two-layer middleware system (Protocol vs HTTP)
- API examples and use cases

✅ **Chapter 10.3: Streamable HTTP**
- HTTP middleware configuration examples
- OAuth middleware integration guide

✅ **API documentation**
- Complete rustdoc with examples
- Type-level documentation for all public APIs

## Extra Features Beyond Original Request

🎁 **Fast path optimization**: Avoids overhead when no middleware configured
🎁 **OAuth precedence policy**: Ensures `auth_provider` takes precedence
🎁 **Metadata propagation**: Allows middleware coordination (e.g., retry protection)
🎁 **Case-insensitive headers**: HTTP standard compliance
🎁 **Error hooks**: Comprehensive error handling with `on_error`

## Files Changed

**Core Implementation:**
- `src/client/http_middleware.rs` - HttpMiddleware trait, chain, abstractions (356 lines)
- `src/shared/streamable_http.rs` - Integration with transport (lines 333-470)
- `src/client/oauth_middleware.rs` - OAuth client middleware (244 lines)

**Tests:**
- `tests/http_middleware_integration.rs` (764 lines)
- `tests/sse_middleware_integration.rs` (421 lines)
- `tests/streamable_http_oauth_integration.rs` (361 lines)

**Documentation:**
- `pmcp-book/src/ch11-middleware.md` - HTTP middleware section
- `pmcp-book/src/ch10-03-streamable-http.md` - Integration guide

**Examples:**
- `examples/40_middleware_demo.rs` - Full demonstration

## Commits

- Phase 1 Foundation: d024af8
- OAuth Integration Tests: 57349e4
- Phase 2 Complete: 9036e5a
- Quality Gate Fixes: 4dc71df

## Related Issues

- ✅ Resolves #82 (this issue)
- ✅ Resolves #83 (OAuth client middleware - see separate comment)
- ⚠️ Partial progress on #80 (HTTP middleware integration complete, ClientBuilder API still pending)

---

This provides a solid foundation for HTTP-level concerns in the PMCP SDK and achieves feature parity with the TypeScript SDK's fetch-wrapping middleware! 🚀

---

## Issue #83: Add OAuth client-side middleware with token injection and auto-refresh

**Status: CORE FEATURES COMPLETE ✅ (Future enhancements noted)**

---

### Closing Comment

The core OAuth client middleware features have been implemented! 🎉

## What Was Delivered

### OAuthClientMiddleware Implementation

✅ **Automatic bearer token injection**:
```rust
pub struct OAuthClientMiddleware {
    token: Arc<RwLock<BearerToken>>,
}

pub struct BearerToken {
    pub token: String,
    pub token_type: String,
    pub expires_at: Option<SystemTime>,
}

impl BearerToken {
    pub fn new(token: String) -> Self;
    pub fn with_expiry(token: String, duration: Duration) -> Self;
    pub fn is_expired(&self) -> bool;
    pub fn to_header_value(&self) -> String;
}
```

✅ **Token lifecycle features**:
- Expiry tracking with `expires_at` field
- Proactive expiry checking in `on_request`
- Returns `Error::Authentication` when token expired
- `is_expired()` method for manual checking

✅ **401/403 detection and metadata**:
- Detects authentication failures in `on_response`
- Sets `auth_failure: true` metadata for retry coordination
- Sets `status_code` metadata for debugging
- Logs warnings on auth failure after retry

✅ **Smart precedence handling**:
- Skips injection if `auth_provider` present (set via metadata)
- Skips if Authorization header already exists (case-insensitive)
- Prevents duplicate authentication headers
- Clean integration with transport-level auth

✅ **Retry coordination**:
- Sets `oauth.retry_used` metadata for double-retry protection
- Prevents infinite retry loops
- Coordinates with external retry middleware
- Uses `on_error` hook for proper error-chain handling

### Integration

✅ **Works with StreamableHttpTransport**:
```rust
use pmcp::client::http_middleware::HttpMiddlewareChain;
use pmcp::client::oauth_middleware::{OAuthClientMiddleware, BearerToken};
use std::time::Duration;

// Create OAuth middleware
let token = BearerToken::with_expiry(
    "api-token-12345".to_string(),
    Duration::from_secs(3600)
);
let mut http_chain = HttpMiddlewareChain::new();
http_chain.add(Arc::new(OAuthClientMiddleware::new(token)));

// Configure transport
let config = StreamableHttpTransportConfig {
    url: Url::parse("http://localhost:8080").unwrap(),
    http_middleware_chain: Some(Arc::new(http_chain)),
    // ...
};

let transport = StreamableHttpTransport::new(config);
let mut client = ClientBuilder::new(transport).build();

// Token automatically injected into all requests!
let info = client.initialize(ClientCapabilities::minimal()).await?;
```

## Testing

✅ **5 OAuth integration tests** (`tests/streamable_http_oauth_integration.rs`):
- `test_oauth_middleware_injects_token` - Verifies automatic token injection
- `test_auth_provider_takes_precedence_over_oauth` - Validates precedence policy
- `test_oauth_token_expiry_triggers_error` - Token expiry handling
- `test_multiple_requests_with_oauth` - Sequential requests with same token
- `test_oauth_with_case_insensitive_header_check` - Duplicate detection

✅ **3 OAuth retry coordination tests** (`tests/http_middleware_integration.rs`):
- `test_oauth_retry_coordination_with_metadata` - Metadata-based coordination
- `test_double_retry_protection` - Prevents infinite loops
- `test_oauth_error_hook_logging` - Error hook integration

✅ **Real client-server integration**:
- Tests use actual `StreamableHttpServer` instances
- Validates end-to-end token flow
- Confirms server receives injected headers

## Documentation

✅ **Chapter 11: Middleware** - OAuth middleware section
✅ **Chapter 10.3: Streamable HTTP** - OAuth configuration examples
✅ **API documentation** - Complete rustdoc with examples

## What's Not Implemented (Future Enhancements)

The following features from the original issue are **not yet implemented** but can be added in future PRs:

### 1. Automatic Token Refresh ⚠️

**Current behavior**: Returns `Error::Authentication` when token expires
**Desired behavior**: Automatically refresh token and retry request

**Future implementation approach**:
```rust
pub trait TokenProvider: Send + Sync {
    async fn get_token(&self) -> Result<AccessToken>;
    async fn refresh_token(&self, current: &AccessToken) -> Result<AccessToken>;
}

pub struct OAuthClientMiddleware {
    token_provider: Arc<dyn TokenProvider>,
    // Automatically refreshes on expiry or 401
}
```

### 2. Multiple OAuth Flows ⚠️

**Current support**: Bearer token only
**Future flows**:
- Client credentials (client_id + client_secret → token)
- Refresh token (refresh_token → new access_token)
- Device code flow
- Custom token providers

### 3. Automatic Retry After 401 ⚠️

**Current behavior**: Sets metadata, app handles retry
**Desired behavior**: Middleware automatically retries after refresh

**Workaround**: Use external retry middleware that checks `auth_failure` metadata

### 4. Token Storage Strategies ⚠️

**Current support**: In-memory only
**Future strategies**:
- Persistent storage (file, keychain)
- Encrypted token storage
- Shared token cache across clients

### 5. Proactive Token Refresh ⚠️

**Partial support**: Can check expiry before request
**Enhancement**: Automatically refresh N seconds before expiry

## Why Close This Issue?

The **core value proposition** of this issue has been delivered:
- ✅ OAuth tokens are automatically injected
- ✅ Token expiry is tracked and validated
- ✅ 401/403 responses are detected
- ✅ Retry coordination infrastructure is in place

The missing features (auto-refresh, multiple flows) are valuable enhancements but can be added incrementally without blocking other work. The current implementation provides a solid foundation that users can build upon.

## Recommendation

**Close this issue** and open separate enhancement issues for:
1. "Add automatic token refresh to OAuthClientMiddleware" (#TODO)
2. "Support multiple OAuth flows (client credentials, refresh token)" (#TODO)
3. "Add TokenProvider trait for custom OAuth flows" (#TODO)

This allows tracking specific enhancements independently while acknowledging the core feature is complete.

## Files Changed

**Implementation:**
- `src/client/oauth_middleware.rs` (244 lines)
- `src/client/http_middleware.rs` (trait definition)

**Tests:**
- `tests/streamable_http_oauth_integration.rs` (361 lines)
- `tests/http_middleware_integration.rs` (OAuth sections)

**Documentation:**
- `pmcp-book/src/ch11-middleware.md`
- `pmcp-book/src/ch10-03-streamable-http.md`

## Commits

- OAuth Middleware Implementation: 57349e4
- Phase 2 Integration Tests: 9036e5a
- Quality Gate Fixes: 4dc71df

## Related Issues

- ✅ Resolves #83 (this issue - core features)
- ✅ Resolves #82 (HttpMiddleware trait)
- ⚠️ Partial progress on #80 (HTTP middleware integration)

---

Great work positioning PMCP as the best MCP SDK! The OAuth middleware provides essential client-side authentication capabilities. 🚀

---

## Issue #80: Add first-class middleware integration to Client/Protocol/Transport

**Status: IN PROGRESS ⚠️ (HTTP layer complete, Client/Protocol layer pending)**

---

### Progress Update Comment

Significant progress has been made on this issue! The HTTP transport layer now has full first-class middleware integration.

## ✅ What's Been Completed

### HTTP-Level Middleware Integration (COMPLETE)

✅ **Transport-level configuration**:
```rust
use pmcp::client::http_middleware::HttpMiddlewareChain;
use pmcp::shared::streamable_http::{StreamableHttpTransport, StreamableHttpTransportConfig};

let mut http_chain = HttpMiddlewareChain::new();
http_chain.add(Arc::new(LoggingMiddleware::default()));
http_chain.add(Arc::new(OAuthClientMiddleware::new(token)));

let config = StreamableHttpTransportConfig {
    url: Url::parse("http://localhost:8080").unwrap(),
    http_middleware_chain: Some(Arc::new(http_chain)),  // ← First-class integration!
    // ...
};

let transport = StreamableHttpTransport::new(config);
let mut client = ClientBuilder::new(transport).build();

// Middleware runs automatically on ALL operations - zero manual calls!
let tools = client.list_tools(None).await?;  // Middleware runs transparently
```

✅ **Automatic invocation** - No manual `chain.process_request()` calls needed:
- `build_request_with_middleware()` - Called automatically before HTTP send
- `apply_response_middleware()` - Called automatically after HTTP receive
- Short-circuit error handling with `on_error` hooks
- Priority-based execution ordering

✅ **Clean architecture**:
- Configuration via `StreamableHttpTransportConfig::http_middleware_chain`
- Middleware chain is `Option<Arc<HttpMiddlewareChain>>` - zero overhead when unused
- Fast path optimization when no middleware configured

✅ **Production-ready features**:
- Error handling with `on_error` lifecycle
- Context metadata for middleware coordination
- Priority-based ordering (lower runs first)
- Conditional execution via `should_execute()`

### Test Coverage

✅ **23 integration tests** demonstrating automatic middleware invocation:
- Real client-server interactions
- Multiple middleware chaining
- Error scenarios
- Retry coordination

### Documentation

✅ **Chapter 11: Middleware** - Complete HTTP middleware section
✅ **Chapter 10.3: Streamable HTTP** - Integration guide
✅ **examples/40_middleware_demo.rs** - Full demonstration

## ⚠️ What's Still Pending

### 1. ClientBuilder API (HIGH PRIORITY)

**Desired API**:
```rust
let client = Client::builder()
    .capabilities(ClientCapabilities::default())
    .with_middleware_chain(chain)  // ← Doesn't exist yet
    .streamable_http_transport("http://localhost:8080")
    .await?
    .build()
    .await?;
```

**Current workaround**: Configure via transport config (works but less ergonomic)

**Implementation needed**:
- Add `middleware_chain: Option<Arc<MiddlewareChain>>` field to `ClientBuilder`
- Add `.with_middleware_chain()` builder method
- Pass middleware to transport during client construction
- Update all transport builders to accept middleware

### 2. Protocol-Level Middleware Auto-Invocation (MEDIUM PRIORITY)

**Current state**: Protocol middleware (`AdvancedMiddleware`) requires manual calls:
```rust
// Manual invocation required
let mut request = create_request("tools/list", None);
middleware_chain.process_request(&mut request).await?;
let response = client.call_method(request).await?;
middleware_chain.process_response(&mut response).await?;
```

**Desired state**: Automatic invocation like HTTP middleware

**Implementation needed**:
- Add middleware chain to `Protocol` struct
- Wrap all request/response paths with automatic middleware invocation
- Ensure backward compatibility

### 3. Unified Middleware Configuration (LOW PRIORITY)

**Desired**: Single configuration point for both HTTP and Protocol middleware
```rust
let client = Client::builder()
    .with_http_middleware(http_chain)      // HTTP layer
    .with_protocol_middleware(proto_chain) // Protocol layer
    .build();
```

**Benefit**: Clear separation of concerns, easier to understand

## Progress Summary

| Component | Status | Notes |
|-----------|--------|-------|
| HTTP middleware integration |**COMPLETE** | Full automatic invocation |
| HTTP middleware config API |**COMPLETE** | Via transport config |
| HTTP middleware tests |**COMPLETE** | 23 integration tests |
| HTTP middleware docs |**COMPLETE** | Chapter 11 + examples |
| ClientBuilder API |**TODO** | No `.with_middleware_chain()` |
| Protocol middleware auto-invoke |**TODO** | Still requires manual calls |
| Unified middleware config |**TODO** | Separate HTTP/Protocol APIs |

## Recommendation

**Keep this issue open** but update the title or description to reflect progress:

**Option 1**: Update title to focus on remaining work:
"Add ClientBuilder middleware API and Protocol-level auto-invocation"

**Option 2**: Create separate issues:
- New Issue: "Add ClientBuilder::with_middleware_chain() API" (#TODO)
- New Issue: "Add automatic Protocol middleware invocation" (#TODO)
- Close #80 noting HTTP layer is complete

**Option 3**: Use milestones:
- Milestone 1: HTTP-Level Integration ✅ **COMPLETE**
- Milestone 2: ClientBuilder API ⚠️ **In Progress**
- Milestone 3: Protocol-Level Integration ⚠️ **Pending**

## What Works Today

Users can **immediately** use HTTP middleware with this pattern:
```rust
use pmcp::client::http_middleware::HttpMiddlewareChain;
use pmcp::client::oauth_middleware::{OAuthClientMiddleware, BearerToken};
use pmcp::shared::streamable_http::{StreamableHttpTransport, StreamableHttpTransportConfig};
use std::sync::Arc;

let mut http_chain = HttpMiddlewareChain::new();
http_chain.add(Arc::new(OAuthClientMiddleware::new(token)));

let config = StreamableHttpTransportConfig {
    url: Url::parse("http://localhost:8080")?,
    http_middleware_chain: Some(Arc::new(http_chain)),
    // ... other config
};

let transport = StreamableHttpTransport::new(config);
let mut client = ClientBuilder::new(transport).build();

// All HTTP operations now have middleware applied automatically!
client.initialize(ClientCapabilities::minimal()).await?;
client.list_tools(None).await?;
```

This provides **immediate value** while we continue improving the ergonomics.

## Files Changed (HTTP Layer)

- `src/client/http_middleware.rs` (356 lines)
- `src/shared/streamable_http.rs` (integration code)
- `tests/http_middleware_integration.rs` (764 lines)
- `tests/sse_middleware_integration.rs` (421 lines)
- `tests/streamable_http_oauth_integration.rs` (361 lines)

## Next Steps

1. **Add ClientBuilder::with_middleware_chain()** - Improves ergonomics
2. **Add Protocol middleware auto-invocation** - Parity with HTTP layer
3. **Update documentation** - Show both patterns

## Related Issues

- ✅ #82 - HttpMiddleware trait (RESOLVED)
- ✅ #83 - OAuth client middleware (RESOLVED)
- ⚠️ #80 - This issue (IN PROGRESS - HTTP complete, Client/Protocol pending)

---

The HTTP middleware integration is production-ready and provides immediate value. The remaining work (ClientBuilder API, Protocol auto-invocation) will further improve ergonomics. 🚀

---

## Summary

**Issues ready to close:**
- **#82** - HttpMiddleware trait (fully implemented)
-**#83** - OAuth client middleware (core features complete)

**Issues to keep open:**
- ⚠️ **#80** - First-class integration (HTTP done, ClientBuilder/Protocol pending)

**New issues to create (optional):**
1. "Add automatic token refresh to OAuthClientMiddleware"
2. "Support multiple OAuth flows (client credentials, refresh token)"
3. "Add ClientBuilder::with_middleware_chain() API"
4. "Add automatic Protocol middleware invocation"