AnyEvent::MCP - Model Context Protocol client/server support
use AnyEvent::MCP::Server::Simple; my $options = mcp_getoptions ...; mcp_tool ...; mcp_start %$options;
or use the object interface directly:
use AnyEvent::MCP::Server (); my $server = AnyEvent::MCP::Server->new (...);
AnyEvent::MCP provides event-driven Model Context Protocol client and server support. There are three starting points:
AnyEvent::MCP::Intro walks through installation, a first filesystem request, client connections, configuration, permissions and combining servers. Start there if you want the tools to work on your projects.
TUTORIAL in AnyEvent::MCP::Server::Simple builds a complete server, calls it, and adds options, replies, permission checks and asynchronous work. Simple handles transport startup and the event loop.
The modules below expose tool registries, protocol dispatch, requests and transports separately, for applications that need to control their own connections or lifetime.
The usual way to implement a small MCP server is AnyEvent::MCP::Server::Simple, which provides a function-based interface and creates the underlying server objects when a transport such as stdio or HTTP is started.
The lower-level object interfaces are intended for applications that need to manage MCP connections, servers or reusable tool registries directly.
Function-based interface for implementing ordinary MCP servers.
Object-oriented MCP server interface.
Object-oriented MCP client interface.
Reusable registry of MCP tool declarations.
One incoming server request: arguments, replies, cancellation and progress.
Input requested from the MCP client, including human elicitation.
HTTP listener and stdio/byte-stream server transports. Both use the protocol handling in AnyEvent::MCP::Server.
HTTP and subprocess/byte-stream client transports; both use AnyEvent::MCP::Client for protocol negotiation and tool calls.
Reverse MCP connection from a server into a multibroker.
Action authorization and sandbox-grant handling for MCP servers.
External permission dialog handling and form conversion for clients.
Private global configuration, named server settings and derived credentials.
Asynchronous JSON exchange with an external form or permission helper.
Persistent workdir identifiers and their locked state-file utilities.
Private authenticated raw-TCP protocols sharing an HTTP listener.
mcp-multibroker combines downstream HTTP and stdio servers under tool-name
prefixes. It uses the same client, server and permission helper facilities,
including modern input forwarding and local permission prompts for legacy
callers. Run mcp-multibroker --help for command-line syntax; its manual
describes authentication and configuration.
The interfaces are still being designed. AnyEvent::MCP::Intro explains the installed programs; module-specific POD describes their Perl APIs.
For using the servers, see AnyEvent::MCP::Intro. It includes a working first connection, a complete configuration example, saved permissions, multibroker setup and troubleshooting.
For writing a server, see TUTORIAL in AnyEvent::MCP::Server::Simple. The tutorial starts with a complete echo server and a client that prints its reply, then develops argument validation, command-line options, result representations, permission retries and cancellable asynchronous work. The Simple function reference follows it.
For custom dispatch and reusable tool registries, see REQUEST AND TOOL WORKFLOW in AnyEvent::MCP::Server::Toolset and AnyEvent::MCP::Server. The client modules describe calling servers from your own application.
The shared per-user configuration is $XDG_CONFIG_HOME/anyevent-mcp/global.ini,
or $HOME/.config/anyevent-mcp/global.ini when XDG_CONFIG_HOME is unset.
AnyEvent::MCP::Config loads and holds it; loading this module does not access
configuration files.
The file is UTF-8 and uses sections with single-line key = value pairs.
Values are unquoted strings. Blank lines and whole-line comments beginning
with # or ; are ignored; inline comment characters remain part of the
value. Later occurrences of a key replace earlier ones in the same section.
[global] secret = ...128 hexadecimal digits... [instance example] bind = 127.0.0.1:31740 tokens = first-token,second-token [program mcp-fs] mount.opt = ro:/opt
Section names are unrestricted. Application-specific sections, including exports, utility paths and user settings, are documented by their applications.
[global] holds secret, a 64-byte master key encoded as 128 hexadecimal
digits. The file must be owned by the current user, with no group or other
permissions. A missing secret is generated automatically on first configuration
access and saved when possible. Existing secrets are never replaced implicitly.
An invalid secret is an error.
Applications can derive their own purpose-specific secrets with derive_secret in AnyEvent::MCP::Config, using the cached global configuration or an explicitly loaded configuration object.
If saving fails, Config warns and retains the secret in memory. Credentials then remain stable only for that process; other processes cannot derive the same credentials. If randomness is unavailable, supply a securely generated secret in the file.
[program PROGRAM] supplies settings for the invoked program basename;
[instance NAME] overrides them for a named instance.
HTTP-capable executables use --name NAME to select these settings, with
default as the default instance name. Names consist of an ASCII
letter or underscore followed by ASCII letters, digits, underscores or
hyphens. Their --bind ADDRESS[:PORT] overrides the configured bind.
The default bind address is IPv4 loopback with a port derived from the program,
instance name and master secret in the range 16384..32767; :0 requests an ephemeral
port. --print-url prints the URL using the first accepted token without
listening. Named server settings also include path, url_host,
url_scheme and tokens. tokens is a comma-separated list of
URL-safe strings. Repeated --token TOKEN options override this list.
Automatic permission defaults use auto-rule.SUFFIX = allow|deny
TOOL[,ACTION[,CAPABILITY]] in program/instance sections, sorted by suffix.
Instance entries override matching keys or suppress them with empty values.
Command-line automatic rules are evaluated before configured defaults.
All accepted tokens work in the URL path or Authorization bearer header.
Without configured tokens, one token is derived from the program and instance
name. All derivation contexts have a fixed anyevent-mcp namespace prefix.
mcp_start resolves named settings and requires the selected token for
HTTP requests. Explicit tokens replace the derived default.
The low-level HTTP transport does not resolve configuration itself.
Simple's stdio startup does not access global HTTP configuration.
Applications can still load it for their own settings; mcp-fs, for
example, reads trusted mount and executable settings even in stdio mode.
Browser origins are permitted by default, but must still pass MCP authentication.
[client NAME] supplies HTTP client defaults: url, bearer and
timeout. Clients consult it only when explicitly given a name.
An unnamed HTTP client and the low-level HTTP server do not read global
configuration. mcp_start resolves a default name and does read it.
Explicit constructor arguments take precedence over named defaults.
Project-specific environment variables use the PERL_ANYEVENT_MCP_ prefix.
PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL=stdio selects stdio in Simple.
PERL_ANYEVENT_MCP_MULTIBROKER_ALIAS names a multibroker routing prefix;
it does not change the service-instance name.
PERL_ANYEVENT_MCP_PERMISSION_HELPER selects the external asking executable.
PERL_ANYEVENT_MCP_PERMISSION_ASK selects the permission engine's
asking default: helper, tty, yad, auto or mcp. See
Choose where to ask in AnyEvent::MCP::Intro for the behavior and client
requirements.
Standard HOME and XDG_CONFIG_HOME keep their standard names.
Returns JSON::XS if available, otherwise JSON::PP. The caller
configures a new coder instance as needed.
Return a UTF-8 byte string for $text, leaving the argument unchanged.
Decode a UTF-8 byte string into characters, dying on invalid UTF-8.
The argument is left unchanged. For intentional in-place conversions,
use Perl's utf8::encode or utf8::decode directly.
Parse INI text and call $callback->($name, $kv) for each section
in file order, including the initial unnamed section (name "").
Repeated sections remain separate; duplicate keys within a section use
their last value. The callback decides how to collect sections.
$filename is optional and used only for line-numbered errors.
Returns exactly $length random bytes. Tries /dev/urandom, then an external
openssl rand command. No additional random-number module is required.
Dies if neither source supplies the requested bytes successfully.
$length must be a nonnegative integer; zero returns an empty string.
Since applying patches to files is a common task for MCP servers, the following utility functions can be used to ease that job. Together, they define a common patch format (unified diff or a simplified version of it, with fewer requirements to make it easier for the LLM to generate), helpers to apply the patch and a helper to verify if the patch was applied with sufficient precision.
Use apply_udiff to normalize, apply and validate a patch in one operation.
It writes a separate output file; the caller checks freshness and metadata
and commits the result.
For an application-specific applier, the workflow is:
Returns a unified diff for GNU patch and the permitted hunk offsets.
Run GNU patch with fuzz zero and capture its verbose output.
Checks the process status and output against the permitted offsets.
parse_patch_output is available separately when only parsing is needed.
The patch format accepted by normalize_udiff is an extension to the (essentially undocumented) unified diff format.
In a hunk header, only the old start -N matters. It is the old-file line
of the first hunk body line, including context, not necessarily the first
changed line. Counts and the new-file position are ignored and recomputed.
Both ordinary headers and the abbreviated form are accepted:
@@ -42,99 +123,99 @@ @@ -42 @@
Optional text after the closing @@ is preserved. File headers and other
text before the first hunk are preserved; a diff must describe one file.
Hunk body lines use the usual prefixes: a space for unchanged context,
- for removed lines and + for added lines. The
\ No newline at end of file marker is preserved and counts neither side.
Trailing empty lines outside the final hunk are discarded.
Pure insertion is permitted only at the beginning, using old start 0.
For example, @@ -0 @@ with one added line becomes @@ -0,0 +1,1 @@.
An empty old-side range names the line before the insertion: -0,0
means before line 1, whereas -1,0 would mean after line 1 and is rejected
here. Nonempty ranges use one-based line numbers.
An insertion elsewhere needs old-side context. Empty hunks are rejected. New positions include the cumulative line-count changes of preceding hunks.
The normalize_udiff function does not apply the patch, check file
contents or guard against concurrent edits. The caller must supply those
checks, if required.
Context must match exactly: apply with fuzz zero. For each hunk,
$max_offset permits relocation by at most the number of distinct old-side
lines minus one, or zero for a pure insertion. Repeated old-side lines do
not increase this allowance. The applier must enforce these limits; an
unrestricted search for matching context can select the wrong occurrence.
For live Vim buffers, also require the expected changedtick to match. File-based users need an equivalent check before replacing the file.
To work around GNU patch's handling of asymmetric context, the first context line of each hunk is rewritten as an identity removal/addition. The line contents and old/new line counts are unchanged. This is normalization for the patch applier, not a relaxation of context matching.
Normalizes a relaxed, single-file unified diff into ordinary unified diff
syntax. Returns the normalized $diff and an array reference $max_offset
containing the permitted whole-hunk relocation for each hunk, in order.
Dies on malformed hunks or when no hunks are found.
Here is a suitable description for the diff format, e.g. in a tool definition:
Apply a unified diff to one regular file. Hunk counts are recomputed, fuzz is disabled, whole-hunk relocation is limited per hunk by distinct old-side lines. Pure insertion is allowed only at the beginning; elsewhere include old-side context.
Asynchronously applies the relaxed $diff to the input file $input,
writing a separate output file $output. $diff must contain bytes;
callers working with characters must encode them for the file themselves.
Normalizes the diff, runs GNU patch using AnyEvent::Util::run_cmd, and
validates its verbose output with check_patch_output. The command uses
batch/forward mode, binary mode and fuzz zero. No shell is involved.
$output must not exist. It is reserved with mode 0600; patch runs in its
directory so incidental temporary files stay there. Input and output paths
are made absolute before starting the command. These must be trusted paths
in a directory controlled by the caller.
Returns a condition variable. Its recv returns the validated hunk array
reference, or throws on command/application failure. Setup failures throw
synchronously. Failed or partial output is removed. $input is unchanged.
The caller owns committing the successful output and removing it when no longer needed. Freshness checks, metadata preservation, atomic replacement and application-specific permissions also belong to the caller.
For your reference, the exact options used are:
--verbose --batch --forward --binary --fuzz=0 --no-backup-if-mismatch --reject-file=- --output=... -- <inputfile>
Parses LC_ALL=C GNU patch --verbose output without judging whether
the application is acceptable. $parsed has two fields:
Hunk records in output order. Each record contains hunk (the one-based
number), success, line (the reported application line), fuzz and
the signed offset. Omitted fuzz and offset are zero. Failed and ignored
hunks have false success.
Lines not recognized as hunk results or GNU patch's verbose framing. The parser preserves them for callers to interpret or reject.
Use verbose output: ordinary patch output omits exact-position successes, so it cannot account for every hunk.
Parses $output and validates a complete application. $status is the
raw process wait status, as returned by waitpid or
AnyEvent::Util::run_cmd, not just the exit code.
Requires status zero, no unrecognized output, exactly the expected hunks
in numbered order, successful application, fuzz zero and each signed offset
within its corresponding absolute limit. [$max_offset...] is the array
reference returned by normalize_udiff. At least one hunk is required.
Returns the parsed hunk array reference on success; otherwise dies with a diagnostic. This is also suitable for callers running patch inside their own sandbox.
AnyEvent::MCP::Intro, AnyEvent::MCP::Server::Simple, AnyEvent::MCP::Helper, mcp-ask-user, anyevent-mcp-form-elicitation, AnyEvent, https://modelcontextprotocol.io/
The main author of this module is OpenAI ChatGPT.
Additional guidance received by:
Marc Lehmann <schmorp@schmorp.de> http://home.schmorp.de/