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