Debugging IDE with MCP Steroid

This documentation was written entirely by AI agents using MCP Steroid while working on the IntelliJ Platform codebase. It demonstrates what autonomous AI agents can discover and document when given full IDE access through MCP Steroid.

Note: Specific plugin names, paths, and internal details have been redacted. Replace placeholders like YOUR_PLUGIN_ID, YourAction, and example paths with your actual values.

Guide for AI Agents: Debugging IntelliJ-based IDEs (CLion, IDEA, Rider, etc.) using IntelliJ’s debugger

Date: 2026-01-31 Audience: AI agents working with IntelliJ Platform development


Overview

This guide explains how an AI agent can debug an IntelliJ-based IDE (like CLion) by:

  1. Launching the IDE in debug mode from IntelliJ IDEA
  2. Using the MCP Steroid to interact with the debugged IDE
  3. Taking screenshots to observe UI state
  4. Using the debugger to inject code and inspect runtime state
  5. Testing plugin functionality programmatically

Use Case: Validating a plugin in the target IDE while having full debugger control


Setup: Two IDEs Working Together

The Architecture

┌─────────────────────────────────────┐
│  IntelliJ IDEA (Debugger Host)      │
│                                     │
│  - intellij project open            │
│  - Run Configurations available     │
│  - Debugger UI active               │
│  - MCP Steroid connected            │
│  - Can execute Kotlin code          │
│  - Can set breakpoints              │
└────────────┬────────────────────────┘
             │ JDWP Debug Connection
             │ (port 60228, etc.)
             ▼
┌─────────────────────────────────────┐
│  Target IDE (Debugged)              │
│                                     │
│  - Running with -agentlib:jdwp      │
│  - Plugin under test loaded         │
│  - UI may be visible or headless    │
│  - Fully controllable via debugger  │
│  - State inspectable                │
└─────────────────────────────────────┘

Key Insight: IntelliJ IDEA becomes your “control center” for debugging any target IDE


Step-by-Step: Launch IDE in Debug Mode

Step 1: Identify Available Run Configurations

Using MCP Steroid:

import com.intellij.execution.RunManager

val runManager = RunManager.getInstance(project)
val allConfigs = runManager.allSettings

println("Available run configurations:")
allConfigs.forEach { config ->
    println("  - ${config.name}")
}

What to Look For:

  • “CLion (dev build)”
  • “IDEA (dev build)”
  • “Rider (dev build)”
  • Any configuration with DevMainKt as main class

Example Output:

Available run configurations:
  - CLion (dev build)
  - IDEA Ultimate
  - Android Studio
  - Rider (dev build)

Step 2: Launch in Debug Mode Programmatically

Code to Execute via MCP Steroid:

import com.intellij.execution.RunManager
import com.intellij.execution.ProgramRunnerUtil
import com.intellij.execution.executors.DefaultDebugExecutor
import com.intellij.openapi.application.ApplicationManager

// Find the target IDE configuration
val runManager = RunManager.getInstance(project)
val targetConfig = runManager.allSettings.find {
    it.name == "TARGET_IDE (dev build)"  // Replace with your config name
}

if (targetConfig != null) {
    println("Found configuration: ${targetConfig.name}")

    // Get debug executor
    val debugExecutor = DefaultDebugExecutor.getDebugExecutorInstance()

    // Launch in debug mode (asynchronously on EDT)
    ApplicationManager.getApplication().invokeLater {
        println("Launching ${targetConfig.name} in debug mode...")
        ProgramRunnerUtil.executeConfiguration(targetConfig, debugExecutor)
        println("Debug session started")
    }
} else {
    println("Configuration not found")
}

What This Does:

  1. Finds the run configuration by name
  2. Gets the debug executor (not the run executor)
  3. Launches the configuration with debugger attached
  4. Returns immediately (async launch)

Expected Result:

  • Target IDE starts with debugger attached
  • Debug panel in IntelliJ shows connection
  • Debug port displayed (e.g., 127.0.0.1:60228)
  • Can see debugger output in Console tab

