MCP server example
A minimal Catalyst app that loads Catalyst::Plugin::JSONRPC::Server and then
Catalyst::Plugin::MCP, mounting a Model Context Protocol (2025-06-18) server
at /mcp. It registers two providers: an echo tool that echoes back a msg
argument, and a static resource (mem://greeting) that returns a fixed
greeting string.
Not a production shape
This example is deliberately minimal: /mcp is wide open, with no
authentication and no Origin check, so anything that can POST to it can run
the echo tool. That is fine for a localhost demo and wrong for real code.
Before copying this layout, read the Security section of the top-level README:
authentication and Origin validation are the application's job, and this
example does neither.
Run it
plackup -p 5000 examples/app.psgi
perl examples/client.pl # or: perl examples/client.pl http://127.0.0.1:5000
The client replays the MCP handshake: initialize, tools/list,
tools/call (the echo tool), then resources/read (the mem://greeting
resource).
Expected output
--> {"id":1,"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18"}}
<-- HTTP 200 {"id":1,"jsonrpc":"2.0","result":{"capabilities":{"resources":{},"tools":{}},"protocolVersion":"2025-06-18","serverInfo":{"name":"example-mcp","version":"0.001"}}}
--> {"id":2,"jsonrpc":"2.0","method":"tools/list"}
<-- HTTP 200 {"id":2,"jsonrpc":"2.0","result":{"tools":[{"description":"Echo back the msg argument","name":"echo"}]}}
--> {"id":3,"jsonrpc":"2.0","method":"tools/call","params":{"arguments":{"msg":"hi"},"name":"echo"}}
<-- HTTP 200 {"id":3,"jsonrpc":"2.0","result":{"content":[{"text":"echo: hi","type":"text"}]}}
--> {"id":4,"jsonrpc":"2.0","method":"resources/read","params":{"uri":"mem://greeting"}}
<-- HTTP 200 {"id":4,"jsonrpc":"2.0","result":{"contents":[{"text":"hello from the example resource","uri":"mem://greeting"}]}}