ruby-mcp-client 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27b97462ec0c99d98299df120726f90944f495aaddf198272d72725544240ec0
4
- data.tar.gz: b58b8a6ba53f698abe1cb3576fba8030d66cb0bd7eafa2e39e28943e7ef1337f
3
+ metadata.gz: a5e35c00292938e3c6cd17885d6578e465d12853ed1844f67dc4633096bb2fa3
4
+ data.tar.gz: e9419a3969689a0768ea8290f2a45f5214fcc4b74521ac38aac3bfd842b7ec49
5
5
  SHA512:
6
- metadata.gz: 50d5791642a74f521133a057d924d3daf14e657ec617e73ad8f95510b4d97f3d52742c5a467c54d75dc02030a087f29e63ee346429271e8c80eb71cb96e38f94
7
- data.tar.gz: 5f92d87a5d176e3271a11e9bbea7eafc6720107c6ae795601250e088cde69e6d6340911eecd6ea8ab32776d97d9bfb0b4d3740087509eb704cb400bf4e23e5aa
6
+ metadata.gz: 063bc6c91cf5d6e82a05b17dd90519e07d3168baf5efb36969e98d0893d40f4d630db3181dfd28929fc255f579780c041b407dfd8bfec17f3461022f2cb84471
7
+ data.tar.gz: e92a6c8d29474c08966008f9e2e5f530520258f546b40694fc0245eaf3da2ad6064ce7bb312596a07d5e4a94f4516603256c2bd7375cfc67065180f636f64eb1
data/README.md CHANGED
@@ -40,17 +40,22 @@ with popular AI services with built-in conversions:
40
40
  - `to_anthropic_tools()` - Formats tools for Anthropic Claude API
41
41
  - `to_google_tools()` - Formats tools for Google Vertex AI API (automatically removes "$schema" keys not accepted by Vertex AI)
42
42
 
43
- ## MCP 2025-03-26 Protocol Features
43
+ ## MCP Protocol Support
44
44
 
45
- This Ruby MCP Client implements key features from the latest MCP specification (Protocol Revision: 2025-03-26):
45
+ This Ruby MCP Client implements the **MCP 2025-06-18** specification with full backward compatibility.
46
46
 
47
- ### Implemented Features
47
+ ### Key Features
48
+
49
+ **MCP 2025-06-18 (Latest):**
50
+ - **Structured Tool Outputs** - Tools can declare output schemas and return type-safe, validated structured data
51
+ - **Elicitation (Server-initiated User Interactions)** - Servers can request user input during tool execution via bidirectional JSON-RPC (stdio, SSE, and Streamable HTTP transports)
52
+ - **Tool Annotations** - Support for tool behavior annotations (readOnly, destructive, requiresConfirmation) for safer tool execution
48
53
  - **OAuth 2.1 Authorization Framework** - Complete authentication with PKCE, dynamic client registration, server discovery, and runtime configuration
49
54
  - **Streamable HTTP Transport** - Enhanced transport with Server-Sent Event formatted responses and session management
50
55
  - **HTTP Redirect Support** - Automatic redirect handling for both SSE and HTTP transports with configurable limits
51
56
  - **FastMCP Compatibility** - Full compatibility with FastMCP servers including proper line ending handling
52
57
  - **Prompts Support** - Full implementation of MCP prompts for dynamic content generation
53
- - **Resources Support** - Complete resources implementation for accessing files and data with URI-based identification
58
+ - **Resources Support** - Full MCP resources specification compliance including templates, subscriptions, pagination, and annotations
54
59
 
55
60
  ## Usage
56
61
 
@@ -165,11 +170,30 @@ result = client.get_prompt('greeting', { name: 'Ruby Developer' })
165
170
  result = client.get_prompt('greeting', { name: 'Ruby Developer' }, server: 'filesystem')
166
171
 
167
172
  # === Working with Resources ===
