ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Simple.pm
Revision: 1.6
Committed: Sun Oct 11 01:02:38 2026 UTC (4 weeks, 2 days ago) by root
Branch: MAIN
Changes since 1.5: +2 -1 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.5 The selected token authenticates either its URL path or an
105     C<Authorization: Bearer TOKEN> header at the clean URL returned by
106 root 1.6 C<< $server->url (0) >>. The corresponding executable's C<--print-url>
107     option prints the first accepted token in its URL.
108 root 1.1
109     C<name> selects the C<[server NAME]> configuration section and credential.
110 root 1.3 It defaults to the program's basename.
111 root 1.5 Named settings C<bind>, C<path>, C<url_host>, C<url_scheme> and C<tokens>
112     are loaded from the global configuration. Repeated C<--token> options
113     replace the configured list in the executables.
114 root 1.3 An explicit C<bind> overrides the configured bind address. Other arguments
115 root 1.1 override these settings. Explicit authentication options replace the default authentication.
116    
117     The global secret is generated automatically when needed; see
118     L<AnyEvent::MCP/GLOBAL CONFIGURATION>. No configuration is loaded for stdio.
119 root 1.4 HTTP startup neither prints to stdout nor changes its buffering.
120 root 1.1
121     =item mcp_stdio %config
122    
123     Creates and runs a server over stdin/stdout. Arguments override the collected
124     configuration. Stdout buffering is disabled. On stdin EOF, pending replies
125     finish and stdout drains before
126     this function returns. Stream errors close the connection and cancel pending
127     requests.
128    
129     =item mcp_http %config
130    
131     Creates and runs an HTTP server. For example:
132    
133     mcp_http
134     host => "127.0.0.1",
135     port => 31740,
136     path => "/mcp/";
137    
138     Arguments override the collected configuration. Unlike C<mcp_start>, this
139     function does not supply cookie authentication or print a URL; use
140     C<on_start> for application-specific startup output.
141    
142     All three starters return when the server is closed or on C<SIGINT> or
143     C<SIGTERM>. Signal shutdown closes connections and cancels outstanding
144     requests. The signal watchers belong to the blocking call.
145    
146     =back
147    
148     =cut
149    
150     package AnyEvent::MCP::Server::Simple;
151    
152     use common::sense;
153     use AnyEvent ();
154     use AnyEvent::MCP ();
155     use AnyEvent::MCP::Server::Toolset ();
156     use Exporter ();
157    
158     our @ISA = qw(Exporter);
159     our @EXPORT = qw(mcp_config mcp_tool mcp_start mcp_stdio mcp_http);
160    
161     my %config;
162     my $toolset = AnyEvent::MCP::Server::Toolset->new;
163    
164     sub _run;
165    
166     sub mcp_config {
167     my %kv = @_;
168    
169     $kv{instructions} = AnyEvent::MCP::_clean_text $kv{instructions}
170     if exists $kv{instructions};
171    
172     @config{keys %kv} = values %kv;
173     }
174    
175     sub mcp_tool {
176     $toolset->tool (@_)
177     }
178    
179     sub mcp_start {
180 root 1.3 require AnyEvent::MCP::Config;
181 root 1.1
182     my %kv = (%config, @_);
183 root 1.3 my $name = AnyEvent::MCP::Config::server_name ($kv{name});
184 root 1.1
185     $kv{name} = $name;
186     $kv{server_info} ||= { name => $name, version => "0" };
187    
188     return mcp_stdio (%kv)
189     if $ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio";
190    
191     my $global = AnyEvent::MCP::Config->global;
192    
193     %kv = (
194     path => "/",
195 root 1.3 $global->http_settings ($name, delete $kv{bind}),
196 root 1.1 %kv,
197     );
198    
199 root 1.4 mcp_http (%kv)
200 root 1.1 }
201    
202     sub mcp_stdio {
203     require AnyEvent::MCP::Server::JSONL;
204    
205     binmode STDIN;
206     binmode STDOUT;
207     my $selected = select STDOUT;
208     $| = 1;
209     select $selected;
210    
211     my $server = AnyEvent::MCP::Server::JSONL->new (toolsets => [$toolset], %config, @_);
212     $server->accept (\*STDIN, \*STDOUT);
213     _run $server
214     }
215    
216     sub mcp_http {
217     require AnyEvent::MCP::Server::HTTP;
218    
219     my $server = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], %config, @_);
220     $server->listen;
221     _run $server
222     }
223    
224     sub _run {
225     my ($server) = @_;
226    
227     my $cv = AnyEvent->condvar;
228     my $stopped;
229     my $stop = sub { $cv->send unless $stopped++ };
230     my $on_close = $server->{on_close};
231     $server->{on_close} = sub {
232     $on_close->(@_) if $on_close;
233     $stop->();
234     };
235    
236     if ($server->isa ("AnyEvent::MCP::Server::JSONL")) {
237     my $on_connection_close = $server->{on_connection_close};
238     $server->{on_connection_close} = sub {
239     $on_connection_close->(@_) if $on_connection_close;
240     $stop->();
241     };
242     }
243    
244     my @signals = map {
245     AnyEvent->signal (signal => $_, cb => sub { $server->close })
246     } qw(INT TERM);
247    
248     my $ok = eval {
249     $server->event (start =>);
250     $cv->recv;
251     1
252     };
253     my $error = $@;
254     $server->close;
255     die $error unless $ok;
256     return
257     }
258    
259     1