datagrout-conduit 0.8.0 → 0.8.2

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: c586cb9aae6cf748677d41c1b453bc4ed896e440b68d43451a50c5ed3333282d
4
- data.tar.gz: bda496e4dc74051928c73115e6c69a23515e3b930ba82ccbe5bfedd12131e00b
3
+ metadata.gz: b5f3083e66b890a25b3fe77e4fd86e7e72ba3d86af8a530477ad93334de26f19
4
+ data.tar.gz: 16bb086f9c923bacad3fe8fc228eb5ea024a475a33bba51f320aad00fa032f56
5
5
  SHA512:
6
- metadata.gz: 856a644371b034af39a9e6caf6ea15ff6c088c0d504c0ef22094f35f70f46eadf7bf6ed85967a14484872f7a8d49f1e2064400d34325c8700032dfbb3d45f134
7
- data.tar.gz: 4f34ee08b4d4e126bde2a8b053ec2f9443ae31f1b1aa332eac6649d88944a736a3f31f6ac87880a685f246a4a68fab56c1bb86f720fa1e0adb5df6d08b2d48df
6
+ metadata.gz: a38b2be0474f517414da7731565fac6a4327664f84b7b95f9e04d478f29183087ac4da84899fbba952bbba11244386735ecd564c1a422038c8baef3b45141d10
7
+ data.tar.gz: 82f0c8a811e8165f1a5c91dcc5c3f93121115cc49d9969774e6b0a2c65d63afb2cf34d646c1042039f52182db2bca2084982d75dee76c56c0ad612405132ec66
data/README.md CHANGED
@@ -9,13 +9,13 @@ Connect to remote MCP and JSONRPC servers, invoke tools, discover capabilities w
9
9
  Add to your Gemfile:
10
10
 
11
11
  ```ruby
12
- gem "datagrout-conduit", "~> 0.8.0"
12
+ gem "datagrout-conduit", "~> 0.8.1"
13
13
  ```
14
14
 
15
15
  Or install directly:
16
16
 
17
17
  ```sh
18
- gem install datagrout-conduit -v 0.8.0
18
+ gem install datagrout-conduit -v 0.8.1
19
19
  ```
20
20
 
21
21
  ## Quick Start
@@ -310,7 +310,7 @@ Supported topics:
310
310
  | `flows.<flow_id>.*` | `flow.into` progress and completion |
311
311
  | `governor.<server_uuid>` | Governor percept events (file change, schedule, webhook) |
312
312
 
313
- **Reconnection**: after a disconnect, `send_request` and `subscribe` raise `DatagroutConduit::NotInitializedError`. Call `client.connect` again and re-subscribe — subscriptions do not survive reconnects in v0.4.
313
+ **Reconnection**: after a disconnect, `send_request` and `subscribe` raise `DatagroutConduit::NotInitializedError`. Call `client.connect` again and re-subscribe — subscriptions do not survive reconnects in v0.4. A connection ends when the server closes it, a read fails, or a keepalive ping goes unanswered for a whole interval; in-flight calls then fail and every subscription's `each` ends.
314
314
 
315
315
  ## Standard MCP Methods
316
316
 
@@ -173,6 +173,20 @@ module DatagroutConduit
173
173
  # Standard MCP Methods
174
174
  # ================================================================
175
175
 
176
+ # Whether a tool name is one of DataGrout's own (the semantic discovery
177
+ # and execution tools) rather than a third-party integration tool.
178
+ #
179
+ # Third-party tools are named +integration@version/tool@version+. DataGrout's
180
+ # own tools reach a client in two spellings depending on the transport: the
181
+ # canonical +data-grout@1/discovery.perform@1+ over WebSocket, the short
182
+ # +discovery.perform+ over HTTP MCP. Treating "contains @" as "third party"
183
+ # kept only the short form, so a WebSocket client with the intelligent
184
+ # interface on saw zero tools.
185
+ def self.dg_native_tool?(name)
186
+ name = name.to_s
187
+ !name.include?("@") || name.start_with?("data-grout@", "data-grout/")
188
+ end
189
+
176
190
  def list_tools
