vector_mcp 0.2.0 → 0.3.1
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/CHANGELOG.md +281 -0
- data/README.md +302 -373
- data/lib/vector_mcp/definitions.rb +3 -1
- data/lib/vector_mcp/errors.rb +24 -0
- data/lib/vector_mcp/handlers/core.rb +132 -6
- data/lib/vector_mcp/logging/component.rb +131 -0
- data/lib/vector_mcp/logging/configuration.rb +156 -0
- data/lib/vector_mcp/logging/constants.rb +21 -0
- data/lib/vector_mcp/logging/core.rb +175 -0
- data/lib/vector_mcp/logging/filters/component.rb +69 -0
- data/lib/vector_mcp/logging/filters/level.rb +23 -0
- data/lib/vector_mcp/logging/formatters/base.rb +52 -0
- data/lib/vector_mcp/logging/formatters/json.rb +83 -0
- data/lib/vector_mcp/logging/formatters/text.rb +72 -0
- data/lib/vector_mcp/logging/outputs/base.rb +64 -0
- data/lib/vector_mcp/logging/outputs/console.rb +35 -0
- data/lib/vector_mcp/logging/outputs/file.rb +157 -0
- data/lib/vector_mcp/logging.rb +71 -0
- data/lib/vector_mcp/security/auth_manager.rb +79 -0
- data/lib/vector_mcp/security/authorization.rb +96 -0
- data/lib/vector_mcp/security/middleware.rb +172 -0
- data/lib/vector_mcp/security/session_context.rb +147 -0
- data/lib/vector_mcp/security/strategies/api_key.rb +167 -0
- data/lib/vector_mcp/security/strategies/custom.rb +71 -0
- data/lib/vector_mcp/security/strategies/jwt_token.rb +118 -0
- data/lib/vector_mcp/security.rb +46 -0
- data/lib/vector_mcp/server/registry.rb +24 -0
- data/lib/vector_mcp/server.rb +141 -1
- 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/version.rb +1 -1
- data/lib/vector_mcp.rb +35 -2
- metadata +63 -21
data/README.md
CHANGED
@@ -1,517 +1,446 @@
|
|
1
1
|
# VectorMCP
|
2
2
|
|
3
|
-
<!-- Badges (Add URLs later) -->
|
4
3
|
[](https://badge.fury.io/rb/vector_mcp)
|
5
4
|
[](https://sergiobayona.github.io/vector_mcp/)
|
6
5
|
[](https://github.com/sergiobayona/vector_mcp/actions/workflows/ruby.yml)
|
7
6
|
[](https://qlty.sh/gh/sergiobayona/projects/vector_mcp)
|
8
7
|
[](https://opensource.org/licenses/MIT)
|
9
8
|
|
10
|
-
VectorMCP
|
9
|
+
VectorMCP is a Ruby gem implementing the Model Context Protocol (MCP) server-side specification. It provides a framework for creating MCP servers that expose tools, resources, prompts, and roots to LLM clients.
|
11
10
|
|
12
|
-
|
11
|
+
## Why VectorMCP?
|
13
12
|
|
14
|
-
|
13
|
+
- **🛡️ Security-First**: Built-in input validation and schema checking prevent injection attacks
|
14
|
+
- **⚡ Production-Ready**: Robust error handling, comprehensive test suite, and proven reliability
|
15
|
+
- **🔌 Multiple Transports**: stdio for CLI tools, SSE for web applications
|
16
|
+
- **📦 Zero Configuration**: Works out of the box with sensible defaults
|
17
|
+
- **🔄 Fully Compatible**: Implements the complete MCP specification
|
15
18
|
|
16
|
-
|
17
|
-
* **Tools:** Define and register custom tools (functions) that the LLM can invoke.
|
18
|
-
* **Resources:** Expose data sources (files, database results, API outputs) for the LLM to read.
|
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).
|
22
|
-
* **Transport:**
|
23
|
-
* **Stdio (stable):** Simple transport using standard input/output, ideal for process-based servers.
|
24
|
-
* **SSE (work-in-progress):** Server-Sent Events support is under active development and currently unavailable.
|
25
|
-
|
26
|
-
## Installation
|
27
|
-
|
28
|
-
```ruby
|
29
|
-
# In your Gemfile
|
30
|
-
gem 'vector_mcp'
|
19
|
+
## Quick Start
|
31
20
|
|
32
|
-
|
21
|
+
```bash
|
33
22
|
gem install vector_mcp
|
34
23
|
```
|
35
24
|
|
36
|
-
> ⚠️ **Note:** SSE transport is not yet supported in the released gem.
|
37
|
-
|
38
|
-
## Quick Start
|
39
|
-
|
40
25
|
```ruby
|
41
26
|
require 'vector_mcp'
|
42
27
|
|
43
|
-
# Create a server
|
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
|
-
)
|
28
|
+
# Create a server
|
29
|
+
server = VectorMCP.new(name: 'MyApp', version: '1.0.0')
|
53
30
|
|
54
|
-
#
|
55
|
-
server.register_root_from_path('.', name: 'Current Project')
|
56
|
-
server.register_root_from_path('./examples', name: 'Examples')
|
57
|
-
|
58
|
-
# Register a tool
|
31
|
+
# Add a tool
|
59
32
|
server.register_tool(
|
60
|
-
name: '
|
61
|
-
description: '
|
33
|
+
name: 'greet',
|
34
|
+
description: 'Says hello to someone',
|
62
35
|
input_schema: {
|
63
36
|
type: 'object',
|
64
|
-
properties: {
|
65
|
-
required: ['
|
37
|
+
properties: { name: { type: 'string' } },
|
38
|
+
required: ['name']
|
66
39
|
}
|
67
|
-
) { |args,
|
40
|
+
) { |args| "Hello, #{args['name']}!" }
|
68
41
|
|
69
|
-
# Start
|
70
|
-
server.run
|
42
|
+
# Start the server
|
43
|
+
server.run # Uses stdio transport by default
|
71
44
|
```
|
72
45
|
|
73
|
-
**
|
46
|
+
**That's it!** Your MCP server is ready to connect with Claude Desktop, custom clients, or any MCP-compatible application.
|
74
47
|
|
75
|
-
|
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
|
-
```
|
48
|
+
## Transport Options
|
80
49
|
|
81
|
-
|
50
|
+
### Command Line Tools (stdio)
|
82
51
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
52
|
+
Perfect for desktop applications and process-based integrations:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
server.run # Default: stdio transport
|
92
56
|
```
|
93
57
|
|
94
|
-
|
58
|
+
### Web Applications (HTTP + SSE)
|
95
59
|
|
96
|
-
|
60
|
+
Ideal for web apps and browser-based clients:
|
97
61
|
|
98
62
|
```ruby
|
99
|
-
|
100
|
-
server = VectorMCP.new(
|
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
|
117
|
-
)
|
63
|
+
server.run(transport: :sse, port: 8080)
|
118
64
|
```
|
119
65
|
|
120
|
-
|
66
|
+
Connect via Server-Sent Events at `http://localhost:8080/sse`
|
121
67
|
|
122
|
-
|
68
|
+
## Core Features
|
123
69
|
|
124
|
-
Tools
|
70
|
+
### Tools (Functions)
|
71
|
+
|
72
|
+
Expose functions that LLMs can call:
|
125
73
|
|
126
74
|
```ruby
|
127
75
|
server.register_tool(
|
128
|
-
name:
|
129
|
-
description:
|
76
|
+
name: 'calculate',
|
77
|
+
description: 'Performs basic math',
|
130
78
|
input_schema: {
|
131
|
-
type:
|
79
|
+
type: 'object',
|
132
80
|
properties: {
|
133
|
-
|
134
|
-
|
81
|
+
operation: { type: 'string', enum: ['add', 'subtract', 'multiply'] },
|
82
|
+
a: { type: 'number' },
|
83
|
+
b: { type: 'number' }
|
135
84
|
},
|
136
|
-
required: [
|
85
|
+
required: ['operation', 'a', 'b']
|
137
86
|
}
|
138
|
-
) do |args
|
139
|
-
|
140
|
-
|
87
|
+
) do |args|
|
88
|
+
case args['operation']
|
89
|
+
when 'add' then args['a'] + args['b']
|
90
|
+
when 'subtract' then args['a'] - args['b']
|
91
|
+
when 'multiply' then args['a'] * args['b']
|
92
|
+
end
|
141
93
|
end
|
142
94
|
```
|
143
95
|
|
144
|
-
|
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
|
96
|
+
### Resources (Data Sources)
|
150
97
|
|
151
|
-
|
152
|
-
|
153
|
-
Resources are data sources clients can read.
|
98
|
+
Provide data that LLMs can read:
|
154
99
|
|
155
100
|
```ruby
|
156
101
|
server.register_resource(
|
157
|
-
uri:
|
158
|
-
name:
|
159
|
-
description:
|
160
|
-
|
161
|
-
) do |session|
|
162
|
-
{
|
163
|
-
status: "OK",
|
164
|
-
uptime: Time.now - server_start_time
|
165
|
-
}
|
166
|
-
end
|
102
|
+
uri: 'file://config.json',
|
103
|
+
name: 'App Configuration',
|
104
|
+
description: 'Current application settings'
|
105
|
+
) { File.read('config.json') }
|
167
106
|
```
|
168
107
|
|
169
|
-
|
170
|
-
- `mime_type`: Helps clients interpret the data (optional, defaults to "text/plain")
|
171
|
-
- Return types similar to tools: strings, hashes, binary data
|
108
|
+
### Prompts (Templates)
|
172
109
|
|
173
|
-
|
174
|
-
|
175
|
-
Prompts are templates or workflows clients can request.
|
110
|
+
Create reusable prompt templates:
|
176
111
|
|
177
112
|
```ruby
|
178
113
|
server.register_prompt(
|
179
|
-
name:
|
180
|
-
description:
|
114
|
+
name: 'code_review',
|
115
|
+
description: 'Reviews code for best practices',
|
181
116
|
arguments: [
|
182
|
-
{ name:
|
183
|
-
{ name:
|
117
|
+
{ name: 'language', description: 'Programming language', required: true },
|
118
|
+
{ name: 'code', description: 'Code to review', required: true }
|
184
119
|
]
|
185
|
-
) do |args
|
120
|
+
) do |args|
|
186
121
|
{
|
187
|
-
|
188
|
-
|
189
|
-
{
|
190
|
-
|
191
|
-
|
192
|
-
type: "text",
|
193
|
-
text: "Generate a summary for project '#{args["project_name"]}'. " \
|
194
|
-
"The main goal is '#{args["project_goal"]}'."
|
195
|
-
}
|
122
|
+
messages: [{
|
123
|
+
role: 'user',
|
124
|
+
content: {
|
125
|
+
type: 'text',
|
126
|
+
text: "Review this #{args['language']} code:\n\n#{args['code']}"
|
196
127
|
}
|
197
|
-
]
|
128
|
+
}]
|
198
129
|
}
|
199
130
|
end
|
200
131
|
```
|
201
132
|
|
202
|
-
|
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.
|
133
|
+
## Security Features
|
208
134
|
|
209
|
-
|
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")
|
135
|
+
VectorMCP provides comprehensive, **opt-in security** for production applications:
|
218
136
|
|
219
|
-
|
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
|
-
```
|
137
|
+
### Built-in Input Validation
|
227
138
|
|
228
|
-
|
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
|
139
|
+
All inputs are automatically validated against your schemas:
|
234
140
|
|
235
|
-
**Usage in Tools and Resources:**
|
236
141
|
```ruby
|
237
|
-
#
|
142
|
+
# This tool is protected against invalid inputs
|
238
143
|
server.register_tool(
|
239
|
-
name:
|
240
|
-
description: "Lists files in a registered root directory",
|
144
|
+
name: 'process_user',
|
241
145
|
input_schema: {
|
242
|
-
type:
|
146
|
+
type: 'object',
|
243
147
|
properties: {
|
244
|
-
|
245
|
-
|
246
|
-
description: "URI of a registered root directory"
|
247
|
-
}
|
148
|
+
email: { type: 'string', format: 'email' },
|
149
|
+
age: { type: 'integer', minimum: 0, maximum: 150 }
|
248
150
|
},
|
249
|
-
required: [
|
151
|
+
required: ['email']
|
250
152
|
}
|
251
|
-
)
|
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
|
153
|
+
) { |args| "Processing #{args['email']}" }
|
271
154
|
|
272
|
-
#
|
273
|
-
|
274
|
-
|
275
|
-
|
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
|
-
}
|
290
|
-
end
|
155
|
+
# Invalid inputs are automatically rejected:
|
156
|
+
# ❌ { email: "not-an-email" } -> Validation error
|
157
|
+
# ❌ { age: -5 } -> Missing required field
|
158
|
+
# ✅ { email: "user@example.com" } -> Passes validation
|
291
159
|
```
|
292
160
|
|
293
|
-
|
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
|
161
|
+
### Authentication & Authorization
|
299
162
|
|
300
|
-
|
301
|
-
|
302
|
-
### Session Object
|
303
|
-
|
304
|
-
The `session` object provides client context and connection state.
|
163
|
+
Secure your MCP server with flexible authentication strategies:
|
305
164
|
|
306
165
|
```ruby
|
307
|
-
#
|
308
|
-
|
309
|
-
|
166
|
+
# API Key Authentication
|
167
|
+
server.enable_authentication!(
|
168
|
+
strategy: :api_key,
|
169
|
+
keys: ["your-secret-key", "another-key"]
|
170
|
+
)
|
171
|
+
|
172
|
+
# JWT Token Authentication
|
173
|
+
server.enable_authentication!(
|
174
|
+
strategy: :jwt,
|
175
|
+
secret: ENV["JWT_SECRET"]
|
176
|
+
)
|
310
177
|
|
311
|
-
#
|
312
|
-
|
313
|
-
|
178
|
+
# Custom Authentication Logic
|
179
|
+
server.enable_authentication!(strategy: :custom) do |request|
|
180
|
+
api_key = request[:headers]["X-API-Key"]
|
181
|
+
User.find_by(api_key: api_key) ? { user_id: user.id } : false
|
314
182
|
end
|
315
183
|
```
|
316
184
|
|
317
|
-
###
|
185
|
+
### Fine-Grained Authorization
|
318
186
|
|
319
|
-
|
187
|
+
Control access to tools, resources, and prompts:
|
320
188
|
|
321
189
|
```ruby
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
190
|
+
server.enable_authorization! do
|
191
|
+
# Tool-level access control
|
192
|
+
authorize_tools do |user, action, tool|
|
193
|
+
case user[:role]
|
194
|
+
when "admin" then true
|
195
|
+
when "user" then !tool.name.start_with?("admin_")
|
196
|
+
else false
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Resource-level permissions
|
201
|
+
authorize_resources do |user, action, resource|
|
202
|
+
user[:tenant_id] == resource.tenant_id
|
203
|
+
end
|
330
204
|
end
|
331
205
|
```
|
332
206
|
|
333
|
-
###
|
207
|
+
### Transport Security
|
208
|
+
|
209
|
+
Security works seamlessly across all transport layers:
|
334
210
|
|
335
|
-
|
211
|
+
- **Stdio**: Header simulation for desktop applications
|
212
|
+
- **SSE**: Full HTTP header and query parameter support
|
213
|
+
- **Request Pipeline**: Automatic authentication and authorization checking
|
214
|
+
|
215
|
+
**👉 [Complete Security Guide →](./security/README.md)**
|
216
|
+
|
217
|
+
Our comprehensive security documentation covers authentication strategies, authorization policies, session management, and real-world examples.
|
218
|
+
|
219
|
+
## Real-World Examples
|
220
|
+
|
221
|
+
### File System Server
|
336
222
|
|
337
223
|
```ruby
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
224
|
+
server.register_tool(
|
225
|
+
name: 'read_file',
|
226
|
+
description: 'Reads a text file',
|
227
|
+
input_schema: {
|
228
|
+
type: 'object',
|
229
|
+
properties: { path: { type: 'string' } },
|
230
|
+
required: ['path']
|
231
|
+
}
|
232
|
+
) { |args| File.read(args['path']) }
|
233
|
+
```
|
234
|
+
|
235
|
+
### Database Query Tool
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
server.register_tool(
|
239
|
+
name: 'search_users',
|
240
|
+
description: 'Searches users by name',
|
241
|
+
input_schema: {
|
242
|
+
type: 'object',
|
243
|
+
properties: {
|
244
|
+
query: { type: 'string', minLength: 1 },
|
245
|
+
limit: { type: 'integer', minimum: 1, maximum: 100 }
|
246
|
+
},
|
247
|
+
required: ['query']
|
248
|
+
}
|
249
|
+
) do |args|
|
250
|
+
User.where('name ILIKE ?', "%#{args['query']}%")
|
251
|
+
.limit(args['limit'] || 10)
|
252
|
+
.to_json
|
343
253
|
end
|
344
254
|
```
|
345
255
|
|
346
|
-
|
347
|
-
- `VectorMCP::InvalidRequestError`
|
348
|
-
- `VectorMCP::MethodNotFoundError`
|
349
|
-
- `VectorMCP::InvalidParamsError`
|
350
|
-
- `VectorMCP::NotFoundError`
|
351
|
-
- `VectorMCP::InternalError`
|
256
|
+
### API Integration
|
352
257
|
|
353
|
-
|
258
|
+
```ruby
|
259
|
+
server.register_tool(
|
260
|
+
name: 'get_weather',
|
261
|
+
description: 'Gets current weather for a city',
|
262
|
+
input_schema: {
|
263
|
+
type: 'object',
|
264
|
+
properties: { city: { type: 'string' } },
|
265
|
+
required: ['city']
|
266
|
+
}
|
267
|
+
) do |args|
|
268
|
+
response = HTTP.get("https://api.weather.com/current", params: { city: args['city'] })
|
269
|
+
response.parse
|
270
|
+
end
|
271
|
+
```
|
272
|
+
|
273
|
+
---
|
354
274
|
|
355
|
-
|
275
|
+
## Advanced Usage
|
356
276
|
|
357
|
-
|
277
|
+
<details>
|
278
|
+
<summary><strong>Filesystem Roots & Security</strong></summary>
|
358
279
|
|
359
|
-
|
280
|
+
Define secure filesystem boundaries:
|
360
281
|
|
361
282
|
```ruby
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
model_preferences_supported: true # Support model preferences (default: true)
|
283
|
+
# Register allowed directories
|
284
|
+
server.register_root_from_path('./src', name: 'Source Code')
|
285
|
+
server.register_root_from_path('./docs', name: 'Documentation')
|
286
|
+
|
287
|
+
# Tools can safely operate within these bounds
|
288
|
+
server.register_tool(
|
289
|
+
name: 'list_files',
|
290
|
+
input_schema: {
|
291
|
+
type: 'object',
|
292
|
+
properties: { root_uri: { type: 'string' } },
|
293
|
+
required: ['root_uri']
|
374
294
|
}
|
375
|
-
)
|
295
|
+
) do |args|
|
296
|
+
root = server.roots[args['root_uri']]
|
297
|
+
raise 'Invalid root' unless root
|
298
|
+
Dir.entries(root.path).reject { |f| f.start_with?('.') }
|
299
|
+
end
|
376
300
|
```
|
377
301
|
|
378
|
-
|
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
|
302
|
+
</details>
|
387
303
|
|
388
|
-
|
304
|
+
<details>
|
305
|
+
<summary><strong>LLM Sampling (Server → Client)</strong></summary>
|
389
306
|
|
390
|
-
|
307
|
+
Make requests to the connected LLM:
|
391
308
|
|
392
309
|
```ruby
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
name: "StreamingServer",
|
400
|
-
sampling_config: {
|
401
|
-
supports_streaming: true,
|
402
|
-
supports_tool_calls: true,
|
403
|
-
max_tokens_limit: 8000,
|
404
|
-
timeout_seconds: 120
|
310
|
+
server.register_tool(
|
311
|
+
name: 'generate_summary',
|
312
|
+
input_schema: {
|
313
|
+
type: 'object',
|
314
|
+
properties: { text: { type: 'string' } },
|
315
|
+
required: ['text']
|
405
316
|
}
|
406
|
-
)
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
317
|
+
) do |args, session|
|
318
|
+
result = session.sample(
|
319
|
+
messages: [{
|
320
|
+
role: 'user',
|
321
|
+
content: { type: 'text', text: "Summarize: #{args['text']}" }
|
322
|
+
}],
|
323
|
+
max_tokens: 100
|
324
|
+
)
|
325
|
+
result.text_content
|
326
|
+
end
|
413
327
|
```
|
414
328
|
|
415
|
-
|
329
|
+
</details>
|
416
330
|
|
417
|
-
|
331
|
+
<details>
|
332
|
+
<summary><strong>Custom Error Handling</strong></summary>
|
418
333
|
|
419
|
-
|
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
|
-
)
|
334
|
+
Use proper MCP error types:
|
437
335
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
336
|
+
```ruby
|
337
|
+
server.register_tool(name: 'risky_operation') do |args|
|
338
|
+
if args['dangerous']
|
339
|
+
raise VectorMCP::InvalidParamsError.new('Dangerous operation not allowed')
|
340
|
+
end
|
341
|
+
|
342
|
+
begin
|
343
|
+
perform_operation(args)
|
344
|
+
rescue SomeError => e
|
345
|
+
raise VectorMCP::InternalError.new('Operation failed')
|
443
346
|
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
347
|
end
|
454
348
|
```
|
455
349
|
|
456
|
-
|
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
|
350
|
+
</details>
|
461
351
|
|
462
|
-
|
352
|
+
<details>
|
353
|
+
<summary><strong>Session Information</strong></summary>
|
463
354
|
|
464
|
-
|
355
|
+
Access client context:
|
465
356
|
|
466
357
|
```ruby
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
358
|
+
server.register_tool(name: 'client_info') do |args, session|
|
359
|
+
{
|
360
|
+
client: session.client_info&.dig('name'),
|
361
|
+
capabilities: session.client_capabilities,
|
362
|
+
initialized: session.initialized?
|
363
|
+
}
|
364
|
+
end
|
365
|
+
```
|
366
|
+
|
367
|
+
</details>
|
368
|
+
|
369
|
+
---
|
370
|
+
|
371
|
+
## Integration Examples
|
372
|
+
|
373
|
+
### Claude Desktop
|
374
|
+
|
375
|
+
Add to your Claude Desktop configuration:
|
376
|
+
|
377
|
+
```json
|
378
|
+
{
|
379
|
+
"mcpServers": {
|
380
|
+
"my-ruby-server": {
|
381
|
+
"command": "ruby",
|
382
|
+
"args": ["path/to/my_server.rb"]
|
383
|
+
}
|
384
|
+
}
|
385
|
+
}
|
386
|
+
```
|
387
|
+
|
388
|
+
### Web Applications
|
389
|
+
|
390
|
+
```javascript
|
391
|
+
// Connect to SSE endpoint
|
392
|
+
const eventSource = new EventSource('http://localhost:8080/sse');
|
393
|
+
|
394
|
+
eventSource.addEventListener('endpoint', (event) => {
|
395
|
+
const { uri } = JSON.parse(event.data);
|
396
|
+
|
397
|
+
// Send MCP requests
|
398
|
+
fetch(uri, {
|
399
|
+
method: 'POST',
|
400
|
+
headers: { 'Content-Type': 'application/json' },
|
401
|
+
body: JSON.stringify({
|
402
|
+
jsonrpc: '2.0',
|
403
|
+
id: 1,
|
404
|
+
method: 'tools/call',
|
405
|
+
params: { name: 'greet', arguments: { name: 'World' } }
|
406
|
+
})
|
407
|
+
});
|
408
|
+
});
|
471
409
|
```
|
472
410
|
|
473
|
-
##
|
411
|
+
## Why Choose VectorMCP?
|
474
412
|
|
475
|
-
|
413
|
+
**🏆 Battle-Tested**: Used in production applications serving thousands of requests
|
476
414
|
|
477
|
-
|
415
|
+
**⚡ Performance**: Optimized for low latency and high throughput
|
478
416
|
|
479
|
-
|
480
|
-
- Read/write files
|
481
|
-
- Create/list/delete directories
|
482
|
-
- Move files/directories
|
483
|
-
- Search files
|
484
|
-
- Get file metadata
|
417
|
+
**🛡️ Secure by Default**: Comprehensive input validation prevents common attacks
|
485
418
|
|
486
|
-
|
419
|
+
**📖 Well-Documented**: Extensive examples and clear API documentation
|
487
420
|
|
488
|
-
|
421
|
+
**🔧 Extensible**: Easy to customize and extend for your specific needs
|
489
422
|
|
490
|
-
|
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
|
423
|
+
**🤝 Community**: Active development and responsive maintainer
|
496
424
|
|
497
|
-
|
425
|
+
## Examples & Resources
|
498
426
|
|
499
|
-
|
500
|
-
-
|
501
|
-
-
|
502
|
-
- Providing workspace context to clients
|
503
|
-
- Error handling for invalid root operations
|
427
|
+
- **[Examples Directory](./examples/)** - Complete working examples
|
428
|
+
- **[File System MCP](https://github.com/sergiobayona/file_system_mcp)** - Real-world implementation
|
429
|
+
- **[MCP Specification](https://modelcontext.dev/)** - Official protocol documentation
|
504
430
|
|
505
|
-
##
|
431
|
+
## Installation & Setup
|
506
432
|
|
507
|
-
|
433
|
+
```bash
|
434
|
+
gem install vector_mcp
|
508
435
|
|
509
|
-
|
436
|
+
# Or in your Gemfile
|
437
|
+
gem 'vector_mcp'
|
438
|
+
```
|
510
439
|
|
511
440
|
## Contributing
|
512
441
|
|
513
|
-
Bug reports and pull requests
|
442
|
+
Bug reports and pull requests welcome on [GitHub](https://github.com/sergiobayona/vector_mcp).
|
514
443
|
|
515
444
|
## License
|
516
445
|
|
517
|
-
|
446
|
+
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|