AnyEvent-MCP
view release on metacpan or search on metacpan
lib/AnyEvent/MCP/Server/HTTP.pm view on Meta::CPAN
=head1 NAME
AnyEvent::MCP::Server::HTTP - an optional MCP HTTP listener and socket handler
=head1 DESCRIPTION
A subclass of L<AnyEvent::MCP::Server> that handles HTTP POST requests.
It can open its own listener, or you can give it sockets accepted elsewhere.
If your application already parses HTTP, you can pass decoded requests to
L<AnyEvent::MCP::Server> instead of running another HTTP implementation.
Requests sharing one connection are dispatched in sequence, preserving their
HTTP response order; requests on other connections are independent. This
implementation returns JSON and drops notifications. It does not support
SSE or subscriptions.
Request bodies need C<Content-Length>: chunked transfer encoding is not
supported. The server does not assign protocol sessions. Legacy clients
use the 2025 request/response subset.
C<Mcp-Param-*> headers from C<x-mcp-header> tool schema annotations are not
currently validated against the body. The built-in protocol headers
C<Mcp-Protocol-Version>, C<Mcp-Method> and, where applicable, C<Mcp-Name>
must agree with modern request metadata and parameters; a mismatch is
rejected. Custom schema-derived headers do not receive this check.
=head1 FIRST USE
For an application that already registers tools in
L<AnyEvent::MCP::Server::Toolset>, start an HTTP listener:
use AnyEvent;
use AnyEvent::MCP::Server::HTTP;
my $server = AnyEvent::MCP::Server::HTTP->new (
toolsets => [$tools], tokens => [$secret],
host => "127.0.0.1", port => 31740,
);
$server->listen;
print $server->url, "\n";
AE::cv->recv;
C<$tools> is a Toolset and C<$secret> is a previously generated private
URL-safe token. Do not use a sample or hardcoded token in a real service.
This object handles sockets and HTTP; L<AnyEvent::MCP::Server> dispatches
requests, while the Toolset runs tool callbacks. For a standalone program
with automatic configuration and startup, prefer
L<AnyEvent::MCP::Server::Simple>.
=head1 AUTHENTICATION
Unlike L<AnyEvent::MCP::Server::Simple/mcp_start>, this low-level object
does not load global configuration or derive credentials. Supply C<tokens>
explicitly, for example:
my $server = AnyEvent::MCP::Server::HTTP->new (
host => "127.0.0.1",
port => 31740,
path => "/mcp/",
tokens => ["a-long-random-secret", "another-long-random-secret"],
);
$server->listen;
Each token authorizes the same requests. For either token, a client may
POST to C</TOKEN/mcp/> without an Authorization header, or POST to
C</mcp/> with C<Authorization: Bearer TOKEN>. URL credentials are path
components, B<not> HTTP cookies. C<url> advertises the first token;
C<url (0)> returns the clean endpoint. A trailing slash may be omitted
from either request path when the configured endpoint path ends in one;
this accommodates clients that strip it from a configured URL.
The accepted tokens are static, URL-safe strings. HTTP Basic and OAuth
access-token validation are not implemented here. There is no automatic
fallback to the master-secret-derived token: that belongs to C<mcp_start>
or L<AnyEvent::MCP::Config/http_settings>.
Authentication defaults to C<tokens>, and construction B<fails> unless a
nonempty array of valid tokens is provided. To deliberately delegate
authentication to a trusted embedding layer (for example, a reverse proxy
that rejects unauthorized requests before they reach MCP), specify
C<auth =E<gt> "none"> instead, B<without> C<tokens>. This is an explicit
opt-out, not a permissive default. The embedding layer then owns the
entire authentication boundary.
The C<origins> setting filters browser origins; it is not authentication.
URLs can leak through logs and histories, and HTTP alone does not encrypt
tokens. Use TLS when credentials cross an untrusted network. A local address
is a sensible default, but not a credential.
=head1 METHODS
=over
=item $server = AnyEvent::MCP::Server::HTTP->new (%config)
Construction does not start a listener. In addition to Server options,
the following configuration options are supported:
=over
=item host => $host
The listening address, defaulting to C<127.0.0.1>.
=item port => $port
The listening port, defaulting to zero for an automatically assigned port.
=item path => $path
The MCP endpoint path, defaulting to C</mcp/>. Bearer authentication uses
C<$path> directly; URL tokens prefix it with C</TOKEN>. If this path ends
in C</>, requests without that final slash are also accepted. Other
path differences are not normalized.
=item connection_close => $boolean
When true, disables persistent connections, sends C<Connection: close> and
closes the socket after draining each response. Defaults to false.
This is useful when an existing listener routes sockets by their first
request and cannot route subsequent requests independently.
=item max_size => $bytes
Limits incoming headers and request bodies, defaulting to 8 MiB.
=item origins => [$origin...]
Allowed Origin header values. Defaults to C<["*"]>, permitting browser requests
from any origin. An empty array rejects requests with an Origin header, while
requests without one are accepted. Allowed origins receive CORS response headers,
and nonempty origin lists enable OPTIONS preflight for POST. Preflight does not
require authentication; the subsequent POST still does.
=item handle => { %handle }
AnyEvent::Handle constructor options, such as C<tls>, C<tls_ctx>, C<timeout>
or C<on_starttls>. The socket and the read/error/EOF handlers belong to this
class.
=item tls_cert => $file
Serve HTTP and HTTPS opportunistically on the same port. FILE contains
a PEM certificate chain and optionally its private key. The TLS context
is loaded once when listening starts. TLS needs L<Net::SSLeay>.
=item tls_key => $file
PEM private key for C<tls_cert>. If only one TLS option is supplied, its
file must contain both certificate and private key. On accepted connections,
C<< $handle->{tls} >> identifies encrypted traffic. Plain HTTP remains
available; TLS is not required of clients.
=item tokens => [$token, ...]
Ordered tokens accepted either as C</TOKEN> before the configured
C<path> or in an C<Authorization: Bearer TOKEN> header. The first token
is used in advertised URLs. Token strings must be URL-safe and nonempty.
=item auth => "tokens" | "none"
Select authentication mode. C<tokens> is the default and requires at least
one token; C<none> explicitly disables HTTP authentication and cannot be
combined with tokens. Other values are rejected. Browser origins are
not a replacement for authentication.
=item name => $name
Identifies the service. Use L<AnyEvent::MCP::Config/http_settings>
to resolve named listener configuration before constructing this transport.
=item url_host => $hostname
Overrides the advertised hostname, for example when listening on a wildcard
address. Otherwise a wildcard uses the nodename from C<uname>.
=item url_scheme => $scheme
Overrides the advertised scheme; otherwise TLS credentials or handle options
select C<https>, and plain handles select C<http>.
=back
Every configured token authenticates in either the URL or the Authorization
header. Without tokens, the constructor requires C<auth =E<gt> "none">.
=item $url = $server->url ($with_token)
Returns the advertised URL with the actual listening port. C<$with_token>
defaults to true, including the first configured token. Pass false for
the clean URL to use with an Authorization bearer token.
The port must be known. Wildcard hostname resolution and reverse-proxy
routing remain the caller's responsibility.
=item $server->listen ($host, $port, %handle)
Starts a listener, replacing any previous listener. Omitted arguments use
the configured host and port. The actual bound port is stored in
C<< $server->{port} >>, including when requesting an ephemeral port.
C<%handle> overrides the configured C<handle> options for connections
accepted by this listener.
=item $handle = $server->accept ($socket, %handle)
Starts handling an already accepted socket. The server takes ownership of
C<$socket>. No listener is needed.
C<%handle> overrides the configured C<handle> options for this connection.
=item $server->close
Stops listening, closes accepted sockets and cancels outstanding requests.
The same object can subsequently listen or accept again.
=back
=head1 EVENTS
=over
=item on_extension => $callback->($server, $handle, $hello)
Accept raw extension connections on the HTTP listener. A first byte of C<[>
selects a bounded, newline-terminated JSON hello whose first two fields are
the protocol name and an accepted listener token. Authenticate before invoking
this callback, which validates the protocol-specific fields and takes ownership
of the handle, preserving buffered input. Throw an exception to reject it.
HTTP connections continue through the normal parser. This dispatch examines
decrypted bytes when TLS is configured, but the built-in reverse connector
currently supports only plain C<http://> endpoints.
=item on_http_request => $callback->($server, $request, $reply)
Called for every complete, syntactically valid HTTP request before MCP routing
or authentication. C<$request> is a hash containing the parsed HTTP fields
(including C<method>, C<uri>, and C<http_version>) and a C<body> field holding
the raw request body. Return false to use the normal MCP handling, or true to
( run in 1.394 second using v1.01-cache-2.11-cpan-3f962061493 )