Add around_request hook for request instrumentation

## Motivation and Context

`instrumentation_callback` fires after request execution, making it impossible to wrap
requests with Application Performance Monitoring (APM) spans (e.g. Datadog tracing).
This adds `around_request`, which wraps request handling and allows executing code
before and after each request.

For example, to wrap requests with a Datadog trace:

```ruby
MCP.configure do |config|
  config.around_request = ->(data, &request_handler) {
    Datadog::Tracing.trace("mcp.#{data[:method]}") do
      request_handler.call
    end
  }
end
```

`instrumentation_callback` is soft-deprecated in favor of `around_request`.

## How Has This Been Tested?

Added tests for `around_request` in configuration, instrumentation,
and server test files. All existing tests continue to pass.

## Breaking Changes

`around_request` itself is optional and defaults to a pass-through,
and `instrumentation_callback` continues to work as before.

However, `instrument_call` now accepts a `server_context:` keyword and its block is invoked
without arguments (previously `yield block` passed the block Proc as an argument).
This affects anyone mixing `MCP::Instrumentation` into their own classes,
though the public `Server` API is unchanged.

Resolves #302.
This commit is contained in:
Koichi ITO 2026-04-10 05:05:08 +09:00
commit fab6f81261

View file

@ -123,6 +123,53 @@ module MCP
refute merged.validate_tool_call_arguments
end
test "initializes with a default pass-through around_request" do
config = Configuration.new
called = false
config.around_request.call({}) { called = true }
assert called
end
test "allows setting a custom around_request" do
config = Configuration.new
call_log = []
config.around_request = ->(_data, &request_handler) {
call_log << :before
request_handler.call
call_log << :after
}
config.around_request.call({}) { call_log << :execute }
assert_equal([:before, :execute, :after], call_log)
end
test "around_request? returns false by default" do
config = Configuration.new
refute config.around_request?
end
test "around_request? returns true when set" do
config = Configuration.new
config.around_request = ->(_data, &request_handler) { request_handler.call }
assert config.around_request?
end
test "merge preserves around_request from other config" do
custom = ->(_data, &request_handler) { request_handler.call }
config1 = Configuration.new
config2 = Configuration.new(around_request: custom)
merged = config1.merge(config2)
assert_equal custom, merged.around_request
end
test "merge preserves around_request from self when other not set" do
custom = ->(_data, &request_handler) { request_handler.call }
config1 = Configuration.new(around_request: custom)
config2 = Configuration.new
merged = config1.merge(config2)
assert_equal custom, merged.around_request
end
test "raises ArgumentError when protocol_version is not a supported value" do
exception = assert_raises(ArgumentError) do
Configuration.new(protocol_version: "1999-12-31")