ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/Simple.pm
Revision: 1.42
Committed: Sun Nov 1 22:07:44 2026 UTC (9 days, 15 hours ago) by root
Branch: MAIN
Changes since 1.41: +13 -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     mcp_config permissions => AnyEvent::MCP::Server::Permissions->new (
304     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.1 =item mcp_tool $name, $description, $input_schema, $callback->($req, $args)
582    
583     Registers a tool named C<$name> with C<$description> and the JSON Schema
584     hash reference C<$input_schema>. C<$description> is normalized like
585     C<instructions>.
586    
587     C<$callback> receives an L<AnyEvent::MCP::Server::Request> object C<$req>
588     and its C<< $req->{args} >> hash reference as C<$args>, for convenience.
589    
590     Tools complete the request with C<< $req->reply ($result) >>, or use
591     C<< $req->result ($data) >> and C<< $req->error ($message) >> for convenience.
592     Request context is available directly in
593     the hash, for example C<< $req->{tool} >> and C<< $req->{server} >>.
594    
595 root 1.33 =item $server_options = mcp_getoptions ({ %settings }, @getopt_options)
596 root 1.8
597     Parse standard server options C<--name>, C<--bind>, repeatable
598 root 1.23 C<--token>, C<--stdio>, C<--custom-instructions>, repeatable
599 root 1.27 C<--auto-allow> and C<--auto-deny>, C<--quiet>, C<--print-url>, C<--tool-help> and C<--help> alongside any
600 root 1.8 application-specific L<Getopt::Long> option specifications:
601    
602     my $verbose;
603 root 1.33 my $options = mcp_getoptions (
604 root 1.9 {
605     usage => "Usage: $0 [OPTION]... WORKDIR\n",
606     description => "Provide example tools through MCP.",
607     option_heading => "Example options:",
608     options => [
609     ["--verbose", "Show additional diagnostics"],
610     ],
611     examples => " $0 --stdio ./project",
612     path => "/",
613     },
614 root 1.8 "verbose!" => \$verbose,
615     );
616     @ARGV == 1 or die "workdir required\n";
617 root 1.33 mcp_start %$options;
618 root 1.8
619 root 1.15 The optional leading hash accepts these help settings:
620    
621     =over
622    
623     =item usage => $text
624    
625     Usage synopsis, including its trailing newline. A default synopsis is
626     generated when omitted.
627    
628     =item description => $text
629    
630     Introductory help paragraph.
631    
632     =item options => [[$label, $description]...]
633    
634     Application option labels and descriptions. A plain string among the
635     pairs starts a new section; a string at the beginning replaces the
636     default section heading.
637    
638     =item option_heading => $text
639    
640     Initial section heading, defaulting to C<Application options:>.
641    
642     =item examples => $text
643    
644     Preformatted examples.
645    
646     =back
647    
648 root 1.35 Common command-line options parsed by C<mcp_getoptions>:
649 root 1.25
650 root 1.35 =over
651    
652     =item --name NAME
653    
654     Select local instance (default C<default>). NAME starts with letter or
655     underscore, followed by letters, digits, underscores or hyphens. Selects
656     C<[instance NAME]> configuration, derived HTTP port and credentials; does
657     not change advertised MCP implementation name. Example: C<--name project>.
658    
659     =item --bind ADDRESS[:PORT]
660    
661     Override HTTP listen address/port. Default: C<127.0.0.1> with
662     stable instance-derived port (16384..32767). C<:31740> means loopback port
663     31740; C<127.0.0.1:31740> chooses both. Port C<0> requests an ephemeral port
664     and cannot be used with C<--print-url>. Ignored for stdio-only startup.
665    
666     =item --token TOKEN
667    
668     Accept TOKEN for HTTP authentication; repeat for multiple accepted tokens.
669     Explicit tokens replace configured or secret-derived defaults. Tokens work
670     in authenticated URL paths and C<Authorization: Bearer TOKEN> headers.
671     Example: C<--token first --token second>.
672    
673     =item --stdio
674    
675     Serve MCP JSONL over stdin/stdout. For ordinary C<mcp_start>, replaces
676     default HTTP listener; programs with additional listeners may keep them.
677     Cannot combine explicitly with C<--connect>.
678    
679     =item --connect URL
680    
681     Connect server to upstream multibroker using reverse connection URL,
682     e.g. C<http://host:port/TOKEN/alias>. Replaces default MCP listener and
683     overrides implicit stdio selection from environment. Cannot combine with
684     explicit C<--stdio>.
685    
686     =item --custom-instructions TEXT
687    
688     Override configured instance-purpose instructions describing when clients
689     should use server. Example: C<--custom-instructions 'Use for build logs'>.
690    
691     =item --auto-allow TOOL[,ACTION[,CAPABILITY]]
692    
693     Add process-local permission allow rule; repeatable. TOOL identifies
694     permission-checked tool; ACTION and CAPABILITY narrow rule. Omitted fields
695     and C<*> match anything. Example: C<--auto-allow '*,read,workdir-read'>.
696     Rules run in command-line order before saved rules; they never authorize
697     tools that bypass permission checks. See
698     L<AnyEvent::MCP::Server::Permissions/auto_rules>.
699    
700     =item --auto-deny TOOL[,ACTION[,CAPABILITY]]
701    
702     Add process-local deny rule with same pattern syntax as C<--auto-allow>.
703     Repeatable; evaluated in command-line order before saved rules.
704     Example: C<--auto-deny '*,exec'>. Rules affect permission-checked tools,
705     not calls with no permission check.
706    
707     =item --quiet
708    
709     Suppress routine tool activity and tool-failure logging. Does not
710     suppress warnings or normal diagnostics; C<PERL_ANYEVENT_LOG> still controls
711     diagnostic levels and destinations.
712    
713     =item --print-url
714    
715     Print first accepted token in authenticated HTTP URL, then exit without
716     starting listener. Uses same name, bind, token and endpoint configuration
717     as startup. Derived tokens require persistent master secret; port C<0>
718     cannot be predicted. Example: C<--name project --print-url>.
719    
720     =item --tool-help
721    
722     Print registered tool names, descriptions, argument types, required fields
723     and defaults; exit without starting MCP listener. Registration may depend on
724     available utilities and application setup. Invoke after registering tools.
725    
726     =item -h, --help
727    
728     Print common and application-specific command-line help; exit without
729     starting server. Invalid options instead report usage.
730    
731     =back
732    
733     These switches also appear in C<--help>; application options are passed
734     through to L<Getopt::Long>. Other leading hash settings (e.g. C<path>,
735     C<auth>, C<url_scheme>) go to C<mcp_start>. Parse positional C<@ARGV>
736     in the application; C<mcp_getoptions> deliberately does not require them
737     for help or URL discovery. Default activity logging includes asynchronous
738     tool failures and formats time, program, instance and PID. Environment
739     C<PERL_ANYEVENT_LOG> can select destinations and verbosity.
740 root 1.8
741 root 1.26 =item mcp_tool_help ($enabled, @toolsets)
742    
743     If C<$enabled> is true, print the definitions registered in the supplied
744     L<AnyEvent::MCP::Server::Toolset> objects and exit. Otherwise do nothing.
745     Call after registering tools but before starting listeners for executables
746     which manage their own toolsets. Only registered tools appear.
747    
748 root 1.27 =item mcp_log $message
749    
750     Emit operator-facing activity to the configured AnyEvent::Log destination,
751     bypassing its diagnostic verbosity filter. C<--quiet> suppresses this log.
752    
753 root 1.38 =item mcp_log_request ($server, $req, %extra)
754 root 1.27
755     C<on_request> callback to report tool invocations once across permission
756 root 1.30 retries, and log tool-error replies, including asynchronous ones. It logs
757     selected arguments rather than entire request or reply payloads. This includes
758 root 1.39 JSON-quoted previews of command arguments and VCS filenames (up to four,
759     80 characters each), and Vim C<ex> commands (up to 200 characters).
760     These can contain sensitive text. The Simple starters install it by default;
761 root 1.30 custom servers can use it too.
762 root 1.38 C<%extra> appends application-specific C<key=value> fields in key order.
763     Sanitize sensitive arguments before passing them.
764 root 1.27
765 root 1.1 =item mcp_start %config
766    
767 root 1.15 Starts and runs the usual server. When C<stdio> is true or
768 root 1.1 C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL> is C<stdio>, uses C<mcp_stdio>.
769 root 1.4 Otherwise starts HTTP without printing its URL. HTTP-capable executables
770     provide C<--print-url> to retrieve the URL explicitly.
771 root 1.1
772 root 1.3 The HTTP defaults are IPv4 loopback, a stable name-derived port in
773     16384..32767, and C</> as the endpoint path.
774 root 1.7 Without explicit C<tokens>, one server-specific token is derived from
775     the private global secret. The selected token authenticates either its URL
776     path or an
777 root 1.5 C<Authorization: Bearer TOKEN> header at the clean URL returned by
778 root 1.6 C<< $server->url (0) >>. The corresponding executable's C<--print-url>
779     option prints the first accepted token in its URL.
780 root 1.1
781 root 1.10 C<name> selects the C<[instance NAME]> configuration section and credential.
782 root 1.16 It defaults to C<default>.
783 root 1.5 Named settings C<bind>, C<path>, C<url_host>, C<url_scheme> and C<tokens>
784 root 1.7 are loaded from the global configuration. Explicit C<tokens> (an ordered,
785     nonempty array reference of URL-safe strings) replaces the configured list
786     and the derived default. Every token works in the URL and bearer header.
787 root 1.3 An explicit C<bind> overrides the configured bind address. Other arguments
788 root 1.7 override these settings.
789 root 1.1
790     The global secret is generated automatically when needed; see
791     L<AnyEvent::MCP/GLOBAL CONFIGURATION>. No configuration is loaded for stdio.
792 root 1.4 HTTP startup neither prints to stdout nor changes its buffering.
793 root 1.7 C<mcp_start> does not parse C<@ARGV>; applications parse their own options.
794 root 1.8 The bundled executables use C<mcp_getoptions>, which handles shared
795     options and URL discovery before starting the event loop.
796 root 1.1
797 root 1.17 =item mcp_connect %config
798    
799     Run a reverse-connecting JSONL server using C<connect =E<gt>
800     "http://host:port/token/alias">. It sends the authenticated extension hello
801     then serves ordinary MCP over the socket. Retry every second for up to sixty
802     attempts after a disconnect; calls are cancelled rather than replayed.
803     This connection has no spawned-child lifecycle authority.
804    
805 root 1.1 =item mcp_stdio %config
806    
807     Creates and runs a server over stdin/stdout. Arguments override the collected
808     configuration. Stdout buffering is disabled. On stdin EOF, pending replies
809     finish and stdout drains before
810     this function returns. Stream errors close the connection and cancel pending
811     requests.
812    
813     =item mcp_http %config
814    
815     Creates and runs an HTTP server. For example:
816    
817     mcp_http
818     host => "127.0.0.1",
819     port => 31740,
820 root 1.7 path => "/mcp/",
821     tokens => ["your-long-random-secret"];
822 root 1.1
823     Arguments override the collected configuration. Unlike C<mcp_start>, this
824 root 1.8 function does not derive or supply authentication. B<Always> pass C<tokens>,
825     or explicitly pass C<auth =E<gt> "none"> when a trusted embedding layer
826     enforces access. Omitting both is an error, not an unauthenticated default.
827     It does not print a URL; use
828 root 1.7 C<on_start> for application-specific startup output. Neither function
829     implements OAuth or Basic authentication. See
830     L<AnyEvent::MCP::Server::HTTP/AUTHENTICATION> for details.
831 root 1.1
832     All three starters return when the server is closed or on C<SIGINT> or
833     C<SIGTERM>. Signal shutdown closes connections and cancels outstanding
834     requests. The signal watchers belong to the blocking call.
835    
836     =back
837    
838     =cut
839    
840     package AnyEvent::MCP::Server::Simple;
841    
842     use common::sense;
843     use AnyEvent ();
844 root 1.40 use AnyEvent::Log ();
845 root 1.1 use AnyEvent::MCP ();
846     use AnyEvent::MCP::Server::Toolset ();
847     use Exporter ();
848 root 1.42 use JSON::XS ();
849     use Scalar::Util qw(refaddr);
850 root 1.1
851     our @ISA = qw(Exporter);
852 root 1.27 our @EXPORT = qw(mcp_config mcp_tool mcp_getoptions mcp_tool_help mcp_log mcp_log_request mcp_start mcp_stdio mcp_http mcp_connect);
853 root 1.1
854 root 1.41 my %config = (
855     auto_instructions => 1,
856     on_request => \&mcp_log_request,
857 root 1.42 on_connection_error => sub {
858     my ($server, $handle, $error) = @_;
859     my $info = $handle->{mcp_peer}{client_info};
860     my $name = ref $info eq "HASH" ? $info->{name} : undef;
861     my @details = (
862     "id=" . sprintf ("%x", refaddr $handle),
863     defined $handle->{mcp_remote} ? "remote=$handle->{mcp_remote}" : (),
864     defined $name && !ref $name ? "client=" . JSON::XS::encode_json (substr $name, 0, 80) : (),
865     );
866     AE::log warn => "MCP connection " . join (" ", @details) . ": $error";
867     },
868 root 1.41 );
869 root 1.1 my $toolset = AnyEvent::MCP::Server::Toolset->new;
870 root 1.27 my $quiet;
871 root 1.1
872     sub _run;
873    
874 root 1.27 # operational messages go to the configured AnyEvent::Log destination,
875     # independently of the diagnostic verbosity filter.
876     sub mcp_log {
877     return if $quiet;
878     require AnyEvent::Log;
879     $AnyEvent::Log::LOG->log (info => @_);
880     }
881    
882     sub mcp_log_request {
883 root 1.38 my ($server, $req, %extra) = @_;
884 root 1.27
885     return unless $req->{method} eq "tools/call";
886     return if $req->{mcp_logged}++;
887    
888     my $args = $req->{args};
889 root 1.39 my $vcs = $req->{tool} =~ /\Avcs-(?:diff|status|log)\z/;
890 root 1.27 my @details;
891 root 1.39 for (qw(target workdir path cwd), $vcs ? "backend" : ()) {
892 root 1.27 push @details, "$_=" . (substr $args->{$_}, 0, 160)
893     if defined $args->{$_} && !ref $args->{$_};
894     }
895 root 1.36 push @details, substr +(join " ", @{$args->{ranges}}), 0, 180
896 root 1.27 if ref $args->{ranges} eq "ARRAY";
897 root 1.39 for my $key ("argv", $vcs ? "files" : ()) {
898     next unless ref $args->{$key} eq "ARRAY" && @{$args->{$key}};
899 root 1.31 require JSON::XS;
900 root 1.39 my @values = @{$args->{$key}};
901     push @details, "$key=" . join (" ", map {
902 root 1.31 JSON::XS::encode_json (substr $_, 0, 80) . (length $_ > 80 ? "..." : "")
903 root 1.39 } @values[0 .. ($#values < 3 ? $#values : 3)])
904     . (@values > 4 ? " ..." : "");
905 root 1.31 }
906     if ($req->{tool} eq "grep" && defined $args->{pattern} && !ref $args->{pattern}) {
907     require JSON::XS;
908     push @details, "pattern=" . JSON::XS::encode_json (substr $args->{pattern}, 0, 160)
909     . (length $args->{pattern} > 160 ? "..." : "");
910     }
911 root 1.29 push @details, "name=" . (substr $args->{name}, 0, 160)
912     if $req->{tool} eq "call-tool" && defined $args->{name} && !ref $args->{name};
913     if ($req->{tool} eq "ex" && defined $args->{command} && !ref $args->{command}) {
914     require JSON::XS;
915     push @details, "command=" . JSON::XS::encode_json (substr $args->{command}, 0, 200)
916     . (length $args->{command} > 200 ? "..." : "");
917     }
918 root 1.28 if ($req->{tool} eq "path-op") {
919     push @details, map {
920     ($_ eq "unlink" || $_ eq "rmdir")
921     ? ($args->{$_} ? $_ : ())
922     : (defined $args->{$_} && !ref $args->{$_}
923     ? "$_=" . (substr $args->{$_}, 0, 80) : ())
924     } qw(unlink rmdir mkpath mkdir symlink truncate chmod rename);
925     }
926 root 1.38 for my $key (sort keys %extra) {
927     next unless defined $extra{$key} && !ref $extra{$key};
928     push @details, "$key=" . (substr $extra{$key}, 0, 160);
929     }
930 root 1.27
931     (my $message = join " ", $req->{tool}, @details) =~ s/[\r\n]/ /g;
932     mcp_log $message;
933    
934     my $reply = $req->{on_reply};
935     $req->{on_reply} = sub {
936     my ($req, $result) = @_;
937    
938     if ($result->{isError}) {
939     my $message = ref $result->{content} eq "ARRAY"
940     && ref $result->{content}[0] eq "HASH"
941     ? $result->{content}[0]{text} // "unknown error" : "unknown error";
942     $message =~ s/\n.*//s;
943     mcp_log "$req->{tool} failed: " . (substr $message, 0, 240);
944     }
945     $reply->(@_);
946     };
947     }
948    
949     sub _mcp_log_init {
950     my ($name, $silent) = @_;
951    
952     $quiet = $silent;
953     my $identity = AnyEvent::MCP::Config::program_name () . "/$name" . "[$$]";
954    
955     no warnings 'redefine';
956     *AnyEvent::Log::default_format = sub ($$$$) {
957     my ($when, $ctx, $level, $message) = @_;
958 root 1.36 my $stamp = substr +(AnyEvent::Log::format_time $when), 8, 14;
959 root 1.27 my $source = $ctx == $AnyEvent::Log::LOG ? "" : "$AnyEvent::Log::LEVEL2STR[$level] $ctx->[0]: ";
960     join "", map "$stamp $identity $source$_\n", split /\n/, $message;
961     };
962     }
963    
964 root 1.1 sub mcp_config {
965     my %kv = @_;
966    
967     $kv{instructions} = AnyEvent::MCP::_clean_text $kv{instructions}
968     if exists $kv{instructions};
969    
970 root 1.14 %config = (%config, %kv);
971 root 1.1 }
972    
973     sub mcp_tool {
974     $toolset->tool (@_)
975     }
976    
977 root 1.26 # format a JSON Schema type for command-line tool help.
978     sub _tool_type {
979     my ($schema) = @_;
980     return "any" unless ref $schema eq "HASH";
981     for my $key (qw(anyOf oneOf)) {
982     return join "|", map _tool_type ($_), @{$schema->{$key}}
983     if ref $schema->{$key} eq "ARRAY";
984     }
985     my $type = $schema->{type} // "any";
986     $type = join "|", @$type if ref $type eq "ARRAY";
987     $type = "array[" . _tool_type ($schema->{items}) . "]" if $type eq "array";
988     $type
989     }
990    
991     sub mcp_tool_help {
992     my ($enabled, @toolsets) = @_;
993     return unless $enabled;
994    
995     my $json = AnyEvent::MCP::json_class ()->new->canonical;
996     my @tools = map @{$_->list}, @toolsets;
997     for my $tool (@tools) {
998     print "$tool->{name}\n";
999     (my $description = $tool->{description} // "") =~ s/\n/\n /g;
1000     print " $description\n" if length $description;
1001    
1002     my $schema = $tool->{inputSchema} || {};
1003     my $properties = $schema->{properties} || {};
1004     my %required = map { $_ => 1 } @{$schema->{required} || []};
1005     for my $name (sort keys %$properties) {
1006     my $property = $properties->{$name};
1007     my $type = _tool_type ($property);
1008     my $status = $required{$name} ? "required" : "optional";
1009     my @extras;
1010     push @extras, "default: " . $json->encode ($property->{default})
1011     if exists $property->{default};
1012     push @extras, "values: " . join (", ", @{$property->{enum}})
1013     if ref $property->{enum} eq "ARRAY";
1014     print " $name $type $status", (@extras ? " (" . join ("; ", @extras) . ")" : ""), "\n";
1015     if (length ($property->{description} // "")) {
1016     (my $detail = $property->{description}) =~ s/\n/\n /g;
1017     print " $detail\n";
1018     }
1019     }
1020     print "\n";
1021     }
1022     print "No tools registered.\n" unless @tools;
1023     exit;
1024     }
1025    
1026 root 1.9 sub _mcp_help_row {
1027     my ($option, $description) = @_;
1028 root 1.13
1029 root 1.9 my $start = " $option";
1030     my $margin = " " x 34;
1031 root 1.13 my $out = length $start < 34 ? "" : "$start\n";
1032     my $line = length $start < 34 ? $start . (substr $margin, length $start) : $margin;
1033 root 1.9
1034     for my $word (split /\s+/, $description) {
1035 root 1.13 if (length $line > 34 && (length $line) + 1 + length $word > 79) {
1036 root 1.9 $out .= "$line\n";
1037     $line = $margin;
1038     }
1039 root 1.13
1040     $line .= " " if length $line > 34;
1041 root 1.9 $line .= $word;
1042     }
1043    
1044     "$out$line\n"
1045     }
1046    
1047     sub _mcp_help {
1048 root 1.19 my ($usage, $description, $options, $option_heading, $examples) = @_;
1049 root 1.13
1050 root 1.9 $usage =~ s/\n*\z/\n/;
1051     my $help = $usage;
1052     $help .= "\n$description\n" if defined $description && length $description;
1053    
1054     $help .= "\nCommon options:\n";
1055     my @common = (
1056 root 1.25 ["--name NAME", "Select named configuration (instance defaults to 'default')"],
1057 root 1.9 ["--bind ADDRESS[:PORT]", "Override the HTTP listen address or port"],
1058     ["--token TOKEN", "Set an accepted token (repeatable; replaces defaults)"],
1059     ["--stdio", "Serve MCP on stdin/stdout; other listeners may remain"],
1060 root 1.17 ["--connect URL", "Connect to a multibroker at http://host:port/token/alias"],
1061 root 1.21 ["--custom-instructions TEXT", "Describe when this server instance should be used"],
1062 root 1.23 ["--auto-allow TOOL[,ACTION[,CAPABILITY]]", "Add an ordered automatic allow rule (repeatable)"],
1063     ["--auto-deny TOOL[,ACTION[,CAPABILITY]]", "Add an ordered automatic deny rule (repeatable)"],
1064 root 1.27 ["--quiet", "Suppress routine tool activity and tool-failure logging"],
1065 root 1.9 ["--print-url", "Print authenticated HTTP URL and exit without listening"],
1066 root 1.26 ["--tool-help", "Print registered tool descriptions and arguments, then exit"],
1067 root 1.9 ["-h, --help", "Display this help and exit"],
1068     );
1069 root 1.19 $help .= _mcp_help_row (@$_) for @common;
1070 root 1.9
1071     if ($options && @$options) {
1072 root 1.12 $help .= "\n" . ($option_heading // "Application options:") . "\n"
1073     if ref $options->[0];
1074     for my $option (@$options) {
1075     if (ref $option) {
1076     $help .= _mcp_help_row (@$option);
1077     } else {
1078     $help .= "\n" unless $help =~ /\n\n\z/;
1079     $help .= "$option\n";
1080     }
1081     }
1082 root 1.9 }
1083     $help .= "\nExamples:\n$examples\n"
1084     if defined $examples && length $examples;
1085    
1086     $help
1087     }
1088    
1089 root 1.8 sub mcp_getoptions {
1090     require Getopt::Long;
1091     require AnyEvent::MCP::Config;
1092    
1093 root 1.13 my %settings = %{ref $_[0] eq "HASH" ? shift : {}};
1094 root 1.8 my $usage = delete $settings{usage}
1095 root 1.9 // "Usage: $0 [OPTION]... [ARG]...\n";
1096 root 1.13 my @help = delete @settings{qw(description options option_heading examples)};
1097 root 1.8 my %url = (%config, %settings);
1098     delete @url{qw(name bind stdio)};
1099 root 1.23 my @auto_rules;
1100 root 1.8
1101     Getopt::Long::GetOptions (
1102 root 1.13 AnyEvent::MCP::Config::http_getopt (\my $name, \my $bind, \my $print_url, \my @tokens),
1103     "stdio" => \my $stdio,
1104 root 1.17 "connect=s" => \my $connect,
1105 root 1.21 "custom-instructions=s" => \my $custom_instructions,
1106 root 1.23 "auto-allow=s" => sub { push @auto_rules, ["allow", $_[1]] },
1107     "auto-deny=s" => sub { push @auto_rules, ["deny", $_[1]] },
1108 root 1.26 "tool-help" => \my $tool_help,
1109 root 1.27 "quiet" => \my $silent,
1110 root 1.13 "help|h" => \my $help,
1111 root 1.8 @_,
1112     ) or die $usage;
1113    
1114     if ($help) {
1115 root 1.19 print _mcp_help $usage, @help;
1116 root 1.8 exit;
1117     }
1118    
1119     $name = AnyEvent::MCP::Config::server_name ($name // $settings{name} // $config{name});
1120 root 1.27 _mcp_log_init ($name, $silent);
1121 root 1.8 my %server = (
1122     %settings,
1123     name => $name,
1124     (defined $bind ? (bind => $bind) : ()),
1125     (@tokens ? (tokens => \@tokens) : ()),
1126     ($stdio ? (stdio => 1) : ()),
1127 root 1.17 (defined $connect ? (connect => $connect) : ()),
1128 root 1.21 (defined $custom_instructions ? (custom_instructions => $custom_instructions) : ()),
1129 root 1.23 (@auto_rules ? (auto_rules => \@auto_rules) : ()),
1130 root 1.26 ($tool_help ? (tool_help => 1) : ()),
1131 root 1.8 );
1132    
1133     if ($print_url) {
1134     my %options = (%url, (@tokens ? (tokens => \@tokens) : ()));
1135     my $global = AnyEvent::MCP::Config->global;
1136 root 1.16 $options{path} //= $global->{section}{"instance $name"}{path}
1137     // $global->{section}{"program " . AnyEvent::MCP::Config::program_name ()}{path} // "/";
1138 root 1.8 if (($options{auth} || "") eq "none") {
1139     !exists $options{tokens} or die "Cannot combine auth => none with tokens\n";
1140     }
1141     my $selected_bind = defined $bind ? $bind : ($settings{bind} // $config{bind});
1142     print $global->http_url ($name, $selected_bind, %options), "\n";
1143     exit;
1144     }
1145    
1146 root 1.33 \%server
1147 root 1.8 }
1148    
1149 root 1.20 sub _permission_options {
1150     my %kv = @_;
1151    
1152 root 1.23 if (my $rules = delete $kv{auto_rules}) {
1153 root 1.20 require AnyEvent::MCP::Server::Permissions;
1154     my $permissions = $kv{permissions} ||= AnyEvent::MCP::Server::Permissions->new;
1155 root 1.23 $permissions->auto_rules ($rules);
1156 root 1.20 }
1157    
1158     %kv
1159     }
1160    
1161 root 1.34 sub _server_options {
1162     my %kv = @_;
1163    
1164     if (exists $kv{server_name} || exists $kv{server_version}) {
1165     !exists $kv{server_info}
1166     or die "Cannot combine server_info with server_name/server_version\n";
1167    
1168     my $name = delete $kv{server_name};
1169     my $version = delete $kv{server_version};
1170     $kv{server_info} = {
1171     name => defined $name ? $name : "AnyEvent::MCP",
1172     version => defined $version ? "$version" : "$AnyEvent::MCP::VERSION",
1173     };
1174     }
1175    
1176     %kv
1177     }
1178    
1179 root 1.1 sub mcp_start {
1180 root 1.3 require AnyEvent::MCP::Config;
1181 root 1.1
1182 root 1.24 my %kv = (%config, @_);
1183 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1184 root 1.3 my $name = AnyEvent::MCP::Config::server_name ($kv{name});
1185 root 1.21 my $config_file = AnyEvent::MCP::Config::_file ();
1186     my $global = -e $config_file ? AnyEvent::MCP::Config->global : undef;
1187     my %ini = $global ? (
1188     %{$global->{section}{"program " . AnyEvent::MCP::Config::program_name ()} || {}},
1189     %{$global->{section}{"instance $name"} || {}},
1190     ) : ();
1191 root 1.24 if ($global) {
1192     my @rules = $global->auto_rules ($name);
1193     $kv{auto_rules} = [@{$kv{auto_rules} || []}, @rules] if @rules;
1194     }
1195 root 1.21 $kv{custom_instructions} = $ini{custom_instructions}
1196     if !exists $kv{custom_instructions} && exists $ini{custom_instructions};
1197 root 1.24 %kv = _permission_options (%kv);
1198 root 1.8 my $stdio = delete $kv{stdio};
1199 root 1.17 my $listen = delete $kv{listen};
1200     my $connect = delete $kv{connect};
1201     !($stdio && defined $connect) or die "Cannot combine --stdio and --connect\n";
1202     $stdio ||= !defined $connect && $ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio";
1203 root 1.1
1204     $kv{name} = $name;
1205    
1206 root 1.17 if (!$listen) {
1207     return mcp_connect (%kv, connect => $connect) if defined $connect;
1208     return mcp_stdio (%kv) if $stdio;
1209     }
1210 root 1.1
1211 root 1.21 my %http = ($global ||= AnyEvent::MCP::Config->global)->http_settings ($name, delete $kv{bind});
1212 root 1.8 delete $http{tokens} if ($kv{auth} || "") eq "none";
1213 root 1.17 my %options = (path => "/", %http, %kv);
1214 root 1.8
1215 root 1.17 if ($stdio || defined $connect) {
1216     require AnyEvent::MCP::Server::HTTP;
1217 root 1.34 my $listener = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], _server_options (%options));
1218 root 1.17 $listener->listen;
1219     my $result = defined $connect
1220     ? mcp_connect (%kv, connect => $connect)
1221     : mcp_stdio (%kv);
1222     $listener->close;
1223     return $result
1224     }
1225    
1226     mcp_http (%options)
1227     }
1228    
1229     sub mcp_connect {
1230     require AnyEvent::MCP::Server::Connect;
1231 root 1.1
1232 root 1.34 my %kv = _server_options (%config, @_);
1233 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1234     my $server = AnyEvent::MCP::Server::Connect->new (toolsets => [$toolset], _permission_options (%kv));
1235 root 1.17 $server->start;
1236     _run ($server, 1)
1237 root 1.1 }
1238    
1239     sub mcp_stdio {
1240     require AnyEvent::MCP::Server::JSONL;
1241    
1242 root 1.34 my %kv = _permission_options (_server_options (%config, @_));
1243 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1244 root 1.11 my $idle_exit = delete $kv{idle_exit};
1245 root 1.1 binmode STDIN;
1246     binmode STDOUT;
1247     my $selected = select STDOUT;
1248     $| = 1;
1249     select $selected;
1250    
1251 root 1.11 my $server = AnyEvent::MCP::Server::JSONL->new (toolsets => [$toolset], %kv);
1252 root 1.1 $server->accept (\*STDIN, \*STDOUT);
1253 root 1.11
1254     if ($idle_exit) {
1255     $idle_exit =~ /\A[0-9]+(?:\.[0-9]+)?\z/ && $idle_exit > 0
1256     or die "invalid idle_exit seconds\n";
1257     if ($ENV{PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL} eq "stdio") {
1258     my ($timer, $check);
1259     $check = sub {
1260     return if $server->{idle_exit_requested};
1261     if (grep $_->{mcp_active}, values %{$server->{connections} || {}}) {
1262     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
1263     } else {
1264     $server->request_idle_exit;
1265     }
1266     };
1267     my $schedule = sub {
1268     $timer = AnyEvent->timer (after => $idle_exit, cb => $check);
1269     };
1270     my $on_request = $server->{on_request};
1271     $server->{on_request} = sub {
1272     my ($server, $req) = @_;
1273 root 1.13
1274 root 1.11 if ($req->{method} ne "anyevent-mcp/shutdown") {
1275     $server->{idle_exit_requested} = 0;
1276     $schedule->();
1277     my $reply = $req->{on_reply};
1278     $req->{on_reply} = sub {
1279     $reply->(@_);
1280     $schedule->() unless $server->{_idle_shutting_down};
1281     };
1282     }
1283     $on_request->(@_) if $on_request;
1284     };
1285     my $on_close = $server->{on_close};
1286     $server->{on_close} = sub {
1287     undef $timer;
1288     $on_close->(@_) if $on_close;
1289     };
1290     $schedule->();
1291     }
1292     }
1293 root 1.1 _run $server
1294     }
1295    
1296     sub mcp_http {
1297     require AnyEvent::MCP::Server::HTTP;
1298    
1299 root 1.34 my %kv = _permission_options (_server_options (%config, @_));
1300 root 1.26 mcp_tool_help (delete $kv{tool_help}, @{$kv{toolsets} || [$toolset]});
1301     my $server = AnyEvent::MCP::Server::HTTP->new (toolsets => [$toolset], %kv);
1302 root 1.1 $server->listen;
1303     _run $server
1304     }
1305    
1306     sub _run {
1307 root 1.17 my ($server, $keep_alive) = @_;
1308 root 1.1
1309     my $cv = AnyEvent->condvar;
1310     my $stopped;
1311     my $stop = sub { $cv->send unless $stopped++ };
1312     my $on_close = $server->{on_close};
1313     $server->{on_close} = sub {
1314     $on_close->(@_) if $on_close;
1315     $stop->();
1316     };
1317    
1318 root 1.17 if (!$keep_alive && $server->isa ("AnyEvent::MCP::Server::JSONL")) {
1319 root 1.1 my $on_connection_close = $server->{on_connection_close};
1320     $server->{on_connection_close} = sub {
1321     $on_connection_close->(@_) if $on_connection_close;
1322     $stop->();
1323     };
1324     }
1325    
1326     my @signals = map {
1327     AnyEvent->signal (signal => $_, cb => sub { $server->close })
1328     } qw(INT TERM);
1329    
1330     my $ok = eval {
1331     $server->event (start =>);
1332     $cv->recv;
1333     1
1334     };
1335     my $error = $@;
1336     $server->close;
1337     die $error unless $ok;
1338     return
1339     }
1340    
1341     1