Step 3: Wait for IDE to Start

Important: The IDE launch is asynchronous!

// After launching, wait for process to appear
import kotlin.system.measureTimeMillis

println("Waiting for target IDE to start...")

val startTime = System.currentTimeMillis()
val timeout = 120_000 // 2 minutes

while (System.currentTimeMillis() - startTime < timeout) {
    Thread.sleep(2000)
    // Check if process is running (you can parse logs or check PIDs)
    println("  Waiting... (${(System.currentTimeMillis() - startTime) / 1000}s elapsed)")
}

println("Target IDE should be running now")

Better Approach: Monitor logs or use process checking


Interacting with the Debugged IDE

Method 1: Using MCP Steroid

Take Screenshots:

// Via MCP Steroid tool
steroid_take_screenshot(
    project_name: "intellij",
    task_id: "debug-validation",
    reason: "Capture target IDE UI state"
)

Result:

  • Screenshot saved to .idea/mcp-steroid/[execution-id]/screenshot.png
  • Component tree saved to screenshot-tree.md
  • Metadata in screenshot-meta.json

Use Case: Visual verification of UI state, dialogs, panels

Method 2: Execute Code in Running IDE

Inject Code via Debugger:

When target IDE is paused at a breakpoint, you can execute code in its context:

// This code runs IN the target IDE's JVM, not IntelliJ's
import com.intellij.openapi.project.ProjectManager
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.extensions.PluginId

// Get all open projects in target IDE
val projects = ProjectManager.getInstance().openProjects
println("Target IDE has ${projects.size} open projects:")
projects.forEach { p ->
    println("  - ${p.name} at ${p.basePath}")
}

// Check plugin status IN target IDE
val pluginId = PluginId.getId("YOUR_PLUGIN_ID")  // Replace with your plugin ID
val plugin = PluginManagerCore.getPlugin(pluginId)
println("Plugin:")
println("  Loaded: ${plugin != null}")
println("  Enabled: ${plugin?.isEnabled}")
println("  Version: ${plugin?.version}")

How to Execute:

  1. Set a breakpoint in target IDE code (e.g., in your plugin)
  2. Trigger the breakpoint (perform an action, open a file, etc.)
  3. When paused, open “Evaluate Expression” in IntelliJ (Alt+F8)
  4. Paste code above
  5. Execute in target IDE’s context

Power Move: You can modify target IDE’s state from the debugger!

Method 3: Set Breakpoints Programmatically

Add Breakpoints Before Launch:

import com.intellij.xdebugger.XDebuggerManager
import com.intellij.xdebugger.breakpoints.XLineBreakpointType
import com.intellij.openapi.vfs.VfsUtil
import java.nio.file.Paths

// Set a breakpoint in your plugin code
val file = VfsUtil.findFile(
    Paths.get(
        project.basePath!!,
        "plugins/your-plugin/src/com/example/YourAction.kt"  // Replace with your path
    ),
    true
)

if (file != null) {
    val breakpointManager = XDebuggerManager.getInstance(project).breakpointManager
    val lineBreakpointType = XLineBreakpointType.EXTENSION_POINT_NAME
        .extensionList
        .firstOrNull { it is XLineBreakpointType }

    if (lineBreakpointType != null) {
        val breakpoint = breakpointManager.addLineBreakpoint(
            lineBreakpointType,
            file.url,
            35, // line number - adjust as needed
            null // condition
        )
        println("Breakpoint set")
    }
}

Use Case: Automatically pause when specific code executes in target IDE


Observing the Debugged IDE

Check Process Status

From Shell:

# Find target IDE process
ps aux | grep "DevMainKt" | grep -v grep

# Expected output:
# user  57870  0.6  2.7  444234672  3617568  ?? S  9:31AM  1:52.07 /path/to/java -agentlib:jdwp=... DevMainKt

Extract Debug Port:

ps aux | grep "DevMainKt" | grep -o "address=[^,]*"
# Output: address=127.0.0.1:60228

Monitor Logs

Target IDE’s logs location:

