ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Simple.pm
Revision: 1.57
Committed: Tue Nov 10 21:05:59 2026 UTC (35 hours, 13 minutes ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.56: +15 -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 root 1.34 server_name => "example",
11     server_version => 0,
12 root 1.1 instructions => <<'EOF';
13     Use echo to return the supplied text.
14     EOF
15    
16     mcp_tool echo =>
17     "Return the supplied text.",
18     {
19     type => "object",
20     properties => { text => { type => "string" } },
21     required => ["text"],
22     },
23     sub {
24     my ($req, $args) = @_;
25    
26     $req->reply ({
27     content => [{ type => "text", text => $args->{text} }],
28     });
29     };
30    
31     mcp_start;
32    
33     =head1 DESCRIPTION
34    
35 root 1.2 For most MCP servers, registering a few tools and starting a transport is
36     enough. This module does just that: call C<mcp_tool> to register callbacks,
37     optionally configure the server with C<mcp_config>, then start it with
38     C<mcp_start>, C<mcp_stdio> or C<mcp_http>. Registration alone does not start
39     a server or an event loop.
40 root 1.1
41     Human-readable descriptions and instructions are stripped of surrounding
42     whitespace and common indentation when registered, so indented here-documents
43     can be used naturally.
44    
45     C<mcp_start>, C<mcp_stdio> and C<mcp_http> are alternative blocking entry
46 root 1.2 points for a self-contained server. They run AnyEvent until shutdown; you
47     do not start another event loop. If your application already owns the server
48     lifetime or needs several transports, use the object interfaces instead.
49    
50     Simple uses L<AnyEvent::MCP::Server> and a default Toolset internally; it is
51     not a second server implementation. Applications needing custom dispatch,
52     such as a multibroker, can use the Server API directly.
53 root 1.1
54 root 1.15 Start with L</TUTORIAL> for a complete server and a first client call.
55     For running the bundled servers without writing Perl, see
56     L<AnyEvent::MCP::Intro>.
57    
58     =head1 TUTORIAL
59    
60     This tutorial builds a small server, calls it, then adds options, permission
61     checks and asynchronous work. You do not need to implement JSON-RPC,
62     authentication or a transport. Simple supplies those parts; your program
63     declares tools and handles their arguments.
64    
65     For installation, connecting AI clients and configuring the bundled servers,
66     see L<AnyEvent::MCP::Intro>. The function reference follows this tutorial.
67    
68     =head2 A complete first server
69    
70     Save the following as C<echo-mcp>:
71    
72     #!/opt/bin/perl
73    
74     use common::sense;
75     use AnyEvent::MCP::Server::Simple;
76    
77 root 1.33 my $options = mcp_getoptions {
78 root 1.15 name => "echo-demo",
79     usage => "Usage: $0 [OPTION]...\n",
80     description => "Return text supplied to the echo tool.",
81     };
82    
83     @ARGV == 0
84     or die "Unexpected arguments\n";
85    
86     mcp_config
87 root 1.34 server_name => "echo-demo",
88     server_version => 1,
89 root 1.15 instructions => "Use echo to return the supplied text.";
90    
91     mcp_tool echo =>
92     "Return the supplied text.",
93     {
94     type => "object",
95     properties => {
96     text => {
97     type => "string",
98     maxLength => 4096,
99     },
100     },
101     required => ["text"],
102     },
103     sub {
104     my ($req, $args) = @_;
105    
106     my $text = $args->{text};
107     defined $text and !ref $text and length $text <= 4096
108     or die "text must be a string of at most 4096 characters\n";
109    
110     $req->reply ({
111     content => [{ type => "text", text => $text }],
112     });
113     };
114    
115 root 1.33 mcp_start %$options;
116 root 1.15
117     Use your installed Perl's explicit path in the shebang, or invoke the file
118     with C<perl> as in the commands below.
119    
120     C<mcp_getoptions> parses the common server options and returns startup
121     settings. C<mcp_config> declares protocol metadata and instructions.
122     C<mcp_tool> registers a description, input schema and callback.
123     C<mcp_start> constructs the transport and runs AnyEvent until shutdown.
124     Register everything before starting: code after C<mcp_start> runs only
125     when the server stops.
126    
127     The input schema tells the client what to supply. The server does B<not>
128     validate arguments against JSON Schema, so the callback checks the value it
129     actually uses. A missing text, array or object fails as a tool error.
130     The example allows an empty string.
131    
132     C<$req> is an L<AnyEvent::MCP::Server::Request>; C<$args> is its argument
133     hash reference. Replying finishes the request. Merely returning from the
134     callback does not. You supply the MCP result, and the server supplies the
135     JSON-RPC envelope.
136    
137     =head2 Start and call the server
138    
139     Inspect the common options with:
140    
141     perl echo-mcp --help
142    
143     For a client launching a local subprocess, configure:
144    
145     perl /absolute/path/to/echo-mcp --stdio
146    
147     The client must launch the process itself. Stdin and stdout carry MCP,
148     so starting the program in a terminal and typing text at it is not a test
149     of the echo tool. Print diagnostics to stderr, not stdout.
150    
151     You can make a first call without an AI client. Save this as C<try-echo>
152     in the same directory as C<echo-mcp>:
153    
154     use common::sense;
155     use AnyEvent;
156     use AnyEvent::MCP::Client::JSONL;
157    
158     my $client = AnyEvent::MCP::Client::JSONL->new (
159     command => [$^X, "./echo-mcp", "--stdio"],
160     );
161    
162     my $cv = AE::cv;
163    
164     $client->call (echo => { text => "hello" }, sub {
165     $cv->send (@_);
166     });
167    
168     my ($success, $result) = $cv->recv;
169     $success
170     or die "$result->{message}\n";
171     !$result->{isError}
172     or die "$result->{content}[0]{text}\n";
173    
174     print $result->{content}[0]{text}, "\n";
175    
176     Run C<perl try-echo>. It prints:
177    
178     hello
179    
180     The JSONL client starts the server, connects and negotiates the protocol on
181     first use. C<$^X> chooses the same Perl interpreter as the test client.
182     The condition variable keeps the event loop running until the reply arrives.
183     Keep C<$client> alive while its operations are outstanding.
184    
185     There are two success checks. C<$success> means the JSON-RPC operation
186     completed successfully; a tool can still return C<isError> in that result.
187     This client expects our echo tool's text result. A general client must handle
188     other MCP content representations too.
189    
190     To use HTTP, start the server in one terminal:
191    
192     perl echo-mcp --bind :31742
193    
194     and retrieve its URL in another:
195    
196     perl echo-mcp --bind :31742 --print-url
197    
198     Configure that URL in your MCP client and ask it to call echo with text
199     C<hello>. Both commands use instance C<echo-demo>, unless you override it
200     with C<--name>. Repeat the same listener overrides when printing the URL.
201     C<--print-url> prints the address and exits; it does not start a server.
202    
203     For the small Perl client above, replace the JSONL import and constructor
204     with:
205    
206     use AnyEvent::MCP::Client::HTTP;
207    
208     my $client = AnyEvent::MCP::Client::HTTP->new (
209     url => $ARGV[0],
210     );
211    
212     Pass the printed URL as its first command-line argument.
213     The rest of the call and result handling stays the same.
214    
215     C<mcp_start> supplies named HTTP settings and a token derived from the
216 root 1.34 private global secret. Stdio needs no HTTP configuration. C<name> selects
217     local settings and credentials; C<server_name> identifies the advertised
218     MCP implementation. The two need not be the same.
219 root 1.15 See L<AnyEvent::MCP::Intro/CONFIGURING YOUR SETUP>.
220    
221     =head2 Add an application option
222    
223     Suppose you want the server user to choose the maximum text length.
224     Replace the original C<mcp_getoptions> call with:
225    
226     my $max_length = 4096;
227    
228 root 1.33 my $options = mcp_getoptions (
229 root 1.15 {
230     name => "echo-demo",
231     usage => "Usage: $0 [OPTION]...\n",
232     description => "Return text supplied to the echo tool.",
233     options => [
234     ["--max-length N", "Maximum text length in characters"],
235     ],
236     },
237     "max-length=i" => \$max_length,
238     );
239    
240     $max_length > 0
241     or die "--max-length must be positive\n";
242    
243     Use C<$max_length> in both the schema's C<maxLength> and the callback's
244     length check. The server user can then start:
245    
246     perl echo-mcp --stdio --max-length 1000
247    
248     The option specification is passed to L<Getopt::Long>. The C<options>
249     entries supply human-readable help; they do not register options.
250     The later tool examples use the original 4096-character limit; use your
251     chosen limit in them too if you keep this option.
252     Common options such as C<--stdio>, C<--bind> and C<--help> remain available.
253    
254     If your server also takes positional arguments, validate the remaining
255     C<@ARGV> after C<mcp_getoptions>. It handles C<--help> and C<--print-url>
256     before that validation, so the user can retrieve an HTTP URL without
257     supplying an application workdir or starting the application.
258    
259     Settings affecting URL discovery, such as C<name> and C<path>, should be
260     available to C<mcp_getoptions> through its leading settings hash or an
261     earlier C<mcp_config> call. A setting supplied only after the parser exits
262     for C<--print-url> cannot affect that printed URL.
263    
264     =head2 Choose a result representation
265    
266     Our echo callback returns one text content item. Use C<reply> when the
267     representation matters: text, other content types, a larger result or a
268     particular MCP result shape.
269    
270     For an ordinary result object, you can replace the reply with:
271    
272     $req->result ({ text => $text, length => length $text });
273    
274     C<result> returns that hash as C<structuredContent> and also serializes it
275     as JSON in one text content item. Clients consuming only text can still
276     see the data, but it is deliberately duplicated. Wrap arrays or scalars in
277     an object, or use C<reply> to choose a different representation.
278    
279     To report a tool failure explicitly:
280    
281     $req->error ("The requested operation cannot be completed");
282    
283     A synchronous exception from the handler also becomes a tool-error result.
284     Malformed protocol requests and an unknown tool name are handled by the
285     server. Your callback does not construct a JSON-RPC response itself.
286    
287     Reply once. Code that calls C<reply>, C<result> or C<error> and then calls
288     another reply method is a programming error. Keep a final reply separate
289     from the work leading to it so the completion point is easy to see.
290    
291     =head2 Add a permission check before changing state
292    
293     Authentication alone does not decide which tool operations a user allows.
294     For an example with a side effect but no external worker, add a tool that
295     stores one note in the server's memory.
296    
297     Add this before registering the tools:
298    
299     use AnyEvent::MCP::Server::Permissions;
300    
301     my $note;
302    
303 root 1.53 mcp_permissions +AnyEvent::MCP::Server::Permissions->new (
304 root 1.15 name => "echo-notes",
305     actions => {
306     remember => "Replace the server's in-memory note",
307     },
308     );
309    
310     Then register this tool before C<mcp_start>:
311    
312     mcp_tool remember =>
313     "Replace the server's in-memory note with the supplied text.",
314     {
315     type => "object",
316     properties => {
317     text => { type => "string", maxLength => 4096 },
318     },
319     required => ["text"],
320     },
321     sub {
322     my ($req, $args) = @_;
323    
324     my $text = $args->{text};
325     defined $text and !ref $text and length $text <= 4096
326     or die "text must be a string of at most 4096 characters\n";
327    
328     $req->require_permission ({
329     tool => $req->{tool},
330     action => "remember",
331     sandbox_grants => [],
332     reason => "replace the server's in-memory note",
333     });
334    
335     $note = $text;
336    
337     $req->result ({ remembered => length $note });
338     };
339    
340     Restart the server and call remember through your client. The default
341     permission helper asks on the server machine. Allow once permits this
342     invocation. Allow always saves a rule for action C<remember>; subsequent
343     matching calls do not ask again. The note itself lasts only for this
344     server process and is shared by its clients.
345    
346     The permission engine's name C<echo-notes> selects its default policy file:
347    
348 root 1.16 ~/.config/anyevent-mcp/rules/echo-notes.ini
349 root 1.15
350     The actual directory follows C<XDG_CONFIG_HOME> when set.
351     This is the shared engine's default convention, different from mcp-fs's
352 root 1.16 C<rules/fs-NAME.ini>. Manage it using C<anyevent-mcp manage-rules-file>
353 root 1.15 with the actual pathname, or supply C<file> to the engine explicitly.
354    
355     For example, a manually written allow rule is:
356    
357     [rule]
358     decision=allow
359     action=remember
360     sandbox-grants=
361    
362     The empty grant list is intentional. This tool updates an in-memory value;
363     it does not launch a sandboxed worker. C<action> is the operation class
364     to authorize, while C<sandbox_grants> describes worker authority.
365    
366     The permission engine checks policy; it does not restrict filesystem or
367     network access by itself. If you add file or command tools, define the
368     necessary grants and enforce the returned set through your worker or
369     sandbox. Do not copy C<sandbox_grants =E<gt> []> into a file-writing
370     handler and assume it confines access.
371    
372     Here the permission name is deliberately fixed and independent of the
373     HTTP instance name. If you want each C<--name> to have separate policy,
374     pass C<$options{name}> as the permission engine's name instead.
375    
376     =head2 Why checks must precede side effects
377    
378     When permission is missing, C<require_permission> throws a special
379     exception. Server catches it, asks, and may invoke the handler again.
380     The assignment to C<$note> therefore comes B<after> the check.
381    
382     With a local prompt, the server retries internally. Modern MCP form asking
383     returns C<input_required>; the client repeats the call with its answer.
384     Earlier one-time grants survive permission retries within this invocation,
385     but do not authorize the next call. Saved rules are rechecked.
386    
387     Do not catch that exception as an ordinary failure around the permission
388     check. It must reach Server's synchronous dispatch boundary.
389     For several checks, complete all of them before modifying state or starting
390     asynchronous work. An C<on_request> hook also runs on retry, so it must not
391     perform a once-only side effect before the handler's checks.
392    
393     To choose client-side form asking for this example:
394    
395     PERL_ANYEVENT_MCP_PERMISSION_ASK=auto perl echo-mcp --stdio
396    
397     C<auto> uses modern form elicitation when the client advertises support,
398     otherwise the local helper. C<mcp> requires that support; C<helper> keeps
399     the prompt local. The tiny try-echo client advertises no form support,
400     so auto uses the helper there. To handle forms in a Perl client, see
401     L<AnyEvent::MCP::Client/on_request> and
402     L<AnyEvent::MCP::Permissions::Ask/elicitation>.
403    
404     For grant definitions, subject scopes and multi-path operations, see
405     L<AnyEvent::MCP::Server::Permissions> and
406     L<AnyEvent::MCP::Server::Toolset/Permission checks and retries>.
407    
408     =head2 Let an asynchronous operation finish later
409    
410     A tool callback runs in the AnyEvent event loop. Blocking it with C<sleep>
411     or a synchronous long-running command also blocks other requests.
412    
413     Add C<use AnyEvent;> and register the following before C<mcp_start>:
414    
415     mcp_tool delayed_echo =>
416     "Return the supplied text after a delay of up to 60 seconds.",
417     {
418     type => "object",
419     properties => {
420     text => { type => "string", maxLength => 4096 },
421     seconds => {
422     type => "integer",
423     minimum => 0,
424     maximum => 60,
425     },
426     },
427     required => ["text", "seconds"],
428     },
429     sub {
430     my ($req, $args) = @_;
431    
432     my $text = $args->{text};
433     defined $text and !ref $text and length $text <= 4096
434     or die "text must be a string of at most 4096 characters\n";
435    
436     my $seconds = $args->{seconds};
437     defined $seconds and !ref $seconds
438     and $seconds =~ /\A[0-9]+\z/ and $seconds <= 60
439     or die "seconds must be an integer from 0 to 60\n";
440    
441     $req->progress (0, 1, "Waiting");
442    
443     my $timer = AnyEvent->timer (after => $seconds, cb => sub {
444     $req->{finished}
445     and return;
446    
447     $req->progress (1, 1, "Done");
448     $req->reply ({
449     content => [{ type => "text", text => $text }],
450     });
451     });
452    
453     $req->{on_cancel} = sub {
454     undef $timer;
455     };
456     };
457    
458     The tool is advertised as C<delayed-echo>, because registration converts
459     underscores to hyphens. Ask the client to call it with text C<hello> and
460     seconds C<2>. The callback returns immediately; the timer supplies the reply
461     later. Other requests can run while it waits.
462    
463     Keep watcher objects alive while they are needed. Here the cancellation
464     callback retains C<$timer>, and the timer's callback retains C<$req> and
465     C<$text>. Cancellation releases the timer. Replying finishes the request
466     and removes its cancellation callback, releasing the watcher as well.
467     No separate job registry is needed for this example.
468    
469     Check C<finished> in late completion callbacks before replying.
470     Cancellation sets both C<finished> and C<cancelled> and sends no reply.
471     For subprocesses or network work, the cancellation callback should stop
472     that operation and release its resources instead of merely abandoning
473     the eventual reply.
474    
475     Server catches exceptions during synchronous dispatch, not exceptions
476     from a later timer or I/O callback. If an asynchronous operation can fail,
477     handle that failure in its completion callback and report it with
478     C<error> or C<reply>, provided the request is still active.
479    
480     C<progress> only sends when the caller supplied a progress token.
481 root 1.55 An outstanding tool request with a progress token emits an automatic
482     elapsed-time heartbeat every 30 seconds. Delivery is best effort:
483     stdio sends notifications directly, while Streamable HTTP sends them
484     over the request's SSE response stream. Clients may ignore progress.
485 root 1.15
486     =head2 When to use the object interfaces
487    
488     Simple owns one default Toolset and the blocking server lifetime.
489     That is useful for a small standalone program. If you already run an
490     event loop, want multiple transports or tool registries, or need custom
491     protocol dispatch, construct the objects directly.
492    
493     L<AnyEvent::MCP::Server::Toolset> registers reusable tools.
494     L<AnyEvent::MCP::Server> handles the protocol.
495     L<AnyEvent::MCP::Server::HTTP> and L<AnyEvent::MCP::Server::JSONL> manage
496     connections. L<AnyEvent::MCP::Server::Request> describes request fields,
497     replies, progress and cancellation. The same tool callback model applies
498     at both levels.
499    
500 root 1.1 =head1 FUNCTIONS
501    
502     =over
503    
504     =item mcp_config %config
505    
506     Adds server configuration. Repeated calls extend or replace previously declared
507     values. C<instructions> is treated as human-readable text and normalized.
508    
509 root 1.15 Server and transport options are passed to the underlying objects.
510     Common options include:
511    
512     =over
513    
514 root 1.34 =item name => $instance
515    
516     Local instance name used to select configuration and credentials. Independent
517     of the name reported to MCP clients.
518    
519     =item server_name => $name
520    
521     Advertised MCP server implementation name. Defaults to C<AnyEvent::MCP>.
522    
523     =item server_version => $version
524    
525     Advertised application version, not the MCP protocol revision. Accepts
526     an ordinary Perl version number; Simple stringifies it for MCP.
527     Defaults to the AnyEvent::MCP library version.
528    
529 root 1.15 =item server_info => { name => $name, version => $version }
530 root 1.1
531 root 1.34 Raw MCP server identity, passed through unchanged to the underlying
532     server. Cannot be combined with C<server_name> or C<server_version>.
533 root 1.1
534 root 1.15 =item instructions => $text
535 root 1.1
536 root 1.15 Human-readable guidance for the client, normalized when configured.
537 root 1.1
538 root 1.21 =item short_instructions => $text
539    
540     Optional condensed guidance for finding this server's tools. Used as the
541     advertised MCP instructions when C<auto_instructions> is enabled.
542    
543     =item auto_instructions => $boolean
544    
545     Enabled by default. Exposes full instructions through the generated
546     C<meta-instructions> tool while advertising the short instructions if set.
547 root 1.22 When short or custom instructions are provided, append a brief hint pointing
548     to the full tool description; otherwise advertise full instructions as before.
549 root 1.21 Disable to advertise full instructions and omit the generated tool.
550    
551     =item custom_instructions => $text
552    
553     Instance-specific purpose prepended to advertised instructions. Can also be
554     set through C<--custom-instructions> or C<custom_instructions> in the program
555     or instance section of the global INI configuration.
556    
557 root 1.15 =item on_request => $callback->($server, $req)
558    
559     Synchronous hook before each dispatch attempt. For example, refresh
560     database state here. This does not replace dispatch or receive a
561     completion callback, and permission retries can invoke it again.
562    
563     =item on_start => $callback->($server)
564    
565     Called after the transport is ready, before entering the event loop.
566     Use it for startup information or installing watchers. Stdout carries
567     protocol messages in stdio mode; print diagnostics to stderr.
568 root 1.1 The callback must not block.
569    
570 root 1.15 =item idle_exit => $seconds
571    
572     Optional positive idle time for spawned stdio servers. With a multibroker
573     advertising C<anyevent-mcp/multibroker> version 1, Simple requests shutdown
574     once no MCP requests have been active for that long. The broker can ignore
575 root 1.54 the request. An application may veto shutdown with C<on_idle_shutdown>;
576     Simple retries after the configured interval if it is declined. HTTP servers,
577     ordinary clients and servers without this option are unaffected.
578 root 1.15
579     =back
580    
581     See L<AnyEvent::MCP::Server> for further protocol options and events.
582 root 1.11
583 root 1.53 =item mcp_permissions $object
584    
585     Select the L<AnyEvent::MCP::Server::Permissions> object used by the Simple
586     starters. If never called (and no explicit C<permissions> option is supplied),
587     Simple creates one automatically, named from the program and selected instance.
588     C<mcp_permissions undef> explicitly disables the permission system:
589     tool callbacks calling C<require_permission> then fail instead of prompting.
590     Applications can still pass C<permissions> through C<mcp_config> or startup
591     arguments when needed.
592    
593     The common C<--ask> option overrides the object's prompting mode, and CLI
594     automatic permission rules precede configured rules and rules originally
595     supplied with the object. Those rules are applied when starting the server,
596     not when C<mcp_permissions> is called. No permission object is created when
597     it is explicitly disabled.
598    
599 root 1.1 =item mcp_tool $name, $description, $input_schema, $callback->($req, $args)
600    
601     Registers a tool named C<$name> with C<$description> and the JSON Schema
602     hash reference C<$input_schema>. C<$description> is normalized like
603     C<instructions>.
604    
605     C<$callback> receives an L<AnyEvent::MCP::Server::Request> object C<$req>
606     and its C<< $req->{args} >> hash reference as C<$args>, for convenience.
607    
608     Tools complete the request with C<< $req->reply ($result) >>, or use
609     C<< $req->result ($data) >> and C<< $req->error ($message) >> for convenience.
610     Request context is available directly in
611     the hash, for example C<< $req->{tool} >> and C<< $req->{server} >>.
612    
613 root 1.33 =item $server_options = mcp_getoptions ({ %settings }, @getopt_options)
614 root 1.8
615 root 1.47 Parse the switches documented in L</COMMON COMMAND-LINE OPTIONS> alongside
616 root 1.8 application-specific L<Getopt::Long> option specifications:
617    
618     my $verbose;
619 root 1.33 my $options = mcp_getoptions (
620 root 1.9 {
621     usage => "Usage: $0 [OPTION]... WORKDIR\n",
622     description => "Provide example tools through MCP.",
623     option_heading => "Example options:",
624     options => [
625     ["--verbose", "Show additional diagnostics"],
626     ],
627     examples => " $0 --stdio ./project",
628     path => "/",
629     },
630 root 1.8 "verbose!" => \$verbose,
631     );
632     @ARGV == 1 or die "workdir required\n";
633 root 1.33 mcp_start %$options;
634 root 1.8
635 root 1.15 The optional leading hash accepts these help settings:
636    
637     =over
638    
639     =item usage => $text
640    
641     Usage synopsis, including its trailing newline. A default synopsis is
642     generated when omitted.
643    
644     =item description => $text
645    
646     Introductory help paragraph.
647    
648     =item options => [[$label, $description]...]
649    
650     Application option labels and descriptions. A plain string among the
651     pairs starts a new section; a string at the beginning replaces the
652     default section heading.
653    
654     =item option_heading => $text
655    
656     Initial section heading, defaulting to C<Application options:>.
657    
658     =item examples => $text
659    
660     Preformatted examples.
661    
662 root 1.52 =item without => [OPTION...]
663    
664     Exclude selected common options by their long names, for example
665     C<< without => ["ask"] >>. The parser rejects excluded switches, and
666     C<--help> omits them. All common options are enabled by default.
667    
668 root 1.57 =item print_url_extra => $callback
669    
670     Append application-specific URLs to C<--print-url> output. Called once with
671     the resolved HTTP settings after the normal MCP URLs have been printed.
672    
673     =item default_port => $port
674    
675     Choose an application-specific fallback port for C<--print-url>, without
676     replacing the bind address or overriding ports explicitly specified by
677     C<--bind> or configuration. The application must also pass it to
678     C<http_settings> when starting its listener.
679    
680 root 1.15 =back
681    
682 root 1.47 See L</COMMON COMMAND-LINE OPTIONS> for the full reference to shared
683     server switches. Application-specific Getopt::Long options are parsed alongside them.
684 root 1.35
685     These switches also appear in C<--help>; application options are passed
686     through to L<Getopt::Long>. Other leading hash settings (e.g. C<path>,
687     C<auth>, C<url_scheme>) go to C<mcp_start>. Parse positional C<@ARGV>
688     in the application; C<mcp_getoptions> deliberately does not require them
689     for help or URL discovery. Default activity logging includes asynchronous
690     tool failures and formats time, program, instance and PID. Environment
691     C<PERL_ANYEVENT_LOG> can select destinations and verbosity.
692 root 1.8
693 root 1.26 =item mcp_tool_help ($enabled, @toolsets)
694    
695     If C<$enabled> is true, print the definitions registered in the supplied
696     L<AnyEvent::MCP::Server::Toolset> objects and exit. Otherwise do nothing.
697     Call after registering tools but before starting listeners for executables
698     which manage their own toolsets. Only registered tools appear.
699    
700 root 1.27 =item mcp_log $message
701    
702     Emit operator-facing activity to the configured AnyEvent::Log destination,
703     bypassing its diagnostic verbosity filter. C<--quiet> suppresses this log.
704    
705 root 1.38 =item mcp_log_request ($server, $req, %extra)
706 root 1.27
707     C<on_request> callback to report tool invocations once across permission
708 root 1.30 retries, and log tool-error replies, including asynchronous ones. It logs
709     selected arguments rather than entire request or reply payloads. This includes
710 root 1.39 JSON-quoted previews of command arguments and VCS filenames (up to four,
711     80 characters each), and Vim C<ex> commands (up to 200 characters).
712     These can contain sensitive text. The Simple starters install it by default;
713 root 1.30 custom servers can use it too.
714 root 1.38 C<%extra> appends application-specific C<key=value> fields in key order.
715     Sanitize sensitive arguments before passing them.
716 root 1.27
717 root 1.1 =item mcp_start %config
718    
719 root 1.15 Starts and runs the usual server. When C<stdio> is true or
720 root 1.1 C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL> is C<stdio>, uses C<mcp_stdio>.
721 root 1.4 Otherwise starts HTTP without printing its URL. HTTP-capable executables
722     provide C<--print-url> to retrieve the URL explicitly.
723 root 1.1
724 root 1.3 The HTTP defaults are IPv4 loopback, a stable name-derived port in
725     16384..32767, and C</> as the endpoint path.
726 root 1.7 Without explicit C<tokens>, one server-specific token is derived from
727     the private global secret. The selected token authenticates either its URL
728     path or an
729 root 1.5 C<Authorization: Bearer TOKEN> header at the clean URL returned by
730 root 1.6 C<< $server->url (0) >>. The corresponding executable's C<--print-url>
731     option prints the first accepted token in its URL.
732 root 1.1
733 root 1.10 C<name> selects the C<[instance NAME]> configuration section and credential.
734 root 1.16 It defaults to C<default>.
735 root 1.43 Named settings C<bind>, C<path>, C<url_host>, C<url_scheme>, C<tokens>,
736     C<tls_cert> and C<tls_key> are loaded from the global configuration.
737     Explicit C<tokens> (an ordered,
738 root 1.7 nonempty array reference of URL-safe strings) replaces the configured list
739     and the derived default. Every token works in the URL and bearer header.
740 root 1.3 An explicit C<bind> overrides the configured bind address. Other arguments
741 root 1.7 override these settings.
742 root 1.1
743     The global secret is generated automatically when needed; see
744     L<AnyEvent::MCP/GLOBAL CONFIGURATION>. No configuration is loaded for stdio.
745 root 1.53 Unless explicitly disabled using C<mcp_permissions undef>, Simple constructs
746     a default permission engine. C<--ask> and automatic permission rules configure
747     it along with any application-supplied permission object.
748 root 1.4 HTTP startup neither prints to stdout nor changes its buffering.
749 root 1.7 C<mcp_start> does not parse C<@ARGV>; applications parse their own options.
750 root 1.8 The bundled executables use C<mcp_getoptions>, which handles shared
751     options and URL discovery before starting the event loop.
752 root 1.1
753 root 1.17 =item mcp_connect %config
754    
755     Run a reverse-connecting JSONL server using C<connect =E<gt>
756 root 1.47 "http[s]://host:port/token/alias">. HTTPS verifies the server certificate
757     and hostname. It sends the authenticated extension hello only after the
758     transport is established, then serves MCP over the socket. Refused TCP
759     connections retry every second for at most sixty attempts, and unexpected
760     established disconnects start a new retry period. Other TCP setup and TLS
761     handshake errors are reported and stop the server.
762     Calls are cancelled rather than replayed.
763 root 1.17 This connection has no spawned-child lifecycle authority.
764    
765 root 1.1 =item mcp_stdio %config
766    
767     Creates and runs a server over stdin/stdout. Arguments override the collected
768     configuration. Stdout buffering is disabled. On stdin EOF, pending replies
769     finish and stdout drains before
770     this function returns. Stream errors close the connection and cancel pending
771     requests.
772    
773     =item mcp_http %config
774    
775     Creates and runs an HTTP server. For example:
776    
777     mcp_http
778     host => "127.0.0.1",
779     port => 31740,
780 root 1.7 path => "/mcp/",
781     tokens => ["your-long-random-secret"];
782 root 1.1
783     Arguments override the collected configuration. Unlike C<mcp_start>, this
784 root 1.8 function does not derive or supply authentication. B<Always> pass C<tokens>,
785     or explicitly pass C<auth =E<gt> "none"> when a trusted embedding layer
786     enforces access. Omitting both is an error, not an unauthenticated default.
787     It does not print a URL; use
788 root 1.7 C<on_start> for application-specific startup output. Neither function
789     implements OAuth or Basic authentication. See
790     L<AnyEvent::MCP::Server::HTTP/AUTHENTICATION> for details.
791 root 1.1
792     All three starters return when the server is closed or on C<SIGINT> or
793     C<SIGTERM>. Signal shutdown closes connections and cancels outstanding
794     requests. The signal watchers belong to the blocking call.
795    
796     =back
797    
798 root 1.47 =head1 COMMON COMMAND-LINE OPTIONS
799    
800     C<mcp_getoptions> parses these options for executables using
801     L<AnyEvent::MCP::Server::Simple>. The executable's own OPTIONS section
802     documents any additional switches or differences in shared-option behavior.
803 root 1.52 An application can exclude specific common switches via C<without>.
804 root 1.47
805     =over
806    
807     =item --name NAME
808    
809 root 1.52 Select the instance (default C<default>). NAME starts with an ASCII letter,
810     digit or underscore; subsequent characters may also contain C<.>, C<@>,
811     C<+> and C<->.
812 root 1.47 Selects C<[instance NAME]> configuration, derived HTTP port and credentials;
813     does not change the advertised MCP implementation name.
814     Example: C<--name project>.
815    
816     =item --bind ADDRESS[:PORT]
817    
818     Override HTTP listening address and port. Defaults to C<127.0.0.1> and a
819     stable instance-derived port in 16384..32767. C<:31740> uses the loopback
820     address and port 31740; C<127.0.0.1:31740> specifies both.
821     Port zero requests an ephemeral port and is incompatible with
822     C<--print-url>. Ignored for stdio-only startup.
823    
824     =item --tls-cert FILE
825    
826     Enable opportunistic HTTPS using a PEM certificate chain. Without
827     C<--tls-key>, FILE must also contain the private key. The same listener
828     continues to accept plain HTTP.
829    
830     =item --tls-key FILE
831    
832     Private-key PEM file for C<--tls-cert>. If supplied alone, FILE must
833     contain both certificate chain and private key. Requires C<Net::SSLeay>.
834    
835     =item --token TOKEN
836    
837     Accept TOKEN for HTTP authentication. Repeat to accept multiple tokens.
838     Explicit tokens replace configured or master-secret-derived defaults.
839     Tokens work both as credential-bearing URL components and with the
840     C<Authorization: Bearer TOKEN> header.
841     Example: C<--token first --token second>.
842    
843 root 1.51 =item -s, --set KEY=VALUE
844    
845     Override one trusted configuration entry for the selected instance in this
846     process. Applied after C<[program NAME]> and C<[instance NAME]> defaults,
847     without changing the configuration file. Repeat to override multiple entries;
848     the last assignment to a key wins. Empty values suppress inherited settings,
849     and additional equals signs belong to the value. The supported keys depend
850     on the server (for example, C<executable.perl> and C<prepend-path> in
851     L<mcp-fs>). Example: C<-s executable.perl=/opt/perl/bin/perl>.
852    
853 root 1.47 =item --stdio
854    
855     Serve MCP JSONL on stdin/stdout rather than the default HTTP listener.
856     Applications with auxiliary listeners can keep those active. Cannot
857     combine explicitly with C<--connect>.
858    
859     =item --connect URL
860    
861     Reverse-connect to an upstream multibroker. For example:
862     C<http://host:port/TOKEN/alias> or C<https://host:port/TOKEN/alias>.
863     HTTPS requires successful certificate and hostname verification; it
864     never falls back to plaintext. Replaces the default MCP listener and
865     overrides implicit stdio selection. Cannot combine with explicit
866     C<--stdio>.
867    
868     =item --custom-instructions TEXT
869    
870     Override configured instance-purpose guidance describing when MCP
871     clients should use the server.
872     Example: C<--custom-instructions 'Use for build logs'>.
873    
874     =item --auto-allow TOOL[,ACTION[,CAPABILITY]]
875    
876     Add a process-local permission allow rule, repeatable. TOOL identifies
877     the permission-checked tool; ACTION and CAPABILITY narrow the match.
878     Missing fields and C<*> match anything.
879     Example: C<--auto-allow '*,read,workdir-read'>.
880     Rules run in command-line order before saved rules; they cannot grant
881     permissions to tools that do not check permissions.
882     See L<AnyEvent::MCP::Server::Permissions/auto_rules>.
883    
884     =item --auto-deny TOOL[,ACTION[,CAPABILITY]]
885    
886     Add a repeatable process-local deny rule with the same matching syntax
887     and evaluation order as C<--auto-allow>.
888     Example: C<--auto-deny '*,exec'>.
889    
890 root 1.52 =item --ask MODE
891    
892     Choose how permissions or other human input are requested: C<helper>,
893     C<tty>, C<yad>, C<mcp>, or C<auto>. C<none> immediately declines requests
894     that would otherwise require prompting; existing allow and deny rules
895     still apply. Multibroker replies to downstream forms with a standard decline.
896     The default depends on the application and its multibroker environment.
897    
898 root 1.47 =item --quiet
899    
900     Suppress routine tool activity and tool-failure logging. Warnings and
901     ordinary diagnostics remain; C<PERL_ANYEVENT_LOG> determines diagnostic
902     levels and destinations.
903    
904     =item --print-url[=USER]
905    
906     Print credential-bearing URLs and exit without starting a listener.
907     With managed users, prints each effective enabled user's URL, or only
908     the exact username USER. Comments identify scope-qualified principals
909     and the selected auxiliary connection user. With configured TLS, HTTPS
910     is printed before HTTP. Explicit tokens retain a single-token output
911     and cannot be combined with a user selector. Requires a predictable,
912     nonzero port and a persistent master secret for derived credentials.
913     Example: C<--name project --print-url=claude>.
914    
915     =item --print-urls
916    
917     Alias for C<--print-url>.
918    
919     =item --tool-help
920    
921     Print currently registered tool names, descriptions, argument types,
922     required fields and defaults; exit without starting the MCP listener.
923     Registered tools can depend on installed utilities and application setup.
924    
925 root 1.48 =item --help
926 root 1.47
927     Print shared and application-specific command-line help; exit without
928     starting a server. Invalid options instead report usage.
929    
930     =back
931    
932 root 1.1 =cut
933    
934     package AnyEvent::MCP::Server::Simple;
935    
936     use common::sense;
937 root 1.46
938     use Exporter ();
939     use JSON::XS ();
940     use Scalar::Util qw(refaddr);
941    
942 root 1.1 use AnyEvent ();
943 root 1.40 use AnyEvent::Log ();
944 root 1.1 use AnyEvent::MCP ();
945 root 1.56 use AnyEvent::MCP::Config ();
946 root 1.1 use AnyEvent::MCP::Server::Toolset ();
947    
948     our @ISA = qw(Exporter);
949 root 1.53 our @EXPORT = qw(mcp_config mcp_permissions mcp_tool mcp_getoptions mcp_tool_help mcp_log mcp_log_request mcp_start mcp_stdio mcp_http mcp_connect);
950 root 1.1
951 root 1.41 my %config = (
952     auto_instructions => 1,
953     on_request => \&mcp_log_request,
954 root 1.42 on_connection_error => sub {
955     my ($server, $handle, $error) = @_;
956     my $info = $handle->{mcp_peer}{client_info};
957     my $name = ref $info eq "HASH" ? $info->{name} : undef;
958     my @details = (
959     "id=" . sprintf ("%x", refaddr $handle),
960     defined $handle->{mcp_remote} ? "remote=$handle->{mcp_remote}" : (),
961     defined $name && !ref $name ? "client=" . JSON::XS::encode_json (substr $name, 0, 80) : (),
962     );
963 root 1.50 AE::log trace => "MCP connection " . join (" ", @details) . ": $error";
964 root 1.42 },
965 root 1.41 );
966 root 1.1 my $toolset = AnyEvent::MCP::Server::Toolset->new;
967 root 1.27 my $quiet;
968 root 1.1
969     sub _run;
970    
971 root 1.27 # operational messages go to the configured AnyEvent::Log destination,
972     # independently of the diagnostic verbosity filter.
973     sub mcp_log {
974     return if $quiet;
975     $AnyEvent::Log::LOG->log (info => @_);
976     }
977    
978     sub mcp_log_request {
979 root 1.38 my ($server, $req, %extra) = @_;
980 root 1.27
981     return unless $req->{method} eq "tools/call";
982     return if $req->{mcp_logged}++;
983    
984     my $args = $req->{args};
985 root 1.39 my $vcs = $req->{tool} =~ /\Avcs-(?:diff|status|log)\z/;
986 root 1.27 my @details;
987 root 1.39 for (qw(target workdir path cwd), $vcs ? "backend" : ()) {
988 root 1.27 push @details, "$_=" . (substr $args->{$_}, 0, 160)
989     if defined $args->{$_} && !ref $args->{$_};
990     }
991 root 1.36 push @details, substr +(join " ", @{$args->{ranges}}), 0, 180
992 root 1.27 if ref $args->{ranges} eq "ARRAY";
993 root 1.39 for my $key ("argv", $vcs ? "files" : ()) {
994     next unless ref $args->{$key} eq "ARRAY" && @{$args->{$key}};
995 root 1.31 require JSON::XS;
996 root 1.39 my @values = @{$args->{$key}};
997     push @details, "$key=" . join (" ", map {
998 root 1.31 JSON::XS::encode_json (substr $_, 0, 80) . (length $_ > 80 ? "..." : "")
999 root 1.39 } @values[0 .. ($#values < 3 ? $#values : 3)])
1000     . (@values > 4 ? " ..." : "");
1001 root 1.31 }
1002     if ($req->{tool} eq "grep" && defined $args->{pattern} && !ref $args->{pattern}) {
1003     require JSON::XS;
1004     push @details, "pattern=" . JSON::XS::encode_json (substr $args->{pattern}, 0, 160)
1005     . (length $args->{pattern} > 160 ? "..." : "");
1006     }
1007 root 1.29 push @details, "name=" . (substr $args->{name}, 0, 160)
1008     if $req->{tool} eq "call-tool" && defined $args->{name} && !ref $args->{name};
1009     if ($req->{tool} eq "ex" && defined $args->{command} && !ref $args->{command}) {
1010     require JSON::XS;
1011     push @details, "command=" . JSON::XS::encode_json (substr $args->{command}, 0, 200)
1012     . (length $args->{command} > 200 ? "..." : "");
1013     }
1014 root 1.28 if ($req->{tool} eq "path-op") {
1015     push @details, map {
1016     ($_ eq "unlink" || $_ eq "rmdir")
1017     ? ($args->{$_} ? $_ : ())
1018     : (defined $args->{$_} && !ref $args->{$_}
1019     ? "$_=" . (substr $args->{$_}, 0, 80) : ())
1020     } qw(unlink rmdir mkpath mkdir symlink truncate chmod rename);
1021     }
1022 root 1.38 for my $key (sort keys %extra) {
1023     next unless defined $extra{$key} && !ref $extra{$key};
1024     push @details, "$key=" . (substr $extra{$key}, 0, 160);
1025     }
1026 root 1.27
1027     (my $message = join " ", $req->{tool}, @details) =~ s/[\r\n]/ /g;
1028     mcp_log $message;
1029    
1030     my $reply = $req->{on_reply};
1031     $req->{on_reply} = sub {
1032     my ($req, $result) = @_;
1033    
1034     if ($result->{isError}) {
1035     my $message = ref $result->{content} eq "ARRAY"
1036     && ref $result->{content}[0] eq "HASH"
1037     ? $result->{content}[0]{text} // "unknown error" : "unknown error";
1038     $message =~ s/\n.*//s;
1039     mcp_log "$req->{tool} failed: " . (substr $message, 0, 240);
1040     }
1041     $reply->(@_);
1042     };
1043     }
1044    
1045     sub _mcp_log_init {
1046     my ($name, $silent) = @_;
1047    
1048     $quiet = $silent;
1049     my $identity = AnyEvent::MCP::Config::program_name () . "/$name" . "[$$]";
1050    
1051     no warnings 'redefine';
1052     *AnyEvent::Log::default_format = sub ($$$$) {
1053     my ($when, $ctx, $level, $message) = @_;
1054 root 1.36 my $stamp = substr +(AnyEvent::Log::format_time $when), 8, 14;
1055 root 1.27 my $source = $ctx == $AnyEvent::Log::LOG ? "" : "$AnyEvent::Log::LEVEL2STR[$level] $ctx->[0]: ";
1056     join "", map "$stamp $identity $source$_\n", split /\n/, $message;
1057     };
1058     }
1059    
1060 root 1.1 sub mcp_config {
1061     my %kv = @_;
1062    
1063     $kv{instructions} = AnyEvent::MCP::_clean_text $kv{instructions}
1064     if exists $kv{instructions};
1065    
1066 root 1.14 %config = (%config, %kv);
1067 root 1.1 }
1068    
1069 root 1.53 sub mcp_permissions {
1070     @_ == 1 or die "mcp_permissions expects one argument (object or undef)\n";
1071     $config{permissions} = $_[0];
1072     }
1073    
1074 root 1.1 sub mcp_tool {
1075     $toolset->tool (@_)
1076     }
1077    
1078 root 1.26 # format a JSON Schema type for command-line tool help.
1079     sub _tool_type {
1080     my ($schema) = @_;
1081     return "any" unless ref $schema eq "HASH";
1082     for my $key (qw(anyOf oneOf)) {
1083     return join "|", map _tool_type ($_), @{$schema->{$key}}
1084     if ref $schema->{$key} eq "ARRAY";
1085     }
1086     my $type = $schema->{type} // "any";
1087     $type = join "|", @$type if ref $type eq "ARRAY";
1088     $type = "array[" . _tool_type ($schema->{items}) . "]" if $type eq "array";
1089     $type
1090     }
1091    
1092     sub mcp_tool_help {
1093     my ($enabled, @toolsets) = @_;
1094     return unless $enabled;
1095    
1096     my $json = AnyEvent::MCP::json_class ()->new->canonical;
1097     my @tools = map @{$_->list}, @toolsets;
1098     for my $tool (@tools) {
1099     print "$tool->{name}\n";
1100     (my $description = $tool->{description} // "") =~ s/\n/\n /g;
1101     print " $description\n" if length $description;
1102    
1103     my $schema = $tool->{inputSchema} || {};
1104     my $properties = $schema->{properties} || {};
1105     my %required = map { $_ => 1 } @{$schema->{required} || []};
1106     for my $name (sort keys %$properties) {
1107     my $property = $properties->{$name};
1108     my $type = _tool_type ($property);
1109     my $status = $required{$name} ? "required" : "optional";
1110     my @extras;
1111     push @extras, "default: " . $json->encode ($property->{default})
1112     if exists $property->{default};
1113     push @extras, "values: " . join (", ", @{$property->{enum}})
1114     if ref $property->{enum} eq "ARRAY";
1115     print " $name $type $status", (@extras ? " (" . join ("; ", @extras) . ")" : ""), "\n";
1116     if (length ($property->{description} // "")) {
1117     (my $detail = $property->{description}) =~ s/\n/\n /g;
1118     print " $detail\n";
1119     }
1120     }
1121     print "\n";
1122     }
1123     print "No tools registered.\n" unless @tools;
1124     exit;
1125     }
1126    
1127 root 1.9 sub _mcp_help_row {
1128     my ($option, $description) = @_;
1129 root 1.13
1130 root 1.9 my $start = " $option";
1131     my $margin = " " x 34;
1132 root 1.13 my $out = length $start < 34 ? "" : "$start\n";
1133     my $line = length $start < 34 ? $start . (substr $margin, length $start) : $margin;
1134 root 1.9
1135     for my $word (split /\s+/, $description) {
1136 root 1.13 if (length $line > 34 && (length $line) + 1 + length $word > 79) {
1137 root 1.9 $out .= "$line\n";
1138     $line = $margin;
1139     }
1140 root 1.13
1141     $line .= " " if length $line > 34;
1142 root 1.9 $line .= $word;
1143     }
1144    
1145     "$out$line\n"
1146     }
1147    
1148     sub _mcp_help {
1149 root 1.52 my ($usage, $description, $options, $option_heading, $examples, $without) = @_;
1150 root 1.13
1151 root 1.9 $usage =~ s/\n*\z/\n/;
1152     my $help = $usage;
1153     $help .= "\n$description\n" if defined $description && length $description;
1154    
1155     $help .= "\nCommon options:\n";
1156     my @common = (
1157 root 1.25 ["--name NAME", "Select named configuration (instance defaults to 'default')"],
1158 root 1.9 ["--bind ADDRESS[:PORT]", "Override the HTTP listen address or port"],
1159 root 1.43 ["--tls-cert FILE", "PEM certificate (and optionally key) for HTTPS"],
1160     ["--tls-key FILE", "PEM private key (or combined key/certificate PEM)"],
1161 root 1.9 ["--token TOKEN", "Set an accepted token (repeatable; replaces defaults)"],
1162 root 1.51 ["-s, --set KEY=VALUE", "Override a trusted instance setting (repeatable)"],
1163 root 1.9 ["--stdio", "Serve MCP on stdin/stdout; other listeners may remain"],
1164 root 1.43 ["--connect URL", "Connect to a multibroker at http[s]://host:port/token/alias"],
1165 root 1.21 ["--custom-instructions TEXT", "Describe when this server instance should be used"],
1166 root 1.23 ["--auto-allow TOOL[,ACTION[,CAPABILITY]]", "Add an ordered automatic allow rule (repeatable)"],
1167     ["--auto-deny TOOL[,ACTION[,CAPABILITY]]", "Add an ordered automatic deny rule (repeatable)"],
1168 root 1.52 ["--ask MODE", "Prompt via helper, tty, yad, mcp, auto; none declines without prompting"],
1169 root 1.27 ["--quiet", "Suppress routine tool activity and tool-failure logging"],
1170 root 1.44 ["--print-url[=USER] (--print-urls)", "Print enabled user URLs, or one exact user"],
1171 root 1.26 ["--tool-help", "Print registered tool descriptions and arguments, then exit"],
1172 root 1.48 ["--help", "Display this help and exit"],
1173 root 1.9 );
1174 root 1.52 $help .= _mcp_help_row (@$_) for grep {
1175     my ($name) = $_->[0] =~ /--([a-z-]+)/;
1176     !$without->{$name}
1177     } @common;
1178 root 1.9
1179     if ($options && @$options) {
1180 root 1.12 $help .= "\n" . ($option_heading // "Application options:") . "\n"
1181     if ref $options->[0];
1182     for my $option (@$options) {
1183     if (ref $option) {
1184     $help .= _mcp_help_row (@$option);
1185     } else {
1186     $help .= "\n" unless $help =~ /\n\n\z/;
1187     $help .= "$option\n";
1188     }
1189     }
1190 root 1.9 }
1191     $help .= "\nExamples:\n$examples\n"
1192     if defined $examples && length $examples;
1193 root 1.45 $help .= "\nNamed users and URLs:\n"
1194     . " anyevent-mcp create-user chatgpt\n"
1195     . " $0 --print-urls # list URLs for all enabled users\n"
1196     . " $0 --print-url=chatgpt # select one user\n"
1197     . "A URL marked (connect) is the one used for browser, Vim, or reverse connections.\n"
1198     . "Users inherit from global, program, then instance; server restart is needed\n"
1199     . "after changing user definitions.\n";
1200 root 1.9
1201     $help
1202     }
1203    
1204 root 1.8 sub mcp_getoptions {
1205     require Getopt::Long;
1206    
1207 root 1.13 my %settings = %{ref $_[0] eq "HASH" ? shift : {}};
1208 root 1.57 my $print_url_extra = delete $settings{print_url_extra};
1209 root 1.8 my $usage = delete $settings{usage}
1210 root 1.9 // "Usage: $0 [OPTION]... [ARG]...\n";
1211 root 1.13 my @help = delete @settings{qw(description options option_heading examples)};
1212 root 1.52 my %without = map { $_ => 1 } @{delete $settings{without} || []};
1213 root 1.8 my %url = (%config, %settings);
1214     delete @url{qw(name bind stdio)};
1215 root 1.23 my @auto_rules;
1216 root 1.51 my %set;
1217 root 1.8
1218 root 1.52 my @common = (
1219 root 1.13 AnyEvent::MCP::Config::http_getopt (\my $name, \my $bind, \my $print_url, \my @tokens),
1220 root 1.43 "tls-cert=s" => \my $tls_cert,
1221     "tls-key=s" => \my $tls_key,
1222 root 1.13 "stdio" => \my $stdio,
1223 root 1.17 "connect=s" => \my $connect,
1224 root 1.21 "custom-instructions=s" => \my $custom_instructions,
1225 root 1.23 "auto-allow=s" => sub { push @auto_rules, ["allow", $_[1]] },
1226     "auto-deny=s" => sub { push @auto_rules, ["deny", $_[1]] },
1227 root 1.51 "set|s=s" => sub {
1228     my ($key, $value) = $_[1] =~ /\A([^\s=]+)=(.*)\z/;
1229     defined $value or die "Invalid --set KEY=VALUE\n";
1230     $set{$key} = $value;
1231     },
1232 root 1.26 "tool-help" => \my $tool_help,
1233 root 1.27 "quiet" => \my $silent,
1234 root 1.52 "ask=s" => \my $ask,
1235 root 1.48 "help" => \my $help,
1236 root 1.52 );
1237     my @enabled;
1238     my %known;
1239     while (@common) {
1240     my ($option, $target) = splice @common, 0, 2;
1241     my ($name) = $option =~ /\A([a-z-]+)/;
1242     $known{$name} = 1;
1243     push @enabled, $option => $target unless $without{$name};
1244     }
1245     die "Unknown common option: " . join (", ", sort grep !$known{$_}, keys %without) . "\n"
1246     if grep !$known{$_}, keys %without;
1247     Getopt::Long::GetOptions (
1248     @enabled,
1249 root 1.8 @_,
1250     ) or die $usage;
1251 root 1.52 die "--ask must be helper, tty, yad, mcp, auto, or none\n"
1252     if defined $ask && $ask !~ /\A(?:helper|tty|yad|mcp|auto|none)\z/;
1253 root 1.8
1254     if ($help) {
1255 root 1.52 print _mcp_help $usage, @help, \%without;
1256 root 1.8 exit;
1257     }
1258    
1259 root 1.52 $name = AnyEvent::MCP::instance_name ($name // $settings{name} // $config{name});
1260 root 1.51 if (%set) {
1261 root 1.56 my $instance = (AnyEvent::MCP::Config::global_config)->{section}{"instance $name"} ||= {};
1262 root 1.51 %$instance = (%$instance, %set);
1263     }
1264    
1265 root 1.27 _mcp_log_init ($name, $silent);
1266 root 1.8 my %server = (
1267     %settings,
1268     name => $name,
1269     (defined $bind ? (bind => $bind) : ()),
1270 root 1.43 (defined $tls_cert ? (tls_cert => $tls_cert) : ()),
1271     (defined $tls_key ? (tls_key => $tls_key) : ()),
1272 root 1.8 (@tokens ? (tokens => \@tokens) : ()),
1273     ($stdio ? (stdio => 1) : ()),
1274 root 1.17 (defined $connect ? (connect => $connect) : ()),
1275 root 1.52 (defined $ask ? (ask => $ask) : ()),
1276 root 1.21 (defined $custom_instructions ? (custom_instructions => $custom_instructions) : ()),
1277 root 1.23 (@auto_rules ? (auto_rules => \@auto_rules) : ()),
1278 root 1.26 ($tool_help ? (tool_help => 1) : ()),
1279 root 1.8 );
1280    
1281 root 1.44 if (defined $print_url) {
1282 root 1.43 my %options = (
1283     %url,
1284     (defined $tls_cert ? (tls_cert => $tls_cert) : ()),
1285     (defined $tls_key ? (tls_key => $tls_key) : ()),
1286     (@tokens ? (tokens => \@tokens) : ()),
1287     );
1288 root 1.56 my $global = AnyEvent::MCP::Config::global_config;
1289 root 1.16 $options{path} //= $global->{section}{"instance $name"}{path}
1290     // $global->{section}{"program " . AnyEvent::MCP::Config::program_name ()}{path} // "/";
1291 root 1.8 if (($options{auth} || "") eq "none") {
1292     !exists $options{tokens} or die "Cannot combine auth => none with tokens\n";
1293     }
1294     my $selected_bind = defined $bind ? $bind : ($settings{bind} // $config{bind});
1295 root 1.57 my %http = ($global->http_settings ($name, $selected_bind, $options{default_port}), %options);
1296 root 1.44 my @schemes = defined $http{tls_cert} || defined $http{tls_key}
1297     ? ("https", "http") : ($http{url_scheme} || "http");
1298     my $managed = ($http{auth} || "") ne "none"
1299     && !exists $options{tokens}
1300     && !exists $global->{section}{"instance $name"}{tokens}
1301     && !exists $global->{section}{"program " . AnyEvent::MCP::Config::program_name ()}{tokens};
1302    
1303     if ($managed) {
1304 root 1.56 my $saved = (ref $global)->new (file => $global->{file})->load;
1305 root 1.44 $saved->{section}{global}{secret} eq $global->{section}{global}{secret}
1306     or die "MCP master secret is not persistently stored in $global->{file}\n";
1307     my $credentials = $global->user_credentials ($name);
1308     if (length $print_url) {
1309     @$credentials = grep $_->[0] eq $print_url, @$credentials;
1310     @$credentials or die "User '$print_url' is not enabled\n";
1311     }
1312     my $connect = $global->connect_user ($name);
1313     for my $entry (@$credentials) {
1314     my ($user, $principal, $token) = @$entry;
1315     for my $scheme (@schemes) {
1316     print $global->http_url ($name, $selected_bind, %options,
1317     tokens => [$token], url_scheme => $scheme),
1318     " # user $principal", ($user eq $connect ? " (connect)" : ""), "\n";
1319     }
1320     }
1321 root 1.43 } else {
1322 root 1.44 length $print_url
1323     and die "Cannot select a managed user with explicit tokens or auth=none\n";
1324     for my $scheme (@schemes) {
1325     print $global->http_url ($name, $selected_bind, %options, url_scheme => $scheme), "\n";
1326     }
1327 root 1.43 }
1328 root 1.57 $print_url_extra->(\%http) if $print_url_extra;
1329 root 1.8 exit;
1330     }
1331    
1332 root 1.33 \%server
1333 root 1.8 }
1334    
1335 root 1.20 sub _permission_options {
1336     my %kv = @_;
1337    
1338 root 1.53 my $rules = delete $kv{auto_rules};
1339     my $ask = delete $kv{ask};
1340     unless (exists $kv{permissions}) {
1341 root 1.20 require AnyEvent::MCP::Server::Permissions;
1342 root 1.53 $kv{permissions} = AnyEvent::MCP::Server::Permissions->new (
1343     name => AnyEvent::MCP::Config::program_name () . "-" . AnyEvent::MCP::instance_name ($kv{name}),
1344     );
1345     }
1346     if (my $permissions = $kv{permissions}) {
1347     $permissions->{ask} = $ask if defined $ask;
1348     $permissions->auto_rules ($rules, 1) if $rules && @$rules;
1349 root 1.20 }
1350    
1351     %kv
1352     }
1353    
1354 root 1.34 sub _server_options {
1355     my %kv = @_;
1356    
1357     if (exists $kv{server_name} || exists $kv{server_version}) {
1358     !exists $kv{server_info}
1359     or die "Cannot combine server_info with server_name/server_version\n";
1360    
1361     my $name = delete $kv{server_name};
1362     my $version = delete $kv{server_version};
1363     $kv{server_info} = {
1364     name => defined $name ? $name : "AnyEvent::MCP",
1365     version => defined $version ? "$version" : "$AnyEvent::MCP::VERSION",
1366     };
1367     }
1368    
1369     %kv
1370     }
1371    
1372 root 1.1 sub mcp_start {
1373 root 1.24 my %kv = (%config, @_);
1374 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1375 root 1.52 my $name = AnyEvent::MCP::instance_name ($kv{name});
1376 root 1.21 my $config_file = AnyEvent::MCP::Config::_file ();
1377 root 1.56 my $global = -e $config_file ? AnyEvent::MCP::Config::global_config : undef;
1378 root 1.21 my %ini = $global ? (
1379     %{$global->{section}{"program " . AnyEvent::MCP::Config::program_name ()} || {}},
1380     %{$global->{section}{"instance $name"} || {}},
1381     ) : ();
1382 root 1.24 if ($global) {
1383     my @rules = $global->auto_rules ($name);
1384     $kv{auto_rules} = [@{$kv{auto_rules} || []}, @rules] if @rules;
1385     }
1386 root 1.21 $kv{custom_instructions} = $ini{custom_instructions}
1387     if !exists $kv{custom_instructions} && exists $ini{custom_instructions};
1388 root 1.24 %kv = _permission_options (%kv);
1389 root 1.8 my $stdio = delete $kv{stdio};
1390 root 1.17 my $listen = delete $kv{listen};
1391     my $connect = delete $kv{connect};
1392     !($stdio && defined $connect) or die "Cannot combine --stdio and --connect\n";
1393     $stdio ||= !defined $connect && $ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio";
1394 root 1.1
1395     $kv{name} = $name;
1396    
1397 root 1.17 if (!$listen) {
1398     return mcp_connect (%kv, connect => $connect) if defined $connect;
1399     return mcp_stdio (%kv) if $stdio;
1400     }
1401 root 1.1
1402 root 1.56 my %http = ($global ||= AnyEvent::MCP::Config::global_config)->http_settings ($name, delete $kv{bind});
1403 root 1.44 delete @http{qw(tokens connect_tokens token_users)} if ($kv{auth} || "") eq "none";
1404 root 1.17 my %options = (path => "/", %http, %kv);
1405 root 1.44 if (exists $kv{tokens}) {
1406     $options{connect_tokens} = $kv{tokens};
1407     delete $options{token_users};
1408     }
1409 root 1.8
1410 root 1.17 if ($stdio || defined $connect) {
1411     require AnyEvent::MCP::Server::HTTP;
1412 root 1.34 my $listener = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], _server_options (%options));
1413 root 1.17 $listener->listen;
1414     my $result = defined $connect
1415     ? mcp_connect (%kv, connect => $connect)
1416     : mcp_stdio (%kv);
1417     $listener->close;
1418     return $result
1419     }
1420    
1421     mcp_http (%options)
1422     }
1423    
1424     sub mcp_connect {
1425     require AnyEvent::MCP::Server::Connect;
1426 root 1.1
1427 root 1.34 my %kv = _server_options (%config, @_);
1428 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1429     my $server = AnyEvent::MCP::Server::Connect->new (toolsets => [$toolset], _permission_options (%kv));
1430 root 1.17 $server->start;
1431     _run ($server, 1)
1432 root 1.1 }
1433    
1434     sub mcp_stdio {
1435     require AnyEvent::MCP::Server::JSONL;
1436    
1437 root 1.34 my %kv = _permission_options (_server_options (%config, @_));
1438 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1439 root 1.11 my $idle_exit = delete $kv{idle_exit};
1440 root 1.1 binmode STDIN;
1441     binmode STDOUT;
1442     my $selected = select STDOUT;
1443     $| = 1;
1444     select $selected;
1445    
1446 root 1.11 my $server = AnyEvent::MCP::Server::JSONL->new (toolsets => [$toolset], %kv);
1447 root 1.1 $server->accept (\*STDIN, \*STDOUT);
1448 root 1.11
1449     if ($idle_exit) {
1450     $idle_exit =~ /\A[0-9]+(?:\.[0-9]+)?\z/ && $idle_exit > 0
1451     or die "invalid idle_exit seconds\n";
1452     if ($ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio") {
1453     my ($timer, $check);
1454     $check = sub {
1455     return if $server->{idle_exit_requested};
1456     if (grep $_->{mcp_active}, values %{$server->{connections} || {}}) {
1457     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
1458     } else {
1459     $server->request_idle_exit;
1460     }
1461     };
1462     my $schedule = sub {
1463     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
1464     };
1465 root 1.54 my $on_idle_shutdown = $server->{on_idle_shutdown};
1466     $server->{on_idle_shutdown} = sub {
1467     my $allowed = !$on_idle_shutdown || $on_idle_shutdown->(@_);
1468     $schedule->() unless $allowed;
1469     $allowed
1470     };
1471 root 1.11 my $on_request = $server->{on_request};
1472     $server->{on_request} = sub {
1473     my ($server, $req) = @_;
1474 root 1.13
1475 root 1.11 if ($req->{method} ne "anyevent-mcp/shutdown") {
1476     $server->{idle_exit_requested} = 0;
1477     $schedule->();
1478     my $reply = $req->{on_reply};
1479     $req->{on_reply} = sub {
1480     $reply->(@_);
1481     $schedule->() unless $server->{_idle_shutting_down};
1482     };
1483     }
1484     $on_request->(@_) if $on_request;
1485     };
1486     my $on_close = $server->{on_close};
1487     $server->{on_close} = sub {
1488     undef $timer;
1489     $on_close->(@_) if $on_close;
1490     };
1491     $schedule->();
1492     }
1493     }
1494 root 1.1 _run $server
1495     }
1496    
1497     sub mcp_http {
1498     require AnyEvent::MCP::Server::HTTP;
1499    
1500 root 1.34 my %kv = _permission_options (_server_options (%config, @_));
1501 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1502     my $server = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], %kv);
1503 root 1.1 $server->listen;
1504     _run $server
1505     }
1506    
1507     sub _run {
1508 root 1.17 my ($server, $keep_alive) = @_;
1509 root 1.1
1510     my $cv = AnyEvent->condvar;
1511     my $stopped;
1512     my $stop = sub { $cv->send unless $stopped++ };
1513     my $on_close = $server->{on_close};
1514     $server->{on_close} = sub {
1515     $on_close->(@_) if $on_close;
1516     $stop->();
1517     };
1518    
1519 root 1.17 if (!$keep_alive && $server->isa ("AnyEvent::MCP::Server::JSONL")) {
1520 root 1.1 my $on_connection_close = $server->{on_connection_close};
1521     $server->{on_connection_close} = sub {
1522     $on_connection_close->(@_) if $on_connection_close;
1523     $stop->();
1524     };
1525     }
1526    
1527     my @signals = map {
1528     AnyEvent->signal (signal => $_, cb => sub { $server->close })
1529     } qw(INT TERM);
1530    
1531     my $ok = eval {
1532     $server->event (start =>);
1533     $cv->recv;
1534     1
1535     };
1536     my $error = $@;
1537     $server->close;
1538     die $error unless $ok;
1539     return
1540     }
1541    
1542     1