NAME

AnyEvent::MCP - Model Context Protocol client/server support

SYNOPSIS

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 (...);

DESCRIPTION

AnyEvent::MCP provides event-driven Model Context Protocol client and server support. There are three starting points:

Use the included servers without writing Perl

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.

Write a small MCP server

"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.

Embed MCP in an application

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.

MODULES

AnyEvent::MCP::Server::Simple

Function-based interface for implementing ordinary MCP servers.

AnyEvent::MCP::Server

Object-oriented MCP server interface.

AnyEvent::MCP::Client

Object-oriented MCP client interface.

AnyEvent::MCP::Server::Toolset

Reusable registry of MCP tool declarations.

AnyEvent::MCP::Server::Request

One incoming server request: arguments, replies, cancellation and progress.

AnyEvent::MCP::Client::Request

Input requested from the MCP client, including human elicitation.

AnyEvent::MCP::Server::HTTP and AnyEvent::MCP::Server::JSONL

HTTP listener and stdio/byte-stream server transports. Both use the protocol handling in AnyEvent::MCP::Server.

AnyEvent::MCP::Client::HTTP and AnyEvent::MCP::Client::JSONL

HTTP and subprocess/byte-stream client transports; both use AnyEvent::MCP::Client for protocol negotiation and tool calls.

AnyEvent::MCP::Server::Connect

Reverse MCP connection from a server into a multibroker.

AnyEvent::MCP::Server::Permissions

Action authorization and sandbox-grant handling for MCP servers.

AnyEvent::MCP::Permissions::Ask

External permission dialog handling and form conversion for clients.

AnyEvent::MCP::Config

Private global configuration, named server settings and derived credentials.

AnyEvent::MCP::Helper

Asynchronous JSON exchange with an external form or permission helper.

AnyEvent::MCP::Workdirs and AnyEvent::MCP::File

Persistent workdir identifiers and their locked state-file utilities.

AnyEvent::MCP::Extension

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.

TUTORIAL

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.

GLOBAL CONFIGURATION

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.

ENVIRONMENT

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.

FUNCTIONS

$class = AnyEvent::MCP::json_class

Returns JSON::XS if available, otherwise JSON::PP. The caller configures a new coder instance as needed.

$bytes = AnyEvent::MCP::utf8enc $text

Return a UTF-8 byte string for $text, leaving the argument unchanged.

$text = AnyEvent::MCP::utf8dec $bytes

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.

AnyEvent::MCP::parse_ini $text, $callback, $filename

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.

$bytes = AnyEvent::MCP::random_bytes $length

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.

PATCH UTILITIES

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:

1. AnyEvent::MCP::normalize_udiff

Returns a unified diff for GNU patch and the permitted hunk offsets.

2. Apply the normalized diff

Run GNU patch with fuzz zero and capture its verbose output.

3. AnyEvent::MCP::check_patch_output

Checks the process status and output against the permitted offsets. parse_patch_output is available separately when only parsing is needed.

Input format

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.

Applying the result

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.

($diff, $max_offset) = AnyEvent::MCP::normalize_udiff $diff

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.
$cv = AnyEvent::MCP::apply_udiff $input, $output, $diff

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>
$parsed = AnyEvent::MCP::parse_patch_output $output

Parses LC_ALL=C GNU patch --verbose output without judging whether the application is acceptable. $parsed has two fields:

hunks => [$hunk...]

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.

unparsed => [$line...]

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.

$hunks = AnyEvent::MCP::check_patch_output $status, $output, [$max_offset...]

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.

SEE ALSO

AnyEvent::MCP::Intro, AnyEvent::MCP::Server::Simple, AnyEvent::MCP::Helper, mcp-ask-user, anyevent-mcp-form-elicitation, AnyEvent, https://modelcontextprotocol.io/

AUTHOR

The main author of this module is OpenAI ChatGPT.

Additional guidance received by:

Marc Lehmann <schmorp@schmorp.de>
http://home.schmorp.de/