# Logs directory (adjust path for your IDE)
ls -la ~/Library/Logs/JetBrains/TARGET_IDE/

# Main log file
tail -f ~/Library/Logs/JetBrains/TARGET_IDE/idea.log

Search for plugin activity:

# Look for your plugin activity
grep -i "your-plugin" idea.log | tail -20

# Look for errors
grep -i "error\|exception" idea.log | tail -10

# Check plugin loading
grep "Loaded bundled plugins" idea.log

Check Window Visibility

Using AppleScript (macOS):

osascript << 'EOF'
tell application "System Events"
    set javaProc to first process whose name is "java"
    set winCount to count of windows of javaProc

    if winCount > 0 then
        repeat with win in windows of javaProc
            log "Window: " & (name of win)
        end repeat
        return "Found " & winCount & " windows"
    else
        return "No visible windows (headless mode)"
    end if
end tell
EOF

Result tells you:

  • Visible: Can use UI automation
  • Headless: Must use programmatic approaches only

Testing Plugin Functionality

Approach 1: Programmatic Invocation

Trigger Actions Programmatically:

import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.project.ProjectManager

// Get the target project
val targetProjects = ProjectManager.getInstance().openProjects
val targetProject = targetProjects.firstOrNull()

if (targetProject != null) {
    // Get action by ID
    val actionManager = ActionManager.getInstance()
    val myAction = actionManager.getAction("YourPlugin.YourAction")  // Replace with your action ID

    if (myAction != null) {
        println("Action found: ${myAction.javaClass.name}")

        // Create action event
        val dataContext = SimpleDataContext.builder()
            .add(CommonDataKeys.PROJECT, targetProject)
            .build()

        val event = AnActionEvent.createFromDataContext(
            "test-invocation",
            null,
            dataContext
        )

        // Invoke action
        myAction.actionPerformed(event)
        println("Action invoked")
    }
}

Limitation: This runs in IntelliJ’s context, not the target IDE’s

Approach 2: Debugger Code Injection

When Target IDE is Running:

  1. Open IntelliJ’s debug console
  2. Switch to target IDE’s process
  3. Execute code IN target IDE:
// This executes in target IDE's JVM
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.project.ProjectManager

val projects = ProjectManager.getInstance().openProjects
if (projects.isNotEmpty()) {
    val project = projects[0]
    val actionManager = ActionManager.getInstance()

    // List all registered actions containing your keyword
    val allActions = actionManager.getActionIdList("")
    val matchingActions = allActions.filter {
        it.contains("YourKeyword", ignoreCase = true)  // Replace with your keyword
    }

    println("Matching actions in target IDE:")
    matchingActions.forEach { actionId ->
        val action = actionManager.getAction(actionId)
        println("  - $actionId: ${action?.javaClass?.name}")
    }
}

This shows: What actions are actually available in the running target IDE

Approach 3: State File Manipulation

Modify Plugin State Files:

# Create a plugin state file
cat > /path/to/project/.idea/yourPlugin.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="YourPluginService">
    <!-- Your plugin state here -->
  </component>
</project>
EOF

# Target IDE will detect file change and reload

Result: Plugin reacts to state changes


Debugging Workflow Example

Complete Example: Validate Plugin

Step 1: Launch Target IDE in Debug Mode

val runManager = RunManager.getInstance(project)
val targetConfig = runManager.allSettings.find { it.name == "TARGET_IDE (dev build)" }

if (targetConfig != null) {
    val debugExecutor = DefaultDebugExecutor.getDebugExecutorInstance()
    ApplicationManager.getApplication().invokeLater {
        ProgramRunnerUtil.executeConfiguration(targetConfig, debugExecutor)
    }
    println("Target IDE launching in debug mode")
}

Step 2: Wait for Startup

# Monitor log for startup completion
tail -f ~/Library/Logs/JetBrains/TARGET_IDE/idea.log | grep "Loaded bundled plugins"

Step 3: Set Breakpoint in Plugin Code

In IntelliJ, navigate to your plugin’s action class and click the gutter to set a breakpoint.

