NAME

Net::Async::MCP::Transport::InProcess - In-process MCP transport via direct MCP::Server calls

VERSION

version 0.007

SYNOPSIS

# Usually created automatically by Net::Async::MCP
use Net::Async::MCP;

my $mcp = Net::Async::MCP->new(server => $my_mcp_server);
$loop->add($mcp);

# Or construct directly for testing:
use Net::Async::MCP::Transport::InProcess;

my $transport = Net::Async::MCP::Transport::InProcess->new(
    server => $my_mcp_server,
);

DESCRIPTION

Net::Async::MCP::Transport::InProcess provides direct in-process communication with an MCP::Server instance. It calls handle() directly on the server object, passing a fresh MCP::Server::Context with each request, making it the most efficient transport for Perl-based MCP servers running in the same process. The context carries no scopes, so MCP::Server's OAuth scope checks impose no restriction for this transport.

MCP is a recommendation of this distribution rather than a requirement, so MCP::Server::Context is loaded when the first request is sent instead of when this module is compiled: without it the module still loads, and so does the rest of the distribution, which needs no part of MCP at all. Where the context class cannot be loaded, handle() is called with the request as its only argument. Nothing is lost by that, because without MCP there is no MCP::Server to hand this transport either - only some other object with a handle() method, which never had a context coming. Install MCP and every request carries one again, with no change to anything here.

If a tool returns a Mojo::Promise (from an async MCP server implementation), the promise is resolved synchronously via wait(). For fully non-blocking async tools, use Net::Async::MCP::Transport::Stdio with a separate subprocess instead.

Communication is strictly request/response: there is no stream the server could push notifications back over, so subscriptions/listen is not usable with this transport. See "send_request".

This transport is selected automatically by Net::Async::MCP when constructed with a server argument.

new

my $transport = Net::Async::MCP::Transport::InProcess->new(
    server => $mcp_server,
);

Constructs a new in-process transport. Requires a server argument which must be an MCP::Server instance (or any object with a handle method that accepts a JSON-RPC request hashref, followed by an MCP::Server::Context instance where MCP is installed and by nothing at all where it is not).

send_request

my $future = $transport->send_request($method, \%params);

Sends a JSON-RPC request to the MCP server by calling handle() directly. Returns a Future that resolves to the result value from the response, or fails with an error message if the server returns a JSON-RPC error.

A JSON-RPC error fails the Future with more than its message. Future's failure convention is ( $message, $category, @details ), so the failure reads ( "MCP error $code: $message", 'mcp', $error ): in scalar context ->failure is the message and nothing has changed, and in list context the raw JSON-RPC error object comes with it.

my ( $message, $category, $error ) = $future->failure;
if (($category // '') eq 'mcp') {
  my $code      = $error->{code};          # -32601, -32602, ...
  my $supported = $error->{data}{supported};
}

The mcp category marks a genuine JSON-RPC error from the server and nothing else. The failures this transport raises on its own - a missing or unusable response, an async tool that rejected, and the subscriptions/listen refusal below - carry their message alone, so a caller that finds no category knows there is no server error object behind it.

An error that is no JSON-RPC error object - a string, a number, an array, a hash without a code - still fails the request, with MCP in-process error response: TEXT carrying the value or its JSON, and no category. MCP::Server never answers that way, but the server object may be anything with a handle() method. A "error": null next to a result is a success, as JSON-RPC 1.0 wrote it; without one it fails with MCP in-process invalid response: a null error and no result.

Accepts the same optional trailing name/value options as the other transports, header_params among them, and ignores all of them: they describe how a request is mirrored into HTTP headers, and this transport hands the request to the server as it stands. See "send_request" in Net::Async::MCP::Transport::HTTP.

subscriptions/listen is the one method that cannot be served here. If the server object has a notification capable transport of its own, MCP::Server answers that request with an MCP::Server::Subscription object rather than a JSON-RPC response, leaving it to the transport to turn it into a notification stream; this transport has none, so the returned Future fails saying that it cannot carry server-initiated notifications. A server without such a transport never gets that far and answers with JSON-RPC error -32601 (METHOD_NOT_FOUND), which is reported like any other server error.

send_notification

my $future = $transport->send_notification($method, \%params);

Sends a JSON-RPC notification (a request with no id, expecting no response) directly to the server via handle(). Returns an immediately resolved Future.

close

my $future = $transport->close;

No-op for the in-process transport since there is no external process or connection to close. Returns an immediately resolved Future.

is_alive

my $alive = $transport->is_alive;

Always true: the server object lives in the same process, so there is no connection state that could go away. Used by "ping" in Net::Async::MCP for its transport-level liveness check.

mirrors_header_params

my $mirrors = $transport->mirrors_header_params;

Always false: there are no HTTP headers here to mirror tool arguments annotated with x-mcp-header into, so "call_tool" in Net::Async::MCP resolves none and never fetches a tool list to do it.

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-net-async-mcp/issues.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <getty@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.