Class: SolidLoop::Mcp::Principal

Inherits:
Object
  • Object
show all
Defined in:
lib/solid_loop/mcp/principal.rb

Overview

The authenticated caller identity returned by an Mcp::Server authorizer.

An authorizer responds to call(token, request) -> principal | nil. To bind sessions to the caller (so principal B cannot ride principal A's session_id) the server needs a STABLE identity key, not just a display label. This value object carries both:

key   — stable, opaque identity used for session ownership + audit. Two
      requests are the same principal iff their keys are equal.
label — human-readable name for the admin UI (may collide / change).

Authorizer contract: key MUST be stable across a principal's requests and unique per principal. Two callers whose keys collide (e.g. an authorizer returning "" or two records that stringify identically) would share a session namespace — the same trust boundary the authorizer already owns.

Backward compatibility: authorizers may still return a bare value (String, Symbol, or any object) and Principal.wrap derives a STABLE key from it:

* a Principal          — used as-is
* an object with        #key AND #label — those are the key/label
* an ActiveRecord-ish   object (responds to #to_global_id) — the GID URI
                      is the stable key; #to_s (or the GID) the label
* anything else         — `to_s` is BOTH key and label

The GID branch matters because a bare object's default #to_s embeds the memory address (unstable per request); using it as the session key would mean a caller could never reuse their own session. to_context always returns the ORIGINAL value, so tool handlers keep receiving what the authorizer returned (a String, a User record, …) unchanged.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key:, label:, principal:) ⇒ Principal

principal is the raw object the authorizer returned — handlers receive THIS via CallContext#principal, so existing handlers that expect the raw value (a String, a User, …) keep working unchanged.



69
70
71
72
73
# File 'lib/solid_loop/mcp/principal.rb', line 69

def initialize(key:, label:, principal:)
  @key       = key
  @label     = label
  @principal = principal
end

Instance Attribute Details

#keyObject (readonly)

Returns the value of attribute key.



34
35
36
# File 'lib/solid_loop/mcp/principal.rb', line 34

def key
  @key
end

#labelObject (readonly)

Returns the value of attribute label.



34
35
36
# File 'lib/solid_loop/mcp/principal.rb', line 34

def label
  @label
end

Class Method Details

.label_for(value, gid) ⇒ Object

Prefer a human-readable #to_s when the object overrides it (not the default "#Klass:0x..."); otherwise fall back to the GID.



61
62
63
64
# File 'lib/solid_loop/mcp/principal.rb', line 61

def self.label_for(value, gid)
  str = value.to_s
  str.start_with?("#<") ? gid : str
end

.stable_gid(value) ⇒ Object

The GlobalID URI for a persisted record, or nil (unpersisted records raise; non-GID objects don't respond) — the caller then falls back to the bare to_s key.



51
52
53
54
55
56
57
# File 'lib/solid_loop/mcp/principal.rb', line 51

def self.stable_gid(value)
  return nil unless value.respond_to?(:to_global_id)

  value.to_global_id.to_s
rescue StandardError
  nil
end

.wrap(value) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/solid_loop/mcp/principal.rb', line 36

def self.wrap(value)
  return value if value.is_a?(Principal)

  if value.respond_to?(:key) && value.respond_to?(:label)
    new(key: value.key.to_s, label: value.label.to_s, principal: value)
  elsif (gid = stable_gid(value))
    new(key: gid, label: label_for(value, gid), principal: value)
  else
    new(key: value.to_s, label: value.to_s, principal: value)
  end
end

Instance Method Details

#to_contextObject

What tool handlers see as ctx.principal. For a bare-value authorizer this is the original value (backward compatible); otherwise the wrapped object.



77
78
79
# File 'lib/solid_loop/mcp/principal.rb', line 77

def to_context
  @principal
end