vector_mcp 0.1.0 → 0.2.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: f97bb6519db2c4d1b5e7cbac2a47954714aaa8e65f3cf797054c43cfc782255c
4
- data.tar.gz: 5188e19bed4e4114729d6ea482361a0ad5adffddf0c400dd6081e7370037eb73
3
+ metadata.gz: 5eb7e5435a5b8e67c4bd570c25432b86b0e791e24c5225ee398f693867030d9a
4
+ data.tar.gz: 9e0a8dce81c39d4912136a0985cff18b7c4bb30e5bc361d19bc81530183d53ac
5
5
  SHA512:
6
- metadata.gz: 04af736a5887198f5d4d24e16d37689dce473872fc3a4c6448f0bdc8aaf49ff06a1f076827725978ad4f71f8a89906a9ab1dc336a91d2d176302a91a8726183d
7
- data.tar.gz: 1386b745783db291622c0aec7b8108c966089e40c114b37386b2fc9e041df100ba19b927353edcac7ece880f0682cc8c37457e51e35d842936fef3dc4aa5447c
6
+ metadata.gz: c46edbc723ece6fc4d6f8658a4ea40f97a5e80a675d187d7a58d60fffcdfb0bf1ba3b9c57e1e2f142520a280196aa97635a1ef747316c0111d4097e15006d363
7
+ data.tar.gz: f67637b1bdbb0fb86ce500b315382ae93a5caa18ea6c8da2f6e563b8c81208f17840fb753cce92eae11b672961a0c9a628d65d11c7eb723affb35e6c9ba27c0d
data/README.md CHANGED
@@ -17,46 +17,45 @@ 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
24
  * **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.
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`).
36
+ > ⚠️ **Note:** SSE transport is not yet supported in the released gem.
48
37
 
49
38
  ## Quick Start
50
39
 
51
- This example creates a simple server that runs over standard input/output and provides one tool.
52
-
53
40
  ```ruby
54
41
  require 'vector_mcp'
55
42
 
56
- # Create a server
57
- server = VectorMCP.new('Echo Server')
43
+ # Create a server with sampling capabilities
44
+ server = VectorMCP.new(
45
+ name: 'Echo Server',
46
+ version: '1.0.0',
47
+ sampling_config: {
48
+ supports_streaming: true,
49
+ max_tokens_limit: 2000,
50
+ timeout_seconds: 45
51
+ }
52
+ )
58
53
 
59
- # Register a single "echo" tool
54
+ # Register filesystem roots for security and context
55
+ server.register_root_from_path('.', name: 'Current Project')
56
+ server.register_root_from_path('./examples', name: 'Examples')
57
+
58
+ # Register a tool
60
59
  server.register_tool(
61
60
  name: 'echo',
62
61
  description: 'Returns whatever message you send.',
@@ -67,76 +66,62 @@ server.register_tool(
67
66
  }
68
67
  ) { |args, _session| args['message'] }
69
68
 
70
- # Start listening on STDIN/STDOUT (default transport)
69
+ # Start listening on STDIN/STDOUT
71
70
  server.run
72
71
  ```
73
72
 
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**.
79
-
80
- You have two easy ways to talk to it:
81
-
82
- **a. Interactive (paste a line, press Enter)**
83
-
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
- ```
91
-
92
- **b. Scripted (pipe a series of echo / printf commands)**
73
+ **To test with stdin/stdout:**
93
74
 
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
- ```
102
-
103
- Each request **must be on a single line and terminated by a newline** so the server knows where the message ends.
104
-
105
- Below are the same requests shown individually:
106
-
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"}}}
110
-
111
- // 2. Initialized notification (client → server, no id)
112
- {"jsonrpc":"2.0","method":"initialized"}
75
+ ```bash
76
+ $ ruby my_server.rb
77
+ # Then paste JSON-RPC requests, one per line:
78
+ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"CLI","version":"0.1"}}}
79
+ ```
113
80
 
114
- // 3. List available tools (client → server)
115
- {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
81
+ Or use a script:
116
82
 
117
- // 4. Call the echo tool (client → server)
118
- {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"message":"Hello VectorMCP!"}}}
83
+ ```bash
84
+ {
85
+ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"CLI","version":"0.1"}}}';
86
+ printf '%s\n' '{"jsonrpc":"2.0","method":"initialized"}';
87
+ printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}';
88
+ printf '%s\n' '{"jsonrpc":"2.0","id":3,"method":"resources/list","params":{}}';
89
+ printf '%s\n' '{"jsonrpc":"2.0","id":4,"method":"roots/list","params":{}}';
90
+ printf '%s\n' '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"echo","arguments":{"message":"Hello VectorMCP!"}}}';
91
+ } | ruby my_server.rb | jq
119
92
  ```
120
93
 
121
- ## Usage
94
+ ## Core Usage
122
95
 
123
96
  ### Creating a Server
124
97
 
125
- Instantiate the server using the factory method:
126
-
127
98
  ```ruby
