mcpspan 0.1.0 → 0.2.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: 84a76f8debbf2cfc4830ae61f49f68e8bea556dee0176e1707415d19ec6fe7b9
4
- data.tar.gz: b7ab651d3b894265c4a7dd21d5827b31f5996f85cb734759e4e1fb420cb17dc6
3
+ metadata.gz: 996561d4546232205752457ea56cb9aa1570402a5f056b9578db0a6bc3a590d3
4
+ data.tar.gz: 846bdb5f3f05efac26377a3a52de6c0319c05acafa7b3e5a33cc808b63973590
5
5
  SHA512:
6
- metadata.gz: 84bfdf6a1e69cb02d126641811e12730e72c2166fdb507c1d2dad5e2dd17cb26ddee618b9776742a6a63be2a79c1ae891cc6666409abfd08d9f41626a3c3dd3e
7
- data.tar.gz: 235cbe6fbeae4a5e88c53048595c11dd8930a7a411a6c7c4fe449997393c9677181c26bc9d36e427d4f87f5efeab3c41d49114087c87cc1bdb68460e77ae8d3e
6
+ metadata.gz: 7295717445bbd5a6c57938769b3ebcb9d0185baf9d99f5f266387f900c7b86aa4881eadc31029232c6a754e4ca76b9419132524641b5c4d8c05ab9ddcbe6508f
7
+ data.tar.gz: 7758b51df47a9aa9c6956477dac680cf083f95a756f961c15116b316e7a17a1a35237daa954d151bdaf9cb0d502a15edd17c7127ef73fa8fc9f3912c0c030bd9
data/README.md CHANGED
@@ -148,8 +148,9 @@ read from the server's wording.
148
148
  mode, not in debug.
149
149
 
150
150
  What is collected: the tool name, how long it took, whether it succeeded, the
151
- error type and a truncated message when it did not, which client called, and
152
- the SDK version. For a resource or a prompt, the same, under the name it was
151
+ error type and a truncated message when it did not, how large the answer was
152
+ in bytes (its size only, never its content), which client called, and the SDK
153
+ version. For a resource or a prompt, the same, under the name it was
153
154
  registered with: never the address a client read, only its template or, for
154
155
  an address the server does not have, its scheme.
155
156
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
3
4
  require "securerandom"
4
5
 
5
6
  module McpSpan
@@ -103,8 +104,23 @@ module McpSpan
103
104
  )
104
105
  end
105
106
 
106
- # Builds the event for a finished call and queues it. It never blocks on the network.
107
- def record(call, success:, source: nil, type: nil, message: nil)
107
+ # The largest size an event carries; anything larger is sent as this (contract, 3.7).
108
+ MAX_RESPONSE_BYTES = 2_147_483_647
109
+
110
+ # Size of an answer in bytes of its compact JSON (contract, 3.7), or nil when there is none or it cannot be
111
+ # encoded. The JSON is counted and dropped; nothing of it is kept or sent.
112
+ def response_bytes(response)
113
+ return nil if response.nil?
114
+
115
+ value = response.is_a?(Hash) || response.is_a?(Array) || response.is_a?(String) ? response : response.to_h
116
+ [JSON.generate(value).bytesize, MAX_RESPONSE_BYTES].min
117
+ rescue StandardError
118
+ nil
119
+ end
120
+
121
+ # Builds the event for a finished call and queues it. It never blocks on the network. `response` is the answer,
122
+ # when there was one, to be measured.
123
+ def record(call, success:, source: nil, type: nil, message: nil, response: nil)
108
124
  duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - call.started) * 1000.0
109
125
  reporter = @reporter
110
126
  return if reporter.nil?
@@ -122,6 +138,7 @@ module McpSpan
122
138
  client_name: Text.client_name(call.client_name),
123
139
  client_version: Text.version(call.client_version),
124
140
  server_version: Text.version(call.server_version),
141
+ response_bytes: response_bytes(response),
125
142
  timestamp: call.timestamp.strftime("%Y-%m-%dT%H:%M:%S.%LZ"),
126
143
  session_id: call.session_id,
127
144
  parameters: call.parameters,
data/lib/mcpspan/event.rb CHANGED
@@ -6,7 +6,8 @@ module McpSpan
6
6
  # One call of a tool, a resource or a prompt, in the shape the ingest API takes. Parameter values are never in it.
7
7
  Event = Struct.new(
8
8
  :id, :kind, :tool_name, :duration_ms, :success, :error_source, :error_type, :error_message,
9
- :client_type, :client_name, :client_version, :server_version, :timestamp, :session_id, :parameters,
9
+ :client_type, :client_name, :client_version, :server_version, :response_bytes, :timestamp, :session_id,
10
+ :parameters,
10
11
  keyword_init: true,
11
12
  ) do
12
13
  # The event as the API takes it, leaving absent fields out rather than sending them as null.
@@ -24,6 +25,7 @@ module McpSpan
24
25
  clientName: client_name,
25
26
  clientVersion: client_version,
26
27
  serverVersion: server_version,
28
+ responseBytes: response_bytes,
27
29
  timestamp: timestamp,
28
30
  sdkVersion: VERSION,
29
31
  sessionId: session_id,
@@ -172,11 +172,12 @@ module McpSpan
172
172
  def __mcpspan_settled(call, state, result)
173
173
  # An interim result asking the client for input settles nothing; the call that follows it does.
174
174
  return if Instrumentation.interim?(result)
175
- return Collector.record(call, success: true) unless result.is_a?(Hash) && result[:isError]
175
+ return Collector.record(call, success: true, response: result) unless result.is_a?(Hash) && result[:isError]
176
176
 
177
177
  # The gem refuses missing or invalid arguments with an error result, before the tool is called.
178
178
  if state.reached
179
- Collector.record(call, success: false, source: Source::RESULT, message: Instrumentation.result_text(result))
179
+ Collector.record(call, success: false, source: Source::RESULT, message: Instrumentation.result_text(result),
180
+ response: result,)
180
181
  else
181
182
  Collector.record(call, success: false, source: Source::ARGUMENTS)
182
183
  end
@@ -204,9 +205,10 @@ module McpSpan
204
205
  if call
205
206
  hash = result.respond_to?(:to_h) ? result.to_h : {}
206
207
  if hash[:isError]
207
- Collector.record(call, success: false, source: Source::RESULT, message: Instrumentation.result_text(hash))
208
+ Collector.record(call, success: false, source: Source::RESULT, message: Instrumentation.result_text(hash),
209
+ response: hash,)
208
210
  elsif !Instrumentation.interim?(result)
209
- Collector.record(call, success: true)
211
+ Collector.record(call, success: true, response: hash)
210
212
  end
211
213
  end
212
214
  result
@@ -141,7 +141,7 @@ module McpSpan
141
141
 
142
142
  def __mcpspan_succeeded(call, result)
143
143
  # An interim result asking the client for input settles nothing; the request that follows it does.
144
- Collector.record(call, success: true) unless Instrumentation.interim?(result)
144
+ Collector.record(call, success: true, response: result) unless Instrumentation.interim?(result)
145
145
  rescue *Instrumentation::INTERNAL
146
146
  nil
147
147
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module McpSpan
4
4
  # The SDK's own version, reported with every event.
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mcpspan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kacper Zatoń