NAME

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

VERSION

version 0.004

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.

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 and an MCP::Server::Context instance).

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.

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.