128
- require 'vector_mcp'
129
-
99
+ # Using the convenience method
130
100
  server = VectorMCP.new(
131
- name: "MyAwesomeServer",
132
- version: "2.1.0",
133
- log_level: Logger::DEBUG # Optional: Default is INFO
101
+ name: "MyServer",
102
+ version: "1.0.0",
103
+ log_level: Logger::INFO,
104
+ sampling_config: {
105
+ enabled: true,
106
+ supports_streaming: false,
107
+ max_tokens_limit: 4000,
108
+ timeout_seconds: 30
109
+ }
110
+ )
111
+
112
+ # Or using the explicit class method
113
+ server = VectorMCP::Server.new(
114
+ name: "MyServer",
115
+ version: "1.0.0",
116
+ sampling_config: { enabled: false } # Disable sampling entirely
134
117
  )
135
118
  ```
136
119
 
120
+ 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.
121
+
137
122
  ### Registering Tools
138
123
 
139
- Tools are functions your server exposes. Use `register_tool` with a block.
124
+ Tools are functions your server exposes to clients.
140
125
 
141
126
  ```ruby
142
127
  server.register_tool(
@@ -145,66 +130,388 @@ server.register_tool(
145
130
  input_schema: {
146
131
  type: "object",
147
132
  properties: {
148
- a: { type: "number", description: "First number" },
149
- b: { type: "number", description: "Second number" }
133
+ a: { type: "number" },
134
+ b: { type: "number" }
150
135
  },
151
136
  required: ["a", "b"]
152
137
  }
153
138
  ) 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: ...}
139
+ sum = args["a"] + args["b"]
140
+ "The sum is: #{sum}"
158
141
  end
159
142
  ```
160
143
 
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.
168
-
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: '...' }`.
144
+ - `input_schema`: A JSON Schema object describing the tool's expected arguments
145
+ - Return value is automatically converted to MCP content format:
146
+ - String `{type: 'text', text: '...'}`
147
+ - Hash with proper MCP structure used as-is
148
+ - Other Hash JSON-encoded text
149
+ - Binary data Base64-encoded blob
177
150
 
178
151
  ### Registering Resources
179
152
 
180
- Resources provide data that the client can read.
153
+ Resources are data sources clients can read.
181
154
 
182
155
  ```ruby
183
156
  server.register_resource(
184
- uri: "memory://status", # Unique URI for this resource
157
+ uri: "memory://status",
185
158
  name: "Server Status",
186
- description: "Provides the current server status.",
187
- mime_type: "application/json" # Optional: Defaults to text/plain
159
+ description: "Current server status.",
160
+ mime_type: "application/json"
188
161
  ) do |session|
189
- # Handler block receives the session object
190
162
  {
191
163
  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
164
+ uptime: Time.now - server_start_time
165
+ }
195
166
  end
167
+ ```
168
+
169
+ - `uri`: Unique identifier for the resource
170
+ - `mime_type`: Helps clients interpret the data (optional, defaults to "text/plain")
171
+ - Return types similar to tools: strings, hashes, binary data
196
172
 
