Class: McpToolkit::Tools::AuthorityBase

Inherits:
Object
  • Object
show all
Defined in:
lib/mcp_toolkit/tools/authority_base.rb

Overview

Optional base class for a HOST's own tools served by the authority dispatcher (McpToolkit::Dispatcher). A host tool MAY subclass this, or may be any object satisfying the duck-typed tool contract the dispatcher calls:

tool.required_permissions_scope        -> String | nil   (gem's scope gate)
tool.call(context:, **arguments)       -> Hash | String  (gem wraps into
                                                         { content: [...] })

The dispatcher treats the object returned by provider.find(name) as the tool; an AuthorityBase SUBCLASS satisfies the contract as CLASS methods (the class .call instantiates, runs, and error-maps a single invocation).

What this base adds over hand-rolling the contract:

* a class DSL (`tool_name` / `description` / `input_schema` /
`required_permissions_scope` / `definition`) mirroring the tool-definition
shape `tools/list` returns;
* per-request accessors (`account` / `principal` / `bearer_token` /
`superuser?`) read from the injected Authority::Context;
* `ensure_resource_accessible!` to gate a superuser-only resource;
* error mapping — an ArgumentError (e.g. a missing required kwarg) becomes an
InvalidParams, any other StandardError an InternalError, while a
deliberately-raised McpToolkit::Protocol::Error passes through with its own
code.

The gem NEVER references a host's API layer, serializers, or resource catalog — all of that lives behind the host's #call.

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context:) ⇒ AuthorityBase

Returns a new instance of AuthorityBase.



77
78
79
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 77

def initialize(context:)
  @context = context
end

Class Attribute Details

._descriptionObject (readonly)

Returns the value of attribute _description.



31
32
33
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 31

def _description
  @_description
end

._input_schemaObject (readonly)

Returns the value of attribute _input_schema.



31
32
33
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 31

def _input_schema
  @_input_schema
end

._tool_nameObject (readonly)

Returns the value of attribute _tool_name.



31
32
33
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 31

def _tool_name
  @_tool_name
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



75
76
77
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 75

def context
  @context
end

Class Method Details

.call(context:, **arguments) ⇒ Object

The dispatcher's entry point: build an instance bound to this request's context and run it, mapping tool-level errors to protocol errors.



70
71
72
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 70

def call(context:, **arguments)
  new(context:).execute(**arguments)
end

.definitionObject



60
61
62
63
64
65
66
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 60

def definition
  {
    name: tool_name,
    description: _description,
    inputSchema: _input_schema || { type: "object", properties: {} }
  }
end

.description(desc = nil) ⇒ Object



41
42
43
44
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 41

def description(desc = nil)
  @_description = desc if desc
  @_description
end

.input_schema(&block) ⇒ Object



46
47
48
49
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 46

def input_schema(&block)
  @_input_schema = yield if block
  @_input_schema || { type: "object", properties: {} }
end

.required_permissions_scope(scope = nil) ⇒ Object

OAuth-style scope (<app>__<action>) a token must carry to call this tool, enforced by the dispatcher before the tool runs. Defaults to nil (no scope required). NOT inherited — a subclass that doesn't declare its own scope is unscoped, even if an ancestor declared one.



55
56
57
58
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 55

def required_permissions_scope(scope = nil)
  @_required_permissions_scope = scope.to_s if scope
  @_required_permissions_scope
end

.tool_name(name = nil) ⇒ Object



33
34
35
36
37
38
39
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 33

def tool_name(name = nil)
  if name
    @_tool_name = name.to_s
  else
    @_tool_name || self.name.to_s.demodulize.underscore.gsub(/_tool$/, "")
  end
end

Instance Method Details

#accountObject



81
82
83
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 81

def 
  context.
end

#bearer_tokenObject



89
90
91
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 89

def bearer_token
  context.bearer_token
end

#call(**_arguments) ⇒ Object

The subclass implements its business logic here, receiving the tool arguments as keywords and returning a Hash or String.

Raises:

  • (NotImplementedError)


124
125
126
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 124

def call(**_arguments)
  raise NotImplementedError, "#{self.class} must implement #call"
end

#ensure_resource_accessible!(resource) ⇒ Object

Guards a resource flagged superusers_only?: a non-superuser caller is refused. No-op for unrestricted resources.



101
102
103
104
105
106
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 101

def ensure_resource_accessible!(resource)
  return unless resource.superusers_only?
  return if superuser?

  raise McpToolkit::Protocol::InvalidRequest, "#{resource.name} is restricted to superuser (user-scoped) MCP tokens"
end

#execute(**arguments) ⇒ Object

Runs the tool's business logic (the subclass's #call) with error mapping. Arrives with symbol-keyed arguments from the dispatcher.



110
111
112
113
114
115
116
117
118
119
120
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 110

def execute(**arguments)
  call(**arguments)
rescue McpToolkit::Protocol::Error
  # A deliberately-raised protocol error carries its own JSON-RPC code
  # (e.g. InvalidParams); let it bubble untouched so the client sees it.
  raise
rescue ArgumentError => e
  raise McpToolkit::Protocol::InvalidParams, e.message
rescue StandardError => e
  raise McpToolkit::Protocol::InternalError, e.message
end

#principalObject



85
86
87
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 85

def principal
  context.principal
end

#superuser?Boolean

Whether the caller is a superuser, per the Context (which duck-types it off the principal). Used to gate resources/tools that expose cross-tenant data.

Returns:

  • (Boolean)


95
96
97
# File 'lib/mcp_toolkit/tools/authority_base.rb', line 95

def superuser?
  context.superuser?
end