APM integrations need to tag a span with the subject of the request, but the
user-defined `server_context` hash passed to `Server.new` never reaches
`around_request` or `instrumentation_callback`. The exception reporter's
`server_context` argument is a different value — it describes where a failure
occurred (`{ request: ... }`, `{ notification: ... }`), not who made the call —
so there is currently no way to read the request subject from a hook.
Add `Configuration#instrument_server_context`, off by default. When enabled,
`instrument_call` puts the host's `server_context` into the instrumentation data
before invoking `around_request`, so it is readable both before and after
`request_handler.call`.
The flag is opt-in rather than unconditional because the hash is
application-supplied and may hold values a tracing backend should not receive.
Hosts that do not expose a `server_context` reader are unaffected.
`instrument_call` reads it through `self.server_context`: the existing
`server_context:` keyword shadows the reader, and the value wanted here is the
user-defined hash, not the reporter context.
Follows the same shape as the earlier additions of `tool_arguments` (#218) and
`client` (#221) to this hash.
51 lines
1.9 KiB
Ruby
51 lines
1.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module MCP
|
|
module Instrumentation
|
|
def instrument_call(method, server_context: {}, exception_already_reported: nil, &block)
|
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
begin
|
|
@instrumentation_data = {}
|
|
add_instrumentation_data(method: method)
|
|
# `self.` is required: the `server_context:` keyword above shadows the
|
|
# reader, and the value we want here is the user-defined hash passed to
|
|
# `Server.new`, not the per-call reporter context.
|
|
if configuration.instrument_server_context? && respond_to?(:server_context)
|
|
add_instrumentation_data(server_context: self.server_context)
|
|
end
|
|
|
|
result = configuration.around_request.call(@instrumentation_data, &block)
|
|
|
|
result
|
|
rescue => e
|
|
already_reported = begin
|
|
!!exception_already_reported&.call(e)
|
|
rescue Exception
|
|
# The predicate is expected to be side-effect-free and return a boolean.
|
|
# Any exception raised from it (including non-StandardError such as SystemExit)
|
|
# must not shadow the original exception.
|
|
false
|
|
end
|
|
|
|
unless already_reported
|
|
add_instrumentation_data(error: :internal_error) unless @instrumentation_data.key?(:error)
|
|
configuration.exception_reporter.call(e, server_context)
|
|
end
|
|
|
|
raise
|
|
ensure
|
|
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
add_instrumentation_data(duration: end_time - start_time)
|
|
|
|
# Backward compatibility: `instrumentation_callback` is soft-deprecated
|
|
# in favor of `around_request`, but existing callers still expect it
|
|
# to fire after every request until it is removed in a future version.
|
|
configuration.instrumentation_callback.call(@instrumentation_data)
|
|
end
|
|
end
|
|
|
|
def add_instrumentation_data(**kwargs)
|
|
@instrumentation_data.merge!(kwargs)
|
|
end
|
|
end
|
|
end
|