ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Simple.pm
Revision: 1.7
Committed: Sun Oct 11 01:08:53 2026 UTC (4 weeks, 1 day ago) by root
Branch: MAIN
Changes since 1.6: +18 -7 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::MCP::Server::Simple - simple function interface for MCP servers
4    
5     =head1 SYNOPSIS
6    
7     use AnyEvent::MCP::Server::Simple;
8    
9     mcp_config
10     server_info => {
11     name => "example",
12     version => "0",
13     },
14     instructions => <<'EOF';
15     Use echo to return the supplied text.
16     EOF
17    
18     mcp_tool echo =>
19     "Return the supplied text.",
20     {
21     type => "object",
22     properties => { text => { type => "string" } },
23     required => ["text"],
24     },
25     sub {
26     my ($req, $args) = @_;
27    
28     $req->reply ({
29     content => [{ type => "text", text => $args->{text} }],
30     });
31     };
32    
33     mcp_start;
34    
35     =head1 DESCRIPTION
36    
37 root 1.2 For most MCP servers, registering a few tools and starting a transport is
38     enough. This module does just that: call C<mcp_tool> to register callbacks,
39     optionally configure the server with C<mcp_config>, then start it with
40     C<mcp_start>, C<mcp_stdio> or C<mcp_http>. Registration alone does not start
41     a server or an event loop.
42 root 1.1
43     Human-readable descriptions and instructions are stripped of surrounding
44     whitespace and common indentation when registered, so indented here-documents
45     can be used naturally.
46    
47     C<mcp_start>, C<mcp_stdio> and C<mcp_http> are alternative blocking entry
48 root 1.2 points for a self-contained server. They run AnyEvent until shutdown; you
49     do not start another event loop. If your application already owns the server
50     lifetime or needs several transports, use the object interfaces instead.
51    
52     Simple uses L<AnyEvent::MCP::Server> and a default Toolset internally; it is
53     not a second server implementation. Applications needing custom dispatch,
54     such as a multibroker, can use the Server API directly.
55 root 1.1
56     =head1 FUNCTIONS
57    
58     =over
59    
60     =item mcp_config %config
61    
62     Adds server configuration. Repeated calls extend or replace previously declared
63     values. C<instructions> is treated as human-readable text and normalized.
64    
65     C<server_info> contains the MCP server metadata, including C<name> and
66     C<version>. C<instructions> contains guidance for the client.
67    
68     C<on_request> is a synchronous hook called before dispatch:
69    
70     $callback->($server, $req)
71    
72     For example, an application can refresh database state before each request.
73     It does not replace dispatch or receive a completion callback. These options
74     belong to Server; Simple merely collects and passes them on.
75    
76     C<on_start> is C<$callback->($server)>, called after the transport is ready
77     and before entering the event loop. It can print application startup
78     information or install watchers. C<$server> is the underlying server object.
79     The callback must not block.
80    
81     =item mcp_tool $name, $description, $input_schema, $callback->($req, $args)
82    
83     Registers a tool named C<$name> with C<$description> and the JSON Schema
84     hash reference C<$input_schema>. C<$description> is normalized like
85     C<instructions>.
86    
87     C<$callback> receives an L<AnyEvent::MCP::Server::Request> object C<$req>
88     and its C<< $req->{args} >> hash reference as C<$args>, for convenience.
89    
90     Tools complete the request with C<< $req->reply ($result) >>, or use
91     C<< $req->result ($data) >> and C<< $req->error ($message) >> for convenience.
92     Request context is available directly in
93     the hash, for example C<< $req->{tool} >> and C<< $req->{server} >>.
94    
95     =item mcp_start %config
96    
97     Starts and runs the usual server. When
98     C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL> is C<stdio>, uses C<mcp_stdio>.
99 root 1.4 Otherwise starts HTTP without printing its URL. HTTP-capable executables
100     provide C<--print-url> to retrieve the URL explicitly.
101 root 1.1
102 root 1.3 The HTTP defaults are IPv4 loopback, a stable name-derived port in
103     16384..32767, and C</> as the endpoint path.
104 root 1.7 Without explicit C<tokens>, one server-specific token is derived from
105     the private global secret. The selected token authenticates either its URL
106     path or an
107 root 1.5 C<Authorization: Bearer TOKEN> header at the clean URL returned by
108 root 1.6 C<< $server->url (0) >>. The corresponding executable's C<--print-url>
109     option prints the first accepted token in its URL.
110 root 1.1
111     C<name> selects the C<[server NAME]> configuration section and credential.
112 root 1.3 It defaults to the program's basename.
113 root 1.5 Named settings C<bind>, C<path>, C<url_host>, C<url_scheme> and C<tokens>
114 root 1.7 are loaded from the global configuration. Explicit C<tokens> (an ordered,
115     nonempty array reference of URL-safe strings) replaces the configured list
116     and the derived default. Every token works in the URL and bearer header.
117 root 1.3 An explicit C<bind> overrides the configured bind address. Other arguments
118 root 1.7 override these settings.
119 root 1.1
120     The global secret is generated automatically when needed; see
121     L<AnyEvent::MCP/GLOBAL CONFIGURATION>. No configuration is loaded for stdio.
122 root 1.4 HTTP startup neither prints to stdout nor changes its buffering.
123 root 1.7 C<mcp_start> does not parse C<@ARGV>; applications parse their own options.
124     The bundled executables use L<AnyEvent::MCP::Config/http_getopt> for common
125     options, and handle C<--print-url> before starting the event loop.
126 root 1.1
127     =item mcp_stdio %config
128    
129     Creates and runs a server over stdin/stdout. Arguments override the collected
130     configuration. Stdout buffering is disabled. On stdin EOF, pending replies
131     finish and stdout drains before
132     this function returns. Stream errors close the connection and cancel pending
133     requests.
134    
135     =item mcp_http %config
136    
137     Creates and runs an HTTP server. For example:
138    
139     mcp_http
140     host => "127.0.0.1",
141     port => 31740,
142 root 1.7 path => "/mcp/",
143     tokens => ["your-long-random-secret"];
144 root 1.1
145     Arguments override the collected configuration. Unlike C<mcp_start>, this
146 root 1.7 function does not derive or supply authentication. B<Always> pass C<tokens>
147     or enforce authentication in a trusted embedding layer; otherwise the HTTP
148     server accepts unauthenticated MCP requests. It does not print a URL; use
149     C<on_start> for application-specific startup output. Neither function
150     implements OAuth or Basic authentication. See
151     L<AnyEvent::MCP::Server::HTTP/AUTHENTICATION> for details.
152 root 1.1
153     All three starters return when the server is closed or on C<SIGINT> or
154     C<SIGTERM>. Signal shutdown closes connections and cancels outstanding
155     requests. The signal watchers belong to the blocking call.
156    
157     =back
158    
159     =cut
160    
161     package AnyEvent::MCP::Server::Simple;
162    
163     use common::sense;
164     use AnyEvent ();
165     use AnyEvent::MCP ();
166     use AnyEvent::MCP::Server::Toolset ();
167     use Exporter ();
168    
169     our @ISA = qw(Exporter);
170     our @EXPORT = qw(mcp_config mcp_tool mcp_start mcp_stdio mcp_http);
171    
172     my %config;
173     my $toolset = AnyEvent::MCP::Server::Toolset->new;
174    
175     sub _run;
176    
177     sub mcp_config {
178     my %kv = @_;
179    
180     $kv{instructions} = AnyEvent::MCP::_clean_text $kv{instructions}
181     if exists $kv{instructions};
182    
183     @config{keys %kv} = values %kv;
184     }
185    
186     sub mcp_tool {
187     $toolset->tool (@_)
188     }
189    
190     sub mcp_start {
191 root 1.3 require AnyEvent::MCP::Config;
192 root 1.1
193     my %kv = (%config, @_);
194 root 1.3 my $name = AnyEvent::MCP::Config::server_name ($kv{name});
195 root 1.1
196     $kv{name} = $name;
197     $kv{server_info} ||= { name => $name, version => "0" };
198    
199     return mcp_stdio (%kv)
200     if $ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio";
201    
202     my $global = AnyEvent::MCP::Config->global;
203    
204     %kv = (
205     path => "/",
206 root 1.3 $global->http_settings ($name, delete $kv{bind}),
207 root 1.1 %kv,
208     );
209    
210 root 1.4 mcp_http (%kv)
211 root 1.1 }
212    
213     sub mcp_stdio {
214     require AnyEvent::MCP::Server::JSONL;
215    
216     binmode STDIN;
217     binmode STDOUT;
218     my $selected = select STDOUT;
219     $| = 1;
220     select $selected;
221    
222     my $server = AnyEvent::MCP::Server::JSONL->new (toolsets => [$toolset], %config, @_);
223     $server->accept (\*STDIN, \*STDOUT);
224     _run $server
225     }
226    
227     sub mcp_http {
228     require AnyEvent::MCP::Server::HTTP;
229    
230     my $server = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], %config, @_);
231     $server->listen;
232     _run $server
233     }
234    
235     sub _run {
236     my ($server) = @_;
237    
238     my $cv = AnyEvent->condvar;
239     my $stopped;
240     my $stop = sub { $cv->send unless $stopped++ };
241     my $on_close = $server->{on_close};
242     $server->{on_close} = sub {
243     $on_close->(@_) if $on_close;
244     $stop->();
245     };
246    
247     if ($server->isa ("AnyEvent::MCP::Server::JSONL")) {
248     my $on_connection_close = $server->{on_connection_close};
249     $server->{on_connection_close} = sub {
250     $on_connection_close->(@_) if $on_connection_close;
251     $stop->();
252     };
253     }
254    
255     my @signals = map {
256     AnyEvent->signal (signal => $_, cb => sub { $server->close })
257     } qw(INT TERM);
258    
259     my $ok = eval {
260     $server->event (start =>);
261     $cv->recv;
262     1
263     };
264     my $error = $@;
265     $server->close;
266     die $error unless $ok;
267     return
268     }
269    
270     1