ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP.pm
Revision: 1.13
Committed: Sun Oct 11 01:02:38 2026 UTC (3 weeks, 2 days ago) by root
Branch: MAIN
Changes since 1.12: +2 -5 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.6 use AnyEvent::MCP::Server::Simple ();
8 root 1.1
9     mcp_tool ...
10 root 1.3 mcp_start;
11 root 1.1
12     or use the object interface directly:
13    
14 root 1.6 use AnyEvent::MCP::Server ();
15 root 1.1
16     my $server = AnyEvent::MCP::Server->new (...);
17    
18     =head1 DESCRIPTION
19    
20     AnyEvent::MCP provides event-driven Model Context Protocol client and server
21     support.
22    
23     The usual way to implement a small MCP server is
24     L<AnyEvent::MCP::Server::Simple>, which provides a function-based interface and
25     creates the underlying server objects when a transport such as stdio or HTTP is
26     started.
27    
28     The lower-level object interfaces are intended for applications that need to
29     manage MCP connections, servers or reusable tool registries directly.
30    
31     =head2 MODULES
32    
33 root 1.7 =over
34 root 1.1
35     =item L<AnyEvent::MCP::Server::Simple>
36    
37     Function-based interface for implementing ordinary MCP servers.
38    
39     =item L<AnyEvent::MCP::Server>
40    
41     Object-oriented MCP server interface.
42    
43     =item L<AnyEvent::MCP::Client>
44    
45     Object-oriented MCP client interface.
46    
47     =item L<AnyEvent::MCP::Server::Toolset>
48    
49     Reusable registry of MCP tool declarations.
50    
51     =item L<AnyEvent::MCP::Server::Permissions>
52    
53     Permission and capability handling for MCP servers.
54    
55     =back
56    
57 root 1.4 C<mcp-multibroker> combines downstream HTTP and stdio servers under tool-name
58     prefixes. It uses the same client, server and permission helper facilities,
59     including modern input forwarding and local permission prompts for legacy
60     callers. Run C<mcp-multibroker --help> for command-line syntax; its manual
61     describes authentication and configuration.
62    
63 root 1.9 The interfaces are still being designed. The walkthrough below explains the
64     installed programs; module-specific POD documents their Perl APIs.
65    
66     =head1 GETTING STARTED
67    
68     This distribution contains an MCP client/server library and several ready-to-use
69     programs. You do not need to write Perl to use them.
70    
71     =over
72    
73     =item C<mcp-fs>
74    
75     A filesystem and command server. Each operation runs in a Linux bubblewrap
76     sandbox with only the filesystem and other capabilities approved for that
77     operation. Read, write, search, patch, path operations and command execution
78     are exposed as MCP tools.
79    
80     =item C<mcp-multibroker>
81    
82     Combines several MCP servers behind one connection. Each downstream server has
83     a prefix, so a C<file-read> tool belonging to C<files> becomes
84     C<files.file-read>. Its clients may connect over HTTP or be launched over
85     stdio. The multibroker itself can serve HTTP or stdio.
86    
87     =item C<anyevent-mcp> and C<anyevent-mcp-askpermission>
88    
89     Manage shared credentials/rules and ask the local user for permission,
90     respectively. Normally the permission helper is started automatically.
91    
92     =back
93    
94     =head2 Start with one filesystem server
95    
96     Install this Perl distribution first. On Debian or Ubuntu, install the
97     runtime sandbox and optional graphical permission helper with:
98    
99     sudo apt install bubblewrap yad
100    
101     The executable is called C<bwrap>, although the package is C<bubblewrap>.
102     C<yad> provides graphical approval dialogs; without it, the default helper
103     tries C</dev/tty> and denies if no terminal is available. Unprivileged user
104     namespaces must be available. C<mcp-fs> also uses GNU C<find>, C<grep>,
105     C<patch> and C<sed>, and a compatible Perl interpreter. On a minimal
106     Debian installation, these can be installed with:
107    
108     sudo apt install findutils grep patch sed perl
109    
110     Tools whose required programs are missing are omitted at startup.
111    
112     Give the server one or more directories that you actually intend to make
113     available. For example:
114    
115 root 1.12 mcp-fs --name code --bind :31741 /home/alice/src/project
116 root 1.9
117 root 1.12 This starts an authenticated HTTP MCP server without printing its URL.
118     C<mcp-fs --name code --print-url> obtains the credential-bearing URL;
119     keep it private. The same token also works in an Authorization bearer
120     header. C<--bind :0> selects an ephemeral port, for which a URL cannot
121     be predicted. The permission-rule file is printed on stderr.
122 root 1.9
123     For a local MCP client that starts its servers as subprocesses, use stdio:
124    
125     mcp-fs --stdio /home/alice/src/project
126    
127     The MCP client, not a human terminal, should start this process: stdin and
128     stdout carry protocol messages. For a quick manual sandbox test without MCP
129     or permission prompting, run from the intended workdir:
130    
131     mcp-fs --exec-cmd workdir-read -- ls
132    
133     C<--exec-cmd> runs the supplied command with exactly the named capabilities,
134     without consulting saved permission rules. It is a local debugging facility.
135    
136     Within MCP, begin with the C<workdirs> tool to inspect the configured
137     workdirs and capabilities. Relative paths are resolved against the selected
138     workdir; access beyond the granted mounts needs additional permission.
139     Providing a workdir does not give arbitrary commands unrestricted host access.
140     See L<mcp-fs/CAPABILITIES> and L<mcp-fs/LIMITATIONS>.
141    
142     Once the client is connected, you can just ask the LLM to use the tools,
143     for example:
144    
145     Show me the available workdirs, find the TODO comments in my project,
146     and suggest the next task. Do not modify any files.
147    
148     The client can call C<workdirs> and C<grep> itself; you need not know the
149     MCP request format or write your own integration.
150    
151     =head2 Combine servers with mcp-multibroker
152    
153     Rather than configure each MCP client with every server, configure them
154     once behind the multibroker. For example, to expose two distinct filesystem
155     servers over one HTTP endpoint:
156    
157     mcp-multibroker code 'stdio:mcp-fs /home/alice/src/project' \
158     notes 'stdio:mcp-fs /home/alice/notes'
159    
160     Each C<stdio:> value is a shell command; quote it as one argument.
161     The multibroker starts the child with the correct stdio environment
162     and an alias equal to its prefix. That alias is also the default
163     C<mcp-fs> permission profile, keeping permissions for C<code> and
164     C<notes> separate.
165    
166     The multibroker prints its own authenticated HTTP URL. A client sees
167     tools such as C<code.file-read>, C<code.file-patch> and
168     C<notes.file-read>. The additional C<discover-brokers>, C<list-tools>
169     and C<call-tool> tools support explicit discovery and dispatch.
170     The client normally can call the prefixed tools directly.
171    
172     If the LLM is still using an old tool list after a downstream server
173     becomes available or changes its tools, simply tell it:
174    
175     Call discover-brokers and list-tools again, then use the newly
176     discovered tools.
177    
178     C<discover-brokers> connects to reachable servers and retrieves their
179     instructions; C<list-tools> requests their current tool lists. C<call-tool>
180     can dispatch a newly discovered prefixed tool even when the client has not
181     refreshed its own direct tool list yet. If the client hides or caches the
182     broker's tools, reconnect or refresh the MCP connection in that client.
183     Changing the broker's configured prefix/URL pairs requires restarting
184     C<mcp-multibroker>; discovery cannot change its command-line configuration.
185    
186     The same broker can mix remote HTTP servers with local subprocesses.
187     For example, an installation might combine office and automation tools:
188    
189     mcp-multibroker \
190     office http://localhost:8766/mcp \
191     homeassistant http://homeassistant.local:8123/api/mcp \
192     --bearer TOKEN \
193     marco http://marco.example.net:31746/COOKIE/
194    
195     Or combine a custom application server, remote machines and a third-party
196     stdio server:
197    
198     mcp-multibroker \
199     agni 'stdio:/opt/agni/bin/agni-mcp' \
200     cerebro http://cerebro.example.net:31748/COOKIE/ \
201     chrome 'stdio:npx -y chrome-devtools-mcp@latest --no-usage-statistics'
202    
203     These are illustrative hostnames and redacted credentials, not usable
204     endpoints. C<--bearer> belongs immediately after the HTTP server it
205     authenticates. A cookie URL already carries its credential. Never publish
206     or check in either kind of secret. The broker does not impose the C<mcp-fs>
207     bubblewrap sandbox on other servers: those processes and services have their
208     own security boundaries. A shell wrapper can use C<exec mcp-multibroker>
209     to replace itself with the broker process.
210    
211     To start the multibroker itself over stdio, let the client set
212     C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL=stdio>, or use a command like:
213    
214     PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL=stdio \
215     mcp-multibroker code 'stdio:mcp-fs /home/alice/src/project'
216    
217     The broker can also connect to existing HTTP MCP servers:
218    
219     mcp-multibroker files 'stdio:mcp-fs /home/alice/src/project' \
220     remote https://example.org/mcp/ --bearer TOKEN
221    
222     The token is a credential; avoid exposing it in shell history or process
223     arguments in a real deployment. An HTTP client can instead use
224     C<[client PREFIX]> in the private global configuration. See
225     L<mcp-multibroker> and L</GLOBAL CONFIGURATION>.
226    
227     =head2 Connect an AI client
228    
229     These are examples for clients with MCP support. Paths are illustrative;
230     substitute your installed commands and real workdirs. Client interfaces
231     evolve independently of this module, so consult their documentation if an
232     option has changed.
233    
234     =head3 OpenAI Codex CLI
235    
236     Codex can start the broker locally over stdio:
237    
238     codex mcp add mytools --env PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL=stdio \
239     -- mcp-multibroker \
240     files 'stdio:mcp-fs /home/alice/src/project'
241     codex mcp list
242    
243     Use C</mcp> within Codex to inspect connections.
244     See L<Codex MCP documentation|https://learn.chatgpt.com/docs/extend/mcp>.
245    
246     =head3 Anthropic Claude Code
247    
248     Claude Code can start the same broker:
249    
250     claude mcp add --transport stdio --scope user mytools \
251     --env PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL=stdio -- \
252     mcp-multibroker files 'stdio:mcp-fs /home/alice/src/project'
253     claude mcp list
254    
255     Use C</mcp> within Claude Code for connection status. See
256     L<Claude Code MCP documentation|https://code.claude.com/docs/en/mcp>.
257    
258     =head3 Google Gemini CLI
259    
260     Add a local broker to C<~/.gemini/settings.json> (merge with any existing
261     settings):
262    
263     {
264     "mcpServers": {
265     "mytools": {
266     "command": "mcp-multibroker",
267     "args": [
268     "files", "stdio:mcp-fs /home/alice/src/project"
269     ],
270     "env": {
271     "PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL": "stdio"
272     }
273     }
274     }
275     }
276    
277     Use C</mcp> in Gemini CLI to inspect the connection.
278     See L<Gemini CLI MCP documentation|https://geminicli.com/docs/tools/mcp-server/>.
279    
280     =head3 ChatGPT web
281    
282     ChatGPT is a hosted client and cannot spawn a local stdio process or
283     connect directly to a local loopback server. Start C<mcp-multibroker> in
284     HTTP mode and connect it through ChatGPT's custom MCP plugin setup.
285     For a private local server, use an appropriately configured Secure MCP
286     Tunnel rather than making the machine publicly accessible. The connection
287     is subject to the client's plan, workspace and feature permissions.
288     See L<ChatGPT custom MCP servers|https://developers.openai.com/api/docs/guides/custom-mcp-server>
289     and L<Secure MCP Tunnel|https://developers.openai.com/blog/connect-private-mcp-servers-to-openai-products>.
290    
291     =head2 Permissions and approval dialogs
292    
293     C<mcp-fs> does not treat a connected model as fully trusted. Each tool
294     asks for the authority it needs; you can allow or deny the request once,
295     or save a rule for subsequent matching requests. Its default prompting
296     mode is C<--ask helper>, using C<anyevent-mcp-askpermission> on the
297     server's machine (YAD or C</dev/tty>). Closing the prompt denies once.
298    
299     For a client that supports the newer MCP form-elicitation protocol, try:
300    
301     mcp-fs --ask auto --stdio /home/alice/src/project
302    
303     C<--ask auto> uses MCP elicitation when the client advertises the necessary
304     support, otherwise the local helper. C<--ask mcp> requires elicitation and
305     fails if it is unavailable; it does not silently fall back. Not all MCP
306     clients or versions support the required input/continuation flow, even if
307     they support ordinary tool calls. For an ordinary stdio broker configuration,
308     put the chosen option in the child command:
309    
310     mcp-multibroker files \
311     'stdio:mcp-fs --ask auto /home/alice/src/project'
312    
313     Permission profiles are stored under C<~/.config/anyevent-mcp/> (or
314     C<$XDG_CONFIG_HOME/anyevent-mcp/>). A child launched by the broker
315     normally uses its prefix as the profile name; use C<--profile NAME>
316     to override it. Review persistent rules rather than granting
317     C<world-write> casually. Run C<anyevent-mcp --help> for the credential
318     and rule-management commands; see
319     L<AnyEvent::MCP::Server::Permissions> for rule syntax and ordering.
320    
321     =head2 Further documentation
322    
323     perldoc mcp-fs
324     perldoc mcp-multibroker
325     perldoc anyevent-mcp
326     perldoc AnyEvent::MCP::Server::Simple
327     perldoc AnyEvent::MCP::Server::Permissions
328    
329     The programs describe their own command-line options. If a client reports
330     no tools, first check that the selected program starts and that its required
331     utilities are detected. Stdio servers must not print diagnostics or banners
332     to stdout. For HTTP, check that the client can reach the configured URL and
333     has the correct authentication credentials.
334 root 1.1
335 root 1.2 =head1 GLOBAL CONFIGURATION
336    
337     The shared per-user configuration is C<$XDG_CONFIG_HOME/anyevent-mcp/global.ini>,
338     or C<$HOME/.config/anyevent-mcp/global.ini> when XDG_CONFIG_HOME is unset.
339     L<AnyEvent::MCP::Config> loads and holds it; loading this module does not access
340     configuration files.
341    
342     The file is UTF-8 and uses sections with single-line C<key = value> pairs.
343     Values are unquoted strings. Blank lines and whole-line comments beginning
344     with C<#> or C<;> are ignored; inline comment characters remain part of the
345     value. Later occurrences of a key replace earlier ones in the same section.
346    
347     [global]
348     secret = ...128 hexadecimal digits...
349    
350     [server example]
351 root 1.11 bind = 127.0.0.1:31740
352 root 1.13 tokens = first-token,second-token
353 root 1.2
354     [mcp-fs]
355     utility = /path/to/utility
356    
357     Section names are unrestricted. Application-specific sections, including
358     exports, utility paths and user settings, are documented by their applications.
359    
360     C<[global]> holds C<secret>, a 64-byte master key encoded as 128 hexadecimal
361     digits. The file must be owned by the current user, with no group or other
362     permissions. A missing secret is generated automatically on first configuration
363     access and saved when possible. Existing secrets are never replaced implicitly.
364     An invalid secret is an error.
365    
366     If saving fails, Config warns and retains the secret in memory. Credentials
367     then remain stable only for that process; other processes cannot derive the
368     same credentials. If randomness is unavailable, supply a securely generated
369     secret in the file.
370    
371 root 1.13 C<[server NAME]> holds named-server settings.
372 root 1.11 HTTP-capable executables use C<--name NAME> to select these settings, with
373     the executable basename as the default name. Names consist of an ASCII
374     letter or underscore followed by ASCII letters, digits, underscores or
375     hyphens. Their C<--bind ADDRESS[:PORT]> overrides the configured C<bind>.
376     The default bind address is IPv4 loopback with a port derived from the name
377     and master secret in the range 16384..32767; C<:0> requests an ephemeral
378 root 1.12 port. C<--print-url> prints the URL using the first accepted token without
379     listening. Named server settings also include C<path>, C<url_host>,
380     C<url_scheme> and C<tokens>. C<tokens> is a comma-separated list of
381     URL-safe strings. Repeated C<--token TOKEN> options override this list.
382     All accepted tokens work in the URL path or Authorization bearer header.
383     Without configured tokens, one server-specific token is derived.
384 root 1.11
385 root 1.12 C<mcp_start> resolves named settings and requires the selected token for
386     HTTP requests. Explicit tokens replace the derived default.
387 root 1.11 The low-level HTTP transport does not resolve configuration itself.
388     Stdio startup does not access global HTTP configuration. Browser origins
389     are permitted by default, but must still pass MCP authentication.
390 root 1.3
391 root 1.12 C<[client NAME]> supplies HTTP client defaults: C<url>, C<bearer> and
392     C<timeout>. Clients consult it only when explicitly given a C<name>.
393     An unnamed client or HTTP server does not read global configuration.
394 root 1.3 Explicit constructor arguments take precedence over named defaults.
395    
396     =head1 ENVIRONMENT
397    
398     Project-specific environment variables use the C<PERL_ANYEVENT_MCP_> prefix.
399     C<PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL=stdio> selects stdio in Simple.
400 root 1.11 C<PERL_ANYEVENT_MCP_MULTIBROKER_ALIAS> names a multibroker routing prefix;
401     it does not change the service-instance name.
402 root 1.3 C<PERL_ANYEVENT_MCP_PERMISSION_HELPER> selects the external asking executable.
403     Standard C<HOME> and C<XDG_CONFIG_HOME> keep their standard names.
404 root 1.2
405     =head1 FUNCTIONS
406    
407 root 1.6 =over
408    
409 root 1.10 =item $class = AnyEvent::MCP::json_class
410    
411     Returns C<JSON::XS> if available, otherwise C<JSON::PP>. The caller
412     configures a new coder instance as needed.
413    
414 root 1.8 =item $bytes = AnyEvent::MCP::utf8enc $text
415    
416     Return a UTF-8 byte string for C<$text>, leaving the argument unchanged.
417    
418     =item $text = AnyEvent::MCP::utf8dec $bytes
419    
420     Decode a UTF-8 byte string into characters, dying on invalid UTF-8.
421     The argument is left unchanged. For intentional in-place conversions,
422     use Perl's C<utf8::encode> or C<utf8::decode> directly.
423    
424 root 1.6 =item $bytes = AnyEvent::MCP::random_bytes $length
425 root 1.2
426     Returns exactly C<$length> random bytes. Tries C</dev/urandom>, then an external
427     C<openssl rand> command. No additional random-number module is required.
428     Dies if neither source supplies the requested bytes successfully.
429     C<$length> must be a nonnegative integer; zero returns an empty string.
430    
431 root 1.6 =back
432    
433     =head2 PATCH UTILITIES
434    
435 root 1.7 Since applying patches to files is a common task for MCP servers, the
436 root 1.6 following utility functions can be used to ease that job. Together, they
437 root 1.7 define a common patch format (unified diff or a simplified version of it,
438     with fewer requirements to make it easier for the LLM to generate), helpers
439 root 1.6 to apply the patch and a helper to verify if the patch was applied
440     with sufficient precision.
441    
442 root 1.7 Use C<apply_udiff> to normalize, apply and validate a patch in one operation.
443     It writes a separate output file; the caller checks freshness and metadata
444     and commits the result.
445    
446     For an application-specific applier, the workflow is:
447 root 1.6
448     =over
449    
450     =item 1. AnyEvent::MCP::normalize_udiff
451    
452 root 1.7 Returns a unified diff for GNU patch and the permitted hunk offsets.
453    
454     =item 2. Apply the normalized diff
455 root 1.6
456 root 1.7 Run GNU patch with fuzz zero and capture its verbose output.
457 root 1.6
458 root 1.7 =item 3. AnyEvent::MCP::check_patch_output
459    
460     Checks the process status and output against the permitted offsets.
461     C<parse_patch_output> is available separately when only parsing is needed.
462 root 1.5
463 root 1.6 =back
464    
465 root 1.5 =head3 Input format
466    
467 root 1.6 The patch format accepted by normalize_udiff is an extension to the
468     (essentially undocumented) unified diff format.
469    
470 root 1.5 In a hunk header, only the old start C<-N> matters. It is the old-file line
471     of the first hunk body line, including context, not necessarily the first
472     changed line. Counts and the new-file position are ignored and recomputed.
473     Both ordinary headers and the abbreviated form are accepted:
474    
475     @@ -42,99 +123,99 @@
476     @@ -42 @@
477    
478     Optional text after the closing C<@@> is preserved. File headers and other
479     text before the first hunk are preserved; a diff must describe one file.
480    
481     Hunk body lines use the usual prefixes: a space for unchanged context,
482     C<-> for removed lines and C<+> for added lines. The
483     C<\ No newline at end of file> marker is preserved and counts neither side.
484     Trailing empty lines outside the final hunk are discarded.
485    
486 root 1.7 Pure insertion is permitted only at the beginning, using old start C<0>.
487     For example, C<@@ -0 @@> with one added line becomes C<@@ -0,0 +1,1 @@>.
488     An empty old-side range names the line before the insertion: C<-0,0>
489     means before line 1, whereas C<-1,0> would mean after line 1 and is rejected
490     here. Nonempty ranges use one-based line numbers.
491    
492 root 1.5 An insertion elsewhere needs old-side context. Empty hunks are rejected.
493     New positions include the cumulative line-count changes of preceding hunks.
494    
495     =head3 Applying the result
496    
497 root 1.6 The C<normalize_udiff> function does not apply the patch, check file
498     contents or guard against concurrent edits. The caller must supply those
499     checks, if required.
500 root 1.5
501     Context must match exactly: apply with fuzz zero. For each hunk,
502     C<$max_offset> permits relocation by at most the number of distinct old-side
503     lines minus one, or zero for a pure insertion. Repeated old-side lines do
504     not increase this allowance. The applier must enforce these limits; an
505     unrestricted search for matching context can select the wrong occurrence.
506    
507     For live Vim buffers, also require the expected changedtick to match.
508     File-based users need an equivalent check before replacing the file.
509    
510 root 1.6 To work around GNU patch's handling of asymmetric context, the
511     first context line of each hunk is rewritten as an identity
512     removal/addition. The line contents and old/new line counts are
513     unchanged. This is normalization for the patch applier, not a relaxation
514     of context matching.
515 root 1.5
516 root 1.6 =over
517    
518     =item ($diff, $max_offset) = AnyEvent::MCP::normalize_udiff $diff
519    
520     Normalizes a relaxed, single-file unified diff into ordinary unified diff
521     syntax. Returns the normalized C<$diff> and an array reference C<$max_offset>
522     containing the permitted whole-hunk relocation for each hunk, in order.
523     Dies on malformed hunks or when no hunks are found.
524    
525     Here is a suitable description for the diff format, e.g. in a tool
526     definition:
527    
528     Apply a unified diff to one regular file. Hunk counts are recomputed,
529     fuzz is disabled, whole-hunk relocation is limited per hunk by distinct
530     old-side lines. Pure insertion is allowed only at the beginning;
531     elsewhere include old-side context.
532    
533     =item $cv = AnyEvent::MCP::apply_udiff $input, $output, $diff
534    
535     Asynchronously applies the relaxed C<$diff> to the input file C<$input>,
536     writing a separate output file C<$output>. C<$diff> must contain bytes;
537     callers working with characters must encode them for the file themselves.
538    
539     Normalizes the diff, runs GNU patch using C<AnyEvent::Util::run_cmd>, and
540     validates its verbose output with C<check_patch_output>. The command uses
541     batch/forward mode, binary mode and fuzz zero. No shell is involved.
542    
543     C<$output> must not exist. It is reserved with mode 0600; patch runs in its
544     directory so incidental temporary files stay there. Input and output paths
545     are made absolute before starting the command. These must be trusted paths
546     in a directory controlled by the caller.
547    
548     Returns a condition variable. Its C<recv> returns the validated hunk array
549     reference, or throws on command/application failure. Setup failures throw
550     synchronously. Failed or partial output is removed. C<$input> is unchanged.
551    
552     The caller owns committing the successful output and removing it when no
553     longer needed. Freshness checks, metadata preservation, atomic replacement
554     and application-specific permissions also belong to the caller.
555    
556     For your reference, the exact options used are:
557    
558     --verbose --batch --forward --binary --fuzz=0
559     --no-backup-if-mismatch
560     --reject-file=- --output=... -- <inputfile>
561    
562     =item $parsed = AnyEvent::MCP::parse_patch_output $output
563 root 1.5
564     Parses C<LC_ALL=C> GNU patch C<--verbose> output without judging whether
565     the application is acceptable. C<$parsed> has two fields:
566    
567 root 1.6 =over
568 root 1.5
569     =item hunks => [$hunk...]
570    
571     Hunk records in output order. Each record contains C<hunk> (the one-based
572     number), C<success>, C<line> (the reported application line), C<fuzz> and
573     the signed C<offset>. Omitted fuzz and offset are zero. Failed and ignored
574     hunks have false C<success>.
575    
576     =item unparsed => [$line...]
577    
578     Lines not recognized as hunk results or GNU patch's verbose framing.
579     The parser preserves them for callers to interpret or reject.
580    
581     =back
582    
583     Use verbose output: ordinary patch output omits exact-position successes,
584     so it cannot account for every hunk.
585    
586 root 1.6 =item $hunks = AnyEvent::MCP::check_patch_output $status, $output, [$max_offset...]
587 root 1.5
588     Parses C<$output> and validates a complete application. C<$status> is the
589     raw process wait status, as returned by C<waitpid> or
590     C<AnyEvent::Util::run_cmd>, not just the exit code.
591    
592     Requires status zero, no unrecognized output, exactly the expected hunks
593     in numbered order, successful application, fuzz zero and each signed offset
594     within its corresponding absolute limit. C<[$max_offset...]> is the array
595     reference returned by C<normalize_udiff>. At least one hunk is required.
596    
597     Returns the parsed hunk array reference on success; otherwise dies with a
598     diagnostic. This is also suitable for callers running patch inside their
599     own sandbox.
600    
601 root 1.6 =back
602 root 1.5
603 root 1.1 =cut
604    
605     package AnyEvent::MCP;
606    
607     use common::sense;
608     use Digest::SHA ();
609    
610     our $VERSION = '0.0';
611    
612 root 1.10 sub json_class {
613     our $JSON_CLASS ||= eval { require JSON::XS; "JSON::XS" }
614     || do { require JSON::PP; "JSON::PP" };
615     }
616    
617 root 1.8 sub utf8enc($) {
618     my $text = shift;
619    
620     utf8::encode $text;
621     $text
622     }
623    
624     sub utf8dec($) {
625     my $bytes = shift;
626    
627     utf8::decode $bytes
628     or die "invalid UTF-8\n";
629     $bytes
630     }
631    
632 root 1.2 sub random_bytes($) {
633     my ($length) = @_;
634    
635     die "Invalid random byte count\n"
636     unless defined $length && $length =~ /\A[0-9]+\z/;
637     return "" unless $length;
638    
639     my $bytes = "";
640    
641     if (open my $fh, "<", "/dev/urandom") {
642     binmode $fh;
643     while (length $bytes < $length) {
644     read ($fh, $bytes, $length - length ($bytes), length $bytes)
645     or last;
646     }
647     close $fh;
648    
649     return $bytes if length $bytes == $length;
650     }
651    
652     if (open my $fh, "-|", "openssl", "rand", $length) {
653     binmode $fh;
654     local $/;
655     $bytes = <$fh>;
656     my $success = close $fh;
657    
658     return $bytes
659     if $success && defined $bytes && length $bytes == $length;
660     }
661    
662     die "Cannot obtain $length random bytes from /dev/urandom or openssl rand\n";
663     }
664    
665 root 1.1 sub hkdf_sha512_expand($$$) {
666     my ($prk, $info, $l) = @_;
667    
668 root 1.2 64 == length $prk
669     or die "hkdf_sha512_expand: prk length mist be 64 octets";
670    
671 root 1.1 my $t = "";
672     my $okm = "";
673     my $i = 0;
674    
675     do {
676     $t = Digest::SHA::hmac_sha512 $t . $info . chr ++$i, $prk;
677     $okm .= $t;
678     } while $l > length $okm;
679    
680     substr $okm, 0, $l
681     }
682    
683 root 1.5 sub normalize_udiff {
684     my $diff = shift;
685     my @line = split /\n/, $diff, -1;
686     my $delta = 0;
687     my $hunks = 0;
688     my @max_offset;
689    
690     pop @line while @line and !length $line[-1];
691    
692     for (my $i = 0; $i < @line; ++$i) {
693     next unless $line[$i] =~ /^@@ /;
694    
695     $line[$i] =~ /^@@ -(\d+)(?:,\d+)?(?: \+\d+(?:,\d+)?)? @@(.*)$/
696     or die "normalize_udiff: bad hunk header '$line[$i]'\n";
697    
698     my ($old_start, $suffix) = ($1, $2);
699     my ($old_count, $new_count) = (0, 0);
700     my %old_line;
701     my $j = $i + 1;
702    
703     # GNU patch mishandles hunks with more prefix than suffix context.
704     # Make the first context line an identity change without losing it.
705     # see https://unix.stackexchange.com/questions/414542/does-patch-work-with-asymmetric-context
706     splice @line, $i + 1, 1, "-$1", "+$1"
707     if $line[$i + 1] =~ /^ (.*)\z/;
708    
709     for (; $j < @line; ++$j) {
710     last if $line[$j] =~ /^@@ /;
711    
712     my $tag = substr $line[$j], 0, 1;
713    
714     if ($tag eq " ") {
715     ++$old_count;
716     ++$new_count;
717     $old_line{substr $line[$j], 1} = 1;
718     } elsif ($tag eq "-") {
719     ++$old_count;
720     $old_line{substr $line[$j], 1} = 1;
721     } elsif ($tag eq "+") {
722     ++$new_count;
723     } elsif ($line[$j] eq "\\ No newline at end of file") {
724     # Marker belongs to the preceding body line and counts neither side.
725     } else {
726     die "normalize_udiff: bad hunk line '$line[$j]'\n";
727     }
728     }
729    
730     $old_count or $new_count
731     or die "normalize_udiff: empty hunk at old line $old_start\n";
732    
733     $old_count or !$old_start
734     or die "normalize_udiff: hunk " . ($hunks + 1)
735     . " needs old-side context for insertion after old line $old_start\n";
736    
737     my $new_start = $old_start + $delta;
738     ++$new_start unless $old_count;
739     --$new_start unless $new_count;
740    
741     $new_start >= 0
742     or die "normalize_udiff: invalid new line $new_start\n";
743    
744     $line[$i] = sprintf "@@ -%d,%d +%d,%d @@%s",
745     $old_start, $old_count, $new_start, $new_count, $suffix;
746    
747     $delta += $new_count - $old_count;
748     ++$hunks;
749     my $max_offset = keys %old_line;
750     --$max_offset
751     if $max_offset;
752     push @max_offset, $max_offset;
753     $i = $j - 1;
754     }
755    
756     $hunks
757     or die "normalize_udiff: no hunks\n";
758    
759     (join ("\n", @line, ""), \@max_offset)
760     }
761    
762     sub parse_patch_output {
763     my ($output) = @_;
764     my (@hunks, @unparsed);
765    
766     for (split /\n/, $output) {
767     if (/^Hunk #(\d+) (succeeded|FAILED|ignored) at (-?\d+)(?: with fuzz (\d+))?(?: \(offset (-?\d+) lines?\))?\.\z/) {
768     push @hunks, {
769     hunk => 0 + $1,
770     success => $2 eq "succeeded",
771     line => 0 + $3,
772     fuzz => 0 + $4,
773     offset => 0 + $5,
774     };
775     } elsif (!length
776     || /^Hmm\.\.\. +Looks like a unified diff to me\.\.\.\z/
777     || /^The text leading up to this was:\z/
778     || /^-+\z/ || /^\|/
779     || /^patching file / || /^checking file /
780     || /^Using Plan [AB]\.\.\.\z/ || /^done\z/) {
781     # GNU patch --verbose framing, not hunk results.
782     } else {
783     push @unparsed, $_;
784     }
785     }
786    
787     { hunks => \@hunks, unparsed => \@unparsed }
788     }
789    
790     sub check_patch_output {
791     my ($status, $output, $max_offset) = @_;
792     my $parsed = parse_patch_output $output;
793     my $hunks = $parsed->{hunks};
794    
795     die "patch failed (wait status $status):\n$output"
796     if $status;
797     die "patch: unrecognized output:\n" . (join "\n", @{$parsed->{unparsed}}) . "\n"
798     if @{$parsed->{unparsed}};
799     die "patch: expected " . @$max_offset . " hunks, got " . @$hunks . "\n"
800     unless @$hunks == @$max_offset && @$hunks;
801    
802     for my $i (0 .. $#$hunks) {
803     my $hunk = $hunks->[$i];
804    
805     die "patch: unexpected hunk number $hunk->{hunk}\n"
806     unless $hunk->{hunk} == $i + 1;
807     die "patch: hunk $hunk->{hunk} failed\n"
808     unless $hunk->{success};
809     die "patch: hunk $hunk->{hunk} used fuzz $hunk->{fuzz}\n"
810     if $hunk->{fuzz};
811     die "patch: hunk $hunk->{hunk} offset $hunk->{offset} exceeds $max_offset->[$i]\n"
812     if abs $hunk->{offset} > $max_offset->[$i];
813     }
814    
815     $hunks
816     }
817    
818     sub apply_udiff {
819     my ($input, $output, $diff) = @_;
820     my ($normalized, $max_offset) = normalize_udiff $diff;
821    
822     require AnyEvent::Util;
823     require File::Spec;
824     require File::Basename;
825     require Fcntl;
826    
827     $input = File::Spec->rel2abs ($input);
828     $output = File::Spec->rel2abs ($output);
829     my $directory = File::Basename::dirname ($output);
830    
831     sysopen my $fh, $output, Fcntl::O_WRONLY () | Fcntl::O_CREAT () | Fcntl::O_EXCL (), 0600
832     or die "$output: $!\n";
833     close $fh or do { my $error = $!; unlink $output; die "$output: $error\n" };
834    
835     my $cv = AE::cv ();
836     my $log;
837     my $process = eval {
838     AnyEvent::Util::run_cmd ([
839     "patch", "--verbose", "--batch", "--forward", "--binary", "--fuzz=0",
840     "--no-backup-if-mismatch", "--reject-file=-", "--output=$output", "--", $input,
841     ],
842     "<" => \$normalized,
843     ">" => \$log,
844     close_all => 1,
845     on_prepare => sub {
846     $ENV{LC_ALL} = "C";
847     chdir $directory or die "$directory: $!\n";
848     defined POSIX::dup2 (1, 2) or die "dup2: $!\n";
849     },
850     );
851     };
852    
853     if ($@) {
854     my $error = $@;
855     unlink $output;
856     die $error;
857     }
858    
859     $process->cb (sub {
860     my $hunks;
861     eval { $hunks = check_patch_output $_[0]->recv, $log, $max_offset; 1 } or do {
862     my $error = $@;
863     unlink $output;
864     $cv->croak ($error);
865     return;
866     };
867    
868     $cv->send ($hunks);
869     });
870    
871     $cv
872     }
873    
874 root 1.1 sub _clean_text($) {
875     my ($text) = @_;
876    
877     my ($prefix) = (join "\n", grep /\S/, split /\n/, $text)
878     =~ /\A([ \t]*)[^\n]*(?:\n\1[^\n]*)*\z/;
879    
880     $text =~ s/^\Q$prefix\E//mg;
881     $text =~ s/\A\s+//;
882     $text =~ s/\s+\z//;
883     $text
884     }
885    
886     package AnyEvent::MCP::Base;
887    
888     sub new {
889     my ($class, %kv) = @_;
890     bless \%kv, $class
891     }
892    
893     sub event {
894     my ($self, $event, @args) = @_;
895     ($self->{"on_$event"} or return)->($self, @args)
896     }
897    
898     =head1 SEE ALSO
899    
900     L<AnyEvent>, L<https://modelcontextprotocol.io/>
901    
902     =head1 AUTHOR
903    
904     The main author of this module is OpenAI ChatGPT.
905    
906     Additional guidance received by:
907    
908     Marc Lehmann <schmorp@schmorp.de>
909     http://home.schmorp.de/
910    
911     =cut
912    
913     1
914