vector_mcp 0.1.0 → 0.3.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.
data/README.md CHANGED
@@ -17,46 +17,44 @@ This library allows you to easily create MCP servers that expose your applicatio
17
17
  * **Tools:** Define and register custom tools (functions) that the LLM can invoke.
18
18
  * **Resources:** Expose data sources (files, database results, API outputs) for the LLM to read.
19
19
  * **Prompts:** Provide structured prompt templates the LLM can request and use.
20
+ * **Roots:** Define filesystem boundaries where the server can operate, enhancing security and providing workspace context.
21
+ * **Sampling:** Server-initiated LLM requests with configurable capabilities (streaming, tool calls, images, token limits).
20
22
  * **Transport:**
21
23
  * **Stdio (stable):** Simple transport using standard input/output, ideal for process-based servers.
22
- * **SSE (work-in-progress):** Server-Sent Events support is under active development and currently unavailable.
23
- * **Extensible Handlers:** Provides default handlers for core MCP methods, which can be overridden.
24
- * **Clear Error Handling:** Custom error classes mapping to JSON-RPC/MCP error codes.
25
- * **Ruby-like API:** Uses blocks for registering handlers, following idiomatic Ruby patterns.
24
+ * **SSE (stable):** Server-Sent Events over HTTP, enabling web-based MCP clients and browser integration.
26
25
 
27
26
  ## Installation
28
27
 
29
- Add this line to your application's Gemfile:
30
-
31
28
  ```ruby
29
+ # In your Gemfile
32
30
  gem 'vector_mcp'
33
- ```
34
-
35
- And then execute:
36
31
 
37
- ```bash
38
- $ bundle install
32
+ # Or install directly
33
+ gem install vector_mcp
39
34
  ```
40
35
 
41
- Or install it yourself as:
42
-
43
- ```bash
44
- $ gem install vector_mcp
45
- ```
46
-
47
- > ⚠️ **Heads-up:** SSE transport is not yet supported in the released gem. When it lands it will require additional gems (`async`, `async-http`, `falcon`, `rack`).
48
36
 
49
37
  ## Quick Start
50
38
 
51
- This example creates a simple server that runs over standard input/output and provides one tool.
52
-
53
39
  ```ruby
54
40
  require 'vector_mcp'
55
41
 
56
- # Create a server
57
- server = VectorMCP.new('Echo Server')
42
+ # Create a server with sampling capabilities
43
+ server = VectorMCP.new(
44
+ name: 'Echo Server',
45
+ version: '1.0.0',
46
+ sampling_config: {
47
+ supports_streaming: true,
48
+ max_tokens_limit: 2000,
49
+ timeout_seconds: 45
50
+ }
51
+ )
52
+
53
+ # Register filesystem roots for security and context
54
+ server.register_root_from_path('.', name: 'Current Project')
55
+ server.register_root_from_path('./examples', name: 'Examples')
58
56
 
59
- # Register a single "echo" tool
57
+ # Register a tool
60
58
  server.register_tool(
61
59
  name: 'echo',
62
60
  description: 'Returns whatever message you send.',
@@ -67,76 +65,127 @@ server.register_tool(
67
65
  }
68
66
  ) { |args, _session| args['message'] }
69
67
 
70
- # Start listening on STDIN/STDOUT (default transport)
68
+ # Start listening on STDIN/STDOUT
71
69
  server.run
72
70
  ```
73
71
 
74
- **To run this:**
75
-
76
- 1. Save it as `my_server.rb`.
77
- 2. Run `ruby my_server.rb`.
78
- 3. The server now waits for **newline-delimited JSON-RPC objects** on **STDIN** and writes responses to **STDOUT**.
72
+ **To test with stdin/stdout:**
79
73
 
80
- You have two easy ways to talk to it:
81
-
82
- **a. Interactive (paste a line, press Enter)**
74
+ ```bash
75
+ $ ruby my_server.rb
76
+ # Then paste JSON-RPC requests, one per line:
77
+ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"CLI","version":"0.1"}}}
78
+ ```
83
79
 
84
- ```bash
85
- $ ruby my_server.rb
86
- # paste the JSON below, press ↵, observe the response
87
- {"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
88
- {"jsonrpc":"2.0","method":"initialized"}
89
- # etc.
90
- ```
80
+ Or use a script:
91
81
 
