ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Request.pm
Revision: 1.2
Committed: Fri Oct 9 09:36:49 2026 UTC (4 weeks, 5 days ago) by root
Branch: MAIN
Changes since 1.1: +9 -4 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     =head2 $grant = $req->require_permission ({ %request })
81    
82     Synchronously checks a permission request using the server's C<permissions>
83     object. C<%request> has the fields described by
84     L<AnyEvent::MCP::Server::Permissions/check>. Returns the granted capability
85     array reference; it can be reduced from the requested set.
86    
87     A denial dies. A missing permission throws a special exception handled by
88     Server, which asks and retries the handler. Multiple checks are supported:
89     one-time grants belong to this tool invocation and survive permission retries.
90     They do not authorize later calls. Current rules are checked on every retry.
91    
92     Complete all checks before side effects or asynchronous work starts.
93     The handler can run more than once. Do not catch the permission exception
94     as an ordinary tool error; let it reach Server's dispatch boundary.
95    
96     =head2 $req->reply ($result)
97    
98     Completes the request with the MCP result hash reference C<$result>. The
99     server adds the JSON-RPC envelope. Tools do not construct JSON-RPC errors.
100     Dies if the request has already finished.
101    
102     Use this method for arbitrary MCP results, including structured errors,
103     large results and results requiring a particular content representation.
104    
105     =head2 $req->error ($message)
106    
107     Replies with C<isError> set to true and C<$message> as one text content item.
108     Use C<reply> for errors with structured content or other content types.
109    
110     =head2 $req->result ($data)
111    
112     Replies with the hash reference C<$data> as C<structuredContent>, and its
113     JSON encoding as one text content item for clients that consume only text.
114    
115     This deliberately duplicates the data. Use C<reply> for large results or
116     when JSON text is unsuitable, including clients that encode text as JSON
117     again. Arrays and scalars must be wrapped in an object explicitly.
118    
119     =head2 $req->notify ($method, $params)
120    
121     Sends a notification associated with this request. C<$method> is the
122     notification method name and C<$params> its parameter hash reference.
123    
124     A notification does not complete the request. Delivery is best effort:
125     notifications can be dropped if the transport does not implement delivery.
126     Calls after completion are ignored.
127    
128     =head2 $req->progress ($progress, $total, $message)
129    
130     Sends a progress notification if the client supplied a progress token in
131     C<< $req->{params}{_meta}{progressToken} >>. C<$progress> is the amount
132     completed; C<$total> and C<$message> can be omitted or undefined.
133    
134     Uses C<notify>, with the same best-effort delivery. Calls after completion
135     are ignored.
136    
137     =head2 $req->cancel ($reason)
138    
139     Marks the request as cancelled and calls C<on_cancel>, allowing the handler
140     to stop work and release resources. C<$reason> is an optional explanation.
141    
142     Cancellation does not send a reply.
143    
144     =head1 EVENTS
145    
146     These callbacks are stored in the request hash like other MCP events.
147    
148     =head2 on_reply => $callback->($req, $result)
149    
150     Called once when the request is completed with C<reply>. The server installs
151     C<$callback> to deliver the result.
152    
153     =head2 on_notify => $callback->($req, $method, $params)
154    
155     Called for each notification while the request is active. The server installs
156     C<$callback> to deliver notifications through the transport.
157    
158     =head2 on_cancel => $callback->($req, $reason)
159    
160     Called once when the request is cancelled. A handler can set C<$callback>
161     to cancel its asynchronous operation.
162    
163     =cut
164    
165     package AnyEvent::MCP::Server::Request;
166    
167     use common::sense;
168     use AnyEvent::MCP ();
169     use JSON::XS ();
170    
171     our @ISA = qw(AnyEvent::MCP::Base);
172    
173     sub require_permission {
174     my ($self, $request) = @_;
175    
176     my $permissions = $self->{server}{permissions}
177     or die "Server has no permission configuration\n";
178    
179     $permissions->require_permission ($self, $request)
180     }
181    
182     sub reply {
183     my ($self, $result) = @_;
184    
185     die "MCP request already finished\n"
186     if $self->{finished};
187    
188     $self->{finished} = 1;
189    
190     my $reply = delete $self->{on_reply};
191     delete @$self{qw(on_notify on_cancel _reply)};
192    
193     $reply->($self, $result) if $reply
194     }
195    
196     sub error {
197     my ($self, $message) = @_;
198    
199     $self->reply ({
200     isError => JSON::XS::true,
201     content => [{ type => "text", text => $message }],
202     })
203     }
204    
205     sub result {
206     my ($self, $data) = @_;
207    
208     die "MCP structured result must be an object\n"
209     unless ref $data eq "HASH";
210    
211     $self->reply ({
212     structuredContent => $data,
213     content => [{ type => "text", text => JSON::XS->new->encode ($data) }],
214     })
215     }
216    
217     sub notify {
218     my ($self, $method, $params) = @_;
219    
220     return if $self->{finished};
221    
222     $self->event (notify => $method, $params)
223     }
224    
225     sub progress {
226     my ($self, $progress, $total, $message) = @_;
227    
228     return unless $self->{on_notify} && !$self->{finished};
229    
230     my $meta = $self->{params}{_meta}
231     or return;
232    
233     return unless exists $meta->{progressToken};
234    
235     my $params = {
236     progressToken => $meta->{progressToken},
237     progress => $progress,
238     };
239    
240     $params->{total} = $total if defined $total;
241     $params->{message} = $message if defined $message;
242    
243     $self->notify ("notifications/progress", $params)
244     }
245    
246     sub cancel {
247     my ($self, $reason) = @_;
248    
249     return if $self->{finished};
250     $self->{finished} = $self->{cancelled} = 1;
251    
252     delete $self->{peer}{requests}{$self->{id}}
253     if $self->{peer};
254    
255     my $cancel = delete $self->{on_cancel};
256     delete @$self{qw(on_reply on_notify _reply)};
257    
258     $cancel->($self, $reason) if $cancel
259     }
260    
261     1