ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Request.pm
Revision: 1.7
Committed: Sun Nov 8 00:34:40 2026 UTC (3 days, 20 hours ago) by root
Branch: MAIN
CVS Tags: rel-0_9, HEAD
Changes since 1.6: +5 -0 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 AnyEvent::MCP::Server::Request - one MCP request
4
5 =head1 DESCRIPTION
6
7 Each incoming request gets a Request object. The handler can reply
8 immediately, or keep the object and reply when asynchronous work finishes.
9 Returning from the handler does not complete the request; calling
10 C<reply>, C<result> or C<error> does.
11
12 There is one important consequence: do not reply after cancellation or after
13 another callback has already completed the request. Check C<finished> in
14 late asynchronous callbacks. A second reply is a programming error.
15
16 You normally receive this object as the first argument to a
17 L<AnyEvent::MCP::Server::Toolset> callback. See
18 L<AnyEvent::MCP::Server::Simple/TUTORIAL> for complete synchronous and
19 asynchronous examples rather than constructing requests yourself.
20
21 =head1 PUBLIC FIELDS
22
23 Request fields are accessed directly through the hash.
24
25 =over
26
27 =item $req->{server}
28
29 The server handling this request.
30
31 =item $req->{id}
32
33 The JSON-RPC request id, used to associate the response with the request.
34
35 =item $req->{method}
36
37 The JSON-RPC method name, such as C<tools/call>.
38
39 =item $req->{protocol_version}
40
41 The protocol revision used for this request. Modern requests supply it in
42 C<_meta>; legacy requests use the initialized peer or HTTP version.
43
44 =item $req->{peer}
45
46 The connection's peer hash reference, supplied by the transport or embedder.
47 It holds transport context and legacy state, rather than a user identity.
48 Modern client identity and capabilities come from this request's C<_meta>.
49
50 =item $req->{params}
51
52 The original decoded JSON-RPC parameters. For a tool call, this hash contains
53 C<name>, C<arguments> and any C<_meta> supplied by the client. These are the
54 protocol parameters, rather than just the tool arguments.
55
56 =item $req->{tool}
57
58 The tool name from C<< $req->{params}{name} >>. Present for tool calls.
59
60 =item $req->{args}
61
62 The tool argument hash reference from C<< $req->{params}{arguments} >>.
63 Present for tool calls. The tool callback also receives this same reference
64 as its second parameter, for convenience.
65
66 =item $req->{toolset}
67
68 The Toolset invoking the tool callback. Set by the Toolset when it dispatches
69 the call; absent for requests dispatched without a Toolset.
70
71 =item $req->{finished}
72
73 True after a reply or cancellation. Replying to a finished request is an
74 error. Notifications and progress after completion are ignored.
75
76 =item $req->{cancelled}
77
78 True if the request was cancelled. An asynchronous handler can check this
79 before attempting to reply.
80
81 =back
82
83 =head1 METHODS
84
85 =over
86
87 =item $grant = $req->require_permission ({ %request })
88
89 Synchronously checks a permission request using the server's C<permissions>
90 object. C<%request> has the fields described by
91 L<AnyEvent::MCP::Server::Permissions/check>. Returns the granted sandbox-grant
92 array reference; it can be reduced from the requested set.
93
94 A denial dies. A missing permission throws a special exception handled by
95 Server, which asks and retries the handler. Multiple checks are supported:
96 one-time grants belong to this tool invocation and survive permission retries.
97 They do not authorize later calls. Current rules are checked on every retry.
98
99 Complete all checks before side effects or asynchronous work starts.
100 The handler can run more than once. Do not catch the permission exception
101 as an ordinary tool error; let it reach Server's dispatch boundary.
102
103 =item $req->reply ($result)
104
105 Completes the request with the MCP result hash reference C<$result>. The
106 server adds the JSON-RPC envelope. Tools do not construct JSON-RPC errors.
107 Dies if the request has already finished.
108
109 Use this method for arbitrary MCP results, including structured errors,
110 large results and results requiring a particular content representation.
111
112 =item $req->error ($message)
113
114 Replies with C<isError> set to true and C<$message> as one text content item.
115 Use C<reply> for errors with structured content or other content types.
116
117 =item $req->result ($data)
118
119 Replies with the hash reference C<$data> as C<structuredContent>, and its
120 JSON encoding as one text content item for clients that consume only text.
121
122 This deliberately duplicates the data. Use C<reply> for large results or
123 when JSON text is unsuitable, including clients that encode text as JSON
124 again. Arrays and scalars must be wrapped in an object explicitly.
125
126 =item $req->notify ($method, $params)
127
128 Sends a notification associated with this request. C<$method> is the
129 notification method name and C<$params> its parameter hash reference.
130
131 A notification does not complete the request. Delivery is best effort:
132 notifications can be dropped if the transport does not implement delivery.
133 Calls after completion are ignored.
134
135 =item $req->progress ($progress, $total, $message)
136
137 Sends a progress notification if the client supplied a progress token in
138 C<< $req->{params}{_meta}{progressToken} >>. C<$progress> is the amount
139 completed; C<$total> and C<$message> can be omitted or undefined.
140 While an asynchronous tool call remains outstanding, Server automatically
141 reports elapsed-time liveness every 30 seconds when the client supplied
142 a progress token and the transport delivers notifications.
143
144 Uses C<notify>, with the same best-effort delivery. Calls after completion
145 are ignored.
146
147 =item $req->cancel ($reason)
148
149 Marks the request as cancelled and calls C<on_cancel>, allowing the handler
150 to stop work and release resources. C<$reason> is an optional explanation.
151
152 Cancellation completes the transport's request bookkeeping without sending
153 a JSON-RPC response.
154
155 =back
156
157 =head1 EVENTS
158
159 These callbacks are stored in the request hash like other MCP events.
160
161 =over
162
163 =item on_reply => $callback->($req, $result)
164
165 Called once when the request is completed with C<reply>. The server installs
166 C<$callback> to deliver the result.
167
168 =item on_notify => $callback->($req, $method, $params)
169
170 Called for each notification while the request is active. The server installs
171 C<$callback> to deliver notifications through the transport.
172
173 =item on_cancel => $callback->($req, $reason)
174
175 Called once when the request is cancelled. A handler can set C<$callback>
176 to cancel its asynchronous operation.
177
178 =back
179
180 =cut
181
182 package AnyEvent::MCP::Server::Request;
183
184 use common::sense;
185 use AnyEvent::MCP ();
186 use JSON::XS ();
187
188 our @ISA = qw(AnyEvent::MCP::Base);
189
190 sub require_permission {
191 my ($self, $request) = @_;
192
193 my $permissions = $self->{server}{permissions}
194 or die "Server has no permission configuration\n";
195
196 $permissions->require_permission ($self, $request)
197 }
198
199 sub reply {
200 my ($self, $result) = @_;
201
202 die "MCP request already finished\n"
203 if $self->{finished};
204
205 $self->{finished} = 1;
206
207 delete $self->{progress_timer};
208 my $reply = delete $self->{on_reply};
209 delete @$self{qw(on_notify on_cancel _reply)};
210
211 $reply->($self, $result) if $reply
212 }
213
214 sub error {
215 my ($self, $message) = @_;
216
217 $self->reply ({
218 isError => JSON::XS::true,
219 content => [{ type => "text", text => $message }],
220 })
221 }
222
223 sub result {
224 my ($self, $data) = @_;
225
226 ref $data eq "HASH"
227 or die "MCP structured result must be an object\n";
228
229 $self->reply ({
230 structuredContent => $data,
231 content => [{ type => "text", text => JSON::XS->new->encode ($data) }],
232 })
233 }
234
235 sub notify {
236 my ($self, $method, $params) = @_;
237
238 return if $self->{finished};
239
240 $self->event (notify => $method, $params)
241 }
242
243 sub progress {
244 my ($self, $progress, $total, $message) = @_;
245
246 return unless $self->{on_notify} && !$self->{finished};
247
248 my $meta = $self->{params}{_meta}
249 or return;
250
251 return unless exists $meta->{progressToken};
252
253 my $params = {
254 progressToken => $meta->{progressToken},
255 progress => $progress,
256 };
257
258 $params->{total} = $total if defined $total;
259 $params->{message} = $message if defined $message;
260
261 $self->notify ("notifications/progress", $params)
262 }
263
264 sub cancel {
265 my ($self, $reason) = @_;
266
267 return if $self->{finished};
268 $self->{finished} = $self->{cancelled} = 1;
269
270 delete $self->{progress_timer};
271 delete $self->{peer}{requests}{$self->{id}}
272 if $self->{peer};
273
274 my $cancel = delete $self->{on_cancel};
275 my $reply = delete $self->{_reply};
276 delete @$self{qw(on_reply on_notify)};
277
278 $cancel->($self, $reason) if $cancel;
279 $reply->(undef) if $reply
280 }
281
282 1