Expose the user-defined server_context in instrumentation data

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.
This commit is contained in:
seunghan Kim 2026-08-08 08:33:07 +09:00
commit 14e02da475
2 changed files with 158 additions and 3 deletions

View file

@ -33,7 +33,7 @@ module MCP
attr_writer :instrumentation_callback
def initialize(exception_reporter: nil, around_request: nil, instrumentation_callback: nil, protocol_version: nil,
validate_tool_call_arguments: true, validate_tool_call_results: false)
validate_tool_call_arguments: true, validate_tool_call_results: false, instrument_server_context: false)
@exception_reporter = exception_reporter
@around_request = around_request
@instrumentation_callback = instrumentation_callback
@ -43,9 +43,11 @@ module MCP
end
validate_value_of_validate_tool_call_arguments!(validate_tool_call_arguments)
validate_value_of_validate_tool_call_results!(validate_tool_call_results)
validate_value_of_instrument_server_context!(instrument_server_context)
@validate_tool_call_arguments = validate_tool_call_arguments
@validate_tool_call_results = validate_tool_call_results
@instrument_server_context = instrument_server_context
end
def protocol_version=(protocol_version)
@ -60,6 +62,16 @@ module MCP
@validate_tool_call_arguments = validate_tool_call_arguments
end
# Opt in to exposing the user-defined `server_context` in the
# `around_request` / `instrumentation_callback` data hash. Off by default:
# the hash is application-supplied and may hold values a tracing backend
# should not receive, so surfacing it has to be a deliberate choice.
def instrument_server_context=(instrument_server_context)
validate_value_of_instrument_server_context!(instrument_server_context)
@instrument_server_context = instrument_server_context
end
def validate_tool_call_results=(validate_tool_call_results)
validate_value_of_validate_tool_call_results!(validate_tool_call_results)
@ -107,6 +119,10 @@ module MCP
attr_reader :validate_tool_call_arguments
attr_reader :validate_tool_call_results
def instrument_server_context?
!!@instrument_server_context
end
def validate_tool_call_arguments?
!!@validate_tool_call_arguments
end
@ -152,6 +168,7 @@ module MCP
protocol_version: protocol_version,
validate_tool_call_arguments: validate_tool_call_arguments,
validate_tool_call_results: validate_tool_call_results,
instrument_server_context: other.instrument_server_context?,
)
end
@ -176,6 +193,12 @@ module MCP
end
end
def validate_value_of_instrument_server_context!(instrument_server_context)
unless instrument_server_context.is_a?(TrueClass) || instrument_server_context.is_a?(FalseClass)
raise ArgumentError, "instrument_server_context must be a boolean"
end
end
def default_exception_reporter
@default_exception_reporter ||= ->(exception, server_context) {}
end

View file

@ -7,6 +7,12 @@ module MCP
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)