MCP::Client::Test::FakeTransport
NAME
MCP::Client::Test::FakeTransport - a scripted MCP::Client::Transport for tests
DESCRIPTION
A transport that answers from a script instead of from a server, and records everything the client sent. It exists so the era machine, the multi round-trip loop and the typed API can be tested exhaustively ā including the failure paths a real server will not produce on demand ā with no subprocess, no socket and no timing.
Responses are queued per method, so a test scripts server/discover and
tools/call independently and does not care what order the client asks in. A
method with no script left is a test bug, not a server behaviour, and is
reported as one.
Scripting
Each scripted answer is a hash with exactly one of:
result => %hā kept, afternormalize-result, as a server result.raw => $anythingā kept verbatim,normalize-resultincluded, so a test can hand the client a JSONnullor a string where a result belongs.error => { code, message?, data? }ā broken with anX::MCP::Client::Protocol, exactly as a real transport does for a JSON-RPC error response.timeout => $secondsā broken with anX::MCP::Client::Timeout.throw => $exceptionā broken with that exception, forServerGoneand friends.respond => -> %msg { ... }ā called with the outbound message and returns any of the above, for answers that depend on what was asked.
Any of them may also carry notify => [%n1, %n2] : those notifications are
delivered to the request's &on-notification before it is settled.
EXAMPLES
The era probe, twice over:
use MCP::Client;
use MCP::Client::Test::FakeTransport;
my $wire = MCP::Client::Test::FakeTransport.new;
$wire.script('server/discover', {
result => {
supportedVersions => ['2026-07-28'],
capabilities => { tools => {} },
_meta => { 'io.modelcontextprotocol/serverInfo' => { name => 'fake' } },
},
});
my $client = MCP::Client.new(transport => $wire);
is $client.era, 'modern';
is $wire.sent-methods, ['server/discover'];
my $wire = MCP::Client::Test::FakeTransport.new;
$wire.script('server/discover', { error => { code => -32601, message => 'nope' } });
$wire.script('initialize', {
result => { protocolVersion => '2025-11-25', capabilities => {}, serverInfo => {} },
});
my $client = MCP::Client.new(transport => $wire);
is $client.era, 'legacy';
is $wire.notified-methods, ['notifications/initialized'];
Multi round-trips, one entry per round:
$wire.script('tools/call',
{ result => {
resultType => 'input_required',
inputRequests => { ask => { method => 'elicitation/create', params => {} } },
requestState => 'opaque-blob',
} },
# NB: the trailing comma -- a bare [%h] flattens the hash into Pairs.
{ result => { content => [{ type => 'text', text => 'done' },] } },
);
my %answer = $client.call-tool('t', {});
is $wire.requests-for('tools/call')[1]<params><requestState>, 'opaque-blob';