html2md_mcp_client 0.2.1 → 0.3.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d360caadc9ac04f0a3218ea55a175274ddf0f43d257b60527b9eaeea83aa09a8
|
|
4
|
+
data.tar.gz: 5f69cf8d255175bb17f8a56071b792a8f90edd1ef18b0d3daa9270f8c9b2afaa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 499b6cfabd2f51ebeb7461aec041b7624325a2c5a187ddd624372f0f54124a65c664d13ea376303c7938cbf15c4d29c0033cfbfdf0a15a9b0bf6280e1765c76c
|
|
7
|
+
data.tar.gz: fd418de434d52c2b8475ded376e8f21db91ceaf49d269817c24ec4cae559ba1942b83fa9afb12e431c4d052dda52fae6abe50ba44fd9749d846c17db797950be
|
|
@@ -61,13 +61,15 @@ module Html2mdMcpClient
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# Call a tool. Returns the content array.
|
|
64
|
-
# Raises ToolError if the server signals an error
|
|
64
|
+
# Raises ToolError if the server signals an error or if text content
|
|
65
|
+
# begins with "Error" (some servers omit the isError flag).
|
|
65
66
|
def call_tool(name, arguments = {})
|
|
66
67
|
ensure_connected!
|
|
67
68
|
result = request('tools/call', { name: name, arguments: arguments })
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
texts = Array(result['content']).select { |c| c['type'] == 'text' }.map { |c| c['text'] }
|
|
71
|
+
|
|
72
|
+
if result['isError'] || texts.any? { |t| t.start_with?('Error') }
|
|
71
73
|
raise ToolError, "Tool '#{name}' error: #{texts.join('; ')}"
|
|
72
74
|
end
|
|
73
75
|
|
|
@@ -75,11 +77,15 @@ module Html2mdMcpClient
|
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
# Convenience: call a tool and return joined text content.
|
|
80
|
+
# Raises ToolError if no text content is returned.
|
|
78
81
|
def tool_text(name, arguments = {})
|
|
79
|
-
call_tool(name, arguments)
|
|
82
|
+
texts = call_tool(name, arguments)
|
|
80
83
|
.select { |c| c['type'] == 'text' }
|
|
81
84
|
.map { |c| c['text'] }
|
|
82
|
-
|
|
85
|
+
|
|
86
|
+
raise ToolError, "Tool '#{name}' returned no text content" if texts.empty?
|
|
87
|
+
|
|
88
|
+
texts.join("\n")
|
|
83
89
|
end
|
|
84
90
|
|
|
85
91
|
# Find a tool definition by name. Returns nil if not found.
|
|
@@ -34,7 +34,9 @@ module Html2mdMcpClient
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
JSON.parse(body)
|
|
37
|
-
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
|
|
37
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH,
|
|
38
|
+
Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EPIPE,
|
|
39
|
+
SocketError, Net::OpenTimeout, Net::ReadTimeout => e
|
|
38
40
|
raise ConnectionError, "Cannot connect to #{@uri}: #{e.message}"
|
|
39
41
|
rescue JSON::ParserError => e
|
|
40
42
|
raise ProtocolError, "Invalid JSON response: #{e.message}"
|
|
@@ -171,6 +171,17 @@ RSpec.describe Html2mdMcpClient::Client do
|
|
|
171
171
|
|
|
172
172
|
expect(client.call_tool('empty_tool')).to eq([])
|
|
173
173
|
end
|
|
174
|
+
|
|
175
|
+
it 'raises ToolError when text content starts with Error even without isError flag' do
|
|
176
|
+
allow(transport).to receive(:send_request)
|
|
177
|
+
.with(hash_including(method: 'tools/call'))
|
|
178
|
+
.and_return(jsonrpc_response(2, {
|
|
179
|
+
'content' => [{ 'type' => 'text', 'text' => 'Error fetching URL: Connection error while fetching URL: https://bad.invalid' }]
|
|
180
|
+
}))
|
|
181
|
+
|
|
182
|
+
expect { client.call_tool('html_to_markdown', { url: 'https://bad.invalid' }) }
|
|
183
|
+
.to raise_error(Html2mdMcpClient::ToolError, /Error fetching URL/)
|
|
184
|
+
end
|
|
174
185
|
end
|
|
175
186
|
|
|
176
187
|
describe '#tool_text' do
|
|
@@ -189,6 +200,24 @@ RSpec.describe Html2mdMcpClient::Client do
|
|
|
189
200
|
|
|
190
201
|
expect(client.tool_text('my_tool')).to eq("Line 1\nLine 2")
|
|
191
202
|
end
|
|
203
|
+
|
|
204
|
+
it 'raises ToolError when no text content is returned' do
|
|
205
|
+
allow(transport).to receive(:send_request)
|
|
206
|
+
.with(hash_including(method: 'tools/call'))
|
|
207
|
+
.and_return(jsonrpc_response(2, {}))
|
|
208
|
+
|
|
209
|
+
expect { client.tool_text('bad_tool') }.to raise_error(Html2mdMcpClient::ToolError, /returned no text content/)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it 'raises ToolError when content has no text entries' do
|
|
213
|
+
content = [{ 'type' => 'image', 'data' => 'base64...' }]
|
|
214
|
+
|
|
215
|
+
allow(transport).to receive(:send_request)
|
|
216
|
+
.with(hash_including(method: 'tools/call'))
|
|
217
|
+
.and_return(jsonrpc_response(2, { 'content' => content }))
|
|
218
|
+
|
|
219
|
+
expect { client.tool_text('img_tool') }.to raise_error(Html2mdMcpClient::ToolError, /returned no text content/)
|
|
220
|
+
end
|
|
192
221
|
end
|
|
193
222
|
|
|
194
223
|
describe '#find_tool' do
|
|
@@ -81,6 +81,51 @@ RSpec.describe Html2mdMcpClient::Transport::Http do
|
|
|
81
81
|
.to raise_error(Html2mdMcpClient::ConnectionError, /Cannot connect/)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
+
it 'raises ConnectionError on open timeout' do
|
|
85
|
+
stub_request(:post, url).to_raise(Net::OpenTimeout)
|
|
86
|
+
|
|
87
|
+
expect { transport.send_request({ jsonrpc: '2.0', id: 1, method: 'test', params: {} }) }
|
|
88
|
+
.to raise_error(Html2mdMcpClient::ConnectionError, /Cannot connect/)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'raises ConnectionError on read timeout' do
|
|
92
|
+
stub_request(:post, url).to_raise(Net::ReadTimeout)
|
|
93
|
+
|
|
94
|
+
expect { transport.send_request({ jsonrpc: '2.0', id: 1, method: 'test', params: {} }) }
|
|
95
|
+
.to raise_error(Html2mdMcpClient::ConnectionError, /Cannot connect/)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'raises ConnectionError on network unreachable' do
|
|
99
|
+
stub_request(:post, url).to_raise(Errno::ENETUNREACH)
|
|
100
|
+
|
|
101
|
+
expect { transport.send_request({ jsonrpc: '2.0', id: 1, method: 'test', params: {} }) }
|
|
102
|
+
.to raise_error(Html2mdMcpClient::ConnectionError, /Cannot connect/)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'raises ConnectionError on connection reset' do
|
|
106
|
+
stub_request(:post, url).to_raise(Errno::ECONNRESET)
|
|
107
|
+
|
|
108
|
+
expect { transport.send_request({ jsonrpc: '2.0', id: 1, method: 'test', params: {} }) }
|
|
109
|
+
.to raise_error(Html2mdMcpClient::ConnectionError, /Cannot connect/)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'raises ConnectionError on connection timed out' do
|
|
113
|
+
stub_request(:post, url).to_raise(Errno::ETIMEDOUT)
|
|
114
|
+
|
|
115
|
+
expect { transport.send_request({ jsonrpc: '2.0', id: 1, method: 'test', params: {} }) }
|
|
116
|
+
.to raise_error(Html2mdMcpClient::ConnectionError, /Cannot connect/)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'raises ConnectionError on unresolvable host' do
|
|
120
|
+
t = described_class.new('http://this-host-does-not-exist.invalid/mcp')
|
|
121
|
+
|
|
122
|
+
stub_request(:post, 'http://this-host-does-not-exist.invalid/mcp')
|
|
123
|
+
.to_raise(SocketError.new('getaddrinfo: Name or service not known'))
|
|
124
|
+
|
|
125
|
+
expect { t.send_request({ jsonrpc: '2.0', id: 1, method: 'test', params: {} }) }
|
|
126
|
+
.to raise_error(Html2mdMcpClient::ConnectionError, /Cannot connect/)
|
|
127
|
+
end
|
|
128
|
+
|
|
84
129
|
it 'raises ProtocolError on invalid JSON' do
|
|
85
130
|
stub_request(:post, url).to_return(status: 200, body: 'not json')
|
|
86
131
|
|