ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Request.pm
Revision: 1.6
Committed: Wed Oct 21 17:28:16 2026 UTC (3 weeks, 2 days ago) by root
Branch: MAIN
CVS Tags: rel-0_81, rel-0_82, rel-0_71, rel-0_61, rel-0_65, rel-0_6, rel-0_7, rel-0_8, rel-0_62
Changes since 1.5: +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    
141     Uses C<notify>, with the same best-effort delivery. Calls after completion
142     are ignored.
143    
144 root 1.4 =item $req->cancel ($reason)
145 root 1.1
146     Marks the request as cancelled and calls C<on_cancel>, allowing the handler
147     to stop work and release resources. C<$reason> is an optional explanation.
148    
149 root 1.5 Cancellation completes the transport's request bookkeeping without sending
150     a JSON-RPC response.
151 root 1.1
152 root 1.4 =back
153    
154 root 1.1 =head1 EVENTS
155    
156     These callbacks are stored in the request hash like other MCP events.
157    
158 root 1.4 =over
159    
160     =item on_reply => $callback->($req, $result)
161 root 1.1
162     Called once when the request is completed with C<reply>. The server installs
163     C<$callback> to deliver the result.
164    
165 root 1.4 =item on_notify => $callback->($req, $method, $params)
166 root 1.1
167     Called for each notification while the request is active. The server installs
168     C<$callback> to deliver notifications through the transport.
169    
170 root 1.4 =item on_cancel => $callback->($req, $reason)
171 root 1.1
172     Called once when the request is cancelled. A handler can set C<$callback>
173     to cancel its asynchronous operation.
174    
175 root 1.4 =back
176    
177 root 1.1 =cut
178    
179     package AnyEvent::MCP::Server::Request;
180    
181     use common::sense;
182     use AnyEvent::MCP ();
183     use JSON::XS ();
184    
185     our @ISA = qw(AnyEvent::MCP::Base);
186    
187     sub require_permission {
188     my ($self, $request) = @_;
189    
190     my $permissions = $self->{server}{permissions}
191     or die "Server has no permission configuration\n";
192    
193     $permissions->require_permission ($self, $request)
194     }
195    
196     sub reply {
197     my ($self, $result) = @_;
198    
199     die "MCP request already finished\n"
200     if $self->{finished};
201    
202     $self->{finished} = 1;
203    
204     my $reply = delete $self->{on_reply};
205     delete @$self{qw(on_notify on_cancel _reply)};
206    
207     $reply->($self, $result) if $reply
208     }
209    
210     sub error {
211     my ($self, $message) = @_;
212    
213     $self->reply ({
214     isError => JSON::XS::true,
215     content => [{ type => "text", text => $message }],
216     })
217     }
218    
219     sub result {
220     my ($self, $data) = @_;
221    
222 root 1.3 ref $data eq "HASH"
223     or die "MCP structured result must be an object\n";
224 root 1.1
225     $self->reply ({
226     structuredContent => $data,
227     content => [{ type => "text", text => JSON::XS->new->encode ($data) }],
228     })
229     }
230    
231     sub notify {
232     my ($self, $method, $params) = @_;
233    
234     return if $self->{finished};
235    
236     $self->event (notify => $method, $params)
237     }
238    
239     sub progress {
240     my ($self, $progress, $total, $message) = @_;
241    
242     return unless $self->{on_notify} && !$self->{finished};
243    
244     my $meta = $self->{params}{_meta}
245     or return;
246    
247     return unless exists $meta->{progressToken};
248    
249     my $params = {
250     progressToken => $meta->{progressToken},
251     progress => $progress,
252     };
253    
254     $params->{total} = $total if defined $total;
255     $params->{message} = $message if defined $message;
256    
257     $self->notify ("notifications/progress", $params)
258     }
259    
260     sub cancel {
261     my ($self, $reason) = @_;
262    
263     return if $self->{finished};
264     $self->{finished} = $self->{cancelled} = 1;
265    
266     delete $self->{peer}{requests}{$self->{id}}
267     if $self->{peer};
268    
269     my $cancel = delete $self->{on_cancel};
270 root 1.5 my $reply = delete $self->{_reply};
271     delete @$self{qw(on_reply on_notify)};
272 root 1.1
273 root 1.5 $cancel->($self, $reason) if $cancel;
274     $reply->(undef) if $reply
275 root 1.1 }
276    
277     1