vector_mcp 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f97bb6519db2c4d1b5e7cbac2a47954714aaa8e65f3cf797054c43cfc782255c
4
+ data.tar.gz: 5188e19bed4e4114729d6ea482361a0ad5adffddf0c400dd6081e7370037eb73
5
+ SHA512:
6
+ metadata.gz: 04af736a5887198f5d4d24e16d37689dce473872fc3a4c6448f0bdc8aaf49ff06a1f076827725978ad4f71f8a89906a9ab1dc336a91d2d176302a91a8726183d
7
+ data.tar.gz: 1386b745783db291622c0aec7b8108c966089e40c114b37386b2fc9e041df100ba19b927353edcac7ece880f0682cc8c37457e51e35d842936fef3dc4aa5447c
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Sergio Bayona
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # VectorMCP
2
+
3
+ <!-- Badges (Add URLs later) -->
4
+ [![Gem Version](https://badge.fury.io/rb/vector_mcp.svg)](https://badge.fury.io/rb/vector_mcp)
5
+ [![Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://sergiobayona.github.io/vector_mcp/)
6
+ [![Build Status](https://github.com/sergiobayona/VectorMCP/actions/workflows/ruby.yml/badge.svg?branch=main)](https://github.com/sergiobayona/vector_mcp/actions/workflows/ruby.yml)
7
+ [![Maintainability](https://qlty.sh/badges/fdb143b3-148a-4a86-8e3b-4ccebe993528/maintainability.svg)](https://qlty.sh/gh/sergiobayona/projects/vector_mcp)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ VectorMCP provides server-side tools for implementing the [Model Context Protocol (MCP)](https://modelcontext.dev/) in Ruby applications. MCP is a specification for how Large Language Models (LLMs) can discover and interact with external tools, resources, and prompts provided by separate applications (MCP Servers).
11
+
12
+ This library allows you to easily create MCP servers that expose your application's capabilities (like functions, data sources, or predefined prompt templates) to compatible LLM clients (e.g., Claude Desktop App, custom clients).
13
+
14
+ ## Features
15
+
16
+ * **MCP Specification Adherence:** Implements core server-side aspects of the MCP specification.
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
+ * **Transport:**
21
+ * **Stdio (stable):** Simple transport using standard input/output, ideal for process-based servers.
22
+ * **SSE (work-in-progress):** Server-Sent Events support is under active development and currently unavailable.
23
+ * **Extensible Handlers:** Provides default handlers for core MCP methods, which can be overridden.
24
+ * **Clear Error Handling:** Custom error classes mapping to JSON-RPC/MCP error codes.
25
+ * **Ruby-like API:** Uses blocks for registering handlers, following idiomatic Ruby patterns.
26
+
27
+ ## Installation
28
+
29
+ Add this line to your application's Gemfile:
30
+
31
+ ```ruby
32
+ gem 'vector_mcp'
33
+ ```
34
+
35
+ And then execute:
36
+
37
+ ```bash
38
+ $ bundle install
39
+ ```
40
+
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
+
49
+ ## Quick Start
50
+
51
+ This example creates a simple server that runs over standard input/output and provides one tool.
52
+
53
+ ```ruby
54
+ require 'vector_mcp'
55
+
56
+ # Create a server
57
+ server = VectorMCP.new('Echo Server')
58
+
59
+ # Register a single "echo" tool
60
+ server.register_tool(
61
+ name: 'echo',
62
+ description: 'Returns whatever message you send.',
63
+ input_schema: {
64
+ type: 'object',
65
+ properties: { message: { type: 'string' } },
66
+ required: ['message']
67
+ }
68
+ ) { |args, _session| args['message'] }
69
+
70
+ # Start listening on STDIN/STDOUT (default transport)
71
+ server.run
72
+ ```
73
+
74
+ **To run this:**
75
+
76
+ 1. Save it as `my_server.rb`.
77
+ 2. Run `ruby my_server.rb`.
78
+ 3. The server now waits for **newline-delimited JSON-RPC objects** on **STDIN** and writes responses to **STDOUT**.
79
+
80
+ You have two easy ways to talk to it:
81
+
82
+ **a. Interactive (paste a line, press Enter)**
83
+
84
+ ```bash
85
+ $ ruby my_server.rb
86
+ # paste the JSON below, press ↵, observe the response
87
+ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
88
+ {"jsonrpc":"2.0","method":"initialized"}
89
+ # etc.
90
+ ```
91
+
92
+ **b. Scripted (pipe a series of echo / printf commands)**
93
+
94
+ ```bash
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
+ ```
102
+
103
+ Each request **must be on a single line and terminated by a newline** so the server knows where the message ends.
104
+
105
+ Below are the same requests shown individually:
106
+
107
+ ```jsonc
108
+ // 1. Initialize (client → server)
109
+ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ManualClient","version":"0.1"}}}
110
+
111
+ // 2. Initialized notification (client → server, no id)
112
+ {"jsonrpc":"2.0","method":"initialized"}
113
+
114
+ // 3. List available tools (client → server)
115
+ {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
116
+
117
+ // 4. Call the echo tool (client → server)
118
+ {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"message":"Hello VectorMCP!"}}}
119
+ ```
120
+
121
+ ## Usage
122
+
123
+ ### Creating a Server
124
+
125
+ Instantiate the server using the factory method:
126
+
127
+ ```ruby
128
+ require 'vector_mcp'
129
+
130
+ server = VectorMCP.new(
131
+ name: "MyAwesomeServer",
132
+ version: "2.1.0",
133
+ log_level: Logger::DEBUG # Optional: Default is INFO
134
+ )
135
+ ```
136
+
137
+ ### Registering Tools
138
+
139
+ Tools are functions your server exposes. Use `register_tool` with a block.
140
+
141
+ ```ruby
142
+ server.register_tool(
143
+ name: "calculate_sum",
144
+ description: "Adds two numbers together.",
145
+ input_schema: {
146
+ type: "object",
147
+ properties: {
148
+ a: { type: "number", description: "First number" },
149
+ b: { type: "number", description: "Second number" }
150
+ },
151
+ required: ["a", "b"]
152
+ }
153
+ ) do |args, session|
154
+ # args is a hash like { "a" => 10, "b" => 5 }
155
+ # session object provides session context (e.g., session.initialized?)
156
+ sum = (args["a"] || 0) + (args["b"] || 0)
157
+ "The sum is: #{sum}" # Return value is converted to {type: "text", text: ...}
158
+ end
159
+ ```
160
+
161
+ * The input_schema must be a Hash representing a valid JSON Schema object describing the tool's expected arguments.
162
+ * The block receives the arguments hash and the VectorMCP::Session object.
163
+ * The **session** object represents the client connection that invoked the tool. It lets you:
164
+ * Inspect the client's `clientInfo` and declared `capabilities` (e.g. `session.client_info['name']`).
165
+ * Store or look up per-connection state (authentication, rate-limiting, feature flags).
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.
168
+
169
+ Passing `session` up-front means tool authors can make use of this context today; if you don't need it, simply ignore the parameter (Ruby will accept extra block parameters).
170
+ * The block's return value is automatically converted into the MCP `content` array format by `VectorMCP::Util.convert_to_mcp_content`. You can return:
171
+ * A `String`: Becomes `{ type: 'text', text: '...' }`.
172
+ * A `Hash` matching the MCP content structure (`{ type: 'text', ... }`, `{ type: 'image', ... }`, etc.): Used as is.
173
+ * Other `Hash` objects: JSON-encoded into `{ type: 'text', text: '...', mimeType: 'application/json' }`.
174
+ * Binary String (`Encoding::ASCII_8BIT`): Base64-encoded into `{ type: 'blob', blob: '...', mimeType: 'application/octet-stream' }`.
175
+ * An `Array` of the above: Each element is converted and combined.
176
+ * Other objects: Converted using `to_s` into `{ type: 'text', text: '...' }`.
177
+
178
+ ### Registering Resources
179
+
180
+ Resources provide data that the client can read.
181
+
182
+ ```ruby
183
+ server.register_resource(
184
+ uri: "memory://status", # Unique URI for this resource
185
+ name: "Server Status",
186
+ description: "Provides the current server status.",
187
+ mime_type: "application/json" # Optional: Defaults to text/plain
188
+ ) do |session|
189
+ # Handler block receives the session object
190
+ {
191
+ status: "OK",
192
+ uptime: Time.now - server_start_time, # Example value
193
+ initialized: session.initialized?
194
+ } # Hash will be JSON encoded due to mime_type
195
+ end
196
+
197
+ # Resource returning binary data
198
+ server.register_resource(
199
+ uri: "file://logo.png",
200
+ name: "Logo Image",
201
+ description: "The server's logo.",
202
+ mime_type: "image/png"
203
+ ) do |session|
204
+ # IMPORTANT: Return binary data as a string with ASCII-8BIT encoding
205
+ File.binread("path/to/logo.png")
206
+ end
207
+ ```
208
+
209
+ * The block receives the `VectorMCP::Session` object.
210
+ * Return `String` for text, or a binary `
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "vector_mcp"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require "irb"
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module VectorMCP
6
+ # This module contains Struct definitions for Tools, Resources, and Prompts
7
+ # that a VectorMCP::Server can provide.
8
+ module Definitions
9
+ # Represents a tool that can be executed by the AI model.
10
+ #
11
+ # @!attribute name [rw] String
12
+ # The unique name of the tool.
13
+ # @!attribute description [rw] String
14
+ # A human-readable description of what the tool does.
15
+ # @!attribute input_schema [rw] Hash
16
+ # A JSON Schema object describing the expected input for the tool.
17
+ # @!attribute handler [rw] Proc
18
+ # A callable (e.g., a Proc or lambda) that executes the tool's logic.
19
+ # It receives the tool input (a Hash) as its argument.
20
+ # The input hash structure should match the input_schema.
21
+ Tool = Struct.new(:name, :description, :input_schema, :handler) do
22
+ # Converts the tool to its MCP definition hash.
23
+ # @return [Hash] A hash representing the tool in MCP format.
24
+ def as_mcp_definition
25
+ {
26
+ name: name,
27
+ description: description,
28
+ inputSchema: input_schema # Expected to be a Hash representing JSON Schema
29
+ }.compact # Remove nil values
30
+ end
31
+ end
32
+
33
+ # Represents a resource (context or data) that can be provided to the AI model or user.
34
+ #
35
+ # @!attribute uri [rw] URI, String
36
+ # The unique URI identifying the resource.
37
+ # @!attribute name [rw] String
38
+ # A human-readable name for the resource.
39
+ # @!attribute description [rw] String
40
+ # A description of the resource content.
41
+ # @!attribute mime_type [rw] String
42
+ # The MIME type of the resource content (e.g., "text/plain", "application/json").
43
+ # @!attribute handler [rw] Proc
44
+ # A callable that returns the content of the resource. It may receive parameters from the request (e.g., for dynamic resources).
45
+ Resource = Struct.new(:uri, :name, :description, :mime_type, :handler) do
46
+ # Converts the resource to its MCP definition hash.
47
+ # @return [Hash] A hash representing the resource in MCP format.
48
+ def as_mcp_definition
49
+ {
50
+ uri: uri.to_s,
51
+ name: name,
52
+ description: description,
53
+ mimeType: mime_type
54
+ }.compact
55
+ end
56
+ end
57
+
58
+ # Represents a prompt or templated message workflow for users or AI models.
59
+ #
60
+ # @!attribute name [rw] String
61
+ # The unique name of the prompt.
62
+ # @!attribute description [rw] String
63
+ # A human-readable description of the prompt.
64
+ # @!attribute arguments [rw] Array<Hash>
65
+ # An array of argument definitions for the prompt, where each hash can contain
66
+ # `:name`, `:description`, and `:required` (Boolean).
67
+ # @!attribute handler [rw] Proc
68
+ # A callable that generates the prompt content. It receives a hash of arguments, validated against the prompt's argument definitions.
69
+ Prompt = Struct.new(:name, :description, :arguments, :handler) do
70
+ # Converts the prompt to its MCP definition hash.
71
+ # @return [Hash] A hash representing the prompt in MCP format.
72
+ def as_mcp_definition
73
+ {
74
+ name: name,
75
+ description: description,
76
+ arguments: arguments # Expected to be an array of { name:, description:, required: } hashes
77
+ }.compact
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VectorMCP
4
+ # Base error class for all VectorMCP specific errors.
5
+ class Error < StandardError; end
6
+
7
+ # Base class for **all** JSON-RPC 2.0 errors that the VectorMCP library can
8
+ # emit. It mirrors the structure defined by the JSON-RPC spec and adds a
9
+ # flexible `details` field that implementers may use to attach structured,
10
+ # implementation-specific metadata to an error payload.
11
+ #
12
+ # @!attribute [r] code
13
+ # @return [Integer] The JSON-RPC error code.
14
+ # @!attribute [r] message
15
+ # @return [String] A short description of the error.
16
+ # @!attribute [rw] request_id
17
+ # @return [String, Integer, nil] The ID of the request that caused this error, if applicable.
18
+ # @!attribute [r] details
19
+ # @return [Hash, nil] Additional implementation-specific details for the error (optional).
20
+ class ProtocolError < Error
21
+ attr_accessor :request_id
22
+ attr_reader :code, :message, :details
23
+
24
+ # Initializes a new ProtocolError.
25
+ #
26
+ # @param message [String] The error message.
27
+ # @param code [Integer] The JSON-RPC error code.
28
+ # @param details [Hash, nil] Additional details for the error (optional).
29
+ # @param request_id [String, Integer, nil] The ID of the originating request.
30
+ def initialize(message, code: -32_600, details: nil, request_id: nil)
31
+ VectorMCP.logger.debug("Initializing ProtocolError with code: #{code}")
32
+ @code = code
33
+ @message = message
34
+ @details = details # NOTE: `data` in JSON-RPC is often used for this purpose.
35
+ @request_id = request_id
36
+ super(message)
37
+ end
38
+ end
39
+
40
+ # Standard JSON-RPC error classes
41
+
42
+ # Represents a JSON-RPC Parse error (-32700).
43
+ # Indicates invalid JSON was received by the server.
44
+ class ParseError < ProtocolError
45
+ # @param message [String] The error message.
46
+ # @param details [Hash, nil] Additional details for the error (optional).
47
+ # @param request_id [String, Integer, nil] The ID of the originating request.
48
+ def initialize(message = "Parse error", details: nil, request_id: nil)
49
+ super(message, code: -32_700, details: details, request_id: request_id)
50
+ end
51
+ end
52
+
53
+ # Represents a JSON-RPC Invalid Request error (-32600).
54
+ # Indicates the JSON sent is not a valid Request object.
55
+ class InvalidRequestError < ProtocolError
56
+ # @param message [String] The error message.
57
+ # @param details [Hash, nil] Additional details for the error (optional).
58
+ # @param request_id [String, Integer, nil] The ID of the originating request.
59
+ def initialize(message = "Invalid Request", details: nil, request_id: nil)
60
+ super(message, code: -32_600, details: details, request_id: request_id)
61
+ end
62
+ end
63
+
64
+ # Represents a JSON-RPC Method Not Found error (-32601).
65
+ # Indicates the method does not exist or is not available.
66
+ class MethodNotFoundError < ProtocolError
67
+ # @param method [String] The name of the method that was not found.
68
+ # @param details [Hash, nil] Additional details for the error (optional).
69
+ # @param request_id [String, Integer, nil] The ID of the originating request.
70
+ def initialize(method, details: nil, request_id: nil)
71
+ details ||= { method_name: method }
72
+ super("Method not found: #{method}", code: -32_601, details: details, request_id: request_id)
73
+ end
74
+ end
75
+
76
+ # Represents a JSON-RPC Invalid Params error (-32602).
77
+ # Indicates invalid method parameter(s).
78
+ class InvalidParamsError < ProtocolError
79
+ # @param message [String] The error message.
80
+ # @param details [Hash, nil] Additional details for the error (optional).
81
+ # @param request_id [String, Integer, nil] The ID of the originating request.
82
+ def initialize(message = "Invalid params", details: nil, request_id: nil)
83
+ super(message, code: -32_602, details: details, request_id: request_id)
84
+ end
85
+ end
86
+
87
+ # Represents a JSON-RPC Internal error (-32603).
88
+ # Indicates an internal error in the JSON-RPC server.
89
+ class InternalError < ProtocolError
90
+ # @param message [String] The error message.
91
+ # @param details [Hash, nil] Additional details for the error (optional).
92
+ # @param request_id [String, Integer, nil] The ID of the originating request.
93
+ def initialize(message = "Internal error", details: nil, request_id: nil)
94
+ super(message, code: -32_603, details: details, request_id: request_id)
95
+ end
96
+ end
97
+
98
+ # Represents a JSON-RPC server-defined error (codes -32000 to -32099).
99
+ class ServerError < ProtocolError
100
+ # @param message [String] The error message.
101
+ # @param code [Integer] The server-defined error code. Must be between -32099 and -32000. If the
102
+ # supplied value falls outside this range it will be coerced to **-32000** in order to comply
103
+ # with the JSON-RPC 2.0 specification.
104
+ # @param details [Hash, nil] Additional details for the error (optional).
105
+ # @param request_id [String, Integer, nil] The ID of the originating request.
106
+ def initialize(message = "Server error", code: -32_000, details: nil, request_id: nil)
107
+ VectorMCP.logger.debug("Initializing ServerError with code: #{code}")
108
+ unless (-32_099..-32_000).cover?(code)
109
+ warn "Server error code #{code} is outside of the reserved range (-32099 to -32000). Using -32000 instead."
110
+ code = -32_000
111
+ end
112
+ super
113
+ end
114
+ end
115
+
116
+ # Represents an error indicating a request was received before server initialization completed (-32002).
117
+ class InitializationError < ServerError
118
+ # @param message [String] The error message.
119
+ # @param details [Hash, nil] Additional details for the error (optional).
120
+ # @param request_id [String, Integer, nil] The ID of the originating request.
121
+ def initialize(message = "Server not initialized", details: nil, request_id: nil)
122
+ super(message, code: -32_002, details: details, request_id: request_id)
123
+ end
124
+ end
125
+
126
+ # Represents an error indicating a requested resource or entity was not found (-32001).
127
+ # Note: This uses a code typically outside the strict JSON-RPC server error range,
128
+ # but is common in practice for "Not Found" scenarios.
129
+ class NotFoundError < ProtocolError
130
+ # @param message [String] The error message.
131
+ # @param details [Hash, nil] Additional details for the error (optional).
132
+ # @param request_id [String, Integer, nil] The ID of the originating request.
133
+ def initialize(message = "Not Found", details: nil, request_id: nil)
134
+ VectorMCP.logger.debug("Initializing NotFoundError with code: -32001")
135
+ super(message, code: -32_001, details: details, request_id: request_id)
136
+ end
137
+ end
138
+ end