Class: McpToolkit::Tools::Base
- Inherits:
-
MCP::Tool
- Object
- MCP::Tool
- McpToolkit::Tools::Base
- Defined in:
- lib/mcp_toolkit/tools/base.rb
Overview
Base class for the generic MCP tools. Subclasses an official-SDK MCP::Tool,
so name/description/input_schema and the call contract are the gem's.
This base adds the shared concern every tool needs: authenticating +
scope-resolving the caller (via McpToolkit::Auth::Authenticator) before
running, and turning tool-level errors into isError: true MCP results
(rather than letting them become JSON-RPC protocol errors).
The bearer token, JSON-RPC _meta, and the account-id header are threaded in
through server_context (set per-request by the controller). The active
McpToolkit config is also threaded in as server_context[:mcp_config] so a
process can, in principle, host more than one configured server; it falls back
to McpToolkit.config.
Direct Known Subclasses
Class Method Summary collapse
- .config_from(server_context) ⇒ Object
- .error_response(message) ⇒ Object
- .lookup_resource(name, config) ⇒ Object
-
.meta_from(server_context) ⇒ Object
The gem nests the request
_metaunder server_context. -
.resolve_descriptor(name, config) ⇒ Object
Validates the
resourceargument is present and resolves its descriptor, raising InvalidParams (=> clean tool error) for a blank or unknown resource. - .text_response(payload) ⇒ Object
-
.with_account(server_context, account_id: nil, required_scope: nil) ⇒ Object
Runs
blockwith an authenticated, scoped context, serializing any McpToolkit::Errors into a clean text tool error. -
.with_authentication(server_context, required_scope: nil) ⇒ Object
Authenticates the token (valid + the explicitly-declared
required_scope) WITHOUT requiring an account selection.
Class Method Details
.config_from(server_context) ⇒ Object
83 84 85 |
# File 'lib/mcp_toolkit/tools/base.rb', line 83 def self.config_from(server_context) server_context[:mcp_config] || McpToolkit.config end |
.error_response(message) ⇒ Object
74 75 76 |
# File 'lib/mcp_toolkit/tools/base.rb', line 74 def self.error_response() MCP::Tool::Response.new([{ type: "text", text: }], error: true) end |
.lookup_resource(name, config) ⇒ Object
87 88 89 90 91 |
# File 'lib/mcp_toolkit/tools/base.rb', line 87 def self.lookup_resource(name, config) config.registry.fetch(name) rescue McpToolkit::Registry::UnknownResource => e raise McpToolkit::Errors::InvalidParams, e. end |
.meta_from(server_context) ⇒ Object
The gem nests the request _meta under server_context.
79 80 81 |
# File 'lib/mcp_toolkit/tools/base.rb', line 79 def self.(server_context) server_context[:_meta] || {} end |
.resolve_descriptor(name, config) ⇒ Object
Validates the resource argument is present and resolves its descriptor,
raising InvalidParams (=> clean tool error) for a blank or unknown resource.
95 96 97 98 99 |
# File 'lib/mcp_toolkit/tools/base.rb', line 95 def self.resolve_descriptor(name, config) raise McpToolkit::Errors::InvalidParams, "resource is required" if name.to_s.strip.empty? lookup_resource(name, config) end |
.text_response(payload) ⇒ Object
69 70 71 72 |
# File 'lib/mcp_toolkit/tools/base.rb', line 69 def self.text_response(payload) text = payload.is_a?(String) ? payload : JSON.generate(payload) MCP::Tool::Response.new([{ type: "text", text: }]) end |
.with_account(server_context, account_id: nil, required_scope: nil) ⇒ Object
Runs block with an authenticated, scoped context, serializing any
McpToolkit::Errors into a clean text tool error.
The resolved scope_root is yielded — it is the tools' serializer scope
AND the root every query is scoped through.
account_id is the superuser account selector arriving as a tool
argument (the gem passes tool args as kwargs, not via server_context),
threaded here so it joins _meta / the header in the resolution order.
required_scope is the explicitly-declared scope a token must carry (the
caller resolves it from the resource — see Registry#required_scope_for).
Empty/nil => no scope check (authorized_for_scope? treats "" as a pass).
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/mcp_toolkit/tools/base.rb', line 29 def self.with_account(server_context, account_id: nil, required_scope: nil) config = config_from(server_context) context = McpToolkit::Auth::Authenticator.call( token: server_context[:bearer_token], meta: (server_context), arguments: { "account_id" => account_id }.compact, header_account_id: server_context[:header_account_id], config: ) unless context.introspection.(required_scope) return error_response("Unauthorized: token lacks the #{required_scope.inspect} scope") end text_response(yield(context.scope_root)) rescue McpToolkit::Errors::Unauthorized => e error_response("Unauthorized: #{e.}") rescue McpToolkit::Errors::InvalidParams => e error_response("Invalid request: #{e.}") end |
.with_authentication(server_context, required_scope: nil) ⇒ Object
Authenticates the token (valid + the explicitly-declared required_scope)
WITHOUT requiring an account selection. Used by the schema-discovery tools,
which reveal shape, not tenant data, so a superuser shouldn't have to pin an
account just to discover what exists. Empty/nil required_scope => no scope
check.
55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/mcp_toolkit/tools/base.rb', line 55 def self.with_authentication(server_context, required_scope: nil) config = config_from(server_context) introspection = McpToolkit::Auth::Introspection.call(server_context[:bearer_token], config:) return error_response("Unauthorized: invalid or expired token") unless introspection.valid? unless introspection.(required_scope) return error_response("Unauthorized: token lacks the #{required_scope.inspect} scope") end text_response(yield) rescue McpToolkit::Errors::InvalidParams => e error_response("Invalid request: #{e.}") end |