Skip to content

Commit dc09f08

Browse files
committed
Phase 5: Implement Authentication & Security
- Add OAuth 2.1 authentication for secure connections - Implement JWT token validation and management - Create permission-based method access control - Add automatic token refresh capabilities - Update HTTP transport with authentication integration - Create HTTP server with authentication middleware - Add examples for authenticated MCP servers and clients - Update documentation to reflect authentication features - Bump version to 0.2.0 for authentication release
1 parent cb5d028 commit dc09f08

File tree

19 files changed

+1603
-18
lines changed

19 files changed

+1603
-18
lines changed

CHANGELOG.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Server implementation with tools, resources, prompts, and roots
1414
- Client implementation for connecting to MCP servers
1515
- HTTP and STDIO transport options
16-
- OAuth 2.1 authentication for remote connections
1716
- Comprehensive test suite
1817
- Detailed documentation
1918

20-
## [0.2.0] - 2024-xx-xx
19+
## [0.2.0] - 2024-07-24
2120

2221
### Added
23-
- Redis storage backend for conversation persistence
24-
- Improved error handling and reporting
22+
- OAuth 2.1 authentication for secure remote connections
23+
- Permission-based access control for MCP methods
24+
- JWT token validation and management
25+
- Automatic token refresh mechanism
26+
- HTTP transport authentication integration
27+
- Middleware architecture for server authentication
28+
- Authentication examples and documentation
2529

26-
## [0.1.0] - 2024-xx-xx
30+
## [0.1.0] - 2024-06-15
2731

2832
### Added
29-
- Initial release with basic REST API functionality
33+
- Initial implementation of core MCP protocol
34+
- JSON-RPC 2.0 message format
35+
- HTTP and STDIO transports
36+
- Basic server and client functionality
37+
- Tool definition and execution
38+
- Resource management
39+
- Prompt handling
40+
- Root filesystem access

README.md

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,44 @@ end
7272
server.start
7373
```
7474

75+
### Authenticated Server
76+
77+
Create a server with OAuth 2.1 authentication:
78+
79+
```ruby
80+
require 'mcp_on_ruby'
81+
82+
# Create the OAuth provider
83+
oauth_provider = MCP::Server::Auth::OAuth.new(
84+
client_id: 'your-client-id',
85+
client_secret: 'your-client-secret',
86+
token_expiry: 3600,
87+
jwt_secret: 'your-jwt-secret',
88+
issuer: 'your-server'
89+
)
90+
91+
# Create the permissions manager
92+
permissions = MCP::Server::Auth::Permissions.new
93+
permissions.add_method('tools/list', ['tools:read'])
94+
permissions.add_method('tools/call', ['tools:call'])
95+
96+
# Create server with authentication
97+
server = MCP::Server.new
98+
server.set_auth_provider(oauth_provider, permissions)
99+
100+
# Define tools
101+
server.tools.define('example') do
102+
parameter :name, :string
103+
104+
execute do |params|
105+
"Hello, #{params[:name]}!"
106+
end
107+
end
108+
109+
# Start the server
110+
server.start
111+
```
112+
75113
## Quick Start: Client
76114

77115
Connect to an MCP server and use its tools:
@@ -86,21 +124,53 @@ client = MCP::Client.new(url: "http://localhost:3000")
86124
client.connect
87125

88126
# List available tools
89-
tools = client.list_tools
127+
tools = client.tools.list
90128
puts tools
91129

92130
# Call a tool
93-
result = client.call_tool("weather.get_forecast", location: "San Francisco")
131+
result = client.tools.call("weather.get_forecast", { location: "San Francisco" })
94132
puts result.inspect
95133

96134
# Get a resource
97-
profile = client.get_resource("user.profile")
135+
profile = client.resources.get("user.profile")
98136
puts profile.inspect
99137

100138
# Disconnect
101139
client.disconnect
102140
```
103141

