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 +4 -4
- data/README.md +3 -2
- data/lib/mcpspan/collector.rb +19 -2
- data/lib/mcpspan/event.rb +3 -1
- data/lib/mcpspan/instrumentation.rb +6 -4
- data/lib/mcpspan/primitives.rb +1 -1
- data/lib/mcpspan/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 996561d4546232205752457ea56cb9aa1570402a5f056b9578db0a6bc3a590d3
|
|
4
|
+
data.tar.gz: 846bdb5f3f05efac26377a3a52de6c0319c05acafa7b3e5a33cc808b63973590
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,
|
|
152
|
-
|
|
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
|
|
data/lib/mcpspan/collector.rb
CHANGED
|
@@ -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
|
-
#
|
|
107
|
-
|
|
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, :
|
|
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
|
data/lib/mcpspan/primitives.rb
CHANGED
|
@@ -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
|
data/lib/mcpspan/version.rb
CHANGED