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, 13 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

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::MCP::Server::Request - one MCP request
4    
5     =head1 DESCRIPTION
6    
7 root 1.2 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 root 1.1
16 root 1.6 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 root 1.1 =head1 PUBLIC FIELDS
22    
23     Request fields are accessed directly through the hash.
24    
25 root 1.2 =over
26 root 1.1
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 root 1.4 =over
86    
87     =item $grant = $req->require_permission ({ %request })
88 root 1.1
89     Synchronously checks a permission request using the server's C<permissions>
90     object. C<%request> has the fields described by
91 root 1.4 L<AnyEvent::MCP::Server::Permissions/check>. Returns the granted sandbox-grant
92 root 1.1 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 root 1.4 =item $req->reply ($result)
104 root 1.1
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 root 1.4 =item $req->error ($message)
113 root 1.1
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 root 1.4 =item $req->result ($data)
118 root 1.1
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 root 1.4 =item $req->notify ($method, $params)
127 root 1.1
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 root 1.4 =item $req->progress ($progress, $total, $message)
136 root 1.1
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 root 1.7 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 root 1.1
144     Uses C<notify>, with the same best-effort delivery. Calls after completion
145     are ignored.
146    
147 root 1.4 =item $req->cancel ($reason)
148 root 1.1
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 root 1.5 Cancellation completes the transport's request bookkeeping without sending
153     a JSON-RPC response.
154 root 1.1
155 root 1.4 =back
156    
157 root 1.1 =head1 EVENTS
158    
159     These callbacks are stored in the request hash like other MCP events.
160    
161 root 1.4 =over
162    
163     =item on_reply => $callback->($req, $result)
164 root 1.1
165     Called once when the request is completed with C<reply>. The server installs
166     C<$callback> to deliver the result.
167    
168 root 1.4 =item on_notify => $callback->($req, $method, $params)
169 root 1.1
170     Called for each notification while the request is active. The server installs
171     C<$callback> to deliver notifications through the transport.
172    
173 root 1.4 =item on_cancel => $callback->($req, $reason)
174 root 1.1
175     Called once when the request is cancelled. A handler can set C<$callback>
176     to cancel its asynchronous operation.
177    
178 root 1.4 =back
179    
180 root 1.1 =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 root 1.7 delete $self->{progress_timer};
208 root 1.1 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 root 1.3 ref $data eq "HASH"
227     or die "MCP structured result must be an object\n";
228 root 1.1
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 root 1.7 delete $self->{progress_timer};
271 root 1.1 delete $self->{peer}{requests}{$self->{id}}
272     if $self->{peer};
273    
274     my $cancel = delete $self->{on_cancel};
275 root 1.5 my $reply = delete $self->{_reply};
276     delete @$self{qw(on_reply on_notify)};
277 root 1.1
278 root 1.5 $cancel->($self, $reason) if $cancel;
279     $reply->(undef) if $reply
280 root 1.1 }
281    
282     1