ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Simple.pm
Revision: 1.25
Committed: Mon Oct 19 16:57:53 2026 UTC (3 weeks, 1 day ago) by root
Branch: MAIN
Changes since 1.24: +4 -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 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.21 =item short_instructions => $text
526    
527     Optional condensed guidance for finding this server's tools. Used as the
528     advertised MCP instructions when C<auto_instructions> is enabled.
529    
530     =item auto_instructions => $boolean
531    
532     Enabled by default. Exposes full instructions through the generated
533     C<meta-instructions> tool while advertising the short instructions if set.
534 root 1.22 When short or custom instructions are provided, append a brief hint pointing
535     to the full tool description; otherwise advertise full instructions as before.
536 root 1.21 Disable to advertise full instructions and omit the generated tool.
537    
538     =item custom_instructions => $text
539    
540     Instance-specific purpose prepended to advertised instructions. Can also be
541     set through C<--custom-instructions> or C<custom_instructions> in the program
542     or instance section of the global INI configuration.
543    
544 root 1.15 =item on_request => $callback->($server, $req)
545    
546     Synchronous hook before each dispatch attempt. For example, refresh
547     database state here. This does not replace dispatch or receive a
548     completion callback, and permission retries can invoke it again.
549    
550     =item on_start => $callback->($server)
551    
552     Called after the transport is ready, before entering the event loop.
553     Use it for startup information or installing watchers. Stdout carries
554     protocol messages in stdio mode; print diagnostics to stderr.
555 root 1.1 The callback must not block.
556    
557 root 1.15 =item idle_exit => $seconds
558    
559     Optional positive idle time for spawned stdio servers. With a multibroker
560     advertising C<anyevent-mcp/multibroker> version 1, Simple requests shutdown
561     once no MCP requests have been active for that long. The broker can ignore
562     the request. HTTP servers, ordinary clients and servers without this
563     option are unaffected.
564    
565     =back
566    
567     See L<AnyEvent::MCP::Server> for further protocol options and events.
568 root 1.11
569 root 1.1 =item mcp_tool $name, $description, $input_schema, $callback->($req, $args)
570    
571     Registers a tool named C<$name> with C<$description> and the JSON Schema
572     hash reference C<$input_schema>. C<$description> is normalized like
573     C<instructions>.
574    
575     C<$callback> receives an L<AnyEvent::MCP::Server::Request> object C<$req>
576     and its C<< $req->{args} >> hash reference as C<$args>, for convenience.
577    
578     Tools complete the request with C<< $req->reply ($result) >>, or use
579     C<< $req->result ($data) >> and C<< $req->error ($message) >> for convenience.
580     Request context is available directly in
581     the hash, for example C<< $req->{tool} >> and C<< $req->{server} >>.
582    
583 root 1.8 =item %server_options = mcp_getoptions ({ %settings }, @getopt_options)
584    
585     Parse standard server options C<--name>, C<--bind>, repeatable
586 root 1.23 C<--token>, C<--stdio>, C<--custom-instructions>, repeatable
587     C<--auto-allow> and C<--auto-deny>, C<--print-url> and C<--help> alongside any
588 root 1.8 application-specific L<Getopt::Long> option specifications:
589    
590     my $verbose;
591     my %options = mcp_getoptions (
592 root 1.9 {
593     usage => "Usage: $0 [OPTION]... WORKDIR\n",
594     description => "Provide example tools through MCP.",
595     option_heading => "Example options:",
596     options => [
597     ["--verbose", "Show additional diagnostics"],
598     ],
599     examples => " $0 --stdio ./project",
600     path => "/",
601     },
602 root 1.8 "verbose!" => \$verbose,
603     );
604     @ARGV == 1 or die "workdir required\n";
605     mcp_start %options;
606    
607 root 1.15 The optional leading hash accepts these help settings:
608    
609     =over
610    
611     =item usage => $text
612    
613     Usage synopsis, including its trailing newline. A default synopsis is
614     generated when omitted.
615    
616     =item description => $text
617    
618     Introductory help paragraph.
619    
620     =item options => [[$label, $description]...]
621    
622     Application option labels and descriptions. A plain string among the
623     pairs starts a new section; a string at the beginning replaces the
624     default section heading.
625    
626     =item option_heading => $text
627    
628     Initial section heading, defaulting to C<Application options:>.
629    
630     =item examples => $text
631    
632     Preformatted examples.
633    
634     =back
635    
636 root 1.25 This section is the canonical reference for common server options.
637     Executable manpages document only their additional or differing behavior.
638    
639 root 1.12 The common MCP options are documented automatically and their descriptions
640     are shared by all callers. Application option descriptions are explicit;
641     a C<Getopt::Long> specification does not contain enough information to
642     write one.
643 root 1.9
644     Other settings in that hash, such as C<path>, C<auth> or C<url_scheme>,
645     are passed through to C<mcp_start>. Without C<usage>, a default synopsis
646     is generated. Standard C<--help> prints the expanded help and exits;
647     a parsing error dies with the shorter usage synopsis.
648     C<--print-url> resolves the same name, bind, tokens and path as
649 root 1.8 startup, prints the first accepted token in the URL and exits B<without>
650     starting a listener. It requires a persistently stored master secret for
651     derived credentials and cannot predict a port requested as zero.
652    
653     All other option specifications and remaining C<@ARGV> follow the caller's
654     normal Getopt::Long configuration, including C<require_order> if selected.
655 root 1.19 C<--stdio> is understood by C<mcp_start> and skips HTTP configuration.
656 root 1.23 C<--auto-allow TOOL[,ACTION[,CAPABILITY]]> and C<--auto-deny> with the same
657     argument each add one process-local rule, in command-line order. Omitted
658     fields and C<*> match any value. Named capabilities limit allowed authority
659     or trigger a denial; see L<AnyEvent::MCP::Server::Permissions/auto_rules>.
660     These rules are checked before saved rules. The startup functions apply them to the configured
661 root 1.20 L<AnyEvent::MCP::Server::Permissions> object, creating an in-memory one if
662 root 1.23 necessary. Only tools that request permission are affected; tools without
663     permission checks are not blocked. In a multibroker they apply to
664     broker-visible tool names, not to independent permission checks performed
665     by downstream servers.
666 root 1.19 C<--connect URL> connects to a multibroker at
667 root 1.17 C<http://host:port/token/alias> instead of serving stdio or starting an HTTP
668     listener by default. It overrides implicit stdio from the environment;
669     explicit C<--stdio> and C<--connect> cannot be combined.
670 root 1.8 The helper deliberately does not enforce application-specific positional
671     arguments; C<--print-url> should not have to supply a workdir just to
672     print an address. It is a parser, not a second command language.
673    
674 root 1.1 =item mcp_start %config
675    
676 root 1.15 Starts and runs the usual server. When C<stdio> is true or
677 root 1.1 C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL> is C<stdio>, uses C<mcp_stdio>.
678 root 1.4 Otherwise starts HTTP without printing its URL. HTTP-capable executables
679     provide C<--print-url> to retrieve the URL explicitly.
680 root 1.1
681 root 1.3 The HTTP defaults are IPv4 loopback, a stable name-derived port in
682     16384..32767, and C</> as the endpoint path.
683 root 1.7 Without explicit C<tokens>, one server-specific token is derived from
684     the private global secret. The selected token authenticates either its URL
685     path or an
686 root 1.5 C<Authorization: Bearer TOKEN> header at the clean URL returned by
687 root 1.6 C<< $server->url (0) >>. The corresponding executable's C<--print-url>
688     option prints the first accepted token in its URL.
689 root 1.1
690 root 1.10 C<name> selects the C<[instance NAME]> configuration section and credential.
691 root 1.16 It defaults to C<default>.
692 root 1.5 Named settings C<bind>, C<path>, C<url_host>, C<url_scheme> and C<tokens>
693 root 1.7 are loaded from the global configuration. Explicit C<tokens> (an ordered,
694     nonempty array reference of URL-safe strings) replaces the configured list
695     and the derived default. Every token works in the URL and bearer header.
696 root 1.3 An explicit C<bind> overrides the configured bind address. Other arguments
697 root 1.7 override these settings.
698 root 1.1
699     The global secret is generated automatically when needed; see
700     L<AnyEvent::MCP/GLOBAL CONFIGURATION>. No configuration is loaded for stdio.
701 root 1.4 HTTP startup neither prints to stdout nor changes its buffering.
702 root 1.7 C<mcp_start> does not parse C<@ARGV>; applications parse their own options.
703 root 1.8 The bundled executables use C<mcp_getoptions>, which handles shared
704     options and URL discovery before starting the event loop.
705 root 1.1
706 root 1.17 =item mcp_connect %config
707    
708     Run a reverse-connecting JSONL server using C<connect =E<gt>
709     "http://host:port/token/alias">. It sends the authenticated extension hello
710     then serves ordinary MCP over the socket. Retry every second for up to sixty
711     attempts after a disconnect; calls are cancelled rather than replayed.
712     This connection has no spawned-child lifecycle authority.
713    
714 root 1.1 =item mcp_stdio %config
715    
716     Creates and runs a server over stdin/stdout. Arguments override the collected
717     configuration. Stdout buffering is disabled. On stdin EOF, pending replies
718     finish and stdout drains before
719     this function returns. Stream errors close the connection and cancel pending
720     requests.
721    
722     =item mcp_http %config
723    
724     Creates and runs an HTTP server. For example:
725    
726     mcp_http
727     host => "127.0.0.1",
728     port => 31740,
729 root 1.7 path => "/mcp/",
730     tokens => ["your-long-random-secret"];
731 root 1.1
732     Arguments override the collected configuration. Unlike C<mcp_start>, this
733 root 1.8 function does not derive or supply authentication. B<Always> pass C<tokens>,
734     or explicitly pass C<auth =E<gt> "none"> when a trusted embedding layer
735     enforces access. Omitting both is an error, not an unauthenticated default.
736     It does not print a URL; use
737 root 1.7 C<on_start> for application-specific startup output. Neither function
738     implements OAuth or Basic authentication. See
739     L<AnyEvent::MCP::Server::HTTP/AUTHENTICATION> for details.
740 root 1.1
741     All three starters return when the server is closed or on C<SIGINT> or
742     C<SIGTERM>. Signal shutdown closes connections and cancels outstanding
743     requests. The signal watchers belong to the blocking call.
744    
745     =back
746    
747     =cut
748    
749     package AnyEvent::MCP::Server::Simple;
750    
751     use common::sense;
752     use AnyEvent ();
753     use AnyEvent::MCP ();
754     use AnyEvent::MCP::Server::Toolset ();
755     use Exporter ();
756    
757     our @ISA = qw(Exporter);
758 root 1.17 our @EXPORT = qw(mcp_config mcp_tool mcp_getoptions mcp_start mcp_stdio mcp_http mcp_connect);
759 root 1.1
760 root 1.21 my %config = (auto_instructions => 1);
761 root 1.1 my $toolset = AnyEvent::MCP::Server::Toolset->new;
762    
763     sub _run;
764    
765     sub mcp_config {
766     my %kv = @_;
767    
768     $kv{instructions} = AnyEvent::MCP::_clean_text $kv{instructions}
769     if exists $kv{instructions};
770    
771 root 1.14 %config = (%config, %kv);
772 root 1.1 }
773    
774     sub mcp_tool {
775     $toolset->tool (@_)
776     }
777    
778 root 1.9 sub _mcp_help_row {
779     my ($option, $description) = @_;
780 root 1.13
781 root 1.9 my $start = " $option";
782     my $margin = " " x 34;
783 root 1.13 my $out = length $start < 34 ? "" : "$start\n";
784     my $line = length $start < 34 ? $start . (substr $margin, length $start) : $margin;
785 root 1.9
786     for my $word (split /\s+/, $description) {
787 root 1.13 if (length $line > 34 && (length $line) + 1 + length $word > 79) {
788 root 1.9 $out .= "$line\n";
789     $line = $margin;
790     }
791 root 1.13
792     $line .= " " if length $line > 34;
793 root 1.9 $line .= $word;
794     }
795    
796     "$out$line\n"
797     }
798    
799     sub _mcp_help {
800 root 1.19 my ($usage, $description, $options, $option_heading, $examples) = @_;
801 root 1.13
802 root 1.9 $usage =~ s/\n*\z/\n/;
803     my $help = $usage;
804     $help .= "\n$description\n" if defined $description && length $description;
805    
806     $help .= "\nCommon options:\n";
807     my @common = (
808 root 1.25 ["--name NAME", "Select named configuration (instance defaults to 'default')"],
809 root 1.9 ["--bind ADDRESS[:PORT]", "Override the HTTP listen address or port"],
810     ["--token TOKEN", "Set an accepted token (repeatable; replaces defaults)"],
811     ["--stdio", "Serve MCP on stdin/stdout; other listeners may remain"],
812 root 1.17 ["--connect URL", "Connect to a multibroker at http://host:port/token/alias"],
813 root 1.21 ["--custom-instructions TEXT", "Describe when this server instance should be used"],
814 root 1.23 ["--auto-allow TOOL[,ACTION[,CAPABILITY]]", "Add an ordered automatic allow rule (repeatable)"],
815     ["--auto-deny TOOL[,ACTION[,CAPABILITY]]", "Add an ordered automatic deny rule (repeatable)"],
816 root 1.9 ["--print-url", "Print authenticated HTTP URL and exit without listening"],
817     ["-h, --help", "Display this help and exit"],
818     );
819 root 1.19 $help .= _mcp_help_row (@$_) for @common;
820 root 1.9
821     if ($options && @$options) {
822 root 1.12 $help .= "\n" . ($option_heading // "Application options:") . "\n"
823     if ref $options->[0];
824     for my $option (@$options) {
825     if (ref $option) {
826     $help .= _mcp_help_row (@$option);
827     } else {
828     $help .= "\n" unless $help =~ /\n\n\z/;
829     $help .= "$option\n";
830     }
831     }
832 root 1.9 }
833     $help .= "\nExamples:\n$examples\n"
834     if defined $examples && length $examples;
835    
836     $help
837     }
838    
839 root 1.8 sub mcp_getoptions {
840     require Getopt::Long;
841     require AnyEvent::MCP::Config;
842    
843 root 1.13 my %settings = %{ref $_[0] eq "HASH" ? shift : {}};
844 root 1.8 my $usage = delete $settings{usage}
845 root 1.9 // "Usage: $0 [OPTION]... [ARG]...\n";
846 root 1.13 my @help = delete @settings{qw(description options option_heading examples)};
847 root 1.8 my %url = (%config, %settings);
848     delete @url{qw(name bind stdio)};
849 root 1.23 my @auto_rules;
850 root 1.8
851     Getopt::Long::GetOptions (
852 root 1.13 AnyEvent::MCP::Config::http_getopt (\my $name, \my $bind, \my $print_url, \my @tokens),
853     "stdio" => \my $stdio,
854 root 1.17 "connect=s" => \my $connect,
855 root 1.21 "custom-instructions=s" => \my $custom_instructions,
856 root 1.23 "auto-allow=s" => sub { push @auto_rules, ["allow", $_[1]] },
857     "auto-deny=s" => sub { push @auto_rules, ["deny", $_[1]] },
858 root 1.13 "help|h" => \my $help,
859 root 1.8 @_,
860     ) or die $usage;
861    
862     if ($help) {
863 root 1.19 print _mcp_help $usage, @help;
864 root 1.8 exit;
865     }
866    
867     $name = AnyEvent::MCP::Config::server_name ($name // $settings{name} // $config{name});
868     my %server = (
869     %settings,
870     name => $name,
871     (defined $bind ? (bind => $bind) : ()),
872     (@tokens ? (tokens => \@tokens) : ()),
873     ($stdio ? (stdio => 1) : ()),
874 root 1.17 (defined $connect ? (connect => $connect) : ()),
875 root 1.21 (defined $custom_instructions ? (custom_instructions => $custom_instructions) : ()),
876 root 1.23 (@auto_rules ? (auto_rules => \@auto_rules) : ()),
877 root 1.8 );
878    
879     if ($print_url) {
880     my %options = (%url, (@tokens ? (tokens => \@tokens) : ()));
881     my $global = AnyEvent::MCP::Config->global;
882 root 1.16 $options{path} //= $global->{section}{"instance $name"}{path}
883     // $global->{section}{"program " . AnyEvent::MCP::Config::program_name ()}{path} // "/";
884 root 1.8 if (($options{auth} || "") eq "none") {
885     !exists $options{tokens} or die "Cannot combine auth => none with tokens\n";
886     }
887     my $selected_bind = defined $bind ? $bind : ($settings{bind} // $config{bind});
888     print $global->http_url ($name, $selected_bind, %options), "\n";
889     exit;
890     }
891    
892     %server
893     }
894    
895 root 1.20 sub _permission_options {
896     my %kv = @_;
897    
898 root 1.23 if (my $rules = delete $kv{auto_rules}) {
899 root 1.20 require AnyEvent::MCP::Server::Permissions;
900     my $permissions = $kv{permissions} ||= AnyEvent::MCP::Server::Permissions->new;
901 root 1.23 $permissions->auto_rules ($rules);
902 root 1.20 }
903    
904     %kv
905     }
906    
907 root 1.1 sub mcp_start {
908 root 1.3 require AnyEvent::MCP::Config;
909 root 1.1
910 root 1.24 my %kv = (%config, @_);
911 root 1.3 my $name = AnyEvent::MCP::Config::server_name ($kv{name});
912 root 1.21 my $config_file = AnyEvent::MCP::Config::_file ();
913     my $global = -e $config_file ? AnyEvent::MCP::Config->global : undef;
914     my %ini = $global ? (
915     %{$global->{section}{"program " . AnyEvent::MCP::Config::program_name ()} || {}},
916     %{$global->{section}{"instance $name"} || {}},
917     ) : ();
918 root 1.24 if ($global) {
919     my @rules = $global->auto_rules ($name);
920     $kv{auto_rules} = [@{$kv{auto_rules} || []}, @rules] if @rules;
921     }
922 root 1.21 $kv{custom_instructions} = $ini{custom_instructions}
923     if !exists $kv{custom_instructions} && exists $ini{custom_instructions};
924 root 1.24 %kv = _permission_options (%kv);
925 root 1.8 my $stdio = delete $kv{stdio};
926 root 1.17 my $listen = delete $kv{listen};
927     my $connect = delete $kv{connect};
928     !($stdio && defined $connect) or die "Cannot combine --stdio and --connect\n";
929     $stdio ||= !defined $connect && $ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio";
930 root 1.1
931     $kv{name} = $name;
932     $kv{server_info} ||= { name => $name, version => "0" };
933    
934 root 1.17 if (!$listen) {
935     return mcp_connect (%kv, connect => $connect) if defined $connect;
936     return mcp_stdio (%kv) if $stdio;
937     }
938 root 1.1
939 root 1.21 my %http = ($global ||= AnyEvent::MCP::Config->global)->http_settings ($name, delete $kv{bind});
940 root 1.8 delete $http{tokens} if ($kv{auth} || "") eq "none";
941 root 1.17 my %options = (path => "/", %http, %kv);
942 root 1.8
943 root 1.17 if ($stdio || defined $connect) {
944     require AnyEvent::MCP::Server::HTTP;
945     my $listener = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], %options);
946     $listener->listen;
947     my $result = defined $connect
948     ? mcp_connect (%kv, connect => $connect)
949     : mcp_stdio (%kv);
950     $listener->close;
951     return $result
952     }
953    
954     mcp_http (%options)
955     }
956    
957     sub mcp_connect {
958     require AnyEvent::MCP::Server::Connect;
959 root 1.1
960 root 1.20 my $server = AnyEvent::MCP::Server::Connect->new (toolsets => [$toolset], _permission_options (%config, @_));
961 root 1.17 $server->start;
962     _run ($server, 1)
963 root 1.1 }
964    
965     sub mcp_stdio {
966     require AnyEvent::MCP::Server::JSONL;
967    
968 root 1.20 my %kv = _permission_options (%config, @_);
969 root 1.11 my $idle_exit = delete $kv{idle_exit};
970 root 1.1 binmode STDIN;
971     binmode STDOUT;
972     my $selected = select STDOUT;
973     $| = 1;
974     select $selected;
975    
976 root 1.11 my $server = AnyEvent::MCP::Server::JSONL->new (toolsets => [$toolset], %kv);
977 root 1.1 $server->accept (\*STDIN, \*STDOUT);
978 root 1.11
979     if ($idle_exit) {
980     $idle_exit =~ /\A[0-9]+(?:\.[0-9]+)?\z/ && $idle_exit > 0
981     or die "invalid idle_exit seconds\n";
982     if ($ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio") {
983     my ($timer, $check);
984     $check = sub {
985     return if $server->{idle_exit_requested};
986     if (grep $_->{mcp_active}, values %{$server->{connections} || {}}) {
987     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
988     } else {
989     $server->request_idle_exit;
990     }
991     };
992     my $schedule = sub {
993     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
994     };
995     my $on_request = $server->{on_request};
996     $server->{on_request} = sub {
997     my ($server, $req) = @_;
998 root 1.13
999 root 1.11 if ($req->{method} ne "anyevent-mcp/shutdown") {
1000     $server->{idle_exit_requested} = 0;
1001     $schedule->();
1002     my $reply = $req->{on_reply};
1003     $req->{on_reply} = sub {
1004     $reply->(@_);
1005     $schedule->() unless $server->{_idle_shutting_down};
1006     };
1007     }
1008     $on_request->(@_) if $on_request;
1009     };
1010     my $on_close = $server->{on_close};
1011     $server->{on_close} = sub {
1012     undef $timer;
1013     $on_close->(@_) if $on_close;
1014     };
1015     $schedule->();
1016     }
1017     }
1018 root 1.1 _run $server
1019     }
1020    
1021     sub mcp_http {
1022     require AnyEvent::MCP::Server::HTTP;
1023    
1024 root 1.20 my $server = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], _permission_options (%config, @_));
1025 root 1.1 $server->listen;
1026     _run $server
1027     }
1028    
1029     sub _run {
1030 root 1.17 my ($server, $keep_alive) = @_;
1031 root 1.1
1032     my $cv = AnyEvent->condvar;
1033     my $stopped;
1034     my $stop = sub { $cv->send unless $stopped++ };
1035     my $on_close = $server->{on_close};
1036     $server->{on_close} = sub {
1037     $on_close->(@_) if $on_close;
1038     $stop->();
1039     };
1040    
1041 root 1.17 if (!$keep_alive && $server->isa ("AnyEvent::MCP::Server::JSONL")) {
1042 root 1.1 my $on_connection_close = $server->{on_connection_close};
1043     $server->{on_connection_close} = sub {
1044     $on_connection_close->(@_) if $on_connection_close;
1045     $stop->();
1046     };
1047     }
1048    
1049     my @signals = map {
1050     AnyEvent->signal (signal => $_, cb => sub { $server->close })
1051     } qw(INT TERM);
1052    
1053     my $ok = eval {
1054     $server->event (start =>);
1055     $cv->recv;
1056     1
1057     };
1058     my $error = $@;
1059     $server->close;
1060     die $error unless $ok;
1061     return
1062     }
1063    
1064     1