Class: VectorMCP::Middleware::Anonymizer

Inherits:
Object
  • Object
show all
Defined in:
lib/vector_mcp/middleware/anonymizer.rb

Overview

Middleware that rewrites selected string fields in outbound tool results into opaque tokens and restores them on inbound tool invocations. All domain knowledge (which keys to match, token prefixes, which keys to treat as atomic blobs) is supplied by the application via the constructor.

Examples:

Wiring on a server

anonymizer = VectorMCP::Middleware::Anonymizer.new(
  store:       VectorMCP::TokenStore.new,
  field_rules: [
    { pattern: /\bname\b/i, prefix: "NAME"  },
    { pattern: /email/i,    prefix: "EMAIL" }
  ],
  atomic_keys: /address/i
)
anonymizer.install_on(server)

Instance Method Summary collapse

Constructor Details

#initialize(store:, field_rules:, atomic_keys: nil) ⇒ Anonymizer

Returns a new instance of Anonymizer.

Parameters:

  • store (VectorMCP::TokenStore)

    the backing token store.

  • field_rules (Array<Hash>)

    an array of { pattern: Regexp, prefix: String } hashes. The pattern is matched against each Hash key whose value is a String.

  • atomic_keys (Regexp, nil) (defaults to: nil)

    optional pattern; Hash values whose parent key matches are serialized and tokenized as a single opaque unit instead of recursed into.

Raises:

  • (ArgumentError)


34
35
36
37
38
39
40
41
# File 'lib/vector_mcp/middleware/anonymizer.rb', line 34

def initialize(store:, field_rules:, atomic_keys: nil)
  raise ArgumentError, "store is required"       if store.nil?
  raise ArgumentError, "field_rules is required" if field_rules.nil?

  @store = store
  @field_rules = field_rules.map { |rule| validate_rule!(rule) }.freeze
  @atomic_keys = atomic_keys
end

Instance Method Details

#after_tool_call(context) ⇒ Object

Middleware hook: tokenize matched fields in the tool result.

Parameters:



85
86
87
88
89
# File 'lib/vector_mcp/middleware/anonymizer.rb', line 85

def after_tool_call(context)
  return if context.result.nil?

  context.result = sweep_outbound(context.result)
end

#before_tool_call(context) ⇒ Object

Middleware hook: rewrite tool arguments before the handler runs.

Parameters:



74
75
76
77
78
79
80
81
# File 'lib/vector_mcp/middleware/anonymizer.rb', line 74

def before_tool_call(context)
  return unless context.params.is_a?(Hash)

  arguments = context.params["arguments"]
  return unless arguments.is_a?(Hash) || arguments.is_a?(Array)

  context.params = context.params.merge("arguments" => sweep_inbound(arguments))
end

#install_on(server, priority: Hook::DEFAULT_PRIORITY) ⇒ Class

Register this anonymizer instance on server for the tool call lifecycle. Creates a thin adapter class so the middleware manager’s argumentless instantiation can still deliver the configured instance.

Parameters:

  • server (VectorMCP::Server)

    the server instance.

  • priority (Integer) (defaults to: Hook::DEFAULT_PRIORITY)

    middleware priority.

Returns:

  • (Class)

    the adapter class registered with the server.



98
99
100
101
102
103
104
105
106
# File 'lib/vector_mcp/middleware/anonymizer.rb', line 98

def install_on(server, priority: Hook::DEFAULT_PRIORITY)
  instance = self
  adapter = Class.new(Base) do
    define_method(:before_tool_call) { |context| instance.before_tool_call(context) }
    define_method(:after_tool_call)  { |context| instance.after_tool_call(context) }
  end
  server.use_middleware(adapter, %i[before_tool_call after_tool_call], priority: priority)
  adapter
end

#sweep_inbound(obj) ⇒ Object

Resolve tokens in an inbound payload back to their original values. Unknown token-shaped strings pass through unchanged.

Parameters:

  • obj (Object)

    a parsed JSON-like Ruby structure.

Returns:

  • (Object)

    a new structure with tokens resolved to original values.



61
62
63
64
65
66
67
68
69
70
# File 'lib/vector_mcp/middleware/anonymizer.rb', line 61

def sweep_inbound(obj)
  VectorMCP::Util::TokenSweeper.sweep(obj) do |value, _parent_key|
    if @store.token?(value)
      resolved = @store.resolve(value)
      resolved.nil? ? value : resolved
    else
      value
    end
  end
end

#sweep_outbound(obj) ⇒ Object

Tokenize sensitive string fields in an outbound payload.

Parameters:

  • obj (Object)

    a parsed JSON-like Ruby structure.

Returns:

  • (Object)

    a new structure with matched values replaced by tokens.



47
48
49
50
51
52
53
54
# File 'lib/vector_mcp/middleware/anonymizer.rb', line 47

def sweep_outbound(obj)
  replace_atomic_nodes(obj).then do |shaped|
    VectorMCP::Util::TokenSweeper.sweep(shaped) do |value, parent_key|
      rule = rule_for(parent_key)
      rule ? @store.tokenize(value, prefix: rule[:prefix]) : value
    end
  end
end