 | Raku MCP SDK:
A Raku (Perl 6) implementation of the Model Context Protocol (MCP) SDK. Build MCP servers and clients in Raku to integrate with LLM applications like Claude Desktop, IDEs, and other AI tools. |
Why Raku?
Raku is a expressive, multi-paradigm language with strong concurrency tools, a flexible type system, and modern Unicode support. It is well suited for building protocol-first infrastructure where clarity, safety, and rapid iteration matter.
Status
Raku MCP SDK provides comprehensive coverage of the MCP specification 2025-11-25.
See the Gap Analysis for details.
Implementation progress
| Feature | Status | Notes |
|---|
| JSON-RPC 2.0 | ✅ Done | Full message handling |
| Stdio Transport | ✅ Done | Production ready |
| Tools | ✅ Done | List, call, builder API, annotations |
| Resources | ✅ Done | List, read, builder API, annotations |
| Prompts | ✅ Done | List, get, builder API |
| Pagination | ✅ Done | Cursor-based for all list endpoints |
| Logging | ✅ Done | Server-side notifications, logging/setLevel, level filtering |
| Progress | ✅ Done | _meta.progressToken extraction, server notifications, client Supply |
| Cancellation | ✅ Done | Request cancellation with notifications |
| Resource subscriptions | ✅ Done | Subscribe, unsubscribe, update notifications |
| Roots | ✅ Done | Client roots, server list-roots |
| Sampling | ✅ Done | Full support with tools, toolChoice, includeContext, stopReason |
| HTTP Transport | ✅ Done | Full client/server with session management, SSE, resumption |
| Legacy SSE Transport | ✅ Done | Backwards-compatible HTTP+SSE transport (spec 2024-11-05) |
| Elicitation | ✅ Done | Form and URL modes with handler callbacks |
| Tasks (experimental) | ✅ Done | Async tool execution, status polling, cancellation |
| Extensions framework | ✅ Done (experimental) | Negotiation via experimental capabilities + extension method routing |
| Completion | ✅ Done | Prompt and resource autocomplete with handler registration |
| Tool output schemas | ✅ Done | outputSchema and structuredContent for structured results |
| Resource templates | ✅ Done | URI templates with pattern matching and builder API |
| Tool metadata | ✅ Done | Tool name validation (SEP-986), icons and title on Tool/Resource/Prompt/Implementation (SEP-973) |
| OAuth 2.1 | ✅ Done | PKCE, token management, server validation, metadata discovery, dynamic client registration, M2M client credentials, enterprise IdP (SEP-990) |
Table of contents
Introduction
MCP
Model Context Protocol (MCP) is an open standard that standardizes how AI models interact with external data and tools.
Consider it a "USB-C port for AI applications", a universal interface that replaces the need to build custom connectors for every new data source or tool.
What is it for?
It solves the "m-by-n" integration problem. Instead of every AI application (Claude, ChatGPT, IDEs) needing a specific adapter for every data source (Postgres, Google Drive, Slack), they all speak one protocol.
- Servers expose resources (data), tools (functions), and prompts.
- Clients (AI applications/Hosts) discover and use these capabilities securely.
Design principles
- JSON-RPC 2.0: The wire protocol is standard JSON-RPC.
- Client-Host-Server Architecture: Clearly separates the Host (the AI app, e.g., Claude Desktop), the Client (the connector inside the host), and the Server (the data provider).
- Capability Negotiation: Connections start with an initialize handshake where both sides declare what they support (e.g., resources, logging, sampling).
- Transport Agnostic: Designed to run over Stdio (local processes) or HTTP (remote).
Current status
- Standard: Released by Anthropic in late 2024; it is now an open standard (managed under the Linux Foundation) supported by major players like OpenAI, Block, and various dev tools (Replit, Sourcegraph).
- Stability: The core specification is stable, but transport specifics (specifically the shift to "Streamable HTTP") have evolved recently (circa early 2025) to simplify deployment.
- Specification: See the most recent specification version 2025-11-25.
Raku
Raku (formerly Perl 6) is a high-level, multi-paradigm programming language optimized for expressiveness, modularity, and consistency.
It is a specification-driven language (with Rakudo being the primary implementation) that encourages "programming as a human language"—allowing code to be written in a way that feels natural and context-aware rather than rigid and machine-like.
Design principles
Raku preserves the spirit of Perl ("There Is More Than One Way To Do It") but rebuilds the foundation entirely. Its core principles include:
- Context Sensitivity: Symbols and operators adapt based on context (e.g., string vs. numeric context), similar to natural language.
- Multi-Paradigm: It does not force a style. It natively supports Object-Oriented, Functional, Concurrent/Reactive, and Procedural programming equally well.
- Gradual Typing: You can write quick "scripty" code with dynamic types, or large-scale robust applications with strict static types, all in the same file.
- Unicode at the Core: Raku assumes Unicode by default, treating characters as graphemes (user-perceived characters) rather than simple bytes, preventing common text-processing bugs.
What is outstanding? (Unique features)
Raku offers features that are often libraries or "hacks" in other languages as first-class citizens:
- Grammars: This is arguably Raku's "killer feature." Instead of unreadable Regex strings, Raku allows you to define formal grammars as classes. You can parse complex files (JSON, XML, custom DSLs) natively without external tools like Lex or Yacc.
- Junctions: You can treat multiple values as a single entity using quantum-like superposition.
- Rational Arithmetic: By default, 0.1 + 0.2 equals exactly 0.3. Raku uses rational numbers (fractions) instead of floating-point math for decimals, eliminating rounding errors common in Python or JavaScript.
- Meta-Object Protocol (MOP): Raku exposes its own object system to the developer. You can hook into class creation, method dispatch, and inheritance at runtime, allowing for powerful metaprogramming.
- Concurrency: It has high-level primitives for concurrency that are safer than threads. It distinguishes between Parallelism (using multiple cores for speed) and Concurrency (managing shared state) using tools like Promises, Supplies (Reactive UI), and Channels.
Raku for AI and MCP
Raku is a "sleeper" choice for specific AI domains, particularly those involving language and orchestration.
Suitability for AI:
- Symbolic AI and NLP: Raku excels here. Its Grammars engine makes it perhaps the best language in existence for cleaning, parsing, and restructuring messy text data before feeding it into a model.
- Data Pipelines: Its functional chains and concurrency support make it excellent for building "glue" code that orchestrates data movement between models.
- Weakness: It lacks the massive ecosystem of numeric tensors and GPU acceleration found in Python (PyTorch/TensorFlow). You would likely use Raku to prepare data or orchestrate agents, rather than training the neural network itself.
Interfacing through MCP (Model Context Protocol): Raku is surprisingly well-architected for the Model Context Protocol (MCP), which connects LLMs to external tools and data.
- Introspection: MCP requires tools to describe their own schemas (inputs/outputs) to the AI. Raku's deep introspection (MOP) and signature objects allow you to auto-generate these JSON schemas directly from your function definitions.
- Gradual Typing: MCP relies on structured data exchange. Raku's strong type system can validate AI-generated JSON inputs automatically, rejecting hallucinations before they hit your business logic.
- Asynchrony: MCP is often conversational and event-driven. Raku's React / Supply blocks (reactive programming) map perfectly to handling streams of requests from an AI agent.
Installation
# From zef
zef install MCP
# From source
git clone https://github.com/wkusnierczyk/raku-mcp-sdk
cd raku-mcp-sdk
zef install .
Quick start
Creating a server
use MCP;
use MCP::Server;
use MCP::Transport::Stdio;
use MCP::Types;
# Create server
my $server = MCP::Server::Server.new(
info => MCP::Types::Implementation.new(
name => 'my-server',
version => '1.0.0'
),
transport => MCP::Transport::Stdio::StdioTransport.new,
);
# Add a tool
$server.add-tool(
name => 'greet',
description => 'Greet someone by name',
schema => {
type => 'object',
properties => {
name => { type => 'string', description => 'Name to greet' }
},
required => ['name'],
},
handler => -> :%params {
"Hello, %params<name>!"
}
);
# Add a resource
$server.add-resource(
uri => 'info://about',
name => 'About',
description => 'About this server',
mimeType => 'text/plain',
reader => { 'This is my MCP server!' }
);
# Start serving
await $server.serve;
Using the fluent builder API
use MCP::Server::Tool;
# Build tools with fluent API
my $calculator = tool()
.name('add')
.description('Add two numbers')
.number-param('a', description => 'First number', :required)
.number-param('b', description => 'Second number', :required)
.annotations(title => 'Calculator', :readOnly, :idempotent)
.handler(-> :%params { %params<a> + %params<b> })
.build;
$server.add-tool($calculator);
Creating a client
use MCP;
use MCP::Client;
use MCP::Transport::Stdio;
use MCP::Types;
# Connect to an MCP server process
my $proc = Proc::Async.new('path/to/mcp-server');
my $client = MCP::Client::Client.new(
info => MCP::Types::Implementation.new(
name => 'my-client',
version => '1.0.0'
),
transport => MCP::Transport::Stdio::StdioTransport.new(
input => $proc.stdout,
output => $proc.stdin,
),
);
await $client.connect;
# List and call tools (with pagination support)
my $tools-result = await $client.list-tools;
for $tools-result<tools> -> $tool {
say "Tool: $tool.name() - $tool.description()";
}
# Use $tools-result<nextCursor> for pagination if present
my $call-result = await $client.call-tool('greet', arguments => { name => 'World' });
say $call-result.content[0].text; # "Hello, World!"
# Read resources
my @contents = await $client.read-resource('info://about');
say @contents[0].text;
Examples
The examples/ directory contains runnable, minimal reference implementations that demonstrate common MCP SDK patterns. Use them to quickly validate your environment, see how transports and handlers fit together, and copy a solid starting point for your own servers and clients.
To list available examples:
# execute without naming any example
make run-examples
Available examples:
• advanced-client # In-process loopback covering pagination, completions, roots, and elicitation
• advanced-server # Pagination, resource subscriptions, and cancellation
• http-server # Streamable HTTP server transport
• oauth-server # HTTP server with OAuth 2.1 token validation
• sampling-client # Client-side sampling handler
• extensions # Extension registration, negotiation, and method dispatch
• simple-server # Stdio server with tools, resources, and prompts
Run any example with:
# execute naming an example
make run-example EXAMPLE=advanced-server
→ Running example: advanced-server...
== Pagination ==
Page 1 tools: tool-beta, tool-epsilon
Page 1 nextCursor present: yes
Page 2 tools: tool-alpha, tool-delta
Page 2 nextCursor present: yes
== Resource Subscriptions ==
Subscribe result keys:
Update notifications sent: 1
Last notification method: notifications/resources/updated
== Cancellation ==
Marked cancelled: yes
Responses sent after cancellation: 0
Done.
Features
Tools are functions that the LLM can call.
$server.add-tool(
name => 'search',
description => 'Search for something',
schema => {
type => 'object',
properties => {
query => { type => 'string' },
limit => { type => 'integer', default => 10 },
},
required => ['query'],
},
handler => -> :%params {
# Return string, Content object, or CallToolResult
my @results = do-search(%params<query>, %params<limit>);
@results.join("\n")
}
);
Resources
Resources provide read-only data.
# Static resource
$server.add-resource(
uri => 'config://app',
name => 'App Config',
mimeType => 'application/json',
reader => { to-json(%config) }
);
# File-based resource
use MCP::Server::Resource;
$server.add-resource(file-resource('data.txt'.IO));
Prompts
Prompts are templated message workflows.
use MCP::Server::Prompt;
$server.add-prompt(
name => 'summarize',
description => 'Summarize content',
arguments => [
{ name => 'content', required => True },
{ name => 'length', required => False },
],
generator => -> :%params {
my $length = %params<length> // 'medium';
user-message("Please provide a $length summary of: %params<content>")
}
);
Development
The project uses a comprehensive Makefile for development tasks:
make about # Show project information
make all # Full build: dependencies → build → test
make test # Run test suite
Makefile Targets
| Target | Description | Notes |
|---|
| Primary targets |
|---|
all | Install deps, build, and test | Runs dependencies → build → test |
build | Validate and precompile modules | Runs validate then build-precompile |
build-precompile | Precompile the main module | Uses raku -Ilib -c lib/MCP.rakumod fallback |
test | Build and run tests | Depends on build |
install | Install module globally | Uses zef install . --/test |
| Validation and metadata |
|---|
validate | Validate META6.json and provides entries | Runs validate-meta and validate-provides |
validate-meta | Check required META6.json fields | Ensures name, version, description, provides |
validate-provides | Verify provides paths exist | Prints each resolved entry |
| Dependencies |
|---|
dependencies | Install runtime dependencies | zef install --deps-only . |
dependencies-dev | Install dev dependencies | Includes Prove6, Test::META, Mi6, Racoco |
dependencies-update | Update dependencies | Runs zef update and zef upgrade |
| Lint and formatting |
|---|
lint | Run syntax + META checks | Runs lint-syntax and lint-meta |
lint-syntax | Compile-check source files | Uses raku -Ilib -c |
lint-meta | Validate META6.json | Requires JSON::Fast |
format | Format guidance and whitespace scan | Non-destructive |
format-fix | Remove trailing whitespace | Applies to source + tests |
check | Run lint + tests | Equivalent to lint test |
| Testing and coverage |
|---|
test-verbose | Run tests with verbose output | Uses prove6 with --verbose |
test-file | Run a specific test file | FILE=t/01-types.rakutest |
test-quick | Run tests without build | Skips build |
coverage | Generate coverage report | HTML in coverage-report/report.html, raw data in .racoco/ |
| Documentation |
|---|
docs | Generate text docs into docs/ | Uses raku --doc=Text per module |
docs-serve | Serve docs (placeholder) | Not implemented |
architecture-diagram | Build architecture PNG | Renders architecture/architecture.mmd to architecture/architecture.png |
| Distribution and release |
|---|
dist | Create source tarball | Writes to dist/ |
release | Interactive release helper | Prompts for fez upload |
| Utilities and examples |
|---|
about | Show project info | Prints metadata from Makefile |
repl | Start REPL with project loaded | raku -Ilib -MMCP |
run-example | Run example by name | EXAMPLE=simple-server |
info | Show toolchain + stats | Raku/Zef/Prove versions |
list-modules | List module files | From lib/ |
list-tests | List test files | From t/ |
| Install/uninstall |
|---|
install-local | Install to home | Uses zef install . --to=home |
install-force | Force install | Uses zef install . --force-install |
uninstall | Uninstall module | zef uninstall MCP |
| CI helpers |
|---|
ci | CI pipeline | dependencies → lint → test |
ci-full | Full CI pipeline | dependencies-dev → lint → test → coverage |
| Version management |
|---|
version | Show or update project version | make TAG=1.2.3 version updates Makefile + META6.json + README (no tag) |
bump-patch | Patch bump | make bump-patch bumps to the next patch version (no tag) |
bump-minor | Minor bump | make bump-minor bumps to the next minor version (no tag) |
bump-major | Major bump | make bump-major bumps to the next major version (no tag) |
| Cleaning |
|---|
clean | Remove build/coverage/dist | Runs clean-build/clean-coverage/clean-dist |
clean-build | Remove precomp/build dirs | Removes .precomp and .build |
clean-coverage | Remove coverage output | Removes .racoco and coverage-report |
clean-dist | Remove tarballs/dist dir | Removes dist/ and *.tar.gz |
clean-all | Deep clean | Also removes docs build output |
Environment Variables
| Variable | Description |
|---|
V=1 | Enable verbose output |
NO_COLOR=1 | Disable colored output |
FILE=<path> | Specify file for test-file target |
EXAMPLE=<name> | Specify example for run-example target |
Versioning
Use make version to set an explicit version (no tag):
make TAG=0.10.0 version
Or use a positional argument:
make version 0.10.0
For quick bumps, use:
make bump-patch # x.y.(z+1)
make bump-minor # x.(y+1).0
make bump-major # (x+1).0.0
Coverage Prerequisites
The coverage report uses RaCoCo. If racoco is not on your PATH, add the
Raku site bin directory:
export PATH="$(brew --prefix rakudo-star)/share/perl6/site/bin:$PATH"
Then run:
make coverage
# report: coverage-report/report.html
Project structure
MCP/
├── MCP.rakumod # Main module, re-exports
├── MCP/
│ ├── Types.rakumod # Protocol types
│ ├── JSONRPC.rakumod # JSON-RPC 2.0
│ ├── Transport/
│ │ ├── Base.rakumod # Transport role
│ │ ├── Stdio.rakumod # Stdio transport
│ │ ├── StreamableHTTP.rakumod # HTTP transport with SSE
│ │ └── SSE.rakumod # Legacy SSE transport (2024-11-05)
│ ├── OAuth.rakumod # OAuth 2.1 types, PKCE, exceptions
│ ├── OAuth/
│ │ ├── Client.rakumod # Client-side OAuth flow
│ │ └── Server.rakumod # Server-side token validation
│ ├── Server.rakumod # Server implementation
│ ├── Server/
│ │ ├── Tool.rakumod # Tool helpers
│ │ ├── Resource.rakumod # Resource helpers
│ │ └── Prompt.rakumod # Prompt helpers
│ └── Client.rakumod # Client implementation
Contributing
Contributions are welcome. You can:
- Open or join a discussion.
- Open an issue for bugs, feature requests, or questions.
- Fork or clone the repository, create a branch, push it, and open a pull request.
Note
- Pushing directly to
main is disallowed. - Merging into
main without positive review and passing checks is disallowed. - Merging into
main of stalled pull requests is disallowed.
You need to merge main into your branch, or rebase your branch onto main before being able to merge into main. - Merging into
main with the whole multi-commit history of your branch is disallowed.
You can only squash-merge as one commit, with a detailed description of your changes.
License
MIT License - see the LICENSE file and the MIT License site.
References
Acknowledgments
Building this repository was supported by:
- Claude (Claude Opus 4.5)
Claude was used to draft and revise the implementation plan, and to generate code stubs and draft the implementation. - ChatGPT (Codex / GPT-5)
ChatGPT was used to review, explain, and critically asses the generated code. - Gemini (Gemini 3 Pro)
Gemini was used to investigate the status of MCP, Raku, and the (missing) implementation of MCP as a Raku SDK.
About
$ make about
Raku MCP SDK: Raku Implementation of the Model Context Protocol
├─ version: 0.28.3
├─ developer: mailto:waclaw.kusnierczyk@gmail.com
├─ source: https://github.com/wkusnierczyk/raku-mcp-sdk
└─ licence: MIT https://opensource.org/licenses/MIT