vector_mcp 0.3.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 +292 -501
- data/lib/vector_mcp/errors.rb +24 -0
- data/lib/vector_mcp/handlers/core.rb +89 -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.rb +141 -1
- data/lib/vector_mcp/version.rb +1 -1
- data/lib/vector_mcp.rb +35 -2
- metadata +55 -3
data/README.md
CHANGED
@@ -1,655 +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 (stable):** Server-Sent Events over HTTP, enabling web-based MCP clients and browser integration.
|
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
|
-
|
37
|
-
## Quick Start
|
38
|
-
|
39
25
|
```ruby
|
40
26
|
require 'vector_mcp'
|
41
27
|
|
42
|
-
# Create a server
|
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')
|
28
|
+
# Create a server
|
29
|
+
server = VectorMCP.new(name: 'MyApp', version: '1.0.0')
|
56
30
|
|
57
|
-
#
|
31
|
+
# Add a tool
|
58
32
|
server.register_tool(
|
59
|
-
name: '
|
60
|
-
description: '
|
33
|
+
name: 'greet',
|
34
|
+
description: 'Says hello to someone',
|
61
35
|
input_schema: {
|
62
36
|
type: 'object',
|
63
|
-
properties: {
|
64
|
-
required: ['
|
37
|
+
properties: { name: { type: 'string' } },
|
38
|
+
required: ['name']
|
65
39
|
}
|
66
|
-
) { |args,
|
40
|
+
) { |args| "Hello, #{args['name']}!" }
|
67
41
|
|
68
|
-
# Start
|
69
|
-
server.run
|
42
|
+
# Start the server
|
43
|
+
server.run # Uses stdio transport by default
|
70
44
|
```
|
71
45
|
|
72
|
-
**
|
73
|
-
|
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
|
-
```
|
46
|
+
**That's it!** Your MCP server is ready to connect with Claude Desktop, custom clients, or any MCP-compatible application.
|
79
47
|
|
80
|
-
|
48
|
+
## Transport Options
|
81
49
|
|
82
|
-
|
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
|
-
```
|
50
|
+
### Command Line Tools (stdio)
|
92
51
|
|
93
|
-
|
94
|
-
|
95
|
-
For web-based clients and browser integration, use the SSE transport:
|
52
|
+
Perfect for desktop applications and process-based integrations:
|
96
53
|
|
97
54
|
```ruby
|
98
|
-
|
55
|
+
server.run # Default: stdio transport
|
56
|
+
```
|
99
57
|
|
100
|
-
|
101
|
-
name: 'My HTTP Server',
|
102
|
-
version: '1.0.0'
|
103
|
-
)
|
58
|
+
### Web Applications (HTTP + SSE)
|
104
59
|
|
105
|
-
|
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'] }
|
60
|
+
Ideal for web apps and browser-based clients:
|
115
61
|
|
116
|
-
|
117
|
-
server.run(transport: :sse,
|
62
|
+
```ruby
|
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
|
-
|
123
|
-
* **Messages:** `POST /message?session_id=<id>` - Sends JSON-RPC requests
|
68
|
+
## Core Features
|
124
69
|
|
125
|
-
|
70
|
+
### Tools (Functions)
|
126
71
|
|
127
|
-
|
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
|
-
});
|
72
|
+
Expose functions that LLMs can call:
|
151
73
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
74
|
+
```ruby
|
75
|
+
server.register_tool(
|
76
|
+
name: 'calculate',
|
77
|
+
description: 'Performs basic math',
|
78
|
+
input_schema: {
|
79
|
+
type: 'object',
|
80
|
+
properties: {
|
81
|
+
operation: { type: 'string', enum: ['add', 'subtract', 'multiply'] },
|
82
|
+
a: { type: 'number' },
|
83
|
+
b: { type: 'number' }
|
84
|
+
},
|
85
|
+
required: ['operation', 'a', 'b']
|
86
|
+
}
|
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
|
93
|
+
end
|
156
94
|
```
|
157
95
|
|
158
|
-
|
96
|
+
### Resources (Data Sources)
|
159
97
|
|
160
|
-
|
98
|
+
Provide data that LLMs can read:
|
161
99
|
|
162
100
|
```ruby
|
163
|
-
|
164
|
-
|
165
|
-
name:
|
166
|
-
|
167
|
-
|
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
|
181
|
-
)
|
101
|
+
server.register_resource(
|
102
|
+
uri: 'file://config.json',
|
103
|
+
name: 'App Configuration',
|
104
|
+
description: 'Current application settings'
|
105
|
+
) { File.read('config.json') }
|
182
106
|
```
|
183
107
|
|
184
|
-
|
108
|
+
### Prompts (Templates)
|
185
109
|
|
186
|
-
|
187
|
-
|
188
|
-
Tools are functions your server exposes to clients.
|
110
|
+
Create reusable prompt templates:
|
189
111
|
|
190
112
|
```ruby
|
191
|
-
server.
|
192
|
-
name:
|
193
|
-
description:
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
113
|
+
server.register_prompt(
|
114
|
+
name: 'code_review',
|
115
|
+
description: 'Reviews code for best practices',
|
116
|
+
arguments: [
|
117
|
+
{ name: 'language', description: 'Programming language', required: true },
|
118
|
+
{ name: 'code', description: 'Code to review', required: true }
|
119
|
+
]
|
120
|
+
) do |args|
|
121
|
+
{
|
122
|
+
messages: [{
|
123
|
+
role: 'user',
|
124
|
+
content: {
|
125
|
+
type: 'text',
|
126
|
+
text: "Review this #{args['language']} code:\n\n#{args['code']}"
|
127
|
+
}
|
128
|
+
}]
|
201
129
|
}
|
202
|
-
) do |args, session|
|
203
|
-
sum = args["a"] + args["b"]
|
204
|
-
"The sum is: #{sum}"
|
205
130
|
end
|
206
131
|
```
|
207
132
|
|
208
|
-
|
133
|
+
## Security Features
|
209
134
|
|
210
|
-
VectorMCP
|
135
|
+
VectorMCP provides comprehensive, **opt-in security** for production applications:
|
211
136
|
|
212
|
-
-
|
213
|
-
|
214
|
-
|
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
|
137
|
+
### Built-in Input Validation
|
138
|
+
|
139
|
+
All inputs are automatically validated against your schemas:
|
217
140
|
|
218
141
|
```ruby
|
219
|
-
#
|
142
|
+
# This tool is protected against invalid inputs
|
220
143
|
server.register_tool(
|
221
|
-
name:
|
222
|
-
description: "Processes user information with strict validation",
|
144
|
+
name: 'process_user',
|
223
145
|
input_schema: {
|
224
|
-
type:
|
146
|
+
type: 'object',
|
225
147
|
properties: {
|
226
|
-
|
227
|
-
|
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
|
-
}
|
148
|
+
email: { type: 'string', format: 'email' },
|
149
|
+
age: { type: 'integer', minimum: 0, maximum: 150 }
|
253
150
|
},
|
254
|
-
required: [
|
255
|
-
additionalProperties: false
|
151
|
+
required: ['email']
|
256
152
|
}
|
257
|
-
)
|
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']})"
|
266
|
-
end
|
267
|
-
```
|
268
|
-
|
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
|
153
|
+
) { |args| "Processing #{args['email']}" }
|
274
154
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
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
|
159
|
+
```
|
279
160
|
|
280
|
-
|
161
|
+
### Authentication & Authorization
|
281
162
|
|
282
|
-
|
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
|
163
|
+
Secure your MCP server with flexible authentication strategies:
|
288
164
|
|
289
|
-
|
165
|
+
```ruby
|
166
|
+
# API Key Authentication
|
167
|
+
server.enable_authentication!(
|
168
|
+
strategy: :api_key,
|
169
|
+
keys: ["your-secret-key", "another-key"]
|
170
|
+
)
|
290
171
|
|
291
|
-
|
172
|
+
# JWT Token Authentication
|
173
|
+
server.enable_authentication!(
|
174
|
+
strategy: :jwt,
|
175
|
+
secret: ENV["JWT_SECRET"]
|
176
|
+
)
|
292
177
|
|
293
|
-
|
294
|
-
server.
|
295
|
-
|
296
|
-
|
297
|
-
description: "Current server status.",
|
298
|
-
mime_type: "application/json"
|
299
|
-
) do |session|
|
300
|
-
{
|
301
|
-
status: "OK",
|
302
|
-
uptime: Time.now - server_start_time
|
303
|
-
}
|
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
|
304
182
|
end
|
305
183
|
```
|
306
184
|
|
307
|
-
-
|
308
|
-
- `mime_type`: Helps clients interpret the data (optional, defaults to "text/plain")
|
309
|
-
- Return types similar to tools: strings, hashes, binary data
|
310
|
-
|
311
|
-
### Registering Prompts
|
185
|
+
### Fine-Grained Authorization
|
312
186
|
|
313
|
-
|
187
|
+
Control access to tools, resources, and prompts:
|
314
188
|
|
315
189
|
```ruby
|
316
|
-
server.
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
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
|
-
}
|
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
|
337
204
|
end
|
338
205
|
```
|
339
206
|
|
340
|
-
|
341
|
-
- Return a Hash conforming to the MCP `GetPromptResult` schema with a `messages` array
|
207
|
+
### Transport Security
|
342
208
|
|
343
|
-
|
209
|
+
Security works seamlessly across all transport layers:
|
344
210
|
|
345
|
-
|
211
|
+
- **Stdio**: Header simulation for desktop applications
|
212
|
+
- **SSE**: Full HTTP header and query parameter support
|
213
|
+
- **Request Pipeline**: Automatic authentication and authorization checking
|
346
214
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
uri: "file:///home/user/projects/myapp",
|
351
|
-
name: "My Application"
|
352
|
-
)
|
215
|
+
**👉 [Complete Security Guide →](./security/README.md)**
|
216
|
+
|
217
|
+
Our comprehensive security documentation covers authentication strategies, authorization policies, session management, and real-world examples.
|
353
218
|
|
354
|
-
|
355
|
-
server.register_root_from_path("/home/user/projects/frontend", name: "Frontend Code")
|
219
|
+
## Real-World Examples
|
356
220
|
|
357
|
-
|
358
|
-
server.register_root_from_path(".", name: "Current Project")
|
221
|
+
### File System Server
|
359
222
|
|
360
|
-
|
361
|
-
server.
|
362
|
-
|
363
|
-
|
223
|
+
```ruby
|
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']) }
|
364
233
|
```
|
365
234
|
|
366
|
-
|
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
|
235
|
+
### Database Query Tool
|
372
236
|
|
373
|
-
**Usage in Tools and Resources:**
|
374
237
|
```ruby
|
375
|
-
# Tool that operates within registered roots
|
376
238
|
server.register_tool(
|
377
|
-
name:
|
378
|
-
description:
|
239
|
+
name: 'search_users',
|
240
|
+
description: 'Searches users by name',
|
379
241
|
input_schema: {
|
380
|
-
type:
|
381
|
-
properties: {
|
382
|
-
|
383
|
-
|
384
|
-
description: "URI of a registered root directory"
|
385
|
-
}
|
242
|
+
type: 'object',
|
243
|
+
properties: {
|
244
|
+
query: { type: 'string', minLength: 1 },
|
245
|
+
limit: { type: 'integer', minimum: 1, maximum: 100 }
|
386
246
|
},
|
387
|
-
required: [
|
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
|
247
|
+
required: ['query']
|
407
248
|
}
|
249
|
+
) do |args|
|
250
|
+
User.where('name ILIKE ?', "%#{args['query']}%")
|
251
|
+
.limit(args['limit'] || 10)
|
252
|
+
.to_json
|
408
253
|
end
|
254
|
+
```
|
409
255
|
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
{
|
421
|
-
uri: uri,
|
422
|
-
name: root.name,
|
423
|
-
path: root.path,
|
424
|
-
accessible: File.readable?(root.path)
|
425
|
-
}
|
426
|
-
end
|
256
|
+
### API Integration
|
257
|
+
|
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']
|
427
266
|
}
|
267
|
+
) do |args|
|
268
|
+
response = HTTP.get("https://api.weather.com/current", params: { city: args['city'] })
|
269
|
+
response.parse
|
428
270
|
end
|
429
271
|
```
|
430
272
|
|
431
|
-
|
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
|
273
|
+
---
|
437
274
|
|
438
|
-
## Advanced
|
275
|
+
## Advanced Usage
|
439
276
|
|
440
|
-
|
277
|
+
<details>
|
278
|
+
<summary><strong>Filesystem Roots & Security</strong></summary>
|
441
279
|
|
442
|
-
|
280
|
+
Define secure filesystem boundaries:
|
443
281
|
|
444
282
|
```ruby
|
445
|
-
#
|
446
|
-
|
447
|
-
|
283
|
+
# Register allowed directories
|
284
|
+
server.register_root_from_path('./src', name: 'Source Code')
|
285
|
+
server.register_root_from_path('./docs', name: 'Documentation')
|
448
286
|
|
449
|
-
#
|
450
|
-
|
451
|
-
|
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']
|
294
|
+
}
|
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?('.') }
|
452
299
|
end
|
453
300
|
```
|
454
301
|
|
455
|
-
|
302
|
+
</details>
|
456
303
|
|
457
|
-
|
304
|
+
<details>
|
305
|
+
<summary><strong>LLM Sampling (Server → Client)</strong></summary>
|
458
306
|
|
459
|
-
|
460
|
-
# Custom request handler
|
461
|
-
server.on_request("my_server/status") do |params, session, server|
|
462
|
-
{ status: "OK", server_name: server.name }
|
463
|
-
end
|
307
|
+
Make requests to the connected LLM:
|
464
308
|
|
465
|
-
|
466
|
-
server.
|
467
|
-
|
309
|
+
```ruby
|
310
|
+
server.register_tool(
|
311
|
+
name: 'generate_summary',
|
312
|
+
input_schema: {
|
313
|
+
type: 'object',
|
314
|
+
properties: { text: { type: 'string' } },
|
315
|
+
required: ['text']
|
316
|
+
}
|
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
|
468
326
|
end
|
469
327
|
```
|
470
328
|
|
471
|
-
|
329
|
+
</details>
|
330
|
+
|
331
|
+
<details>
|
332
|
+
<summary><strong>Custom Error Handling</strong></summary>
|
472
333
|
|
473
|
-
Use proper
|
334
|
+
Use proper MCP error types:
|
474
335
|
|
475
336
|
```ruby
|
476
|
-
|
477
|
-
if
|
478
|
-
|
479
|
-
|
480
|
-
|
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')
|
346
|
+
end
|
481
347
|
end
|
482
348
|
```
|
483
349
|
|
484
|
-
|
485
|
-
- `VectorMCP::InvalidRequestError`
|
486
|
-
- `VectorMCP::MethodNotFoundError`
|
487
|
-
- `VectorMCP::InvalidParamsError`
|
488
|
-
- `VectorMCP::NotFoundError`
|
489
|
-
- `VectorMCP::InternalError`
|
350
|
+
</details>
|
490
351
|
|
491
|
-
|
352
|
+
<details>
|
353
|
+
<summary><strong>Session Information</strong></summary>
|
492
354
|
|
493
|
-
|
494
|
-
|
495
|
-
#### Configuring Sampling Capabilities
|
496
|
-
|
497
|
-
You can configure your server's sampling capabilities during initialization to advertise what features your server supports:
|
355
|
+
Access client context:
|
498
356
|
|
499
357
|
```ruby
|
500
|
-
server
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
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)
|
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?
|
512
363
|
}
|
513
|
-
|
364
|
+
end
|
514
365
|
```
|
515
366
|
|
516
|
-
|
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
|
367
|
+
</details>
|
525
368
|
|
526
|
-
|
369
|
+
---
|
527
370
|
|
528
|
-
|
371
|
+
## Integration Examples
|
529
372
|
|
530
|
-
|
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
|
-
```
|
373
|
+
### Claude Desktop
|
552
374
|
|
553
|
-
|
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
|
-
)
|
375
|
+
Add to your Claude Desktop configuration:
|
575
376
|
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
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
|
377
|
+
```json
|
378
|
+
{
|
379
|
+
"mcpServers": {
|
380
|
+
"my-ruby-server": {
|
381
|
+
"command": "ruby",
|
382
|
+
"args": ["path/to/my_server.rb"]
|
383
|
+
}
|
384
|
+
}
|
385
|
+
}
|
592
386
|
```
|
593
387
|
|
594
|
-
|
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
|
388
|
+
### Web Applications
|
599
389
|
|
600
|
-
|
601
|
-
|
602
|
-
|
390
|
+
```javascript
|
391
|
+
// Connect to SSE endpoint
|
392
|
+
const eventSource = new EventSource('http://localhost:8080/sse');
|
603
393
|
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
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
|
+
});
|
609
409
|
```
|
610
410
|
|
611
|
-
##
|
411
|
+
## Why Choose VectorMCP?
|
612
412
|
|
613
|
-
|
413
|
+
**🏆 Battle-Tested**: Used in production applications serving thousands of requests
|
614
414
|
|
615
|
-
|
415
|
+
**⚡ Performance**: Optimized for low latency and high throughput
|
616
416
|
|
617
|
-
|
618
|
-
- Read/write files
|
619
|
-
- Create/list/delete directories
|
620
|
-
- Move files/directories
|
621
|
-
- Search files
|
622
|
-
- Get file metadata
|
417
|
+
**🛡️ Secure by Default**: Comprehensive input validation prevents common attacks
|
623
418
|
|
624
|
-
|
419
|
+
**📖 Well-Documented**: Extensive examples and clear API documentation
|
625
420
|
|
626
|
-
|
421
|
+
**🔧 Extensible**: Easy to customize and extend for your specific needs
|
627
422
|
|
628
|
-
|
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
|
423
|
+
**🤝 Community**: Active development and responsive maintainer
|
634
424
|
|
635
|
-
|
425
|
+
## Examples & Resources
|
636
426
|
|
637
|
-
|
638
|
-
-
|
639
|
-
-
|
640
|
-
- Providing workspace context to clients
|
641
|
-
- 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
|
642
430
|
|
643
|
-
##
|
431
|
+
## Installation & Setup
|
644
432
|
|
645
|
-
|
433
|
+
```bash
|
434
|
+
gem install vector_mcp
|
646
435
|
|
647
|
-
|
436
|
+
# Or in your Gemfile
|
437
|
+
gem 'vector_mcp'
|
438
|
+
```
|
648
439
|
|
649
440
|
## Contributing
|
650
441
|
|
651
|
-
Bug reports and pull requests
|
442
|
+
Bug reports and pull requests welcome on [GitHub](https://github.com/sergiobayona/vector_mcp).
|
652
443
|
|
653
444
|
## License
|
654
445
|
|
655
|
-
|
446
|
+
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|