Class: KairosMcp::InvocationContext

Inherits:
Object
  • Object
show all
Defined in:
lib/kairos_mcp/invocation_context.rb

Overview

Tracks invocation chain metadata for internal tool-to-tool calls. Carries depth, caller, mandate, and policy (whitelist/blacklist) through the entire invocation chain. Created by BaseTool#invoke_tool, threaded through ToolRegistry#call_tool.

Defined Under Namespace

Classes: DepthExceededError, PolicyDeniedError

Constant Summary collapse

MAX_DEPTH =
10

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(depth: 0, caller_tool: nil, mandate_id: nil, token_budget: nil, whitelist: nil, blacklist: nil, root_invocation_id: nil) ⇒ InvocationContext

Returns a new instance of InvocationContext.



16
17
18
19
20
21
22
23
24
25
26
# File 'lib/kairos_mcp/invocation_context.rb', line 16

def initialize(depth: 0, caller_tool: nil, mandate_id: nil,
               token_budget: nil, whitelist: nil, blacklist: nil,
               root_invocation_id: nil)
  @depth = depth
  @caller_tool = caller_tool
  @mandate_id = mandate_id
  @token_budget = token_budget
  @whitelist = whitelist
  @blacklist = blacklist
  @root_invocation_id = root_invocation_id || SecureRandom.hex(8)
end

Instance Attribute Details

#blacklistObject (readonly)

Returns the value of attribute blacklist.



13
14
15
# File 'lib/kairos_mcp/invocation_context.rb', line 13

def blacklist
  @blacklist
end

#caller_toolObject (readonly)

Returns the value of attribute caller_tool.



13
14
15
# File 'lib/kairos_mcp/invocation_context.rb', line 13

def caller_tool
  @caller_tool
end

#depthObject (readonly)

Returns the value of attribute depth.



13
14
15
# File 'lib/kairos_mcp/invocation_context.rb', line 13

def depth
  @depth
end

#mandate_idObject (readonly)

Returns the value of attribute mandate_id.



13
14
15
# File 'lib/kairos_mcp/invocation_context.rb', line 13

def mandate_id
  @mandate_id
end

#root_invocation_idObject (readonly)

Returns the value of attribute root_invocation_id.



13
14
15
# File 'lib/kairos_mcp/invocation_context.rb', line 13

def root_invocation_id
  @root_invocation_id
end

#token_budgetObject (readonly)

Returns the value of attribute token_budget.



13
14
15
# File 'lib/kairos_mcp/invocation_context.rb', line 13

def token_budget
  @token_budget
end

#whitelistObject (readonly)

Returns the value of attribute whitelist.



13
14
15
# File 'lib/kairos_mcp/invocation_context.rb', line 13

def whitelist
  @whitelist
end

Class Method Details

.from_h(hash) ⇒ Object

Reconstruct policy from a Hash (e.g., parsed from tool arguments). Only restores policy fields — depth and caller are not transferred.



81
82
83
84
85
86
87
88
89
90
# File 'lib/kairos_mcp/invocation_context.rb', line 81

def self.from_h(hash)
  return nil if hash.nil?

  new(
    whitelist: hash['whitelist'],
    blacklist: hash['blacklist'],
    mandate_id: hash['mandate_id'],
    token_budget: hash['token_budget']
  )
end

.from_json(json_string) ⇒ Object



92
93
94
95
# File 'lib/kairos_mcp/invocation_context.rb', line 92

def self.from_json(json_string)
  require 'json'
  from_h(JSON.parse(json_string))
end

Instance Method Details

#allowed?(tool_name) ⇒ Boolean

Check if a tool is allowed by whitelist/blacklist policy. Blacklist is checked first (deny wins). Both use fnmatch patterns. For namespaced tools (e.g., “peer1/agent_start”), also checks the bare name (“agent_start”) to prevent blacklist bypass via remote proxy tool namespace prefix.

Returns:

  • (Boolean)


102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/kairos_mcp/invocation_context.rb', line 102

def allowed?(tool_name)
  names = [tool_name]
  names << tool_name.split('/').last if tool_name.include?('/')

  if @blacklist
    return false if names.any? { |n| @blacklist.any? { |pat| File.fnmatch(pat, n) } }
  end
  if @whitelist
    return names.any? { |n| @whitelist.any? { |pat| File.fnmatch(pat, n) } }
  end
  true
end

#child(caller_tool:) ⇒ Object

Create a child context for a nested invocation. Inherits all policy from the parent; increments depth.

Raises:



30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/kairos_mcp/invocation_context.rb', line 30

def child(caller_tool:)
  raise DepthExceededError, "Max invocation depth (#{MAX_DEPTH}) exceeded" if @depth >= MAX_DEPTH

  self.class.new(
    depth: @depth + 1,
    caller_tool: caller_tool,
    mandate_id: @mandate_id,
    token_budget: @token_budget,
    whitelist: @whitelist&.dup,
    blacklist: @blacklist&.dup,
    root_invocation_id: @root_invocation_id
  )
end

#derive(blacklist_remove: [], blacklist_add: []) ⇒ Object

Derive a new context with modified blacklist, preserving all other fields. Used by agent ACT phase to selectively unblock autoexec tools. Does NOT increment depth — child() does that at invoke_tool time.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/kairos_mcp/invocation_context.rb', line 47

def derive(blacklist_remove: [], blacklist_add: [])
  new_blacklist = Array(@blacklist).dup
  blacklist_remove.each { |pat| new_blacklist.delete(pat) }
  blacklist_add.each { |pat| new_blacklist << pat unless new_blacklist.include?(pat) }

  self.class.new(
    depth: @depth,
    caller_tool: @caller_tool,
    mandate_id: @mandate_id,
    token_budget: @token_budget,
    whitelist: @whitelist&.dup,
    blacklist: new_blacklist.empty? ? nil : new_blacklist,
    root_invocation_id: @root_invocation_id
  )
end

#to_hObject

Serialize to a plain Hash for passing through tool arguments. Only includes policy-relevant fields (whitelist, blacklist, mandate_id, token_budget).



65
66
67
68
69
70
71
72
# File 'lib/kairos_mcp/invocation_context.rb', line 65

def to_h
  {
    'whitelist' => @whitelist,
    'blacklist' => @blacklist,
    'mandate_id' => @mandate_id,
    'token_budget' => @token_budget
  }
end

#to_json(*args) ⇒ Object



74
75
76
77
# File 'lib/kairos_mcp/invocation_context.rb', line 74

def to_json(*args)
  require 'json'
  to_h.to_json(*args)
end