Class: PostHog::MCP::Instrumentation Private

Inherits:
Object
  • Object
show all
Defined in:
lib/posthog/mcp/instrumentation.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

One JSON-RPC request's analytics lifecycle. Created by ServerExtension around the dispatch lambda MCP::Server#handle_request returns, so it sees the raw request, the params Hash the handler will receive (and may strip injected arguments from it), the session, the result, and any raised error. Everything it needs travels through its own instance variables; nothing is read from the gem's @instrumentation_data.

Analytics failures are logged and swallowed: the handler's result or exception is always returned or re-raised unchanged.

Constant Summary collapse

TRACKED_METHODS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

{
  'initialize' => :initialize,
  'tools/list' => :tools_list,
  'tools/call' => :tools_call,
  'prompts/get' => :prompts_get,
  'prompts/list' => :prompts_list,
  'resources/read' => :resources_read,
  'resources/list' => :resources_list
}.freeze
GENERIC_EVENT_TYPES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

{
  prompts_get: EventType::MCP_PROMPTS_GET,
  prompts_list: EventType::MCP_PROMPTS_LIST,
  resources_read: EventType::MCP_RESOURCES_READ,
  resources_list: EventType::MCP_RESOURCES_LIST
}.freeze
MODERN_PROTOCOL_REVISION =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

'2026-07-28'
REVISION_SHAPE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

/\A\d{4}-\d{2}-\d{2}\z/
DRAFT_REVISION =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

'draft'
META_CLIENT_INFO_KEY =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

'io.modelcontextprotocol/clientInfo'
META_PROTOCOL_VERSION_KEY =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

'io.modelcontextprotocol/protocolVersion'
INJECTED_PARAMS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

['context', ConversationId::PARAM_NAME, ModelCapture::PARAM_NAME].freeze
UNRESOLVED_ACTOR =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Passed as actor: by a caller that has no request identity to offer, and is distinct from an explicit nil (a request that resolved to nobody).

Object.new.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(server, data, method:, request:, params:, session: nil, request_id: nil) ⇒ Instrumentation

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Instrumentation.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/posthog/mcp/instrumentation.rb', line 93

def initialize(server, data, method:, request:, params:, session: nil, request_id: nil)
  @server = server
  @data = data
  @options = data.options
  @kind = TRACKED_METHODS.fetch(method)
  @method = method
  @request = request.is_a?(Hash) ? request : {}
  @params = params.is_a?(Hash) ? params : {}
  @session = session
  @request_id = request_id
  @scope = RequestScope.current
  @headers = @scope ? (@scope[:headers] || {}) : {}
  @token = SessionToken.decode(header_session_id)
  @data.http_transport_seen = true if http?
  @identified_in_request = {}
end

Class Method Details

.capture_event(data, input, actor: UNRESOLVED_ACTOR) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Enrich an event with session/identity/server metadata and hand it to the sink.

Parameters:

  • actor (UserIdentity, nil, Object) (defaults to: UNRESOLVED_ACTOR) —

    the identity resolved for the request this event belongs to, pinned when the request started. Only UNRESOLVED_ACTOR falls back to the session-keyed cache, which a concurrent request on the same session may have moved on since.



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/posthog/mcp/instrumentation.rb', line 57

def capture_event(data, input, actor: UNRESOLVED_ACTOR)
  sink = data.sink
  return nil if sink.nil?

  session_id = input['session_id'] || data.session_id
  actor = (session_id ? data.identified_sessions.get(session_id) : nil) if actor.equal?(UNRESOLVED_ACTOR)
  timestamp = input['timestamp'] || Time.now.utc
  duration = input['duration']
  duration = (Time.now - timestamp) * 1000.0 if duration.nil? && input['timestamp']

  full = input.merge(
    'session_id' => session_id,
    'event_type' => input['event_type'] || EventType::CUSTOM,
    'timestamp' => timestamp,
    'duration' => duration,
    'server_name' => data.server_name,
    'server_version' => data.server_version,
    'identify_actor_given_id' => actor&.distinct_id,
    'identify_actor_data' => actor ? (actor.properties || {}) : {},
    'groups' => actor&.groups
  )
  sink.capture(full, data.options)
end

.legacy_era?(protocol_version) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


85
86
87
88
89
90
# File 'lib/posthog/mcp/instrumentation.rb', line 85

def legacy_era?(protocol_version)
  return true unless protocol_version.is_a?(String) && !protocol_version.empty?
  return false if protocol_version == DRAFT_REVISION

  !(REVISION_SHAPE.match?(protocol_version) && protocol_version >= MODERN_PROTOCOL_REVISION)
end

.monotonic_now ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



81
82
83
# File 'lib/posthog/mcp/instrumentation.rb', line 81

def monotonic_now
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
end

.tracked?(method) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


46
47
48
# File 'lib/posthog/mcp/instrumentation.rb', line 46

def tracked?(method)
  TRACKED_METHODS.key?(method)
end

Instance Method Details

#dispatch(&handler) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Runs the wrapped handler and records the request.



111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/posthog/mcp/instrumentation.rb', line 111

def dispatch(&handler)
  @start = self.class.monotonic_now
  return dispatch_kind(&handler) if @scope

  # Only the Streamable HTTP transport publishes a scope. Opening one for
  # every other transport too (stdio, a custom dispatcher) means the session
  # settled before the tool body runs is the one {Analytics#capture} reads
  # inside it, whatever the request is anchored on.
  RequestScope.with(headers: {}, transport: :other) do |scope|
    @scope = scope
    dispatch_kind(&handler)
  end
end