ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Simple.pm
Revision: 1.16
Committed: Wed Oct 14 14:16:04 2026 UTC (3 weeks, 6 days ago) by root
Branch: MAIN
Changes since 1.15: +5 -4 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     C<--stdio> is understood by C<mcp_start> and skips HTTP configuration.
633     The helper deliberately does not enforce application-specific positional
634     arguments; C<--print-url> should not have to supply a workdir just to
635     print an address. It is a parser, not a second command language.
636    
637 root 1.1 =item mcp_start %config
638    
639 root 1.15 Starts and runs the usual server. When C<stdio> is true or
640 root 1.1 C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL> is C<stdio>, uses C<mcp_stdio>.
641 root 1.4 Otherwise starts HTTP without printing its URL. HTTP-capable executables
642     provide C<--print-url> to retrieve the URL explicitly.
643 root 1.1
644 root 1.3 The HTTP defaults are IPv4 loopback, a stable name-derived port in
645     16384..32767, and C</> as the endpoint path.
646 root 1.7 Without explicit C<tokens>, one server-specific token is derived from
647     the private global secret. The selected token authenticates either its URL
648     path or an
649 root 1.5 C<Authorization: Bearer TOKEN> header at the clean URL returned by
650 root 1.6 C<< $server->url (0) >>. The corresponding executable's C<--print-url>
651     option prints the first accepted token in its URL.
652 root 1.1
653 root 1.10 C<name> selects the C<[instance NAME]> configuration section and credential.
654 root 1.16 It defaults to C<default>.
655 root 1.5 Named settings C<bind>, C<path>, C<url_host>, C<url_scheme> and C<tokens>
656 root 1.7 are loaded from the global configuration. Explicit C<tokens> (an ordered,
657     nonempty array reference of URL-safe strings) replaces the configured list
658     and the derived default. Every token works in the URL and bearer header.
659 root 1.3 An explicit C<bind> overrides the configured bind address. Other arguments
660 root 1.7 override these settings.
661 root 1.1
662     The global secret is generated automatically when needed; see
663     L<AnyEvent::MCP/GLOBAL CONFIGURATION>. No configuration is loaded for stdio.
664 root 1.4 HTTP startup neither prints to stdout nor changes its buffering.
665 root 1.7 C<mcp_start> does not parse C<@ARGV>; applications parse their own options.
666 root 1.8 The bundled executables use C<mcp_getoptions>, which handles shared
667     options and URL discovery before starting the event loop.
668 root 1.1
669     =item mcp_stdio %config
670    
671     Creates and runs a server over stdin/stdout. Arguments override the collected
672     configuration. Stdout buffering is disabled. On stdin EOF, pending replies
673     finish and stdout drains before
674     this function returns. Stream errors close the connection and cancel pending
675     requests.
676    
677     =item mcp_http %config
678    
679     Creates and runs an HTTP server. For example:
680    
681     mcp_http
682     host => "127.0.0.1",
683     port => 31740,
684 root 1.7 path => "/mcp/",
685     tokens => ["your-long-random-secret"];
686 root 1.1
687     Arguments override the collected configuration. Unlike C<mcp_start>, this
688 root 1.8 function does not derive or supply authentication. B<Always> pass C<tokens>,
689     or explicitly pass C<auth =E<gt> "none"> when a trusted embedding layer
690     enforces access. Omitting both is an error, not an unauthenticated default.
691     It does not print a URL; use
692 root 1.7 C<on_start> for application-specific startup output. Neither function
693     implements OAuth or Basic authentication. See
694     L<AnyEvent::MCP::Server::HTTP/AUTHENTICATION> for details.
695 root 1.1
696     All three starters return when the server is closed or on C<SIGINT> or
697     C<SIGTERM>. Signal shutdown closes connections and cancels outstanding
698     requests. The signal watchers belong to the blocking call.
699    
700     =back
701    
702     =cut
703    
704     package AnyEvent::MCP::Server::Simple;
705    
706     use common::sense;
707     use AnyEvent ();
708     use AnyEvent::MCP ();
709     use AnyEvent::MCP::Server::Toolset ();
710     use Exporter ();
711    
712     our @ISA = qw(Exporter);
713 root 1.8 our @EXPORT = qw(mcp_config mcp_tool mcp_getoptions mcp_start mcp_stdio mcp_http);
714 root 1.1
715     my %config;
716     my $toolset = AnyEvent::MCP::Server::Toolset->new;
717    
718     sub _run;
719    
720     sub mcp_config {
721     my %kv = @_;
722    
723     $kv{instructions} = AnyEvent::MCP::_clean_text $kv{instructions}
724     if exists $kv{instructions};
725    
726 root 1.14 %config = (%config, %kv);
727 root 1.1 }
728    
729     sub mcp_tool {
730     $toolset->tool (@_)
731     }
732    
733 root 1.9 sub _mcp_help_row {
734     my ($option, $description) = @_;
735 root 1.13
736 root 1.9 my $start = " $option";
737     my $margin = " " x 34;
738 root 1.13 my $out = length $start < 34 ? "" : "$start\n";
739     my $line = length $start < 34 ? $start . (substr $margin, length $start) : $margin;
740 root 1.9
741     for my $word (split /\s+/, $description) {
742 root 1.13 if (length $line > 34 && (length $line) + 1 + length $word > 79) {
743 root 1.9 $out .= "$line\n";
744     $line = $margin;
745     }
746 root 1.13
747     $line .= " " if length $line > 34;
748 root 1.9 $line .= $word;
749     }
750    
751     "$out$line\n"
752     }
753    
754     sub _mcp_help {
755     my ($usage, $description, $options, $option_heading, $examples) = @_;
756 root 1.13
757 root 1.9 $usage =~ s/\n*\z/\n/;
758     my $help = $usage;
759     $help .= "\n$description\n" if defined $description && length $description;
760    
761     $help .= "\nCommon options:\n";
762     my @common = (
763     ["--name NAME", "Select named configuration (default: executable name)"],
764     ["--bind ADDRESS[:PORT]", "Override the HTTP listen address or port"],
765     ["--token TOKEN", "Set an accepted token (repeatable; replaces defaults)"],
766     ["--stdio", "Serve MCP on stdin/stdout; other listeners may remain"],
767     ["--print-url", "Print authenticated HTTP URL and exit without listening"],
768     ["-h, --help", "Display this help and exit"],
769     );
770     $help .= _mcp_help_row (@$_) for @common;
771    
772     if ($options && @$options) {
773 root 1.12 $help .= "\n" . ($option_heading // "Application options:") . "\n"
774     if ref $options->[0];
775     for my $option (@$options) {
776     if (ref $option) {
777     $help .= _mcp_help_row (@$option);
778     } else {
779     $help .= "\n" unless $help =~ /\n\n\z/;
780     $help .= "$option\n";
781     }
782     }
783 root 1.9 }
784     $help .= "\nExamples:\n$examples\n"
785     if defined $examples && length $examples;
786    
787     $help
788     }
789    
790 root 1.8 sub mcp_getoptions {
791     require Getopt::Long;
792     require AnyEvent::MCP::Config;
793    
794 root 1.13 my %settings = %{ref $_[0] eq "HASH" ? shift : {}};
795 root 1.8 my $usage = delete $settings{usage}
796 root 1.9 // "Usage: $0 [OPTION]... [ARG]...\n";
797 root 1.13 my @help = delete @settings{qw(description options option_heading examples)};
798 root 1.8 my %url = (%config, %settings);
799     delete @url{qw(name bind stdio)};
800    
801     Getopt::Long::GetOptions (
802 root 1.13 AnyEvent::MCP::Config::http_getopt (\my $name, \my $bind, \my $print_url, \my @tokens),
803     "stdio" => \my $stdio,
804     "help|h" => \my $help,
805 root 1.8 @_,
806     ) or die $usage;
807    
808     if ($help) {
809 root 1.13 print _mcp_help $usage, @help;
810 root 1.8 exit;
811     }
812    
813     $name = AnyEvent::MCP::Config::server_name ($name // $settings{name} // $config{name});
814     my %server = (
815     %settings,
816     name => $name,
817     (defined $bind ? (bind => $bind) : ()),
818     (@tokens ? (tokens => \@tokens) : ()),
819     ($stdio ? (stdio => 1) : ()),
820     );
821    
822     if ($print_url) {
823     my %options = (%url, (@tokens ? (tokens => \@tokens) : ()));
824     my $global = AnyEvent::MCP::Config->global;
825 root 1.16 $options{path} //= $global->{section}{"instance $name"}{path}
826     // $global->{section}{"program " . AnyEvent::MCP::Config::program_name ()}{path} // "/";
827 root 1.8 if (($options{auth} || "") eq "none") {
828     !exists $options{tokens} or die "Cannot combine auth => none with tokens\n";
829     }
830     my $selected_bind = defined $bind ? $bind : ($settings{bind} // $config{bind});
831     print $global->http_url ($name, $selected_bind, %options), "\n";
832     exit;
833     }
834    
835     %server
836     }
837    
838 root 1.1 sub mcp_start {
839 root 1.3 require AnyEvent::MCP::Config;
840 root 1.1
841     my %kv = (%config, @_);
842 root 1.3 my $name = AnyEvent::MCP::Config::server_name ($kv{name});
843 root 1.8 my $stdio = delete $kv{stdio};
844 root 1.1
845     $kv{name} = $name;
846     $kv{server_info} ||= { name => $name, version => "0" };
847    
848     return mcp_stdio (%kv)
849 root 1.8 if $stdio || $ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio";
850 root 1.1
851     my $global = AnyEvent::MCP::Config->global;
852    
853 root 1.8 my %http = $global->http_settings ($name, delete $kv{bind});
854     delete $http{tokens} if ($kv{auth} || "") eq "none";
855    
856 root 1.1 %kv = (
857     path => "/",
858 root 1.8 %http,
859 root 1.1 %kv,
860     );
861    
862 root 1.4 mcp_http (%kv)
863 root 1.1 }
864    
865     sub mcp_stdio {
866     require AnyEvent::MCP::Server::JSONL;
867    
868 root 1.11 my %kv = (%config, @_);
869     my $idle_exit = delete $kv{idle_exit};
870 root 1.1 binmode STDIN;
871     binmode STDOUT;
872     my $selected = select STDOUT;
873     $| = 1;
874     select $selected;
875    
876 root 1.11 my $server = AnyEvent::MCP::Server::JSONL->new (toolsets => [$toolset], %kv);
877 root 1.1 $server->accept (\*STDIN, \*STDOUT);
878 root 1.11
879     if ($idle_exit) {
880     $idle_exit =~ /\A[0-9]+(?:\.[0-9]+)?\z/ && $idle_exit > 0
881     or die "invalid idle_exit seconds\n";
882     if ($ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio") {
883     my ($timer, $check);
884     $check = sub {
885     return if $server->{idle_exit_requested};
886     if (grep $_->{mcp_active}, values %{$server->{connections} || {}}) {
887     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
888     } else {
889     $server->request_idle_exit;
890     }
891     };
892     my $schedule = sub {
893     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
894     };
895     my $on_request = $server->{on_request};
896     $server->{on_request} = sub {
897     my ($server, $req) = @_;
898 root 1.13
899 root 1.11 if ($req->{method} ne "anyevent-mcp/shutdown") {
900     $server->{idle_exit_requested} = 0;
901     $schedule->();
902     my $reply = $req->{on_reply};
903     $req->{on_reply} = sub {
904     $reply->(@_);
905     $schedule->() unless $server->{_idle_shutting_down};
906     };
907     }
908     $on_request->(@_) if $on_request;
909     };
910     my $on_close = $server->{on_close};
911     $server->{on_close} = sub {
912     undef $timer;
913     $on_close->(@_) if $on_close;
914     };
915     $schedule->();
916     }
917     }
918 root 1.1 _run $server
919     }
920    
921     sub mcp_http {
922     require AnyEvent::MCP::Server::HTTP;
923    
924     my $server = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], %config, @_);
925     $server->listen;
926     _run $server
927     }
928    
929     sub _run {
930     my ($server) = @_;
931    
932     my $cv = AnyEvent->condvar;
933     my $stopped;
934     my $stop = sub { $cv->send unless $stopped++ };
935     my $on_close = $server->{on_close};
936     $server->{on_close} = sub {
937     $on_close->(@_) if $on_close;
938     $stop->();
939     };
940    
941     if ($server->isa ("AnyEvent::MCP::Server::JSONL")) {
942     my $on_connection_close = $server->{on_connection_close};
943     $server->{on_connection_close} = sub {
944     $on_connection_close->(@_) if $on_connection_close;
945     $stop->();
946     };
947     }
948    
949     my @signals = map {
950     AnyEvent->signal (signal => $_, cb => sub { $server->close })
951     } qw(INT TERM);
952    
953     my $ok = eval {
954     $server->event (start =>);
955     $cv->recv;
956     1
957     };
958     my $error = $@;
959     $server->close;
960     die $error unless $ok;
961     return
962     }
963    
964     1