Implement Ruby MCP server with auth feature#3
Open
bettercallsaulj wants to merge 32 commits intodev_ruby_sdkfrom
Open
Implement Ruby MCP server with auth feature#3bettercallsaulj wants to merge 32 commits intodev_ruby_sdkfrom
bettercallsaulj wants to merge 32 commits intodev_ruby_sdkfrom
Conversation
added 30 commits
March 19, 2026 14:45
Create the auth module foundation for the Ruby SDK with the AuthContext class that represents authentication context for requests. AuthContext provides: - Storage for user_id, scopes, audience, token_expiry, and authenticated status - has_scope? method with case-insensitive matching against space-separated scopes - AuthContext.empty class method for creating unauthenticated contexts - AuthContext.anonymous(scopes) class method for auth-disabled mode The class follows the same pattern as the JS SDK's GopherAuthContext and C# SDK's AuthContext implementations. Files added: - lib/gopher_orch/auth/auth_context.rb - spec/gopher_orch/auth/auth_context_spec.rb
Create the error handling foundation for the Ruby SDK auth module with ErrorCodes constants and AuthError exception class. ErrorCodes module provides: - SUCCESS (0) for successful operations - INVALID_TOKEN (-1000) for malformed tokens - EXPIRED_TOKEN (-1001) for tokens past expiration - INVALID_SIGNATURE (-1002) for signature verification failures - INVALID_ISSUER (-1003) for issuer mismatch - INVALID_AUDIENCE (-1004) for audience mismatch - INSUFFICIENT_SCOPE (-1005) for missing required scopes - JWKS_FETCH_FAILED (-1006) for JWKS retrieval failures - INVALID_KEY (-1007) for invalid key errors - NETWORK_ERROR (-1008) for network failures - INVALID_CONFIG (-1009) for configuration errors - NOT_INITIALIZED (-1012) for uninitialized library - INTERNAL_ERROR (-1013) for internal errors - description(code) class method for human-readable error messages
Create the ProtectedResourceMetadata class implementing RFC 9728 OAuth 2.0 Protected Resource Metadata specification. ProtectedResourceMetadata provides: - resource: the protected resource identifier (URL) - authorization_servers: list of authorization server URLs - scopes_supported: list of OAuth scopes supported by the resource - bearer_methods_supported: supported bearer token delivery methods - resource_documentation: optional documentation URL Features: - Keyword argument initialization with sensible defaults - to_h method for hash conversion with snake_case keys - to_json method for JSON serialization - Automatic omission of nil optional fields (resource_documentation) - Mutable attribute accessors for post-creation modification Files added: - lib/gopher_orch/auth/oauth/protected_resource_metadata.rb - spec/gopher_orch/auth/oauth/protected_resource_metadata_spec.rb
…ation (#2) Create OAuth metadata classes implementing RFC 8414 Authorization Server Metadata and OpenID Connect Discovery specifications. AuthorizationServerMetadata (RFC 8414) provides: - issuer: authorization server identifier - authorization_endpoint: URL for authorization requests - token_endpoint: URL for token requests - jwks_uri: URL of JSON Web Key Set (optional) - registration_endpoint: dynamic client registration URL (optional) - scopes_supported: list of supported OAuth scopes - response_types_supported: supported response types - grant_types_supported: supported grant types - token_endpoint_auth_methods_supported: supported auth methods - code_challenge_methods_supported: supported PKCE methods OpenIdConfiguration extends AuthorizationServerMetadata with: - userinfo_endpoint: URL for user info requests (optional) - subject_types_supported: supported subject identifier types - id_token_signing_alg_values_supported: supported ID token algorithms
…helper (#2) Create ClientRegistrationResponse class implementing RFC 7591 Dynamic Client Registration and WwwAuthenticate module for RFC 6750 Bearer challenges. ClientRegistrationResponse (RFC 7591) provides: - client_id: the assigned client identifier - client_secret: the client secret (optional for public clients) - client_id_issued_at: Unix timestamp when client_id was issued - client_secret_expires_at: Unix timestamp when secret expires (0 = never) - redirect_uris: list of registered redirect URIs - grant_types: allowed grant types for this client - response_types: allowed response types - token_endpoint_auth_method: authentication method for token endpoint WwwAuthenticate module provides: - generate: creates RFC 6750 Bearer challenge headers with parameters: - realm: authentication realm - resource_metadata: URL to OAuth protected resource metadata - scope: required scopes - error: OAuth error code - error_description: human-readable error message - escape: safely escapes values for HTTP header quoted strings - Escapes backslashes and double quotes per RFC 7230
Create the auth example project structure with Gemfile and main module skeleton for the OAuth-protected MCP server demonstration. Project structure: - examples/auth/lib/ - main application code - examples/auth/lib/middleware/ - Rack middleware components - examples/auth/lib/routes/ - HTTP route handlers - examples/auth/lib/tools/ - MCP tool implementations - examples/auth/spec/ - test files Gemfile dependencies: - gopher_orch: SDK reference via path (../../) - sinatra (~> 3.0): web framework - puma (~> 6.0): application server - json (~> 2.6): JSON handling - rack (~> 2.2): Rack utilities - rspec (~> 3.12): testing framework (dev/test) - rack-test (~> 2.1): Rack testing utilities (dev/test)
Create the Config class for loading and managing server configuration from INI-style configuration files. Config class provides: - host: server bind address (default: 0.0.0.0) - port: server port (default: 3000) - server_url: public URL of the server - auth_server_url: OAuth authorization server URL - jwks_uri: JSON Web Key Set URL (derived from auth_server_url) - issuer: token issuer (derived from auth_server_url) - client_id: OAuth client identifier - client_secret: OAuth client secret - oauth_authorize_url: authorization endpoint (derived) - oauth_token_url: token endpoint (derived) - allowed_scopes: space-separated list of allowed scopes - exchange_idps: comma-separated list of token exchange IDPs - jwks_cache_duration: JWKS cache TTL in seconds - jwks_auto_refresh: enable automatic JWKS refresh - request_timeout: HTTP request timeout in seconds - auth_disabled: bypass authentication (development mode)
Create CORS handling middleware for cross-origin request support with
MCP protocol-specific headers.
Cors module provides:
- STANDARD_HEADERS constant with all CORS headers:
- Access-Control-Allow-Origin: * (allow all origins)
- Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD
- Access-Control-Allow-Headers: standard headers plus MCP-specific:
- Mcp-Session-Id: MCP session identifier
- Mcp-Protocol-Version: MCP protocol version
- Access-Control-Expose-Headers: WWW-Authenticate, Content-Length, Content-Type
- Access-Control-Max-Age: 86400 (24 hour preflight cache)
- preflight_response: returns [204, headers, ['']] for OPTIONS requests
CorsMiddleware Rack middleware provides:
- Automatic OPTIONS request handling with preflight response
- CORS header injection for all non-OPTIONS responses
- Preserves original response headers while adding CORS headers
- Transparent passthrough to wrapped application
Create the health check endpoint for server monitoring and load balancer
health probes.
Health module (Sinatra extension) provides:
- GET /health: Returns JSON health status response
- status: 'ok' indicating server is running
- timestamp: ISO8601 formatted UTC timestamp
- version: AuthMcpServer::VERSION (1.0.0)
- OPTIONS /health: CORS preflight response for cross-origin requests
Response format:
{
"status": "ok",
"timestamp": "2024-01-01T00:00:00Z",
"version": "1.0.0"
}
Implementation follows the JS auth example pattern with proper
Content-Type: application/json header and CORS support.
Files added:
- examples/auth/lib/routes/health.rb
- examples/auth/spec/routes/health_spec.rb
…#2) Create the Protected Resource Metadata discovery endpoint implementing RFC 9728 OAuth 2.0 Protected Resource Metadata specification. OAuthEndpoints module (Sinatra extension) provides: - GET /.well-known/oauth-protected-resource: Returns resource metadata - GET /.well-known/oauth-protected-resource/mcp: Same response (MCP path) - OPTIONS handlers for CORS preflight on both endpoints Protected Resource Metadata response includes: - resource: MCP endpoint URL ({server_url}/mcp) - authorization_servers: list containing server_url - scopes_supported: parsed from config.allowed_scopes - bearer_methods_supported: ['header', 'query'] - resource_documentation: documentation URL ({server_url}/docs)
…ct (#2) Extend OAuthEndpoints with RFC 8414 Authorization Server Metadata and OpenID Connect Discovery endpoints. Authorization Server Metadata endpoint: - GET /.well-known/oauth-authorization-server - Returns issuer, authorization/token endpoints, JWKS URI, registration - Derives endpoints from auth_server_url (Keycloak pattern) - Includes PKCE support (S256) - OPTIONS handler for CORS OpenID Connect Discovery endpoint: - GET /.well-known/openid-configuration - Extends Authorization Server Metadata with OIDC fields - Merges OIDC scopes (openid, profile, email) with config scopes - Includes userinfo_endpoint, subject_types, signing algorithms - OPTIONS handler for CORS Helper methods added: - build_authorization_server_metadata: constructs RFC 8414 metadata - build_openid_configuration: constructs OIDC discovery document - derive_authorization_endpoint: resolves from oauth_authorize_url or auth_server_url - derive_token_endpoint: resolves from oauth_token_url or auth_server_url - derive_userinfo_endpoint: constructs from auth_server_url
…ion (#2) Extend OAuthEndpoints with authorization redirect and RFC 7591 dynamic client registration endpoints. Authorization endpoint: - GET /oauth/authorize: Redirects to OAuth authorization server - Forwards all query parameters (client_id, redirect_uri, scope, etc.) - Uses 302 redirect status - OPTIONS handler for CORS Dynamic Client Registration endpoint (RFC 7591): - POST /oauth/register: Returns client credentials - Returns 201 Created with ClientRegistrationResponse - Extracts redirect_uris from request body - Uses client_id and client_secret from server config - Sets token_endpoint_auth_method based on secret presence: - 'client_secret_post' when secret configured - 'none' for public clients (no secret) - OPTIONS handler for CORS
Create the OAuth authentication middleware with path-based access control and bearer token extraction from requests. OAuthAuthMiddleware Rack middleware provides: - Path-based access control with configurable rules - Bearer token extraction from Authorization header and query parameter - Auth context population for downstream handlers - Support for auth_disabled mode (development/testing) Path access rules: - PUBLIC_PATHS: /health, /favicon.ico (never require auth) - PUBLIC_PREFIXES: /.well-known/, /oauth/ (discovery/registration) - PROTECTED_PREFIXES: /mcp, /rpc, /events, /sse (require auth) Token extraction: - Checks Authorization header for 'Bearer <token>' first - Falls back to access_token query parameter - Returns nil if no token found
…te (#2) Complete the OAuth authentication middleware with RFC 6750 Bearer challenge response generation using the SDK's WwwAuthenticate helper. send_unauthorized method now generates: - WWW-Authenticate header with RFC 6750 Bearer challenge - realm: server_url from config - resource_metadata: URL to protected resource metadata endpoint - scope: allowed_scopes from config - error: OAuth error code (invalid_request, invalid_token, etc.) - error_description: human-readable error message - CORS headers for cross-origin error responses - Access-Control-Allow-Origin: * - Access-Control-Allow-Methods: full method list - Access-Control-Allow-Headers: including Authorization - Access-Control-Expose-Headers: WWW-Authenticate - JSON response body with error and error_description Supported OAuth error codes: - invalid_request: malformed request or missing token - invalid_token: token is expired, revoked, or malformed - insufficient_scope: token lacks required scopes
Create the MCP handler foundation with JSON-RPC 2.0 protocol support including request parsing, response building, and standard error codes. JsonRpcErrorCodes module provides standard error codes: - PARSE_ERROR (-32700): Invalid JSON - INVALID_REQUEST (-32600): Invalid JSON-RPC request - METHOD_NOT_FOUND (-32601): Method does not exist - INVALID_PARAMS (-32602): Invalid method parameters - INTERNAL_ERROR (-32603): Internal server error JsonRpcError exception class provides: - code: numeric error code - message: human-readable error description - data: optional additional error information - to_h: converts to hash for JSON response
Extend McpHandler with tool registration system and method dispatch for routing JSON-RPC requests to appropriate handlers. Tool registration: - register_tool(name, spec, &handler): registers a tool with spec and handler block - spec: hash with description, inputSchema - handler: block receiving (arguments, auth_context) - get_tools: returns array of tool specs with name field added Request handling: - handle_request(body, auth_context): main entry point - Parses JSON-RPC request - Dispatches to appropriate method handler - Returns JSON-RPC response (success or error) - Catches JsonRpcError for protocol errors - Catches StandardError for internal errors Method dispatch: - dispatch_method(method, params, auth_context): routes to handlers - Supported methods: - initialize: MCP protocol initialization - tools/list: list available tools - tools/call: execute a tool - ping: health check - Returns METHOD_NOT_FOUND error for unknown methods
Implement the MCP JSON-RPC method handlers for the auth example server. These handlers process MCP protocol messages and execute the appropriate operations. Method implementations: - handle_initialize: Returns protocol version 2024-11-05, capabilities with tools support, and server info - handle_tools_list: Returns all registered tools with their specifications via get_tools - handle_tools_call: Extracts tool name and arguments from params, validates name is present (INVALID_PARAMS error if missing), finds tool (METHOD_NOT_FOUND error if not found), and executes the tool handler with arguments and auth context - handle_ping: Returns empty hash for keepalive messages
Implement HTTP endpoints for MCP JSON-RPC communication as a Sinatra extension. These endpoints expose the MCP handler over HTTP with proper CORS support. Endpoint implementations: - POST /mcp: Main MCP endpoint that reads request body, retrieves auth context from env, calls mcp_handler.handle_request, and returns JSON response with CORS headers - POST /rpc: Alias endpoint providing same functionality as /mcp - OPTIONS /mcp: CORS preflight handler using Cors.preflight_response - OPTIONS /rpc: CORS preflight handler for the alias endpoint Implementation details: - Sinatra extension pattern using self.registered(app) - McpHelpers module adds handle_mcp_request helper to Sinatra - Reads mcp_handler from app settings (set :mcp_handler) - Reads auth_context from env['auth_context'] (set by middleware) - Sets Content-Type: application/json on all responses - Adds all CORS headers from Cors::STANDARD_HEADERS - JSON parse errors handled by mcp_handler returning PARSE_ERROR
Implement example weather tools that demonstrate OAuth scope-based
access control patterns for MCP tools. These tools provide simulated
weather data for testing authentication and authorization flows.
Tool implementations:
- get-weather: No authentication required. Returns current weather
for a specified city with temperature, condition, humidity, and
wind speed based on deterministic hash simulation
- get-forecast: Requires mcp:read scope. Returns 5-day forecast
with configurable number of days. Checks has_scope?('mcp:read')
unless auth_disabled
- get-weather-alerts: Requires mcp:admin scope. Returns weather
alerts for a region with type, severity, and message fields
Access control:
- All tools work without scope checks when config.auth_disabled
- Protected tools return error_result with isError: true when
scope check fails
- Error messages include required scope for debugging
Complete the auth example with main application entry point, run scripts for multiple platforms, documentation, and integration tests for full request flow verification. Application entry point (auth_mcp_server.rb): - AuthMcpServer::App class extending Sinatra::Base - App.setup(config) method configuring middleware and routes - Middleware chain: CorsMiddleware, OAuthAuthMiddleware - Route registration: Health, OAuthEndpoints, McpEndpoints - Creates McpHandler and registers weather tools - print_banner displays startup banner - print_endpoints lists all available endpoints - print_auth_status shows auth enabled/disabled state - start method loads config and starts Puma server Run scripts: - run_example.sh for Unix/Linux/macOS - run_example.ps1 for Windows PowerShell - Both scripts install dependencies and start server - Accept optional config_path argument
Update the gopher-orch submodule configuration to track the br_release branch and update to the latest commit. Changes: - Add branch = br_release to .gitmodules - Convert third_party/gopher-orch from embedded files to proper submodule reference - Update to commit c8e7c406 (Release version 0.1.2)
Update build.sh to include the auth example in the build process. Changes: - Add Step 6: Building auth example - Install auth example dependencies with bundle install - Run auth example tests with bundle exec rspec - Add run auth server instruction to build completion message - Handle cases where Ruby environment is not ready or example missing
Update run_example.sh to use Homebrew Ruby on macOS since the system Ruby 2.6 is too old for the SDK (requires >= 2.7.0). Add Ruby 4.0 compatibility gems that were removed from stdlib: - ostruct: Required by Rack - logger: Required by Rack Protection Changes: - Update run_example.sh to prefer Homebrew Ruby path on macOS - Add ostruct and logger gems to Gemfile for Ruby 4.0 support
The server_url was not being derived from the configured port,
causing endpoints to show port 3000 when running on port 3001.
Update derive_endpoints to compute server_url from host and port
when not explicitly set in config file.
Changes:
- Derive server_url as http://{host}:{port} when using default
- Replace 0.0.0.0 with localhost for URL display
The Helpers module in OAuthEndpoints was defined but never included in the Sinatra app, causing 'undefined method' errors when accessing OAuth metadata discovery endpoints. Changes: - Add app.helpers Helpers in the registered method to make helper methods available in route blocks
Updated MCP and OAuth endpoints to use consistent app.helpers pattern for registering helper methods, and fixed test mock app classes to support all required methods (helpers, post). Changes: - Refactor mcp_endpoints.rb to use app.helpers Helpers pattern - Add helpers() and post() methods to mock app classes in tests - Fix tools/call dispatch test to use valid params with registered tool
Configuration file for the OAuth-protected MCP server example with test environment settings for Keycloak integration.
Updated RuboCop config to disable unused cops (Capybara, FactoryBot), relax strict RSpec file path requirements, and add exclusions for example/spec directories. Ran rubocop -a to auto-fix style issues. Changes: - Disable Capybara and FactoryBot cops (not used) - Disable RSpec/FilePath and RSpec/SpecFilePathFormat cops - Add exclusions for examples/** and spec/** for various cops - Increase RSpec example length (25) and expectations (12) limits - Auto-format all Ruby files with rubocop -a
RuboCop incorrectly applied Rails-specific transformations to the test files which broke them: - Reverted have_http_status() back to last_response.status (Rails matcher) - Reverted described_class back to explicit class name in Sinatra.new blocks - Disabled RSpec/Rails/HaveHttpStatus and RSpec/DescribedClass cops
The cop was renamed from RSpec/Rails/HaveHttpStatus to RSpecRails/HaveHttpStatus in newer rubocop-rspec versions.
added 2 commits
March 20, 2026 02:26
The system Ruby (2.6) doesn't have bundler 4.0.4 required by Gemfile.lock. This wrapper script ensures Homebrew Ruby is used. Usage: ./bin/rubocop -a
- Disable RSpecRails cops (not using Rails) - Disable Capybara/RSpec/PredicateMatcher (buggy with Ruby 4.0) - Disable RSpec/DescribedClass to prevent incorrect replacements - Exclude line length checks from spec/examples/gemspec - Relax metrics limits for this project
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR must be merged after #1