Class: SolidLoop::Mcp::Principal
- Inherits:
-
Object
- Object
- SolidLoop::Mcp::Principal
- 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
-
#key ⇒ Object
readonly
Returns the value of attribute key.
-
#label ⇒ Object
readonly
Returns the value of attribute label.
Class Method Summary collapse
-
.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.
-
.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.
- .wrap(value) ⇒ Object
Instance Method Summary collapse
-
#initialize(key:, label:, principal:) ⇒ Principal
constructor
principalis 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. -
#to_context ⇒ Object
What tool handlers see as ctx.principal.
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
#key ⇒ Object (readonly)
Returns the value of attribute key.
34 35 36 |
# File 'lib/solid_loop/mcp/principal.rb', line 34 def key @key end |
#label ⇒ Object (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_context ⇒ Object
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 |