diff --git a/.changeset/fix-mcp-request-keyword-forwarding.md b/.changeset/fix-mcp-request-keyword-forwarding.md new file mode 100644 index 00000000..4e8e1522 --- /dev/null +++ b/.changeset/fix-mcp-request-keyword-forwarding.md @@ -0,0 +1,5 @@ +--- +"posthog-ruby": patch +--- + +Fix MCP instrumentation returning internal errors with mcp 1.8.0 by forwarding request keywords, including authentication context, to the MCP server. diff --git a/lib/posthog/mcp/server_extension.rb b/lib/posthog/mcp/server_extension.rb index 771d399b..0975be8b 100644 --- a/lib/posthog/mcp/server_extension.rb +++ b/lib/posthog/mcp/server_extension.rb @@ -11,7 +11,7 @@ module MCP module ServerExtension private - def handle_request(request, method, session: nil, related_request_id: nil) + def handle_request(request, method, session: nil, related_request_id: nil, **kwargs) handler = super data = PostHog::MCP.tracking_data(self) return handler unless data && handler.is_a?(Proc) && Instrumentation.tracked?(method) diff --git a/spec/posthog/mcp/instrument_spec.rb b/spec/posthog/mcp/instrument_spec.rb index 4b2f24af..32f7a92d 100644 --- a/spec/posthog/mcp/instrument_spec.rb +++ b/spec/posthog/mcp/instrument_spec.rb @@ -137,6 +137,36 @@ def initialize_request(id = 1, version = '2025-06-18') expect(drain_events(other_client)).to be_empty end + %i[handle handle_json].each do |entrypoint| + it "preserves auth_info through #{entrypoint} on instrumented and uninstrumented servers" do + skip 'The MCP gem does not support auth_info' unless server.method(entrypoint).parameters.include?(%i[key auth_info]) + + seen = [] + auth_info = Object.new + tool = Class.new(MCP::Tool) do + tool_name 'authenticated' + define_singleton_method(:call) do |server_context:| + seen << server_context.auth_info + MCP::Tool::Response.new([{ type: 'text', text: 'ok' }]) + end + end + instrumented = MCP::Server.new(name: 'instrumented', tools: [tool]) + uninstrumented = MCP::Server.new(name: 'uninstrumented', tools: [tool]) + described_class.instrument(instrumented, client, enable_conversation_id: false) + + [instrumented, uninstrumented].each do |target| + request = rpc(1, 'tools/call', { name: 'authenticated', arguments: {} }) + request = JSON.generate(request) if entrypoint == :handle_json + response = target.public_send(entrypoint, request, auth_info: auth_info) + response = JSON.parse(response, symbolize_names: true) if entrypoint == :handle_json + expect(response.dig(:result, :content)).to eq([{ type: 'text', text: 'ok' }]) + end + + expect(seen).to eq([auth_info, auth_info]) + expect(events_named(drain_events(client), '$mcp_tool_call').length).to eq(1) + end + end + it 'returns a no-op handle for unsupported servers and when no client is available' do allow(Kernel).to receive(:warn) allow(PostHog).to receive(:client).and_return(nil) if PostHog.respond_to?(:client)