Class: McpToolkit::Resource
- Inherits:
-
Object
- Object
- McpToolkit::Resource
- Defined in:
- lib/mcp_toolkit/resource.rb
Overview
Descriptor for a single read-only resource exposed via the MCP server. Built
via McpToolkit.registry.register; consumed by the List/Get executors and the
resource_schema tool.
The scope_block is the account-rooting relation: it receives the resolved
local scope root (typically an Account) and MUST return a relation already
scoped so that every row belongs to that root (directly via a foreign key, or
transitively through an owning record). This is the single tenancy chokepoint —
every get/list query roots on it.
The serializer is INJECTABLE per resource: it may be a subclass of the gem's
McpToolkit::Serializer::Base, or any class satisfying the serializer contract
(serialize_one / serialize_collection) — e.g. an app's existing serializer.
Defined Under Namespace
Classes: NotConfigured
Instance Attribute Summary collapse
-
#name ⇒ Object
readonly
Returns the value of attribute name.
Instance Method Summary collapse
-
#association_descriptors ⇒ Object
Association descriptors (the
linksshape) read off the serializer. -
#attribute_names ⇒ Object
Serialized attribute names (the response shape), read off the serializer's declared attributes.
- #description(text = nil) ⇒ Object
-
#effective_required_permissions_scope(default = nil) ⇒ Object
The scope actually enforced for this resource: its own declared scope if set, otherwise the registry-level
defaultpassed in. -
#filterable(mapping = nil) ⇒ Object
Declares the per-attribute filters this resource accepts on the
listtool. -
#filterable_columns ⇒ Object
Request-facing filter key (symbol) => backing column (symbol).
-
#filterable_keys ⇒ Object
Request-facing filter keys (symbols, sorted) this resource can be filtered by.
-
#initialize(name) ⇒ Resource
constructor
A new instance of Resource.
- #model(klass = nil) ⇒ Object
-
#required_permissions_scope(scope = nil) ⇒ Object
The OAuth-style scope a token MUST carry to reach this resource via the generic tools (e.g. "notifications__read").
-
#resolve_relation(scope_root) ⇒ Object
The account-scoped relation for this resource.
- #scope(&block) ⇒ Object
- #serializer(klass = nil) ⇒ Object
Constructor Details
#initialize(name) ⇒ Resource
Returns a new instance of Resource.
21 22 23 24 25 26 27 28 29 |
# File 'lib/mcp_toolkit/resource.rb', line 21 def initialize(name) @name = name.to_s @model = nil @serializer = nil @scope_block = nil @description = nil @filterable = {} @required_permissions_scope = nil end |
Instance Attribute Details
#name ⇒ Object (readonly)
Returns the value of attribute name.
19 20 21 |
# File 'lib/mcp_toolkit/resource.rb', line 19 def name @name end |
Instance Method Details
#association_descriptors ⇒ Object
Association descriptors (the links shape) read off the serializer.
124 125 126 127 128 |
# File 'lib/mcp_toolkit/resource.rb', line 124 def association_descriptors return [] unless serializer.respond_to?(:declared_associations) serializer.declared_associations end |
#attribute_names ⇒ Object
Serialized attribute names (the response shape), read off the serializer's
declared attributes. Requires a serializer that exposes declared_attributes
(the gem's base does); resource_schema degrades gracefully otherwise.
117 118 119 120 121 |
# File 'lib/mcp_toolkit/resource.rb', line 117 def attribute_names return [] unless serializer.respond_to?(:declared_attributes) serializer.declared_attributes.map(&:to_sym) end |
#description(text = nil) ⇒ Object
46 47 48 49 |
# File 'lib/mcp_toolkit/resource.rb', line 46 def description(text = nil) @description = text if text @description end |
#effective_required_permissions_scope(default = nil) ⇒ Object
The scope actually enforced for this resource: its own declared scope if set,
otherwise the registry-level default passed in. nil = no scope required.
65 66 67 |
# File 'lib/mcp_toolkit/resource.rb', line 65 def (default = nil) @required_permissions_scope || default end |
#filterable(mapping = nil) ⇒ Object
Declares the per-attribute filters this resource accepts on the list tool.
Each entry maps a REQUEST-FACING filter key to the backing DATABASE COLUMN the
WHERE is applied to. The mapping is what lets the consumer-facing key differ
from the storage column (e.g. exposing a synced foreign key under its public
name):
filterable booking_id: :synced_booking_id
A declared key accepts both a bare equality value AND operator-based
conditions ({ op:, value: } or an array of them, ANDed) — see
McpToolkit::Filtering for the supported operators per column type.
Unmapped/unknown keys are rejected by the list executor, never silently dropped, so a typo surfaces as actionable feedback.
83 84 85 86 87 88 89 90 |
# File 'lib/mcp_toolkit/resource.rb', line 83 def filterable(mapping = nil) return @filterable if mapping.nil? mapping.each do |request_key, column| @filterable[request_key.to_sym] = column.to_sym end @filterable end |
#filterable_columns ⇒ Object
Request-facing filter key (symbol) => backing column (symbol). Consumed by the list executor to build the WHERE clause.
100 101 102 |
# File 'lib/mcp_toolkit/resource.rb', line 100 def filterable_columns @filterable end |
#filterable_keys ⇒ Object
Request-facing filter keys (symbols, sorted) this resource can be filtered
by. Surfaced via the resource_schema tool.
94 95 96 |
# File 'lib/mcp_toolkit/resource.rb', line 94 def filterable_keys @filterable.keys.sort end |
#model(klass = nil) ⇒ Object
31 32 33 34 |
# File 'lib/mcp_toolkit/resource.rb', line 31 def model(klass = nil) @model = klass if klass @model end |
#required_permissions_scope(scope = nil) ⇒ Object
The OAuth-style scope a token MUST carry to reach this resource via the generic tools (e.g. "notifications__read"). Declared explicitly per resource:
"notifications__read"
Default nil = no scope required for this resource (unless the registry sets a default — see Registry#default_required_permissions_scope). Read with no arg.
58 59 60 61 |
# File 'lib/mcp_toolkit/resource.rb', line 58 def (scope = nil) @required_permissions_scope = scope if scope @required_permissions_scope end |
#resolve_relation(scope_root) ⇒ Object
The account-scoped relation for this resource. Raises if misconfigured so a registry mistake fails loudly rather than leaking an unscoped query.
106 107 108 109 110 111 112 |
# File 'lib/mcp_toolkit/resource.rb', line 106 def resolve_relation(scope_root) raise NotConfigured, "resource #{name.inspect} has no scope block" unless scope raise NotConfigured, "resource #{name.inspect} has no model" unless model raise NotConfigured, "resource #{name.inspect} has no serializer" unless serializer scope.call(scope_root) end |
#scope(&block) ⇒ Object
41 42 43 44 |
# File 'lib/mcp_toolkit/resource.rb', line 41 def scope(&block) @scope_block = block if block @scope_block end |
#serializer(klass = nil) ⇒ Object
36 37 38 39 |
# File 'lib/mcp_toolkit/resource.rb', line 36 def serializer(klass = nil) @serializer = klass if klass @serializer end |