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
//! Server-side logging from a tool handler: `extra.log(..)` and the level that
//! decides what a client actually sees (Phase 118.2, CONF-10).
//!
//! Run with: cargo run --example s55_handler_logging
//!
//! It finishes in well under a second: the sink is a local capture, so nothing
//! here opens a port, spawns a transport or touches the network. Contrast
//! `examples/s54_v2_dual_conformance.rs`, which drives the same `extra.log(..)`
//! surface over a real streamable-HTTP server for the official conformance
//! suite — same API, opposite end of the wiring.
//!
//! # What this shows
//!
//! A handler emits diagnostics through
//! [`RequestHandlerExtra::log`](pmcp::RequestHandlerExtra::log) and
//! [`log_with_data`](pmcp::RequestHandlerExtra::log_with_data). Every record it
//! emits at or above the request's effective level is handed to that request's
//! notification sink and reaches the client as a `notifications/message`
//! notification; everything below it is dropped at the emitter and never leaves
//! the process.
//!
//! In a real server you do **not** wire the sink or the level yourself — the
//! transport does both, per request:
//!
//! | Era | How the client asks for a level |
//! |-----|--------------------------------|
//! | 2025-11-25 (v1) | the `logging/setLevel` RPC, remembered for that SESSION |
//! | 2026-07-28 (v2) | `params._meta["io.modelcontextprotocol/logLevel"]`, for THAT REQUEST only (the RPC is retired) |
//!
//! When the client asked for neither, `DEFAULT_LOG_LEVEL` — `info` — applies.
//!
//! This example stands the sink up by hand so the whole story fits in one
//! process with no ports and no client: the sink here prints the exact JSON a
//! connected client would receive on the wire.
//!
//! # Three things worth knowing
//!
//! * **Levels order by SEVERITY, not alphabetically.** `error` is more severe
//! than `debug`, which is what `LoggingLevel`'s `Ord` encodes — a plain string
//! comparison would put `critical` below `debug` and invert every filter.
//! * **`Ok(())` is not delivery acknowledgement.** It means the record was handed
//! to whatever sink this request has, or that there was none. A handler with no
//! sink attached — `RequestHandlerExtra::default()`, as used in unit tests —
//! logs successfully and emits nothing, which is what keeps a logging handler
//! callable outside a server.
//! * **Emitting is synchronous.** No `.await`, so a handler can log from anywhere
//! in its body without restructuring.
//! * **Every frame carries `data`, and a plain `log(..)` repeats the message
//! there.** That is why the printed JSON below shows
//! `"message":"query dispatched","data":"query dispatched"` — not a bug. The
//! MCP schema declares `data` REQUIRED with no `message` member at all, and the
//! official reference client's `z.unknown()` is non-optional under zod v4, so a
//! frame without `data` is dropped on the floor. pmcp therefore defaults `data`
//! to the message and keeps `message` alongside as an extension. A
//! `log_with_data(..)` value is passed through verbatim and never overwritten
//! — the `slow query` record below shows both halves at once.
use ;
use ;
use RequestHandlerExtra;
use json;
/// The per-request notification sink's exact type, spelled once.
///
/// This is the signature `RequestHandlerExtra::with_log_sink` takes, so it is
/// worth naming: it is **synchronous** and it returns `()`. A sink that cannot
/// report failure is why `log(..)`'s `Ok(())` is not a delivery acknowledgement.
type LogSink = ;
/// Stand in for the transport's per-request notification sink.
///
/// A real server never writes this: `Server` and `ServerCore` both attach the
/// request's sink at dispatch, from the transport's back-channel. Printing the
/// serialized notification is the point — this is byte-for-byte what a connected
/// client receives.
/// The body of a tool handler. In a real server this is
/// `ToolHandler::handle(&self, args, extra)`.