Skip to content

Commit c10196f

Browse files
committed
examples: mechanical update batch (slice 3) — fix all named staleness
All 30 update-row items from docs/plans/2026-07-28-examples-review.md fixed or dispositioned: - Version sweep: .version("0.3.47") → "0.4.0" everywhere; named 1.0.0 cases (client-initialise-server ×4, lambda-mcp-server) bumped - README rewrites where drift was total: derive-macro-server (phantom tools), resource-server (legacy #[uri] syntax, wrong port/URIs), function-macro-server (false 'macro in development'), prompts-server (wrong port, phantom prompts — live-verified incl. -32602 contract) - pagination-server README: app-level cursors vs protocol pagination distinction, 10k rows, real refresh_data tool - middleware-logging-server: real duration measurement via ctx metadata (live-verified), 2026 server/discover printed curl - audit-trail-server: actor-centric stats + request_id correlation; session-required gating and incidental session storage removed - oauth-resource-server: --required-scope enforcement via manual OAuthResourceMiddleware (403 insufficient_scope); doc header updated - middleware-auth-{server,lambda}: dead initialize bypasses dropped; lambda DynamoDB framing corrected; TESTING.md discover item - lambda-mcp-client: stub subcommands removed, dev-log comment scrubbed - lambda-authorizer: POST-only 2026 motivation (OPTIONS/.well-known) - streamable-http-client-2025-11-25: f64 progress contract, real negotiated_version() info, streaming demo always runs - zero-config/minimal-server READMEs: session/initialize prose corrected - EXAMPLES.md: dead rows removed, pairing row repaired Full ci-gates.sh all green.
1 parent 2a829a0 commit c10196f

36 files changed

Lines changed: 457 additions & 1646 deletions

File tree

EXAMPLES.md

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ cargo run --example minimal-server
6161
- **PostgreSQL**: Requires Docker container (instructions in example)
6262
- **DynamoDB**: Requires AWS credentials configuration
6363

64-
## 🟡 **RESOURCE SERVERS** (6 examples) - Resource Handling & Phase 6 Session-Aware
64+
## 🟡 **RESOURCE SERVERS** - Resource Handling & Session-Aware (2025 lane)
6565

6666
| Example | Port | Status | Description | Key Features |
6767
|---------|------|--------|-------------|--------------|
@@ -88,7 +88,6 @@ cargo run --example minimal-server
8888

8989
| Example | Port | Status | Description | Advanced Features |
9090
|---------|------|--------|-------------|-------------------|
91-
| **comprehensive-server** | 8002 | ✅ VALIDATED | All MCP features in one server | Complete framework showcase |
9291
| **audit-trail-server** | 8009 | ✅ VALIDATED | Audit logging | Comprehensive audit logging system |
9392
| **zero-config-getting-started** | 8641 | ✅ VALIDATED | Zero-configuration setup | Getting started tutorial server |
9493

@@ -125,7 +124,6 @@ cargo run --example client-initialise-report -- --url http://127.0.0.1:8641/mcp
125124
| Example | Type | Status | Description | AWS Features |
126125
|---------|------|--------|-------------|--------------|
127126
| **lambda-mcp-server** | Lambda | ✅ VALIDATED | Serverless MCP server | Basic Lambda deployment |
128-
| **lambda-mcp-server-streaming** | Lambda | ✅ VALIDATED | Streaming Lambda server | Lambda with streaming support |
129127
| **lambda-mcp-client** | Lambda Client | ✅ VALIDATED | Lambda MCP client | AWS Lambda client integration |
130128
| **lambda-authorizer** | Lambda | ✅ VALIDATED | API Gateway authorizer | REQUEST authorizer with wildcard methodArn for MCP |
131129

@@ -154,7 +152,6 @@ cargo run --example client-initialise-report -- --url http://127.0.0.1:8641/mcp
154152
|---------|------|--------|-------------|---------------|
155153
| **tasks-e2e-inmemory-server** | Server | ✅ VALIDATED | Task-enabled MCP server | `slow_add` tool with configurable delay, InMemory storage |
156154
| **tasks-e2e-inmemory-client** | Client | ✅ VALIDATED | Task lifecycle client | Full task lifecycle: create, poll, cancel, result |
157-
| **client-task-lifecycle** | Client | ✅ VALIDATED | Task API demonstration | `call_tool_with_task`, `get_task`, `cancel_task` |
158155

159156
**Task E2E Testing**:
160157
```bash
@@ -171,28 +168,27 @@ These examples demonstrate MCP 2025-11-25 type construction without starting a s
171168

172169
| Example | Type | Status | Description | Types Demonstrated |
173170
|---------|------|--------|-------------|-------------------|
174-
| **builders-showcase** | Demo | ✅ VALIDATED | All 9 MCP builders | Tool, Resource, Prompt, Completion builders |
175171
| **icon-showcase** | Demo | ✅ VALIDATED | Icon support | `Icon` struct on tools, resources, prompts |
176-
| **sampling-with-tools-showcase** | Demo | ✅ VALIDATED | Sampling with tools | `tools` field on `CreateMessageParams` |
177-
| **task-types-showcase** | Demo | ✅ VALIDATED | Task type system | `Task`, `TaskStatus`, `TaskMetadata`, CRUD types |
178172

179173
## 📚 **PERFORMANCE TESTING** (1 example) - Benchmarks
180174

181175
| Example | Type | Status | Description | Purpose |
182176
|---------|------|--------|-------------|---------|
183-
| **performance-testing** | Benchmark | ✅ VALIDATED | Performance benchmarks | Comprehensive benchmark suite |
184177

185178
## 🚨 **COMPREHENSIVE VALIDATION RESULTS**
186179

187-
### **ALL 58 EXAMPLES COMPILE — 50 FUNCTIONALLY VERIFIED**
188-
**v0.3.0 (MCP 2025-11-25) — Last verified: 2026-02-26**
180+
### **ALL 50 ACTIVE EXAMPLES COMPILE UNDER THEIR LANE'S CI GATES**
181+
**0.4.0 branch (MCP 2026-07-28 default, 2025-11-25 opt-in) — last reconciled 2026-06-12.**
182+
The per-example functional verification ledger lives in
183+
`docs/plans/2026-07-28-examples-review.md`; the 2026-06-12 migrate slice
184+
re-verified its six examples live on the wire.
189185

190186
- **Getting Started** - 5 examples (all tool creation levels)
191187
- **Session Storage** - 3 examples (SQLite, PostgreSQL, DynamoDB)
192188
- **Resource Servers** - 6 examples (session-aware resources)
193189
- **Feature-Specific** - 8 examples (prompts, sampling, elicitation, etc.)
194-
- **Advanced/Composite** - 5 examples (comprehensive, alerts, audit, logging)
195-
- **Session & State** - 4 examples (stateful operations, logging)
190+
- **Advanced/Composite** - audit-trail, pagination, icon showcase
191+
- **Session & State** - stateful-server + session-aware-resource-server (2025-pinned) and the storage-backend trio
196192
- **Client Examples** - 7 examples (client-server communication — see the pairs table above)
197193
- **AWS Lambda** - 4 examples (server, streaming, client, authorizer)
198194
- **Tool Creation & Schemas** - 6 examples (macro patterns + output schemas)

docs/plans/2026-07-28-examples-review.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,34 @@ own binary. The worst offenders print broken curl commands from their own stdout
7070

7171
## 🛠️ Update (30) — keep, fix named staleness
7272

73+
> **EXECUTED 2026-06-12** (slice 3, mechanical batch): every named staleness
74+
> item below is fixed or dispositioned. Highlights: version-string sweep
75+
> (`0.3.47``0.4.0` everywhere; named `1.0.0` cases in
76+
> client-initialise-server/lambda-mcp-server bumped); derive-macro-server,
77+
> resource-server, function-macro-server READMEs rewritten to match their
78+
> actual code; pagination-server README reframed as app-level cursors (not
79+
> protocol `_meta` pagination) with the real 10,000-row/`refresh_data`
80+
> facts; prompts-server README rewritten (port 8006, 3 real prompts,
81+
> `_meta` curls — live-verified incl. the -32602 missing-arg contract) and
82+
> its no-op `.sse(true)` dropped; middleware-logging-server now measures
83+
> real durations via context metadata (live-verified) and prints a 2026
84+
> `server/discover` curl; middleware-auth-{server,lambda} dead `initialize`
85+
> bypasses dropped; audit-trail-server re-anchored to actor-centric stats +
86+
> `request_id` correlation (session-keyed `unique_sessions` removed);
87+
> lambda-mcp-client println-stub subcommands removed, dev-log comment
88+
> scrubbed, user agents bumped; lambda-authorizer motivation reframed for
89+
> the POST-only 2026 surface (OPTIONS/.well-known still need wildcarding);
90+
> streamable-http-client-2025-11-25 progress moved to the `f64` contract,
91+
> placeholder server-info replaced with `negotiated_version()`, and the
92+
> streaming demo now always runs; oauth-resource-server gained
93+
> `--required-scope` enforcement via manual `OAuthResourceMiddleware`
94+
> construction; zero-config + minimal-server README session/initialize
95+
> prose corrected. Dispositions: client-initialise-report "Phase 1/2/3"
96+
> strings are runtime test stages (not dev-phase tags) — kept; 2025-lane
97+
> initialize banners/curls in pinned examples are lane-correct — kept;
98+
> house-placeholder `.version("1.0.0")` strings outside the two named
99+
> examples — kept per the audit's own function-resource-server note.
100+
73101
| Example | Lane | Teaches | Why / required fixes | Gate-ref |
74102
|---|---|---|---|---|
75103
| `audit-trail-server` | 2026-default | Application-owned persistence inside tools: an immutable SQLite (sqlx) audit log with log/search/report tools, independent of the framework's session storage. | The core pattern — tools owning their own sqlx pool (OnceLock at main.rs:19) for durable, queryable application data — is spec-neutral and fully current on 2026; it compiles clean on the default lane. The fix is narrow: re-anchor actor/correlation from session_id to a 2026-meaningful identity (per-request _meta clientInfo, W3C traceparent per the spec plan's distributed-tracing item, or an explicit actor param), rewrite the unique_sessions stats, and drop the session-required framing. Not a merge/archive candidate — no sibling teaches app-owned DB persistence. — **Staleness:** src/main.rs:108 keys every audit row by session.session_id and main.rs:347-370 reports 'unique_sessions' / 'events_per_session' stats — on the 2026 default the session id is an ephemeral per-request internal id (never sent to the client), so unique_sessions ≈ total_events and the attribution/statistics are meaningless; main.rs:99 'Session required' gating implies a client-visible session that no longer exists; main.rs:443 with_session_storage(SqliteSessionStorage) is incidental to the example's teaching | no |

examples/audit-trail-server/src/main.rs

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
//!
33
//! This example demonstrates a compliance-focused audit trail system using SQLite for persistence.
44
//! It shows how to log immutable audit events, search audit logs, and generate compliance reports.
5+
//!
6+
//! Attribution is actor-centric: callers pass an explicit `actor`, and each row also
7+
//! records the per-request correlation id. On the 2026-07-28 stateless core there is no
8+
//! client-visible session, so nothing here keys on cross-request session identity.
59
610
use chrono::{DateTime, Utc};
711
use serde::{Deserialize, Serialize};
@@ -12,7 +16,6 @@ use std::sync::{Arc, OnceLock};
1216
use turul_mcp_derive::McpTool;
1317
use turul_mcp_protocol::{McpError, McpResult};
1418
use turul_mcp_server::{McpServer, SessionContext};
15-
use turul_mcp_session_storage::SqliteSessionStorage;
1619
use uuid::Uuid;
1720

1821
/// Global database pool shared by all tools
@@ -28,7 +31,7 @@ fn get_db_pool() -> McpResult<&'static Arc<SqlitePool>> {
2831
struct AuditEvent {
2932
id: String,
3033
timestamp: DateTime<Utc>,
31-
session_id: String,
34+
request_id: String,
3235
event_type: String,
3336
actor: Option<String>,
3437
resource: Option<String>,
@@ -44,7 +47,7 @@ async fn init_database(pool: &SqlitePool) -> Result<(), sqlx::Error> {
4447
CREATE TABLE IF NOT EXISTS audit_logs (
4548
id TEXT PRIMARY KEY,
4649
timestamp TEXT NOT NULL,
47-
session_id TEXT NOT NULL,
50+
request_id TEXT NOT NULL,
4851
event_type TEXT NOT NULL,
4952
actor TEXT,
5053
resource TEXT,
@@ -54,7 +57,7 @@ async fn init_database(pool: &SqlitePool) -> Result<(), sqlx::Error> {
5457
);
5558
5659
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_logs(timestamp);
57-
CREATE INDEX IF NOT EXISTS idx_audit_session ON audit_logs(session_id);
60+
CREATE INDEX IF NOT EXISTS idx_audit_request ON audit_logs(request_id);
5861
CREATE INDEX IF NOT EXISTS idx_audit_event_type ON audit_logs(event_type);
5962
CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit_logs(actor);
6063
"#,
@@ -95,17 +98,21 @@ pub struct LogAuditEventTool {
9598

9699
impl LogAuditEventTool {
97100
async fn execute(&self, session: Option<SessionContext>) -> McpResult<Value> {
98-
let session =
99-
session.ok_or_else(|| McpError::SessionError("Session required".to_string()))?;
100101
let db_pool = get_db_pool()?;
102+
// Correlation id for this request: the framework's per-request internal
103+
// context id when present, otherwise a fresh UUID.
104+
let request_id = session
105+
.as_ref()
106+
.map(|s| s.session_id.clone())
107+
.unwrap_or_else(|| Uuid::now_v7().as_simple().to_string());
101108

102109
let metadata = self.metadata.clone().unwrap_or(json!({}));
103110

104111
// Create audit event
105112
let audit_event = AuditEvent {
106113
id: Uuid::now_v7().as_simple().to_string(),
107114
timestamp: Utc::now(),
108-
session_id: session.session_id.clone(),
115+
request_id,
109116
event_type: self.event_type.clone(),
110117
actor: self.actor.clone(),
111118
resource: self.resource.clone(),
@@ -117,13 +124,13 @@ impl LogAuditEventTool {
117124
// Store in database (immutable)
118125
let db_result = sqlx::query(
119126
r#"
120-
INSERT INTO audit_logs (id, timestamp, session_id, event_type, actor, resource, action, result, metadata)
127+
INSERT INTO audit_logs (id, timestamp, request_id, event_type, actor, resource, action, result, metadata)
121128
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
122129
"#,
123130
)
124131
.bind(&audit_event.id)
125132
.bind(audit_event.timestamp.to_rfc3339())
126-
.bind(&audit_event.session_id)
133+
.bind(&audit_event.request_id)
127134
.bind(&audit_event.event_type)
128135
.bind(&audit_event.actor)
129136
.bind(&audit_event.resource)
@@ -135,13 +142,10 @@ impl LogAuditEventTool {
135142

136143
match db_result {
137144
Ok(_) => {
138-
// Send progress notification
139-
session
140-
.notify_progress(
141-
format!("audit_{}", audit_event.event_type.to_lowercase()),
142-
1,
143-
)
144-
.await;
145+
// Request-scoped progress: no-op unless the caller sent a progressToken
146+
if let Some(session) = &session {
147+
session.notify_request_progress(1.0, Some(1.0)).await;
148+
}
145149

146150
Ok(json!({
147151
"logged": true,
@@ -238,7 +242,7 @@ impl SearchAuditTrailTool {
238242
json!({
239243
"id": row.get::<String, _>("id"),
240244
"timestamp": timestamp_str,
241-
"session_id": row.get::<String, _>("session_id"),
245+
"request_id": row.get::<String, _>("request_id"),
242246
"event_type": row.get::<String, _>("event_type"),
243247
"actor": row.get::<Option<String>, _>("actor"),
244248
"resource": row.get::<Option<String>, _>("resource"),
@@ -343,17 +347,17 @@ impl GenerateComplianceReportTool {
343347
.map(|row| (row.get::<String, _>("result"), row.get::<i32, _>("count")))
344348
.collect();
345349

346-
// Get session statistics
347-
let session_stats_query = format!(
348-
"SELECT COUNT(DISTINCT session_id) as unique_sessions, COUNT(*) as total_events FROM audit_logs WHERE 1=1 {}",
350+
// Actor-centric statistics (attribution comes from the explicit actor param)
351+
let actor_stats_query = format!(
352+
"SELECT COUNT(DISTINCT actor) as unique_actors, COUNT(*) as total_events FROM audit_logs WHERE 1=1 {}",
349353
time_filter
350354
);
351-
let stats_row = sqlx::query(&session_stats_query)
355+
let stats_row = sqlx::query(&actor_stats_query)
352356
.fetch_one(&**db_pool)
353357
.await
354-
.map_err(|e| McpError::tool_execution(&format!("Session stats query failed: {}", e)))?;
358+
.map_err(|e| McpError::tool_execution(&format!("Actor stats query failed: {}", e)))?;
355359

356-
let unique_sessions: i32 = stats_row.get("unique_sessions");
360+
let unique_actors: i32 = stats_row.get("unique_actors");
357361
let total_events: i32 = stats_row.get("total_events");
358362

359363
let report = match self.report_type.as_str() {
@@ -366,8 +370,8 @@ impl GenerateComplianceReportTool {
366370
},
367371
"summary": {
368372
"total_events": total_events,
369-
"unique_sessions": unique_sessions,
370-
"events_per_session": if unique_sessions > 0 { total_events as f64 / unique_sessions as f64 } else { 0.0 },
373+
"unique_actors": unique_actors,
374+
"events_per_actor": if unique_actors > 0 { total_events as f64 / unique_actors as f64 } else { 0.0 },
371375
"success_rate": *result_counts.get("SUCCESS").unwrap_or(&0) as f64 / total_events.max(1) as f64 * 100.0
372376
}
373377
}),
@@ -380,7 +384,7 @@ impl GenerateComplianceReportTool {
380384
},
381385
"statistics": {
382386
"total_events": total_events,
383-
"unique_sessions": unique_sessions,
387+
"unique_actors": unique_actors,
384388
"event_types": event_counts,
385389
"results": result_counts
386390
}
@@ -439,23 +443,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
439443
// Set global database pool
440444
DB_POOL.set(db_pool).expect("DB_POOL already initialized");
441445

442-
// Create SQLite session storage
443-
let session_storage = Arc::new(SqliteSessionStorage::new().await?);
444-
println!("Session storage initialized successfully");
445-
446446
let server = McpServer::builder()
447447
.name("audit-trail-server")
448448
.version("1.0.0")
449449
.title("Audit Trail Server")
450450
.instructions(
451451
"This server provides compliance-focused audit trail logging with SQLite persistence.",
452452
)
453-
.with_session_storage(session_storage)
454453
.tool(LogAuditEventTool::default())
455454
.tool(SearchAuditTrailTool::default())
456455
.tool(GenerateComplianceReportTool::default())
457456
.bind_address("127.0.0.1:8009".parse()?)
458-
.sse(true)
459457
.build()?;
460458

461459
println!("Audit trail server running at: http://127.0.0.1:8009/mcp");

examples/calculator-add-builder-server/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3636

3737
let server = McpServer::builder()
3838
.name("calculator_add_builder")
39-
.version("0.3.47")
39+
.version("0.4.0")
4040
.title("Calculator Add Builder Server")
4141
.instructions("Add two numbers using builder pattern (Level 3 - Runtime Flexibility)")
4242
.tool(add_tool)

examples/calculator-add-function-server/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2626

2727
let server = McpServer::builder()
2828
.name("calculator_add_function")
29-
.version("0.3.47")
29+
.version("0.4.0")
3030
.title("Calculator Add Function Server")
3131
.instructions("Add two numbers using function macro (Level 1 - Ultra Simple)")
3232
.tool_fn(calculator_add) // Perfect! Use the original function name

examples/calculator-add-manual-server/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
105105
info!("Starting calculator_add_manual server (Level 4)");
106106
let server = McpServer::builder()
107107
.name("calculator_add_manual")
108-
.version("0.3.47")
108+
.version("0.4.0")
109109
.title("Calculator Add Manual Server")
110110
.instructions(
111111
"Add two numbers using fully manual implementation (Level 4 - Maximum Control)",

examples/calculator-add-simple-server-derive/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
5454
info!("Starting calculator_add_derive server");
5555
let server = McpServer::builder()
5656
.name("calculator_add_derive")
57-
.version("0.3.47")
57+
.version("0.4.0")
5858
.title("Calculator Add Derive Server")
5959
.instructions("Add two numbers using derive macro (Level 2)")
6060
.tool(CalculatorAddDeriveTool { a: 0.0, b: 0.0 })

examples/client-initialise-server/src/main.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ async fn main() -> Result<()> {
440440

441441
McpServer::builder()
442442
.name("client-initialise-server")
443-
.version("1.0.0")
443+
.version("0.4.0")
444444
.title("MCP Initialize Test Server")
445445
.bind_address(bind_address)
446446
.with_session_storage(storage_arc)
@@ -473,7 +473,7 @@ async fn main() -> Result<()> {
473473

474474
McpServer::builder()
475475
.name("client-initialise-server")
476-
.version("1.0.0")
476+
.version("0.4.0")
477477
.title("MCP Initialize Test Server")
478478
.bind_address(bind_address)
479479
.with_session_storage(storage_arc)
@@ -515,7 +515,7 @@ async fn main() -> Result<()> {
515515

516516
McpServer::builder()
517517
.name("client-initialise-server")
518-
.version("1.0.0")
518+
.version("0.4.0")
519519
.title("MCP Initialize Test Server")
520520
.bind_address(bind_address)
521521
.with_session_storage(storage_arc)
@@ -543,7 +543,7 @@ async fn main() -> Result<()> {
543543

544544
McpServer::builder()
545545
.name("client-initialise-server")
546-
.version("1.0.0")
546+
.version("0.4.0")
547547
.title("MCP Initialize Test Server")
548548
.bind_address(bind_address)
549549
.with_session_storage(storage_arc)

0 commit comments

Comments
 (0)