142+
### Authenticated Client
143+
144+
Connect to an authenticated MCP server:
145+
146+
```ruby
147+
require 'mcp_on_ruby'
148+
149+
# Create the client
150+
client = MCP::Client.new(url: "http://localhost:3000/mcp")
151+
152+
# Set up OAuth credentials
153+
client.set_oauth_credentials(
154+
client_id: 'your-client-id',
155+
client_secret: 'your-client-secret',
156+
site: 'http://localhost:3000',
157+
authorize_url: '/oauth/authorize',
158+
token_url: '/oauth/token',
159+
scopes: ['tools:read', 'tools:call'],
160+
auto_refresh: true
161+
)
162+
163+
# Exchange authorization code for token (after user authorization)
164+
token = client.exchange_code('authorization_code')
165+
166+
# Or set token directly
167+
client.set_access_token('your-access-token')
168+
169+
# Connect and use tools with authentication
170+
client.connect
171+
results = client.tools.call('example', { name: 'World' })
172+
```
173+
104174
## MCP Implementation
105175

106176
This library is an implementation of the Model Context Protocol specification:
@@ -132,20 +202,47 @@ This library is an implementation of the Model Context Protocol specification:
132202
MCP on Ruby follows the MCP specification's architecture:
133203

134204
1. **Protocol Layer**: JSON-RPC 2.0 communication over multiple transports
135-
2. **Server**: Exposes tools, resources, and prompts to clients
205+
2. **Server**: Exposes tools, resources, prompts, and roots to clients
136206
3. **Client**: Connects to servers and uses their capabilities
137207
4. **Authentication**: OAuth 2.1 for secure remote connections
138208

209+
## Security Features
210+
211+
### OAuth 2.1 Authentication
212+
213+
MCP on Ruby supports secure authentication with OAuth 2.1:
214+
215+
- **Token-based Authentication**: Industry-standard OAuth 2.1 flow
216+
- **JWT Implementation**: Secure token validation and management
217+
- **Automatic Token Refresh**: Seamless handling of token expiration
218+
- **Scope-based Authorization**: Fine-grained access control for MCP methods
219+
220+
### Permission Management
221+
222+
- **Method-level Permissions**: Control access to individual MCP methods
223+
- **Scope Requirements**: Define required scopes for each method
224+
- **Integration with OAuth**: Leverage OAuth scopes for authorization decisions
225+
- **Middleware Architecture**: Apply permissions consistently across all requests
226+
139227
## Advanced Usage
140228