92
- **b. Scripted (pipe a series of echo / printf commands)**
82
+ ```bash
83
+ {
84
+ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"CLI","version":"0.1"}}}';
85
+ printf '%s\n' '{"jsonrpc":"2.0","method":"initialized"}';
86
+ printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}';
87
+ printf '%s\n' '{"jsonrpc":"2.0","id":3,"method":"resources/list","params":{}}';
88
+ printf '%s\n' '{"jsonrpc":"2.0","id":4,"method":"roots/list","params":{}}';
89
+ printf '%s\n' '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"echo","arguments":{"message":"Hello VectorMCP!"}}}';
90
+ } | ruby my_server.rb | jq
91
+ ```
93
92
 
94
- ```bash
95
- {
96
- printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"CLI","version":"0.1"}}}';
97
- printf '%s\n' '{"jsonrpc":"2.0","method":"initialized"}';
98
- printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}';
99
- printf '%s\n' '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"message":"Hello VectorMCP!"}}}';
100
- } | ruby my_server.rb | jq # jq formats the JSON responses
101
- ```
93
+ ### HTTP/SSE Transport
102
94
 
103
- Each request **must be on a single line and terminated by a newline** so the server knows where the message ends.
95
+ For web-based clients and browser integration, use the SSE transport:
104
96
 