197
- # Resource returning binary data
173
+ ### Registering Prompts
174
+
175
+ Prompts are templates or workflows clients can request.
176
+
177
+ ```ruby
178
+ server.register_prompt(
179
+ name: "project_summary_generator",
180
+ description: "Creates a project summary.",
181
+ arguments: [
182
+ { name: "project_name", description: "Project name", type: "string", required: true },
183
+ { name: "project_goal", description: "Project objective", type: "string", required: true }
184
+ ]
185
+ ) do |args, session|
186
+ {
187
+ description: "Project summary prompt for '#{args["project_name"]}'",
188
+ messages: [
189
+ {
190
+ role: "user",
191
+ content: {
192
+ type: "text",
193
+ text: "Generate a summary for project '#{args["project_name"]}'. " \
194
+ "The main goal is '#{args["project_goal"]}'."
195
+ }
196
+ }
197
+ ]
198
+ }
199
+ end
200
+ ```
201
+
202
+ - `arguments`: Defines the parameters this prompt template expects
203
+ - Return a Hash conforming to the MCP `GetPromptResult` schema with a `messages` array
204
+
205
+ ### Registering Roots
206
+
207
+ 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.
208
+
209
+ ```ruby
210
+ # Register a project directory as a root
211
+ server.register_root(
212
+ uri: "file:///home/user/projects/myapp",
213
+ name: "My Application"
214
+ )
215
+
216
+ # Register from a local path (automatically creates file:// URI)
217
+ server.register_root_from_path("/home/user/projects/frontend", name: "Frontend Code")
218
+
219
+ # Register current directory
220
+ server.register_root_from_path(".", name: "Current Project")
221
+
222
+ # Chain multiple root registrations
223
+ server.register_root_from_path("./src", name: "Source Code")
224
+ .register_root_from_path("./docs", name: "Documentation")
225
+ .register_root_from_path("./tests", name: "Test Suite")
226
+ ```
227
+
228
+ **Security Features:**
229
+ - **File URI Validation:** Only `file://` scheme URIs are supported
230
+ - **Directory Verification:** Ensures the path exists and is a directory
231
+ - **Readability Checks:** Verifies the server can read the directory
232
+ - **Path Traversal Protection:** Prevents `../` attacks and unsafe path patterns
233
+ - **Access Control:** Tools and resources can verify operations against registered roots
234
+
235
+ **Usage in Tools and Resources:**
236
+ ```ruby
237
+ # Tool that operates within registered roots
238
+ server.register_tool(
239
+ name: "list_files_in_root",
240
+ description: "Lists files in a registered root directory",
241
+ input_schema: {
242
+ type: "object",
243
+ properties: {
244
+ root_uri: {
245
+ type: "string",
246
+ description: "URI of a registered root directory"
247
+ }
248
+ },
249
+ required: ["root_uri"]
250
+ }
251
+ ) do |args, session|
252
+ root_uri = args["root_uri"]
253
+
254
+ # Verify the root is registered (security check)
255
+ root = server.roots[root_uri]
256
+ raise ArgumentError, "Root '#{root_uri}' is not registered" unless root
257
+
258
+ # Get the actual filesystem path
259
+ path = root.path
260
+
261
+ # Safely list directory contents
262
+ entries = Dir.entries(path).reject { |entry| entry.start_with?('.') }
263
+
264
+ {
265
+ root_name: root.name,
266
+ root_uri: root_uri,
267
+ files: entries.sort,
268
+ total_count: entries.size
269
+ }
270
+ end
271
+
272
+ # Resource that provides root information
198
273
  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")
274
+ uri: "workspace://roots",
275
+ name: "Workspace Roots",
276
+ description: "Information about registered workspace roots",
277
+ mime_type: "application/json"
278
+ ) do |params|
279
+ {
280
+ total_roots: server.roots.size,
281
+ roots: server.roots.map do |uri, root|
282
+ {
283
+ uri: uri,
284
+ name: root.name,
285
+ path: root.path,
286
+ accessible: File.readable?(root.path)
287
+ }
288
+ end
289
+ }
206
290
  end
207
291
  ```
208
292
 
