Module: McpAuthorization::Diagnostics
- Defined in:
- lib/mcp_authorization/diagnostics.rb
Overview
Shared diagnostic helpers used by both the field-level RbsSchemaCompiler#predicate_excluded? and the tool-level Tool#gates_pass? paths.
Lives in its own module so the two predicate sites — field-level (compile time) and tool-level (request time) — share a single “Did you mean?” warning implementation. Avoids two copies of Levenshtein drifting.
Class Method Summary collapse
-
.levenshtein(a, b) ⇒ Integer
Minimal Levenshtein distance for typo suggestions.
-
.warn_unknown_predicate(name, server_context, site:) ⇒ void
Emit a development-mode warning when a predicate method is not found on the server context.
Class Method Details
.levenshtein(a, b) ⇒ Integer
Minimal Levenshtein distance for typo suggestions.
Iterative two-row implementation — O(m*n) time, O(m) extra space. Used only in development for “Did you mean?” suggestions, so the naive version is fine.
: (String, String) -> Integer
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/mcp_authorization/diagnostics.rb', line 68 def levenshtein(a, b) m, n = a.length, b.length d = Array.new(m + 1) { |i| i } (1..n).each do |j| prev = d[0] d[0] = j (1..m).each do |i| cost = a[i - 1] == b[j - 1] ? 0 : 1 temp = d[i] d[i] = [d[i] + 1, d[i - 1] + 1, prev + cost].min prev = temp end end d[m] end |
.warn_unknown_predicate(name, server_context, site:) ⇒ void
This method returns an undefined value.
Emit a development-mode warning when a predicate method is not found on the server context. Helps catch typos like @feture(:x) or gate :feture, :sms.
: (String | Symbol, untyped, Symbol) -> void
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/mcp_authorization/diagnostics.rb', line 22 def warn_unknown_predicate(name, server_context, site:) return unless defined?(Rails) && Rails.respond_to?(:env) && Rails.env.local? str = name.to_s available = server_context.class.public_instance_methods(true) .select { |m| m.to_s.end_with?("?") } .map { |m| m.to_s.chomp("?") } best = available.min_by { |a| levenshtein(a, str) } suggestion = best && levenshtein(best, str) <= 3 ? best : nil hint = case site when :tool then suggestion ? " Did you mean gate :#{suggestion}?" : "" when :field then suggestion ? " Did you mean @#{suggestion}?" : "" else suggestion ? " Did you mean #{suggestion}?" : "" end surface = case site when :tool then "Gate predicate" when :field then "Predicate" else "Predicate" end fallthrough = case site when :tool then "Tool will be shown to all users." when :field then "Field will be shown to all users." else "" end Rails.logger&.warn( "[McpAuthorization] #{surface} '#{str}?' not found on #{server_context.class}.#{hint} #{fallthrough}".strip ) end |