Catalyst-Plugin-MCP

 view release on metacpan or  search on metacpan

t/server-verbs.t  view on Meta::CPAN

use v5.36;
use Test::More;
use Test::Fatal;
use Catalyst::Plugin::MCP::Server;

# --- stub providers that record their args and exercise every path ------
package RecTools {
    use Moo;
    with 'Catalyst::Plugin::MCP::Role::ToolProvider';
    has seen => ( is => 'ro', default => sub { {} } );
    sub list ( $self, $cursor = undef ) {
        $self->seen->{list_cursor} = $cursor;
        return { tools => [ { name => 'echo' } ], nextCursor => 'NEXT' };
    }
    sub call ( $self, $name, $args ) {
        $self->seen->{call} = [ $name, $args ];
        return { isError => 1, content => [ { type => 'text', text => 'boom' } ] }
            if $args->{fail};
        return { content => [ { type => 'text', text => "ran $name" } ] };
    }
}
package RecResources {
    use Moo;
    with 'Catalyst::Plugin::MCP::Role::ResourceProvider';
    sub list      ( $self, $cursor = undef ) { return { resources => [], nextCursor => $cursor } }
    sub templates ( $self )                  { return { resourceTemplates => [ { uriTemplate => 't' } ] } }
    sub read ( $self, $uri ) {
        return undef if $uri eq 'gone://x';
        return { contents => [ { uri => $uri, text => 'hi' } ] };
    }
}
package RecPrompts {
    use Moo;
    with 'Catalyst::Plugin::MCP::Role::PromptProvider';
    sub list ( $self, $cursor = undef ) { return { prompts => [], nextCursor => $cursor } }
    sub get ( $self, $name, $args ) {
        return undef if $name eq 'nope';
        return { messages => [ { role => 'user', content => $args } ] };
    }
}
# ------------------------------------------------------------------------

my $tools = RecTools->new;
my $engine = Catalyst::Plugin::MCP::Server->new;
$engine->register_provider($tools);
$engine->register_provider( RecResources->new );
$engine->register_provider( RecPrompts->new );
my $h = $engine->handlers;

# pagination pass-through: cursor in -> provider sees it; nextCursor out
my $list = $h->{'tools/list'}->( { cursor => 'C1' } );
is( $tools->seen->{list_cursor}, 'C1', 'cursor passed to provider list()' );
is( $list->{nextCursor}, 'NEXT', 'nextCursor returned verbatim' );

# resources/list passes the cursor too
is( $h->{'resources/list'}->( { cursor => 'RC' } )->{nextCursor},
    'RC', 'resources/list pass-through cursor' );

# resources/read happy path
is_deeply( $h->{'resources/read'}->( { uri => 'file://a' } ),
    { contents => [ { uri => 'file://a', text => 'hi' } ] },
    'resources/read returns provider contents' );



( run in 0.697 second using v1.01-cache-2.11-cpan-54e63673c56 )