Debugging with Xdebug or PHP Debugger, by hand or with an AI
The JumPHP IDE has a real step debugger: you stop the program on a line, look at the variables, change one, go on step by step. It speaks DBGp, the protocol of Xdebug, so it can drive two engines:
- Xdebug, the classic one (the default);
- PHP Debugger, a newer DBGp debugger, free and open source, compatible with Xdebug's settings, with near-zero overhead.
Both work the same way in the IDE. You can also hand the debugger to an AI (Claude Code): it sets the breakpoints, runs the program, reads the variables and finds the bug itself.
Which engine to choose
| Xdebug | PHP Debugger | |
|---|---|---|
| In JumPHP | default, all runtimes | desktop runtime (PHP 8.2 to 8.5) |
| Overhead | PHP runs noticeably slower | "near-zero overhead when idle", always on, no trigger to set |
Async runtime (PHP 8.6, runtime: async) |
yes | no: Xdebug is used instead (bin/jumphp says so) |
| AI | the JumPHP MCP server (bin/jumphp debug) |
its own MCP server (dbgp mcp), which also sees the script's output and PHP warnings |
In practice: choose PHP Debugger for everyday debugging, for long runs (a game that plays 600 frames, a test suite) and for AI debugging. Keep Xdebug if you work with the async runtime (threads), or if you need something only Xdebug does. The two are interchangeable in the IDE, so you can try both on the same project.
PHP Debugger's own page gives no benchmark figure: it only states the near-zero cost until you debug. Measure on your own project if the difference matters to you.
Choose the engine in the IDE
- The Debug button in the header (the bug beside Run,
Shift+F9) unfolds a menu: Debug with (Xdebug or PHP Debugger, for this run) and Default engine (the one a plain click uses from then on). A right click on the button opens the same menu. - The first run with a given engine builds a Docker image once (about a minute). Later runs start at once.
- A notification tells you when the program never connects to the debugger (it ended first, or nothing after 3 minutes). The Run window's output usually says why.
Choose the engine from a terminal
Everything goes through bin/jumphp, as always:
# Xdebug (default)
JUMPHP_XDEBUG=9003 bin/jumphp test pong
# PHP Debugger
JUMPHP_DEBUGGER=php-debugger JUMPHP_XDEBUG=9003 bin/jumphp test pong
JUMPHP_XDEBUG=<port>turns step debugging on: PHP connects to a debugger listening on that port of your computer (composer, phpstan andphp -lrun without it).JUMPHP_DEBUGGER=xdebug|php-debuggerpicks the engine (defaultxdebug).- With
php-debugger,bin/jumphpbuilds the imagedocker/desktop-phpdebuggeronce, from the desktop image, which stays without it. - On the async runtime (PHP 8.6) the message "PHP Debugger does not run on PHP 8.6 (async runtime): Xdebug is used" is shown and Xdebug runs.
Debug by hand in the IDE
- Breakpoint: click in the gutter, left of a line of PHP. A right click gives a condition (a PHP expression: it stops only when true, e.g.
$this->score < 0);Ctrl+F8sets one at the caret. Breakpoints are kept per project and follow their lines as you edit. - Debug (
Shift+F9): the program runs, and stops on the first breakpoint. The line is marked and the Debug tool window opens. - Look: the call stack (click a frame to see its variables), the variables as a tree read on demand, watches, and a field to evaluate any PHP expression where the program stopped.
- Change a value: double click on a variable or
F2, type a PHP expression (9,"Ada",[1, 2],null) and go on with the new value. - Move: Resume
F9, Step OverF8, Step IntoF7, Step OutShift+F8, Run to CursorAlt+F9, Stop. - Exceptions: Stop at Exceptions… (the warning icon of the Debug window):
*or class names (TypeError,App\GameOver). It stops where the exception is thrown, caught or not, and shows which one with its message.
Hover inspector (coming in the next IDE update): when the program is stopped at a breakpoint, move the mouse over a variable in the editor to get an expandable tree of its value, with Copy and Edit.
A tip for games: debug headless runs (test, shot): a game stopped inside a window freezes until you resume it.
Debug with an AI
An AI debugs better with a debugger than with var_dump(): it reads the real values, one question at a time, and changes nothing in your code. Two MCP servers are available. Use one of them at a time (both listen on port 9003).
1. The JumPHP server: bin/jumphp debug
The IDE's own debugger as an MCP server (it runs in Docker, with Xdebug):
claude mcp add jumphp-debug -- bin/jumphp debug
Its tools:
| Tool | What it does |
|---|---|
debug_breakpoints |
sets a file's breakpoints (lines, or {line, condition}), lists them without a path, clear_all |
debug_exceptions |
the exceptions that stop the program (* = all, [] = none) |
debug_wait |
waits until the program stops at a breakpoint or an exception, then gives where, the stack and the variables |
debug_state |
where it is stopped, the stack, the variables of a frame |
debug_step |
step_over, step_into, step_out, run, stop: waits for the next stop |
debug_run_to |
runs to a line |
debug_eval |
evaluates a PHP expression where it stopped |
debug_variables |
a frame's variables, or the children of one ($this->player, $list[3]) |
debug_set_value |
gives a variable a new value (a PHP expression) |
Paths are relative to the project. Claude starts the program itself, in the background, with JUMPHP_XDEBUG=9003, then calls debug_wait.
2. PHP Debugger's server: dbgp mcp
PHP Debugger ships its own MCP server in a small program, dbgp (MIT license; Linux and macOS, amd64 and arm64; Windows amd64).
# install (macOS / Linux)
curl -fsSL https://github.com/php-debugger/dbgp-client/releases/latest/download/install.sh | sh
dbgp -version
# for this project / for all your projects
claude mcp add dbgp -- dbgp mcp
claude mcp add --scope user dbgp -- dbgp mcp
Inside Claude Code, /mcp shows that it is connected. Its tools, by their names in PHP Debugger's documentation:
- connection:
status,listen,unlisten,sessions,wait_for_session,detach; - breakpoints:
add_breakpoint,remove_breakpoint,breakpoints; - running:
continue,wait_for_stop,stop; - looking:
stack,variables,variable,variable_value,source,output,warnings; - acting:
eval,set_variable; - Docker / remote:
add_path_mapping,path_mappings.
Useful flags (run dbgp mcp -h for all): -php-port PORT (the port PHP connects to, 9003 by default), -mcp-port PORT, -idekey KEY, -map LOCAL=REMOTE (a path mapping, repeatable), and -no-eval, which forbids eval, set_variable and conditional breakpoints: the AI can only look. It does not hide values, so passwords and keys stay visible to it.
Through bin/jumphp, JUMPHP_DEBUGGER=php-debugger bin/jumphp debug starts dbgp mcp for you (it needs dbgp installed on your computer). Then start the program with the same engine:
JUMPHP_DEBUGGER=php-debugger JUMPHP_XDEBUG=9003 bin/jumphp test pong
A worked session
You, to Claude Code:
The score of the
ponggame goes up by 2 instead of 1 when the ball passes the left paddle. Use the debugger, not var_dump. Set a breakpoint where the score is increased (look inPlayScene), runbin/jumphp test pongwithJUMPHP_XDEBUG=9003, stop on it, read the variables, and tell me why. Fix it only after you have shown me the cause.
What the AI then does, with the tools above:
- finds the line that adds a point and sets a breakpoint there, with a condition if the line runs on every frame (
$this->ballX < 0); - starts the test in the background and waits for the stop (
debug_wait/wait_for_stop); - reads
$this->score,$this->scoredand the stack to see who called this line; - steps out once (
debug_stepstep_out) to see the second caller: the point is counted twice; - checks its idea without editing anything: it gives
$this->scoreda value (debug_set_value/set_variable) and continues to see the score stay right; - only then edits the code and runs the test again, with no breakpoint.
Tips
- Say it plainly: "use the debugger". Otherwise an agent often falls back to
var_dump(). - Give it the command that shows the bug, what you expect, and where you suspect it.
- Small steps: a few
step_over, then look. A conditional breakpoint ($enemy->hp <= 0) beats stepping through 600 frames. - Let it read variables and evaluate expressions: no
var_dump(), noecho, nothing to clean up after. - Prefer headless runs (
test,shot,php) to a window. - Secrets: the AI sees every variable it reads (passwords, tokens, API keys). Debug with test data, and use
-no-evalwithdbgpif you do not want it to run code in your program. - Do not leave the MCP server of a debugger connected in a project where you do not debug: remove it with
claude mcp remove.
Troubleshooting
Nothing stops / "did not connect".
Is a debugger listening on the port (the IDE's Debug window, or an MCP server)? The program must be started with JUMPHP_XDEBUG=<port> and the same engine as the listener.
Port already in use.
Two listeners on 9003: the IDE and bin/jumphp debug (or dbgp mcp), or two IDEs. Stop one, or use another port: JUMPHP_DEBUG_PORT=9010 bin/jumphp debug with JUMPHP_XDEBUG=9010, or dbgp -php-port 9010 mcp.
The breakpoint is never hit.
It is on a line without code (a comment, a blank line, a closing brace), or in a file the program never loads. Run debug_state / look at the stack to see where it is. With a -map or an IDE: the paths must match (below).
Paths in Docker.
bin/jumphp mounts your project at /workspace in the container: the debugger says file:///workspace/.... The IDE and bin/jumphp debug map this to the project's files by themselves, and the paths you give are relative to the project. With dbgp mcp alone, map the folders: dbgp mcp -map /home/you/project=/workspace.
PHP 8.6 (async runtime).
PHP Debugger does not run there: Xdebug is used. To use PHP Debugger, debug on the default runtime (PHP 8.5), or remove runtime: async for the session.
Browser games.
The debugger works on the desktop runtime. In the browser, reproduce the bug with bin/jumphp test or shot on the desktop.
See also Coding, running and testing, Claude in the IDE and IDE Remote.