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.
- checksums.yaml +4 -4
- data/README.md +552 -107
- data/lib/vector_mcp/definitions.rb +212 -1
- data/lib/vector_mcp/errors.rb +39 -0
- data/lib/vector_mcp/handlers/core.rb +60 -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 +313 -0
- data/lib/vector_mcp/server.rb +53 -415
- data/lib/vector_mcp/session.rb +100 -23
- data/lib/vector_mcp/transport/sse/client_connection.rb +113 -0
- data/lib/vector_mcp/transport/sse/message_handler.rb +166 -0
- data/lib/vector_mcp/transport/sse/puma_config.rb +77 -0
- data/lib/vector_mcp/transport/sse/stream_manager.rb +92 -0
- data/lib/vector_mcp/transport/sse.rb +119 -460
- 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 +26 -16
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 (
|
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
|
-
|
38
|
-
|
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(
|
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
|
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
|
68
|
+
# Start listening on STDIN/STDOUT
|
71
69
|
server.run
|
72
70
|
```
|
73
71
|
|
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**.
|
72
|
+
**To test with stdin/stdout:**
|
79
73
|
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
95
|
+
For web-based clients and browser integration, use the SSE transport:
|
104
96
|
|
105
|
-
|
97
|
+
```ruby
|
98
|
+
require 'vector_mcp'
|
106
99
|
|
107
|
-
|
108
|
-
|
109
|
-
|
100
|
+
server = VectorMCP.new(
|
101
|
+
name: 'My HTTP Server',
|
102
|
+
version: '1.0.0'
|
103
|
+
)
|
110
104
|
|
111
|
-
|
112
|
-
|
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
|
-
|
115
|
-
|
116
|
+
# Start HTTP server with SSE transport
|
117
|
+
server.run(transport: :sse, options: { port: 8080, host: 'localhost' })
|
118
|
+
```
|
116
119
|
|
117
|
-
|
118
|
-
|
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
|
-
|
129
|
-
|
163
|
+
# Using the convenience method
|
130
164
|
server = VectorMCP.new(
|
131
|
-
name: "
|
132
|
-
version: "
|
133
|
-
log_level: Logger::
|
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
|
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"
|
149
|
-
b: { type: "number"
|
197
|
+
a: { type: "number" },
|
198
|
+
b: { type: "number" }
|
150
199
|
},
|
151
200
|
required: ["a", "b"]
|
152
201
|
}
|
153
202
|
) do |args, session|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
291
|
+
Resources are data sources clients can read.
|
181
292
|
|
182
293
|
```ruby
|
183
294
|
server.register_resource(
|
184
|
-
uri: "memory://status",
|
295
|
+
uri: "memory://status",
|
185
296
|
name: "Server Status",
|
186
|
-
description: "
|
187
|
-
mime_type: "application/json"
|
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
|
193
|
-
|
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
|
-
|
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: "
|
200
|
-
name: "
|
201
|
-
description: "
|
202
|
-
mime_type: "
|
203
|
-
) do |
|
204
|
-
|
205
|
-
|
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
|
-
|
210
|
-
|
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).
|