Module: PostHog::MCP::SessionToken Private

Defined in:
lib/posthog/mcp/session_token.rb

Overview

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

Self-encoded session tokens for stateless / multi-pod MCP servers.

A stateless server keeps nothing between requests, so every request would start a new session and the client identity (only sent at initialize) would be lost. Clients replay the Mcp-Session-Id header on every request, so at initialize we mint that header as an unsigned base64url(JSON) token with short keys (sid, cn, cv, pv).

Constant Summary collapse

MAX_TOKEN_LENGTH =

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.

4096
MAX_SESSION_ID_LENGTH =

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.

128
MAX_CLIENT_FIELD_LENGTH =

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.

200
BASE64URL_PATTERN =

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[A-Za-z0-9_-]+={0,2}\z/

Class Method Summary collapse

Class Method Details

.decode(value) ⇒ SessionTokenPayload?

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.

Decode an Mcp-Session-Id value. Returns nil for anything that is not one of our tokens (transport UUIDs, JWTs, garbage) and never raises.

Returns:



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

def decode(value)
  return nil unless value.is_a?(String) && !value.empty? && value.length <= MAX_TOKEN_LENGTH
  return nil unless BASE64URL_PATTERN.match?(value)

  parsed = begin
    JSON.parse(Base64.urlsafe_decode64(value.delete('=')))
  rescue ArgumentError, JSON::ParserError, EncodingError
    nil
  end
  return nil unless parsed.is_a?(Hash)

  sid = parsed['sid']
  return nil unless sid.is_a?(String) && !sid.empty? && sid.length <= MAX_SESSION_ID_LENGTH

  payload = SessionTokenPayload.new(session_id: sid)
  payload.client_name = parsed['cn'][0, MAX_CLIENT_FIELD_LENGTH] if present_string?(parsed['cn'])
  payload.client_version = parsed['cv'][0, MAX_CLIENT_FIELD_LENGTH] if present_string?(parsed['cv'])
  payload.protocol_version = parsed['pv'][0, MAX_CLIENT_FIELD_LENGTH] if present_string?(parsed['pv'])
  payload
end

.encode(payload) ⇒ String

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 token for the Mcp-Session-Id response header.

Parameters:

Returns:

  • (String) —

    token for the Mcp-Session-Id response header

Raises:

  • (ArgumentError) —

    when session_id is missing or empty



40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/posthog/mcp/session_token.rb', line 40

def encode(payload)
  payload = SessionTokenPayload.new(**payload) if payload.is_a?(Hash)
  session_id = payload.session_id
  unless session_id.is_a?(String) && !session_id.empty?
    raise ArgumentError, 'encode_session_id requires a non-empty `session_id` (use new_session_id())'
  end

  wire = { 'sid' => session_id }
  wire['cn'] = payload.client_name[0, MAX_CLIENT_FIELD_LENGTH] if present_string?(payload.client_name)
  wire['cv'] = payload.client_version[0, MAX_CLIENT_FIELD_LENGTH] if present_string?(payload.client_version)
  wire['pv'] = payload.protocol_version[0, MAX_CLIENT_FIELD_LENGTH] if present_string?(payload.protocol_version)
  Base64.urlsafe_encode64(JSON.generate(wire), padding: false)
end

.read_header(headers) ⇒ 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.

Read the mcp-session-id value off a headers Hash (case-insensitive keys, array values, trimmed). Returns nil when absent or blank.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/posthog/mcp/session_token.rb', line 81

def read_header(headers)
  return nil unless headers.respond_to?(:each_pair)

  value = headers[MCP_SESSION_HEADER]
  if value.nil?
    headers.each_pair do |key, candidate|
      next unless key.is_a?(String) && key.downcase == MCP_SESSION_HEADER

      value = candidate
      break
    end
  end
  value = value.first if value.is_a?(Array)
  return nil unless value.is_a?(String)

  trimmed = value.strip
  trimmed.empty? ? nil : trimmed
end