ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Simple.pm
Revision: 1.19
Committed: Fri Oct 16 08:58:13 2026 UTC (3 weeks, 4 days ago) by root
Branch: MAIN
Changes since 1.18: +5 -16 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 root 1.15 Start with L</TUTORIAL> for a complete server and a first client call.
57     For running the bundled servers without writing Perl, see
58     L<AnyEvent::MCP::Intro>.
59    
60     =head1 TUTORIAL
61    
62     This tutorial builds a small server, calls it, then adds options, permission
63     checks and asynchronous work. You do not need to implement JSON-RPC,
64     authentication or a transport. Simple supplies those parts; your program
65     declares tools and handles their arguments.
66    
67     For installation, connecting AI clients and configuring the bundled servers,
68     see L<AnyEvent::MCP::Intro>. The function reference follows this tutorial.
69    
70     =head2 A complete first server
71    
72     Save the following as C<echo-mcp>:
73    
74     #!/opt/bin/perl
75    
76     use common::sense;
77     use AnyEvent::MCP::Server::Simple;
78    
79     my %options = mcp_getoptions {
80     name => "echo-demo",
81     usage => "Usage: $0 [OPTION]...\n",
82     description => "Return text supplied to the echo tool.",
83     };
84    
85     @ARGV == 0
86     or die "Unexpected arguments\n";
87    
88     mcp_config
89     server_info => {
90     name => "echo-demo",
91     version => "1",
92     },
93     instructions => "Use echo to return the supplied text.";
94    
95     mcp_tool echo =>
96     "Return the supplied text.",
97     {
98     type => "object",
99     properties => {
100     text => {
101     type => "string",
102     maxLength => 4096,
103     },
104     },
105     required => ["text"],
106     },
107     sub {
108     my ($req, $args) = @_;
109    
110     my $text = $args->{text};
111     defined $text and !ref $text and length $text <= 4096
112     or die "text must be a string of at most 4096 characters\n";
113    
114     $req->reply ({
115     content => [{ type => "text", text => $text }],
116     });
117     };
118    
119     mcp_start %options;
120    
121     Use your installed Perl's explicit path in the shebang, or invoke the file
122     with C<perl> as in the commands below.
123    
124     C<mcp_getoptions> parses the common server options and returns startup
125     settings. C<mcp_config> declares protocol metadata and instructions.
126     C<mcp_tool> registers a description, input schema and callback.
127     C<mcp_start> constructs the transport and runs AnyEvent until shutdown.
128     Register everything before starting: code after C<mcp_start> runs only
129     when the server stops.
130    
131     The input schema tells the client what to supply. The server does B<not>
132     validate arguments against JSON Schema, so the callback checks the value it
133     actually uses. A missing text, array or object fails as a tool error.
134     The example allows an empty string.
135    
136     C<$req> is an L<AnyEvent::MCP::Server::Request>; C<$args> is its argument
137     hash reference. Replying finishes the request. Merely returning from the
138     callback does not. You supply the MCP result, and the server supplies the
139     JSON-RPC envelope.
140    
141     =head2 Start and call the server
142    
143     Inspect the common options with:
144    
145     perl echo-mcp --help
146    
147     For a client launching a local subprocess, configure:
148    
149     perl /absolute/path/to/echo-mcp --stdio
150    
151     The client must launch the process itself. Stdin and stdout carry MCP,
152     so starting the program in a terminal and typing text at it is not a test
153     of the echo tool. Print diagnostics to stderr, not stdout.
154    
155     You can make a first call without an AI client. Save this as C<try-echo>
156     in the same directory as C<echo-mcp>:
157    
158     use common::sense;
159     use AnyEvent;
160     use AnyEvent::MCP::Client::JSONL;
161    
162     my $client = AnyEvent::MCP::Client::JSONL->new (
163     command => [$^X, "./echo-mcp", "--stdio"],
164     );
165    
166     my $cv = AE::cv;
167    
168     $client->call (echo => { text => "hello" }, sub {
169     $cv->send (@_);
170     });
171    
172     my ($success, $result) = $cv->recv;
173     $success
174     or die "$result->{message}\n";
175     !$result->{isError}
176     or die "$result->{content}[0]{text}\n";
177    
178     print $result->{content}[0]{text}, "\n";
179    
180     Run C<perl try-echo>. It prints:
181    
182     hello
183    
184     The JSONL client starts the server, connects and negotiates the protocol on
185     first use. C<$^X> chooses the same Perl interpreter as the test client.
186     The condition variable keeps the event loop running until the reply arrives.
187     Keep C<$client> alive while its operations are outstanding.
188    
189     There are two success checks. C<$success> means the JSON-RPC operation
190     completed successfully; a tool can still return C<isError> in that result.
191     This client expects our echo tool's text result. A general client must handle
192     other MCP content representations too.
193    
194     To use HTTP, start the server in one terminal:
195    
196     perl echo-mcp --bind :31742
197    
198     and retrieve its URL in another:
199    
200     perl echo-mcp --bind :31742 --print-url
201    
202     Configure that URL in your MCP client and ask it to call echo with text
203     C<hello>. Both commands use instance C<echo-demo>, unless you override it
204     with C<--name>. Repeat the same listener overrides when printing the URL.
205     C<--print-url> prints the address and exits; it does not start a server.
206    
207     For the small Perl client above, replace the JSONL import and constructor
208     with:
209    
210     use AnyEvent::MCP::Client::HTTP;
211    
212     my $client = AnyEvent::MCP::Client::HTTP->new (
213     url => $ARGV[0],
214     );
215    
216     Pass the printed URL as its first command-line argument.
217     The rest of the call and result handling stays the same.
218    
219     C<mcp_start> supplies named HTTP settings and a token derived from the
220     private global secret. Stdio needs no HTTP configuration. The advertised
221     C<server_info> describes the program; the startup C<name> selects local
222     settings and credentials. They need not be identical.
223     See L<AnyEvent::MCP::Intro/CONFIGURING YOUR SETUP>.
224    
225     =head2 Add an application option
226    
227     Suppose you want the server user to choose the maximum text length.
228     Replace the original C<mcp_getoptions> call with:
229    
230     my $max_length = 4096;
231    
232     my %options = mcp_getoptions (
233     {
234     name => "echo-demo",
235     usage => "Usage: $0 [OPTION]...\n",
236     description => "Return text supplied to the echo tool.",
237     options => [
238     ["--max-length N", "Maximum text length in characters"],
239     ],
240     },
241     "max-length=i" => \$max_length,
242     );
243    
244     $max_length > 0
245     or die "--max-length must be positive\n";
246    
247     Use C<$max_length> in both the schema's C<maxLength> and the callback's
248     length check. The server user can then start:
249    
250     perl echo-mcp --stdio --max-length 1000
251    
252     The option specification is passed to L<Getopt::Long>. The C<options>
253     entries supply human-readable help; they do not register options.
254     The later tool examples use the original 4096-character limit; use your
255     chosen limit in them too if you keep this option.
256     Common options such as C<--stdio>, C<--bind> and C<--help> remain available.
257    
258     If your server also takes positional arguments, validate the remaining
259     C<@ARGV> after C<mcp_getoptions>. It handles C<--help> and C<--print-url>
260     before that validation, so the user can retrieve an HTTP URL without
261     supplying an application workdir or starting the application.
262    
263     Settings affecting URL discovery, such as C<name> and C<path>, should be
264     available to C<mcp_getoptions> through its leading settings hash or an
265     earlier C<mcp_config> call. A setting supplied only after the parser exits
266     for C<--print-url> cannot affect that printed URL.
267    
268     =head2 Choose a result representation
269    
270     Our echo callback returns one text content item. Use C<reply> when the
271     representation matters: text, other content types, a larger result or a
272     particular MCP result shape.
273    
274     For an ordinary result object, you can replace the reply with:
275    
276     $req->result ({ text => $text, length => length $text });
277    
278     C<result> returns that hash as C<structuredContent> and also serializes it
279     as JSON in one text content item. Clients consuming only text can still
280     see the data, but it is deliberately duplicated. Wrap arrays or scalars in
281     an object, or use C<reply> to choose a different representation.
282    
283     To report a tool failure explicitly:
284    
285     $req->error ("The requested operation cannot be completed");
286    
287     A synchronous exception from the handler also becomes a tool-error result.
288     Malformed protocol requests and an unknown tool name are handled by the
289     server. Your callback does not construct a JSON-RPC response itself.
290    
291     Reply once. Code that calls C<reply>, C<result> or C<error> and then calls
292     another reply method is a programming error. Keep a final reply separate
293     from the work leading to it so the completion point is easy to see.
294    
295     =head2 Add a permission check before changing state
296    
297     Authentication alone does not decide which tool operations a user allows.
298     For an example with a side effect but no external worker, add a tool that
299     stores one note in the server's memory.
300    
301     Add this before registering the tools:
302    
303     use AnyEvent::MCP::Server::Permissions;
304    
305     my $note;
306    
307     mcp_config permissions => AnyEvent::MCP::Server::Permissions->new (
308     name => "echo-notes",
309     actions => {
310     remember => "Replace the server's in-memory note",
311     },
312     );
313    
314     Then register this tool before C<mcp_start>:
315    
316     mcp_tool remember =>
317     "Replace the server's in-memory note with the supplied text.",
318     {
319     type => "object",
320     properties => {
321     text => { type => "string", maxLength => 4096 },
322     },
323     required => ["text"],
324     },
325     sub {
326     my ($req, $args) = @_;
327    
328     my $text = $args->{text};
329     defined $text and !ref $text and length $text <= 4096
330     or die "text must be a string of at most 4096 characters\n";
331    
332     $req->require_permission ({
333     tool => $req->{tool},
334     action => "remember",
335     sandbox_grants => [],
336     reason => "replace the server's in-memory note",
337     });
338    
339     $note = $text;
340    
341     $req->result ({ remembered => length $note });
342     };
343    
344     Restart the server and call remember through your client. The default
345     permission helper asks on the server machine. Allow once permits this
346     invocation. Allow always saves a rule for action C<remember>; subsequent
347     matching calls do not ask again. The note itself lasts only for this
348     server process and is shared by its clients.
349    
350     The permission engine's name C<echo-notes> selects its default policy file:
351    
352 root 1.16 ~/.config/anyevent-mcp/rules/echo-notes.ini
353 root 1.15
354     The actual directory follows C<XDG_CONFIG_HOME> when set.
355     This is the shared engine's default convention, different from mcp-fs's
356 root 1.16 C<rules/fs-NAME.ini>. Manage it using C<anyevent-mcp manage-rules-file>
357 root 1.15 with the actual pathname, or supply C<file> to the engine explicitly.
358    
359     For example, a manually written allow rule is:
360    
361     [rule]
362     decision=allow
363     action=remember
364     sandbox-grants=
365    
366     The empty grant list is intentional. This tool updates an in-memory value;
367     it does not launch a sandboxed worker. C<action> is the operation class
368     to authorize, while C<sandbox_grants> describes worker authority.
369    
370     The permission engine checks policy; it does not restrict filesystem or
371     network access by itself. If you add file or command tools, define the
372     necessary grants and enforce the returned set through your worker or
373     sandbox. Do not copy C<sandbox_grants =E<gt> []> into a file-writing
374     handler and assume it confines access.
375    
376     Here the permission name is deliberately fixed and independent of the
377     HTTP instance name. If you want each C<--name> to have separate policy,
378     pass C<$options{name}> as the permission engine's name instead.
379    
380     =head2 Why checks must precede side effects
381    
382     When permission is missing, C<require_permission> throws a special
383     exception. Server catches it, asks, and may invoke the handler again.
384     The assignment to C<$note> therefore comes B<after> the check.
385    
386     With a local prompt, the server retries internally. Modern MCP form asking
387     returns C<input_required>; the client repeats the call with its answer.
388     Earlier one-time grants survive permission retries within this invocation,
389     but do not authorize the next call. Saved rules are rechecked.
390    
391     Do not catch that exception as an ordinary failure around the permission
392     check. It must reach Server's synchronous dispatch boundary.
393     For several checks, complete all of them before modifying state or starting
394     asynchronous work. An C<on_request> hook also runs on retry, so it must not
395     perform a once-only side effect before the handler's checks.
396    
397     To choose client-side form asking for this example:
398    
399     PERL_ANYEVENT_MCP_PERMISSION_ASK=auto perl echo-mcp --stdio
400    
401     C<auto> uses modern form elicitation when the client advertises support,
402     otherwise the local helper. C<mcp> requires that support; C<helper> keeps
403     the prompt local. The tiny try-echo client advertises no form support,
404     so auto uses the helper there. To handle forms in a Perl client, see
405     L<AnyEvent::MCP::Client/on_request> and
406     L<AnyEvent::MCP::Permissions::Ask/elicitation>.
407    
408     For grant definitions, subject scopes and multi-path operations, see
409     L<AnyEvent::MCP::Server::Permissions> and
410     L<AnyEvent::MCP::Server::Toolset/Permission checks and retries>.
411    
412     =head2 Let an asynchronous operation finish later
413    
414     A tool callback runs in the AnyEvent event loop. Blocking it with C<sleep>
415     or a synchronous long-running command also blocks other requests.
416    
417     Add C<use AnyEvent;> and register the following before C<mcp_start>:
418    
419     mcp_tool delayed_echo =>
420     "Return the supplied text after a delay of up to 60 seconds.",
421     {
422     type => "object",
423     properties => {
424     text => { type => "string", maxLength => 4096 },
425     seconds => {
426     type => "integer",
427     minimum => 0,
428     maximum => 60,
429     },
430     },
431     required => ["text", "seconds"],
432     },
433     sub {
434     my ($req, $args) = @_;
435    
436     my $text = $args->{text};
437     defined $text and !ref $text and length $text <= 4096
438     or die "text must be a string of at most 4096 characters\n";
439    
440     my $seconds = $args->{seconds};
441     defined $seconds and !ref $seconds
442     and $seconds =~ /\A[0-9]+\z/ and $seconds <= 60
443     or die "seconds must be an integer from 0 to 60\n";
444    
445     $req->progress (0, 1, "Waiting");
446    
447     my $timer = AnyEvent->timer (after => $seconds, cb => sub {
448     $req->{finished}
449     and return;
450    
451     $req->progress (1, 1, "Done");
452     $req->reply ({
453     content => [{ type => "text", text => $text }],
454     });
455     });
456    
457     $req->{on_cancel} = sub {
458     undef $timer;
459     };
460     };
461    
462     The tool is advertised as C<delayed-echo>, because registration converts
463     underscores to hyphens. Ask the client to call it with text C<hello> and
464     seconds C<2>. The callback returns immediately; the timer supplies the reply
465     later. Other requests can run while it waits.
466    
467     Keep watcher objects alive while they are needed. Here the cancellation
468     callback retains C<$timer>, and the timer's callback retains C<$req> and
469     C<$text>. Cancellation releases the timer. Replying finishes the request
470     and removes its cancellation callback, releasing the watcher as well.
471     No separate job registry is needed for this example.
472    
473     Check C<finished> in late completion callbacks before replying.
474     Cancellation sets both C<finished> and C<cancelled> and sends no reply.
475     For subprocesses or network work, the cancellation callback should stop
476     that operation and release its resources instead of merely abandoning
477     the eventual reply.
478    
479     Server catches exceptions during synchronous dispatch, not exceptions
480     from a later timer or I/O callback. If an asynchronous operation can fail,
481     handle that failure in its completion callback and report it with
482     C<error> or C<reply>, provided the request is still active.
483    
484     C<progress> only sends when the caller supplied a progress token.
485     Delivery is best effort; the HTTP server in this distribution does not
486     deliver progress notifications. A client without progress display can
487     still receive the final result.
488    
489     =head2 When to use the object interfaces
490    
491     Simple owns one default Toolset and the blocking server lifetime.
492     That is useful for a small standalone program. If you already run an
493     event loop, want multiple transports or tool registries, or need custom
494     protocol dispatch, construct the objects directly.
495    
496     L<AnyEvent::MCP::Server::Toolset> registers reusable tools.
497     L<AnyEvent::MCP::Server> handles the protocol.
498     L<AnyEvent::MCP::Server::HTTP> and L<AnyEvent::MCP::Server::JSONL> manage
499     connections. L<AnyEvent::MCP::Server::Request> describes request fields,
500     replies, progress and cancellation. The same tool callback model applies
501     at both levels.
502    
503 root 1.1 =head1 FUNCTIONS
504    
505     =over
506    
507     =item mcp_config %config
508    
509     Adds server configuration. Repeated calls extend or replace previously declared
510     values. C<instructions> is treated as human-readable text and normalized.
511    
512 root 1.15 Server and transport options are passed to the underlying objects.
513     Common options include:
514    
515     =over
516    
517     =item server_info => { name => $name, version => $version }
518 root 1.1
519 root 1.15 Advertised MCP server identity.
520 root 1.1
521 root 1.15 =item instructions => $text
522 root 1.1
523 root 1.15 Human-readable guidance for the client, normalized when configured.
524 root 1.1
525 root 1.15 =item on_request => $callback->($server, $req)
526    
527     Synchronous hook before each dispatch attempt. For example, refresh
528     database state here. This does not replace dispatch or receive a
529     completion callback, and permission retries can invoke it again.
530    
531     =item on_start => $callback->($server)
532    
533     Called after the transport is ready, before entering the event loop.
534     Use it for startup information or installing watchers. Stdout carries
535     protocol messages in stdio mode; print diagnostics to stderr.
536 root 1.1 The callback must not block.
537    
538 root 1.15 =item idle_exit => $seconds
539    
540     Optional positive idle time for spawned stdio servers. With a multibroker
541     advertising C<anyevent-mcp/multibroker> version 1, Simple requests shutdown
542     once no MCP requests have been active for that long. The broker can ignore
543     the request. HTTP servers, ordinary clients and servers without this
544     option are unaffected.
545    
546     =back
547    
548     See L<AnyEvent::MCP::Server> for further protocol options and events.
549 root 1.11
550 root 1.1 =item mcp_tool $name, $description, $input_schema, $callback->($req, $args)
551    
552     Registers a tool named C<$name> with C<$description> and the JSON Schema
553     hash reference C<$input_schema>. C<$description> is normalized like
554     C<instructions>.
555    
556     C<$callback> receives an L<AnyEvent::MCP::Server::Request> object C<$req>
557     and its C<< $req->{args} >> hash reference as C<$args>, for convenience.
558    
559     Tools complete the request with C<< $req->reply ($result) >>, or use
560     C<< $req->result ($data) >> and C<< $req->error ($message) >> for convenience.
561     Request context is available directly in
562     the hash, for example C<< $req->{tool} >> and C<< $req->{server} >>.
563    
564 root 1.8 =item %server_options = mcp_getoptions ({ %settings }, @getopt_options)
565    
566     Parse standard server options C<--name>, C<--bind>, repeatable
567     C<--token>, C<--stdio>, C<--print-url> and C<--help> alongside any
568     application-specific L<Getopt::Long> option specifications:
569    
570     my $verbose;
571     my %options = mcp_getoptions (
572 root 1.9 {
573     usage => "Usage: $0 [OPTION]... WORKDIR\n",
574     description => "Provide example tools through MCP.",
575     option_heading => "Example options:",
576     options => [
577     ["--verbose", "Show additional diagnostics"],
578     ],
579     examples => " $0 --stdio ./project",
580     path => "/",
581     },
582 root 1.8 "verbose!" => \$verbose,
583     );
584     @ARGV == 1 or die "workdir required\n";
585     mcp_start %options;
586    
587 root 1.15 The optional leading hash accepts these help settings:
588    
589     =over
590    
591     =item usage => $text
592    
593     Usage synopsis, including its trailing newline. A default synopsis is
594     generated when omitted.
595    
596     =item description => $text
597    
598     Introductory help paragraph.
599    
600     =item options => [[$label, $description]...]
601    
602     Application option labels and descriptions. A plain string among the
603     pairs starts a new section; a string at the beginning replaces the
604     default section heading.
605    
606     =item option_heading => $text
607    
608     Initial section heading, defaulting to C<Application options:>.
609    
610     =item examples => $text
611    
612     Preformatted examples.
613    
614     =back
615    
616 root 1.12 The common MCP options are documented automatically and their descriptions
617     are shared by all callers. Application option descriptions are explicit;
618     a C<Getopt::Long> specification does not contain enough information to
619     write one.
620 root 1.9
621     Other settings in that hash, such as C<path>, C<auth> or C<url_scheme>,
622     are passed through to C<mcp_start>. Without C<usage>, a default synopsis
623     is generated. Standard C<--help> prints the expanded help and exits;
624     a parsing error dies with the shorter usage synopsis.
625     C<--print-url> resolves the same name, bind, tokens and path as
626 root 1.8 startup, prints the first accepted token in the URL and exits B<without>
627     starting a listener. It requires a persistently stored master secret for
628     derived credentials and cannot predict a port requested as zero.
629    
630     All other option specifications and remaining C<@ARGV> follow the caller's
631     normal Getopt::Long configuration, including C<require_order> if selected.
632 root 1.19 C<--stdio> is understood by C<mcp_start> and skips HTTP configuration.
633     C<--connect URL> connects to a multibroker at
634 root 1.17 C<http://host:port/token/alias> instead of serving stdio or starting an HTTP
635     listener by default. It overrides implicit stdio from the environment;
636     explicit C<--stdio> and C<--connect> cannot be combined.
637 root 1.8 The helper deliberately does not enforce application-specific positional
638     arguments; C<--print-url> should not have to supply a workdir just to
639     print an address. It is a parser, not a second command language.
640    
641 root 1.1 =item mcp_start %config
642    
643 root 1.15 Starts and runs the usual server. When C<stdio> is true or
644 root 1.1 C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL> is C<stdio>, uses C<mcp_stdio>.
645 root 1.4 Otherwise starts HTTP without printing its URL. HTTP-capable executables
646     provide C<--print-url> to retrieve the URL explicitly.
647 root 1.1
648 root 1.3 The HTTP defaults are IPv4 loopback, a stable name-derived port in
649     16384..32767, and C</> as the endpoint path.
650 root 1.7 Without explicit C<tokens>, one server-specific token is derived from
651     the private global secret. The selected token authenticates either its URL
652     path or an
653 root 1.5 C<Authorization: Bearer TOKEN> header at the clean URL returned by
654 root 1.6 C<< $server->url (0) >>. The corresponding executable's C<--print-url>
655     option prints the first accepted token in its URL.
656 root 1.1
657 root 1.10 C<name> selects the C<[instance NAME]> configuration section and credential.
658 root 1.16 It defaults to C<default>.
659 root 1.5 Named settings C<bind>, C<path>, C<url_host>, C<url_scheme> and C<tokens>
660 root 1.7 are loaded from the global configuration. Explicit C<tokens> (an ordered,
661     nonempty array reference of URL-safe strings) replaces the configured list
662     and the derived default. Every token works in the URL and bearer header.
663 root 1.3 An explicit C<bind> overrides the configured bind address. Other arguments
664 root 1.7 override these settings.
665 root 1.1
666     The global secret is generated automatically when needed; see
667     L<AnyEvent::MCP/GLOBAL CONFIGURATION>. No configuration is loaded for stdio.
668 root 1.4 HTTP startup neither prints to stdout nor changes its buffering.
669 root 1.7 C<mcp_start> does not parse C<@ARGV>; applications parse their own options.
670 root 1.8 The bundled executables use C<mcp_getoptions>, which handles shared
671     options and URL discovery before starting the event loop.
672 root 1.1
673 root 1.17 =item mcp_connect %config
674    
675     Run a reverse-connecting JSONL server using C<connect =E<gt>
676     "http://host:port/token/alias">. It sends the authenticated extension hello
677     then serves ordinary MCP over the socket. Retry every second for up to sixty
678     attempts after a disconnect; calls are cancelled rather than replayed.
679     This connection has no spawned-child lifecycle authority.
680    
681 root 1.1 =item mcp_stdio %config
682    
683     Creates and runs a server over stdin/stdout. Arguments override the collected
684     configuration. Stdout buffering is disabled. On stdin EOF, pending replies
685     finish and stdout drains before
686     this function returns. Stream errors close the connection and cancel pending
687     requests.
688    
689     =item mcp_http %config
690    
691     Creates and runs an HTTP server. For example:
692    
693     mcp_http
694     host => "127.0.0.1",
695     port => 31740,
696 root 1.7 path => "/mcp/",
697     tokens => ["your-long-random-secret"];
698 root 1.1
699     Arguments override the collected configuration. Unlike C<mcp_start>, this
700 root 1.8 function does not derive or supply authentication. B<Always> pass C<tokens>,
701     or explicitly pass C<auth =E<gt> "none"> when a trusted embedding layer
702     enforces access. Omitting both is an error, not an unauthenticated default.
703     It does not print a URL; use
704 root 1.7 C<on_start> for application-specific startup output. Neither function
705     implements OAuth or Basic authentication. See
706     L<AnyEvent::MCP::Server::HTTP/AUTHENTICATION> for details.
707 root 1.1
708     All three starters return when the server is closed or on C<SIGINT> or
709     C<SIGTERM>. Signal shutdown closes connections and cancels outstanding
710     requests. The signal watchers belong to the blocking call.
711    
712     =back
713    
714     =cut
715    
716     package AnyEvent::MCP::Server::Simple;
717    
718     use common::sense;
719     use AnyEvent ();
720     use AnyEvent::MCP ();
721     use AnyEvent::MCP::Server::Toolset ();
722     use Exporter ();
723    
724     our @ISA = qw(Exporter);
725 root 1.17 our @EXPORT = qw(mcp_config mcp_tool mcp_getoptions mcp_start mcp_stdio mcp_http mcp_connect);
726 root 1.1
727     my %config;
728     my $toolset = AnyEvent::MCP::Server::Toolset->new;
729    
730     sub _run;
731    
732     sub mcp_config {
733     my %kv = @_;
734    
735     $kv{instructions} = AnyEvent::MCP::_clean_text $kv{instructions}
736     if exists $kv{instructions};
737    
738 root 1.14 %config = (%config, %kv);
739 root 1.1 }
740    
741     sub mcp_tool {
742     $toolset->tool (@_)
743     }
744    
745 root 1.9 sub _mcp_help_row {
746     my ($option, $description) = @_;
747 root 1.13
748 root 1.9 my $start = " $option";
749     my $margin = " " x 34;
750 root 1.13 my $out = length $start < 34 ? "" : "$start\n";
751     my $line = length $start < 34 ? $start . (substr $margin, length $start) : $margin;
752 root 1.9
753     for my $word (split /\s+/, $description) {
754 root 1.13 if (length $line > 34 && (length $line) + 1 + length $word > 79) {
755 root 1.9 $out .= "$line\n";
756     $line = $margin;
757     }
758 root 1.13
759     $line .= " " if length $line > 34;
760 root 1.9 $line .= $word;
761     }
762    
763     "$out$line\n"
764     }
765    
766     sub _mcp_help {
767 root 1.19 my ($usage, $description, $options, $option_heading, $examples) = @_;
768 root 1.13
769 root 1.9 $usage =~ s/\n*\z/\n/;
770     my $help = $usage;
771     $help .= "\n$description\n" if defined $description && length $description;
772    
773     $help .= "\nCommon options:\n";
774     my @common = (
775     ["--name NAME", "Select named configuration (default: executable name)"],
776     ["--bind ADDRESS[:PORT]", "Override the HTTP listen address or port"],
777     ["--token TOKEN", "Set an accepted token (repeatable; replaces defaults)"],
778     ["--stdio", "Serve MCP on stdin/stdout; other listeners may remain"],
779 root 1.17 ["--connect URL", "Connect to a multibroker at http://host:port/token/alias"],
780 root 1.9 ["--print-url", "Print authenticated HTTP URL and exit without listening"],
781     ["-h, --help", "Display this help and exit"],
782     );
783 root 1.19 $help .= _mcp_help_row (@$_) for @common;
784 root 1.9
785     if ($options && @$options) {
786 root 1.12 $help .= "\n" . ($option_heading // "Application options:") . "\n"
787     if ref $options->[0];
788     for my $option (@$options) {
789     if (ref $option) {
790     $help .= _mcp_help_row (@$option);
791     } else {
792     $help .= "\n" unless $help =~ /\n\n\z/;
793     $help .= "$option\n";
794     }
795     }
796 root 1.9 }
797     $help .= "\nExamples:\n$examples\n"
798     if defined $examples && length $examples;
799    
800     $help
801     }
802    
803 root 1.8 sub mcp_getoptions {
804     require Getopt::Long;
805     require AnyEvent::MCP::Config;
806    
807 root 1.13 my %settings = %{ref $_[0] eq "HASH" ? shift : {}};
808 root 1.8 my $usage = delete $settings{usage}
809 root 1.9 // "Usage: $0 [OPTION]... [ARG]...\n";
810 root 1.13 my @help = delete @settings{qw(description options option_heading examples)};
811 root 1.8 my %url = (%config, %settings);
812     delete @url{qw(name bind stdio)};
813    
814     Getopt::Long::GetOptions (
815 root 1.13 AnyEvent::MCP::Config::http_getopt (\my $name, \my $bind, \my $print_url, \my @tokens),
816     "stdio" => \my $stdio,
817 root 1.17 "connect=s" => \my $connect,
818 root 1.13 "help|h" => \my $help,
819 root 1.8 @_,
820     ) or die $usage;
821    
822     if ($help) {
823 root 1.19 print _mcp_help $usage, @help;
824 root 1.8 exit;
825     }
826    
827     $name = AnyEvent::MCP::Config::server_name ($name // $settings{name} // $config{name});
828     my %server = (
829     %settings,
830     name => $name,
831     (defined $bind ? (bind => $bind) : ()),
832     (@tokens ? (tokens => \@tokens) : ()),
833     ($stdio ? (stdio => 1) : ()),
834 root 1.17 (defined $connect ? (connect => $connect) : ()),
835 root 1.8 );
836    
837     if ($print_url) {
838     my %options = (%url, (@tokens ? (tokens => \@tokens) : ()));
839     my $global = AnyEvent::MCP::Config->global;
840 root 1.16 $options{path} //= $global->{section}{"instance $name"}{path}
841     // $global->{section}{"program " . AnyEvent::MCP::Config::program_name ()}{path} // "/";
842 root 1.8 if (($options{auth} || "") eq "none") {
843     !exists $options{tokens} or die "Cannot combine auth => none with tokens\n";
844     }
845     my $selected_bind = defined $bind ? $bind : ($settings{bind} // $config{bind});
846     print $global->http_url ($name, $selected_bind, %options), "\n";
847     exit;
848     }
849    
850     %server
851     }
852    
853 root 1.1 sub mcp_start {
854 root 1.3 require AnyEvent::MCP::Config;
855 root 1.1
856     my %kv = (%config, @_);
857 root 1.3 my $name = AnyEvent::MCP::Config::server_name ($kv{name});
858 root 1.8 my $stdio = delete $kv{stdio};
859 root 1.17 my $listen = delete $kv{listen};
860     my $connect = delete $kv{connect};
861     !($stdio && defined $connect) or die "Cannot combine --stdio and --connect\n";
862     $stdio ||= !defined $connect && $ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio";
863 root 1.1
864     $kv{name} = $name;
865     $kv{server_info} ||= { name => $name, version => "0" };
866    
867 root 1.17 if (!$listen) {
868     return mcp_connect (%kv, connect => $connect) if defined $connect;
869     return mcp_stdio (%kv) if $stdio;
870     }
871 root 1.1
872     my $global = AnyEvent::MCP::Config->global;
873 root 1.8 my %http = $global->http_settings ($name, delete $kv{bind});
874     delete $http{tokens} if ($kv{auth} || "") eq "none";
875 root 1.17 my %options = (path => "/", %http, %kv);
876 root 1.8
877 root 1.17 if ($stdio || defined $connect) {
878     require AnyEvent::MCP::Server::HTTP;
879     my $listener = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], %options);
880     $listener->listen;
881     my $result = defined $connect
882     ? mcp_connect (%kv, connect => $connect)
883     : mcp_stdio (%kv);
884     $listener->close;
885     return $result
886     }
887    
888     mcp_http (%options)
889     }
890    
891     sub mcp_connect {
892     require AnyEvent::MCP::Server::Connect;
893 root 1.1
894 root 1.17 my $server = AnyEvent::MCP::Server::Connect->new (toolsets => [$toolset], %config, @_);
895     $server->start;
896     _run ($server, 1)
897 root 1.1 }
898    
899     sub mcp_stdio {
900     require AnyEvent::MCP::Server::JSONL;
901    
902 root 1.11 my %kv = (%config, @_);
903     my $idle_exit = delete $kv{idle_exit};
904 root 1.1 binmode STDIN;
905     binmode STDOUT;
906     my $selected = select STDOUT;
907     $| = 1;
908     select $selected;
909    
910 root 1.11 my $server = AnyEvent::MCP::Server::JSONL->new (toolsets => [$toolset], %kv);
911 root 1.1 $server->accept (\*STDIN, \*STDOUT);
912 root 1.11
913     if ($idle_exit) {
914     $idle_exit =~ /\A[0-9]+(?:\.[0-9]+)?\z/ && $idle_exit > 0
915     or die "invalid idle_exit seconds\n";
916     if ($ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio") {
917     my ($timer, $check);
918     $check = sub {
919     return if $server->{idle_exit_requested};
920     if (grep $_->{mcp_active}, values %{$server->{connections} || {}}) {
921     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
922     } else {
923     $server->request_idle_exit;
924     }
925     };
926     my $schedule = sub {
927     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
928     };
929     my $on_request = $server->{on_request};
930     $server->{on_request} = sub {
931     my ($server, $req) = @_;
932 root 1.13
933 root 1.11 if ($req->{method} ne "anyevent-mcp/shutdown") {
934     $server->{idle_exit_requested} = 0;
935     $schedule->();
936     my $reply = $req->{on_reply};
937     $req->{on_reply} = sub {
938     $reply->(@_);
939     $schedule->() unless $server->{_idle_shutting_down};
940     };
941     }
942     $on_request->(@_) if $on_request;
943     };
944     my $on_close = $server->{on_close};
945     $server->{on_close} = sub {
946     undef $timer;
947     $on_close->(@_) if $on_close;
948     };
949     $schedule->();
950     }
951     }
952 root 1.1 _run $server
953     }
954    
955     sub mcp_http {
956     require AnyEvent::MCP::Server::HTTP;
957    
958     my $server = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], %config, @_);
959     $server->listen;
960     _run $server
961     }
962    
963     sub _run {
964 root 1.17 my ($server, $keep_alive) = @_;
965 root 1.1
966     my $cv = AnyEvent->condvar;
967     my $stopped;
968     my $stop = sub { $cv->send unless $stopped++ };
969     my $on_close = $server->{on_close};
970     $server->{on_close} = sub {
971     $on_close->(@_) if $on_close;
972     $stop->();
973     };
974    
975 root 1.17 if (!$keep_alive && $server->isa ("AnyEvent::MCP::Server::JSONL")) {
976 root 1.1 my $on_connection_close = $server->{on_connection_close};
977     $server->{on_connection_close} = sub {
978     $on_connection_close->(@_) if $on_connection_close;
979     $stop->();
980     };
981     }
982    
983     my @signals = map {
984     AnyEvent->signal (signal => $_, cb => sub { $server->close })
985     } qw(INT TERM);
986    
987     my $ok = eval {
988     $server->event (start =>);
989     $cv->recv;
990     1
991     };
992     my $error = $@;
993     $server->close;
994     die $error unless $ok;
995     return
996     }
997    
998     1