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 +4 -4
- data/README.md +421 -114
- data/lib/vector_mcp/definitions.rb +210 -1
- data/lib/vector_mcp/errors.rb +39 -0
- data/lib/vector_mcp/handlers/core.rb +17 -0
- data/lib/vector_mcp/image_util.rb +358 -0
- data/lib/vector_mcp/sampling/request.rb +193 -0
- data/lib/vector_mcp/sampling/result.rb +80 -0
- data/lib/vector_mcp/server/capabilities.rb +156 -0
- data/lib/vector_mcp/server/message_handling.rb +166 -0
- data/lib/vector_mcp/server/registry.rb +289 -0
- data/lib/vector_mcp/server.rb +53 -415
- data/lib/vector_mcp/session.rb +100 -23
- data/lib/vector_mcp/transport/stdio.rb +174 -16
- data/lib/vector_mcp/util.rb +135 -10
- data/lib/vector_mcp/version.rb +1 -1
- data/lib/vector_mcp.rb +2 -1
- metadata +21 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5eb7e5435a5b8e67c4bd570c25432b86b0e791e24c5225ee398f693867030d9a
|
4
|
+
data.tar.gz: 9e0a8dce81c39d4912136a0985cff18b7c4bb30e5bc361d19bc81530183d53ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
38
|
-
|
32
|
+
# Or install directly
|
33
|
+
gem install vector_mcp
|
39
34
|
```
|
40
35
|
|
41
|
-
|
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(
|
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
|
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
|
69
|
+
# Start listening on STDIN/STDOUT
|
71
70
|
server.run
|
72
71
|
```
|
73
72
|
|
74
|
-
**To
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
115
|
-
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
|
81
|
+
Or use a script:
|
116
82
|
|
117
|
-
|
118
|
-
{
|
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
|
-
|
129
|
-
|
99
|
+
# Using the convenience method
|
130
100
|
server = VectorMCP.new(
|
131
|
-
name: "
|
132
|
-
version: "
|
133
|
-
log_level: Logger::
|
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
|
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"
|
149
|
-
b: { type: "number"
|
133
|
+
a: { type: "number" },
|
134
|
+
b: { type: "number" }
|
150
135
|
},
|
151
136
|
required: ["a", "b"]
|
152
137
|
}
|
153
138
|
) do |args, session|
|
154
|
-
|
155
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
153
|
+
Resources are data sources clients can read.
|
181
154
|
|
182
155
|
```ruby
|
183
156
|
server.register_resource(
|
184
|
-
uri: "memory://status",
|
157
|
+
uri: "memory://status",
|
185
158
|
name: "Server Status",
|
186
|
-
description: "
|
187
|
-
mime_type: "application/json"
|
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
|
193
|
-
|
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
|
-
|
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: "
|
200
|
-
name: "
|
201
|
-
description: "
|
202
|
-
mime_type: "
|
203
|
-
) do |
|
204
|
-
|
205
|
-
|
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
|
-
|
210
|
-
|
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).
|