ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/AnyEvent-MCP/lib/AnyEvent/MCP.pm
Revision: 1.47
Committed: Mon Nov 9 15:54:25 2026 UTC (3 days, 20 hours ago) by root
Branch: MAIN
CVS Tags: rel-0_9, HEAD
Changes since 1.46: +1 -1 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 AnyEvent::MCP - Model Context Protocol client/server support
4
5 =head1 SYNOPSIS
6
7 use AnyEvent::MCP::Server::Simple;
8
9 my $options = mcp_getoptions ...;
10 mcp_tool ...;
11 mcp_start %$options;
12
13 or use the object interface directly:
14
15 use AnyEvent::MCP::Server ();
16
17 my $server = AnyEvent::MCP::Server->new (...);
18
19 =head1 DESCRIPTION
20
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
34 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
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 =over
57
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::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 =item L<AnyEvent::MCP::Server::Permissions>
97
98 Action authorization and sandbox-grant handling for MCP servers.
99
100 =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 =item L<AnyEvent::MCP::Workdirs>
113
114 Persistent workdir identifiers; state-file locking and replacement are
115 provided by this module.
116
117 =item L<AnyEvent::MCP::Extension>
118
119 Private authenticated raw-TCP protocols sharing an HTTP listener.
120
121 =back
122
123 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 The interfaces are still being designed. L<AnyEvent::MCP::Intro> explains
130 the installed programs; module-specific POD describes their Perl APIs.
131
132 =head1 TUTORIAL
133
134 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
149
150 =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 [instance example]
166 bind = 127.0.0.1:31740
167 tokens = first-token,second-token
168
169 [program mcp-fs]
170 mount.opt = ro:/opt
171
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 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 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 C<[program PROGRAM]> supplies settings for the invoked program basename;
191 C<[instance NAME]> overrides them for a named instance.
192 HTTP-capable executables use C<--name NAME> to select these settings, with
193 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 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 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 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 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 All accepted tokens work in the URL path or Authorization bearer header.
208 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
221 C<mcp_start> resolves named settings and requires the selected token for
222 HTTP requests. Explicit tokens replace the derived managed users.
223 The low-level HTTP transport does not resolve configuration itself.
224 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
229 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 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 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 C<PERL_ANYEVENT_MCP_MULTIBROKER_ALIAS> names a multibroker routing prefix;
240 it does not change the service-instance name.
241 C<PERL_ANYEVENT_MCP_PERMISSION_HELPER> selects the external asking executable.
242 C<PERL_ANYEVENT_MCP_PERMISSION_ASK> selects the permission engine's
243 asking default: C<helper>, C<tty>, C<yad>, C<auto>, C<mcp> or C<none>. See
244 L<AnyEvent::MCP::Intro/Choose where to ask> for the behavior and client
245 requirements.
246 Standard C<HOME> and C<XDG_CONFIG_HOME> keep their standard names.
247
248 =head1 FUNCTIONS
249
250 =over
251
252 =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 =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 =item $bytes = AnyEvent::MCP::random_bytes $length
268
269 Returns exactly C<$length> random bytes. Tries C</dev/urandom>, then an external
270 C<openssl rand> command. No additional random-number module is required.
271 Dies if neither source supplies the requested bytes successfully.
272 C<$length> must be a nonnegative integer; zero returns an empty string.
273
274 =back
275
276 =head2 PATCH UTILITIES
277
278 Since applying patches to files is a common task for MCP servers, the
279 following utility functions can be used to ease that job. Together, they
280 define a common patch format (unified diff or a simplified version of it,
281 with fewer requirements to make it easier for the LLM to generate), helpers
282 to apply the patch and a helper to verify if the patch was applied
283 with sufficient precision.
284
285 Use C<apply_udiff> to normalize, apply and validate a patch in one operation.
286 It writes a separate output file; the caller checks freshness and metadata
287 and commits the result.
288
289 For an application-specific applier, the workflow is:
290
291 =over
292
293 =item 1. AnyEvent::MCP::normalize_udiff
294
295 Returns a unified diff for GNU patch and the permitted hunk offsets.
296
297 =item 2. Apply the normalized diff
298
299 Run GNU patch with fuzz zero and capture its verbose output.
300
301 =item 3. AnyEvent::MCP::check_patch_output
302
303 Checks the process status and output against the permitted offsets.
304 C<parse_patch_output> is available separately when only parsing is needed.
305
306 =back
307
308 =head3 Input format
309
310 The patch format accepted by normalize_udiff is an extension to the
311 (essentially undocumented) unified diff format.
312
313 In a hunk header, only the old start C<-N> matters. It is the old-file line
314 of the first hunk body line, including context, not necessarily the first
315 changed line. Counts and the new-file position are ignored and recomputed.
316 Both ordinary headers and the abbreviated form are accepted:
317
318 @@ -42,99 +123,99 @@
319 @@ -42 @@
320
321 Optional text after the closing C<@@> is preserved. File headers and other
322 text before the first hunk are preserved; a diff must describe one file.
323
324 Hunk body lines use the usual prefixes: a space for unchanged context,
325 C<-> for removed lines and C<+> for added lines. The
326 C<\ No newline at end of file> marker is preserved and counts neither side.
327 Trailing empty lines outside the final hunk are discarded.
328
329 Pure insertion is permitted only at the beginning, using old start C<0>.
330 For example, C<@@ -0 @@> with one added line becomes C<@@ -0,0 +1,1 @@>.
331 An empty old-side range names the line before the insertion: C<-0,0>
332 means before line 1, whereas C<-1,0> would mean after line 1 and is rejected
333 here. Nonempty ranges use one-based line numbers.
334
335 An insertion elsewhere needs old-side context. Empty hunks are rejected.
336 New positions include the cumulative line-count changes of preceding hunks.
337
338 =head3 Applying the result
339
340 The C<normalize_udiff> function does not apply the patch, check file
341 contents or guard against concurrent edits. The caller must supply those
342 checks, if required.
343
344 Context must match exactly: apply with fuzz zero. For each hunk,
345 C<$max_offset> permits relocation by at most the number of distinct old-side
346 lines minus one, or zero for a pure insertion. Repeated old-side lines do
347 not increase this allowance. The applier must enforce these limits; an
348 unrestricted search for matching context can select the wrong occurrence.
349
350 For live Vim buffers, also require the expected changedtick to match.
351 File-based users need an equivalent check before replacing the file.
352
353 To work around GNU patch's handling of asymmetric context, the
354 first context line of each hunk is rewritten as an identity
355 removal/addition. The line contents and old/new line counts are
356 unchanged. This is normalization for the patch applier, not a relaxation
357 of context matching.
358
359 =over
360
361 =item ($diff, $max_offset) = AnyEvent::MCP::normalize_udiff $diff
362
363 Normalizes a relaxed, single-file unified diff into ordinary unified diff
364 syntax. Returns the normalized C<$diff> and an array reference C<$max_offset>
365 containing the permitted whole-hunk relocation for each hunk, in order.
366 Dies on malformed hunks or when no hunks are found.
367
368 Here is a suitable description for the diff format, e.g. in a tool
369 definition:
370
371 Apply a unified diff to one regular file. Hunk counts are recomputed,
372 fuzz is disabled, whole-hunk relocation is limited per hunk by distinct
373 old-side lines. Pure insertion is allowed only at the beginning;
374 elsewhere include old-side context.
375
376 =item $cv = AnyEvent::MCP::apply_udiff $input, $output, $diff
377
378 Asynchronously applies the relaxed C<$diff> to the input file C<$input>,
379 writing a separate output file C<$output>. C<$diff> must contain bytes;
380 callers working with characters must encode them for the file themselves.
381
382 Normalizes the diff, runs GNU patch using C<AnyEvent::Util::run_cmd>, and
383 validates its verbose output with C<check_patch_output>. The command uses
384 batch/forward mode, binary mode and fuzz zero. No shell is involved.
385
386 C<$output> must not exist. It is reserved with mode 0600; patch runs in its
387 directory so incidental temporary files stay there. Input and output paths
388 are made absolute before starting the command. These must be trusted paths
389 in a directory controlled by the caller.
390
391 Returns a condition variable. Its C<recv> returns the validated hunk array
392 reference, or throws on command/application failure. Setup failures throw
393 synchronously. Failed or partial output is removed. C<$input> is unchanged.
394
395 The caller owns committing the successful output and removing it when no
396 longer needed. Freshness checks, metadata preservation, atomic replacement
397 and application-specific permissions also belong to the caller.
398
399 For your reference, the exact options used are:
400
401 --verbose --batch --forward --binary --fuzz=0
402 --no-backup-if-mismatch
403 --reject-file=- --output=... -- <inputfile>
404
405 =item $parsed = AnyEvent::MCP::parse_patch_output $output
406
407 Parses C<LC_ALL=C> GNU patch C<--verbose> output without judging whether
408 the application is acceptable. C<$parsed> has two fields:
409
410 =over
411
412 =item hunks => [$hunk...]
413
414 Hunk records in output order. Each record contains C<hunk> (the one-based
415 number), C<success>, C<line> (the reported application line), C<fuzz> and
416 the signed C<offset>. Omitted fuzz and offset are zero. Failed and ignored
417 hunks have false C<success>.
418
419 =item unparsed => [$line...]
420
421 Lines not recognized as hunk results or GNU patch's verbose framing.
422 The parser preserves them for callers to interpret or reject.
423
424 =back
425
426 Use verbose output: ordinary patch output omits exact-position successes,
427 so it cannot account for every hunk.
428
429 =item $hunks = AnyEvent::MCP::check_patch_output $status, $output, [$max_offset...]
430
431 Parses C<$output> and validates a complete application. C<$status> is the
432 raw process wait status, as returned by C<waitpid> or
433 C<AnyEvent::Util::run_cmd>, not just the exit code.
434
435 Requires status zero, no unrecognized output, exactly the expected hunks
436 in numbered order, successful application, fuzz zero and each signed offset
437 within its corresponding absolute limit. C<[$max_offset...]> is the array
438 reference returned by C<normalize_udiff>. At least one hunk is required.
439
440 Returns the parsed hunk array reference on success; otherwise dies with a
441 diagnostic. This is also suitable for callers running patch inside their
442 own sandbox.
443
444 =back
445
446 =head1 SMALL UTILITIES
447
448 =over
449
450 =item $name = AnyEvent::MCP::instance_name ($name)
451
452 Validate an instance name, defaulting to C<default> if undefined. Names
453 start with an ASCII letter, digit or underscore, followed by letters,
454 digits, underscores, hyphens, dots, C<@> and C<+>. This intentionally
455 small character set is convenient to use unquoted in a shell.
456
457 =item $lock = AnyEvent::MCP::file_lock ($file)
458
459 Create the parent directory and state file if necessary, and obtain an
460 exclusive advisory lock. If another cooperating writer atomically replaced
461 the pathname while waiting, retry on the new inode. Keep the handle alive
462 through reading, modifying and replacing the file.
463
464 =item AnyEvent::MCP::file_replace ($file, $bytes)
465
466 With the C<file_lock> handle held by the caller, write a complete new version
467 to a temporary file beside C<$file>, preserve the existing file mode and
468 atomically rename it over C<$file>. Remove the temporary on failure.
469 This provides atomic visibility, not crash durability; all writers must
470 follow the locking protocol.
471
472 =item $text = AnyEvent::MCP::file_load ($file, $fh)
473
474 Load a UTF-8 text file as Unicode characters, rejecting invalid UTF-8.
475 Optionally read from an already open filehandle C<$fh>, leaving it open
476 and advancing its position. The caller retains responsibility for locking.
477
478 =item AnyEvent::MCP::file_save ($file, $text)
479
480 Encode Unicode text as UTF-8 and atomically replace C<$file> using
481 C<file_replace>. As with C<file_replace>, the caller holds C<file_lock>.
482
483 =back
484
485 =cut
486
487 package AnyEvent::MCP;
488
489 use common::sense;
490
491 our $VERSION = 0.9;
492
493 sub json_class {
494 our $JSON_CLASS ||= eval { require JSON::XS; "JSON::XS" }
495 || do { require JSON::PP; "JSON::PP" };
496 }
497
498 sub utf8enc($) {
499 my $text = shift;
500
501 utf8::encode $text;
502 $text
503 }
504
505 sub utf8dec($) {
506 my $bytes = shift;
507
508 utf8::decode $bytes
509 or die "invalid UTF-8\n";
510 $bytes
511 }
512
513 sub instance_name {
514 my ($name) = @_;
515
516 $name = "default" unless defined $name;
517
518 $name =~ /\A[A-Za-z0-9_][A-Za-z0-9_.@+-]*\z/
519 or die "Invalid MCP server name '$name'\n";
520
521 $name
522 }
523
524 sub file_lock {
525 my ($file) = @_;
526
527 require File::Path;
528 require File::Basename;
529 require Fcntl;
530 require Errno;
531
532 File::Path::mkpath (File::Basename::dirname ($file), 0, 0700);
533
534 while (1) {
535 my $fh;
536 sysopen $fh, $file, Fcntl::O_RDWR () | Fcntl::O_CREAT () | Fcntl::O_EXCL (), 0600 or do {
537 die "$file: $!\n" unless $! == Errno::EEXIST ();
538 sysopen $fh, $file, Fcntl::O_RDWR () or die "$file: $!\n";
539 };
540
541 my @stat = stat $fh;
542 flock $fh, Fcntl::LOCK_EX () or die "$file: $!\n";
543 my @current = stat $file;
544
545 return $fh if @current && $stat[0] == $current[0] && $stat[1] == $current[1];
546
547 die "$file: $!\n" unless @current || $! == Errno::ENOENT ();
548
549 close $fh;
550 }
551 }
552
553 sub file_replace {
554 my ($file, $bytes) = @_;
555
556 require Fcntl;
557 require Errno;
558
559 my @stat = stat $file;
560 die "$file: $!\n" unless @stat || $! == Errno::ENOENT ();
561 my $temporary = "$file.$$~";
562 sysopen my $fh, $temporary, Fcntl::O_WRONLY () | Fcntl::O_CREAT () | Fcntl::O_EXCL (), 0600
563 or die "$temporary: $!\n";
564 my $ok = eval {
565 print {$fh} $bytes or die "$temporary: $!\n";
566 if (@stat) {
567 my $mode = $stat[2] & 07777;
568 chmod $mode, $fh or die "$temporary: $!\n";
569 }
570 close $fh or die "$temporary: $!\n";
571 rename $temporary, $file or die "$file: $!\n";
572 1
573 };
574
575 my $error = $@;
576 unlink $temporary unless $ok;
577 die $error unless $ok;
578 }
579
580 sub file_load {
581 my ($file, $fh) = @_;
582 my $close = !defined $fh;
583 open $fh, "<:raw", $file or die "$file: $!\n" if $close;
584
585 local $/;
586 my $bytes = <$fh>;
587 defined $bytes or die "$file: $!\n";
588 close $fh or die "$file: $!\n" if $close;
589 utf8dec $bytes
590 }
591
592 sub file_save {
593 my ($file, $text) = @_;
594 file_replace $file, utf8enc $text
595 }
596
597
598 sub random_bytes($) {
599 my ($length) = @_;
600
601 defined $length && $length =~ /\A[0-9]+\z/
602 or die "Invalid random byte count\n";
603 return "" unless $length;
604
605 my $bytes = "";
606
607 if (open my $fh, "<", "/dev/urandom") {
608 binmode $fh;
609 while (length $bytes < $length) {
610 read $fh, $bytes, $length - length $bytes, length $bytes
611 or last;
612 }
613 close $fh;
614
615 return $bytes if length $bytes == $length;
616 }
617
618 if (open my $fh, "-|", "openssl", "rand", $length) {
619 binmode $fh;
620 local $/;
621 $bytes = <$fh>;
622 my $success = close $fh;
623
624 return $bytes
625 if $success && defined $bytes && length $bytes == $length;
626 }
627
628 die "Cannot obtain $length random bytes from /dev/urandom or openssl rand\n";
629 }
630
631 sub normalize_udiff {
632 my $diff = shift;
633 my @line = split /\n/, $diff, -1;
634 my $delta = 0;
635 my $hunks = 0;
636 my @max_offset;
637
638 pop @line while @line and !length $line[-1];
639
640 for (my $i = 0; $i < @line; ++$i) {
641 next unless $line[$i] =~ /^@@ /;
642
643 $line[$i] =~ /^@@ -(\d+)(?:,\d+)?(?: \+\d+(?:,\d+)?)? @@(.*)$/
644 or die "normalize_udiff: bad hunk header '$line[$i]'\n";
645
646 my ($old_start, $suffix) = ($1, $2);
647 my ($old_count, $new_count) = (0, 0);
648 my %old_line;
649 my $j = $i + 1;
650
651 # GNU patch mishandles hunks with more prefix than suffix context.
652 # make the first context line an identity change without losing it.
653 # see https://unix.stackexchange.com/questions/414542/does-patch-work-with-asymmetric-context
654 splice @line, $i + 1, 1, "-$1", "+$1"
655 if $line[$i + 1] =~ /^ (.*)\z/;
656
657 for (; $j < @line; ++$j) {
658 last if $line[$j] =~ /^@@ /;
659
660 my $tag = substr $line[$j], 0, 1;
661
662 if ($tag eq " ") {
663 ++$old_count;
664 ++$new_count;
665 $old_line{substr $line[$j], 1} = 1;
666 } elsif ($tag eq "-") {
667 ++$old_count;
668 $old_line{substr $line[$j], 1} = 1;
669 } elsif ($tag eq "+") {
670 ++$new_count;
671 } elsif ($line[$j] eq "\\ No newline at end of file") {
672 # marker belongs to the preceding body line and counts neither side.
673 } else {
674 die "normalize_udiff: bad hunk line '$line[$j]'\n";
675 }
676 }
677
678 $old_count or $new_count
679 or die "normalize_udiff: empty hunk at old line $old_start\n";
680
681 $old_count or !$old_start
682 or die "normalize_udiff: hunk " . ($hunks + 1)
683 . " needs old-side context for insertion after old line $old_start\n";
684
685 my $new_start = $old_start + $delta;
686 ++$new_start unless $old_count;
687 --$new_start unless $new_count;
688
689 $new_start >= 0
690 or die "normalize_udiff: invalid new line $new_start\n";
691
692 $line[$i] = sprintf "@@ -%d,%d +%d,%d @@%s",
693 $old_start, $old_count, $new_start, $new_count, $suffix;
694
695 $delta += $new_count - $old_count;
696 ++$hunks;
697 my $max_offset = keys %old_line;
698 --$max_offset
699 if $max_offset;
700 push @max_offset, $max_offset;
701 $i = $j - 1;
702 }
703
704 $hunks
705 or die "normalize_udiff: no hunks\n";
706
707 ((join "\n", @line, ""), \@max_offset)
708 }
709
710 sub parse_patch_output {
711 my ($output) = @_;
712 my (@hunks, @unparsed);
713
714 for (split /\n/, $output) {
715 if (/^Hunk #(\d+) (succeeded|FAILED|ignored) at (-?\d+)(?: with fuzz (\d+))?(?: \(offset (-?\d+) lines?\))?\.\z/) {
716 push @hunks, {
717 hunk => 0 + $1,
718 success => $2 eq "succeeded",
719 line => 0 + $3,
720 fuzz => 0 + $4,
721 offset => 0 + $5,
722 };
723 } elsif (!length
724 || /^Hmm\.\.\. +Looks like a unified diff to me\.\.\.\z/
725 || /^The text leading up to this was:\z/
726 || /^-+\z/ || /^\|/
727 || /^patching file / || /^checking file /
728 || /^Using Plan [AB]\.\.\.\z/ || /^done\z/) {
729 # GNU patch --verbose framing, not hunk results.
730 } else {
731 push @unparsed, $_;
732 }
733 }
734
735 { hunks => \@hunks, unparsed => \@unparsed }
736 }
737
738 sub check_patch_output {
739 my ($status, $output, $max_offset) = @_;
740 my $parsed = parse_patch_output $output;
741 my $hunks = $parsed->{hunks};
742
743 die "patch failed (wait status $status):\n$output"
744 if $status;
745 die "patch: unrecognized output:\n" . (join "\n", @{$parsed->{unparsed}}) . "\n"
746 if @{$parsed->{unparsed}};
747 die "patch: expected " . @$max_offset . " hunks, got " . @$hunks . "\n"
748 unless @$hunks == @$max_offset && @$hunks;
749
750 for my $i (0 .. $#$hunks) {
751 my $hunk = $hunks->[$i];
752
753 die "patch: unexpected hunk number $hunk->{hunk}\n"
754 unless $hunk->{hunk} == $i + 1;
755 die "patch: hunk $hunk->{hunk} failed\n"
756 unless $hunk->{success};
757 die "patch: hunk $hunk->{hunk} used fuzz $hunk->{fuzz}\n"
758 if $hunk->{fuzz};
759 die "patch: hunk $hunk->{hunk} offset $hunk->{offset} exceeds $max_offset->[$i]\n"
760 if abs $hunk->{offset} > $max_offset->[$i];
761 }
762
763 $hunks
764 }
765
766 sub apply_udiff {
767 my ($input, $output, $diff) = @_;
768 my ($normalized, $max_offset) = normalize_udiff $diff;
769
770 require AnyEvent::Util;
771 require File::Spec;
772 require File::Basename;
773 require Fcntl;
774
775 $input = File::Spec->rel2abs ($input);
776 $output = File::Spec->rel2abs ($output);
777 my $directory = File::Basename::dirname ($output);
778
779 sysopen my $fh, $output, Fcntl::O_WRONLY () | Fcntl::O_CREAT () | Fcntl::O_EXCL (), 0600
780 or die "$output: $!\n";
781 close $fh or do { my $error = $!; unlink $output; die "$output: $error\n" };
782
783 my $cv = AE::cv ();
784 my $log;
785 my $process = eval {
786 AnyEvent::Util::run_cmd ([
787 "patch", "--verbose", "--batch", "--forward", "--binary", "--fuzz=0",
788 "--no-backup-if-mismatch", "--reject-file=-", "--output=$output", "--", $input,
789 ],
790 "<" => \$normalized,
791 ">" => \$log,
792 close_all => 1,
793 on_prepare => sub {
794 $ENV{LC_ALL} = "C";
795 chdir $directory or die "$directory: $!\n";
796 defined POSIX::dup2 (1, 2) or die "dup2: $!\n";
797 },
798 );
799 };
800
801 if ($@) {
802 my $error = $@;
803 unlink $output;
804 die $error;
805 }
806
807 $process->cb (sub {
808 my $hunks;
809 eval { $hunks = check_patch_output $_[0]->recv, $log, $max_offset; 1 } or do {
810 my $error = $@;
811 unlink $output;
812 $cv->croak ($error);
813 return;
814 };
815
816 $cv->send ($hunks);
817 });
818
819 $cv
820 }
821
822 sub _clean_text($) {
823 my ($text) = @_;
824
825 my ($prefix) = (join "\n", grep /\S/, split /\n/, $text)
826 =~ /\A([ \t]*)[^\n]*(?:\n\1[^\n]*)*\z/;
827
828 $text =~ s/^\Q$prefix\E//mg;
829 $text =~ s/\A\s+//;
830 $text =~ s/\s+\z//;
831 $text
832 }
833
834 package AnyEvent::MCP::Base;
835
836 sub new {
837 my ($class, %kv) = @_;
838
839 bless \%kv, $class
840 }
841
842 sub event {
843 my ($self, $event, @args) = @_;
844
845 ($self->{"on_$event"} or return)->($self, @args)
846 }
847
848 =head1 SEE ALSO
849
850 L<AnyEvent::MCP::Intro>, L<AnyEvent::MCP::Server::Simple>,
851 L<AnyEvent::MCP::Helper>, L<mcp-ask-user>, L<anyevent-mcp-form-elicitation>,
852 L<AnyEvent>, L<https://modelcontextprotocol.io/>
853
854 =head1 AUTHOR
855
856 The main author of this module is OpenAI ChatGPT.
857
858 Additional guidance received by:
859
860 Marc Lehmann <schmorp@schmorp.de>
861 http://home.schmorp.de/
862
863 =cut
864
865 1
866