168
- # List available resources from all servers
169
- resources = client.list_resources
173
+ # List available resources from all servers (returns hash with 'resources' array and optional 'nextCursor')
174
+ result = client.list_resources
175
+ resources = result['resources'] # Array of Resource objects from all servers
176
+ next_cursor = result['nextCursor'] # nil when aggregating multiple servers
177
+
178
+ # Get resources from a specific server with pagination support
179
+ result = client.servers.first.list_resources
180
+ resources = result['resources'] # Array of Resource objects
181
+ next_cursor = result['nextCursor'] # For pagination
170
182
 
171
- # Read a specific resource by URI
183
+ # List resources with pagination (only works with single server or client.list_resources with cursor)
184
+ result = client.list_resources(cursor: next_cursor) # Uses first server when cursor provided
185
+
186
+ # Read a specific resource by URI (returns array of ResourceContent objects)
172
187
  contents = client.read_resource('file:///example.txt')
188
+ contents.each do |content|
189
+ if content.text?
190
+ puts content.text # Text content
191
+ elsif content.binary?
192
+ data = Base64.decode64(content.blob) # Binary content
193
+ end
194
+ puts content.mime_type if content.mime_type
195
+ puts content.annotations if content.annotations # Optional metadata
196
+ end
173
197
 
174
198
  # Read a resource from a specific server
175
199
  contents = client.read_resource('file:///example.txt', server: 'filesystem')
@@ -413,11 +437,16 @@ prompts.each { |prompt| puts "- #{prompt.name}: #{prompt.description}" }
413
437
  greeting = client.get_prompt('greeting', { name: 'Ruby Developer' })
414
438
 
415
439
  # List and read resources
416
- resources = client.list_resources
440
+ result = client.list_resources
441
+ resources = result['resources']
417
442
  puts "Found #{resources.length} resources:"
418
443
  resources.each { |resource| puts "- #{resource.name} (#{resource.uri})" }
419
444
 
420
- readme_content = client.read_resource('file:///sample/README.md')
445
+ # Read resource (returns array of ResourceContent objects)
446
+ contents = client.read_resource('file:///sample/README.md')
447
+ contents.each do |content|
448
+ puts content.text if content.text?
449
+ end
421
450
  ```
422
451
 
423
452
  #### Streamable HTTP Example
@@ -557,14 +586,22 @@ client = Anthropic::Client.new(access_token: ENV['ANTHROPIC_API_KEY'])
557
586
  ```
558
587
 
559
588
  Complete examples can be found in the `examples/` directory:
589
+
590
+ **AI Integration Examples:**
560
591
  - `ruby_openai_mcp.rb` - Integration with alexrudall/ruby-openai gem
561
592
  - `openai_ruby_mcp.rb` - Integration with official openai/openai-ruby gem
562
593
  - `ruby_anthropic_mcp.rb` - Integration with alexrudall/ruby-anthropic gem
563
594
  - `gemini_ai_mcp.rb` - Integration with Google Vertex AI and Gemini models
595
+
596
+ **Transport Examples:**
564
597
  - `streamable_http_example.rb` - Streamable HTTP transport with Playwright MCP
565
598
  - `echo_server.py` & `echo_server_client.rb` - FastMCP server example with full setup
566
599
  - `echo_server_streamable.py` & `echo_server_streamable_client.rb` - Enhanced streamable HTTP server example
567
600
 
601
+ **MCP 2025 Protocol Features:**
602
+ - `structured_output_server.py` & `test_structured_outputs.rb` - Structured tool outputs (MCP 2025-06-18)
603
+ - `echo_server_with_annotations.py` & `test_tool_annotations.rb` - Tool annotations (MCP 2025-03-26)
604
+
568
605
  ## MCP Server Compatibility
569
606
 
570
607
  This client works with any MCP-compatible server, including:
@@ -753,6 +790,83 @@ auth_url = oauth_provider.start_authorization_flow
753
790
  token = oauth_provider.complete_authorization_flow(code, state)