177
191
  ensure_initialized!
178
192
 
@@ -194,7 +208,7 @@ module DatagroutConduit
194
208
  end
195
209
 
196
210
  if @use_intelligent_interface
197
- all_tools.reject! { |t| t.name.include?("@") }
211
+ all_tools.select! { |t| self.class.dg_native_tool?(t.name) }
198
212
  end
199
213
 
200
214
  all_tools
@@ -442,6 +442,19 @@ module DatagroutConduit
442
442
  subs.each_value { |list| list.each(&:_close) }
443
443
  end
444
444
 
445
+ # The peer stopped answering pings: release every waiter as a close
446
+ # does, then close the socket so the read thread stops too.
447
+ def peer_gone
448
+ was_connected = @mutex.synchronize do
449
+ old = @connected
450
+ @connected = false
451
+ old
452
+ end
453
+
454
+ fail_all_pending(:ping_timeout) if was_connected
455
+ @io&.close rescue nil
456
+ end
457
+
445
458
  def cleanup_socket
446
459
  stop_ping_thread
447
460
  @write_mutex.synchronize do
@@ -458,11 +471,17 @@ module DatagroutConduit
458
471
  # Start the background ping thread. Sends one WS ping frame every
459
472
  # @ping_interval seconds until the connection is torn down. No-op when
460
473
  # @ping_interval is non-positive (used by tests to disable pinging).
474
+ #
475
+ # A ping still unanswered when the next is due closes the connection: a
476
+ # peer that vanished without a close frame (a dropped NAT mapping, a
477
+ # host that went away) is otherwise invisible until the OS gives up on
478
+ # the TCP connection.
461
479
  def start_ping_thread
462
480
  return if @ping_thread
463
481
  return if @ping_interval <= 0
464
482
 
465
483
  interval = @ping_interval
484
+ @awaiting_pong = false
466
485
  @ping_thread = Thread.new do
467
486
  loop do
468
487
  sleep interval
@@ -471,15 +490,26 @@ module DatagroutConduit
471
490
  driver = @driver
472
491
  break unless driver
473
492
 
493
+ if @awaiting_pong
494
+ peer_gone
495
+ break
496
+ end
497
+
474
498
  begin
475
499
  # WebSocket::Driver#ping returns false if the connection is
476
- # already closing; treat that as "stop the loop" too.
477
- @write_mutex.synchronize { driver.ping } || break
500
+ # already closing; treat that as "stop the loop" too. Its block
501
+ # runs when the matching pong arrives.
502
+ @awaiting_pong = true
503
+ @write_mutex.synchronize { driver.ping { @awaiting_pong = false } } || break
478
504
  @pings_sent += 1
479
505
  rescue StandardError
480
506
  break
481
507
  end
482
508
  end
509
+ ensure
510
+ # A loop that ended on its own must not stop the next connect from
511
+ # starting a fresh one.
512
+ @ping_thread = nil if @ping_thread == Thread.current
483
513
  end
484
514
  @ping_thread.abort_on_exception = false
485
515
  @ping_thread.name = "conduit-ws-ping"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DatagroutConduit
4
- VERSION = "0.8.0"
4
+ VERSION = "0.8.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datagrout-conduit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - DataGrout
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-09-10 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: faraday
@@ -162,7 +161,6 @@ metadata:
162
161
  source_code_uri: https://github.com/DataGrout/conduit-sdk/tree/main/ruby
163
162
  changelog_uri: https://github.com/DataGrout/conduit-sdk/blob/main/CHANGELOG.md
164
163
  bug_tracker_uri: https://github.com/DataGrout/conduit-sdk/issues
165
- post_install_message:
166
164
  rdoc_options: []
167
165
  require_paths:
168
166
  - lib
@@ -177,8 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
175
  - !ruby/object:Gem::Version
178
176
  version: '0'
179
177
  requirements: []
180
- rubygems_version: 3.5.22
181
- signing_key:
178
+ rubygems_version: 4.0.16
182
179
  specification_version: 4
183
180
  summary: Production-ready MCP client with mTLS, OAuth 2.1, and semantic discovery
184
181
  test_files: []