Acme-MCP
view release on metacpan or search on metacpan
}
},
"configure" : {
"requires" : {
"Module::Build::Tiny" : "0",
"perl" : "v5.42.0"
}
},
"runtime" : {
"requires" : {
"JSON::PP" : "0",
"perl" : "v5.42.0"
}
},
"test" : {
"requires" : {
"Test2::V0" : "0"
}
}
},
"provides" : {
"Documenter"
],
"x_ignore" : [
".tidyallrc",
".gitignore",
".github/",
".github/*",
".github/**",
".clang-format"
],
"x_serialization_backend" : "JSON::PP version 4.16",
"x_static_install" : 0
}
}
);
# Start the server (STDIO transport)
$mcp->run();
```
# DESCRIPTION
`Acme::MCP` implements a painfully basic server for the Model Context Protocol (MCP). It allows you to expose your
Perl modules, local data, or internal tools to AI agents (like Claude or custom LLMs) through a standardized JSON-RPC
2.0 interface.
# PUBLIC METHODS
## `add_tool( %params )`
Registers a new tool that the AI agent can call.
- `name`: Unique tool identifier.
- `description`: Human-readable explanation of what the tool does.
- `schema`: JSON Schema describing the required arguments.
- `code`: Coderef executed when the tool is called.
## `run( )`
Starts the server loop. Listens for JSON-RPC requests on `STDIN` and writes responses to `STDOUT`. This transport is
compatible with standard MCP hosts.
# AUTHOR
Sanko Robinson <sanko@cpan.org>
# COPYRIGHT
Copyright (C) 2026 by Sanko Robinson.
lib/Acme/MCP.pm view on Meta::CPAN
use v5.42;
use feature 'class';
no warnings 'experimental::class';
#
class Acme::MCP v1.0.1 {
use JSON::PP;
use Carp qw[carp croak];
#
field $name : param : reader : writer = 'Generic MCP Server';
field $version : param : reader : writer = '1.0.0';
field %tools : reader;
field $json = JSON::PP->new->utf8(1);
#
method add_tool (%params) {
my $name = $params{name} or croak 'Tool name required';
my $code = $params{code} or croak 'Tool code (sub) required';
$tools{$name}
= { description => $params{description} // '', inputSchema => $params{schema} // { type => 'object', properties => {} }, code => $code };
}
method run () {
$| = 1; # Autoflush for stdio communication
lib/Acme/MCP.pm view on Meta::CPAN
}
}
method _handle_request ($req) {
my $id = $req->{id};
my $method = $req->{method} // '';
if ( $method eq 'initialize' ) {
$self->_send_response(
$id,
{ protocolVersion => '2024-11-05',
capabilities => { tools => { listChanged => JSON::PP::true }, },
serverInfo => { name => $name, version => $version }
}
);
}
elsif ( $method eq 'tools/list' ) {
my @tool_list;
for my $t_name ( sort keys %tools ) {
push @tool_list, { name => $t_name, description => $tools{$t_name}{description}, inputSchema => $tools{$t_name}{inputSchema} };
}
$self->_send_response( $id, { tools => \@tool_list } );
lib/Acme/MCP.pm view on Meta::CPAN
}
method _send_response ( $id, $result ) {
print STDOUT $json->encode( { jsonrpc => '2.0', id => $id, result => $result } ) . "\n";
}
method _send_tool_result ( $id, $content, $is_error = 0 ) {
$self->_send_response(
$id,
{ content => [ { type => 'text', text => ref($content) ? $json->encode($content) : $content } ],
isError => $is_error ? JSON::PP::true : JSON::PP::false
}
);
}
method _send_error ( $id, $code, $message ) {
print STDOUT $json->encode( { jsonrpc => '2.0', id => $id, error => { code => $code, message => $message } } ) . "\n";
}
};
#
1;
lib/Acme/MCP.pod view on Meta::CPAN
return $args->{a} + $args->{b};
}
);
# Start the server (STDIO transport)
$mcp->run();
=head1 DESCRIPTION
C<Acme::MCP> implements a painfully basic server for the Model Context Protocol (MCP). It allows you to expose your
Perl modules, local data, or internal tools to AI agents (like Claude or custom LLMs) through a standardized JSON-RPC
2.0 interface.
=head1 PUBLIC METHODS
=head2 C<add_tool( %params )>
Registers a new tool that the AI agent can call.
=over
=item C<name>: Unique tool identifier.
=item C<description>: Human-readable explanation of what the tool does.
=item C<schema>: JSON Schema describing the required arguments.
=item C<code>: Coderef executed when the tool is called.
=back
=head2 C<run( )>
Starts the server loop. Listens for JSON-RPC requests on C<STDIN> and writes responses to C<STDOUT>. This transport is
compatible with standard MCP hosts.
=head1 AUTHOR
Sanko Robinson E<lt>sanko@cpan.orgE<gt>
=head1 COPYRIGHT
Copyright (C) 2026 by Sanko Robinson.
use v5.42;
use Test2::V0;
use lib '../lib';
use Acme::MCP;
use JSON::PP;
#
subtest 'Tool Registration' => sub {
isa_ok my $mcp = Acme::MCP->new(), ['Acme::MCP'];
ok $mcp->add_tool( name => 'echo', code => sub ($args) { $args->{text} } ), 'add_tool->( ... )';
# Internal check
my %all_tools = $mcp->tools;
is $all_tools{echo}, E(), 'Tool registered';
};
# We mock STDIN/STDOUT to test the JSON-RPC loop
subtest 'JSON-RPC Handling' => sub {
my $mcp = Acme::MCP->new( name => 'TestServer' );
$mcp->add_tool( name => 'add', code => sub ($args) { $args->{a} + $args->{b} } );
my $json = JSON::PP->new->utf8(1);
my $request
= $json->encode( { jsonrpc => '2.0', id => 1, method => 'tools/call', params => { name => 'add', arguments => { a => 5, b => 10 } } } );
# Mock handle_request directly to avoid loop
my $response;
no warnings 'redefine';
local *Acme::MCP::_send_response = sub ( $self, $id, $res ) {
$response = $res;
};
$mcp->_handle_request( $json->decode($request) );
( run in 1.642 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )