MCP::Server(3)

πŸ“¦
version 0.150.0 Β· 2 versions
, in opensuse-leap 16.0

πŸ’… MCP::Server β€” the manual page, but make it make sense πŸŽ€

okay so you're reading MCP::Server(3) from the User Contributed Perl Documentation and honestly? good for you bestie, this one's a whole vibe. it's the server side of the MCP world and it's genuinely iconic once you get past the manpage accent. let's go ✨


🌸 NAME 🌸

MCP::Server β€” an MCP server implementation. that's it, that's the name, short and slay πŸ’…


πŸ¦‹ SYNOPSIS πŸ¦‹

here's the whole thing in action, no cap. literally copy-paste energy:

use MCP::Server;

my $server = MCP::Server->new(name => 'MyServer');

$server->tool(
  name         => 'echo',
  description  => 'Echo the input text',
  input_schema => {type => 'object', properties => {msg => {type => 'string'}}, required => ['msg']},
  code         => sub ($tool, $args) {
    return "Echo: $args->{msg}";
  }
);

$server->prompt(
  name        => 'echo',
  description => 'A prompt to demonstrate the echo tool',
  code        => sub ($prompt, $args) {
    return 'Use the echo tool with the message "Hello, World!"';
  }
);

$server->resource(
  uri         => 'file:///example.txt',
  name        => 'example',
  description => 'A simple text resource',
  mime_type   => 'text/plain',
  code        => sub ($resource) {
    return 'This is an example resource content.';
  }
);

$server->to_stdio;

like… a tool, a prompt, a resource, and then to_stdio to actually serve it. chef's kiss 🧚


πŸ’– DESCRIPTION πŸ’–

MCP::Server is an MCP (Model Context Protocol) server. that's the whole description. short king πŸ‘‘


🌈 EVENTS 🌈

so MCP::Server inherits all the events from Mojo::EventEmitter, and then it goes ahead and emits these new ones too. obsessed 🫢

⭐ prompts

$server->on(prompts => sub ($server, $prompts, $context) { ... });

fires whenever somebody accesses the list of prompts. every single time, no exceptions πŸ”₯

⭐ resources

$server->on(resources => sub ($server, $resources, $context) { ... });

fires whenever the list of resources gets accessed ✨

⭐ tools

$server->on(tools => sub ($server, $tools, $context) { ... });

fires whenever the list of tools gets accessed. the tools stay booked and busy πŸ’…


πŸŽ€ ATTRIBUTES πŸŽ€

MCP::Server implements these attributes, one by one, all of them, we're not skipping a single one bestie πŸ’–

πŸ“ cache_scope

my $scope = $server->cache_scope;
$server   = $server->cache_scope('private');

this is the cache scope that gets advertised for "server/discover" results, and it's either "public" or "private". defaults to "public". and listen β€” the cache hints for the other cacheable results are declared per primitive with "cache_scope" in MCP::Primitive, so don't come for this one's wig πŸ‘‘

πŸ“ cache_ttl

my $ttl = $server->cache_ttl;
$server = $server->cache_ttl(3_600_000);

how long "server/discover" results are allowed to be cached, measured in milliseconds (yes, milliseconds, not seconds, read it again πŸ”₯). defaults to 0, which literally means the document has to be revalidated on every single request. no free rides πŸ’€

πŸ“ instructions

my $instructions = $server->instructions;
$server          = $server->instructions('Use the echo tool to repeat text.');

free-form guidance for the model about how to use this server, and it's returned by "server/discover". when it's undef it's just omitted from the document entirely β€” and undef is the default, so if you want the model to get guidance you gotta actually set it 🫢

πŸ“ log

my $log = $server->log;
$server = $server->log(Mojo::Log->new);

this is where exceptions thrown by prompts, resources, and tools get reported. it defaults to a Mojo::Log object writing to "STDERR" β€” which, fun fact, is exactly where an MCP host collects the output of a stdio server ✨

if the server is mounted in a Mojolicious application with "to_action", it adopts that application's log on its first request, so exceptions end up wherever the rest of the app logs, with the same level and the same format. set this attribute yourself if you wanna opt out of that πŸŽ€

important one: the caller never sees the exception itself β€” only an "InternalError" β€” because the exception can easily contain file system paths or connection strings. keep that in mind, iykyk πŸ”

πŸ“ name

my $name = $server->name;
$server  = $server->name('MyServer');

the name of the server, used for identification. your server's whole government name πŸ’…

πŸ“ prompts

my $prompts = $server->prompts;
$server    = $server->prompts([MCP::Prompt->new]);

an array reference containing the registered prompts 🌸

πŸ“ resources

my $resources = $server->resources;
$server      = $server->resources([MCP::Resource->new]);

an array reference containing the registered resources πŸ¦‹

πŸ“ state_secret

my $secret = $server->state_secret;
$server    = $server->state_secret($ENV{MY_MCP_SECRET});

this is the key used to authenticate the request state of "input_required" results. that state travels through the client, so it has to be tamper proof, periodt. it defaults to 32 random bytes generated once per process ⭐

⚠️ warning, bestie, seriously: that default is only correct for a single process. under a pre-forking web server or behind a load balancer, every worker mints state that the others reject, so retries just loop instead of completing, and you have to configure the same secret everywhere. use at least 32 bytes from a cryptographically secure source, and keep it out of your code πŸ”₯

πŸ“ state_timeout

my $seconds = $server->state_timeout;
$server     = $server->state_timeout(60);

how long the request state of an "input_required" result stays valid, in seconds. defaults to 300, and it should be no longer than a client plausibly needs to gather the requested input. don't leave the door open, ngl πŸ™ˆ

πŸ“ tools

my $tools = $server->tools;
$server   = $server->tools([MCP::Tool->new]);

an array reference containing the registered tools πŸ’œ

πŸ“ transport

my $transport = $server->transport;
$server       = $server->transport(MCP::Server::Transport::HTTP->new);

the transport layer the server uses, such as MCP::Server::Transport::HTTP or MCP::Server::Transport::Stdio. pick your fighter πŸš€

πŸ“ version

my $version = $server->version;
$server     = $server->version('1.0.0');

the version of the server. semver girlies rise 🌈


πŸ’œ METHODS πŸ’œ

MCP::Server inherits all the methods from Mojo::EventEmitter and implements these new ones. we love an inheritance moment πŸ‘‘

🧚 handle

my $response = $server->handle($request, $context);

handle a JSON-RPC request and return a response β€” which may also be a Mojo::Promise, so heads up on that. a "subscriptions/listen" request yields an MCP::Server::Subscription object instead, which the transport turns into a notification stream ✨ and notifications? they yield "undef". that's it, that's the deal 🫢

🧚 notify_list_changed

my $bool = $server->notify_list_changed('tools');

broadcasts a "notifications/$kind/list_changed" JSON-RPC notification to all connected clients. returns true on success, or "undef" if no notification could be delivered. group chat blast πŸ“£

🧚 oauth_metadata

my $metadata = $server->oauth_metadata(
  resource              => 'https://example.com/mcp',
  authorization_servers => ['https://auth.example.com']
);

build an OAuth 2.0 Protected Resource Metadata document from the given fields, meant to be served from "/.well-known/oauth-protected-resource" πŸ” unless "scopes_supported" is provided, it gets filled in with the sorted union of all the scopes declared by registered tools, prompts, and resources. sorted! union! automatic! we love efficiency πŸ’…

🧚 prompt

my $prompt = $server->prompt(
  name        => 'my_prompt',
  description => 'A sample prompt',
  arguments   => [{name => 'foo', description => 'Whatever', required => 1}],
  code        => sub ($prompt, $args) { ... }
);

register a new prompt with the server 🌸

🧚 resource

my $resource = $server->resource(
  uri         => 'file://my_resource',
  name        => 'sample_resource',
  description => 'A sample resource',
  mime_type   => 'text/plain',
  code        => sub ($resource) { ... }
);

register a new resource with the server πŸ¦‹

🧚 to_action

my $action = $server->to_action;
my $action = $server->to_action({streaming => 1});

convert the server into a Mojolicious action. any options get passed straight through to the constructor of MCP::Server::Transport::HTTP β€” and in particular, "streaming => 1" is how you opt in to "subscriptions/listen", the long-lived notification stream πŸ”₯

🧚 to_stdio

$server->to_stdio;

handles JSON-RPC requests over standard input/output. classic, reliable, iconic ⭐

🧚 tool

my $tool = $server->tool(
  name         => 'my_tool',
  description  => 'A sample tool',
  input_schema => {type => 'object', properties => {foo => {type => 'string'}}},
  code         => sub ($tool, $args) { ... }
);

register a new tool with the server πŸ’œ


πŸ‘‘ SEE ALSO πŸ‘‘

MCP, <https://mojolicious.org>, <https://modelcontextprotocol.io>. go read them, they're giving main character energy 🌈


and that's the whole page, bestie β€” written by the Perl doc girlies, perl v5.42.0, dated 2026-07-31, and filed under MCP::Server(3). mwah πŸ’‹βœ¨

πŸ“¦ where this page comes from