Class: DebugMcp::Tools::GetContext

Inherits:
MCP::Tool
  • Object
show all
Defined in:
lib/debug_mcp/tools/get_context.rb

Constant Summary collapse

TRUNCATION_PATTERN =

Pattern for detecting truncated values in debug gem output. Matches lines where the value portion ends with “…” possibly followed by closing delimiters like ], }, “, or >.

/\.\.\.[\]}"'>)]*\s*\z/
FRAMEWORK_PATH_PATTERN =

Pattern for detecting framework/gem frames in call stack.

%r{/gems/|/\.rbenv/|/\.bundle/|/vendor/bundle/|\[C\]|/ruby/\d}

Class Method Summary collapse

Class Method Details

.call(session_id: nil, server_context:) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/debug_mcp/tools/get_context.rb', line 44

def call(session_id: nil, server_context:)
  client = server_context[:session_manager].client(session_id)
  client.auto_repause!

  parts = []
  total_truncated = 0

  # Show trap context warning as the first section.
  # Use cached value if available to avoid an extra round-trip.
  in_trap = if client.respond_to?(:trap_context) && !client.trap_context.nil?
    client.trap_context
  elsif client.respond_to?(:in_trap_context?)
    client.in_trap_context?
  end
  if in_trap
    parts << "=== Context: Signal Trap ===\n" \
             "Restricted: DB queries, require, autoloading, method breakpoints\n" \
             "Available: evaluate_code (simple expressions), set_breakpoint (file:line), rails_routes\n" \
             "To escape: set_breakpoint(file, line) + trigger_request"
  end

  # Collect each section independently so partial results are still useful
  variable_commands = %w[info\ locals info\ ivars]
  sections = [
    ["Current Location", "list"],
    ["Local Variables", "info locals"],
    ["Instance Variables", "info ivars"],
    ["Call Stack", "bt"],
    ["Breakpoints", "info breakpoints"],
  ]

  bt_raw = nil
  sections.each do |title, command|
    output = client.send_command(command)

    # For variable sections, detect and annotate truncated values
    if variable_commands.include?(command)
      output, truncated_count = annotate_truncated_values(output)
      if truncated_count > 0
        total_truncated += truncated_count
        title += " (#{truncated_count} truncated)"
      end
    end

    # Summarize long call stacks by collapsing framework frames
    if command == "bt"
      bt_raw = output
      output = summarize_call_stack(output)
    end

    parts << "=== #{title} ===\n#{output}"
  rescue DebugMcp::TimeoutError
    parts << "=== #{title} ===\n(timed out)"
  end

  # Annotate return events: the return value is shown in bt output (#=>)
  # and in local variables (%return). Add a note so the agent understands
  # the current line has already executed.
  if bt_raw && return_event_frame?(bt_raw)
    return_note = "=== Stop Event: Return ===\n" \
                  "The current line (=>) has ALREADY been executed. " \
                  "You are seeing the state AFTER this line ran.\n" \
                  "Return value is shown in Call Stack (#=>) and Local Variables (%return)."

    # Check if the return is due to an exception
    begin
      if (exception_info = client.check_current_exception)
        return_note += "\n\nException in scope: #{exception_info}\n" \
                       "This method/block is returning due to an exception, not a normal return."
      end
    rescue DebugMcp::Error
      # Best-effort
    end

    parts << return_note
  end

  if total_truncated > 0
    parts << "---\n#{total_truncated} variable(s) have truncated values. " \
             "Use 'inspect_object' to see full contents (e.g., inspect_object(expression: 'variable_name'))."
  else
    parts << "---\nTip: Use 'evaluate_code' or 'inspect_object' for detailed variable inspection."
  end

  if (http_note = PendingHttpHelper.pending_http_note(client))
    parts << http_note
  end

  MCP::Tool::Response.new([{ type: "text", text: parts.join("\n\n") }])
rescue DebugMcp::Error => e
  MCP::Tool::Response.new([{ type: "text", text: "Error: #{e.message}" }])
end