754
791
  ```
755
792
 
793
+ ### Browser-Based OAuth Authentication
794
+
795
+ For the easiest authentication experience, use the browser-based OAuth flow that automatically handles the entire process:
796
+
797
+ ```ruby
798
+ require 'mcp_client/auth/browser_oauth'
799
+
800
+ # Create OAuth provider
801
+ oauth_provider = MCPClient::Auth::OAuthProvider.new(
802
+ server_url: 'https://api.example.com/mcp',
803
+ redirect_uri: 'http://localhost:8080/callback',
804
+ scope: 'read:tools write:tools' # Optional
805
+ )
806
+
807
+ # Create browser OAuth helper
808
+ browser_oauth = MCPClient::Auth::BrowserOAuth.new(
809
+ oauth_provider,
810
+ callback_port: 8080, # Optional: Port for local server (default: 8080)
811
+ callback_path: '/callback' # Optional: Callback path (default: '/callback')
812
+ )
813
+
814
+ # Authenticate (opens browser automatically and handles callback)
815
+ token = browser_oauth.authenticate(
816
+ timeout: 300, # Optional: 5 minutes (default: 300)
817
+ auto_open_browser: true # Optional: Auto-open browser (default: true)
818
+ )
819
+
820
+ # Create authenticated client (use streamable_http for modern MCP servers)
821
+ server_config = {
822
+ type: 'streamable_http', # or 'http' for simple HTTP-only servers
823
+ base_url: 'https://api.example.com/mcp',
824
+ oauth_provider: oauth_provider
825
+ }
826
+
827
+ client = MCPClient::Client.new(mcp_server_configs: [server_config])
828
+ ```
829
+
830
+ **How it works:**
831
+ 1. **Automatically starts a local HTTP server** (using pure Ruby `TCPServer`) to handle the OAuth callback
832
+ 2. **Opens your default browser** to the authorization page (macOS: `open`, Linux: `xdg-open`, Windows: `start`)
833
+ 3. **Captures the authorization code** automatically from the callback
834
+ 4. **Completes the OAuth flow** and stores the access token
835
+ 5. **Handles token refresh** automatically when tokens expire
836
+
837
+ **Features:**
838
+ - **Zero external dependencies** - Uses pure Ruby stdlib (`TCPServer`)
839
+ - **User-friendly HTML pages** - Shows success/error pages in the browser
840
+ - **Automatic token management** - Handles token refresh transparently
841
+ - **Configurable timeout** - Raises `Timeout::Error` if user doesn't authorize in time
842
+ - **Token storage** - Supports custom storage backends for persistent tokens
843
+ - **Port conflict detection** - Clear error messages if port is already in use
844
+ - **Security hardened** - PKCE, CSRF protection, request validation, header limits
845
+
846
+ **Error Handling:**
847
+ ```ruby
848
+ begin
849
+ token = browser_oauth.authenticate
850
+ rescue Timeout::Error
851
+ puts "User took too long to authorize"
852
+ rescue MCPClient::Errors::ConnectionError => e
853
+ puts "OAuth flow failed: #{e.message}"
854
+ # Port already in use, server doesn't support OAuth, etc.
855
+ rescue ArgumentError => e
856
+ puts "Invalid state parameter (CSRF protection)"
857
+ end
858
+ ```
859
+
860
+ **Server Requirements:**
861
+ - OAuth 2.1 Protocol with PKCE
862
+ - Authorization Server Discovery via:
863
+ - `/.well-known/oauth-authorization-server` (primary, for self-contained servers)
864
+ - `/.well-known/oauth-protected-resource` (fallback, for delegated auth)
865
+ - Dynamic Client Registration (recommended)
866
+ - Authorization Code Grant Flow
867
+
868
+ For a complete working example, see [`examples/oauth_browser_auth.rb`](examples/oauth_browser_auth.rb).
869
+
756
870
  ### OAuth Features
757
871
 
758
872
  - **OAuth 2.1 compliance** with PKCE for security
@@ -761,9 +875,378 @@ token = oauth_provider.complete_authorization_flow(code, state)
761
875
  - **Token refresh** and automatic token management
762
876
  - **Pluggable storage** for tokens and client credentials
763
877
  - **Runtime configuration** via getter/setter methods
878
+ - **Browser-based authentication** with automatic callback handling
764
879
 
765
880
  For complete OAuth documentation, see [OAUTH.md](OAUTH.md).
766
881
 
882
+ ## Resources
883
+
884
+ The Ruby MCP Client provides full support for the MCP resources specification, enabling access to files, data, and other content with URI-based identification.
885
+
886
+ ### Resource Features
887
+
888
+ - **Resource listing** with cursor-based pagination
889
+ - **Resource templates** with URI patterns (RFC 6570)
890
+ - **Resource subscriptions** for real-time updates
891
+ - **Binary content support** with base64 encoding
892
+ - **Resource annotations** for metadata (audience, priority, lastModified)
893
+ - **ResourceContent objects** for structured content access
894
+
895
+ ### Resource API
896
+
897
+ ```ruby
898
+ # Get a server instance
899
+ server = client.servers.first # or client.find_server('name')
900
+
901
+ # List resources with pagination
902
+ result = server.list_resources
903
+ resources = result['resources'] # Array of Resource objects
904
+ next_cursor = result['nextCursor'] # String cursor for next page (if any)
905
+
906
+ # Get next page of resources
907
+ if next_cursor
908
+ next_result = server.list_resources(cursor: next_cursor)
909
+ end
910
+
911
+ # Access Resource properties
912
+ resource = resources.first
913
+ resource.uri # "file:///example.txt"
914
+ resource.name # "example.txt"
915
+ resource.title # Optional human-readable title
916
+ resource.description # Optional description
917
+ resource.mime_type # "text/plain"
918
+ resource.size # Optional file size in bytes
919
+ resource.annotations # Optional metadata hash
920
+
921
+ # Read resource contents (returns array of ResourceContent objects)
922
+ contents = server.read_resource(resource.uri)
923
+
924
+ contents.each do |content|
925
+ # Check content type
926
+ if content.text?
927
+ # Text content
928
+ text = content.text
929
+ mime = content.mime_type # e.g., "text/plain"
930
+ elsif content.binary?
931
+ # Binary content (base64 encoded)
932
+ blob = content.blob # Base64 string
933
+ data = Base64.decode64(blob) # Decoded binary data
934
+ end
935
+
936
+ # Access optional annotations
937
+ if content.annotations
938
+ audience = content.annotations['audience'] # e.g., ["user", "assistant"]
939
+ priority = content.annotations['priority'] # e.g., 0.5
940
+ modified = content.annotations['lastModified'] # ISO 8601 timestamp
941
+ end
942
+ end
943
+
944
+ # List resource templates
945
+ templates_result = server.list_resource_templates
946
+ templates = templates_result['resourceTemplates'] # Array of ResourceTemplate objects
947
+
948
+ template = templates.first
949
+ template.uri_template # "file:///{path}" (RFC 6570 URI template)
950
+ template.name # Template name
951
+ template.title # Optional title
952
+ template.description # Optional description
953
+ template.mime_type # Optional MIME type hint
954
+
955
+ # Subscribe to resource updates
956
+ server.subscribe_resource('file:///watched.txt') # Returns true on success
957
+
958
+ # Unsubscribe from resource updates
959
+ server.unsubscribe_resource('file:///watched.txt') # Returns true on success
960
+
961
+ # Check server capabilities for resources
962
+ capabilities = server.capabilities
963
+ if capabilities['resources']
964
+ can_subscribe = capabilities['resources']['subscribe'] # true/false
965
+ list_changed = capabilities['resources']['listChanged'] # true/false
966
+ end
967
+ ```
968
+
969
+ ### Working with ResourceContent
970
+
971
+ The `ResourceContent` class provides a structured way to handle both text and binary content:
972
+
973
+ ```ruby
974
+ # ResourceContent for text
975
+ content = MCPClient::ResourceContent.new(
976
+ uri: 'file:///example.txt',
977
+ name: 'example.txt',
978
+ mime_type: 'text/plain',
979
+ text: 'File contents here',
980
+ annotations: {
981
+ 'audience' => ['user'],
982
+ 'priority' => 1.0
983
+ }
984
+ )
985
+
986
+ # ResourceContent for binary data
987
+ binary_content = MCPClient::ResourceContent.new(
988
+ uri: 'file:///image.png',
989
+ name: 'image.png',
990
+ mime_type: 'image/png',
991
+ blob: Base64.strict_encode64(binary_data)
992
+ )
993
+
994
+ # Access content
995
+ if content.text?
996
+ puts content.text
997
+ elsif content.binary?
998
+ data = Base64.decode64(content.blob)
999
+ end
1000
+ ```
1001
+
1002
+ ## Tool Annotations
1003
+
1004
+ MCP 2025-06-18 supports tool annotations that describe tool behavior, enabling safer and more informed tool execution. The Ruby MCP Client provides full support for reading and interpreting these annotations.
1005
+
1006
+ ### Annotation Types
1007
+
1008
+ Tools can include the following annotations:
1009
+
1010
+ - **`readOnly`** - Indicates the tool only reads data and doesn't modify state
1011
+ - **`destructive`** - Indicates the tool performs potentially dangerous operations (e.g., deleting data)
1012
+ - **`requiresConfirmation`** - Indicates the tool should require explicit user confirmation before execution
1013
+
1014
+ ### Using Tool Annotations
1015
+
1016
+ ```ruby
1017
+ # List tools and check their annotations
1018
+ tools = client.list_tools
1019
+
1020
+ tools.each do |tool|
1021
+ puts "Tool: #{tool.name}"
1022
+
1023
+ # Check annotations using helper methods
1024
+ if tool.read_only?
1025
+ puts " → Safe to execute (read-only)"
1026
+ end
1027
+
1028
+ if tool.destructive?
1029
+ puts " ⚠️ Warning: This tool is destructive"
1030
+ end
1031
+
1032
+ if tool.requires_confirmation?
1033
+ puts " 🛡️ Requires user confirmation before execution"
1034
+ end
1035
+
1036
+ # Access raw annotations hash
1037
+ if tool.annotations
1038
+ puts " Annotations: #{tool.annotations.inspect}"
1039
+ end
1040
+ end
1041
+
1042
+ # Make informed decisions based on annotations
1043
+ tool = client.find_tool('delete_user')
1044
+ if tool.destructive? && tool.requires_confirmation?
1045
+ # Prompt user for confirmation before executing
1046
+ puts "This will delete user data. Are you sure? (y/n)"
1047
+ response = gets.chomp
1048
+ if response.downcase == 'y'
1049
+ result = client.call_tool('delete_user', { user_id: 123 })
1050
+ else
1051
+ puts "Operation cancelled"
1052
+ end
1053
+ else
1054
+ # Safe to execute without confirmation
1055
+ result = client.call_tool('get_user', { user_id: 123 })
1056
+ end
1057
+ ```
1058
+
1059
+ ### Tool Annotation Helper Methods
1060
+
1061
+ The `Tool` class provides convenient helper methods:
1062
+
1063
+ - `tool.read_only?` - Returns true if the tool is marked as read-only
1064
+ - `tool.destructive?` - Returns true if the tool is marked as destructive
1065
+ - `tool.requires_confirmation?` - Returns true if the tool requires confirmation
1066
+ - `tool.annotations` - Returns the raw annotations hash
1067
+
1068
+ ### Example
1069
+
1070
+ See `examples/test_tool_annotations.rb` for a complete working example demonstrating:
1071
+ - Listing tools with their annotations
1072
+ - Using annotation helper methods
1073
+ - Making annotation-aware decisions about tool execution
1074
+ - Handling destructive operations safely
1075
+
1076
+ ## Structured Tool Outputs
1077
+
1078
+ MCP 2025-06-18 introduces structured tool outputs, allowing tools to declare output schemas and return type-safe, validated data structures instead of just text.
1079
+
1080
+ ### Overview
1081
+
1082
+ Tools can now specify an `outputSchema` (JSON Schema) that defines the expected structure of their results. When called, they return data in a `structuredContent` field that validates against this schema, providing:
1083
+
1084
+ - **Type safety** - Predictable, validated data structures
1085
+ - **Better IDE support** - Code completion and type checking
1086
+ - **Easier parsing** - No need to parse text or guess formats
1087
+ - **Backward compatibility** - Text content is still provided alongside structured data
1088
+
1089
+ ### Using Structured Outputs
1090
+
1091
+ ```ruby
1092
+ # List tools and check for structured output support
1093
+ tools = client.list_tools
1094
+
1095
+ tools.each do |tool|
1096
+ if tool.structured_output?
1097
+ puts "#{tool.name} supports structured output"
1098
+ puts "Output schema: #{tool.output_schema}"
1099
+ end
1100
+ end
1101
+
1102
+ # Call a tool that returns structured output
1103
+ result = client.call_tool('get_weather', { location: 'San Francisco' })
1104
+
1105
+ # Access structured data (type-safe, validated)
1106
+ if result['structuredContent']
1107
+ data = result['structuredContent']
1108
+ puts "Temperature: #{data['temperature']}°C"
1109
+ puts "Conditions: #{data['conditions']}"
1110
+ puts "Humidity: #{data['humidity']}%"
1111
+ end
1112
+
1113
+ # Backward compatibility: text content is still available
1114
+ text_content = result['content']&.first&.dig('text')
1115
+ parsed = JSON.parse(text_content) if text_content
1116
+ ```
1117
+
1118
+ ### Helper Methods
1119
+
1120
+ The `Tool` class provides a convenient helper method:
1121
+
1122
+ - `tool.structured_output?` - Returns true if the tool has an output schema defined
1123
+ - `tool.output_schema` - Returns the JSON Schema for the tool's output
1124
+
1125
+ ### Example
1126
+
1127
+ See `examples/test_structured_outputs.rb` for a complete working example demonstrating:
1128
+ - Detecting tools with structured output support
1129
+ - Accessing output schemas
1130
+ - Calling tools and receiving structured data
1131
+ - Backward compatibility with text content
1132
+
1133
+ The example includes a Python MCP server (`examples/structured_output_server.py`) that provides three tools with structured outputs:
1134
+ - `get_weather` - Weather data with temperature, conditions, humidity
1135
+ - `analyze_text` - Text analysis with word count, character count, statistics
1136
+ - `calculate_stats` - Statistical calculations (mean, median, min, max, etc.)
1137
+
1138
+ ## Elicitation (Server-initiated User Interactions) - MCP 2025-06-18
1139
+
1140
+ Elicitation enables MCP servers to request user input during tool execution via bidirectional JSON-RPC.
1141
+ This allows servers to gather additional information, confirm sensitive operations, or interact with users
1142
+ dynamically during the execution of tools.
1143
+
1144
+ ### Transport Support
1145
+
1146
+ **Fully Supported:**
1147
+ - ✅ **stdio** - Full bidirectional JSON-RPC over stdin/stdout
1148
+ - ✅ **SSE** - Server sends requests via SSE stream, client responds via HTTP POST
1149
+ - ✅ **Streamable HTTP** - Server sends requests via SSE-formatted responses, client responds via HTTP POST
1150
+
1151
+ **Not Supported:**
1152
+ - ❌ **HTTP** - Pure request-response architecture prevents server-initiated requests
1153
+
1154
+ ### Using Elicitation
1155
+
1156
+ To use elicitation, register a handler when creating the client:
1157
+
1158
+ ```ruby
1159
+ # Define an elicitation handler
1160
+ elicitation_handler = lambda do |message, requested_schema|
1161
+ puts "Server requests: #{message}"
1162
+
1163
+ # Show expected input format from schema
1164
+ if requested_schema && requested_schema['properties']
1165
+ requested_schema['properties'].each do |field, schema|
1166
+ puts " #{field}: #{schema['type']}"
1167
+ end
1168
+ end
1169
+
1170
+ # Prompt user and return one of three response types:
1171
+
1172
+ # 1. Accept and provide data to the server
1173
+ {
1174
+ 'action' => 'accept',
1175
+ 'content' => {
1176
+ 'field_name' => 'user_input_value'
1177
+ }
1178
+ }
1179
+
1180
+ # 2. Decline to provide input
1181
+ # { 'action' => 'decline' }
1182
+
1183
+ # 3. Cancel the operation
1184
+ # { 'action' => 'cancel' }
1185
+ end
1186
+
1187
+ # Create client with elicitation handler
1188
+ # Works with stdio, SSE, and Streamable HTTP transports
1189
+ client = MCPClient::Client.new(
1190
+ mcp_server_configs: [
1191
+ # Stdio transport
1192
+ MCPClient.stdio_config(
1193
+ command: 'python my_server.py',
1194
+ name: 'my-server'
1195
+ ),
1196
+ # Or SSE transport
1197
+ MCPClient.sse_config(
1198
+ base_url: 'https://api.example.com/sse',
1199
+ name: 'remote-server'
1200
+ ),
1201
+ # Or Streamable HTTP transport
1202
+ MCPClient.streamable_http_config(
1203
+ base_url: 'https://api.example.com',
1204
+ endpoint: '/mcp',
1205
+ name: 'streamable-server'
1206
+ )
1207
+ ],
1208
+ elicitation_handler: elicitation_handler
1209
+ )
1210
+
1211
+ # Call tools - connection happens automatically
1212
+ # Server may send elicitation requests during tool execution
1213
+ result = client.call_tool('create_document', { format: 'markdown' }, server: 'my-server')
1214
+ ```
1215
+
1216
+ ### Response Actions
1217
+
1218
+ The elicitation handler should return a hash with an `action` field:
1219
+
1220
+ 1. **Accept** - Provide the requested input:
1221
+ ```ruby
1222
+ {
1223
+ 'action' => 'accept',
1224
+ 'content' => { 'field' => 'value' } # Must match requested schema
1225
+ }
1226
+ ```
1227
+
1228
+ 2. **Decline** - Refuse to provide input (server should handle gracefully):
1229
+ ```ruby
1230
+ { 'action' => 'decline' }
1231
+ ```
1232
+
1233
+ 3. **Cancel** - Cancel the entire operation:
1234
+ ```ruby
1235
+ { 'action' => 'cancel' }
1236
+ ```
1237
+
1238
+ ### Example
1239
+
1240
+ See `examples/elicitation/test_elicitation.rb` for a complete working example demonstrating:
1241
+ - Registering an elicitation handler
1242
+ - Handling different message types and schemas
1243
+ - Responding with accept/decline/cancel actions
1244
+ - Interactive user prompts during tool execution
1245
+
1246
+ The example includes a Python MCP server (`examples/elicitation/elicitation_server.py`) that provides tools using elicitation:
1247
+ - `create_document` - Requests title and content via elicitation
1248
+ - `send_notification` - Confirms before sending a notification
1249
+
767
1250
  ## Key Features
768
1251
 
769
1252
  ### Client Features
@@ -774,6 +1257,9 @@ For complete OAuth documentation, see [OAUTH.md](OAUTH.md).
774
1257
  - **Server lookup** - Find servers by name using `find_server`
775
1258
  - **Tool association** - Each tool knows which server it belongs to
776
1259
  - **Tool discovery** - Find tools by name or pattern
1260
+ - **Structured outputs** - Support for MCP 2025-06-18 structured tool outputs with output schemas and type-safe responses
1261
+ - **Tool annotations** - Support for readOnly, destructive, and requiresConfirmation annotations with helper methods
1262
+ - **Elicitation support** - Server-initiated user interactions during tool execution (stdio, SSE, and Streamable HTTP transports)
777
1263
  - **Server disambiguation** - Specify which server to use when tools with same name exist in multiple servers
778
1264
  - **Atomic tool calls** - Simple API for invoking tools with parameters
779
1265
  - **Batch support** - Call multiple tools in a single operation