ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Simple.pm
Revision: 1.53
Committed: Sat Nov 7 03:54:03 2026 UTC (3 days, 5 hours ago) by root
Branch: MAIN
Changes since 1.52: +37 -5 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::MCP::Server::Simple - simple function interface for MCP servers
4    
5     =head1 SYNOPSIS
6    
7     use AnyEvent::MCP::Server::Simple;
8    
9     mcp_config
10 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     Delivery is best effort; the HTTP server in this distribution does not
482     deliver progress notifications. A client without progress display can
483     still receive the final result.
484    
485     =head2 When to use the object interfaces
486    
487     Simple owns one default Toolset and the blocking server lifetime.
488     That is useful for a small standalone program. If you already run an
489     event loop, want multiple transports or tool registries, or need custom
490     protocol dispatch, construct the objects directly.
491    
492     L<AnyEvent::MCP::Server::Toolset> registers reusable tools.
493     L<AnyEvent::MCP::Server> handles the protocol.
494     L<AnyEvent::MCP::Server::HTTP> and L<AnyEvent::MCP::Server::JSONL> manage
495     connections. L<AnyEvent::MCP::Server::Request> describes request fields,
496     replies, progress and cancellation. The same tool callback model applies
497     at both levels.
498    
499 root 1.1 =head1 FUNCTIONS
500    
501     =over
502    
503     =item mcp_config %config
504    
505     Adds server configuration. Repeated calls extend or replace previously declared
506     values. C<instructions> is treated as human-readable text and normalized.
507    
508 root 1.15 Server and transport options are passed to the underlying objects.
509     Common options include:
510    
511     =over
512    
513 root 1.34 =item name => $instance
514    
515     Local instance name used to select configuration and credentials. Independent
516     of the name reported to MCP clients.
517    
518     =item server_name => $name
519    
520     Advertised MCP server implementation name. Defaults to C<AnyEvent::MCP>.
521    
522     =item server_version => $version
523    
524     Advertised application version, not the MCP protocol revision. Accepts
525     an ordinary Perl version number; Simple stringifies it for MCP.
526     Defaults to the AnyEvent::MCP library version.
527    
528 root 1.15 =item server_info => { name => $name, version => $version }
529 root 1.1
530 root 1.34 Raw MCP server identity, passed through unchanged to the underlying
531     server. Cannot be combined with C<server_name> or C<server_version>.
532 root 1.1
533 root 1.15 =item instructions => $text
534 root 1.1
535 root 1.15 Human-readable guidance for the client, normalized when configured.
536 root 1.1
537 root 1.21 =item short_instructions => $text
538    
539     Optional condensed guidance for finding this server's tools. Used as the
540     advertised MCP instructions when C<auto_instructions> is enabled.
541    
542     =item auto_instructions => $boolean
543    
544     Enabled by default. Exposes full instructions through the generated
545     C<meta-instructions> tool while advertising the short instructions if set.
546 root 1.22 When short or custom instructions are provided, append a brief hint pointing
547     to the full tool description; otherwise advertise full instructions as before.
548 root 1.21 Disable to advertise full instructions and omit the generated tool.
549    
550     =item custom_instructions => $text
551    
552     Instance-specific purpose prepended to advertised instructions. Can also be
553     set through C<--custom-instructions> or C<custom_instructions> in the program
554     or instance section of the global INI configuration.
555    
556 root 1.15 =item on_request => $callback->($server, $req)
557    
558     Synchronous hook before each dispatch attempt. For example, refresh
559     database state here. This does not replace dispatch or receive a
560     completion callback, and permission retries can invoke it again.
561    
562     =item on_start => $callback->($server)
563    
564     Called after the transport is ready, before entering the event loop.
565     Use it for startup information or installing watchers. Stdout carries
566     protocol messages in stdio mode; print diagnostics to stderr.
567 root 1.1 The callback must not block.
568    
569 root 1.15 =item idle_exit => $seconds
570    
571     Optional positive idle time for spawned stdio servers. With a multibroker
572     advertising C<anyevent-mcp/multibroker> version 1, Simple requests shutdown
573     once no MCP requests have been active for that long. The broker can ignore
574     the request. HTTP servers, ordinary clients and servers without this
575     option are unaffected.
576    
577     =back
578    
579     See L<AnyEvent::MCP::Server> for further protocol options and events.
580 root 1.11
581 root 1.53 =item mcp_permissions $object
582    
583     Select the L<AnyEvent::MCP::Server::Permissions> object used by the Simple
584     starters. If never called (and no explicit C<permissions> option is supplied),
585     Simple creates one automatically, named from the program and selected instance.
586     C<mcp_permissions undef> explicitly disables the permission system:
587     tool callbacks calling C<require_permission> then fail instead of prompting.
588     Applications can still pass C<permissions> through C<mcp_config> or startup
589     arguments when needed.
590    
591     The common C<--ask> option overrides the object's prompting mode, and CLI
592     automatic permission rules precede configured rules and rules originally
593     supplied with the object. Those rules are applied when starting the server,
594     not when C<mcp_permissions> is called. No permission object is created when
595     it is explicitly disabled.
596    
597 root 1.1 =item mcp_tool $name, $description, $input_schema, $callback->($req, $args)
598    
599     Registers a tool named C<$name> with C<$description> and the JSON Schema
600     hash reference C<$input_schema>. C<$description> is normalized like
601     C<instructions>.
602    
603     C<$callback> receives an L<AnyEvent::MCP::Server::Request> object C<$req>
604     and its C<< $req->{args} >> hash reference as C<$args>, for convenience.
605    
606     Tools complete the request with C<< $req->reply ($result) >>, or use
607     C<< $req->result ($data) >> and C<< $req->error ($message) >> for convenience.
608     Request context is available directly in
609     the hash, for example C<< $req->{tool} >> and C<< $req->{server} >>.
610    
611 root 1.33 =item $server_options = mcp_getoptions ({ %settings }, @getopt_options)
612 root 1.8
613 root 1.47 Parse the switches documented in L</COMMON COMMAND-LINE OPTIONS> alongside
614 root 1.8 application-specific L<Getopt::Long> option specifications:
615    
616     my $verbose;
617 root 1.33 my $options = mcp_getoptions (
618 root 1.9 {
619     usage => "Usage: $0 [OPTION]... WORKDIR\n",
620     description => "Provide example tools through MCP.",
621     option_heading => "Example options:",
622     options => [
623     ["--verbose", "Show additional diagnostics"],
624     ],
625     examples => " $0 --stdio ./project",
626     path => "/",
627     },
628 root 1.8 "verbose!" => \$verbose,
629     );
630     @ARGV == 1 or die "workdir required\n";
631 root 1.33 mcp_start %$options;
632 root 1.8
633 root 1.15 The optional leading hash accepts these help settings:
634    
635     =over
636    
637     =item usage => $text
638    
639     Usage synopsis, including its trailing newline. A default synopsis is
640     generated when omitted.
641    
642     =item description => $text
643    
644     Introductory help paragraph.
645    
646     =item options => [[$label, $description]...]
647    
648     Application option labels and descriptions. A plain string among the
649     pairs starts a new section; a string at the beginning replaces the
650     default section heading.
651    
652     =item option_heading => $text
653    
654     Initial section heading, defaulting to C<Application options:>.
655    
656     =item examples => $text
657    
658     Preformatted examples.
659    
660 root 1.52 =item without => [OPTION...]
661    
662     Exclude selected common options by their long names, for example
663     C<< without => ["ask"] >>. The parser rejects excluded switches, and
664     C<--help> omits them. All common options are enabled by default.
665    
666 root 1.15 =back
667    
668 root 1.47 See L</COMMON COMMAND-LINE OPTIONS> for the full reference to shared
669     server switches. Application-specific Getopt::Long options are parsed alongside them.
670 root 1.35
671     These switches also appear in C<--help>; application options are passed
672     through to L<Getopt::Long>. Other leading hash settings (e.g. C<path>,
673     C<auth>, C<url_scheme>) go to C<mcp_start>. Parse positional C<@ARGV>
674     in the application; C<mcp_getoptions> deliberately does not require them
675     for help or URL discovery. Default activity logging includes asynchronous
676     tool failures and formats time, program, instance and PID. Environment
677     C<PERL_ANYEVENT_LOG> can select destinations and verbosity.
678 root 1.8
679 root 1.26 =item mcp_tool_help ($enabled, @toolsets)
680    
681     If C<$enabled> is true, print the definitions registered in the supplied
682     L<AnyEvent::MCP::Server::Toolset> objects and exit. Otherwise do nothing.
683     Call after registering tools but before starting listeners for executables
684     which manage their own toolsets. Only registered tools appear.
685    
686 root 1.27 =item mcp_log $message
687    
688     Emit operator-facing activity to the configured AnyEvent::Log destination,
689     bypassing its diagnostic verbosity filter. C<--quiet> suppresses this log.
690    
691 root 1.38 =item mcp_log_request ($server, $req, %extra)
692 root 1.27
693     C<on_request> callback to report tool invocations once across permission
694 root 1.30 retries, and log tool-error replies, including asynchronous ones. It logs
695     selected arguments rather than entire request or reply payloads. This includes
696 root 1.39 JSON-quoted previews of command arguments and VCS filenames (up to four,
697     80 characters each), and Vim C<ex> commands (up to 200 characters).
698     These can contain sensitive text. The Simple starters install it by default;
699 root 1.30 custom servers can use it too.
700 root 1.38 C<%extra> appends application-specific C<key=value> fields in key order.
701     Sanitize sensitive arguments before passing them.
702 root 1.27
703 root 1.1 =item mcp_start %config
704    
705 root 1.15 Starts and runs the usual server. When C<stdio> is true or
706 root 1.1 C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL> is C<stdio>, uses C<mcp_stdio>.
707 root 1.4 Otherwise starts HTTP without printing its URL. HTTP-capable executables
708     provide C<--print-url> to retrieve the URL explicitly.
709 root 1.1
710 root 1.3 The HTTP defaults are IPv4 loopback, a stable name-derived port in
711     16384..32767, and C</> as the endpoint path.
712 root 1.7 Without explicit C<tokens>, one server-specific token is derived from
713     the private global secret. The selected token authenticates either its URL
714     path or an
715 root 1.5 C<Authorization: Bearer TOKEN> header at the clean URL returned by
716 root 1.6 C<< $server->url (0) >>. The corresponding executable's C<--print-url>
717     option prints the first accepted token in its URL.
718 root 1.1
719 root 1.10 C<name> selects the C<[instance NAME]> configuration section and credential.
720 root 1.16 It defaults to C<default>.
721 root 1.43 Named settings C<bind>, C<path>, C<url_host>, C<url_scheme>, C<tokens>,
722     C<tls_cert> and C<tls_key> are loaded from the global configuration.
723     Explicit C<tokens> (an ordered,
724 root 1.7 nonempty array reference of URL-safe strings) replaces the configured list
725     and the derived default. Every token works in the URL and bearer header.
726 root 1.3 An explicit C<bind> overrides the configured bind address. Other arguments
727 root 1.7 override these settings.
728 root 1.1
729     The global secret is generated automatically when needed; see
730     L<AnyEvent::MCP/GLOBAL CONFIGURATION>. No configuration is loaded for stdio.
731 root 1.53 Unless explicitly disabled using C<mcp_permissions undef>, Simple constructs
732     a default permission engine. C<--ask> and automatic permission rules configure
733     it along with any application-supplied permission object.
734 root 1.4 HTTP startup neither prints to stdout nor changes its buffering.
735 root 1.7 C<mcp_start> does not parse C<@ARGV>; applications parse their own options.
736 root 1.8 The bundled executables use C<mcp_getoptions>, which handles shared
737     options and URL discovery before starting the event loop.
738 root 1.1
739 root 1.17 =item mcp_connect %config
740    
741     Run a reverse-connecting JSONL server using C<connect =E<gt>
742 root 1.47 "http[s]://host:port/token/alias">. HTTPS verifies the server certificate
743     and hostname. It sends the authenticated extension hello only after the
744     transport is established, then serves MCP over the socket. Refused TCP
745     connections retry every second for at most sixty attempts, and unexpected
746     established disconnects start a new retry period. Other TCP setup and TLS
747     handshake errors are reported and stop the server.
748     Calls are cancelled rather than replayed.
749 root 1.17 This connection has no spawned-child lifecycle authority.
750    
751 root 1.1 =item mcp_stdio %config
752    
753     Creates and runs a server over stdin/stdout. Arguments override the collected
754     configuration. Stdout buffering is disabled. On stdin EOF, pending replies
755     finish and stdout drains before
756     this function returns. Stream errors close the connection and cancel pending
757     requests.
758    
759     =item mcp_http %config
760    
761     Creates and runs an HTTP server. For example:
762    
763     mcp_http
764     host => "127.0.0.1",
765     port => 31740,
766 root 1.7 path => "/mcp/",
767     tokens => ["your-long-random-secret"];
768 root 1.1
769     Arguments override the collected configuration. Unlike C<mcp_start>, this
770 root 1.8 function does not derive or supply authentication. B<Always> pass C<tokens>,
771     or explicitly pass C<auth =E<gt> "none"> when a trusted embedding layer
772     enforces access. Omitting both is an error, not an unauthenticated default.
773     It does not print a URL; use
774 root 1.7 C<on_start> for application-specific startup output. Neither function
775     implements OAuth or Basic authentication. See
776     L<AnyEvent::MCP::Server::HTTP/AUTHENTICATION> for details.
777 root 1.1
778     All three starters return when the server is closed or on C<SIGINT> or
779     C<SIGTERM>. Signal shutdown closes connections and cancels outstanding
780     requests. The signal watchers belong to the blocking call.
781    
782     =back
783    
784 root 1.47 =head1 COMMON COMMAND-LINE OPTIONS
785    
786     C<mcp_getoptions> parses these options for executables using
787     L<AnyEvent::MCP::Server::Simple>. The executable's own OPTIONS section
788     documents any additional switches or differences in shared-option behavior.
789 root 1.52 An application can exclude specific common switches via C<without>.
790 root 1.47
791     =over
792    
793     =item --name NAME
794    
795 root 1.52 Select the instance (default C<default>). NAME starts with an ASCII letter,
796     digit or underscore; subsequent characters may also contain C<.>, C<@>,
797     C<+> and C<->.
798 root 1.47 Selects C<[instance NAME]> configuration, derived HTTP port and credentials;
799     does not change the advertised MCP implementation name.
800     Example: C<--name project>.
801    
802     =item --bind ADDRESS[:PORT]
803    
804     Override HTTP listening address and port. Defaults to C<127.0.0.1> and a
805     stable instance-derived port in 16384..32767. C<:31740> uses the loopback
806     address and port 31740; C<127.0.0.1:31740> specifies both.
807     Port zero requests an ephemeral port and is incompatible with
808     C<--print-url>. Ignored for stdio-only startup.
809    
810     =item --tls-cert FILE
811    
812     Enable opportunistic HTTPS using a PEM certificate chain. Without
813     C<--tls-key>, FILE must also contain the private key. The same listener
814     continues to accept plain HTTP.
815    
816     =item --tls-key FILE
817    
818     Private-key PEM file for C<--tls-cert>. If supplied alone, FILE must
819     contain both certificate chain and private key. Requires C<Net::SSLeay>.
820    
821     =item --token TOKEN
822    
823     Accept TOKEN for HTTP authentication. Repeat to accept multiple tokens.
824     Explicit tokens replace configured or master-secret-derived defaults.
825     Tokens work both as credential-bearing URL components and with the
826     C<Authorization: Bearer TOKEN> header.
827     Example: C<--token first --token second>.
828    
829 root 1.51 =item -s, --set KEY=VALUE
830    
831     Override one trusted configuration entry for the selected instance in this
832     process. Applied after C<[program NAME]> and C<[instance NAME]> defaults,
833     without changing the configuration file. Repeat to override multiple entries;
834     the last assignment to a key wins. Empty values suppress inherited settings,
835     and additional equals signs belong to the value. The supported keys depend
836     on the server (for example, C<executable.perl> and C<prepend-path> in
837     L<mcp-fs>). Example: C<-s executable.perl=/opt/perl/bin/perl>.
838    
839 root 1.47 =item --stdio
840    
841     Serve MCP JSONL on stdin/stdout rather than the default HTTP listener.
842     Applications with auxiliary listeners can keep those active. Cannot
843     combine explicitly with C<--connect>.
844    
845     =item --connect URL
846    
847     Reverse-connect to an upstream multibroker. For example:
848     C<http://host:port/TOKEN/alias> or C<https://host:port/TOKEN/alias>.
849     HTTPS requires successful certificate and hostname verification; it
850     never falls back to plaintext. Replaces the default MCP listener and
851     overrides implicit stdio selection. Cannot combine with explicit
852     C<--stdio>.
853    
854     =item --custom-instructions TEXT
855    
856     Override configured instance-purpose guidance describing when MCP
857     clients should use the server.
858     Example: C<--custom-instructions 'Use for build logs'>.
859    
860     =item --auto-allow TOOL[,ACTION[,CAPABILITY]]
861    
862     Add a process-local permission allow rule, repeatable. TOOL identifies
863     the permission-checked tool; ACTION and CAPABILITY narrow the match.
864     Missing fields and C<*> match anything.
865     Example: C<--auto-allow '*,read,workdir-read'>.
866     Rules run in command-line order before saved rules; they cannot grant
867     permissions to tools that do not check permissions.
868     See L<AnyEvent::MCP::Server::Permissions/auto_rules>.
869    
870     =item --auto-deny TOOL[,ACTION[,CAPABILITY]]
871    
872     Add a repeatable process-local deny rule with the same matching syntax
873     and evaluation order as C<--auto-allow>.
874     Example: C<--auto-deny '*,exec'>.
875    
876 root 1.52 =item --ask MODE
877    
878     Choose how permissions or other human input are requested: C<helper>,
879     C<tty>, C<yad>, C<mcp>, or C<auto>. C<none> immediately declines requests
880     that would otherwise require prompting; existing allow and deny rules
881     still apply. Multibroker replies to downstream forms with a standard decline.
882     The default depends on the application and its multibroker environment.
883    
884 root 1.47 =item --quiet
885    
886     Suppress routine tool activity and tool-failure logging. Warnings and
887     ordinary diagnostics remain; C<PERL_ANYEVENT_LOG> determines diagnostic
888     levels and destinations.
889    
890     =item --print-url[=USER]
891    
892     Print credential-bearing URLs and exit without starting a listener.
893     With managed users, prints each effective enabled user's URL, or only
894     the exact username USER. Comments identify scope-qualified principals
895     and the selected auxiliary connection user. With configured TLS, HTTPS
896     is printed before HTTP. Explicit tokens retain a single-token output
897     and cannot be combined with a user selector. Requires a predictable,
898     nonzero port and a persistent master secret for derived credentials.
899     Example: C<--name project --print-url=claude>.
900    
901     =item --print-urls
902    
903     Alias for C<--print-url>.
904    
905     =item --tool-help
906    
907     Print currently registered tool names, descriptions, argument types,
908     required fields and defaults; exit without starting the MCP listener.
909     Registered tools can depend on installed utilities and application setup.
910    
911 root 1.48 =item --help
912 root 1.47
913     Print shared and application-specific command-line help; exit without
914     starting a server. Invalid options instead report usage.
915    
916     =back
917    
918 root 1.1 =cut
919    
920     package AnyEvent::MCP::Server::Simple;
921    
922     use common::sense;
923 root 1.46
924     use Exporter ();
925     use JSON::XS ();
926     use Scalar::Util qw(refaddr);
927    
928 root 1.1 use AnyEvent ();
929 root 1.40 use AnyEvent::Log ();
930 root 1.1 use AnyEvent::MCP ();
931     use AnyEvent::MCP::Server::Toolset ();
932    
933     our @ISA = qw(Exporter);
934 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);
935 root 1.1
936 root 1.41 my %config = (
937     auto_instructions => 1,
938     on_request => \&mcp_log_request,
939 root 1.42 on_connection_error => sub {
940     my ($server, $handle, $error) = @_;
941     my $info = $handle->{mcp_peer}{client_info};
942     my $name = ref $info eq "HASH" ? $info->{name} : undef;
943     my @details = (
944     "id=" . sprintf ("%x", refaddr $handle),
945     defined $handle->{mcp_remote} ? "remote=$handle->{mcp_remote}" : (),
946     defined $name && !ref $name ? "client=" . JSON::XS::encode_json (substr $name, 0, 80) : (),
947     );
948 root 1.50 AE::log trace => "MCP connection " . join (" ", @details) . ": $error";
949 root 1.42 },
950 root 1.41 );
951 root 1.1 my $toolset = AnyEvent::MCP::Server::Toolset->new;
952 root 1.27 my $quiet;
953 root 1.1
954     sub _run;
955    
956 root 1.27 # operational messages go to the configured AnyEvent::Log destination,
957     # independently of the diagnostic verbosity filter.
958     sub mcp_log {
959     return if $quiet;
960     $AnyEvent::Log::LOG->log (info => @_);
961     }
962    
963     sub mcp_log_request {
964 root 1.38 my ($server, $req, %extra) = @_;
965 root 1.27
966     return unless $req->{method} eq "tools/call";
967     return if $req->{mcp_logged}++;
968    
969     my $args = $req->{args};
970 root 1.39 my $vcs = $req->{tool} =~ /\Avcs-(?:diff|status|log)\z/;
971 root 1.27 my @details;
972 root 1.39 for (qw(target workdir path cwd), $vcs ? "backend" : ()) {
973 root 1.27 push @details, "$_=" . (substr $args->{$_}, 0, 160)
974     if defined $args->{$_} && !ref $args->{$_};
975     }
976 root 1.36 push @details, substr +(join " ", @{$args->{ranges}}), 0, 180
977 root 1.27 if ref $args->{ranges} eq "ARRAY";
978 root 1.39 for my $key ("argv", $vcs ? "files" : ()) {
979     next unless ref $args->{$key} eq "ARRAY" && @{$args->{$key}};
980 root 1.31 require JSON::XS;
981 root 1.39 my @values = @{$args->{$key}};
982     push @details, "$key=" . join (" ", map {
983 root 1.31 JSON::XS::encode_json (substr $_, 0, 80) . (length $_ > 80 ? "..." : "")
984 root 1.39 } @values[0 .. ($#values < 3 ? $#values : 3)])
985     . (@values > 4 ? " ..." : "");
986 root 1.31 }
987     if ($req->{tool} eq "grep" && defined $args->{pattern} && !ref $args->{pattern}) {
988     require JSON::XS;
989     push @details, "pattern=" . JSON::XS::encode_json (substr $args->{pattern}, 0, 160)
990     . (length $args->{pattern} > 160 ? "..." : "");
991     }
992 root 1.29 push @details, "name=" . (substr $args->{name}, 0, 160)
993     if $req->{tool} eq "call-tool" && defined $args->{name} && !ref $args->{name};
994     if ($req->{tool} eq "ex" && defined $args->{command} && !ref $args->{command}) {
995     require JSON::XS;
996     push @details, "command=" . JSON::XS::encode_json (substr $args->{command}, 0, 200)
997     . (length $args->{command} > 200 ? "..." : "");
998     }
999 root 1.28 if ($req->{tool} eq "path-op") {
1000     push @details, map {
1001     ($_ eq "unlink" || $_ eq "rmdir")
1002     ? ($args->{$_} ? $_ : ())
1003     : (defined $args->{$_} && !ref $args->{$_}
1004     ? "$_=" . (substr $args->{$_}, 0, 80) : ())
1005     } qw(unlink rmdir mkpath mkdir symlink truncate chmod rename);
1006     }
1007 root 1.38 for my $key (sort keys %extra) {
1008     next unless defined $extra{$key} && !ref $extra{$key};
1009     push @details, "$key=" . (substr $extra{$key}, 0, 160);
1010     }
1011 root 1.27
1012     (my $message = join " ", $req->{tool}, @details) =~ s/[\r\n]/ /g;
1013     mcp_log $message;
1014    
1015     my $reply = $req->{on_reply};
1016     $req->{on_reply} = sub {
1017     my ($req, $result) = @_;
1018    
1019     if ($result->{isError}) {
1020     my $message = ref $result->{content} eq "ARRAY"
1021     && ref $result->{content}[0] eq "HASH"
1022     ? $result->{content}[0]{text} // "unknown error" : "unknown error";
1023     $message =~ s/\n.*//s;
1024     mcp_log "$req->{tool} failed: " . (substr $message, 0, 240);
1025     }
1026     $reply->(@_);
1027     };
1028     }
1029    
1030     sub _mcp_log_init {
1031     my ($name, $silent) = @_;
1032    
1033     $quiet = $silent;
1034     my $identity = AnyEvent::MCP::Config::program_name () . "/$name" . "[$$]";
1035    
1036     no warnings 'redefine';
1037     *AnyEvent::Log::default_format = sub ($$$$) {
1038     my ($when, $ctx, $level, $message) = @_;
1039 root 1.36 my $stamp = substr +(AnyEvent::Log::format_time $when), 8, 14;
1040 root 1.27 my $source = $ctx == $AnyEvent::Log::LOG ? "" : "$AnyEvent::Log::LEVEL2STR[$level] $ctx->[0]: ";
1041     join "", map "$stamp $identity $source$_\n", split /\n/, $message;
1042     };
1043     }
1044    
1045 root 1.1 sub mcp_config {
1046     my %kv = @_;
1047    
1048     $kv{instructions} = AnyEvent::MCP::_clean_text $kv{instructions}
1049     if exists $kv{instructions};
1050    
1051 root 1.14 %config = (%config, %kv);
1052 root 1.1 }
1053    
1054 root 1.53 sub mcp_permissions {
1055     @_ == 1 or die "mcp_permissions expects one argument (object or undef)\n";
1056     $config{permissions} = $_[0];
1057     }
1058    
1059 root 1.1 sub mcp_tool {
1060     $toolset->tool (@_)
1061     }
1062    
1063 root 1.26 # format a JSON Schema type for command-line tool help.
1064     sub _tool_type {
1065     my ($schema) = @_;
1066     return "any" unless ref $schema eq "HASH";
1067     for my $key (qw(anyOf oneOf)) {
1068     return join "|", map _tool_type ($_), @{$schema->{$key}}
1069     if ref $schema->{$key} eq "ARRAY";
1070     }
1071     my $type = $schema->{type} // "any";
1072     $type = join "|", @$type if ref $type eq "ARRAY";
1073     $type = "array[" . _tool_type ($schema->{items}) . "]" if $type eq "array";
1074     $type
1075     }
1076    
1077     sub mcp_tool_help {
1078     my ($enabled, @toolsets) = @_;
1079     return unless $enabled;
1080    
1081     my $json = AnyEvent::MCP::json_class ()->new->canonical;
1082     my @tools = map @{$_->list}, @toolsets;
1083     for my $tool (@tools) {
1084     print "$tool->{name}\n";
1085     (my $description = $tool->{description} // "") =~ s/\n/\n /g;
1086     print " $description\n" if length $description;
1087    
1088     my $schema = $tool->{inputSchema} || {};
1089     my $properties = $schema->{properties} || {};
1090     my %required = map { $_ => 1 } @{$schema->{required} || []};
1091     for my $name (sort keys %$properties) {
1092     my $property = $properties->{$name};
1093     my $type = _tool_type ($property);
1094     my $status = $required{$name} ? "required" : "optional";
1095     my @extras;
1096     push @extras, "default: " . $json->encode ($property->{default})
1097     if exists $property->{default};
1098     push @extras, "values: " . join (", ", @{$property->{enum}})
1099     if ref $property->{enum} eq "ARRAY";
1100     print " $name $type $status", (@extras ? " (" . join ("; ", @extras) . ")" : ""), "\n";
1101     if (length ($property->{description} // "")) {
1102     (my $detail = $property->{description}) =~ s/\n/\n /g;
1103     print " $detail\n";
1104     }
1105     }
1106     print "\n";
1107     }
1108     print "No tools registered.\n" unless @tools;
1109     exit;
1110     }
1111    
1112 root 1.9 sub _mcp_help_row {
1113     my ($option, $description) = @_;
1114 root 1.13
1115 root 1.9 my $start = " $option";
1116     my $margin = " " x 34;
1117 root 1.13 my $out = length $start < 34 ? "" : "$start\n";
1118     my $line = length $start < 34 ? $start . (substr $margin, length $start) : $margin;
1119 root 1.9
1120     for my $word (split /\s+/, $description) {
1121 root 1.13 if (length $line > 34 && (length $line) + 1 + length $word > 79) {
1122 root 1.9 $out .= "$line\n";
1123     $line = $margin;
1124     }
1125 root 1.13
1126     $line .= " " if length $line > 34;
1127 root 1.9 $line .= $word;
1128     }
1129    
1130     "$out$line\n"
1131     }
1132    
1133     sub _mcp_help {
1134 root 1.52 my ($usage, $description, $options, $option_heading, $examples, $without) = @_;
1135 root 1.13
1136 root 1.9 $usage =~ s/\n*\z/\n/;
1137     my $help = $usage;
1138     $help .= "\n$description\n" if defined $description && length $description;
1139    
1140     $help .= "\nCommon options:\n";
1141     my @common = (
1142 root 1.25 ["--name NAME", "Select named configuration (instance defaults to 'default')"],
1143 root 1.9 ["--bind ADDRESS[:PORT]", "Override the HTTP listen address or port"],
1144 root 1.43 ["--tls-cert FILE", "PEM certificate (and optionally key) for HTTPS"],
1145     ["--tls-key FILE", "PEM private key (or combined key/certificate PEM)"],
1146 root 1.9 ["--token TOKEN", "Set an accepted token (repeatable; replaces defaults)"],
1147 root 1.51 ["-s, --set KEY=VALUE", "Override a trusted instance setting (repeatable)"],
1148 root 1.9 ["--stdio", "Serve MCP on stdin/stdout; other listeners may remain"],
1149 root 1.43 ["--connect URL", "Connect to a multibroker at http[s]://host:port/token/alias"],
1150 root 1.21 ["--custom-instructions TEXT", "Describe when this server instance should be used"],
1151 root 1.23 ["--auto-allow TOOL[,ACTION[,CAPABILITY]]", "Add an ordered automatic allow rule (repeatable)"],
1152     ["--auto-deny TOOL[,ACTION[,CAPABILITY]]", "Add an ordered automatic deny rule (repeatable)"],
1153 root 1.52 ["--ask MODE", "Prompt via helper, tty, yad, mcp, auto; none declines without prompting"],
1154 root 1.27 ["--quiet", "Suppress routine tool activity and tool-failure logging"],
1155 root 1.44 ["--print-url[=USER] (--print-urls)", "Print enabled user URLs, or one exact user"],
1156 root 1.26 ["--tool-help", "Print registered tool descriptions and arguments, then exit"],
1157 root 1.48 ["--help", "Display this help and exit"],
1158 root 1.9 );
1159 root 1.52 $help .= _mcp_help_row (@$_) for grep {
1160     my ($name) = $_->[0] =~ /--([a-z-]+)/;
1161     !$without->{$name}
1162     } @common;
1163 root 1.9
1164     if ($options && @$options) {
1165 root 1.12 $help .= "\n" . ($option_heading // "Application options:") . "\n"
1166     if ref $options->[0];
1167     for my $option (@$options) {
1168     if (ref $option) {
1169     $help .= _mcp_help_row (@$option);
1170     } else {
1171     $help .= "\n" unless $help =~ /\n\n\z/;
1172     $help .= "$option\n";
1173     }
1174     }
1175 root 1.9 }
1176     $help .= "\nExamples:\n$examples\n"
1177     if defined $examples && length $examples;
1178 root 1.45 $help .= "\nNamed users and URLs:\n"
1179     . " anyevent-mcp create-user chatgpt\n"
1180     . " $0 --print-urls # list URLs for all enabled users\n"
1181     . " $0 --print-url=chatgpt # select one user\n"
1182     . "A URL marked (connect) is the one used for browser, Vim, or reverse connections.\n"
1183     . "Users inherit from global, program, then instance; server restart is needed\n"
1184     . "after changing user definitions.\n";
1185 root 1.9
1186     $help
1187     }
1188    
1189 root 1.8 sub mcp_getoptions {
1190     require Getopt::Long;
1191     require AnyEvent::MCP::Config;
1192    
1193 root 1.13 my %settings = %{ref $_[0] eq "HASH" ? shift : {}};
1194 root 1.8 my $usage = delete $settings{usage}
1195 root 1.9 // "Usage: $0 [OPTION]... [ARG]...\n";
1196 root 1.13 my @help = delete @settings{qw(description options option_heading examples)};
1197 root 1.52 my %without = map { $_ => 1 } @{delete $settings{without} || []};
1198 root 1.8 my %url = (%config, %settings);
1199     delete @url{qw(name bind stdio)};
1200 root 1.23 my @auto_rules;
1201 root 1.51 my %set;
1202 root 1.8
1203 root 1.52 my @common = (
1204 root 1.13 AnyEvent::MCP::Config::http_getopt (\my $name, \my $bind, \my $print_url, \my @tokens),
1205 root 1.43 "tls-cert=s" => \my $tls_cert,
1206     "tls-key=s" => \my $tls_key,
1207 root 1.13 "stdio" => \my $stdio,
1208 root 1.17 "connect=s" => \my $connect,
1209 root 1.21 "custom-instructions=s" => \my $custom_instructions,
1210 root 1.23 "auto-allow=s" => sub { push @auto_rules, ["allow", $_[1]] },
1211     "auto-deny=s" => sub { push @auto_rules, ["deny", $_[1]] },
1212 root 1.51 "set|s=s" => sub {
1213     my ($key, $value) = $_[1] =~ /\A([^\s=]+)=(.*)\z/;
1214     defined $value or die "Invalid --set KEY=VALUE\n";
1215     $set{$key} = $value;
1216     },
1217 root 1.26 "tool-help" => \my $tool_help,
1218 root 1.27 "quiet" => \my $silent,
1219 root 1.52 "ask=s" => \my $ask,
1220 root 1.48 "help" => \my $help,
1221 root 1.52 );
1222     my @enabled;
1223     my %known;
1224     while (@common) {
1225     my ($option, $target) = splice @common, 0, 2;
1226     my ($name) = $option =~ /\A([a-z-]+)/;
1227     $known{$name} = 1;
1228     push @enabled, $option => $target unless $without{$name};
1229     }
1230     die "Unknown common option: " . join (", ", sort grep !$known{$_}, keys %without) . "\n"
1231     if grep !$known{$_}, keys %without;
1232     Getopt::Long::GetOptions (
1233     @enabled,
1234 root 1.8 @_,
1235     ) or die $usage;
1236 root 1.52 die "--ask must be helper, tty, yad, mcp, auto, or none\n"
1237     if defined $ask && $ask !~ /\A(?:helper|tty|yad|mcp|auto|none)\z/;
1238 root 1.8
1239     if ($help) {
1240 root 1.52 print _mcp_help $usage, @help, \%without;
1241 root 1.8 exit;
1242     }
1243    
1244 root 1.52 $name = AnyEvent::MCP::instance_name ($name // $settings{name} // $config{name});
1245 root 1.51 if (%set) {
1246     my $instance = AnyEvent::MCP::Config->global->{section}{"instance $name"} ||= {};
1247     %$instance = (%$instance, %set);
1248     }
1249    
1250 root 1.27 _mcp_log_init ($name, $silent);
1251 root 1.8 my %server = (
1252     %settings,
1253     name => $name,
1254     (defined $bind ? (bind => $bind) : ()),
1255 root 1.43 (defined $tls_cert ? (tls_cert => $tls_cert) : ()),
1256     (defined $tls_key ? (tls_key => $tls_key) : ()),
1257 root 1.8 (@tokens ? (tokens => \@tokens) : ()),
1258     ($stdio ? (stdio => 1) : ()),
1259 root 1.17 (defined $connect ? (connect => $connect) : ()),
1260 root 1.52 (defined $ask ? (ask => $ask) : ()),
1261 root 1.21 (defined $custom_instructions ? (custom_instructions => $custom_instructions) : ()),
1262 root 1.23 (@auto_rules ? (auto_rules => \@auto_rules) : ()),
1263 root 1.26 ($tool_help ? (tool_help => 1) : ()),
1264 root 1.8 );
1265    
1266 root 1.44 if (defined $print_url) {
1267 root 1.43 my %options = (
1268     %url,
1269     (defined $tls_cert ? (tls_cert => $tls_cert) : ()),
1270     (defined $tls_key ? (tls_key => $tls_key) : ()),
1271     (@tokens ? (tokens => \@tokens) : ()),
1272     );
1273 root 1.8 my $global = AnyEvent::MCP::Config->global;
1274 root 1.16 $options{path} //= $global->{section}{"instance $name"}{path}
1275     // $global->{section}{"program " . AnyEvent::MCP::Config::program_name ()}{path} // "/";
1276 root 1.8 if (($options{auth} || "") eq "none") {
1277     !exists $options{tokens} or die "Cannot combine auth => none with tokens\n";
1278     }
1279     my $selected_bind = defined $bind ? $bind : ($settings{bind} // $config{bind});
1280 root 1.43 my %http = ($global->http_settings ($name, $selected_bind), %options);
1281 root 1.44 my @schemes = defined $http{tls_cert} || defined $http{tls_key}
1282     ? ("https", "http") : ($http{url_scheme} || "http");
1283     my $managed = ($http{auth} || "") ne "none"
1284     && !exists $options{tokens}
1285     && !exists $global->{section}{"instance $name"}{tokens}
1286     && !exists $global->{section}{"program " . AnyEvent::MCP::Config::program_name ()}{tokens};
1287    
1288     if ($managed) {
1289     my $saved = (ref $global)->new (file => $global->{file});
1290     $saved->{section}{global}{secret} eq $global->{section}{global}{secret}
1291     or die "MCP master secret is not persistently stored in $global->{file}\n";
1292     my $credentials = $global->user_credentials ($name);
1293     if (length $print_url) {
1294     @$credentials = grep $_->[0] eq $print_url, @$credentials;
1295     @$credentials or die "User '$print_url' is not enabled\n";
1296     }
1297     my $connect = $global->connect_user ($name);
1298     for my $entry (@$credentials) {
1299     my ($user, $principal, $token) = @$entry;
1300     for my $scheme (@schemes) {
1301     print $global->http_url ($name, $selected_bind, %options,
1302     tokens => [$token], url_scheme => $scheme),
1303     " # user $principal", ($user eq $connect ? " (connect)" : ""), "\n";
1304     }
1305     }
1306 root 1.43 } else {
1307 root 1.44 length $print_url
1308     and die "Cannot select a managed user with explicit tokens or auth=none\n";
1309     for my $scheme (@schemes) {
1310     print $global->http_url ($name, $selected_bind, %options, url_scheme => $scheme), "\n";
1311     }
1312 root 1.43 }
1313 root 1.8 exit;
1314     }
1315    
1316 root 1.33 \%server
1317 root 1.8 }
1318    
1319 root 1.20 sub _permission_options {
1320     my %kv = @_;
1321    
1322 root 1.53 my $rules = delete $kv{auto_rules};
1323     my $ask = delete $kv{ask};
1324     unless (exists $kv{permissions}) {
1325 root 1.20 require AnyEvent::MCP::Server::Permissions;
1326 root 1.53 require AnyEvent::MCP::Config;
1327     $kv{permissions} = AnyEvent::MCP::Server::Permissions->new (
1328     name => AnyEvent::MCP::Config::program_name () . "-" . AnyEvent::MCP::instance_name ($kv{name}),
1329     );
1330     }
1331     if (my $permissions = $kv{permissions}) {
1332     $permissions->{ask} = $ask if defined $ask;
1333     $permissions->auto_rules ($rules, 1) if $rules && @$rules;
1334 root 1.20 }
1335    
1336     %kv
1337     }
1338    
1339 root 1.34 sub _server_options {
1340     my %kv = @_;
1341    
1342     if (exists $kv{server_name} || exists $kv{server_version}) {
1343     !exists $kv{server_info}
1344     or die "Cannot combine server_info with server_name/server_version\n";
1345    
1346     my $name = delete $kv{server_name};
1347     my $version = delete $kv{server_version};
1348     $kv{server_info} = {
1349     name => defined $name ? $name : "AnyEvent::MCP",
1350     version => defined $version ? "$version" : "$AnyEvent::MCP::VERSION",
1351     };
1352     }
1353    
1354     %kv
1355     }
1356    
1357 root 1.1 sub mcp_start {
1358 root 1.3 require AnyEvent::MCP::Config;
1359 root 1.1
1360 root 1.24 my %kv = (%config, @_);
1361 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1362 root 1.52 my $name = AnyEvent::MCP::instance_name ($kv{name});
1363 root 1.21 my $config_file = AnyEvent::MCP::Config::_file ();
1364     my $global = -e $config_file ? AnyEvent::MCP::Config->global : undef;
1365     my %ini = $global ? (
1366     %{$global->{section}{"program " . AnyEvent::MCP::Config::program_name ()} || {}},
1367     %{$global->{section}{"instance $name"} || {}},
1368     ) : ();
1369 root 1.24 if ($global) {
1370     my @rules = $global->auto_rules ($name);
1371     $kv{auto_rules} = [@{$kv{auto_rules} || []}, @rules] if @rules;
1372     }
1373 root 1.21 $kv{custom_instructions} = $ini{custom_instructions}
1374     if !exists $kv{custom_instructions} && exists $ini{custom_instructions};
1375 root 1.24 %kv = _permission_options (%kv);
1376 root 1.8 my $stdio = delete $kv{stdio};
1377 root 1.17 my $listen = delete $kv{listen};
1378     my $connect = delete $kv{connect};
1379     !($stdio && defined $connect) or die "Cannot combine --stdio and --connect\n";
1380     $stdio ||= !defined $connect && $ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio";
1381 root 1.1
1382     $kv{name} = $name;
1383    
1384 root 1.17 if (!$listen) {
1385     return mcp_connect (%kv, connect => $connect) if defined $connect;
1386     return mcp_stdio (%kv) if $stdio;
1387     }
1388 root 1.1
1389 root 1.21 my %http = ($global ||= AnyEvent::MCP::Config->global)->http_settings ($name, delete $kv{bind});
1390 root 1.44 delete @http{qw(tokens connect_tokens token_users)} if ($kv{auth} || "") eq "none";
1391 root 1.17 my %options = (path => "/", %http, %kv);
1392 root 1.44 if (exists $kv{tokens}) {
1393     $options{connect_tokens} = $kv{tokens};
1394     delete $options{token_users};
1395     }
1396 root 1.8
1397 root 1.17 if ($stdio || defined $connect) {
1398     require AnyEvent::MCP::Server::HTTP;
1399 root 1.34 my $listener = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], _server_options (%options));
1400 root 1.17 $listener->listen;
1401     my $result = defined $connect
1402     ? mcp_connect (%kv, connect => $connect)
1403     : mcp_stdio (%kv);
1404     $listener->close;
1405     return $result
1406     }
1407    
1408     mcp_http (%options)
1409     }
1410    
1411     sub mcp_connect {
1412     require AnyEvent::MCP::Server::Connect;
1413 root 1.1
1414 root 1.34 my %kv = _server_options (%config, @_);
1415 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1416     my $server = AnyEvent::MCP::Server::Connect->new (toolsets => [$toolset], _permission_options (%kv));
1417 root 1.17 $server->start;
1418     _run ($server, 1)
1419 root 1.1 }
1420    
1421     sub mcp_stdio {
1422     require AnyEvent::MCP::Server::JSONL;
1423    
1424 root 1.34 my %kv = _permission_options (_server_options (%config, @_));
1425 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1426 root 1.11 my $idle_exit = delete $kv{idle_exit};
1427 root 1.1 binmode STDIN;
1428     binmode STDOUT;
1429     my $selected = select STDOUT;
1430     $| = 1;
1431     select $selected;
1432    
1433 root 1.11 my $server = AnyEvent::MCP::Server::JSONL->new (toolsets => [$toolset], %kv);
1434 root 1.1 $server->accept (\*STDIN, \*STDOUT);
1435 root 1.11
1436     if ($idle_exit) {
1437     $idle_exit =~ /\A[0-9]+(?:\.[0-9]+)?\z/ && $idle_exit > 0
1438     or die "invalid idle_exit seconds\n";
1439     if ($ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio") {
1440     my ($timer, $check);
1441     $check = sub {
1442     return if $server->{idle_exit_requested};
1443     if (grep $_->{mcp_active}, values %{$server->{connections} || {}}) {
1444     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
1445     } else {
1446     $server->request_idle_exit;
1447     }
1448     };
1449     my $schedule = sub {
1450     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
1451     };
1452     my $on_request = $server->{on_request};
1453     $server->{on_request} = sub {
1454     my ($server, $req) = @_;
1455 root 1.13
1456 root 1.11 if ($req->{method} ne "anyevent-mcp/shutdown") {
1457     $server->{idle_exit_requested} = 0;
1458     $schedule->();
1459     my $reply = $req->{on_reply};
1460     $req->{on_reply} = sub {
1461     $reply->(@_);
1462     $schedule->() unless $server->{_idle_shutting_down};
1463     };
1464     }
1465     $on_request->(@_) if $on_request;
1466     };
1467     my $on_close = $server->{on_close};
1468     $server->{on_close} = sub {
1469     undef $timer;
1470     $on_close->(@_) if $on_close;
1471     };
1472     $schedule->();
1473     }
1474     }
1475 root 1.1 _run $server
1476     }
1477    
1478     sub mcp_http {
1479     require AnyEvent::MCP::Server::HTTP;
1480    
1481 root 1.34 my %kv = _permission_options (_server_options (%config, @_));
1482 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1483     my $server = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], %kv);
1484 root 1.1 $server->listen;
1485     _run $server
1486     }
1487    
1488     sub _run {
1489 root 1.17 my ($server, $keep_alive) = @_;
1490 root 1.1
1491     my $cv = AnyEvent->condvar;
1492     my $stopped;
1493     my $stop = sub { $cv->send unless $stopped++ };
1494     my $on_close = $server->{on_close};
1495     $server->{on_close} = sub {
1496     $on_close->(@_) if $on_close;
1497     $stop->();
1498     };
1499    
1500 root 1.17 if (!$keep_alive && $server->isa ("AnyEvent::MCP::Server::JSONL")) {
1501 root 1.1 my $on_connection_close = $server->{on_connection_close};
1502     $server->{on_connection_close} = sub {
1503     $on_connection_close->(@_) if $on_connection_close;
1504     $stop->();
1505     };
1506     }
1507    
1508     my @signals = map {
1509     AnyEvent->signal (signal => $_, cb => sub { $server->close })
1510     } qw(INT TERM);
1511    
1512     my $ok = eval {
1513     $server->event (start =>);
1514     $cv->recv;
1515     1
1516     };
1517     my $error = $@;
1518     $server->close;
1519     die $error unless $ok;
1520     return
1521     }
1522    
1523     1