105
- Below are the same requests shown individually:
97
+ ```ruby
98
+ require 'vector_mcp'
106
99
 
107
- ```jsonc
108
- // 1. Initialize (client → server)
109
- {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ManualClient","version":"0.1"}}}
100
+ server = VectorMCP.new(
101
+ name: 'My HTTP Server',
102
+ version: '1.0.0'
103
+ )
110
104
 
111
- // 2. Initialized notification (client → server, no id)
112
- {"jsonrpc":"2.0","method":"initialized"}
105
+ # Register tools, resources, prompts...
106
+ server.register_tool(
107
+ name: 'echo',
108
+ description: 'Returns the input message',
109
+ input_schema: {
110
+ type: 'object',
111
+ properties: { message: { type: 'string' } },
112
+ required: ['message']
113
+ }
114
+ ) { |args| args['message'] }
113
115
 
114
- // 3. List available tools (client → server)
115
- {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
116
+ # Start HTTP server with SSE transport
117
+ server.run(transport: :sse, options: { port: 8080, host: 'localhost' })
118
+ ```
116
119
 
117
- // 4. Call the echo tool (client → server)
118
- {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"message":"Hello VectorMCP!"}}}
120
+ The server provides two HTTP endpoints:
121
+
122
+ * **SSE Stream:** `GET /sse` - Establishes server-sent events connection
123
+ * **Messages:** `POST /message?session_id=<id>` - Sends JSON-RPC requests
124
+
125
+ **Client Integration:**
126
+
127
+ ```javascript
128
+ // Connect to SSE stream
129
+ const eventSource = new EventSource('http://localhost:8080/sse');
130
+
131
+ eventSource.addEventListener('endpoint', (event) => {
132
+ const data = JSON.parse(event.data);
133
+ const messageUrl = data.uri; // POST endpoint for this session
134
+
135
+ // Send MCP initialization
136
+ fetch(messageUrl, {
137
+ method: 'POST',
138
+ headers: { 'Content-Type': 'application/json' },
139
+ body: JSON.stringify({
140
+ jsonrpc: '2.0',
141
+ id: 1,
142
+ method: 'initialize',
143
+ params: {
144
+ protocolVersion: '2024-11-05',
145
+ capabilities: {},
146
+ clientInfo: { name: 'WebClient', version: '1.0' }
147
+ }
148
+ })
149
+ });
150
+ });
151
+
152
+ eventSource.addEventListener('message', (event) => {
153
+ const response = JSON.parse(event.data);
154
+ console.log('MCP Response:', response);
155
+ });
119
156
  ```
120
157
 
121
- ## Usage
158
+ ## Core Usage
122
159
 
123
160
  ### Creating a Server
124
161
 
125
- Instantiate the server using the factory method:
126
-
127
162
  ```ruby
128
- require 'vector_mcp'
129
-
163
+ # Using the convenience method
130
164
  server = VectorMCP.new(
131
- name: "MyAwesomeServer",
132
- version: "2.1.0",
133
- log_level: Logger::DEBUG # Optional: Default is INFO
165
+ name: "MyServer",
166
+ version: "1.0.0",
167
+ log_level: Logger::INFO,
168
+ sampling_config: {
169
+ enabled: true,
170
+ supports_streaming: false,
171
+ max_tokens_limit: 4000,
172
+ timeout_seconds: 30
173
+ }
174
+ )
175
+
176
+ # Or using the explicit class method
177
+ server = VectorMCP::Server.new(
178
+ name: "MyServer",
179
+ version: "1.0.0",
180
+ sampling_config: { enabled: false } # Disable sampling entirely
134
181
  )
135
182
  ```
136
183
 
184
+ The `sampling_config` parameter allows you to configure what sampling capabilities your server advertises to clients. See the [Sampling Configuration](#configuring-sampling-capabilities) section for detailed options.
185
+
137
186
  ### Registering Tools
138
187
 
139
- Tools are functions your server exposes. Use `register_tool` with a block.
188
+ Tools are functions your server exposes to clients.
140
189
 
141
190
  ```ruby
142
191
  server.register_tool(
@@ -145,66 +194,462 @@ server.register_tool(
145
194
  input_schema: {
146
195
  type: "object",
147
196
  properties: {
148
- a: { type: "number", description: "First number" },
149
- b: { type: "number", description: "Second number" }
197
+ a: { type: "number" },
198
+ b: { type: "number" }
150
199
  },
151
200
  required: ["a", "b"]
152
201
  }
153
202
  ) do |args, session|
154
- # args is a hash like { "a" => 10, "b" => 5 }
155
- # session object provides session context (e.g., session.initialized?)
156
- sum = (args["a"] || 0) + (args["b"] || 0)
157
- "The sum is: #{sum}" # Return value is converted to {type: "text", text: ...}
203
+ sum = args["a"] + args["b"]
204
+ "The sum is: #{sum}"
205
+ end
206
+ ```
207
+
208
+ #### Automatic Input Validation
209
+
210
+ VectorMCP automatically validates all tool arguments against their defined `input_schema` before executing the tool handler. This provides several security and reliability benefits:
211
+
212
+ - **Type Safety**: Ensures arguments match expected types (string, number, boolean, etc.)
213
+ - **Required Fields**: Validates that all required parameters are present
214
+ - **Format Validation**: Supports JSON Schema constraints like patterns, enums, and ranges
215
+ - **Security**: Prevents injection attacks and malformed input from reaching your tool logic
216
+ - **Better Error Messages**: Provides clear validation errors to help clients fix their requests
217
+
218
+ ```ruby
219
+ # Example with comprehensive validation
220
+ server.register_tool(
221
+ name: "process_user_data",
222
+ description: "Processes user information with strict validation",
223
+ input_schema: {
224
+ type: "object",
225
+ properties: {
226
+ name: {
227
+ type: "string",
228
+ minLength: 1,
229
+ maxLength: 100,
230
+ pattern: "^[a-zA-Z\\s]+$"
231
+ },
232
+ age: {
233
+ type: "integer",
234
+ minimum: 0,
235
+ maximum: 150
236
+ },
237
+ email: {
238
+ type: "string",
239
+ format: "email"
240
+ },
241
+ role: {
242
+ type: "string",
243
+ enum: ["admin", "user", "guest"]
244
+ },
245
+ preferences: {
246
+ type: "object",
247
+ properties: {
248
+ theme: { type: "string" },
249
+ notifications: { type: "boolean" }
250
+ },
251
+ additionalProperties: false
252
+ }
253
+ },
254
+ required: ["name", "email", "role"],
255
+ additionalProperties: false
256
+ }
257
+ ) do |args, session|
258
+ # At this point, you can trust that:
259
+ # - All required fields are present
260
+ # - Data types are correct
261
+ # - String lengths and number ranges are valid
262
+ # - Email format is valid
263
+ # - Role is one of the allowed values
264
+
265
+ "Processing user: #{args['name']} (#{args['role']})"
158
266
  end
159
267
  ```
160
268
 
161
- * The input_schema must be a Hash representing a valid JSON Schema object describing the tool's expected arguments.
162
- * The block receives the arguments hash and the VectorMCP::Session object.
163
- * The **session** object represents the client connection that invoked the tool. It lets you:
164
- * Inspect the client's `clientInfo` and declared `capabilities` (e.g. `session.client_info['name']`).
165
- * Store or look up per-connection state (authentication, rate-limiting, feature flags).
166
- * Send follow-up notifications or streaming updates back only to that client.
167
- * Check whether the session is already `initialized?` before doing expensive work.
269
+ **What happens with invalid input:**
270
+ - VectorMCP returns a JSON-RPC error response with code `-32602` (Invalid params)
271
+ - The error message includes specific details about what validation failed
272
+ - Your tool handler is never called with invalid data
273
+ - Clients receive clear feedback on how to fix their requests
168
274
 
169
- Passing `session` up-front means tool authors can make use of this context today; if you don't need it, simply ignore the parameter (Ruby will accept extra block parameters).
170
- * The block's return value is automatically converted into the MCP `content` array format by `VectorMCP::Util.convert_to_mcp_content`. You can return:
171
- * A `String`: Becomes `{ type: 'text', text: '...' }`.
172
- * A `Hash` matching the MCP content structure (`{ type: 'text', ... }`, `{ type: 'image', ... }`, etc.): Used as is.
173
- * Other `Hash` objects: JSON-encoded into `{ type: 'text', text: '...', mimeType: 'application/json' }`.
174
- * Binary String (`Encoding::ASCII_8BIT`): Base64-encoded into `{ type: 'blob', blob: '...', mimeType: 'application/octet-stream' }`.
175
- * An `Array` of the above: Each element is converted and combined.
176
- * Other objects: Converted using `to_s` into `{ type: 'text', text: '...' }`.
275
+ **Backward Compatibility:**
276
+ - Tools without `input_schema` continue to work normally
277
+ - No validation is performed if `input_schema` is not provided
278
+ - Existing tools are unaffected by this security enhancement
279
+
280
+ #### Tool Registration Options
281
+
282
+ - `input_schema`: A JSON Schema object describing the tool's expected arguments (recommended for security)
283
+ - Return value is automatically converted to MCP content format:
284
+ - String → `{type: 'text', text: '...'}`
285
+ - Hash with proper MCP structure → used as-is
286
+ - Other Hash → JSON-encoded text
287
+ - Binary data → Base64-encoded blob
177
288
 
178
289
  ### Registering Resources
179
290
 
180
- Resources provide data that the client can read.
291
+ Resources are data sources clients can read.
181
292
 
182
293
  ```ruby
183
294
  server.register_resource(
184
- uri: "memory://status", # Unique URI for this resource
295
+ uri: "memory://status",
185
296
  name: "Server Status",
186
- description: "Provides the current server status.",
187
- mime_type: "application/json" # Optional: Defaults to text/plain
297
+ description: "Current server status.",
298
+ mime_type: "application/json"
188
299
  ) do |session|
189
- # Handler block receives the session object
190
300
  {
191
301
  status: "OK",
192
- uptime: Time.now - server_start_time, # Example value
193
- initialized: session.initialized?
194
- } # Hash will be JSON encoded due to mime_type
302
+ uptime: Time.now - server_start_time
303
+ }
195
304
  end
305
+ ```
306
+
307
+ - `uri`: Unique identifier for the resource
308
+ - `mime_type`: Helps clients interpret the data (optional, defaults to "text/plain")
309
+ - Return types similar to tools: strings, hashes, binary data
196
310
 
197
- # Resource returning binary data
311
+ ### Registering Prompts
312
+
313
+ Prompts are templates or workflows clients can request.
314
+
315
+ ```ruby
316
+ server.register_prompt(
317
+ name: "project_summary_generator",
318
+ description: "Creates a project summary.",
319
+ arguments: [
320
+ { name: "project_name", description: "Project name", type: "string", required: true },
321
+ { name: "project_goal", description: "Project objective", type: "string", required: true }
322
+ ]
323
+ ) do |args, session|
324
+ {
325
+ description: "Project summary prompt for '#{args["project_name"]}'",
326
+ messages: [
327
+ {
328
+ role: "user",
329
+ content: {
330
+ type: "text",
331
+ text: "Generate a summary for project '#{args["project_name"]}'. " \
332
+ "The main goal is '#{args["project_goal"]}'."
333
+ }
334
+ }
335
+ ]
336
+ }
337
+ end
338
+ ```
339
+
340
+ - `arguments`: Defines the parameters this prompt template expects
341
+ - Return a Hash conforming to the MCP `GetPromptResult` schema with a `messages` array
342
+
343
+ ### Registering Roots
344
+
345
+ Roots define filesystem boundaries where your MCP server can operate. They provide security by establishing clear boundaries and help clients understand which directories your server has access to.
346
+
347
+ ```ruby
348
+ # Register a project directory as a root
349
+ server.register_root(
350
+ uri: "file:///home/user/projects/myapp",
351
+ name: "My Application"
352
+ )
353
+
354
+ # Register from a local path (automatically creates file:// URI)
355
+ server.register_root_from_path("/home/user/projects/frontend", name: "Frontend Code")
356
+
357
+ # Register current directory
358
+ server.register_root_from_path(".", name: "Current Project")
359
+
360
+ # Chain multiple root registrations
361
+ server.register_root_from_path("./src", name: "Source Code")
362
+ .register_root_from_path("./docs", name: "Documentation")
363
+ .register_root_from_path("./tests", name: "Test Suite")
364
+ ```
365
+
366
+ **Security Features:**
367
+ - **File URI Validation:** Only `file://` scheme URIs are supported
368
+ - **Directory Verification:** Ensures the path exists and is a directory
369
+ - **Readability Checks:** Verifies the server can read the directory
370
+ - **Path Traversal Protection:** Prevents `../` attacks and unsafe path patterns
371
+ - **Access Control:** Tools and resources can verify operations against registered roots
372
+
373
+ **Usage in Tools and Resources:**
374
+ ```ruby
375
+ # Tool that operates within registered roots
376
+ server.register_tool(
377
+ name: "list_files_in_root",
378
+ description: "Lists files in a registered root directory",
379
+ input_schema: {
380
+ type: "object",
381
+ properties: {
382
+ root_uri: {
383
+ type: "string",
384
+ description: "URI of a registered root directory"
385
+ }
386
+ },
387
+ required: ["root_uri"]
388
+ }
389
+ ) do |args, session|
390
+ root_uri = args["root_uri"]
391
+
392
+ # Verify the root is registered (security check)
393
+ root = server.roots[root_uri]
394
+ raise ArgumentError, "Root '#{root_uri}' is not registered" unless root
395
+
396
+ # Get the actual filesystem path
397
+ path = root.path
398
+
399
+ # Safely list directory contents
400
+ entries = Dir.entries(path).reject { |entry| entry.start_with?('.') }
401
+
402
+ {
403
+ root_name: root.name,
404
+ root_uri: root_uri,
405
+ files: entries.sort,
406
+ total_count: entries.size
407
+ }
408
+ end
409
+
410
+ # Resource that provides root information
198
411
  server.register_resource(
199
- uri: "file://logo.png",
200
- name: "Logo Image",
201
- description: "The server's logo.",
202
- mime_type: "image/png"
203
- ) do |session|
204
- # IMPORTANT: Return binary data as a string with ASCII-8BIT encoding
205
- File.binread("path/to/logo.png")
412
+ uri: "workspace://roots",
413
+ name: "Workspace Roots",
414
+ description: "Information about registered workspace roots",
415
+ mime_type: "application/json"
416
+ ) do |params|
417
+ {
418
+ total_roots: server.roots.size,
419
+ roots: server.roots.map do |uri, root|
420
+ {
421
+ uri: uri,
422
+ name: root.name,
423
+ path: root.path,
424
+ accessible: File.readable?(root.path)
425
+ }
426
+ end
427
+ }
206
428
  end
207
429
  ```
208
430
 
209
- * The block receives the `VectorMCP::Session` object.
210
- * Return `String` for text, or a binary `
431
+ **Key Benefits:**
432
+ - **Security:** Establishes clear filesystem boundaries for server operations
433
+ - **Context:** Provides workspace information to LLM clients
434
+ - **Organization:** Helps structure multi-directory projects
435
+ - **Validation:** Automatic path validation and security checks
436
+ - **MCP Compliance:** Full support for the MCP roots specification
437
+
438
+ ## Advanced Features
439
+
440
+ ### Session Object
441
+
442
+ The `session` object provides client context and connection state.
443
+
444
+ ```ruby
445
+ # Access client information
446
+ client_name = session.client_info&.dig('name')
447
+ client_capabilities = session.client_capabilities
448
+
449
+ # Check if the client is fully initialized
450
+ if session.initialized?
451
+ # Perform operations
452
+ end
453
+ ```
454
+
455
+ ### Custom Handlers
456
+
457
+ Override default behaviors or add custom methods.
458
+
459
+ ```ruby
460
+ # Custom request handler
461
+ server.on_request("my_server/status") do |params, session, server|
462
+ { status: "OK", server_name: server.name }
463
+ end
464
+
465
+ # Custom notification handler
466
+ server.on_notification("my_server/log") do |params, session, server|
467
+ server.logger.info("Event: #{params['message']}")
468
+ end
469
+ ```
470
+
471
+ ### Error Handling
472
+
473
+ Use proper error classes for correct JSON-RPC error responses.
474
+
475
+ ```ruby
476
+ # In a tool handler
477
+ if resource_not_found
478
+ raise VectorMCP::NotFoundError.new("Resource not available")
479
+ elsif invalid_parameters
480
+ raise VectorMCP::InvalidParamsError.new("Invalid parameter format")
481
+ end
482
+ ```
483
+
484
+ Common error classes:
485
+ - `VectorMCP::InvalidRequestError`
486
+ - `VectorMCP::MethodNotFoundError`
487
+ - `VectorMCP::InvalidParamsError`
488
+ - `VectorMCP::NotFoundError`
489
+ - `VectorMCP::InternalError`
490
+
491
+ ### Sampling (LLM completions)
492
+
493
+ VectorMCP servers can ask the connected client to run an LLM completion and return the result. This allows servers to leverage LLMs for tasks like content generation, analysis, or decision-making, while keeping the user in control of the final interaction with the LLM (as mediated by the client).
494
+
495
+ #### Configuring Sampling Capabilities
496
+
497
+ You can configure your server's sampling capabilities during initialization to advertise what features your server supports:
498
+
499
+ ```ruby
500
+ server = VectorMCP::Server.new(
501
+ name: "MyServer",
502
+ version: "1.0.0",
503
+ sampling_config: {
504
+ enabled: true, # Enable/disable sampling (default: true)
505
+ supports_streaming: true, # Support streaming responses (default: false)
506
+ supports_tool_calls: true, # Support tool calls in sampling (default: false)
507
+ supports_images: true, # Support image content (default: false)
508
+ max_tokens_limit: 4000, # Maximum tokens limit (default: nil, no limit)
509
+ timeout_seconds: 60, # Default timeout in seconds (default: 30)
510
+ context_inclusion_methods: ["none", "thisServer", "allServers"], # Supported context methods
511
+ model_preferences_supported: true # Support model preferences (default: true)
512
+ }
513
+ )
514
+ ```
515
+
516
+ **Configuration Options:**
517
+ - `enabled`: Whether sampling is available at all
518
+ - `supports_streaming`: Advertise support for streaming responses
519
+ - `supports_tool_calls`: Advertise support for tool calls within sampling
520
+ - `supports_images`: Advertise support for image content in messages
521
+ - `max_tokens_limit`: Maximum tokens your server can handle (helps clients choose appropriate limits)
522
+ - `timeout_seconds`: Default timeout for sampling requests
523
+ - `context_inclusion_methods`: Supported context inclusion modes (`"none"`, `"thisServer"`, `"allServers"`)
524
+ - `model_preferences_supported`: Whether your server supports model selection hints
525
+
526
+ These capabilities are advertised to clients during the MCP handshake, helping them understand what your server supports and make appropriate sampling requests.
527
+
528
+ #### Minimal Configuration Examples
529
+
530
+ ```ruby
531
+ # Basic sampling (default configuration)
532
+ server = VectorMCP::Server.new(name: "BasicServer")
533
+ # Advertises: createMessage support, model preferences, 30s timeout, basic context inclusion
534
+
535
+ # Advanced streaming server
536
+ server = VectorMCP::Server.new(
537
+ name: "StreamingServer",
538
+ sampling_config: {
539
+ supports_streaming: true,
540
+ supports_tool_calls: true,
541
+ max_tokens_limit: 8000,
542
+ timeout_seconds: 120
543
+ }
544
+ )
545
+
546
+ # Disable sampling entirely
547
+ server = VectorMCP::Server.new(
548
+ name: "NoSamplingServer",
549
+ sampling_config: { enabled: false }
550
+ )
551
+ ```
552
+
553
+ #### Using Sampling in Your Handlers
554
+
555
+ The `session.sample` method sends a `sampling/createMessage` request to the client and waits for the response:
556
+
557
+ ```ruby
558
+ # Inside a tool, resource, or prompt handler block:
559
+ tool_input = args["topic"] # Assuming 'args' are the input to your handler
560
+
561
+ begin
562
+ sampling_result = session.sample(
563
+ messages: [
564
+ { role: "user", content: { type: "text", text: "Generate a short, catchy tagline for: #{tool_input}" } }
565
+ ],
566
+ max_tokens: 25,
567
+ temperature: 0.8,
568
+ model_preferences: { # Optional: guide client on model selection
569
+ hints: [{ name: "claude-3-haiku" }, { name: "gpt-3.5-turbo" }], # Preferred models
570
+ intelligence_priority: 0.5, # Balance between capability, speed, and cost
571
+ speed_priority: 0.8
572
+ },
573
+ timeout: 15 # Optional: per-request timeout in seconds
574
+ )
575
+
576
+ if sampling_result.text?
577
+ tagline = sampling_result.text_content
578
+ "Generated tagline: #{tagline}"
579
+ else
580
+ "LLM did not return text content."
581
+ end
582
+ rescue VectorMCP::SamplingTimeoutError => e
583
+ server.logger.warn("Sampling request timed out: #{e.message}")
584
+ "Sorry, the request for a tagline timed out."
585
+ rescue VectorMCP::SamplingError => e
586
+ server.logger.error("Sampling request failed: #{e.message}")
587
+ "Sorry, couldn't generate a tagline due to an error."
588
+ rescue ArgumentError => e
589
+ server.logger.error("Invalid arguments for sampling: #{e.message}")
590
+ "Internal error: Invalid arguments for tagline generation."
591
+ end
592
+ ```
593
+
594
+ **Key Points:**
595
+ - The `session.sample` method takes a hash conforming to the `VectorMCP::Sampling::Request` structure
596
+ - It returns a `VectorMCP::Sampling::Result` object with methods like `text?`, `text_content`, `image?`, etc.
597
+ - Raises `VectorMCP::SamplingTimeoutError`, `VectorMCP::SamplingError`, or `VectorMCP::SamplingRejectedError` on failures
598
+ - Your server's advertised capabilities help clients understand what parameters are supported
599
+
600
+ #### Accessing Sampling Configuration
601
+
602
+ You can access your server's sampling configuration at runtime:
603
+
604
+ ```ruby
605
+ config = server.sampling_config
606
+ puts "Streaming supported: #{config[:supports_streaming]}"
607
+ puts "Max tokens: #{config[:max_tokens_limit] || 'unlimited'}"
608
+ puts "Timeout: #{config[:timeout_seconds]}s"
609
+ ```
610
+
611
+ ## Example Implementations
612
+
613
+ These projects demonstrate real-world implementations of VectorMCP servers:
614
+
615
+ ### [file_system_mcp](https://github.com/sergiobayona/file_system_mcp)
616
+
617
+ A complete MCP server providing filesystem operations:
618
+ - Read/write files
619
+ - Create/list/delete directories
620
+ - Move files/directories
621
+ - Search files
622
+ - Get file metadata
623
+
624
+ Works with Claude Desktop and other MCP clients.
625
+
626
+ ### Roots Demo (included)
627
+
628
+ The `examples/roots_demo.rb` script demonstrates comprehensive roots functionality:
629
+ - Multiple root registration with automatic validation
630
+ - Security boundaries and workspace context
631
+ - Tools that operate safely within registered roots
632
+ - Resources providing workspace information
633
+ - Integration with other MCP features
634
+
635
+ Run it with: `ruby examples/roots_demo.rb`
636
+
637
+ This example shows best practices for:
638
+ - Registering multiple project directories as roots
639
+ - Implementing security checks in tools
640
+ - Providing workspace context to clients
641
+ - Error handling for invalid root operations
642
+
643
+ ## Development
644
+
645
+ 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.
646
+
647
+ To install this gem onto your local machine, run `bundle exec rake install`.
648
+
649
+ ## Contributing
650
+
651
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sergiobayona/vector_mcp.
652
+
653
+ ## License
654
+
655
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).