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