A Crystal implementation of Anthropic's Model Context Protocol (MCP), providing type-safe bindings for building MCP servers and clients with the latest protocol enhancements.
The Model Context Protocol (MCP) is an open standard that enables AI applications to securely connect to various data sources and tools. Think of MCP as a "USB-C port for AI" - it provides a standardized way to connect AI models to:
- Resources: Files, databases, APIs, and other data sources
- Tools: Functions that AI models can execute
- Prompts: Templated interactions and workflows
- Elicitation: Structured user input collection through the client
Current Implementation: MCP 2025-06-18 ✨
- ✅ Full MCP 2025-06-18 support - Latest protocol with all new features
- ✅ Backward compatibility - Graceful handling of MCP 2025-03-26 clients
- ✅ Protocol version negotiation - Automatic feature detection and compatibility
- ✅ Zero breaking changes - Existing 2025-03-26 code continues to work
Server-to-client structured user input requests with schema validation:
# Create a user configuration form
schema = MCProtocol::ElicitRequestSchema.new(
properties: {
"email" => MCProtocol::StringSchema.new(
description: "User's email address",
title: "Email",
format: "email"
).as(MCProtocol::PrimitiveSchemaDefinition)
},
required: ["email"]
)
request = MCProtocol::ElicitRequest.new(
params: MCProtocol::ElicitRequestParams.new(
message: "Please configure your user preferences.",
requested_schema: schema
)
)Type-safe form validation with primitive schema types:
# Boolean schema with default
boolean_schema = MCProtocol::BooleanSchema.new(
description: "Enable notifications",
title: "Notifications",
default: true
)
# Number schema with constraints
number_schema = MCProtocol::NumberSchema.new(
"integer",
description: "Age in years",
minimum: 13,
maximum: 120
)
# Enum schema with display names
enum_schema = MCProtocol::EnumSchema.new(
["light", "dark", "auto"],
enum_names: ["Light Theme", "Dark Theme", "Auto-detect"],
description: "UI theme preference"
)New ResourceLink content type for file and resource references:
# Link to a file resource
file_link = MCProtocol::ResourceLink.new(
name: "user_guide",
uri: "file:///docs/guide.pdf",
title: "User Guide",
description: "Complete user documentation",
meta: {
"file_size" => JSON::Any.new("2.4MB"),
"last_updated" => JSON::Any.new("2025-06-18T10:30:00Z")
}
)
# Use in prompt messages
prompt = MCProtocol::PromptMessage.new(
content: file_link.as(MCProtocol::ContentBlock),
role: MCProtocol::Role::User
)Enhanced metadata fields across all major types:
# Every content type now supports rich metadata
text_content = MCProtocol::TextContent.new(
"Analysis results are ready for review.",
meta: {
"content_type" => JSON::Any.new("analysis_result"),
"confidence" => JSON::Any.new(0.95),
"created_at" => JSON::Any.new("2025-06-18T14:30:00Z")
}
)Tools now support structured output schemas:
tool = MCProtocol::Tool.new(
name: "analyze_document",
title: "Document Analysis",
description: "Analyze document content and extract insights",
outputSchema: JSON::Any.new({
"type" => "object",
"properties" => {
"summary" => {"type" => "string"},
"confidence" => {"type" => "number"}
}
})
)- ✅ Type-Safe: Hand-crafted Crystal classes with full type safety
- ✅ Complete Protocol Support: Full MCP 2025-06-18 implementation
- ✅ Elicitation System: Structured user input collection
- ✅ Enhanced Metadata: Rich metadata support across all types
- ✅ Schema Validation: Boolean, Number, String, and Enum schemas
- ✅ Protocol Versioning: Backward compatibility with graceful degradation
- ✅ Server & Client Support: Build both MCP servers and clients
- ✅ JSON-RPC 2.0: Full compliance with the underlying JSON-RPC protocol
- ✅ SSE Compatible: Ready for Server-Sent Events implementations
- ✅ Extensible: Easy to extend with custom capabilities
-
Add the dependency to your
shard.yml:dependencies: mcprotocol: github: nobodywasishere/mcprotocol
-
Run
shards install
require "mcprotocol"
# Protocol version negotiation
protocol_version = MCProtocol.negotiate_protocol_version("2025-06-18") # or "2025-03-26"
# Parse MCP messages with version awareness
message_data = %{{"method": "elicitation/create", "params": {...}}}
request = MCProtocol.parse_message(message_data, protocol_version: protocol_version)
# Check feature availability
if MCProtocol.feature_available?("elicitation", protocol_version)
# Handle elicitation requests
end
# Create MCP objects with enhanced capabilities
capabilities = MCProtocol::ServerCapabilities.new(
tools: MCProtocol::ServerCapabilitiesTools.new(listChanged: true),
resources: MCProtocol::ServerCapabilitiesResources.new(subscribe: true),
elicitation: MCProtocol::ServerCapabilitiesElicitation.new(enabled: true) # New!
)require "mcprotocol"
require "json"
class AdvancedMCPServer
def initialize
@tools = [
MCProtocol::Tool.new(
name: "collect_feedback",
title: "Collect User Feedback",
description: "Collect structured feedback from users",
outputSchema: JSON::Any.new({
"type" => "object",
"properties" => {
"rating" => {"type" => "integer", "minimum" => 1, "maximum" => 5},
"comments" => {"type" => "string"}
}
})
)
]
end
def handle_initialize(request : MCProtocol::InitializeRequest)
MCProtocol::InitializeResult.new(
protocolVersion: MCProtocol::PROTOCOL_VERSION, # "2025-06-18"
capabilities: MCProtocol::ServerCapabilities.new(
tools: MCProtocol::ServerCapabilitiesTools.new(listChanged: true),
elicitation: MCProtocol::ServerCapabilitiesElicitation.new(enabled: true)
),
serverInfo: MCProtocol::Implementation.new(
name: "advanced-server",
title: "Advanced MCP Server",
version: "1.0.0"
)
)
end
def handle_elicitation_request
# Create feedback collection form
rating_schema = MCProtocol::NumberSchema.new(
"integer",
description: "Rate your experience",
title: "Rating",
minimum: 1,
maximum: 5
)
comments_schema = MCProtocol::StringSchema.new(
description: "Additional comments",
title: "Comments",
min_length: 10,
max_length: 500
)
schema = MCProtocol::ElicitRequestSchema.new(
properties: {
"rating" => rating_schema.as(MCProtocol::PrimitiveSchemaDefinition),
"comments" => comments_schema.as(MCProtocol::PrimitiveSchemaDefinition)
},
required: ["rating"]
)
MCProtocol::ElicitRequest.new(
params: MCProtocol::ElicitRequestParams.new(
message: "Please rate your experience and provide feedback.",
requested_schema: schema
)
)
end
def handle_call_tool(request : MCProtocol::CallToolRequest)
case request.params.name
when "collect_feedback"
# Use elicitation to collect structured feedback
elicit_request = handle_elicitation_request
# Return with ResourceLink to documentation
help_link = MCProtocol::ResourceLink.new(
name: "feedback_help",
uri: "https://docs.example.com/feedback",
title: "Feedback Guidelines",
description: "How to provide effective feedback"
)
MCProtocol::CallToolResult.new(
content: [
MCProtocol::TextContent.new("Feedback collection initiated."),
help_link.as(MCProtocol::ContentBlock)
],
structuredContent: JSON::Any.new({
"elicitation_id" => JSON::Any.new("feedback_001"),
"status" => JSON::Any.new("pending")
})
)
else
raise "Unknown tool: #{request.params.name}"
end
end
endrequire "mcprotocol"
# Server creates an elicitation request
def create_user_setup_form
# Define schema fields
name_schema = MCProtocol::StringSchema.new(
description: "Your display name",
title: "Display Name",
min_length: 1,
max_length: 50
)
theme_schema = MCProtocol::EnumSchema.new(
["light", "dark", "auto"],
enum_names: ["Light Theme", "Dark Theme", "Auto-detect"],
description: "Preferred UI theme",
title: "Theme"
)
notifications_schema = MCProtocol::BooleanSchema.new(
description: "Receive email notifications",
title: "Email Notifications",
default: true
)
# Create complete form schema
schema = MCProtocol::ElicitRequestSchema.new(
properties: {
"displayName" => name_schema.as(MCProtocol::PrimitiveSchemaDefinition),
"theme" => theme_schema.as(MCProtocol::PrimitiveSchemaDefinition),
"notifications" => notifications_schema.as(MCProtocol::PrimitiveSchemaDefinition)
},
required: ["displayName"]
)
# Create elicitation request
MCProtocol::ElicitRequest.new(
params: MCProtocol::ElicitRequestParams.new(
message: "Welcome! Please set up your preferences.",
requested_schema: schema
)
)
end
# Client responds with structured data
def handle_user_response(result : MCProtocol::ElicitResult)
case result.action
when "accept"
if content = result.content
puts "User configuration:"
puts " Display Name: #{content["displayName"]}"
puts " Theme: #{content["theme"]}"
puts " Notifications: #{content["notifications"]}"
end
when "decline"
puts "User declined setup, using defaults"
when "cancel"
puts "User cancelled setup"
end
endThe library includes all MCP protocol message types for both 2025-06-18 and backward compatibility:
ElicitRequest- Server requests structured user inputElicitResult- Client responds with form data or action
CallToolResult- Now supportsstructuredContentfieldTool- Now supportsoutputSchemafor structured responses- All content types - Now support
_metametadata fields PromptMessage- Enhanced content block support
InitializeRequest/Result- Connection and capability negotiationListToolsRequest/Result- Tool discovery and listingCallToolRequest/Result- Tool executionListResourcesRequest/Result- Resource discoveryReadResourceRequest/Result- Resource content accessListPromptsRequest/Result- Prompt template discoveryGetPromptRequest/Result- Prompt template access
The library supports all MCP protocol methods:
MCProtocol::METHOD_TYPES.keys
# => ["initialize", "ping", "resources/list", "tools/call", "elicitation/create", ...]
# Check protocol version support
MCProtocol::SUPPORTED_PROTOCOL_VERSIONS
# => ["2025-06-18", "2025-03-26"]┌─────────────────┐ JSON-RPC 2.0 ┌─────────────────┐
│ MCP Client │ ◄─────────────────► │ MCP Server │
│ (AI Application)│ + Elicitation │ (Your Service) │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ MCProtocol │ │ MCProtocol │
│ Crystal Library │ │ Crystal Library │
│ (2025-06-18) │ │ (2025-06-18) │
└─────────────────┘ └─────────────────┘
MCProtocol::ClientRequest- Union type for all client requestsMCProtocol::ServerResult- Union type for all server responsesMCProtocol::ClientNotification- Union type for client notificationsMCProtocol::ServerNotification- Union type for server notifications
MCProtocol::ElicitRequest- Server requests for user inputMCProtocol::ElicitResult- Client responses with form dataMCProtocol::BooleanSchema- Boolean field validationMCProtocol::NumberSchema- Number field validationMCProtocol::StringSchema- String field validationMCProtocol::EnumSchema- Enumerated value validation
MCProtocol::ContentBlock- Union of all content typesMCProtocol::ResourceLink- Resource reference content typeMCProtocol::TextContent- Text content with metadataMCProtocol::ImageContent- Image content with metadata
MCProtocol::ClientCapabilities- What the client supportsMCProtocol::ServerCapabilities- What the server providesMCProtocol::ServerCapabilitiesElicitation- Elicitation support
MCProtocol::Tool- Enhanced tool definitions with output schemasMCProtocol::Resource- Resource definitions with metadataMCProtocol::Prompt- Prompt templates with title support
begin
# Protocol version negotiation
version = MCProtocol.negotiate_protocol_version("2025-06-18")
request = MCProtocol.parse_message(message_data, protocol_version: version)
rescue MCProtocol::UnsupportedProtocolVersionError => ex
puts "Unsupported protocol version: #{ex.message}"
rescue MCProtocol::ParseError => ex
puts "Failed to parse MCP message: #{ex.message}"
endMigrating from 2025-03-26 to 2025-06-18:
# Check what's available in your protocol version
def handle_tool_call(request, protocol_version)
result = execute_tool(request)
if MCProtocol.feature_available?("structured_content", protocol_version)
# Use enhanced CallToolResult with structured content
MCProtocol::CallToolResult.new(
content: [MCProtocol::TextContent.new("Tool executed successfully")],
structuredContent: JSON::Any.new({"status" => "success"})
)
else
# Use basic CallToolResult for older clients
MCProtocol::CallToolResult.new(
content: [MCProtocol::TextContent.new("Tool executed successfully")]
)
end
endWhen implementing MCP servers with elicitation:
- Validate all inputs - Especially user-provided elicitation responses
- Implement proper authentication - Use OAuth 2.0 for remote servers
- Limit resource access - Only expose necessary data and tools
- Sanitize elicitation schemas - Prevent injection attacks through form fields
- Log security events - Monitor for suspicious activity
- Handle errors gracefully - Don't leak sensitive information
See the examples/ directory for complete working examples:
elicitation_example.cr: Complete elicitation system workflowsschema_examples.cr: Schema definition and validation examplescontent_blocks_example.cr: Enhanced content blocks with ResourceLinkbasic_server.cr: Simple tool and resource serversse_server.cr: Server-Sent Events implementation
Build and test the library:
# Build the library
crystal build src/mcprotocol.cr
# Run tests (87 passing tests)
crystal spec
# Run examples
crystal run examples/elicitation_example.cr
crystal run examples/schema_examples.cr# Full test suite
crystal spec
# Test specific features
crystal spec spec/elicitation_system_spec.cr
crystal spec spec/schema_system_spec.cr
crystal spec spec/backward_compatibility_spec.crFor detailed migration information from older MCP versions:
BREAKING_CHANGES.md- Complete list of breaking changes and solutionsMIGRATION_SCRIPT.md- Automated migration assistance and scripts
- Fork it (https://github.com/nobodywasishere/mcprotocol/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
MIT License - see LICENSE for details.
- Margret Riegert - creator and maintainer
Ready for Production ✨ | MCP 2025-06-18 Complete 🚀 | 87 Tests Passing ✅