MCP Rails Implementation
Implement a complete Model Context Protocol stack in Rails, enabling your app to:
- Connect to external MCP servers as a client
- Expose itself as an MCP server for Claude Desktop, VS Code, etc.
- Manage subprocess MCP servers via Docker containers
Quick Decision Guide
What do you need?
| Goal | Start With | |------|------------| | Connect to external MCP tools | Client Core | | Build an MCP server in Rails | Server Implementation | | Create multi-tool MCP server | Tools and Schemas | | Add UI widgets to tool results | UI Widget Pipeline | | Run MCP servers as Docker containers | Docker Supervisor | | Add OAuth to MCP connections | OAuth Flow |
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ Your Rails App │
├─────────────────────────────────────────────────────────────┤
│ MCP Client Layer │ MCP Server Layer │
│ ┌─────────────────────┐ │ ┌─────────────────────┐ │
│ │ MCPClient │ │ │ BaseMCPServer │ │
│ │ - Remote HTTP/SSE │ │ │ - JSON-RPC Handler │ │
│ │ - Subprocess Docker │ │ │ - Tool Registry │ │
│ │ - OAuth PKCE Flow │ │ │ - Resource Support │ │
│ └─────────────────────┘ │ └─────────────────────┘ │
│ │ │ │ │
│ ▼ │ ▼ │
│ ┌─────────────────────┐ │ ┌─────────────────────┐ │
│ │ Transport Layer │ │ │ MCP::ServersController│ │
│ │ - HTTP Transport │ │ │ - POST /mcp/:name/ │ │
│ │ - SSE Transport │ │ │ - GET /mcp/:name/sse│ │
│ │ - Subprocess I/O │ │ │ - OAuth Auth │ │
│ └─────────────────────┘ │ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Implementation Order
Phase 1: Client Core (2-3 hours)
- Read Client Core
- Run scaffold:
ruby scripts/scaffold_mcp_client.rb - Implement basic JSON-RPC communication
Phase 2: Transport Layer (2-3 hours)
- Read Transport Layer
- Choose transport strategy: HTTP-first, SSE-first, or subprocess
- Implement transport abstraction
Phase 3: OAuth Flow (Optional, 3-4 hours)
- Read OAuth Flow
- Implement PKCE discovery and token exchange
- Add token storage to your tool configuration model
Phase 4: Docker Supervisor (Optional, 4-6 hours)
- Read Docker Supervisor
- Implement process lifecycle management
- Add multi-worker coordination via database locks
Phase 5: Server Implementation (4-6 hours)
- Read Server Implementation
- Create BaseMCPServer DSL
- Implement JSON-RPC controller
Key Files to Create
app/
├── services/
│ ├── mcp_client.rb # Core client
│ ├── mcp_client/
│ │ ├── session.rb # Session management
│ │ ├── oauth_flow.rb # OAuth PKCE
│ │ └── transport/
│ │ ├── json_rpc_transport.rb # Base interface
│ │ ├── remote_http_transport.rb # HTTP transport
│ │ ├── remote_sse_transport.rb # SSE transport
│ │ └── subprocess_transport.rb # Docker transport
│ └── mcp_docker_process_supervisor.rb # Container management
├── controllers/mcp/
│ └── servers_controller.rb # MCP server endpoints
├── mcp_servers/
│ └── base_mcp_server.rb # Server DSL
└── models/mcp/
├── client.rb # Client registration
└── client_session.rb # Session tracking
db/migrate/
├── create_mcp_clients.rb
└── create_mcp_client_sessions.rb
Output Checklist
When implementation is complete, verify:
- [ ] MCP client can initialize sessions and persist
Mcp-Session-Id - [ ] Client can list tools (
tools/list) and call tools (tools/call) - [ ] Transport fallback works (HTTP → SSE or vice versa)
- [ ] OAuth-enabled tools trigger PKCE flow when unauthorized
- [ ] Subprocess mode starts Docker containers with health checks
- [ ] Multi-worker coordination prevents duplicate supervisors
- [ ] Rails app exposes
/mcp/:serverJSON-RPC endpoint - [ ] Optional SSE endpoint at
/mcp/:server/ssestreams responses - [ ] UI resources return correct
mimeTypeanduriformats
Common Pitfalls
- Missing
Mcp-Session-Idheader: Always send session ID afterinitializecall - Ignoring transport fallback: When HTTP returns 404/405, try SSE transport
- Treating OAuth tools like legacy tools: Must return
WWW-Authenticatechallenge on missing token - Failing to release locks for stale supervisors: Implement timeout-based cleanup
- Embedding UI resources without correct format: Ensure
mimeTypeandurifollow spec - Not handling OAuth token refresh: Tokens expire, implement refresh or re-auth flow
- Process coordination race conditions: Use database locks for subprocess ownership
Testing Notes
Client Testing
- Initialize session and assert
Mcp-Session-Idpersists across requests - Exercise both HTTP and SSE transport paths
- Simulate OAuth
401challenge and verify token exchange flow - Test transport fallback when primary transport fails
Server Testing
- Validate JSON-RPC success and error envelope formats
- Confirm scope enforcement on protected tools
- Test session creation, extension, and expiration
- Verify tool registration and discovery
Subprocess Testing
- Start supervisor and wait for ready state
- Kill process and verify restart or failure marking
- Simulate multi-worker contention for supervisor ownership
- Test cleanup of stale containers
References
- Client Core - JSON-RPC client implementation
- Transport Layer - HTTP, SSE, subprocess transports
- OAuth Flow - PKCE discovery and token exchange
- Docker Supervisor - Container lifecycle
- Server Implementation - Building MCP servers
- Session Management - Client sessions
- Multi-Worker Coordination - Database locks
- Host Integration - Tool configuration and app wiring
- UI Resources - Rich client rendering resources
- Testing - Test strategies and examples
- Tools and Schemas - Multi-tool server patterns
- UI Widget Pipeline - Widget template hydration