Acme-MCP

 view release on metacpan or  search on metacpan

META.json  view on Meta::CPAN

         }
      },
      "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" : {

META.json  view on Meta::CPAN

      "Documenter"
   ],
   "x_ignore" : [
      ".tidyallrc",
      ".gitignore",
      ".github/",
      ".github/*",
      ".github/**",
      ".clang-format"
   ],
   "x_serialization_backend" : "JSON::PP version 4.16",
   "x_static_install" : 0
}

README.md  view on Meta::CPAN

    }
);

# 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.

t/basics.t  view on Meta::CPAN

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 )