209
- * The block receives the `VectorMCP::Session` object.
210
- * Return `String` for text, or a binary `
293
+ **Key Benefits:**
294
+ - **Security:** Establishes clear filesystem boundaries for server operations
295
+ - **Context:** Provides workspace information to LLM clients
296
+ - **Organization:** Helps structure multi-directory projects
297
+ - **Validation:** Automatic path validation and security checks
298
+ - **MCP Compliance:** Full support for the MCP roots specification
299
+
300
+ ## Advanced Features
301
+
302
+ ### Session Object
303
+
304
+ The `session` object provides client context and connection state.
305
+
306
+ ```ruby
307
+ # Access client information
308
+ client_name = session.client_info&.dig('name')
309
+ client_capabilities = session.client_capabilities
310
+
311
+ # Check if the client is fully initialized
312
+ if session.initialized?
313
+ # Perform operations
314
+ end
315
+ ```
316
+
317
+ ### Custom Handlers
318
+
319
+ Override default behaviors or add custom methods.
320
+
321
+ ```ruby
322
+ # Custom request handler
323
+ server.on_request("my_server/status") do |params, session, server|
324
+ { status: "OK", server_name: server.name }
325
+ end
326
+
327
+ # Custom notification handler
328
+ server.on_notification("my_server/log") do |params, session, server|
329
+ server.logger.info("Event: #{params['message']}")
330
+ end
331
+ ```
332
+
333
+ ### Error Handling
334
+
335
+ Use proper error classes for correct JSON-RPC error responses.
336
+
337
+ ```ruby
338
+ # In a tool handler
339
+ if resource_not_found
340
+ raise VectorMCP::NotFoundError.new("Resource not available")
341
+ elsif invalid_parameters
342
+ raise VectorMCP::InvalidParamsError.new("Invalid parameter format")
343
+ end
344
+ ```
345
+
346
+ Common error classes:
347
+ - `VectorMCP::InvalidRequestError`
348
+ - `VectorMCP::MethodNotFoundError`
349
+ - `VectorMCP::InvalidParamsError`
350
+ - `VectorMCP::NotFoundError`
351
+ - `VectorMCP::InternalError`
352
+
353
+ ### Sampling (LLM completions)
354
+
355
+ 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).
356
+
357
+ #### Configuring Sampling Capabilities
358
+
359
+ You can configure your server's sampling capabilities during initialization to advertise what features your server supports:
360
+
361
+ ```ruby
362
+ server = VectorMCP::Server.new(
363
+ name: "MyServer",
364
+ version: "1.0.0",
365
+ sampling_config: {
366
+ enabled: true, # Enable/disable sampling (default: true)
367
+ supports_streaming: true, # Support streaming responses (default: false)
368
+ supports_tool_calls: true, # Support tool calls in sampling (default: false)
369
+ supports_images: true, # Support image content (default: false)
370
+ max_tokens_limit: 4000, # Maximum tokens limit (default: nil, no limit)
371
+ timeout_seconds: 60, # Default timeout in seconds (default: 30)
372
+ context_inclusion_methods: ["none", "thisServer", "allServers"], # Supported context methods
373
+ model_preferences_supported: true # Support model preferences (default: true)
374
+ }
375
+ )
376
+ ```
377
+
378
+ **Configuration Options:**
379
+ - `enabled`: Whether sampling is available at all
380
+ - `supports_streaming`: Advertise support for streaming responses
381
+ - `supports_tool_calls`: Advertise support for tool calls within sampling
382
+ - `supports_images`: Advertise support for image content in messages
383
+ - `max_tokens_limit`: Maximum tokens your server can handle (helps clients choose appropriate limits)
384
+ - `timeout_seconds`: Default timeout for sampling requests
385
+ - `context_inclusion_methods`: Supported context inclusion modes (`"none"`, `"thisServer"`, `"allServers"`)
386
+ - `model_preferences_supported`: Whether your server supports model selection hints
387
+
388
+ These capabilities are advertised to clients during the MCP handshake, helping them understand what your server supports and make appropriate sampling requests.
389
+
390
+ #### Minimal Configuration Examples
391
+
392
+ ```ruby
393
+ # Basic sampling (default configuration)
394
+ server = VectorMCP::Server.new(name: "BasicServer")
395
+ # Advertises: createMessage support, model preferences, 30s timeout, basic context inclusion
396
+
397
+ # Advanced streaming server
398
+ server = VectorMCP::Server.new(
399
+ name: "StreamingServer",
400
+ sampling_config: {
401
+ supports_streaming: true,
402
+ supports_tool_calls: true,
403
+ max_tokens_limit: 8000,
404
+ timeout_seconds: 120
405
+ }
406
+ )
407
+
408
+ # Disable sampling entirely
409
+ server = VectorMCP::Server.new(
410
+ name: "NoSamplingServer",
411
+ sampling_config: { enabled: false }
412
+ )
413
+ ```
414
+
415
+ #### Using Sampling in Your Handlers
416
+
417
+ The `session.sample` method sends a `sampling/createMessage` request to the client and waits for the response:
418
+
419
+ ```ruby
420
+ # Inside a tool, resource, or prompt handler block:
421
+ tool_input = args["topic"] # Assuming 'args' are the input to your handler
422
+
423
+ begin
424
+ sampling_result = session.sample(
425
+ messages: [
426
+ { role: "user", content: { type: "text", text: "Generate a short, catchy tagline for: #{tool_input}" } }
427
+ ],
428
+ max_tokens: 25,
429
+ temperature: 0.8,
430
+ model_preferences: { # Optional: guide client on model selection
431
+ hints: [{ name: "claude-3-haiku" }, { name: "gpt-3.5-turbo" }], # Preferred models
432
+ intelligence_priority: 0.5, # Balance between capability, speed, and cost
433
+ speed_priority: 0.8
434
+ },
435
+ timeout: 15 # Optional: per-request timeout in seconds
436
+ )
437
+
438
+ if sampling_result.text?
439
+ tagline = sampling_result.text_content
440
+ "Generated tagline: #{tagline}"
441
+ else
442
+ "LLM did not return text content."
443
+ end
444
+ rescue VectorMCP::SamplingTimeoutError => e
445
+ server.logger.warn("Sampling request timed out: #{e.message}")
446
+ "Sorry, the request for a tagline timed out."
447
+ rescue VectorMCP::SamplingError => e
448
+ server.logger.error("Sampling request failed: #{e.message}")
449
+ "Sorry, couldn't generate a tagline due to an error."
450
+ rescue ArgumentError => e
451
+ server.logger.error("Invalid arguments for sampling: #{e.message}")
452
+ "Internal error: Invalid arguments for tagline generation."
453
+ end
454
+ ```
455
+
456
+ **Key Points:**
457
+ - The `session.sample` method takes a hash conforming to the `VectorMCP::Sampling::Request` structure
458
+ - It returns a `VectorMCP::Sampling::Result` object with methods like `text?`, `text_content`, `image?`, etc.
459
+ - Raises `VectorMCP::SamplingTimeoutError`, `VectorMCP::SamplingError`, or `VectorMCP::SamplingRejectedError` on failures
460
+ - Your server's advertised capabilities help clients understand what parameters are supported
461
+
462
+ #### Accessing Sampling Configuration
463
+
464
+ You can access your server's sampling configuration at runtime:
465
+
466
+ ```ruby
467
+ config = server.sampling_config
468
+ puts "Streaming supported: #{config[:supports_streaming]}"
469
+ puts "Max tokens: #{config[:max_tokens_limit] || 'unlimited'}"
470
+ puts "Timeout: #{config[:timeout_seconds]}s"
471
+ ```
472
+
473
+ ## Example Implementations
474
+
475
+ These projects demonstrate real-world implementations of VectorMCP servers:
476
+
477
+ ### [file_system_mcp](https://github.com/sergiobayona/file_system_mcp)
478
+
479
+ A complete MCP server providing filesystem operations:
480
+ - Read/write files
481
+ - Create/list/delete directories
482
+ - Move files/directories
483
+ - Search files
484
+ - Get file metadata
485
+
486
+ Works with Claude Desktop and other MCP clients.
487
+
488
+ ### Roots Demo (included)
489
+
490
+ The `examples/roots_demo.rb` script demonstrates comprehensive roots functionality:
491
+ - Multiple root registration with automatic validation
492
+ - Security boundaries and workspace context
493
+ - Tools that operate safely within registered roots
494
+ - Resources providing workspace information
495
+ - Integration with other MCP features
496
+
497
+ Run it with: `ruby examples/roots_demo.rb`
498
+
499
+ This example shows best practices for:
500
+ - Registering multiple project directories as roots
501
+ - Implementing security checks in tools
502
+ - Providing workspace context to clients
503
+ - Error handling for invalid root operations
504
+
505
+ ## Development
506
+
507
+ 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.
508
+
509
+ To install this gem onto your local machine, run `bundle exec rake install`.
510
+
511
+ ## Contributing
512
+
513
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sergiobayona/vector_mcp.
514
+
515
+ ## License
516
+
517
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).