vector_mcp 0.3.4 → 0.5.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/CHANGELOG.md +82 -0
- data/README.md +147 -337
- data/lib/vector_mcp/definitions.rb +30 -0
- data/lib/vector_mcp/handlers/core.rb +78 -81
- data/lib/vector_mcp/image_util.rb +34 -11
- data/lib/vector_mcp/middleware/anonymizer.rb +186 -0
- data/lib/vector_mcp/middleware/base.rb +1 -5
- data/lib/vector_mcp/middleware/context.rb +11 -1
- data/lib/vector_mcp/middleware/hook.rb +7 -24
- data/lib/vector_mcp/middleware.rb +26 -9
- data/lib/vector_mcp/rails/tool.rb +85 -0
- data/lib/vector_mcp/request_context.rb +1 -1
- data/lib/vector_mcp/security/auth_manager.rb +12 -13
- data/lib/vector_mcp/security/auth_result.rb +33 -0
- data/lib/vector_mcp/security/authorization.rb +5 -9
- data/lib/vector_mcp/security/middleware.rb +2 -2
- data/lib/vector_mcp/security/session_context.rb +11 -27
- data/lib/vector_mcp/security/strategies/api_key.rb +1 -5
- data/lib/vector_mcp/security/strategies/custom.rb +10 -37
- data/lib/vector_mcp/security/strategies/jwt_token.rb +1 -10
- data/lib/vector_mcp/server/capabilities.rb +22 -32
- data/lib/vector_mcp/server/message_handling.rb +21 -14
- data/lib/vector_mcp/server/registry.rb +102 -120
- data/lib/vector_mcp/server.rb +98 -57
- data/lib/vector_mcp/session.rb +5 -3
- data/lib/vector_mcp/token_store.rb +80 -0
- data/lib/vector_mcp/tool.rb +221 -0
- data/lib/vector_mcp/transport/base_session_manager.rb +1 -17
- data/lib/vector_mcp/transport/http_stream/event_store.rb +29 -17
- data/lib/vector_mcp/transport/http_stream/session_manager.rb +41 -36
- data/lib/vector_mcp/transport/http_stream/stream_handler.rb +132 -47
- data/lib/vector_mcp/transport/http_stream.rb +242 -124
- data/lib/vector_mcp/util/token_sweeper.rb +74 -0
- data/lib/vector_mcp/version.rb +1 -1
- data/lib/vector_mcp.rb +8 -8
- metadata +8 -10
- data/lib/vector_mcp/transport/sse/client_connection.rb +0 -113
- data/lib/vector_mcp/transport/sse/message_handler.rb +0 -166
- data/lib/vector_mcp/transport/sse/puma_config.rb +0 -77
- data/lib/vector_mcp/transport/sse/stream_manager.rb +0 -92
- data/lib/vector_mcp/transport/sse.rb +0 -377
- data/lib/vector_mcp/transport/sse_session_manager.rb +0 -188
- data/lib/vector_mcp/transport/stdio.rb +0 -473
- data/lib/vector_mcp/transport/stdio_session_manager.rb +0 -181
data/README.md
CHANGED
|
@@ -6,441 +6,251 @@
|
|
|
6
6
|
[](https://qlty.sh/gh/sergiobayona/projects/vector_mcp)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
|
-
VectorMCP is a Ruby
|
|
9
|
+
VectorMCP is a Ruby implementation of the Model Context Protocol (MCP) server-side specification. It gives you a framework for exposing tools, resources, prompts, roots, sampling, middleware, and security over the MCP streamable HTTP transport.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Highlights
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
13
|
+
- Streamable HTTP is the built-in transport, with session management, resumability, and MCP 2025-11-25 compliance
|
|
14
|
+
- Class-based tools via `VectorMCP::Tool`, plus the original block-based `register_tool` API
|
|
15
|
+
- Rack and Rails mounting through `server.rack_app`
|
|
16
|
+
- Opt-in authentication and authorization, structured logging, and middleware hooks
|
|
17
|
+
- Image-aware tools/resources/prompts, roots, and server-initiated sampling
|
|
18
|
+
- Token-based field anonymization middleware to keep sensitive values out of LLM context
|
|
18
19
|
|
|
19
|
-
##
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Ruby 3.2+
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
20
25
|
|
|
21
26
|
```bash
|
|
22
27
|
gem install vector_mcp
|
|
23
28
|
```
|
|
24
29
|
|
|
25
30
|
```ruby
|
|
26
|
-
|
|
31
|
+
gem "vector_mcp"
|
|
32
|
+
```
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
server = VectorMCP.new(name: 'MyApp', version: '1.0.0')
|
|
34
|
+
## Quick Start
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
```ruby
|
|
37
|
+
require "vector_mcp"
|
|
38
|
+
|
|
39
|
+
class Greet < VectorMCP::Tool
|
|
40
|
+
description "Say hello to someone"
|
|
41
|
+
param :name, type: :string, desc: "Name to greet", required: true
|
|
42
|
+
|
|
43
|
+
def call(args, _session)
|
|
44
|
+
"Hello, #{args["name"]}!"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
server = VectorMCP::Server.new(name: "MyApp", version: "1.0.0")
|
|
49
|
+
server.register(Greet)
|
|
50
|
+
server.run(port: 8080)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The class-based DSL is optional. The existing block-based API still works:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
32
56
|
server.register_tool(
|
|
33
|
-
name:
|
|
34
|
-
description:
|
|
57
|
+
name: "echo",
|
|
58
|
+
description: "Echo back the supplied text",
|
|
35
59
|
input_schema: {
|
|
36
|
-
type:
|
|
37
|
-
properties: {
|
|
38
|
-
required: [
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: { text: { type: "string" } },
|
|
62
|
+
required: ["text"]
|
|
39
63
|
}
|
|
40
|
-
) { |args|
|
|
41
|
-
|
|
42
|
-
# Start the server
|
|
43
|
-
server.run # Uses stdio transport by default
|
|
64
|
+
) { |args| args["text"] }
|
|
44
65
|
```
|
|
45
66
|
|
|
46
|
-
|
|
67
|
+
## Rack and Rails
|
|
68
|
+
|
|
69
|
+
VectorMCP can run as a standalone HTTP server or be mounted inside an existing Rack app:
|
|
47
70
|
|
|
48
|
-
|
|
71
|
+
```ruby
|
|
72
|
+
require "vector_mcp"
|
|
73
|
+
|
|
74
|
+
server = VectorMCP::Server.new(name: "MyApp", version: "1.0.0")
|
|
75
|
+
server.register(Greet)
|
|
49
76
|
|
|
50
|
-
|
|
77
|
+
MCP_APP = server.rack_app
|
|
78
|
+
```
|
|
51
79
|
|
|
52
|
-
|
|
80
|
+
In Rails, mount it in `config/routes.rb`:
|
|
53
81
|
|
|
54
82
|
```ruby
|
|
55
|
-
|
|
83
|
+
mount MCP_APP => "/mcp"
|
|
56
84
|
```
|
|
57
85
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Ideal for web apps and browser-based clients:
|
|
86
|
+
For ActiveRecord-backed tools, opt into `VectorMCP::Rails::Tool`:
|
|
61
87
|
|
|
62
88
|
```ruby
|
|
63
|
-
|
|
64
|
-
```
|
|
89
|
+
require "vector_mcp/rails/tool"
|
|
65
90
|
|
|
66
|
-
|
|
91
|
+
class FindUser < VectorMCP::Rails::Tool
|
|
92
|
+
description "Find a user by id"
|
|
93
|
+
param :id, type: :integer, required: true
|
|
94
|
+
|
|
95
|
+
def call(args, _session)
|
|
96
|
+
user = find!(User, args[:id])
|
|
97
|
+
{ id: user.id, email: user.email }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
```
|
|
67
101
|
|
|
68
|
-
|
|
102
|
+
See [docs/rails-setup-guide.md](./docs/rails-setup-guide.md) for a full setup guide.
|
|
69
103
|
|
|
70
|
-
|
|
104
|
+
## Tools, Resources, and Prompts
|
|
71
105
|
|
|
72
|
-
Expose
|
|
106
|
+
Expose callable tools:
|
|
73
107
|
|
|
74
108
|
```ruby
|
|
75
109
|
server.register_tool(
|
|
76
|
-
name:
|
|
77
|
-
description:
|
|
110
|
+
name: "calculate",
|
|
111
|
+
description: "Performs basic math",
|
|
78
112
|
input_schema: {
|
|
79
|
-
type:
|
|
113
|
+
type: "object",
|
|
80
114
|
properties: {
|
|
81
|
-
operation: { type:
|
|
82
|
-
a: { type:
|
|
83
|
-
b: { type:
|
|
115
|
+
operation: { type: "string", enum: ["add", "subtract", "multiply"] },
|
|
116
|
+
a: { type: "number" },
|
|
117
|
+
b: { type: "number" }
|
|
84
118
|
},
|
|
85
|
-
required: [
|
|
119
|
+
required: ["operation", "a", "b"]
|
|
86
120
|
}
|
|
87
121
|
) do |args|
|
|
88
|
-
case args[
|
|
89
|
-
when
|
|
90
|
-
when
|
|
91
|
-
when
|
|
122
|
+
case args["operation"]
|
|
123
|
+
when "add" then args["a"] + args["b"]
|
|
124
|
+
when "subtract" then args["a"] - args["b"]
|
|
125
|
+
when "multiply" then args["a"] * args["b"]
|
|
92
126
|
end
|
|
93
127
|
end
|
|
94
128
|
```
|
|
95
129
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
Provide data that LLMs can read:
|
|
130
|
+
Expose readable resources:
|
|
99
131
|
|
|
100
132
|
```ruby
|
|
101
133
|
server.register_resource(
|
|
102
|
-
uri:
|
|
103
|
-
name:
|
|
104
|
-
description:
|
|
105
|
-
) { File.read(
|
|
134
|
+
uri: "file://config.json",
|
|
135
|
+
name: "App Configuration",
|
|
136
|
+
description: "Current application settings"
|
|
137
|
+
) { File.read("config.json") }
|
|
106
138
|
```
|
|
107
139
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
Create reusable prompt templates:
|
|
140
|
+
Define prompt templates:
|
|
111
141
|
|
|
112
142
|
```ruby
|
|
113
143
|
server.register_prompt(
|
|
114
|
-
name:
|
|
115
|
-
description:
|
|
144
|
+
name: "code_review",
|
|
145
|
+
description: "Reviews code for best practices",
|
|
116
146
|
arguments: [
|
|
117
|
-
{ name:
|
|
118
|
-
{ name:
|
|
147
|
+
{ name: "language", description: "Programming language", required: true },
|
|
148
|
+
{ name: "code", description: "Code to review", required: true }
|
|
119
149
|
]
|
|
120
150
|
) do |args|
|
|
121
151
|
{
|
|
122
152
|
messages: [{
|
|
123
|
-
role:
|
|
153
|
+
role: "user",
|
|
124
154
|
content: {
|
|
125
|
-
type:
|
|
126
|
-
text: "Review this #{args[
|
|
155
|
+
type: "text",
|
|
156
|
+
text: "Review this #{args["language"]} code:\n\n#{args["code"]}"
|
|
127
157
|
}
|
|
128
158
|
}]
|
|
129
159
|
}
|
|
130
160
|
end
|
|
131
161
|
```
|
|
132
162
|
|
|
133
|
-
|
|
163
|
+
`VectorMCP::Tool` also supports `type: :date` and `type: :datetime`, which are validated as strings in JSON Schema and coerced to `Date` and `Time` before `#call` runs.
|
|
134
164
|
|
|
135
|
-
|
|
165
|
+
## Security and Middleware
|
|
136
166
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
All inputs are automatically validated against your schemas:
|
|
167
|
+
VectorMCP keeps security opt-in, but the primitives are built in:
|
|
140
168
|
|
|
141
169
|
```ruby
|
|
142
|
-
# This tool is protected against invalid inputs
|
|
143
|
-
server.register_tool(
|
|
144
|
-
name: 'process_user',
|
|
145
|
-
input_schema: {
|
|
146
|
-
type: 'object',
|
|
147
|
-
properties: {
|
|
148
|
-
email: { type: 'string', format: 'email' },
|
|
149
|
-
age: { type: 'integer', minimum: 0, maximum: 150 }
|
|
150
|
-
},
|
|
151
|
-
required: ['email']
|
|
152
|
-
}
|
|
153
|
-
) { |args| "Processing #{args['email']}" }
|
|
154
|
-
|
|
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
|
-
```
|
|
160
|
-
|
|
161
|
-
### Authentication & Authorization
|
|
162
|
-
|
|
163
|
-
Secure your MCP server with flexible authentication strategies:
|
|
164
|
-
|
|
165
|
-
```ruby
|
|
166
|
-
# API Key Authentication
|
|
167
170
|
server.enable_authentication!(
|
|
168
171
|
strategy: :api_key,
|
|
169
|
-
keys: ["your-secret-key"
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
# JWT Token Authentication
|
|
173
|
-
server.enable_authentication!(
|
|
174
|
-
strategy: :jwt,
|
|
175
|
-
secret: ENV["JWT_SECRET"]
|
|
172
|
+
keys: ["your-secret-key"]
|
|
176
173
|
)
|
|
177
174
|
|
|
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
|
|
182
|
-
end
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Fine-Grained Authorization
|
|
186
|
-
|
|
187
|
-
Control access to tools, resources, and prompts:
|
|
188
|
-
|
|
189
|
-
```ruby
|
|
190
175
|
server.enable_authorization! do
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
176
|
+
authorize_tools do |user, _action, tool|
|
|
177
|
+
user[:role] == "admin" || !tool.name.start_with?("admin_")
|
|
203
178
|
end
|
|
204
179
|
end
|
|
205
180
|
```
|
|
206
181
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
Security works seamlessly across all transport layers:
|
|
210
|
-
|
|
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
|
|
222
|
-
|
|
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']) }
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### Database Query Tool
|
|
182
|
+
Custom authentication works too:
|
|
236
183
|
|
|
237
184
|
```ruby
|
|
238
|
-
server.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
253
|
-
end
|
|
254
|
-
```
|
|
255
|
-
|
|
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']
|
|
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
|
-
---
|
|
274
|
-
|
|
275
|
-
## Advanced Usage
|
|
276
|
-
|
|
277
|
-
<details>
|
|
278
|
-
<summary><strong>Filesystem Roots & Security</strong></summary>
|
|
279
|
-
|
|
280
|
-
Define secure filesystem boundaries:
|
|
281
|
-
|
|
282
|
-
```ruby
|
|
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']
|
|
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?('.') }
|
|
299
|
-
end
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
</details>
|
|
303
|
-
|
|
304
|
-
<details>
|
|
305
|
-
<summary><strong>LLM Sampling (Server → Client)</strong></summary>
|
|
306
|
-
|
|
307
|
-
Make requests to the connected LLM:
|
|
308
|
-
|
|
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
|
|
185
|
+
server.enable_authentication!(strategy: :custom) do |request|
|
|
186
|
+
api_key = request[:headers]["X-API-Key"]
|
|
187
|
+
user = User.find_by(api_key: api_key)
|
|
188
|
+
user ? { user_id: user.id, role: user.role } : false
|
|
326
189
|
end
|
|
327
190
|
```
|
|
328
191
|
|
|
329
|
-
|
|
192
|
+
For MCP clients that speak OAuth 2.1 (e.g. Claude Desktop), pass a `resource_metadata_url:` to turn on RFC 9728 discovery. Unauthenticated requests to `/mcp` return `401` with a `WWW-Authenticate` header pointing at the configured metadata document, and the client drives the rest of the OAuth dance automatically. See [docs/oauth_resource_server.md](./docs/oauth_resource_server.md) for the feature reference and [docs/rails_oauth_integration.md](./docs/rails_oauth_integration.md) for a full Rails + Doorkeeper recipe.
|
|
330
193
|
|
|
331
|
-
|
|
332
|
-
<summary><strong>Custom Error Handling</strong></summary>
|
|
194
|
+
Middleware can hook into tool, resource, prompt, sampling, auth, and transport events, including `before_auth`, `after_auth`, `on_auth_error`, `before_request`, `after_response`, and `on_transport_error`.
|
|
333
195
|
|
|
334
|
-
|
|
196
|
+
See [security/README.md](./security/README.md) for the full security guide.
|
|
335
197
|
|
|
336
|
-
|
|
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
|
|
347
|
-
end
|
|
348
|
-
```
|
|
198
|
+
### Field Anonymization
|
|
349
199
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
<details>
|
|
353
|
-
<summary><strong>Session Information</strong></summary>
|
|
354
|
-
|
|
355
|
-
Access client context:
|
|
200
|
+
Keep sensitive string values out of the LLM context by substituting them with stable opaque tokens. Values are tokenized on outbound tool results and restored on inbound tool arguments, so the LLM sees only tokens while your handlers receive the original data.
|
|
356
201
|
|
|
357
202
|
```ruby
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
203
|
+
anonymizer = VectorMCP::Middleware::Anonymizer.new(
|
|
204
|
+
store: VectorMCP::TokenStore.new,
|
|
205
|
+
field_rules: [
|
|
206
|
+
{ pattern: /email/i, prefix: "EMAIL" },
|
|
207
|
+
{ pattern: /\bssn\b/i, prefix: "SSN" }
|
|
208
|
+
]
|
|
209
|
+
)
|
|
210
|
+
anonymizer.install_on(server)
|
|
365
211
|
```
|
|
366
212
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
---
|
|
213
|
+
## Transport Notes
|
|
370
214
|
|
|
371
|
-
|
|
215
|
+
- VectorMCP ships with streamable HTTP as its built-in transport
|
|
216
|
+
- `POST /mcp` accepts a single JSON-RPC request, notification, or response; batch arrays are rejected
|
|
217
|
+
- `GET /mcp` opens an SSE stream for server-initiated messages
|
|
218
|
+
- `DELETE /mcp` terminates the session
|
|
219
|
+
- The server advertises MCP protocol `2025-11-25` and accepts `2025-03-26` and `2024-11-05` headers for compatibility
|
|
220
|
+
- Default allowed origins are restricted to localhost and loopback addresses
|
|
372
221
|
|
|
373
|
-
|
|
222
|
+
Initialize a session with curl:
|
|
374
223
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
"
|
|
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
|
-
});
|
|
224
|
+
```bash
|
|
225
|
+
curl -X POST http://localhost:8080/mcp \
|
|
226
|
+
-H "Content-Type: application/json" \
|
|
227
|
+
-H "Accept: application/json, text/event-stream" \
|
|
228
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'
|
|
409
229
|
```
|
|
410
230
|
|
|
411
|
-
##
|
|
412
|
-
|
|
413
|
-
**🏆 Battle-Tested**: Used in production applications serving thousands of requests
|
|
414
|
-
|
|
415
|
-
**⚡ Performance**: Optimized for low latency and high throughput
|
|
416
|
-
|
|
417
|
-
**🛡️ Secure by Default**: Comprehensive input validation prevents common attacks
|
|
231
|
+
## More Features
|
|
418
232
|
|
|
419
|
-
|
|
233
|
+
- Roots via `register_root` and `register_root_from_path`
|
|
234
|
+
- Image resources and image-aware tools/prompts
|
|
235
|
+
- Structured logging with component loggers
|
|
236
|
+
- Server-initiated sampling with streaming/tool-call support
|
|
237
|
+
- Middleware-driven request shaping and observability
|
|
420
238
|
|
|
421
|
-
|
|
239
|
+
## Documentation
|
|
422
240
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
-
|
|
428
|
-
-
|
|
429
|
-
-
|
|
430
|
-
|
|
431
|
-
## Installation & Setup
|
|
432
|
-
|
|
433
|
-
```bash
|
|
434
|
-
gem install vector_mcp
|
|
435
|
-
|
|
436
|
-
# Or in your Gemfile
|
|
437
|
-
gem 'vector_mcp'
|
|
438
|
-
```
|
|
241
|
+
- [CHANGELOG.md](./CHANGELOG.md)
|
|
242
|
+
- [examples/](./examples/)
|
|
243
|
+
- [docs/rails-setup-guide.md](./docs/rails-setup-guide.md)
|
|
244
|
+
- [docs/rails_oauth_integration.md](./docs/rails_oauth_integration.md)
|
|
245
|
+
- [docs/oauth_resource_server.md](./docs/oauth_resource_server.md)
|
|
246
|
+
- [docs/streamable-http-spec-compliance.md](./docs/streamable-http-spec-compliance.md)
|
|
247
|
+
- [security/README.md](./security/README.md)
|
|
248
|
+
- [MCP Specification](https://modelcontextprotocol.io/)
|
|
439
249
|
|
|
440
250
|
## Contributing
|
|
441
251
|
|
|
442
|
-
Bug reports and pull requests welcome on [GitHub](https://github.com/sergiobayona/vector_mcp).
|
|
252
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/sergiobayona/vector_mcp).
|
|
443
253
|
|
|
444
254
|
## License
|
|
445
255
|
|
|
446
|
-
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|
|
256
|
+
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -29,6 +29,36 @@ module VectorMCP
|
|
|
29
29
|
}.compact # Remove nil values
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# Class method to create a tool whose input_schema already declares a
|
|
33
|
+
# base64-encoded image property. Mirrors Resource.from_image_file and
|
|
34
|
+
# Prompt.with_image_support — keeps schema-building logic with the
|
|
35
|
+
# definition, not with the registry.
|
|
36
|
+
#
|
|
37
|
+
# @param name [String] Unique name for the tool.
|
|
38
|
+
# @param description [String] Human-readable description.
|
|
39
|
+
# @param image_parameter [String] Name of the image parameter.
|
|
40
|
+
# @param additional_parameters [Hash] Additional JSON Schema properties.
|
|
41
|
+
# @param required_parameters [Array<String>] Required parameter names.
|
|
42
|
+
# @param handler [Proc] Tool handler block.
|
|
43
|
+
# @return [Tool]
|
|
44
|
+
def self.with_image_support(name:, description:, image_parameter: "image",
|
|
45
|
+
additional_parameters: {}, required_parameters: [], &handler)
|
|
46
|
+
image_property = {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Base64 encoded image data or file path to image",
|
|
49
|
+
contentEncoding: "base64",
|
|
50
|
+
contentMediaType: "image/*"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
input_schema = {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: { image_parameter => image_property }.merge(additional_parameters),
|
|
56
|
+
required: required_parameters
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
new(name, description, input_schema, handler)
|
|
60
|
+
end
|
|
61
|
+
|
|
32
62
|
# Checks if this tool supports image inputs based on its input schema.
|
|
33
63
|
# @return [Boolean] True if the tool's input schema includes image properties.
|
|
34
64
|
def supports_image_input?
|