manceps 1.0.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 +7 -0
- data/CHANGELOG.md +30 -0
- data/LICENSE +21 -0
- data/README.md +325 -0
- data/lib/manceps/auth/api_key_header.rb +17 -0
- data/lib/manceps/auth/bearer.rb +16 -0
- data/lib/manceps/auth/none.rb +12 -0
- data/lib/manceps/auth/oauth.rb +202 -0
- data/lib/manceps/backoff.rb +25 -0
- data/lib/manceps/client.rb +278 -0
- data/lib/manceps/content.rb +33 -0
- data/lib/manceps/elicitation.rb +26 -0
- data/lib/manceps/errors.rb +32 -0
- data/lib/manceps/json_rpc.rb +55 -0
- data/lib/manceps/prompt.rb +30 -0
- data/lib/manceps/prompt_result.rb +27 -0
- data/lib/manceps/resource.rb +17 -0
- data/lib/manceps/resource_contents.rb +16 -0
- data/lib/manceps/resource_template.rb +17 -0
- data/lib/manceps/session.rb +43 -0
- data/lib/manceps/sse_parser.rb +64 -0
- data/lib/manceps/task.rb +42 -0
- data/lib/manceps/tool.rb +24 -0
- data/lib/manceps/tool_result.rb +26 -0
- data/lib/manceps/transport/base.rb +36 -0
- data/lib/manceps/transport/stdio.rb +143 -0
- data/lib/manceps/transport/streamable_http.rb +190 -0
- data/lib/manceps/version.rb +5 -0
- data/lib/manceps.rb +66 -0
- metadata +102 -0
data/lib/manceps/tool.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Manceps
|
|
4
|
+
# An MCP tool definition.
|
|
5
|
+
class Tool
|
|
6
|
+
attr_reader :name, :description, :input_schema, :output_schema, :annotations, :title
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@name = data['name']
|
|
10
|
+
@description = data['description']
|
|
11
|
+
@title = data['title']
|
|
12
|
+
@input_schema = data['inputSchema']
|
|
13
|
+
@output_schema = data['outputSchema']
|
|
14
|
+
@annotations = data['annotations']
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_h
|
|
18
|
+
h = { 'name' => name, 'description' => description, 'inputSchema' => input_schema }
|
|
19
|
+
h['title'] = title if title
|
|
20
|
+
h['outputSchema'] = output_schema if output_schema
|
|
21
|
+
h
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Manceps
|
|
4
|
+
# Result of a tool invocation.
|
|
5
|
+
class ToolResult
|
|
6
|
+
attr_reader :content, :is_error, :structured_content
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@content = (data['content'] || []).map { |c| Content.new(c) }
|
|
10
|
+
@is_error = data['isError'] || false
|
|
11
|
+
@structured_content = data['structuredContent']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def error?
|
|
15
|
+
is_error
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def text
|
|
19
|
+
content.select(&:text?).map(&:text).join("\n")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def structured?
|
|
23
|
+
!structured_content.nil?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Manceps
|
|
4
|
+
module Transport
|
|
5
|
+
# Abstract base class for MCP transports.
|
|
6
|
+
class Base
|
|
7
|
+
def request(body)
|
|
8
|
+
raise NotImplementedError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def request_streaming(body, &)
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def notify(body)
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def terminate_session(session_id)
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def close
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def listen(&)
|
|
28
|
+
raise NotImplementedError
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def on_notification(&block)
|
|
32
|
+
@notification_callback = block
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Manceps
|
|
7
|
+
module Transport
|
|
8
|
+
# Stdio transport: communicates with a local subprocess via stdin/stdout.
|
|
9
|
+
class Stdio < Base
|
|
10
|
+
def initialize(command, args: [], env: {})
|
|
11
|
+
super()
|
|
12
|
+
@command = command
|
|
13
|
+
@args = args
|
|
14
|
+
@env = env
|
|
15
|
+
@stdin = nil
|
|
16
|
+
@stdout = nil
|
|
17
|
+
@stderr = nil
|
|
18
|
+
@wait_thread = nil
|
|
19
|
+
@mutex = Mutex.new
|
|
20
|
+
@notification_callback = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def open
|
|
24
|
+
close if @wait_thread # Clean up any existing process
|
|
25
|
+
|
|
26
|
+
@stdin, @stdout, @stderr, @wait_thread = Open3.popen3(@env, @command, *@args)
|
|
27
|
+
|
|
28
|
+
at_exit { close }
|
|
29
|
+
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def request(body)
|
|
34
|
+
@mutex.synchronize do
|
|
35
|
+
write_message(body)
|
|
36
|
+
read_response
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def notify(body)
|
|
41
|
+
@mutex.synchronize do
|
|
42
|
+
write_message(body)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def terminate_session(_session_id)
|
|
47
|
+
# No-op for stdio -- session ends when the process exits
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def listen(&block)
|
|
51
|
+
raise ConnectionError, 'Stdio transport not open' unless @stdout
|
|
52
|
+
|
|
53
|
+
loop do
|
|
54
|
+
line = @stdout.gets
|
|
55
|
+
break if line.nil?
|
|
56
|
+
|
|
57
|
+
parsed = begin
|
|
58
|
+
JSON.parse(line)
|
|
59
|
+
rescue StandardError
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
block.call(parsed) if parsed['method']
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def close
|
|
67
|
+
return unless @wait_thread
|
|
68
|
+
|
|
69
|
+
begin
|
|
70
|
+
@stdin&.close
|
|
71
|
+
rescue StandardError
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if @wait_thread.alive?
|
|
76
|
+
begin
|
|
77
|
+
Process.kill('TERM', @wait_thread.pid)
|
|
78
|
+
rescue StandardError
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
unless @wait_thread.join(5)
|
|
83
|
+
begin
|
|
84
|
+
Process.kill('KILL', @wait_thread.pid)
|
|
85
|
+
rescue StandardError
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
begin
|
|
89
|
+
@wait_thread.join(1)
|
|
90
|
+
rescue StandardError
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
begin
|
|
97
|
+
@stdout&.close
|
|
98
|
+
rescue StandardError
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
begin
|
|
102
|
+
@stderr&.close
|
|
103
|
+
rescue StandardError
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
@stdin = nil
|
|
107
|
+
@stdout = nil
|
|
108
|
+
@stderr = nil
|
|
109
|
+
@wait_thread = nil
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def write_message(body)
|
|
115
|
+
raise ConnectionError, 'Stdio transport not open' unless @stdin
|
|
116
|
+
|
|
117
|
+
json = JSON.generate(body)
|
|
118
|
+
@stdin.write("#{json}\n")
|
|
119
|
+
@stdin.flush
|
|
120
|
+
rescue Errno::EPIPE
|
|
121
|
+
raise ConnectionError, 'Process exited unexpectedly'
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def read_response
|
|
125
|
+
raise ConnectionError, 'Stdio transport not open' unless @stdout
|
|
126
|
+
|
|
127
|
+
loop do
|
|
128
|
+
line = @stdout.gets
|
|
129
|
+
raise ConnectionError, 'Process exited unexpectedly' if line.nil?
|
|
130
|
+
|
|
131
|
+
parsed = JSON.parse(line)
|
|
132
|
+
|
|
133
|
+
return parsed unless parsed['method'] && !parsed.key?('id')
|
|
134
|
+
|
|
135
|
+
# This is a server-initiated notification; dispatch and keep reading
|
|
136
|
+
@notification_callback&.call(parsed)
|
|
137
|
+
end
|
|
138
|
+
rescue JSON::ParserError => e
|
|
139
|
+
raise ProtocolError, "Invalid JSON from process: #{e.message}"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'httpx'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Manceps
|
|
7
|
+
module Transport
|
|
8
|
+
# Streamable HTTP transport with persistent connections and SSE support.
|
|
9
|
+
class StreamableHTTP < Base
|
|
10
|
+
attr_reader :session_id
|
|
11
|
+
attr_writer :protocol_version
|
|
12
|
+
|
|
13
|
+
def initialize(url, auth:, timeout: nil)
|
|
14
|
+
super()
|
|
15
|
+
@url = url
|
|
16
|
+
@auth = auth
|
|
17
|
+
@session_id = nil
|
|
18
|
+
@last_event_id = nil
|
|
19
|
+
@protocol_version = nil
|
|
20
|
+
|
|
21
|
+
timeout_opts = timeout || {
|
|
22
|
+
connect_timeout: Manceps.configuration.connect_timeout,
|
|
23
|
+
request_timeout: Manceps.configuration.request_timeout
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# httpx maintains persistent connections by default —
|
|
27
|
+
# critical because MCP servers bind Mcp-Session-Id to the TCP connection
|
|
28
|
+
@http = HTTPX.with(timeout: timeout_opts)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def request(body)
|
|
32
|
+
response = @http.post(@url, headers: base_headers, body: JSON.generate(body))
|
|
33
|
+
handle_connection_error(response)
|
|
34
|
+
handle_error_response(response)
|
|
35
|
+
capture_session_id(response)
|
|
36
|
+
result = parse_response(response)
|
|
37
|
+
track_event_ids_from_response(response)
|
|
38
|
+
result
|
|
39
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE, Errno::EHOSTUNREACH => e
|
|
40
|
+
raise ConnectionError, e.message
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def request_streaming(body, &block)
|
|
44
|
+
response = @http.post(@url, headers: base_headers, body: JSON.generate(body))
|
|
45
|
+
handle_connection_error(response)
|
|
46
|
+
handle_error_response(response)
|
|
47
|
+
capture_session_id(response)
|
|
48
|
+
|
|
49
|
+
content_type = response.content_type&.mime_type.to_s
|
|
50
|
+
|
|
51
|
+
if content_type.include?('text/event-stream')
|
|
52
|
+
events = SSEParser.parse_events(response.body.to_s)
|
|
53
|
+
track_event_ids(events)
|
|
54
|
+
final_result = nil
|
|
55
|
+
events.each do |event|
|
|
56
|
+
parsed = begin
|
|
57
|
+
JSON.parse(event[:data])
|
|
58
|
+
rescue StandardError
|
|
59
|
+
next
|
|
60
|
+
end
|
|
61
|
+
if parsed['result'] || parsed['error']
|
|
62
|
+
final_result = parsed
|
|
63
|
+
elsif block
|
|
64
|
+
block.call(parsed)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
final_result || parse_response(response)
|
|
68
|
+
else
|
|
69
|
+
parse_response(response)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def notify(body)
|
|
74
|
+
response = @http.post(@url, headers: base_headers, body: JSON.generate(body))
|
|
75
|
+
handle_connection_error(response)
|
|
76
|
+
handle_error_response(response) unless response.status == 202
|
|
77
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE, Errno::EHOSTUNREACH => e
|
|
78
|
+
raise ConnectionError, e.message
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def terminate_session(session_id)
|
|
82
|
+
headers = {}
|
|
83
|
+
headers['mcp-session-id'] = session_id
|
|
84
|
+
@auth.apply(headers)
|
|
85
|
+
begin
|
|
86
|
+
@http.delete(@url, headers: headers)
|
|
87
|
+
rescue StandardError
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def listen(&block)
|
|
93
|
+
headers = base_headers.dup
|
|
94
|
+
headers.delete('content-type')
|
|
95
|
+
headers['accept'] = 'text/event-stream'
|
|
96
|
+
|
|
97
|
+
response = @http.get(@url, headers: headers)
|
|
98
|
+
handle_error_response(response)
|
|
99
|
+
|
|
100
|
+
content_type = response.content_type&.mime_type.to_s
|
|
101
|
+
return unless content_type.include?('text/event-stream')
|
|
102
|
+
|
|
103
|
+
events = SSEParser.parse_events(response.body.to_s)
|
|
104
|
+
events.each do |event|
|
|
105
|
+
parsed = begin
|
|
106
|
+
JSON.parse(event[:data])
|
|
107
|
+
rescue StandardError
|
|
108
|
+
next
|
|
109
|
+
end
|
|
110
|
+
block.call(parsed) if parsed['method']
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def close
|
|
115
|
+
@http.close if @http.respond_to?(:close)
|
|
116
|
+
@session_id = nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def base_headers
|
|
122
|
+
headers = {
|
|
123
|
+
'content-type' => 'application/json',
|
|
124
|
+
'accept' => 'application/json, text/event-stream'
|
|
125
|
+
}
|
|
126
|
+
headers['mcp-session-id'] = @session_id if @session_id
|
|
127
|
+
headers['mcp-protocol-version'] = @protocol_version if @protocol_version
|
|
128
|
+
headers['last-event-id'] = @last_event_id if @last_event_id
|
|
129
|
+
@auth.apply(headers)
|
|
130
|
+
headers
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse_response(response)
|
|
134
|
+
body = response.body.to_s
|
|
135
|
+
content_type = response.content_type&.mime_type.to_s
|
|
136
|
+
|
|
137
|
+
if content_type.include?('text/event-stream')
|
|
138
|
+
SSEParser.extract_json(body)
|
|
139
|
+
else
|
|
140
|
+
JSON.parse(body)
|
|
141
|
+
end
|
|
142
|
+
rescue JSON::ParserError => e
|
|
143
|
+
raise ProtocolError, "Invalid JSON in response: #{e.message}"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def capture_session_id(response)
|
|
147
|
+
sid = response.headers['mcp-session-id']
|
|
148
|
+
@session_id = sid if sid
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def track_event_ids(events)
|
|
152
|
+
last = events.select { |e| e[:id] }.last
|
|
153
|
+
@last_event_id = last[:id] if last
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def track_event_ids_from_response(response)
|
|
157
|
+
content_type = response.content_type&.mime_type.to_s
|
|
158
|
+
return unless content_type.include?('text/event-stream')
|
|
159
|
+
|
|
160
|
+
events = SSEParser.parse_events(response.body.to_s)
|
|
161
|
+
track_event_ids(events)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def handle_connection_error(response)
|
|
165
|
+
return unless response.is_a?(HTTPX::ErrorResponse)
|
|
166
|
+
|
|
167
|
+
error = response.error
|
|
168
|
+
case error
|
|
169
|
+
when HTTPX::TimeoutError
|
|
170
|
+
raise TimeoutError, error.message
|
|
171
|
+
else
|
|
172
|
+
raise ConnectionError, error.message
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def handle_error_response(response)
|
|
177
|
+
return if response.status < 400
|
|
178
|
+
|
|
179
|
+
case response.status
|
|
180
|
+
when 401
|
|
181
|
+
raise AuthenticationError, "Server returned 401: #{response.body}"
|
|
182
|
+
when 404
|
|
183
|
+
raise SessionExpiredError, 'Session expired (404)'
|
|
184
|
+
else
|
|
185
|
+
raise ConnectionError, "HTTP #{response.status}: #{response.body}"
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
data/lib/manceps.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
|
|
7
|
+
require 'httpx'
|
|
8
|
+
|
|
9
|
+
require_relative 'manceps/version'
|
|
10
|
+
require_relative 'manceps/errors'
|
|
11
|
+
require_relative 'manceps/json_rpc'
|
|
12
|
+
require_relative 'manceps/sse_parser'
|
|
13
|
+
require_relative 'manceps/content'
|
|
14
|
+
require_relative 'manceps/tool'
|
|
15
|
+
require_relative 'manceps/tool_result'
|
|
16
|
+
require_relative 'manceps/prompt'
|
|
17
|
+
require_relative 'manceps/prompt_result'
|
|
18
|
+
require_relative 'manceps/resource'
|
|
19
|
+
require_relative 'manceps/resource_template'
|
|
20
|
+
require_relative 'manceps/resource_contents'
|
|
21
|
+
require_relative 'manceps/elicitation'
|
|
22
|
+
require_relative 'manceps/backoff'
|
|
23
|
+
require_relative 'manceps/session'
|
|
24
|
+
require_relative 'manceps/auth/none'
|
|
25
|
+
require_relative 'manceps/auth/bearer'
|
|
26
|
+
require_relative 'manceps/auth/api_key_header'
|
|
27
|
+
require_relative 'manceps/auth/oauth'
|
|
28
|
+
require_relative 'manceps/transport/base'
|
|
29
|
+
require_relative 'manceps/transport/streamable_http'
|
|
30
|
+
require_relative 'manceps/transport/stdio'
|
|
31
|
+
require_relative 'manceps/task'
|
|
32
|
+
require_relative 'manceps/client'
|
|
33
|
+
|
|
34
|
+
# Ruby client for the Model Context Protocol (MCP).
|
|
35
|
+
module Manceps
|
|
36
|
+
Configuration = Struct.new(
|
|
37
|
+
:client_name,
|
|
38
|
+
:client_version,
|
|
39
|
+
:client_description,
|
|
40
|
+
:protocol_version,
|
|
41
|
+
:supported_versions,
|
|
42
|
+
:request_timeout,
|
|
43
|
+
:connect_timeout,
|
|
44
|
+
keyword_init: true
|
|
45
|
+
) do
|
|
46
|
+
def initialize(**)
|
|
47
|
+
super
|
|
48
|
+
self.client_name ||= 'Manceps'
|
|
49
|
+
self.client_version ||= Manceps::VERSION
|
|
50
|
+
self.protocol_version ||= '2025-11-25'
|
|
51
|
+
self.supported_versions ||= %w[2025-11-25 2025-06-18 2025-03-26]
|
|
52
|
+
self.request_timeout ||= 30
|
|
53
|
+
self.connect_timeout ||= 10
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class << self
|
|
58
|
+
def configuration
|
|
59
|
+
@configuration ||= Configuration.new
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def configure
|
|
63
|
+
yield(configuration)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: manceps
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Obie Fernandez
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: httpx
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
40
|
+
description: A production-grade MCP client with first-class auth support. Connect
|
|
41
|
+
to MCP servers over Streamable HTTP or stdio, discover and invoke tools, read resources,
|
|
42
|
+
and get prompts.
|
|
43
|
+
email:
|
|
44
|
+
- obie@fernandez.net
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- CHANGELOG.md
|
|
50
|
+
- LICENSE
|
|
51
|
+
- README.md
|
|
52
|
+
- lib/manceps.rb
|
|
53
|
+
- lib/manceps/auth/api_key_header.rb
|
|
54
|
+
- lib/manceps/auth/bearer.rb
|
|
55
|
+
- lib/manceps/auth/none.rb
|
|
56
|
+
- lib/manceps/auth/oauth.rb
|
|
57
|
+
- lib/manceps/backoff.rb
|
|
58
|
+
- lib/manceps/client.rb
|
|
59
|
+
- lib/manceps/content.rb
|
|
60
|
+
- lib/manceps/elicitation.rb
|
|
61
|
+
- lib/manceps/errors.rb
|
|
62
|
+
- lib/manceps/json_rpc.rb
|
|
63
|
+
- lib/manceps/prompt.rb
|
|
64
|
+
- lib/manceps/prompt_result.rb
|
|
65
|
+
- lib/manceps/resource.rb
|
|
66
|
+
- lib/manceps/resource_contents.rb
|
|
67
|
+
- lib/manceps/resource_template.rb
|
|
68
|
+
- lib/manceps/session.rb
|
|
69
|
+
- lib/manceps/sse_parser.rb
|
|
70
|
+
- lib/manceps/task.rb
|
|
71
|
+
- lib/manceps/tool.rb
|
|
72
|
+
- lib/manceps/tool_result.rb
|
|
73
|
+
- lib/manceps/transport/base.rb
|
|
74
|
+
- lib/manceps/transport/stdio.rb
|
|
75
|
+
- lib/manceps/transport/streamable_http.rb
|
|
76
|
+
- lib/manceps/version.rb
|
|
77
|
+
homepage: https://github.com/zarpay/manceps
|
|
78
|
+
licenses:
|
|
79
|
+
- MIT
|
|
80
|
+
metadata:
|
|
81
|
+
homepage_uri: https://github.com/zarpay/manceps
|
|
82
|
+
source_code_uri: https://github.com/zarpay/manceps
|
|
83
|
+
changelog_uri: https://github.com/zarpay/manceps/blob/main/CHANGELOG.md
|
|
84
|
+
rubygems_mfa_required: 'true'
|
|
85
|
+
rdoc_options: []
|
|
86
|
+
require_paths:
|
|
87
|
+
- lib
|
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: 3.4.0
|
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: '0'
|
|
98
|
+
requirements: []
|
|
99
|
+
rubygems_version: 3.6.9
|
|
100
|
+
specification_version: 4
|
|
101
|
+
summary: Ruby client for the Model Context Protocol (MCP)
|
|
102
|
+
test_files: []
|