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