ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/AnyEvent-MCP/lib/AnyEvent/MCP.pm
Revision: 1.44
Committed: Sat Nov 7 21:00:41 2026 UTC (6 days, 2 hours ago) by root
Branch: MAIN
Changes since 1.43: +2 -2 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::MCP - Model Context Protocol client/server support
4    
5     =head1 SYNOPSIS
6    
7 root 1.15 use AnyEvent::MCP::Server::Simple;
8 root 1.1
9 root 1.31 my $options = mcp_getoptions ...;
10 root 1.21 mcp_tool ...;
11 root 1.31 mcp_start %$options;
12 root 1.1
13     or use the object interface directly:
14    
15 root 1.6 use AnyEvent::MCP::Server ();
16 root 1.1
17     my $server = AnyEvent::MCP::Server->new (...);
18    
19     =head1 DESCRIPTION
20    
21 root 1.21 AnyEvent::MCP provides event-driven Model Context Protocol client and server
22     support. There are three starting points:
23    
24     =over
25    
26     =item Use the included servers without writing Perl
27    
28     L<AnyEvent::MCP::Intro> walks through installation, a first filesystem
29     request, client connections, configuration, permissions and combining
30     servers. Start there if you want the tools to work on your projects.
31    
32     =item Write a small MCP server
33 root 1.17
34 root 1.21 L<AnyEvent::MCP::Server::Simple/TUTORIAL> builds a complete server,
35     calls it, and adds options, replies, permission checks and asynchronous
36     work. Simple handles transport startup and the event loop.
37    
38     =item Embed MCP in an application
39    
40     The modules below expose tool registries, protocol dispatch, requests and
41     transports separately, for applications that need to control their own
42     connections or lifetime.
43    
44     =back
45 root 1.1
46     The usual way to implement a small MCP server is
47     L<AnyEvent::MCP::Server::Simple>, which provides a function-based interface and
48     creates the underlying server objects when a transport such as stdio or HTTP is
49     started.
50    
51     The lower-level object interfaces are intended for applications that need to
52     manage MCP connections, servers or reusable tool registries directly.
53    
54     =head2 MODULES
55    
56 root 1.7 =over
57 root 1.1
58     =item L<AnyEvent::MCP::Server::Simple>
59    
60     Function-based interface for implementing ordinary MCP servers.
61    
62     =item L<AnyEvent::MCP::Server>
63    
64     Object-oriented MCP server interface.
65    
66     =item L<AnyEvent::MCP::Client>
67    
68     Object-oriented MCP client interface.
69    
70     =item L<AnyEvent::MCP::Server::Toolset>
71    
72     Reusable registry of MCP tool declarations.
73    
74 root 1.29 =item L<AnyEvent::MCP::Server::Request>
75    
76     One incoming server request: arguments, replies, cancellation and progress.
77    
78     =item L<AnyEvent::MCP::Client::Request>
79    
80     Input requested from the MCP client, including human elicitation.
81    
82     =item L<AnyEvent::MCP::Server::HTTP> and L<AnyEvent::MCP::Server::JSONL>
83    
84     HTTP listener and stdio/byte-stream server transports. Both use the
85     protocol handling in L<AnyEvent::MCP::Server>.
86    
87     =item L<AnyEvent::MCP::Client::HTTP> and L<AnyEvent::MCP::Client::JSONL>
88    
89     HTTP and subprocess/byte-stream client transports; both use
90     L<AnyEvent::MCP::Client> for protocol negotiation and tool calls.
91    
92     =item L<AnyEvent::MCP::Server::Connect>
93    
94     Reverse MCP connection from a server into a multibroker.
95    
96 root 1.1 =item L<AnyEvent::MCP::Server::Permissions>
97    
98 root 1.21 Action authorization and sandbox-grant handling for MCP servers.
99 root 1.1
100 root 1.29 =item L<AnyEvent::MCP::Permissions::Ask>
101    
102     External permission dialog handling and form conversion for clients.
103    
104     =item L<AnyEvent::MCP::Config>
105    
106     Private global configuration, named server settings and derived credentials.
107    
108     =item L<AnyEvent::MCP::Helper>
109    
110     Asynchronous JSON exchange with an external form or permission helper.
111    
112 root 1.42 =item L<AnyEvent::MCP::Workdirs>
113 root 1.29
114 root 1.42 Persistent workdir identifiers; state-file locking and replacement are
115     provided by this module.
116 root 1.29
117     =item L<AnyEvent::MCP::Extension>
118    
119     Private authenticated raw-TCP protocols sharing an HTTP listener.
120    
121 root 1.1 =back
122    
123 root 1.4 C<mcp-multibroker> combines downstream HTTP and stdio servers under tool-name
124     prefixes. It uses the same client, server and permission helper facilities,
125     including modern input forwarding and local permission prompts for legacy
126     callers. Run C<mcp-multibroker --help> for command-line syntax; its manual
127     describes authentication and configuration.
128    
129 root 1.16 The interfaces are still being designed. L<AnyEvent::MCP::Intro> explains
130     the installed programs; module-specific POD describes their Perl APIs.
131 root 1.9
132 root 1.16 =head1 TUTORIAL
133 root 1.9
134 root 1.21 For using the servers, see L<AnyEvent::MCP::Intro>. It includes a working
135     first connection, a complete configuration example, saved permissions,
136     multibroker setup and troubleshooting.
137    
138     For writing a server, see L<AnyEvent::MCP::Server::Simple/TUTORIAL>.
139     The tutorial starts with a complete echo server and a client that prints
140     its reply, then develops argument validation, command-line options,
141     result representations, permission retries and cancellable asynchronous
142     work. The Simple function reference follows it.
143    
144     For custom dispatch and reusable tool registries, see
145     L<AnyEvent::MCP::Server::Toolset/REQUEST AND TOOL WORKFLOW> and
146     L<AnyEvent::MCP::Server>. The client modules describe calling servers
147     from your own application.
148 root 1.9
149 root 1.1
150 root 1.2 =head1 GLOBAL CONFIGURATION
151    
152     The shared per-user configuration is C<$XDG_CONFIG_HOME/anyevent-mcp/global.ini>,
153     or C<$HOME/.config/anyevent-mcp/global.ini> when XDG_CONFIG_HOME is unset.
154     L<AnyEvent::MCP::Config> loads and holds it; loading this module does not access
155     configuration files.
156    
157     The file is UTF-8 and uses sections with single-line C<key = value> pairs.
158     Values are unquoted strings. Blank lines and whole-line comments beginning
159     with C<#> or C<;> are ignored; inline comment characters remain part of the
160     value. Later occurrences of a key replace earlier ones in the same section.
161    
162     [global]
163     secret = ...128 hexadecimal digits...
164    
165 root 1.18 [instance example]
166 root 1.11 bind = 127.0.0.1:31740
167 root 1.13 tokens = first-token,second-token
168 root 1.2
169 root 1.21 [program mcp-fs]
170     mount.opt = ro:/opt
171 root 1.2
172     Section names are unrestricted. Application-specific sections, including
173     exports, utility paths and user settings, are documented by their applications.
174    
175     C<[global]> holds C<secret>, a 64-byte master key encoded as 128 hexadecimal
176     digits. The file must be owned by the current user, with no group or other
177     permissions. A missing secret is generated automatically on first configuration
178     access and saved when possible. Existing secrets are never replaced implicitly.
179     An invalid secret is an error.
180    
181 root 1.22 Applications can derive their own purpose-specific secrets with
182     L<AnyEvent::MCP::Config/derive_secret>, using the cached global configuration
183     or an explicitly loaded configuration object.
184    
185 root 1.2 If saving fails, Config warns and retains the secret in memory. Credentials
186     then remain stable only for that process; other processes cannot derive the
187     same credentials. If randomness is unavailable, supply a securely generated
188     secret in the file.
189    
190 root 1.24 C<[program PROGRAM]> supplies settings for the invoked program basename;
191     C<[instance NAME]> overrides them for a named instance.
192 root 1.11 HTTP-capable executables use C<--name NAME> to select these settings, with
193 root 1.42 C<default> as the default instance name. Names start with an ASCII letter,
194     digit or underscore and may then contain letters, digits, C<_>, C<->,
195     C<.>, C<@> and C<+>. Their C<--bind ADDRESS[:PORT]> overrides the configured C<bind>.
196 root 1.24 The default bind address is IPv4 loopback with a port derived from the program,
197     instance name and master secret in the range 16384..32767; C<:0> requests an ephemeral
198 root 1.37 port. C<--print-url> prints enabled user URLs, and C<--print-url=USER>
199     prints just the chosen user's URLs, without listening.
200     Named server settings also include C<path>, C<url_host>,
201 root 1.12 C<url_scheme> and C<tokens>. C<tokens> is a comma-separated list of
202     URL-safe strings. Repeated C<--token TOKEN> options override this list.
203 root 1.27 Automatic permission defaults use C<auto-rule.SUFFIX = allow|deny
204     TOOL[,ACTION[,CAPABILITY]]> in program/instance sections, sorted by suffix.
205     Instance entries override matching keys or suppress them with empty values.
206     Command-line automatic rules are evaluated before configured defaults.
207 root 1.12 All accepted tokens work in the URL path or Authorization bearer header.
208 root 1.37 Without configured tokens, enabled users in C<[global]>,
209     C<[program PROGRAM]> and C<[instance NAME]> each have derived credentials,
210     with later scopes overriding earlier ones. A missing global
211     C<user.default> is equivalent to C<user.default = 1>. Positive
212     generations are enabled; negative generations are revoked and retain
213     their counter for later rotation. Zero is invalid. The principal used
214     for permissions is C<global:USER>, C<program:USER>, or C<instance:USER>.
215     All derivation contexts have the C<anyevent-mcp> namespace prefix.
216     The C<connect_user> setting (default: C<default>) chooses which user's
217     token may attach Vim editor, browser WebSocket or reverse-MCP connections.
218     Disabling that user prevents new auxiliary connections, without stopping
219     other enabled users from making MCP requests.
220 root 1.11
221 root 1.12 C<mcp_start> resolves named settings and requires the selected token for
222 root 1.37 HTTP requests. Explicit tokens replace the derived managed users.
223 root 1.11 The low-level HTTP transport does not resolve configuration itself.
224 root 1.21 Simple's stdio startup does not access global HTTP configuration.
225     Applications can still load it for their own settings; C<mcp-fs>, for
226     example, reads trusted mount and executable settings even in stdio mode.
227     Browser origins are permitted by default, but must still pass MCP authentication.
228 root 1.3
229 root 1.12 C<[client NAME]> supplies HTTP client defaults: C<url>, C<bearer> and
230     C<timeout>. Clients consult it only when explicitly given a C<name>.
231 root 1.21 An unnamed HTTP client and the low-level HTTP server do not read global
232     configuration. C<mcp_start> resolves a default name and does read it.
233 root 1.3 Explicit constructor arguments take precedence over named defaults.
234    
235     =head1 ENVIRONMENT
236    
237     Project-specific environment variables use the C<PERL_ANYEVENT_MCP_> prefix.
238     C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL=stdio> selects stdio in Simple.
239 root 1.11 C<PERL_ANYEVENT_MCP_MULTIBROKER_ALIAS> names a multibroker routing prefix;
240     it does not change the service-instance name.
241 root 1.3 C<PERL_ANYEVENT_MCP_PERMISSION_HELPER> selects the external asking executable.
242 root 1.21 C<PERL_ANYEVENT_MCP_PERMISSION_ASK> selects the permission engine's
243 root 1.42 asking default: C<helper>, C<tty>, C<yad>, C<auto>, C<mcp> or C<none>. See
244 root 1.21 L<AnyEvent::MCP::Intro/Choose where to ask> for the behavior and client
245     requirements.
246 root 1.3 Standard C<HOME> and C<XDG_CONFIG_HOME> keep their standard names.
247 root 1.2
248     =head1 FUNCTIONS
249    
250 root 1.6 =over
251    
252 root 1.10 =item $class = AnyEvent::MCP::json_class
253    
254     Returns C<JSON::XS> if available, otherwise C<JSON::PP>. The caller
255     configures a new coder instance as needed.
256    
257 root 1.8 =item $bytes = AnyEvent::MCP::utf8enc $text
258    
259     Return a UTF-8 byte string for C<$text>, leaving the argument unchanged.
260    
261     =item $text = AnyEvent::MCP::utf8dec $bytes
262    
263     Decode a UTF-8 byte string into characters, dying on invalid UTF-8.
264     The argument is left unchanged. For intentional in-place conversions,
265     use Perl's C<utf8::encode> or C<utf8::decode> directly.
266    
267 root 1.43 =item AnyEvent::MCP::parse_ini $bytes, $callback, $filename
268 root 1.26
269 root 1.43 Decode UTF-8 INI bytes and call C<$callback-E<gt>($name, $kv)> with character
270 root 1.44 strings for each section in file order, including the initial unnamed
271     section (name C<"">).
272 root 1.26 Repeated sections remain separate; duplicate keys within a section use
273     their last value. The callback decides how to collect sections.
274     C<$filename> is optional and used only for line-numbered errors.
275    
276 root 1.6 =item $bytes = AnyEvent::MCP::random_bytes $length
277 root 1.2
278     Returns exactly C<$length> random bytes. Tries C</dev/urandom>, then an external
279     C<openssl rand> command. No additional random-number module is required.
280     Dies if neither source supplies the requested bytes successfully.
281     C<$length> must be a nonnegative integer; zero returns an empty string.
282    
283 root 1.6 =back
284    
285     =head2 PATCH UTILITIES
286    
287 root 1.7 Since applying patches to files is a common task for MCP servers, the
288 root 1.6 following utility functions can be used to ease that job. Together, they
289 root 1.7 define a common patch format (unified diff or a simplified version of it,
290     with fewer requirements to make it easier for the LLM to generate), helpers
291 root 1.6 to apply the patch and a helper to verify if the patch was applied
292     with sufficient precision.
293    
294 root 1.7 Use C<apply_udiff> to normalize, apply and validate a patch in one operation.
295     It writes a separate output file; the caller checks freshness and metadata
296     and commits the result.
297    
298     For an application-specific applier, the workflow is:
299 root 1.6
300     =over
301    
302     =item 1. AnyEvent::MCP::normalize_udiff
303    
304 root 1.7 Returns a unified diff for GNU patch and the permitted hunk offsets.
305    
306     =item 2. Apply the normalized diff
307 root 1.6
308 root 1.7 Run GNU patch with fuzz zero and capture its verbose output.
309 root 1.6
310 root 1.7 =item 3. AnyEvent::MCP::check_patch_output
311    
312     Checks the process status and output against the permitted offsets.
313     C<parse_patch_output> is available separately when only parsing is needed.
314 root 1.5
315 root 1.6 =back
316    
317 root 1.5 =head3 Input format
318    
319 root 1.6 The patch format accepted by normalize_udiff is an extension to the
320     (essentially undocumented) unified diff format.
321    
322 root 1.5 In a hunk header, only the old start C<-N> matters. It is the old-file line
323     of the first hunk body line, including context, not necessarily the first
324     changed line. Counts and the new-file position are ignored and recomputed.
325     Both ordinary headers and the abbreviated form are accepted:
326    
327     @@ -42,99 +123,99 @@
328     @@ -42 @@
329    
330     Optional text after the closing C<@@> is preserved. File headers and other
331     text before the first hunk are preserved; a diff must describe one file.
332    
333     Hunk body lines use the usual prefixes: a space for unchanged context,
334     C<-> for removed lines and C<+> for added lines. The
335     C<\ No newline at end of file> marker is preserved and counts neither side.
336     Trailing empty lines outside the final hunk are discarded.
337    
338 root 1.7 Pure insertion is permitted only at the beginning, using old start C<0>.
339     For example, C<@@ -0 @@> with one added line becomes C<@@ -0,0 +1,1 @@>.
340     An empty old-side range names the line before the insertion: C<-0,0>
341     means before line 1, whereas C<-1,0> would mean after line 1 and is rejected
342     here. Nonempty ranges use one-based line numbers.
343    
344 root 1.5 An insertion elsewhere needs old-side context. Empty hunks are rejected.
345     New positions include the cumulative line-count changes of preceding hunks.
346    
347     =head3 Applying the result
348    
349 root 1.6 The C<normalize_udiff> function does not apply the patch, check file
350     contents or guard against concurrent edits. The caller must supply those
351     checks, if required.
352 root 1.5
353     Context must match exactly: apply with fuzz zero. For each hunk,
354     C<$max_offset> permits relocation by at most the number of distinct old-side
355     lines minus one, or zero for a pure insertion. Repeated old-side lines do
356     not increase this allowance. The applier must enforce these limits; an
357     unrestricted search for matching context can select the wrong occurrence.
358    
359     For live Vim buffers, also require the expected changedtick to match.
360     File-based users need an equivalent check before replacing the file.
361    
362 root 1.6 To work around GNU patch's handling of asymmetric context, the
363     first context line of each hunk is rewritten as an identity
364     removal/addition. The line contents and old/new line counts are
365     unchanged. This is normalization for the patch applier, not a relaxation
366     of context matching.
367 root 1.5
368 root 1.6 =over
369    
370     =item ($diff, $max_offset) = AnyEvent::MCP::normalize_udiff $diff
371    
372     Normalizes a relaxed, single-file unified diff into ordinary unified diff
373     syntax. Returns the normalized C<$diff> and an array reference C<$max_offset>
374     containing the permitted whole-hunk relocation for each hunk, in order.
375     Dies on malformed hunks or when no hunks are found.
376    
377     Here is a suitable description for the diff format, e.g. in a tool
378     definition:
379    
380     Apply a unified diff to one regular file. Hunk counts are recomputed,
381     fuzz is disabled, whole-hunk relocation is limited per hunk by distinct
382     old-side lines. Pure insertion is allowed only at the beginning;
383     elsewhere include old-side context.
384    
385     =item $cv = AnyEvent::MCP::apply_udiff $input, $output, $diff
386    
387     Asynchronously applies the relaxed C<$diff> to the input file C<$input>,
388     writing a separate output file C<$output>. C<$diff> must contain bytes;
389     callers working with characters must encode them for the file themselves.
390    
391     Normalizes the diff, runs GNU patch using C<AnyEvent::Util::run_cmd>, and
392     validates its verbose output with C<check_patch_output>. The command uses
393     batch/forward mode, binary mode and fuzz zero. No shell is involved.
394    
395     C<$output> must not exist. It is reserved with mode 0600; patch runs in its
396     directory so incidental temporary files stay there. Input and output paths
397     are made absolute before starting the command. These must be trusted paths
398     in a directory controlled by the caller.
399    
400     Returns a condition variable. Its C<recv> returns the validated hunk array
401     reference, or throws on command/application failure. Setup failures throw
402     synchronously. Failed or partial output is removed. C<$input> is unchanged.
403    
404     The caller owns committing the successful output and removing it when no
405     longer needed. Freshness checks, metadata preservation, atomic replacement
406     and application-specific permissions also belong to the caller.
407    
408     For your reference, the exact options used are:
409    
410     --verbose --batch --forward --binary --fuzz=0
411     --no-backup-if-mismatch
412     --reject-file=- --output=... -- <inputfile>
413    
414     =item $parsed = AnyEvent::MCP::parse_patch_output $output
415 root 1.5
416     Parses C<LC_ALL=C> GNU patch C<--verbose> output without judging whether
417     the application is acceptable. C<$parsed> has two fields:
418    
419 root 1.6 =over
420 root 1.5
421     =item hunks => [$hunk...]
422    
423     Hunk records in output order. Each record contains C<hunk> (the one-based
424     number), C<success>, C<line> (the reported application line), C<fuzz> and
425     the signed C<offset>. Omitted fuzz and offset are zero. Failed and ignored
426     hunks have false C<success>.
427    
428     =item unparsed => [$line...]
429    
430     Lines not recognized as hunk results or GNU patch's verbose framing.
431     The parser preserves them for callers to interpret or reject.
432    
433     =back
434    
435     Use verbose output: ordinary patch output omits exact-position successes,
436     so it cannot account for every hunk.
437    
438 root 1.6 =item $hunks = AnyEvent::MCP::check_patch_output $status, $output, [$max_offset...]
439 root 1.5
440     Parses C<$output> and validates a complete application. C<$status> is the
441     raw process wait status, as returned by C<waitpid> or
442     C<AnyEvent::Util::run_cmd>, not just the exit code.
443    
444     Requires status zero, no unrecognized output, exactly the expected hunks
445     in numbered order, successful application, fuzz zero and each signed offset
446     within its corresponding absolute limit. C<[$max_offset...]> is the array
447     reference returned by C<normalize_udiff>. At least one hunk is required.
448    
449     Returns the parsed hunk array reference on success; otherwise dies with a
450     diagnostic. This is also suitable for callers running patch inside their
451     own sandbox.
452    
453 root 1.6 =back
454 root 1.5
455 root 1.42 =head1 SMALL UTILITIES
456    
457     =over
458    
459     =item $name = AnyEvent::MCP::instance_name ($name)
460    
461     Validate an instance name, defaulting to C<default> if undefined. Names
462     start with an ASCII letter, digit or underscore, followed by letters,
463     digits, underscores, hyphens, dots, C<@> and C<+>. This intentionally
464     small character set is convenient to use unquoted in a shell.
465    
466     =item $lock = AnyEvent::MCP::file_lock ($file)
467    
468     Create the parent directory and state file if necessary, and obtain an
469     exclusive advisory lock. If another cooperating writer atomically replaced
470     the pathname while waiting, retry on the new inode. Keep the handle alive
471     through reading, modifying and replacing the file.
472    
473     =item AnyEvent::MCP::file_replace ($file, $bytes)
474    
475     With the C<file_lock> handle held by the caller, write a complete new version
476     to a temporary file beside C<$file>, preserve the existing file mode and
477     atomically rename it over C<$file>. Remove the temporary on failure.
478     This provides atomic visibility, not crash durability; all writers must
479     follow the locking protocol.
480    
481     =back
482    
483 root 1.1 =cut
484    
485     package AnyEvent::MCP;
486    
487     use common::sense;
488    
489 root 1.41 our $VERSION = 0.82;
490 root 1.1
491 root 1.10 sub json_class {
492     our $JSON_CLASS ||= eval { require JSON::XS; "JSON::XS" }
493     || do { require JSON::PP; "JSON::PP" };
494     }
495    
496 root 1.8 sub utf8enc($) {
497     my $text = shift;
498    
499     utf8::encode $text;
500     $text
501     }
502    
503     sub utf8dec($) {
504     my $bytes = shift;
505    
506     utf8::decode $bytes
507     or die "invalid UTF-8\n";
508     $bytes
509     }
510    
511 root 1.42 sub instance_name {
512     my ($name) = @_;
513    
514     $name = "default" unless defined $name;
515    
516     $name =~ /\A[A-Za-z0-9_][A-Za-z0-9_.@+-]*\z/
517     or die "Invalid MCP server name '$name'\n";
518    
519     $name
520     }
521    
522     sub file_lock {
523     my ($file) = @_;
524    
525     require File::Path;
526     require File::Basename;
527     require Fcntl;
528     require Errno;
529    
530     File::Path::mkpath (File::Basename::dirname ($file), 0, 0700);
531    
532     while (1) {
533     my $fh;
534     sysopen $fh, $file, Fcntl::O_RDWR () | Fcntl::O_CREAT () | Fcntl::O_EXCL (), 0600 or do {
535     die "$file: $!\n" unless $! == Errno::EEXIST ();
536     sysopen $fh, $file, Fcntl::O_RDWR () or die "$file: $!\n";
537     };
538    
539     my @stat = stat $fh;
540     flock $fh, Fcntl::LOCK_EX () or die "$file: $!\n";
541     my @current = stat $file;
542    
543     return $fh if @current && $stat[0] == $current[0] && $stat[1] == $current[1];
544    
545     die "$file: $!\n" unless @current || $! == Errno::ENOENT ();
546    
547     close $fh;
548     }
549     }
550    
551     sub file_replace {
552     my ($file, $bytes) = @_;
553    
554     require Fcntl;
555     require Errno;
556    
557     my @stat = stat $file;
558     die "$file: $!\n" unless @stat || $! == Errno::ENOENT ();
559     my $temporary = "$file.$$~";
560     sysopen my $fh, $temporary, Fcntl::O_WRONLY () | Fcntl::O_CREAT () | Fcntl::O_EXCL (), 0600
561     or die "$temporary: $!\n";
562     my $ok = eval {
563     print {$fh} $bytes or die "$temporary: $!\n";
564     if (@stat) {
565     my $mode = $stat[2] & 07777;
566     chmod $mode, $fh or die "$temporary: $!\n";
567     }
568     close $fh or die "$temporary: $!\n";
569     rename $temporary, $file or die "$file: $!\n";
570     1
571     };
572    
573     my $error = $@;
574     unlink $temporary unless $ok;
575     die $error unless $ok;
576     }
577    
578 root 1.26 sub parse_ini {
579 root 1.43 my ($bytes, $cb, $file) = @_;
580 root 1.26 $file //= "INI";
581 root 1.43 my $text = utf8dec $bytes;
582 root 1.26 my ($name, $kv, $line) = ("", {}, 0);
583    
584     for (split /\n/, $text) {
585     ++$line;
586     s/\r\z//;
587     next if /\A\s*(?:[#;]|\z)/;
588     if (/\A\s*\[([^\]\r\n]+)\]\s*\z/) {
589     my $next = $1;
590     $cb->($name, $kv);
591     ($name, $kv) = ($next, {});
592     } elsif (/\A\s*([^=\s]+)\s*=\s*(.*?)\s*\z/) {
593     $kv->{$1} = $2;
594     } else {
595     die "$file:$line: invalid INI line\n";
596     }
597     }
598    
599     $cb->($name, $kv);
600     }
601    
602 root 1.2 sub random_bytes($) {
603     my ($length) = @_;
604    
605 root 1.19 defined $length && $length =~ /\A[0-9]+\z/
606     or die "Invalid random byte count\n";
607 root 1.2 return "" unless $length;
608    
609     my $bytes = "";
610    
611     if (open my $fh, "<", "/dev/urandom") {
612     binmode $fh;
613     while (length $bytes < $length) {
614 root 1.34 read $fh, $bytes, $length - length $bytes, length $bytes
615 root 1.2 or last;
616     }
617     close $fh;
618    
619     return $bytes if length $bytes == $length;
620     }
621    
622     if (open my $fh, "-|", "openssl", "rand", $length) {
623     binmode $fh;
624     local $/;
625     $bytes = <$fh>;
626     my $success = close $fh;
627    
628     return $bytes
629     if $success && defined $bytes && length $bytes == $length;
630     }
631    
632     die "Cannot obtain $length random bytes from /dev/urandom or openssl rand\n";
633     }
634    
635 root 1.5 sub normalize_udiff {
636     my $diff = shift;
637     my @line = split /\n/, $diff, -1;
638     my $delta = 0;
639     my $hunks = 0;
640     my @max_offset;
641    
642     pop @line while @line and !length $line[-1];
643    
644     for (my $i = 0; $i < @line; ++$i) {
645     next unless $line[$i] =~ /^@@ /;
646    
647     $line[$i] =~ /^@@ -(\d+)(?:,\d+)?(?: \+\d+(?:,\d+)?)? @@(.*)$/
648     or die "normalize_udiff: bad hunk header '$line[$i]'\n";
649    
650     my ($old_start, $suffix) = ($1, $2);
651     my ($old_count, $new_count) = (0, 0);
652     my %old_line;
653     my $j = $i + 1;
654    
655     # GNU patch mishandles hunks with more prefix than suffix context.
656 root 1.38 # make the first context line an identity change without losing it.
657 root 1.5 # see https://unix.stackexchange.com/questions/414542/does-patch-work-with-asymmetric-context
658     splice @line, $i + 1, 1, "-$1", "+$1"
659     if $line[$i + 1] =~ /^ (.*)\z/;
660    
661     for (; $j < @line; ++$j) {
662     last if $line[$j] =~ /^@@ /;
663    
664     my $tag = substr $line[$j], 0, 1;
665    
666     if ($tag eq " ") {
667     ++$old_count;
668     ++$new_count;
669     $old_line{substr $line[$j], 1} = 1;
670     } elsif ($tag eq "-") {
671     ++$old_count;
672     $old_line{substr $line[$j], 1} = 1;
673     } elsif ($tag eq "+") {
674     ++$new_count;
675     } elsif ($line[$j] eq "\\ No newline at end of file") {
676 root 1.38 # marker belongs to the preceding body line and counts neither side.
677 root 1.5 } else {
678     die "normalize_udiff: bad hunk line '$line[$j]'\n";
679     }
680     }
681    
682     $old_count or $new_count
683     or die "normalize_udiff: empty hunk at old line $old_start\n";
684    
685     $old_count or !$old_start
686     or die "normalize_udiff: hunk " . ($hunks + 1)
687     . " needs old-side context for insertion after old line $old_start\n";
688    
689     my $new_start = $old_start + $delta;
690     ++$new_start unless $old_count;
691     --$new_start unless $new_count;
692    
693     $new_start >= 0
694     or die "normalize_udiff: invalid new line $new_start\n";
695    
696     $line[$i] = sprintf "@@ -%d,%d +%d,%d @@%s",
697     $old_start, $old_count, $new_start, $new_count, $suffix;
698    
699     $delta += $new_count - $old_count;
700     ++$hunks;
701     my $max_offset = keys %old_line;
702     --$max_offset
703     if $max_offset;
704     push @max_offset, $max_offset;
705     $i = $j - 1;
706     }
707    
708     $hunks
709     or die "normalize_udiff: no hunks\n";
710    
711 root 1.34 ((join "\n", @line, ""), \@max_offset)
712 root 1.5 }
713    
714     sub parse_patch_output {
715     my ($output) = @_;
716     my (@hunks, @unparsed);
717    
718     for (split /\n/, $output) {
719     if (/^Hunk #(\d+) (succeeded|FAILED|ignored) at (-?\d+)(?: with fuzz (\d+))?(?: \(offset (-?\d+) lines?\))?\.\z/) {
720     push @hunks, {
721     hunk => 0 + $1,
722     success => $2 eq "succeeded",
723     line => 0 + $3,
724     fuzz => 0 + $4,
725     offset => 0 + $5,
726     };
727     } elsif (!length
728     || /^Hmm\.\.\. +Looks like a unified diff to me\.\.\.\z/
729     || /^The text leading up to this was:\z/
730     || /^-+\z/ || /^\|/
731     || /^patching file / || /^checking file /
732     || /^Using Plan [AB]\.\.\.\z/ || /^done\z/) {
733     # GNU patch --verbose framing, not hunk results.
734     } else {
735     push @unparsed, $_;
736     }
737     }
738    
739     { hunks => \@hunks, unparsed => \@unparsed }
740     }
741    
742     sub check_patch_output {
743     my ($status, $output, $max_offset) = @_;
744     my $parsed = parse_patch_output $output;
745     my $hunks = $parsed->{hunks};
746    
747     die "patch failed (wait status $status):\n$output"
748     if $status;
749     die "patch: unrecognized output:\n" . (join "\n", @{$parsed->{unparsed}}) . "\n"
750     if @{$parsed->{unparsed}};
751     die "patch: expected " . @$max_offset . " hunks, got " . @$hunks . "\n"
752     unless @$hunks == @$max_offset && @$hunks;
753    
754     for my $i (0 .. $#$hunks) {
755     my $hunk = $hunks->[$i];
756    
757     die "patch: unexpected hunk number $hunk->{hunk}\n"
758     unless $hunk->{hunk} == $i + 1;
759     die "patch: hunk $hunk->{hunk} failed\n"
760     unless $hunk->{success};
761     die "patch: hunk $hunk->{hunk} used fuzz $hunk->{fuzz}\n"
762     if $hunk->{fuzz};
763     die "patch: hunk $hunk->{hunk} offset $hunk->{offset} exceeds $max_offset->[$i]\n"
764     if abs $hunk->{offset} > $max_offset->[$i];
765     }
766    
767     $hunks
768     }
769    
770     sub apply_udiff {
771     my ($input, $output, $diff) = @_;
772     my ($normalized, $max_offset) = normalize_udiff $diff;
773    
774     require AnyEvent::Util;
775     require File::Spec;
776     require File::Basename;
777     require Fcntl;
778    
779     $input = File::Spec->rel2abs ($input);
780     $output = File::Spec->rel2abs ($output);
781     my $directory = File::Basename::dirname ($output);
782    
783     sysopen my $fh, $output, Fcntl::O_WRONLY () | Fcntl::O_CREAT () | Fcntl::O_EXCL (), 0600
784     or die "$output: $!\n";
785     close $fh or do { my $error = $!; unlink $output; die "$output: $error\n" };
786    
787     my $cv = AE::cv ();
788     my $log;
789     my $process = eval {
790     AnyEvent::Util::run_cmd ([
791     "patch", "--verbose", "--batch", "--forward", "--binary", "--fuzz=0",
792     "--no-backup-if-mismatch", "--reject-file=-", "--output=$output", "--", $input,
793     ],
794     "<" => \$normalized,
795     ">" => \$log,
796     close_all => 1,
797     on_prepare => sub {
798     $ENV{LC_ALL} = "C";
799     chdir $directory or die "$directory: $!\n";
800     defined POSIX::dup2 (1, 2) or die "dup2: $!\n";
801     },
802     );
803     };
804    
805     if ($@) {
806     my $error = $@;
807     unlink $output;
808     die $error;
809     }
810    
811     $process->cb (sub {
812     my $hunks;
813     eval { $hunks = check_patch_output $_[0]->recv, $log, $max_offset; 1 } or do {
814     my $error = $@;
815     unlink $output;
816     $cv->croak ($error);
817     return;
818     };
819    
820     $cv->send ($hunks);
821     });
822    
823     $cv
824     }
825    
826 root 1.1 sub _clean_text($) {
827     my ($text) = @_;
828    
829     my ($prefix) = (join "\n", grep /\S/, split /\n/, $text)
830     =~ /\A([ \t]*)[^\n]*(?:\n\1[^\n]*)*\z/;
831    
832     $text =~ s/^\Q$prefix\E//mg;
833     $text =~ s/\A\s+//;
834     $text =~ s/\s+\z//;
835     $text
836     }
837    
838     package AnyEvent::MCP::Base;
839    
840     sub new {
841     my ($class, %kv) = @_;
842 root 1.19
843 root 1.1 bless \%kv, $class
844     }
845    
846     sub event {
847     my ($self, $event, @args) = @_;
848 root 1.19
849 root 1.1 ($self->{"on_$event"} or return)->($self, @args)
850     }
851    
852     =head1 SEE ALSO
853    
854 root 1.16 L<AnyEvent::MCP::Intro>, L<AnyEvent::MCP::Server::Simple>,
855 root 1.23 L<AnyEvent::MCP::Helper>, L<mcp-ask-user>, L<anyevent-mcp-form-elicitation>,
856 root 1.1 L<AnyEvent>, L<https://modelcontextprotocol.io/>
857    
858     =head1 AUTHOR
859    
860     The main author of this module is OpenAI ChatGPT.
861    
862     Additional guidance received by:
863    
864     Marc Lehmann <schmorp@schmorp.de>
865     http://home.schmorp.de/
866    
867     =cut
868    
869     1
870