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