MCP::Client::Test::TestKit
NAME
MCP::Client::Test::TestKit - a toolkit with one of everything, for client tests
DESCRIPTION
An MCP::Server::Toolkit that exists to be talked to. It registers a tool for
each shape a client has to survive ā an answer, a slow answer, an exception, a
log notification ā plus a prompt and two resources, so every catalog a client
knows how to list has something in it and the cacheable and uncacheable halves
of resources/read are both covered.
Everything it registers is thread-safe: the tests drive it through transports that dispatch on their own threads, and several calls may be in flight at once.
This ā and its siblings MCP::Client::Test::FakeTransport,
MCP::Client::Test::InProcessTransport and MCP::Client::Test::ProgressKit
ā is test support, not library surface. It ships inside the dist's own
namespace (rather than under t/lib) deliberately, so that zef install's
test phase precompiles it as part of lib/ staging before any test file
runs. A t/lib CompUnit repo starts cold on a fresh zef install, and
parallel test files pulling the fixtures' dependency chain into that cold
store can race and fail the install outright; moving test support into
lib/ under a ::Test namespace is the fleet-wide fix for that failure
class.
What is in it
echoā returns itstextargument. The happy path.slowā sleeps formsmilliseconds (default:$slow-ms) and then answers. For "is this request still in flight?" and for timeouts.dyingā throws.MCP::Serverturns that into a tool result withisError, which is the shape a client must not mistake for a protocol failure.noisyā emits anotifications/messageat the level asked for (defaultinfo) and then answers. It goes through the request context, so it respects the 2026-07-28 rule that a server MUST NOT log for a request that did not opt in through_metalogLevel.prompt
greetingā one argument, one message.resource
test://cachedā carriesttlMsandcacheScope, so a client that caches correctly reads it from the server exactly once.resource
test://volatileā carries neither, so a client that caches correctly reads it from the server every single time.
Both resources count their reads (count-of('read:cached')), which is how a
test proves a cache hit never reached the handler, not merely that it never
reached the transport.
EXAMPLES
use MCP::Server;
use MCP::Client::Test::TestKit;
my $kit = MCP::Client::Test::TestKit.new(slow-ms => 50);
my $server = MCP::Server.new(:name<fixture>, :version<1.0.0>);
$server.plug($kit);
# ... drive the server ...
say $kit.count-of('echo'); # how many times the echo tool ran
Plugged under a prefix, everything it registers is namespaced, which is how the registry tests build a server with two of it:
$server.plug(MCP::Client::Test::TestKit.new, prefix => 'other'); # other_echo, ...