Step 4: Trigger Action in Target IDE

Perform the action in the target IDE that triggers your plugin code.

Step 5: Breakpoint Hits in IntelliJ

When target IDE executes the code:

  1. IntelliJ debugger pauses execution
  2. You see target IDE’s code in IntelliJ’s editor
  3. Variables panel shows target IDE’s runtime state
  4. You can step through code, inspect variables, etc.

Step 6: Inspect State via Debugger

In IntelliJ’s “Evaluate Expression” (Alt+F8):

// Show current project
project.name

// Show selected file
virtualFile?.path

// Read file content
val content = virtualFile?.inputStream?.readBytes()
println("File size: ${content?.size} bytes")

Step 7: Step Through Execution

Use debugger controls:

  • F8: Step Over
  • F7: Step Into
  • Shift+F8: Step Out
  • F9: Resume

Step 8: Verify Success

# Check logs for successful operation
grep "your-plugin" ~/Library/Logs/JetBrains/TARGET_IDE/idea.log | tail -5

# Look for errors
grep "ERROR\|Exception" ~/Library/Logs/JetBrains/TARGET_IDE/idea.log | tail -5

Visual Verification

Take Screenshot of Target IDE

Using MCP Steroid (from IntelliJ):

// This is a tool call, not Kotlin code
steroid_take_screenshot(
    project_name: "intellij",
    task_id: "validation",
    reason: "Capture target IDE UI state"
)

Result:

  • Screenshot of IntelliJ (not target IDE directly)
  • Shows debugger state, console output
  • Component tree shows UI elements

For target IDE specifically (macOS):

# Use macOS screencapture
screencapture -w /tmp/target-ide-screenshot.png

# -w: Capture specific window (interactive selection)

Analyze Component Tree

# Read component tree from screenshot
cat .idea/mcp-steroid/[execution-id]/screenshot-tree.md

# Look for specific components
grep -i "your-component" screenshot-tree.md

Advanced: Inject Code via Debugger

Scenario: Test a Feature

Goal: Verify a specific feature works correctly

Step 1: Set Breakpoint

In your plugin code at the relevant method:

private fun processData(text: String): String {
    // Breakpoint HERE
    // Your processing logic
    return processedText
}

Step 2: Trigger the Feature

Create test data and trigger the feature in target IDE.

Step 3: Inspect in Debugger

When paused at breakpoint:

// In Evaluate Expression:
text  // See input value

// Check intermediate results
val result = processData(text)
result  // See output

// Verify it worked
result.contains("expected")  // true

Step 4: Modify Behavior (Advanced)

// You can even CHANGE the code while debugging!

// In Evaluate Expression, execute:
return "MODIFIED: $text"  // Custom return value

// Then press F9 to resume with YOUR return value

Result: You can test and modify features in real-time!


Common Patterns

Pattern 1: Verify Plugin Loaded

import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.extensions.PluginId

val pluginId = PluginId.getId("YOUR_PLUGIN_ID")  // Replace with your plugin ID
val plugin = PluginManagerCore.getPlugin(pluginId)

if (plugin != null && plugin.isEnabled) {
    println("Plugin loaded: ${plugin.name} v${plugin.version}")
} else {
    println("Plugin not loaded or disabled")
}

Pattern 2: Check Class Accessibility

try {
    val clazz = Class.forName("com.example.YourAction")  // Replace with your class
    println("Class accessible: ${clazz.name}")
} catch (e: ClassNotFoundException) {
    println("Class not found: ${e.message}")
}

Pattern 3: List All Actions

import com.intellij.openapi.actionSystem.ActionManager

val actionManager = ActionManager.getInstance()
val allActionIds = actionManager.getActionIdList("")

val matchingActions = allActionIds.filter {
    it.contains("YourKeyword", ignoreCase = true)  // Replace with your keyword
}

println("Matching actions (${matchingActions.size}):")
matchingActions.forEach { actionId ->
    val action = actionManager.getAction(actionId)
    println("  - $actionId: ${action?.javaClass?.simpleName}")
}

