ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Simple.pm
Revision: 1.26
Committed: Mon Oct 19 17:44:27 2026 UTC (3 weeks ago) by root
Branch: MAIN
Changes since 1.25: +73 -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.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 root 1.26 C<--auto-allow> and C<--auto-deny>, C<--print-url>, C<--tool-help> 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 root 1.26 C<--tool-help> prints the registered tools with their argument types,
649     required fields, defaults and descriptions, then exits before listening.
650     Call it only after registering tools; it reflects the actual registry,
651     including conditional registration. See C<mcp_tool_help> for custom servers.
652 root 1.9 C<--print-url> resolves the same name, bind, tokens and path as
653 root 1.8 startup, prints the first accepted token in the URL and exits B<without>
654     starting a listener. It requires a persistently stored master secret for
655     derived credentials and cannot predict a port requested as zero.
656    
657     All other option specifications and remaining C<@ARGV> follow the caller's
658     normal Getopt::Long configuration, including C<require_order> if selected.
659 root 1.19 C<--stdio> is understood by C<mcp_start> and skips HTTP configuration.
660 root 1.23 C<--auto-allow TOOL[,ACTION[,CAPABILITY]]> and C<--auto-deny> with the same
661     argument each add one process-local rule, in command-line order. Omitted
662     fields and C<*> match any value. Named capabilities limit allowed authority
663     or trigger a denial; see L<AnyEvent::MCP::Server::Permissions/auto_rules>.
664     These rules are checked before saved rules. The startup functions apply them to the configured
665 root 1.20 L<AnyEvent::MCP::Server::Permissions> object, creating an in-memory one if
666 root 1.23 necessary. Only tools that request permission are affected; tools without
667     permission checks are not blocked. In a multibroker they apply to
668     broker-visible tool names, not to independent permission checks performed
669     by downstream servers.
670 root 1.19 C<--connect URL> connects to a multibroker at
671 root 1.17 C<http://host:port/token/alias> instead of serving stdio or starting an HTTP
672     listener by default. It overrides implicit stdio from the environment;
673     explicit C<--stdio> and C<--connect> cannot be combined.
674 root 1.8 The helper deliberately does not enforce application-specific positional
675     arguments; C<--print-url> should not have to supply a workdir just to
676     print an address. It is a parser, not a second command language.
677    
678 root 1.26 =item mcp_tool_help ($enabled, @toolsets)
679    
680     If C<$enabled> is true, print the definitions registered in the supplied
681     L<AnyEvent::MCP::Server::Toolset> objects and exit. Otherwise do nothing.
682     Call after registering tools but before starting listeners for executables
683     which manage their own toolsets. Only registered tools appear.
684    
685 root 1.1 =item mcp_start %config
686    
687 root 1.15 Starts and runs the usual server. When C<stdio> is true or
688 root 1.1 C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL> is C<stdio>, uses C<mcp_stdio>.
689 root 1.4 Otherwise starts HTTP without printing its URL. HTTP-capable executables
690     provide C<--print-url> to retrieve the URL explicitly.
691 root 1.1
692 root 1.3 The HTTP defaults are IPv4 loopback, a stable name-derived port in
693     16384..32767, and C</> as the endpoint path.
694 root 1.7 Without explicit C<tokens>, one server-specific token is derived from
695     the private global secret. The selected token authenticates either its URL
696     path or an
697 root 1.5 C<Authorization: Bearer TOKEN> header at the clean URL returned by
698 root 1.6 C<< $server->url (0) >>. The corresponding executable's C<--print-url>
699     option prints the first accepted token in its URL.
700 root 1.1
701 root 1.10 C<name> selects the C<[instance NAME]> configuration section and credential.
702 root 1.16 It defaults to C<default>.
703 root 1.5 Named settings C<bind>, C<path>, C<url_host>, C<url_scheme> and C<tokens>
704 root 1.7 are loaded from the global configuration. Explicit C<tokens> (an ordered,
705     nonempty array reference of URL-safe strings) replaces the configured list
706     and the derived default. Every token works in the URL and bearer header.
707 root 1.3 An explicit C<bind> overrides the configured bind address. Other arguments
708 root 1.7 override these settings.
709 root 1.1
710     The global secret is generated automatically when needed; see
711     L<AnyEvent::MCP/GLOBAL CONFIGURATION>. No configuration is loaded for stdio.
712 root 1.4 HTTP startup neither prints to stdout nor changes its buffering.
713 root 1.7 C<mcp_start> does not parse C<@ARGV>; applications parse their own options.
714 root 1.8 The bundled executables use C<mcp_getoptions>, which handles shared
715     options and URL discovery before starting the event loop.
716 root 1.1
717 root 1.17 =item mcp_connect %config
718    
719     Run a reverse-connecting JSONL server using C<connect =E<gt>
720     "http://host:port/token/alias">. It sends the authenticated extension hello
721     then serves ordinary MCP over the socket. Retry every second for up to sixty
722     attempts after a disconnect; calls are cancelled rather than replayed.
723     This connection has no spawned-child lifecycle authority.
724    
725 root 1.1 =item mcp_stdio %config
726    
727     Creates and runs a server over stdin/stdout. Arguments override the collected
728     configuration. Stdout buffering is disabled. On stdin EOF, pending replies
729     finish and stdout drains before
730     this function returns. Stream errors close the connection and cancel pending
731     requests.
732    
733     =item mcp_http %config
734    
735     Creates and runs an HTTP server. For example:
736    
737     mcp_http
738     host => "127.0.0.1",
739     port => 31740,
740 root 1.7 path => "/mcp/",
741     tokens => ["your-long-random-secret"];
742 root 1.1
743     Arguments override the collected configuration. Unlike C<mcp_start>, this
744 root 1.8 function does not derive or supply authentication. B<Always> pass C<tokens>,
745     or explicitly pass C<auth =E<gt> "none"> when a trusted embedding layer
746     enforces access. Omitting both is an error, not an unauthenticated default.
747     It does not print a URL; use
748 root 1.7 C<on_start> for application-specific startup output. Neither function
749     implements OAuth or Basic authentication. See
750     L<AnyEvent::MCP::Server::HTTP/AUTHENTICATION> for details.
751 root 1.1
752     All three starters return when the server is closed or on C<SIGINT> or
753     C<SIGTERM>. Signal shutdown closes connections and cancels outstanding
754     requests. The signal watchers belong to the blocking call.
755    
756     =back
757    
758     =cut
759    
760     package AnyEvent::MCP::Server::Simple;
761    
762     use common::sense;
763     use AnyEvent ();
764     use AnyEvent::MCP ();
765     use AnyEvent::MCP::Server::Toolset ();
766     use Exporter ();
767    
768     our @ISA = qw(Exporter);
769 root 1.26 our @EXPORT = qw(mcp_config mcp_tool mcp_getoptions mcp_tool_help mcp_start mcp_stdio mcp_http mcp_connect);
770 root 1.1
771 root 1.21 my %config = (auto_instructions => 1);
772 root 1.1 my $toolset = AnyEvent::MCP::Server::Toolset->new;
773    
774     sub _run;
775    
776     sub mcp_config {
777     my %kv = @_;
778    
779     $kv{instructions} = AnyEvent::MCP::_clean_text $kv{instructions}
780     if exists $kv{instructions};
781    
782 root 1.14 %config = (%config, %kv);
783 root 1.1 }
784    
785     sub mcp_tool {
786     $toolset->tool (@_)
787     }
788    
789 root 1.26 # format a JSON Schema type for command-line tool help.
790     sub _tool_type {
791     my ($schema) = @_;
792     return "any" unless ref $schema eq "HASH";
793     for my $key (qw(anyOf oneOf)) {
794     return join "|", map _tool_type ($_), @{$schema->{$key}}
795     if ref $schema->{$key} eq "ARRAY";
796     }
797     my $type = $schema->{type} // "any";
798     $type = join "|", @$type if ref $type eq "ARRAY";
799     $type = "array[" . _tool_type ($schema->{items}) . "]" if $type eq "array";
800     $type
801     }
802    
803     sub mcp_tool_help {
804     my ($enabled, @toolsets) = @_;
805     return unless $enabled;
806    
807     my $json = AnyEvent::MCP::json_class ()->new->canonical;
808     my @tools = map @{$_->list}, @toolsets;
809     for my $tool (@tools) {
810     print "$tool->{name}\n";
811     (my $description = $tool->{description} // "") =~ s/\n/\n /g;
812     print " $description\n" if length $description;
813    
814     my $schema = $tool->{inputSchema} || {};
815     my $properties = $schema->{properties} || {};
816     my %required = map { $_ => 1 } @{$schema->{required} || []};
817     for my $name (sort keys %$properties) {
818     my $property = $properties->{$name};
819     my $type = _tool_type ($property);
820     my $status = $required{$name} ? "required" : "optional";
821     my @extras;
822     push @extras, "default: " . $json->encode ($property->{default})
823     if exists $property->{default};
824     push @extras, "values: " . join (", ", @{$property->{enum}})
825     if ref $property->{enum} eq "ARRAY";
826     print " $name $type $status", (@extras ? " (" . join ("; ", @extras) . ")" : ""), "\n";
827     if (length ($property->{description} // "")) {
828     (my $detail = $property->{description}) =~ s/\n/\n /g;
829     print " $detail\n";
830     }
831     }
832     print "\n";
833     }
834     print "No tools registered.\n" unless @tools;
835     exit;
836     }
837    
838 root 1.9 sub _mcp_help_row {
839     my ($option, $description) = @_;
840 root 1.13
841 root 1.9 my $start = " $option";
842     my $margin = " " x 34;
843 root 1.13 my $out = length $start < 34 ? "" : "$start\n";
844     my $line = length $start < 34 ? $start . (substr $margin, length $start) : $margin;
845 root 1.9
846     for my $word (split /\s+/, $description) {
847 root 1.13 if (length $line > 34 && (length $line) + 1 + length $word > 79) {
848 root 1.9 $out .= "$line\n";
849     $line = $margin;
850     }
851 root 1.13
852     $line .= " " if length $line > 34;
853 root 1.9 $line .= $word;
854     }
855    
856     "$out$line\n"
857     }
858    
859     sub _mcp_help {
860 root 1.19 my ($usage, $description, $options, $option_heading, $examples) = @_;
861 root 1.13
862 root 1.9 $usage =~ s/\n*\z/\n/;
863     my $help = $usage;
864     $help .= "\n$description\n" if defined $description && length $description;
865    
866     $help .= "\nCommon options:\n";
867     my @common = (
868 root 1.25 ["--name NAME", "Select named configuration (instance defaults to 'default')"],
869 root 1.9 ["--bind ADDRESS[:PORT]", "Override the HTTP listen address or port"],
870     ["--token TOKEN", "Set an accepted token (repeatable; replaces defaults)"],
871     ["--stdio", "Serve MCP on stdin/stdout; other listeners may remain"],
872 root 1.17 ["--connect URL", "Connect to a multibroker at http://host:port/token/alias"],
873 root 1.21 ["--custom-instructions TEXT", "Describe when this server instance should be used"],
874 root 1.23 ["--auto-allow TOOL[,ACTION[,CAPABILITY]]", "Add an ordered automatic allow rule (repeatable)"],
875     ["--auto-deny TOOL[,ACTION[,CAPABILITY]]", "Add an ordered automatic deny rule (repeatable)"],
876 root 1.9 ["--print-url", "Print authenticated HTTP URL and exit without listening"],
877 root 1.26 ["--tool-help", "Print registered tool descriptions and arguments, then exit"],
878 root 1.9 ["-h, --help", "Display this help and exit"],
879     );
880 root 1.19 $help .= _mcp_help_row (@$_) for @common;
881 root 1.9
882     if ($options && @$options) {
883 root 1.12 $help .= "\n" . ($option_heading // "Application options:") . "\n"
884     if ref $options->[0];
885     for my $option (@$options) {
886     if (ref $option) {
887     $help .= _mcp_help_row (@$option);
888     } else {
889     $help .= "\n" unless $help =~ /\n\n\z/;
890     $help .= "$option\n";
891     }
892     }
893 root 1.9 }
894     $help .= "\nExamples:\n$examples\n"
895     if defined $examples && length $examples;
896    
897     $help
898     }
899    
900 root 1.8 sub mcp_getoptions {
901     require Getopt::Long;
902     require AnyEvent::MCP::Config;
903    
904 root 1.13 my %settings = %{ref $_[0] eq "HASH" ? shift : {}};
905 root 1.8 my $usage = delete $settings{usage}
906 root 1.9 // "Usage: $0 [OPTION]... [ARG]...\n";
907 root 1.13 my @help = delete @settings{qw(description options option_heading examples)};
908 root 1.8 my %url = (%config, %settings);
909     delete @url{qw(name bind stdio)};
910 root 1.23 my @auto_rules;
911 root 1.8
912     Getopt::Long::GetOptions (
913 root 1.13 AnyEvent::MCP::Config::http_getopt (\my $name, \my $bind, \my $print_url, \my @tokens),
914     "stdio" => \my $stdio,
915 root 1.17 "connect=s" => \my $connect,
916 root 1.21 "custom-instructions=s" => \my $custom_instructions,
917 root 1.23 "auto-allow=s" => sub { push @auto_rules, ["allow", $_[1]] },
918     "auto-deny=s" => sub { push @auto_rules, ["deny", $_[1]] },
919 root 1.26 "tool-help" => \my $tool_help,
920 root 1.13 "help|h" => \my $help,
921 root 1.8 @_,
922     ) or die $usage;
923    
924     if ($help) {
925 root 1.19 print _mcp_help $usage, @help;
926 root 1.8 exit;
927     }
928    
929     $name = AnyEvent::MCP::Config::server_name ($name // $settings{name} // $config{name});
930     my %server = (
931     %settings,
932     name => $name,
933     (defined $bind ? (bind => $bind) : ()),
934     (@tokens ? (tokens => \@tokens) : ()),
935     ($stdio ? (stdio => 1) : ()),
936 root 1.17 (defined $connect ? (connect => $connect) : ()),
937 root 1.21 (defined $custom_instructions ? (custom_instructions => $custom_instructions) : ()),
938 root 1.23 (@auto_rules ? (auto_rules => \@auto_rules) : ()),
939 root 1.26 ($tool_help ? (tool_help => 1) : ()),
940 root 1.8 );
941    
942     if ($print_url) {
943     my %options = (%url, (@tokens ? (tokens => \@tokens) : ()));
944     my $global = AnyEvent::MCP::Config->global;
945 root 1.16 $options{path} //= $global->{section}{"instance $name"}{path}
946     // $global->{section}{"program " . AnyEvent::MCP::Config::program_name ()}{path} // "/";
947 root 1.8 if (($options{auth} || "") eq "none") {
948     !exists $options{tokens} or die "Cannot combine auth => none with tokens\n";
949     }
950     my $selected_bind = defined $bind ? $bind : ($settings{bind} // $config{bind});
951     print $global->http_url ($name, $selected_bind, %options), "\n";
952     exit;
953     }
954    
955     %server
956     }
957    
958 root 1.20 sub _permission_options {
959     my %kv = @_;
960    
961 root 1.23 if (my $rules = delete $kv{auto_rules}) {
962 root 1.20 require AnyEvent::MCP::Server::Permissions;
963     my $permissions = $kv{permissions} ||= AnyEvent::MCP::Server::Permissions->new;
964 root 1.23 $permissions->auto_rules ($rules);
965 root 1.20 }
966    
967     %kv
968     }
969    
970 root 1.1 sub mcp_start {
971 root 1.3 require AnyEvent::MCP::Config;
972 root 1.1
973 root 1.24 my %kv = (%config, @_);
974 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
975 root 1.3 my $name = AnyEvent::MCP::Config::server_name ($kv{name});
976 root 1.21 my $config_file = AnyEvent::MCP::Config::_file ();
977     my $global = -e $config_file ? AnyEvent::MCP::Config->global : undef;
978     my %ini = $global ? (
979     %{$global->{section}{"program " . AnyEvent::MCP::Config::program_name ()} || {}},
980     %{$global->{section}{"instance $name"} || {}},
981     ) : ();
982 root 1.24 if ($global) {
983     my @rules = $global->auto_rules ($name);
984     $kv{auto_rules} = [@{$kv{auto_rules} || []}, @rules] if @rules;
985     }
986 root 1.21 $kv{custom_instructions} = $ini{custom_instructions}
987     if !exists $kv{custom_instructions} && exists $ini{custom_instructions};
988 root 1.24 %kv = _permission_options (%kv);
989 root 1.8 my $stdio = delete $kv{stdio};
990 root 1.17 my $listen = delete $kv{listen};
991     my $connect = delete $kv{connect};
992     !($stdio && defined $connect) or die "Cannot combine --stdio and --connect\n";
993     $stdio ||= !defined $connect && $ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio";
994 root 1.1
995     $kv{name} = $name;
996     $kv{server_info} ||= { name => $name, version => "0" };
997    
998 root 1.17 if (!$listen) {
999     return mcp_connect (%kv, connect => $connect) if defined $connect;
1000     return mcp_stdio (%kv) if $stdio;
1001     }
1002 root 1.1
1003 root 1.21 my %http = ($global ||= AnyEvent::MCP::Config->global)->http_settings ($name, delete $kv{bind});
1004 root 1.8 delete $http{tokens} if ($kv{auth} || "") eq "none";
1005 root 1.17 my %options = (path => "/", %http, %kv);
1006 root 1.8
1007 root 1.17 if ($stdio || defined $connect) {
1008     require AnyEvent::MCP::Server::HTTP;
1009     my $listener = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], %options);
1010     $listener->listen;
1011     my $result = defined $connect
1012     ? mcp_connect (%kv, connect => $connect)
1013     : mcp_stdio (%kv);
1014     $listener->close;
1015     return $result
1016     }
1017    
1018     mcp_http (%options)
1019     }
1020    
1021     sub mcp_connect {
1022     require AnyEvent::MCP::Server::Connect;
1023 root 1.1
1024 root 1.26 my %kv = (%config, @_);
1025     mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1026     my $server = AnyEvent::MCP::Server::Connect->new (toolsets => [$toolset], _permission_options (%kv));
1027 root 1.17 $server->start;
1028     _run ($server, 1)
1029 root 1.1 }
1030    
1031     sub mcp_stdio {
1032     require AnyEvent::MCP::Server::JSONL;
1033    
1034 root 1.20 my %kv = _permission_options (%config, @_);
1035 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1036 root 1.11 my $idle_exit = delete $kv{idle_exit};
1037 root 1.1 binmode STDIN;
1038     binmode STDOUT;
1039     my $selected = select STDOUT;
1040     $| = 1;
1041     select $selected;
1042    
1043 root 1.11 my $server = AnyEvent::MCP::Server::JSONL->new (toolsets => [$toolset], %kv);
1044 root 1.1 $server->accept (\*STDIN, \*STDOUT);
1045 root 1.11
1046     if ($idle_exit) {
1047     $idle_exit =~ /\A[0-9]+(?:\.[0-9]+)?\z/ && $idle_exit > 0
1048     or die "invalid idle_exit seconds\n";
1049     if ($ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio") {
1050     my ($timer, $check);
1051     $check = sub {
1052     return if $server->{idle_exit_requested};
1053     if (grep $_->{mcp_active}, values %{$server->{connections} || {}}) {
1054     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
1055     } else {
1056     $server->request_idle_exit;
1057     }
1058     };
1059     my $schedule = sub {
1060     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
1061     };
1062     my $on_request = $server->{on_request};
1063     $server->{on_request} = sub {
1064     my ($server, $req) = @_;
1065 root 1.13
1066 root 1.11 if ($req->{method} ne "anyevent-mcp/shutdown") {
1067     $server->{idle_exit_requested} = 0;
1068     $schedule->();
1069     my $reply = $req->{on_reply};
1070     $req->{on_reply} = sub {
1071     $reply->(@_);
1072     $schedule->() unless $server->{_idle_shutting_down};
1073     };
1074     }
1075     $on_request->(@_) if $on_request;
1076     };
1077     my $on_close = $server->{on_close};
1078     $server->{on_close} = sub {
1079     undef $timer;
1080     $on_close->(@_) if $on_close;
1081     };
1082     $schedule->();
1083     }
1084     }
1085 root 1.1 _run $server
1086     }
1087    
1088     sub mcp_http {
1089     require AnyEvent::MCP::Server::HTTP;
1090    
1091 root 1.26 my %kv = _permission_options (%config, @_);
1092     mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1093     my $server = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], %kv);
1094 root 1.1 $server->listen;
1095     _run $server
1096     }
1097    
1098     sub _run {
1099 root 1.17 my ($server, $keep_alive) = @_;
1100 root 1.1
1101     my $cv = AnyEvent->condvar;
1102     my $stopped;
1103     my $stop = sub { $cv->send unless $stopped++ };
1104     my $on_close = $server->{on_close};
1105     $server->{on_close} = sub {
1106     $on_close->(@_) if $on_close;
1107     $stop->();
1108     };
1109    
1110 root 1.17 if (!$keep_alive && $server->isa ("AnyEvent::MCP::Server::JSONL")) {
1111 root 1.1 my $on_connection_close = $server->{on_connection_close};
1112     $server->{on_connection_close} = sub {
1113     $on_connection_close->(@_) if $on_connection_close;
1114     $stop->();
1115     };
1116     }
1117    
1118     my @signals = map {
1119     AnyEvent->signal (signal => $_, cb => sub { $server->close })
1120     } qw(INT TERM);
1121    
1122     my $ok = eval {
1123     $server->event (start =>);
1124     $cv->recv;
1125     1
1126     };
1127     my $error = $@;
1128     $server->close;
1129     die $error unless $ok;
1130     return
1131     }
1132    
1133     1