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: 77778d9f03e8ebdf5d815b1b3effe0016ba73f22f3128e38b1b5b282a9a5f344
4
- data.tar.gz: a34209ff17d9f23d649c01aabda40453f93465f439aafd17699363689cd9ca2e
3
+ metadata.gz: d360caadc9ac04f0a3218ea55a175274ddf0f43d257b60527b9eaeea83aa09a8
4
+ data.tar.gz: 5f69cf8d255175bb17f8a56071b792a8f90edd1ef18b0d3daa9270f8c9b2afaa
5
5
  SHA512:
6
- metadata.gz: 4335781e4e0a9a6f35a9014d6ad266f2759e8ddde8584757ad46f34c5dedf808aee2236fad96643715a14ca75ff805415cc33b1320395e4f4a104809afc83d95
7
- data.tar.gz: 4cb614d6ccc1354ef07e9f370385eb094fd02b0940c7f30f270ce4c3a1c7a94ee513a339a4b7ceec5c2a58cbefd6ade32dbd0906fcb69a62a8f0a76e6b22782e
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
- if result['isError']
70
- texts = Array(result['content']).select { |c| c['type'] == 'text' }.map { |c| c['text'] }
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
- .join("\n")
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, SocketError => e
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}"
@@ -1,3 +1,3 @@
1
1
  module Html2mdMcpClient
2
- VERSION = '0.2.1'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
@@ -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
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: html2md_mcp_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roscommon