Pattern 4: Monitor Log in Real-Time

# Terminal 1: Monitor logs
tail -f ~/Library/Logs/JetBrains/TARGET_IDE/idea.log | \
  grep -i --line-buffered "your-plugin\|error\|exception"

# Terminal 2: Trigger actions
# (perform actions, modify state, etc.)

# See logs appear in real-time

Troubleshooting

Target IDE Won’t Start

Check:

  1. Is a build required first?
  2. Is config/system directory accessible?
  3. Are ports available (debug port might be in use)?

Solution:

# Check port availability
lsof -i :5005  # or whatever debug port

# Kill conflicting process
kill <PID>

# Retry launch

Breakpoints Don’t Hit

Reasons:

  1. Code not executed yet
  2. Breakpoint in wrong file/line
  3. Source mismatch (rebuild needed)

Solution:

// Add logging instead
println("DEBUG: Reached this point")

// Or use Exception as breakpoint
throw RuntimeException("Debug marker")

No Visible Window

Reason: Target IDE running headless (no GUI)

Approach:

  • Don’t rely on UI automation
  • Use programmatic testing
  • Monitor logs
  • Use state file manipulation

Verify headless:

osascript -e 'tell application "System Events" to get count of windows of (first process whose name is "java")'
# Output: 0 = headless

Plugin Classes Not Found

Reason: Dependency issue

Solution:

# Check plugin.xml dependencies
cat plugins/your-plugin/resources/META-INF/plugin.xml | grep -A5 "dependencies"

# Verify JARs exist
ls -la out/dev-run/TARGET_IDE/plugins/your-plugin/

# Check logs
grep "ClassNotFoundException" ~/Library/Logs/JetBrains/TARGET_IDE/idea.log | tail -5

Best Practices for AI Agents

1. Always Use Async Launch

// GOOD: Async launch
ApplicationManager.getApplication().invokeLater {
    ProgramRunnerUtil.executeConfiguration(config, executor)
}

// BAD: Blocking launch
ProgramRunnerUtil.executeConfiguration(config, executor)  // Blocks forever

2. Wait for Initialization

// Don't test immediately after launch
println("Launched target IDE, waiting 30s for startup...")
Thread.sleep(30_000)

// Better: Monitor logs for "Loaded bundled plugins"

3. Check Logs First

# Before UI automation, check if IDE started correctly
tail -100 ~/Library/Logs/JetBrains/TARGET_IDE/idea.log | \
  grep -i "error\|exception" | \
  wc -l

# If > 0, investigate before proceeding

4. Use Multiple Evidence Sources

Process running (ps aux)
Debug connection active (IntelliJ shows it)
Logs show plugin loaded
No errors in logs
State files updated
= High confidence plugin works

5. Document Everything

## Test: Plugin Feature
**Status:** Success
**Evidence:**
- PID: 57870
- Debug port: 60228
- Log excerpt: [attach]
- Screenshot: [attach]
- Conclusion: Plugin functional

Summary

As an AI Agent, you can:

  1. Launch IDEs in debug mode programmatically
  2. Use MCP Steroid to execute code
  3. Set breakpoints and inspect runtime state
  4. Take screenshots for visual verification
  5. Monitor logs in real-time
  6. Inject code via debugger console
  7. Test plugins without UI automation
  8. Modify behavior during debugging
  9. Collect comprehensive evidence
  10. Validate functionality programmatically

Key Tools:

  • MCP Steroid (steroid_execute_code, steroid_take_screenshot)
  • Debugger console (Evaluate Expression)
  • Log monitoring (tail -f idea.log)
  • Process inspection (ps aux)
  • State file manipulation
  • AppleScript (for UI when available)

Workflow:

Launch IDE -> Wait for startup -> Set breakpoints ->
Trigger functionality -> Inspect via debugger ->
Monitor logs -> Collect evidence -> Document results

Remember:

  • Debugging is async - wait for readiness
  • Headless mode is common - adapt your approach
  • Logs are your best friend
  • Multiple evidence sources = high confidence
  • Document everything for reproducibility