ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP.pm
Revision: 1.28
Committed: Wed Oct 21 15:20:27 2026 UTC (13 days, 12 hours ago) by root
Branch: MAIN
Changes since 1.27: +1 -1 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.21 my %options = mcp_getoptions ...;
10     mcp_tool ...;
11     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     =item L<AnyEvent::MCP::Server::Permissions>
75    
76 root 1.21 Action authorization and sandbox-grant handling for MCP servers.
77 root 1.1
78     =back
79    
80 root 1.4 C<mcp-multibroker> combines downstream HTTP and stdio servers under tool-name
81     prefixes. It uses the same client, server and permission helper facilities,
82     including modern input forwarding and local permission prompts for legacy
83     callers. Run C<mcp-multibroker --help> for command-line syntax; its manual
84     describes authentication and configuration.
85    
86 root 1.16 The interfaces are still being designed. L<AnyEvent::MCP::Intro> explains
87     the installed programs; module-specific POD describes their Perl APIs.
88 root 1.9
89 root 1.16 =head1 TUTORIAL
90 root 1.9
91 root 1.21 For using the servers, see L<AnyEvent::MCP::Intro>. It includes a working
92     first connection, a complete configuration example, saved permissions,
93     multibroker setup and troubleshooting.
94    
95     For writing a server, see L<AnyEvent::MCP::Server::Simple/TUTORIAL>.
96     The tutorial starts with a complete echo server and a client that prints
97     its reply, then develops argument validation, command-line options,
98     result representations, permission retries and cancellable asynchronous
99     work. The Simple function reference follows it.
100    
101     For custom dispatch and reusable tool registries, see
102     L<AnyEvent::MCP::Server::Toolset/REQUEST AND TOOL WORKFLOW> and
103     L<AnyEvent::MCP::Server>. The client modules describe calling servers
104     from your own application.
105 root 1.9
106 root 1.1
107 root 1.2 =head1 GLOBAL CONFIGURATION
108    
109     The shared per-user configuration is C<$XDG_CONFIG_HOME/anyevent-mcp/global.ini>,
110     or C<$HOME/.config/anyevent-mcp/global.ini> when XDG_CONFIG_HOME is unset.
111     L<AnyEvent::MCP::Config> loads and holds it; loading this module does not access
112     configuration files.
113    
114     The file is UTF-8 and uses sections with single-line C<key = value> pairs.
115     Values are unquoted strings. Blank lines and whole-line comments beginning
116     with C<#> or C<;> are ignored; inline comment characters remain part of the
117     value. Later occurrences of a key replace earlier ones in the same section.
118    
119     [global]
120     secret = ...128 hexadecimal digits...
121    
122 root 1.18 [instance example]
123 root 1.11 bind = 127.0.0.1:31740
124 root 1.13 tokens = first-token,second-token
125 root 1.2
126 root 1.21 [program mcp-fs]
127     mount.opt = ro:/opt
128 root 1.2
129     Section names are unrestricted. Application-specific sections, including
130     exports, utility paths and user settings, are documented by their applications.
131    
132     C<[global]> holds C<secret>, a 64-byte master key encoded as 128 hexadecimal
133     digits. The file must be owned by the current user, with no group or other
134     permissions. A missing secret is generated automatically on first configuration
135     access and saved when possible. Existing secrets are never replaced implicitly.
136     An invalid secret is an error.
137    
138 root 1.22 Applications can derive their own purpose-specific secrets with
139     L<AnyEvent::MCP::Config/derive_secret>, using the cached global configuration
140     or an explicitly loaded configuration object.
141    
142 root 1.2 If saving fails, Config warns and retains the secret in memory. Credentials
143     then remain stable only for that process; other processes cannot derive the
144     same credentials. If randomness is unavailable, supply a securely generated
145     secret in the file.
146    
147 root 1.24 C<[program PROGRAM]> supplies settings for the invoked program basename;
148     C<[instance NAME]> overrides them for a named instance.
149 root 1.11 HTTP-capable executables use C<--name NAME> to select these settings, with
150 root 1.24 C<default> as the default instance name. Names consist of an ASCII
151 root 1.11 letter or underscore followed by ASCII letters, digits, underscores or
152     hyphens. Their C<--bind ADDRESS[:PORT]> overrides the configured C<bind>.
153 root 1.24 The default bind address is IPv4 loopback with a port derived from the program,
154     instance name and master secret in the range 16384..32767; C<:0> requests an ephemeral
155 root 1.12 port. C<--print-url> prints the URL using the first accepted token without
156     listening. Named server settings also include C<path>, C<url_host>,
157     C<url_scheme> and C<tokens>. C<tokens> is a comma-separated list of
158     URL-safe strings. Repeated C<--token TOKEN> options override this list.
159 root 1.27 Automatic permission defaults use C<auto-rule.SUFFIX = allow|deny
160     TOOL[,ACTION[,CAPABILITY]]> in program/instance sections, sorted by suffix.
161     Instance entries override matching keys or suppress them with empty values.
162     Command-line automatic rules are evaluated before configured defaults.
163 root 1.12 All accepted tokens work in the URL path or Authorization bearer header.
164 root 1.24 Without configured tokens, one token is derived from the program and instance
165     name. All derivation contexts have a fixed C<anyevent-mcp> namespace prefix.
166 root 1.11
167 root 1.12 C<mcp_start> resolves named settings and requires the selected token for
168     HTTP requests. Explicit tokens replace the derived default.
169 root 1.11 The low-level HTTP transport does not resolve configuration itself.
170 root 1.21 Simple's stdio startup does not access global HTTP configuration.
171     Applications can still load it for their own settings; C<mcp-fs>, for
172     example, reads trusted mount and executable settings even in stdio mode.
173     Browser origins are permitted by default, but must still pass MCP authentication.
174 root 1.3
175 root 1.12 C<[client NAME]> supplies HTTP client defaults: C<url>, C<bearer> and
176     C<timeout>. Clients consult it only when explicitly given a C<name>.
177 root 1.21 An unnamed HTTP client and the low-level HTTP server do not read global
178     configuration. C<mcp_start> resolves a default name and does read it.
179 root 1.3 Explicit constructor arguments take precedence over named defaults.
180    
181     =head1 ENVIRONMENT
182    
183     Project-specific environment variables use the C<PERL_ANYEVENT_MCP_> prefix.
184     C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL=stdio> selects stdio in Simple.
185 root 1.11 C<PERL_ANYEVENT_MCP_MULTIBROKER_ALIAS> names a multibroker routing prefix;
186     it does not change the service-instance name.
187 root 1.3 C<PERL_ANYEVENT_MCP_PERMISSION_HELPER> selects the external asking executable.
188 root 1.21 C<PERL_ANYEVENT_MCP_PERMISSION_ASK> selects the permission engine's
189 root 1.25 asking default: C<helper>, C<tty>, C<yad>, C<auto> or C<mcp>. See
190 root 1.21 L<AnyEvent::MCP::Intro/Choose where to ask> for the behavior and client
191     requirements.
192 root 1.3 Standard C<HOME> and C<XDG_CONFIG_HOME> keep their standard names.
193 root 1.2
194     =head1 FUNCTIONS
195    
196 root 1.6 =over
197    
198 root 1.10 =item $class = AnyEvent::MCP::json_class
199    
200     Returns C<JSON::XS> if available, otherwise C<JSON::PP>. The caller
201     configures a new coder instance as needed.
202    
203 root 1.8 =item $bytes = AnyEvent::MCP::utf8enc $text
204    
205     Return a UTF-8 byte string for C<$text>, leaving the argument unchanged.
206    
207     =item $text = AnyEvent::MCP::utf8dec $bytes
208    
209     Decode a UTF-8 byte string into characters, dying on invalid UTF-8.
210     The argument is left unchanged. For intentional in-place conversions,
211     use Perl's C<utf8::encode> or C<utf8::decode> directly.
212    
213 root 1.26 =item AnyEvent::MCP::parse_ini $text, $callback, $filename
214    
215     Parse INI text and call C<$callback-E<gt>($name, $kv)> for each section
216     in file order, including the initial unnamed section (name C<"">).
217     Repeated sections remain separate; duplicate keys within a section use
218     their last value. The callback decides how to collect sections.
219     C<$filename> is optional and used only for line-numbered errors.
220    
221 root 1.6 =item $bytes = AnyEvent::MCP::random_bytes $length
222 root 1.2
223     Returns exactly C<$length> random bytes. Tries C</dev/urandom>, then an external
224     C<openssl rand> command. No additional random-number module is required.
225     Dies if neither source supplies the requested bytes successfully.
226     C<$length> must be a nonnegative integer; zero returns an empty string.
227    
228 root 1.6 =back
229    
230     =head2 PATCH UTILITIES
231    
232 root 1.7 Since applying patches to files is a common task for MCP servers, the
233 root 1.6 following utility functions can be used to ease that job. Together, they
234 root 1.7 define a common patch format (unified diff or a simplified version of it,
235     with fewer requirements to make it easier for the LLM to generate), helpers
236 root 1.6 to apply the patch and a helper to verify if the patch was applied
237     with sufficient precision.
238    
239 root 1.7 Use C<apply_udiff> to normalize, apply and validate a patch in one operation.
240     It writes a separate output file; the caller checks freshness and metadata
241     and commits the result.
242    
243     For an application-specific applier, the workflow is:
244 root 1.6
245     =over
246    
247     =item 1. AnyEvent::MCP::normalize_udiff
248    
249 root 1.7 Returns a unified diff for GNU patch and the permitted hunk offsets.
250    
251     =item 2. Apply the normalized diff
252 root 1.6
253 root 1.7 Run GNU patch with fuzz zero and capture its verbose output.
254 root 1.6
255 root 1.7 =item 3. AnyEvent::MCP::check_patch_output
256    
257     Checks the process status and output against the permitted offsets.
258     C<parse_patch_output> is available separately when only parsing is needed.
259 root 1.5
260 root 1.6 =back
261    
262 root 1.5 =head3 Input format
263    
264 root 1.6 The patch format accepted by normalize_udiff is an extension to the
265     (essentially undocumented) unified diff format.
266    
267 root 1.5 In a hunk header, only the old start C<-N> matters. It is the old-file line
268     of the first hunk body line, including context, not necessarily the first
269     changed line. Counts and the new-file position are ignored and recomputed.
270     Both ordinary headers and the abbreviated form are accepted:
271    
272     @@ -42,99 +123,99 @@
273     @@ -42 @@
274    
275     Optional text after the closing C<@@> is preserved. File headers and other
276     text before the first hunk are preserved; a diff must describe one file.
277    
278     Hunk body lines use the usual prefixes: a space for unchanged context,
279     C<-> for removed lines and C<+> for added lines. The
280     C<\ No newline at end of file> marker is preserved and counts neither side.
281     Trailing empty lines outside the final hunk are discarded.
282    
283 root 1.7 Pure insertion is permitted only at the beginning, using old start C<0>.
284     For example, C<@@ -0 @@> with one added line becomes C<@@ -0,0 +1,1 @@>.
285     An empty old-side range names the line before the insertion: C<-0,0>
286     means before line 1, whereas C<-1,0> would mean after line 1 and is rejected
287     here. Nonempty ranges use one-based line numbers.
288    
289 root 1.5 An insertion elsewhere needs old-side context. Empty hunks are rejected.
290     New positions include the cumulative line-count changes of preceding hunks.
291    
292     =head3 Applying the result
293    
294 root 1.6 The C<normalize_udiff> function does not apply the patch, check file
295     contents or guard against concurrent edits. The caller must supply those
296     checks, if required.
297 root 1.5
298     Context must match exactly: apply with fuzz zero. For each hunk,
299     C<$max_offset> permits relocation by at most the number of distinct old-side
300     lines minus one, or zero for a pure insertion. Repeated old-side lines do
301     not increase this allowance. The applier must enforce these limits; an
302     unrestricted search for matching context can select the wrong occurrence.
303    
304     For live Vim buffers, also require the expected changedtick to match.
305     File-based users need an equivalent check before replacing the file.
306    
307 root 1.6 To work around GNU patch's handling of asymmetric context, the
308     first context line of each hunk is rewritten as an identity
309     removal/addition. The line contents and old/new line counts are
310     unchanged. This is normalization for the patch applier, not a relaxation
311     of context matching.
312 root 1.5
313 root 1.6 =over
314    
315     =item ($diff, $max_offset) = AnyEvent::MCP::normalize_udiff $diff
316    
317     Normalizes a relaxed, single-file unified diff into ordinary unified diff
318     syntax. Returns the normalized C<$diff> and an array reference C<$max_offset>
319     containing the permitted whole-hunk relocation for each hunk, in order.
320     Dies on malformed hunks or when no hunks are found.
321    
322     Here is a suitable description for the diff format, e.g. in a tool
323     definition:
324    
325     Apply a unified diff to one regular file. Hunk counts are recomputed,
326     fuzz is disabled, whole-hunk relocation is limited per hunk by distinct
327     old-side lines. Pure insertion is allowed only at the beginning;
328     elsewhere include old-side context.
329    
330     =item $cv = AnyEvent::MCP::apply_udiff $input, $output, $diff
331    
332     Asynchronously applies the relaxed C<$diff> to the input file C<$input>,
333     writing a separate output file C<$output>. C<$diff> must contain bytes;
334     callers working with characters must encode them for the file themselves.
335    
336     Normalizes the diff, runs GNU patch using C<AnyEvent::Util::run_cmd>, and
337     validates its verbose output with C<check_patch_output>. The command uses
338     batch/forward mode, binary mode and fuzz zero. No shell is involved.
339    
340     C<$output> must not exist. It is reserved with mode 0600; patch runs in its
341     directory so incidental temporary files stay there. Input and output paths
342     are made absolute before starting the command. These must be trusted paths
343     in a directory controlled by the caller.
344    
345     Returns a condition variable. Its C<recv> returns the validated hunk array
346     reference, or throws on command/application failure. Setup failures throw
347     synchronously. Failed or partial output is removed. C<$input> is unchanged.
348    
349     The caller owns committing the successful output and removing it when no
350     longer needed. Freshness checks, metadata preservation, atomic replacement
351     and application-specific permissions also belong to the caller.
352    
353     For your reference, the exact options used are:
354    
355     --verbose --batch --forward --binary --fuzz=0
356     --no-backup-if-mismatch
357     --reject-file=- --output=... -- <inputfile>
358    
359     =item $parsed = AnyEvent::MCP::parse_patch_output $output
360 root 1.5
361     Parses C<LC_ALL=C> GNU patch C<--verbose> output without judging whether
362     the application is acceptable. C<$parsed> has two fields:
363    
364 root 1.6 =over
365 root 1.5
366     =item hunks => [$hunk...]
367    
368     Hunk records in output order. Each record contains C<hunk> (the one-based
369     number), C<success>, C<line> (the reported application line), C<fuzz> and
370     the signed C<offset>. Omitted fuzz and offset are zero. Failed and ignored
371     hunks have false C<success>.
372    
373     =item unparsed => [$line...]
374    
375     Lines not recognized as hunk results or GNU patch's verbose framing.
376     The parser preserves them for callers to interpret or reject.
377    
378     =back
379    
380     Use verbose output: ordinary patch output omits exact-position successes,
381     so it cannot account for every hunk.
382    
383 root 1.6 =item $hunks = AnyEvent::MCP::check_patch_output $status, $output, [$max_offset...]
384 root 1.5
385     Parses C<$output> and validates a complete application. C<$status> is the
386     raw process wait status, as returned by C<waitpid> or
387     C<AnyEvent::Util::run_cmd>, not just the exit code.
388    
389     Requires status zero, no unrecognized output, exactly the expected hunks
390     in numbered order, successful application, fuzz zero and each signed offset
391     within its corresponding absolute limit. C<[$max_offset...]> is the array
392     reference returned by C<normalize_udiff>. At least one hunk is required.
393    
394     Returns the parsed hunk array reference on success; otherwise dies with a
395     diagnostic. This is also suitable for callers running patch inside their
396     own sandbox.
397    
398 root 1.6 =back
399 root 1.5
400 root 1.1 =cut
401    
402     package AnyEvent::MCP;
403    
404     use common::sense;
405    
406 root 1.28 our $VERSION = '0.6';
407 root 1.1
408 root 1.10 sub json_class {
409     our $JSON_CLASS ||= eval { require JSON::XS; "JSON::XS" }
410     || do { require JSON::PP; "JSON::PP" };
411     }
412    
413 root 1.8 sub utf8enc($) {
414     my $text = shift;
415    
416     utf8::encode $text;
417     $text
418     }
419    
420     sub utf8dec($) {
421     my $bytes = shift;
422    
423     utf8::decode $bytes
424     or die "invalid UTF-8\n";
425     $bytes
426     }
427    
428 root 1.26 sub parse_ini {
429     my ($text, $cb, $file) = @_;
430     $file //= "INI";
431     my ($name, $kv, $line) = ("", {}, 0);
432    
433     for (split /\n/, $text) {
434     ++$line;
435     s/\r\z//;
436     next if /\A\s*(?:[#;]|\z)/;
437     if (/\A\s*\[([^\]\r\n]+)\]\s*\z/) {
438     my $next = $1;
439     $cb->($name, $kv);
440     ($name, $kv) = ($next, {});
441     } elsif (/\A\s*([^=\s]+)\s*=\s*(.*?)\s*\z/) {
442     $kv->{$1} = $2;
443     } else {
444     die "$file:$line: invalid INI line\n";
445     }
446     }
447    
448     $cb->($name, $kv);
449     }
450    
451 root 1.2 sub random_bytes($) {
452     my ($length) = @_;
453    
454 root 1.19 defined $length && $length =~ /\A[0-9]+\z/
455     or die "Invalid random byte count\n";
456 root 1.2 return "" unless $length;
457    
458     my $bytes = "";
459    
460     if (open my $fh, "<", "/dev/urandom") {
461     binmode $fh;
462     while (length $bytes < $length) {
463     read ($fh, $bytes, $length - length ($bytes), length $bytes)
464     or last;
465     }
466     close $fh;
467    
468     return $bytes if length $bytes == $length;
469     }
470    
471     if (open my $fh, "-|", "openssl", "rand", $length) {
472     binmode $fh;
473     local $/;
474     $bytes = <$fh>;
475     my $success = close $fh;
476    
477     return $bytes
478     if $success && defined $bytes && length $bytes == $length;
479     }
480    
481     die "Cannot obtain $length random bytes from /dev/urandom or openssl rand\n";
482     }
483    
484 root 1.5 sub normalize_udiff {
485     my $diff = shift;
486     my @line = split /\n/, $diff, -1;
487     my $delta = 0;
488     my $hunks = 0;
489     my @max_offset;
490    
491     pop @line while @line and !length $line[-1];
492    
493     for (my $i = 0; $i < @line; ++$i) {
494     next unless $line[$i] =~ /^@@ /;
495    
496     $line[$i] =~ /^@@ -(\d+)(?:,\d+)?(?: \+\d+(?:,\d+)?)? @@(.*)$/
497     or die "normalize_udiff: bad hunk header '$line[$i]'\n";
498    
499     my ($old_start, $suffix) = ($1, $2);
500     my ($old_count, $new_count) = (0, 0);
501     my %old_line;
502     my $j = $i + 1;
503    
504     # GNU patch mishandles hunks with more prefix than suffix context.
505     # Make the first context line an identity change without losing it.
506     # see https://unix.stackexchange.com/questions/414542/does-patch-work-with-asymmetric-context
507     splice @line, $i + 1, 1, "-$1", "+$1"
508     if $line[$i + 1] =~ /^ (.*)\z/;
509    
510     for (; $j < @line; ++$j) {
511     last if $line[$j] =~ /^@@ /;
512    
513     my $tag = substr $line[$j], 0, 1;
514    
515     if ($tag eq " ") {
516     ++$old_count;
517     ++$new_count;
518     $old_line{substr $line[$j], 1} = 1;
519     } elsif ($tag eq "-") {
520     ++$old_count;
521     $old_line{substr $line[$j], 1} = 1;
522     } elsif ($tag eq "+") {
523     ++$new_count;
524     } elsif ($line[$j] eq "\\ No newline at end of file") {
525     # Marker belongs to the preceding body line and counts neither side.
526     } else {
527     die "normalize_udiff: bad hunk line '$line[$j]'\n";
528     }
529     }
530    
531     $old_count or $new_count
532     or die "normalize_udiff: empty hunk at old line $old_start\n";
533    
534     $old_count or !$old_start
535     or die "normalize_udiff: hunk " . ($hunks + 1)
536     . " needs old-side context for insertion after old line $old_start\n";
537    
538     my $new_start = $old_start + $delta;
539     ++$new_start unless $old_count;
540     --$new_start unless $new_count;
541    
542     $new_start >= 0
543     or die "normalize_udiff: invalid new line $new_start\n";
544    
545     $line[$i] = sprintf "@@ -%d,%d +%d,%d @@%s",
546     $old_start, $old_count, $new_start, $new_count, $suffix;
547    
548     $delta += $new_count - $old_count;
549     ++$hunks;
550     my $max_offset = keys %old_line;
551     --$max_offset
552     if $max_offset;
553     push @max_offset, $max_offset;
554     $i = $j - 1;
555     }
556    
557     $hunks
558     or die "normalize_udiff: no hunks\n";
559    
560     (join ("\n", @line, ""), \@max_offset)
561     }
562    
563     sub parse_patch_output {
564     my ($output) = @_;
565     my (@hunks, @unparsed);
566    
567     for (split /\n/, $output) {
568     if (/^Hunk #(\d+) (succeeded|FAILED|ignored) at (-?\d+)(?: with fuzz (\d+))?(?: \(offset (-?\d+) lines?\))?\.\z/) {
569     push @hunks, {
570     hunk => 0 + $1,
571     success => $2 eq "succeeded",
572     line => 0 + $3,
573     fuzz => 0 + $4,
574     offset => 0 + $5,
575     };
576     } elsif (!length
577     || /^Hmm\.\.\. +Looks like a unified diff to me\.\.\.\z/
578     || /^The text leading up to this was:\z/
579     || /^-+\z/ || /^\|/
580     || /^patching file / || /^checking file /
581     || /^Using Plan [AB]\.\.\.\z/ || /^done\z/) {
582     # GNU patch --verbose framing, not hunk results.
583     } else {
584     push @unparsed, $_;
585     }
586     }
587    
588     { hunks => \@hunks, unparsed => \@unparsed }
589     }
590    
591     sub check_patch_output {
592     my ($status, $output, $max_offset) = @_;
593     my $parsed = parse_patch_output $output;
594     my $hunks = $parsed->{hunks};
595    
596     die "patch failed (wait status $status):\n$output"
597     if $status;
598     die "patch: unrecognized output:\n" . (join "\n", @{$parsed->{unparsed}}) . "\n"
599     if @{$parsed->{unparsed}};
600     die "patch: expected " . @$max_offset . " hunks, got " . @$hunks . "\n"
601     unless @$hunks == @$max_offset && @$hunks;
602    
603     for my $i (0 .. $#$hunks) {
604     my $hunk = $hunks->[$i];
605    
606     die "patch: unexpected hunk number $hunk->{hunk}\n"
607     unless $hunk->{hunk} == $i + 1;
608     die "patch: hunk $hunk->{hunk} failed\n"
609     unless $hunk->{success};
610     die "patch: hunk $hunk->{hunk} used fuzz $hunk->{fuzz}\n"
611     if $hunk->{fuzz};
612     die "patch: hunk $hunk->{hunk} offset $hunk->{offset} exceeds $max_offset->[$i]\n"
613     if abs $hunk->{offset} > $max_offset->[$i];
614     }
615    
616     $hunks
617     }
618    
619     sub apply_udiff {
620     my ($input, $output, $diff) = @_;
621     my ($normalized, $max_offset) = normalize_udiff $diff;
622    
623     require AnyEvent::Util;
624     require File::Spec;
625     require File::Basename;
626     require Fcntl;
627    
628     $input = File::Spec->rel2abs ($input);
629     $output = File::Spec->rel2abs ($output);
630     my $directory = File::Basename::dirname ($output);
631    
632     sysopen my $fh, $output, Fcntl::O_WRONLY () | Fcntl::O_CREAT () | Fcntl::O_EXCL (), 0600
633     or die "$output: $!\n";
634     close $fh or do { my $error = $!; unlink $output; die "$output: $error\n" };
635    
636     my $cv = AE::cv ();
637     my $log;
638     my $process = eval {
639     AnyEvent::Util::run_cmd ([
640     "patch", "--verbose", "--batch", "--forward", "--binary", "--fuzz=0",
641     "--no-backup-if-mismatch", "--reject-file=-", "--output=$output", "--", $input,
642     ],
643     "<" => \$normalized,
644     ">" => \$log,
645     close_all => 1,
646     on_prepare => sub {
647     $ENV{LC_ALL} = "C";
648     chdir $directory or die "$directory: $!\n";
649     defined POSIX::dup2 (1, 2) or die "dup2: $!\n";
650     },
651     );
652     };
653    
654     if ($@) {
655     my $error = $@;
656     unlink $output;
657     die $error;
658     }
659    
660     $process->cb (sub {
661     my $hunks;
662     eval { $hunks = check_patch_output $_[0]->recv, $log, $max_offset; 1 } or do {
663     my $error = $@;
664     unlink $output;
665     $cv->croak ($error);
666     return;
667     };
668    
669     $cv->send ($hunks);
670     });
671    
672     $cv
673     }
674    
675 root 1.1 sub _clean_text($) {
676     my ($text) = @_;
677    
678     my ($prefix) = (join "\n", grep /\S/, split /\n/, $text)
679     =~ /\A([ \t]*)[^\n]*(?:\n\1[^\n]*)*\z/;
680    
681     $text =~ s/^\Q$prefix\E//mg;
682     $text =~ s/\A\s+//;
683     $text =~ s/\s+\z//;
684     $text
685     }
686    
687     package AnyEvent::MCP::Base;
688    
689     sub new {
690     my ($class, %kv) = @_;
691 root 1.19
692 root 1.1 bless \%kv, $class
693     }
694    
695     sub event {
696     my ($self, $event, @args) = @_;
697 root 1.19
698 root 1.1 ($self->{"on_$event"} or return)->($self, @args)
699     }
700    
701     =head1 SEE ALSO
702    
703 root 1.16 L<AnyEvent::MCP::Intro>, L<AnyEvent::MCP::Server::Simple>,
704 root 1.23 L<AnyEvent::MCP::Helper>, L<mcp-ask-user>, L<anyevent-mcp-form-elicitation>,
705 root 1.1 L<AnyEvent>, L<https://modelcontextprotocol.io/>
706    
707     =head1 AUTHOR
708    
709     The main author of this module is OpenAI ChatGPT.
710    
711     Additional guidance received by:
712    
713     Marc Lehmann <schmorp@schmorp.de>
714     http://home.schmorp.de/
715    
716     =cut
717    
718     1
719