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:
- Launching the IDE in debug mode from IntelliJ IDEA
- Using the MCP Steroid to interact with the debugged IDE
- Taking screenshots to observe UI state
- Using the debugger to inject code and inspect runtime state
- 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
DevMainKtas 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:
- Finds the run configuration by name
- Gets the debug executor (not the run executor)
- Launches the configuration with debugger attached
- 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:
- Set a breakpoint in target IDE code (e.g., in your plugin)
- Trigger the breakpoint (perform an action, open a file, etc.)
- When paused, open “Evaluate Expression” in IntelliJ (Alt+F8)
- Paste code above
- 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:
- Open IntelliJ’s debug console
- Switch to target IDE’s process
- 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:
- IntelliJ debugger pauses execution
- You see target IDE’s code in IntelliJ’s editor
- Variables panel shows target IDE’s runtime state
- 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:
- Is a build required first?
- Is config/system directory accessible?
- 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:
- Code not executed yet
- Breakpoint in wrong file/line
- 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:
- Launch IDEs in debug mode programmatically
- Use MCP Steroid to execute code
- Set breakpoints and inspect runtime state
- Take screenshots for visual verification
- Monitor logs in real-time
- Inject code via debugger console
- Test plugins without UI automation
- Modify behavior during debugging
- Collect comprehensive evidence
- 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