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