141229
See the [wiki](https://github.com/nagstler/mcp_on_ruby/wiki) for advanced usage, including:
142230

143231
- Creating complex tool hierarchies
144-
- Implementing custom authentication
232+
- Implementing custom OAuth providers
233+
- Configuring method-level permissions
234+
- Token management and refresh strategies
145235
- Streaming responses
146236
- Working with resources and prompts
147237
- File system integration with roots
148238

239+
Check out the `/examples` directory for complete working examples:
240+
241+
- **Simple Server** - Basic MCP server implementation
242+
- **Authentication** - OAuth 2.1 integration example
243+
- **Rails Integration** - Using MCP with Rails
244+
- **Streaming** - Real-time bidirectional communication
245+
149246
## Development
150247

151248
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

examples/auth_server/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Authenticated MCP Server Example
2+
3+
This example demonstrates how to create an MCP server with OAuth 2.1 authentication and permission scopes.
4+
5+
## Components
6+
7+
The example consists of three main components:
8+
9+
1. **OAuth Server** - A simple OAuth 2.1 server implementation for testing purposes
10+
2. **MCP Server** - An MCP server with authentication enabled
11+
3. **MCP Client** - A client that connects to the MCP server with authentication
12+
13+
## Setting Up
14+
15+
You'll need to run each component in a separate terminal.
16+
17+
### 1. Start the OAuth Server
18+
19+
```bash
20+
ruby oauth_server.rb
21+
```
22+
23+
This will start a simple OAuth server on port 3001.
24+
25+
### 2. Start the MCP Server
26+
27+
```bash
28+
ruby server.rb
29+
```
30+
31+
This will start an MCP server on port 3000. The server is configured with OAuth authentication and permission scopes.
32+
33+
### 3. Run the Client
34+
35+
```bash
36+
ruby client.rb
37+
```
38+
39+
The client will:
40+
1. Connect to the MCP server with authentication
41+
2. List available tools
42+
3. Call the "hello" tool
43+
44+
## Understanding the Authentication Flow
45+
46+
In a real application, the OAuth flow would typically involve:
47+
48+
1. The client redirecting the user to the authorization server
49+
2. The user granting permission
50+
3. The authorization server redirecting back to the client with an authorization code
51+
4. The client exchanging the code for an access token
52+
5. The client using the access token to access the API
53+
54+
For simplicity, this example bypasses this flow and creates a token directly.
55+
56+
## Permission Scopes
57+
58+
The server is configured with the following permission scopes:
59+
60+
- `tools:read` - Required to list tools
61+
- `tools:call` - Required to call tools
62+
- `resources:read` - Required to list resources
63+
- `resources:call` - Required to call resources
64+
65+
The client requests the `tools:read` and `tools:call` scopes, which allows it to list and call tools, but not work with resources.
66+
67+
## How Authentication is Integrated
68+
69+
1. **HTTP Transport** - Adds Authentication headers to requests
70+
2. **Server Middleware** - Validates tokens and checks permissions
71+
3. **Client Authentication** - Manages token lifecycle (acquisition, refresh, etc.)
72+
4. **Permission Manager** - Maps MCP methods to required scopes
73+
74+
This architecture follows the principles of OAuth 2.1 and integrates with the MCP protocol's JSON-RPC 2.0 message format.

examples/auth_server/client.rb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require 'ruby_mcp'
5+
require 'logger'
6+
7+
# Create a logger
8+
logger = Logger.new(STDOUT)
9+
logger.level = Logger::INFO
10+
11+
# Create the client
12+
client = MCP::Client::Client.new(
13+
url: 'http://localhost:3000/mcp',
14+
logger: logger
15+
)
16+
17+
# Set up OAuth credentials for the client authorization code flow
18+
client.set_oauth_credentials(
19+
client_id: 'example-client',
20+
client_secret: 'example-secret',
21+
site: 'http://localhost:3000',
22+
authorize_url: '/oauth/authorize',
23+
token_url: '/oauth/token',
24+
redirect_uri: 'http://localhost:8000/callback',
25+
scopes: ['tools:read', 'tools:call'],
26+
auto_refresh: true
27+
)
28+
29+
# Generate an auth URL
30+
state = SecureRandom.hex(8)
31+
auth_url = client.authorization_url(state)
32+
puts "Visit this URL to authorize the client: #{auth_url}"
33+
34+
# For this example, we'll bypass the authorization flow and directly set a token
35+
# In a real application, you would need to implement the full OAuth flow
36+
puts "Since this is an example, we'll create a token directly:"
37+
38+
# Create a sample token (this would normally come from the OAuth authorization flow)
39+
token = OAuth2::AccessToken.new(
40+
OAuth2::Client.new('example-client', 'example-secret'),
41+
'example_access_token',
42+
refresh_token: 'example_refresh_token',
43+
expires_at: Time.now.to_i + 3600,
44+
params: { 'scope' => 'tools:read tools:call' }
45+
)
46+
47+
# Set the access token directly
48+
client.set_access_token(token)
49+
puts "Token set: #{token.token}"
50+
51+
# Connect to the server
52+
begin
53+
client.connect
54+
puts "Connected to server: #{client.server_info[:name]}"
55+
56+
# List available tools
57+
tools = client.tools.list
58+
puts "Available tools: #{tools.map { |t| t[:name] }.join(', ')}"
59+
60+
# Call the hello tool
61+
result = client.tools.call('hello', { name: 'Ruby MCP Client' })
62+
puts "Result: #{result}"
63+
rescue => e
64+
puts "Error: #{e.message}"
65+
end

0 commit comments

Comments
 (0)