phpstan-mcp-mx 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +449 -365
  3. package/dist/baseline.d.ts +23 -2
  4. package/dist/baseline.js +32 -6
  5. package/dist/baseline.js.map +1 -1
  6. package/dist/changed.d.ts +47 -0
  7. package/dist/changed.js +170 -1
  8. package/dist/changed.js.map +1 -1
  9. package/dist/config.d.ts +11 -0
  10. package/dist/config.js +20 -2
  11. package/dist/config.js.map +1 -1
  12. package/dist/defaults.js +2 -2
  13. package/dist/defaults.js.map +1 -1
  14. package/dist/phpstan/parameters.d.ts +41 -0
  15. package/dist/phpstan/parameters.js +66 -0
  16. package/dist/phpstan/parameters.js.map +1 -0
  17. package/dist/phpstan/process.d.ts +6 -0
  18. package/dist/phpstan/process.js +8 -1
  19. package/dist/phpstan/process.js.map +1 -1
  20. package/dist/phpstan.d.ts +6 -1
  21. package/dist/phpstan.js +100 -28
  22. package/dist/phpstan.js.map +1 -1
  23. package/dist/report.d.ts +2 -0
  24. package/dist/report.js +24 -3
  25. package/dist/report.js.map +1 -1
  26. package/dist/runner.d.ts +19 -4
  27. package/dist/runner.js +48 -18
  28. package/dist/runner.js.map +1 -1
  29. package/dist/tmpdir.d.ts +7 -0
  30. package/dist/tmpdir.js +21 -2
  31. package/dist/tmpdir.js.map +1 -1
  32. package/dist/tools/analysis.js +44 -3
  33. package/dist/tools/analysis.js.map +1 -1
  34. package/dist/tools/inspect.js +59 -11
  35. package/dist/tools/inspect.js.map +1 -1
  36. package/dist/tools/prompts.d.ts +12 -4
  37. package/dist/tools/prompts.js +302 -58
  38. package/dist/tools/prompts.js.map +1 -1
  39. package/dist/tools/shared.d.ts +4 -0
  40. package/dist/tools/shared.js +14 -4
  41. package/dist/tools/shared.js.map +1 -1
  42. package/dist/tools.js +1 -1
  43. package/dist/tools.js.map +1 -1
  44. package/dist/version.d.ts +1 -1
  45. package/dist/version.js +1 -1
  46. package/package.json +2 -2
package/README.md CHANGED
@@ -1,42 +1,45 @@
1
1
  # phpstan-mcp-mx
2
2
 
3
- Een MCP-server die [PHPStan](https://phpstan.org) ontsluit als tools voor Claude. Eén server
4
- bedient meerdere PHP-projecten tegelijk; welke dat zijn, leg je zelf vast.
3
+ An MCP server that exposes [PHPStan](https://phpstan.org) as tools for Claude. One server
4
+ serves several PHP projects at once; you decide which ones.
5
5
 
6
- ## Installatie
6
+ The server's own output — messages, tool descriptions, prompts and reports — is in Dutch.
7
+ The examples below show that output verbatim, so you can recognise it when you see it.
7
8
 
8
- De server draait als los proces dat je MCP-client opstart. Installeer hem één keer globaal
9
- en meld hem daarna aan bij de client die je gebruikt — Claude Code en Claude Desktop houden
10
- elk hun eigen lijst bij, dus voor allebei doe je dat apart.
9
+ ## Installation
10
+
11
+ The server runs as a separate process that your MCP client starts. Install it globally once
12
+ and then register it with the client you use — Claude Code and Claude Desktop each keep their
13
+ own list, so you do this separately for both.
11
14
 
12
15
  ```bash
13
16
  npm install -g phpstan-mcp-mx
14
17
  ```
15
18
 
16
- Vervang hieronder overal `/pad/naar/projecten` door de map waar je PHP-projecten in staan.
17
- Die is verplicht; zie [Configuratie](#configuratie) voor waarom.
19
+ Below, replace `/path/to/projects` everywhere with the directory that holds your PHP
20
+ projects. It is required; see [Configuration](#configuration) for why.
18
21
 
19
22
  ### Claude Code
20
23
 
21
- Met `--scope user` is de server in al je projecten beschikbaar wat je wilt bij één server
22
- die meerdere projecten bedient.
24
+ With `--scope user` the server is available in all your projectswhich is what you want
25
+ from one server that serves several projects.
23
26
 
24
27
  ```bash
25
28
  claude mcp add phpstan --scope user \
26
- --env PHPSTAN_MCP_ROOTS=/pad/naar/projecten \
29
+ --env PHPSTAN_MCP_ROOTS=/path/to/projects \
27
30
  -- phpstan-mcp-mx
28
31
  ```
29
32
 
30
- Zonder globale installatie kan het ook rechtstreeks van de registry; dat kost wel elke
31
- sessiestart een npx-resolutie:
33
+ Without a global installation you can also run it straight from the registry; that does cost
34
+ an npx resolution at every session start:
32
35
 
33
36
  ```bash
34
37
  claude mcp add phpstan --scope user \
35
- --env PHPSTAN_MCP_ROOTS=/pad/naar/projecten \
38
+ --env PHPSTAN_MCP_ROOTS=/path/to/projects \
36
39
  -- npx -y phpstan-mcp-mx
37
40
  ```
38
41
 
39
- Nagaan of het gelukt is:
42
+ To check that it worked:
40
43
 
41
44
  ```bash
42
45
  claude mcp get phpstan
@@ -49,77 +52,77 @@ phpstan:
49
52
  Type: stdio
50
53
  Command: phpstan-mcp-mx
51
54
  Environment:
52
- PHPSTAN_MCP_ROOTS=/pad/naar/projecten
55
+ PHPSTAN_MCP_ROOTS=/path/to/projects
53
56
  ```
54
57
 
55
- `claude mcp get` en `claude mcp list` verbinden echt met de server in plaats van alleen de
56
- configuratie terug te lezen ze melden dus of hij ook daadwerkelijk start. Binnen een
57
- sessie doet `/mcp` hetzelfde. Vraag daarna om de projectenlijst: komt die leeg terug terwijl
58
- de verbinding staat, dan wijzen je roots naar de verkeerde map en is er niets mis met de
59
- server zelf.
58
+ `claude mcp get` and `claude mcp list` actually connect to the server instead of just reading
59
+ back the configurationso they tell you whether it really starts. Inside a session, `/mcp`
60
+ does the same. Then ask for the project list: if it comes back empty while the connection is
61
+ up, your roots point to the wrong directory and there is nothing wrong with the server
62
+ itself.
60
63
 
61
64
  ### Claude Desktop
62
65
 
63
- Desktop leest zijn servers uit `claude_desktop_config.json`:
66
+ Desktop reads its servers from `claude_desktop_config.json`:
64
67
 
65
- | besturingssysteem | pad |
68
+ | operating system | path |
66
69
  | --- | --- |
67
70
  | macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
68
71
  | Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
69
72
  | Linux | `~/.config/Claude/claude_desktop_config.json` |
70
73
 
71
- Bestaat dat bestand nog niet, maak het dan zelf aan:
74
+ If that file doesn't exist yet, create it yourself:
72
75
 
73
76
  ```json
74
77
  {
75
78
  "mcpServers": {
76
79
  "phpstan": {
77
80
  "command": "phpstan-mcp-mx",
78
- "env": { "PHPSTAN_MCP_ROOTS": "/pad/naar/projecten" }
81
+ "env": { "PHPSTAN_MCP_ROOTS": "/path/to/projects" }
79
82
  }
80
83
  }
81
84
  }
82
85
  ```
83
86
 
84
- Staat er al een `mcpServers`-blok, zet `phpstan` er dan naast in plaats van het bestand te
85
- vervangenje overschrijft anders de servers die er al in staan.
87
+ If there already is an `mcpServers` block, add `phpstan` next to the existing entries instead
88
+ of replacing the file otherwise you overwrite the servers that are already in it.
86
89
 
87
- Desktop leest dat bestand alleen bij het opstarten, en op macOS is het venster sluiten geen
88
- afsluiten: quit de app volledig (⌘Q) en start hem opnieuw. Daarna staat `phpstan` tussen de
89
- beschikbare tools in het gesprek.
90
+ Desktop only reads that file at startup, and on macOS closing the window is not quitting:
91
+ quit the app completely (⌘Q) and start it again. After that, `phpstan` shows up among the
92
+ available tools in the conversation.
90
93
 
91
- Heb je Desktop al ingericht en wil je hetzelfde in Claude Code, dan neemt
92
- `claude mcp add-from-claude-desktop` die servers over (alleen macOS en WSL).
94
+ If you have already set up Desktop and want the same in Claude Code,
95
+ `claude mcp add-from-claude-desktop` copies those servers over (macOS and WSL only).
93
96
 
94
- Werkt het in je terminal wel maar in Desktop niet, ga dan meteen naar de volgende sectie:
95
- een GUI-app erft je shell-profiel niet, en dat is hier veruit de meest voorkomende oorzaak.
97
+ If it works in your terminal but not in Desktop, go straight to the next section: a GUI app
98
+ does not inherit your shell profile, and that is by far the most common cause here.
96
99
 
97
- ### Als de server niet start: PATH
100
+ ### If the server won't start: PATH
98
101
 
99
- Krijg je iets als `env: node: No such file or directory`, of blijft de server op "failed to
100
- connect" staan, dan is dit vrijwel altijd de oorzaak.
102
+ If you get something like `env: node: No such file or directory`, or the server stays on
103
+ "failed to connect", this is almost always the cause.
101
104
 
102
- Het commando `phpstan-mcp-mx` is een script dat begint met `#!/usr/bin/env node`, en `npx`
103
- is zelf ook een commando dat gevonden moet worden. Allebei werken ze in je terminal omdat je
104
- shell-profiel Node op `PATH` zet. Maar de MCP-server wordt gestart door een ánder programma,
105
- en dat erft je shell-profiel niet altijd een GUI-app op macOS krijgt vaak alleen een kale
106
- `PATH` met `/usr/bin` en `/bin`. Node staat daar niet: Homebrew zet hem in
107
- `/opt/homebrew/bin`, en nvm, fnm, asdf en volta ergens onder je home.
105
+ The `phpstan-mcp-mx` command is a script that starts with `#!/usr/bin/env node`, and `npx`
106
+ is itself a command that has to be found. Both work in your terminal because your shell
107
+ profile puts Node on `PATH`. But the MCP server is started by a *different* program, and that
108
+ one does not always inherit your shell profilea GUI app on macOS often gets only a bare
109
+ `PATH` with `/usr/bin` and `/bin`. Node isn't there: Homebrew puts it in `/opt/homebrew/bin`,
110
+ and nvm, fnm, asdf and volta somewhere under your home directory.
108
111
 
109
- De oplossing is absolute paden gebruiken, zodat er niets meer opgezocht hoeft te worden:
112
+ The fix is to use absolute paths, so nothing needs to be looked up any more:
110
113
 
111
114
  ```bash
112
- command -v node # bijv. /opt/homebrew/bin/node
113
- npm root -g # bijv. /opt/homebrew/lib/node_modules
115
+ command -v node # e.g. /opt/homebrew/bin/node
116
+ npm root -g # e.g. /opt/homebrew/lib/node_modules
114
117
  ```
115
118
 
116
119
  ```bash
117
120
  claude mcp add phpstan --scope user \
118
- --env PHPSTAN_MCP_ROOTS=/pad/naar/projecten \
121
+ --env PHPSTAN_MCP_ROOTS=/path/to/projects \
119
122
  -- /opt/homebrew/bin/node /opt/homebrew/lib/node_modules/phpstan-mcp-mx/dist/index.js
120
123
  ```
121
124
 
122
- En voor Claude Desktop:
125
+ And for Claude Desktop:
123
126
 
124
127
  ```json
125
128
  {
@@ -127,15 +130,15 @@ En voor Claude Desktop:
127
130
  "phpstan": {
128
131
  "command": "/opt/homebrew/bin/node",
129
132
  "args": ["/opt/homebrew/lib/node_modules/phpstan-mcp-mx/dist/index.js"],
130
- "env": { "PHPSTAN_MCP_ROOTS": "/pad/naar/projecten" }
133
+ "env": { "PHPSTAN_MCP_ROOTS": "/path/to/projects" }
131
134
  }
132
135
  }
133
136
  }
134
137
  ```
135
138
 
136
- Zelf nagaan of het aan `PATH` ligt, zonder de MCP-client erbij te halen. Het binary wordt
137
- eerst in je eigen shell opgezocht en daarna met een kale `PATH` gestart anders is het
138
- commando zelf al onvindbaar en meet je iets anders:
139
+ You can check whether `PATH` is the problem yourself, without involving the MCP client. The
140
+ binary is looked up in your own shell first and then started with a bare `PATH` — otherwise
141
+ the command itself can't be found and you are measuring something else:
139
142
 
140
143
  ```bash
141
144
  echo '' | env -i PATH=/usr/bin:/bin "$(command -v phpstan-mcp-mx)"
@@ -145,8 +148,8 @@ echo '' | env -i PATH=/usr/bin:/bin "$(command -v phpstan-mcp-mx)"
145
148
  env: node: No such file or directory
146
149
  ```
147
150
 
148
- Komt dat eruit, dan weet je genoeg. Dezelfde test op de absolute variant hoort gewoon te
149
- starten:
151
+ If that is what comes out, you know enough. The same test on the absolute variant should
152
+ start normally:
150
153
 
151
154
  ```bash
152
155
  echo '' | env -i PATH=/usr/bin:/bin \
@@ -157,188 +160,217 @@ echo '' | env -i PATH=/usr/bin:/bin \
157
160
  phpstan-mcp 0.1.0 luistert op stdio, 0 root(s) geconfigureerd
158
161
  ```
159
162
 
160
- (Nul roots klopt hier: `env -i` gooit ook `PHPSTAN_MCP_ROOTS` weg. Het gaat er alleen om dát
161
- hij start.)
163
+ (Zero roots is correct here: `env -i` also throws away `PHPSTAN_MCP_ROOTS`. All that matters
164
+ is *that* it starts.)
165
+
166
+ ### Updating
162
167
 
163
- ### Vanuit de repo
168
+ A global installation does not update itself:
169
+
170
+ ```bash
171
+ npm install -g phpstan-mcp-mx@latest
172
+ ```
173
+
174
+ Then start a **new session** in your client. Just restarting is not enough: a server that is
175
+ already running keeps the code it loaded at startup, and the client holds on to the tool list
176
+ from the start of the session. New tools are then missing, and an argument the client doesn't
177
+ know yet (such as `runId`) is silently dropped.
178
+
179
+ If you register the server through `npx`, use `phpstan-mcp-mx@latest` instead of the bare
180
+ name; otherwise npx may keep using an older version from its cache. `server_config` shows
181
+ which version is running.
182
+
183
+ ### From the repository
164
184
 
165
185
  ```bash
166
186
  npm install && npm run build
167
187
  claude mcp add phpstan --scope user \
168
- --env PHPSTAN_MCP_ROOTS=/pad/naar/projecten \
169
- -- node /pad/naar/phpstan-mcp-mx/dist/index.js
188
+ --env PHPSTAN_MCP_ROOTS=/path/to/projects \
189
+ -- node /path/to/phpstan-mcp-mx/dist/index.js
170
190
  ```
171
191
 
172
- Let op dat je dan vastzit aan die map: verplaats of verwijder je hem, dan faalt de server in
173
- al je projecten.
192
+ Note that you are then tied to that directory: move or delete it, and the server fails in
193
+ all your projects.
174
194
 
175
- ## Configuratie
195
+ ## Configuration
176
196
 
177
- Alles kan als env-variabele in je MCP-config, of in één configbestand. **De omgeving wint van
178
- het bestand**: die staat bij één client, het bestand geldt voor de hele machine.
197
+ Everything can be set as an env variable in your MCP config, or in a single config file.
198
+ **The environment wins over the file**: the environment belongs to one client, the file
199
+ applies to the whole machine.
179
200
 
180
- ### Een configbestand
201
+ ### A config file
181
202
 
182
- `~/.config/phpstan-mcp/config.json` (of `$XDG_CONFIG_HOME/phpstan-mcp/config.json`, of het
183
- pad in `PHPSTAN_MCP_CONFIG`):
203
+ `~/.config/phpstan-mcp/config.json` (or `$XDG_CONFIG_HOME/phpstan-mcp/config.json`, or the
204
+ path in `PHPSTAN_MCP_CONFIG`):
184
205
 
185
206
  ```json
186
207
  {
187
- "roots": ["/Users/jij/projecten"],
208
+ "roots": ["/Users/you/projects"],
188
209
  "memoryLimit": "2G",
189
210
  "maxParallel": 2,
190
211
  "tmpDir": "project",
191
212
  "allowWrites": false,
192
213
  "runners": {
193
- "/Users/jij/projecten/admin": {
214
+ "/Users/you/projects/admin": {
194
215
  "command": ["docker", "exec", "-w", "/var/www/html", "admin-app"],
195
216
  "workdir": "/var/www/html"
196
217
  }
197
218
  },
198
219
  "projects": {
199
- "/Users/jij/projecten/shop": { "memoryLimit": "4G", "timeoutSeconds": 900 }
220
+ "/Users/you/projects/shop": { "memoryLimit": "4G", "timeoutSeconds": 900 }
200
221
  }
201
222
  }
202
223
  ```
203
224
 
204
- Daarmee is je MCP-registratie één regel zonder `--env`, en staat de configuratie op één plek
205
- in plaats van verdeeld over zeven variabelen.
225
+ That makes your MCP registration a single line without `--env`, and keeps the configuration
226
+ in one place instead of spread over seven variables.
206
227
 
207
- Een bestand dat er niet is, is geen fout: dan geldt alleen de omgeving. Een bestand dat er
208
- wél is maar niet klopt, laat de server niet startendan heeft iemand iets bedoeld wat
209
- anders stilzwijgend niet gebeurt.
228
+ A file that doesn't exist is not an error: then only the environment applies. A file that
229
+ *does* exist but is invalid stops the server from startingsomeone meant something there
230
+ that would otherwise silently not happen.
210
231
 
211
- ### `PHPSTAN_MCP_ROOTS` (verplicht)
232
+ ### `PHPSTAN_MCP_ROOTS` (required)
212
233
 
213
- `PHPSTAN_MCP_ROOTS` is verplicht: een lijst absolute paden, gescheiden door een dubbele punt
214
- (een puntkomma op Windows). Alles daaronder mag geanalyseerd worden, alles daarbuiten niet.
234
+ `PHPSTAN_MCP_ROOTS` is required: a list of absolute paths, separated by a colon (a semicolon
235
+ on Windows). Everything below them may be analysed, nothing outside them.
215
236
 
216
237
  ```
217
- PHPSTAN_MCP_ROOTS=/Users/jij/projecten:/Users/jij/werk/klant-x
238
+ PHPSTAN_MCP_ROOTS=/Users/you/projects:/Users/you/work/client-x
218
239
  ```
219
240
 
220
- Een root hoeft niet zelf een project te zijn. De server zoekt tot vier niveaus diep naar
221
- mappen met een `composer.json`, stopt zodra hij er een vindt, en slaat `vendor/`,
222
- `node_modules/` en verborgen mappen over. Een indeling als `~/projecten/<klant>/<project>`
223
- werkt dus met `~/projecten` als enige root.
241
+ A root doesn't have to be a project itself. The server searches up to four levels deep for
242
+ directories with a `composer.json`, stops as soon as it finds one, and skips `vendor/`,
243
+ `node_modules/` and hidden directories. A layout like `~/projects/<client>/<project>` works
244
+ with `~/projects` as the only root.
224
245
 
225
- ### `PHPSTAN_MCP_MAX_PARALLEL` (optioneel)
246
+ ### `PHPSTAN_MCP_MAX_PARALLEL` (optional)
226
247
 
227
- Hoeveel analyses er tegelijk mogen draaien, over projecten heen. Standaard twee.
248
+ How many analyses may run at the same time, across projects. Two by default.
228
249
 
229
250
  ```
230
251
  PHPSTAN_MCP_MAX_PARALLEL=4
231
252
  ```
232
253
 
233
- Analyses binnen hetzelfde project gaan altijd op volgordePHPStans resultcache ligt in het
234
- project en twee gelijktijdige runs schrijven daar overheen. Deze grens gaat over de machine
235
- als geheel: zonder hem start een model dat na `list_projects` tien projecten aanroept tien
236
- PHPStan-processen, die elk hun eigen parallelle workers opzetten. Met
237
- `PHPSTAN_MCP_MEMORY_LIMIT=2G` is dat tien keer dat plafond.
254
+ Analyses within the same project always run in ordertwo simultaneous runs would overwrite
255
+ each other's result cache. This limit is about the machine as a whole: without it, a model
256
+ that calls ten projects after `list_projects` starts ten PHPStan processes, each spinning up
257
+ its own parallel workers. With `PHPSTAN_MCP_MEMORY_LIMIT=2G` that is ten times that ceiling.
238
258
 
239
- Een onbruikbare waarde wordt geweigerd in plaats van stil vervangen door de standaard, en
240
- komt als `LET OP` in de opstartregel:
259
+ An unusable value is rejected instead of silently replaced by the default, and shows up as
260
+ `LET OP` ("attention") in the startup line:
241
261
 
242
262
  ```
243
263
  phpstan-mcp 0.3.1 luistert op stdio, 1 root(s) geconfigureerd, hoogstens 2 tegelijk
244
264
  ```
245
265
 
246
- ### `PHPSTAN_MCP_TMPDIR` (optioneel)
266
+ ### `PHPSTAN_MCP_TMPDIR` (optional)
247
267
 
248
- Waar PHPStan zijn tijdelijke bestanden neerzet, en daarmee waar de resultcache landt.
268
+ Where PHPStan puts its temporary files, and therefore where the result cache ends up.
249
269
 
250
- | waarde | betekenis |
270
+ | value | meaning |
251
271
  | --- | --- |
252
- | `project` (standaard) | elk project een eigen map onder de tijdelijke map van het systeem |
253
- | `shared` | niets zetten; wat het systeem toch al gebruikt |
272
+ | `project` (default) | each project gets its own directory under the system temp directory |
273
+ | `shared` | set nothing; whatever the system uses anyway |
254
274
 
255
- De standaard lost een stil probleem op. PHPStans `tmpDir` is standaard
256
- `sys_get_temp_dir()/phpstan`, dus élk project zonder eigen `tmpDir` in zijn config deelt één
257
- `resultCache.php`. Twee projecten na elkaar maken dan elkaars cache ongeldig, en de cache
258
- de reden dat een tweede analyse snel is — doet niets meer. Gemeten op twee echte projecten,
259
- met een ander project ertussendoor:
275
+ The default solves a silent problem. PHPStan's `tmpDir` defaults to
276
+ `sys_get_temp_dir()/phpstan`, so *every* project without its own `tmpDir` in its config
277
+ shares a single `resultCache.php`. Two projects in a row then invalidate each other's cache,
278
+ and the cache the reason a second analysis is fast stops doing anything. Measured on two
279
+ real projects, with another project in between:
260
280
 
261
281
  ```
262
- eigen map: 1s → (ander project) → 1s
263
- gedeelde map: 5s → (ander project) → 3s
282
+ own directory: 1s → (other project) → 1s
283
+ shared directory: 5s → (other project) → 3s
264
284
  ```
265
285
 
266
- Zet je `tmpDir` zelf in je `phpstan.neon`, dan wint die: een configuratie die je expliciet
267
- hebt gemaakt gaat vóór wat deze server aandraagt.
286
+ The directory name also contains a hash of `composer.lock`. After a `composer update`,
287
+ PHPStan therefore starts in a fresh directory: a result cache from before such an update
288
+ used to make PHPStan crash with a message that pointed nowhere. The server leaves old
289
+ directories in place; the system cleans those up itself.
290
+
291
+ If you set `tmpDir` yourself in your `phpstan.neon`, that wins: a configuration you made
292
+ explicitly takes precedence over what this server provides.
268
293
 
269
- Kies `shared` als je wil dat de server dezelfde cache gebruikt als je terminal, of als het
270
- zetten van `TMPDIR` iets in je project stoort bij Larastan draait er een hele
271
- Laravel-applicatie in hetzelfde proces mee.
294
+ Choose `shared` if you want the server to use the same cache as your terminal, or if setting
295
+ `TMPDIR` disturbs something in your project — with Larastan, an entire Laravel application
296
+ runs in the same process.
272
297
 
273
- ### `PHPSTAN_MCP_RUNNERS` (optioneel)
298
+ ### `PHPSTAN_MCP_RUNNERS` (optional)
274
299
 
275
- Sommige projecten zijn op de host niet te analyseren. Larastan start je Laravel-applicatie
276
- écht op, en mist de host bijvoorbeeld `ext-redis`, dan klapt de bootstrap en stopt de analyse
277
- terwijl dezelfde repo in zijn container probleemloos doorloopt.
300
+ Some projects cannot be analysed on the host. Larastan really boots your Laravel
301
+ application, and if the host lacks, say, `ext-redis`, the bootstrap fails and the analysis
302
+ stops while the same repository runs fine inside its container.
278
303
 
279
- Met deze variabele zeg je per project waar PHPStan gedraaid moet worden:
304
+ With this variable you say, per project, where PHPStan should run:
280
305
 
281
306
  ```json
282
307
  {
283
- "/Users/jij/projecten/admin": {
308
+ "/Users/you/projects/admin": {
284
309
  "command": ["docker", "exec", "-w", "/var/www/html", "admin-app"],
285
310
  "workdir": "/var/www/html"
286
311
  }
287
312
  }
288
313
  ```
289
314
 
290
- `command` is het commando dat ervóór komt; `workdir` is het pad waarop diezelfde projectmap
291
- in die omgeving staat. Dat tweede is geen detail: paden die je meegeeft worden ernaartoe
292
- vertaald, en de bestandspaden in het antwoord komen terug als host-paden anders krijg je
293
- `/var/www/html/app/User.php` terug en kun je dat bestand niet openen.
315
+ `command` is the command that goes in front; `workdir` is the path at which that same
316
+ project directory lives inside that environment. The second one is not a detail: paths you
317
+ pass in are translated to it, and file paths in the response come back as host paths
318
+ otherwise you get `/var/www/html/app/User.php` back and can't open that file.
319
+
320
+ `list_projects` shows which environment applies to each project, and with a runner there is
321
+ no need for `vendor/bin/phpstan` on the host. Everything then goes through the runner:
322
+ analysing, but also `diagnose_project`, `clear_result_cache` and `generate_baseline`. Only
323
+ `unmatched_ignores` does not work with a runner (see there).
294
324
 
295
- `list_projects` vermeldt per project welke omgeving geldt, en met een runner hoeft er op de
296
- host geen `vendor/bin/phpstan` te staan.
325
+ `workdir` is a POSIX path, even when the host is Windows: the environment is a Linux
326
+ container or WSL. The project key may be written in your platform's form
327
+ (`C:\Users\you\admin` or `C:/Users/you/admin`, with or without a trailing slash); it is
328
+ compared after resolving symlinks, just like the `project` argument itself.
297
329
 
298
- **Het commando komt uit de serverconfiguratie, nooit uit een tool-aanroep.** Net als de roots
299
- is dit iets wat een mens één keer vastlegt: een model kiest alleen een project, en of daar
300
- een omgeving bij hoort ligt dan al vast. Zou een aanroeper het commando mogen meegeven, dan
301
- bepaalt die wat er uitgevoerd wordt en is de whitelist zinloos.
330
+ **The command comes from the server configuration, never from a tool call.** Like the roots,
331
+ this is something a human sets once: a model only picks a project, and whether an
332
+ environment belongs to it is already decided. If a caller could pass in the command, the
333
+ caller would decide what gets executed and the whitelist would be pointless.
302
334
 
303
- ### Waarom die lijst verplicht is
335
+ ### Why that list is required
304
336
 
305
- PHPStan is geen passieve parser. Het laadt de autoloader van het project, voert
306
- `bootstrapFiles` uit de configuratie uit en laadt extensies als PHP-code. "Analyseer map X"
307
- betekent daarmee "voer de PHP-code uit die in X's configuratie staat" — en het pad komt van
308
- een taalmodel. De lijst met roots maakt expliciet en menselijk vastgesteld wat binnen bereik
309
- ligt.
337
+ PHPStan is not a passive parser. It loads the project's autoloader, executes
338
+ `bootstrapFiles` from the configuration and loads extensions as PHP code. "Analyse directory
339
+ X" therefore means "execute the PHP code that X's configuration points to" — and the path
340
+ comes from a language model. The list of roots makes what is in reach explicit and decided
341
+ by a human.
310
342
 
311
- ### `PHPSTAN_MCP_MEMORY_LIMIT` (optioneel)
343
+ ### `PHPSTAN_MCP_MEMORY_LIMIT` (optional)
312
344
 
313
- Klapt PHPStan op de PHP-geheugenlimiet, dan kan een aanroep `memoryLimit` meegeven maar
314
- dat gebeurt pas nadat de analyse al mislukt is. Met deze variabele leg je de standaard één
315
- keer vast, naast je roots:
345
+ If PHPStan runs into the PHP memory limit, a call can pass `memoryLimit` — but only after
346
+ the analysis has already failed. With this variable you set the default once, next to your
347
+ roots:
316
348
 
317
349
  ```
318
350
  PHPSTAN_MCP_MEMORY_LIMIT=2G
319
351
  ```
320
352
 
321
- Dat wordt `--memory-limit=2G` op elke aanroep die zelf niets meegeeft; een `memoryLimit` in
322
- de aanroep gaat vóór. Toegestaan is wat PHPStan zelf accepteert: een getal in bytes,
323
- optioneel met `K`, `M` of `G`, of `-1` voor onbeperkt. Iets anders wordt geweigerd met een
324
- melding in plaats van doorgegeven, want PHPStan zou er per analyse op klappen.
353
+ That becomes `--memory-limit=2G` on every call that doesn't pass one itself; a `memoryLimit`
354
+ in the call takes precedence. Allowed is whatever PHPStan itself accepts: a number of bytes,
355
+ optionally with `K`, `M` or `G`, or `-1` for unlimited. Anything else is rejected with a
356
+ message instead of passed on, because PHPStan would crash on it for every analysis.
325
357
 
326
- Dit is de enige manier om het bij de server vast te leggen. PHPStan kent er geen
327
- `phpstan.neon`-parameter voor (`Unexpected item 'parameters › memoryLimit'`) en ook geen
328
- eigen env-variabele; het alternatief is `memory_limit` in je `php.ini`, en dat geldt dan
329
- voor élke PHP-CLI op je machine.
358
+ This is the only way to set it at the server level. PHPStan has no `phpstan.neon` parameter
359
+ for it (`Unexpected item 'parameters › memoryLimit'`) and no env variable of its own; the
360
+ alternative is `memory_limit` in your `php.ini`, which then applies to *every* PHP CLI on
361
+ your machine.
330
362
 
331
- Heeft één project meer nodig dan de rest, zet het dan bij dat project in
332
- `PHPSTAN_MCP_PROJECTS` in plaats van deze waarde voor iedereen op te hogen — met
333
- `PHPSTAN_MCP_MAX_PARALLEL=2` is een machinebrede 4G twee keer 4G.
363
+ If one project needs more than the rest, set it for that project in `PHPSTAN_MCP_PROJECTS`
364
+ instead of raising this value for everyone with `PHPSTAN_MCP_MAX_PARALLEL=2`, a
365
+ machine-wide 4G is two times 4G.
334
366
 
335
- ### `PHPSTAN_MCP_PROJECTS` (optioneel)
367
+ ### `PHPSTAN_MCP_PROJECTS` (optional)
336
368
 
337
- Standaarden voor één project, als JSON — of onder `projects` in het configbestand:
369
+ Defaults for a single project, as JSON — or under `projects` in the config file:
338
370
 
339
371
  ```json
340
372
  {
341
- "/Users/jij/projecten/shop": {
373
+ "/Users/you/projects/shop": {
342
374
  "memoryLimit": "4G",
343
375
  "timeoutSeconds": 900,
344
376
  "config": "phpstan-ci.neon"
@@ -346,222 +378,251 @@ Standaarden voor één project, als JSON — of onder `projects` in het configbe
346
378
  }
347
379
  ```
348
380
 
349
- | Sleutel | Betekenis |
381
+ | Key | Meaning |
350
382
  | --- | --- |
351
- | `memoryLimit` | `--memory-limit` voor dit project, in dezelfde vorm als `PHPSTAN_MCP_MEMORY_LIMIT` |
352
- | `timeoutSeconds` | timeout voor dit project, van 1 tot en met 1800 |
353
- | `config` | configbestand binnen het project, als PHPStan dat niet zelf vindt |
383
+ | `memoryLimit` | `--memory-limit` for this project, in the same form as `PHPSTAN_MCP_MEMORY_LIMIT` |
384
+ | `timeoutSeconds` | timeout for this project, from 1 up to and including 1800 |
385
+ | `config` | config file inside the project, if PHPStan doesn't find it by itself |
354
386
 
355
- De volgorde is steeds dezelfde: wat de aanroep meegeeft, dan de standaard van het project,
356
- dan die van de hele server. Een standaard is dus geen plafond.
387
+ The order is always the same: what the call passes, then the project default, then the
388
+ server-wide one. A default is therefore not a ceiling.
357
389
 
358
- `config` gaat door dezelfde padcontrole als een `config` uit een aanroep en moet binnen het
359
- project liggen; een neon kan PHP-code laten uitvoeren. Hij telt overal als config van het
360
- project: bij de melding dat er een baseline meedoet en bij `unmatched_ignores`.
390
+ `config` goes through the same path check as a `config` from a call and must lie inside the
391
+ project; a neon file can cause PHP code to be executed. It counts everywhere as *the* config
392
+ of the project: for the notice that a baseline is included and for `unmatched_ignores`.
361
393
 
362
- Een onbekende sleutel wordt geweigerd, niet genegeerd: `memorylimit` met een kleine l zou
363
- anders stil niets doen. `list_projects` en `server_config` tonen welke standaarden er gelden,
364
- en een kapotte waarde staat daar als probleem bij de lijst met projecten blijft werken.
394
+ An unknown key is rejected, not ignored: `memorylimit` with a lowercase l would otherwise
395
+ silently do nothing. `list_projects` and `server_config` show which defaults apply, and a
396
+ broken value is listed there as a problemthe project list keeps working.
365
397
 
366
398
  ## Tools
367
399
 
368
- Elke tool verwacht een expliciet `project`-pad. De server heeft geen "huidig project": dat
369
- zou toestand zijn die het model niet in zijn context ziet staan, met stilzwijgend het
370
- verkeerde project als gevolg.
400
+ Every tool expects an explicit `project` path. The server has no "current project": that
401
+ would be state the model doesn't see in its context, with the wrong project silently
402
+ analysed as a result.
371
403
 
372
404
  ### `list_projects`
373
405
 
374
- Somt de projecten op die binnen bereik liggen, met per project of PHPStan geïnstalleerd is
375
- en welk configbestand er ligt. Begin hierhet `path` uit het resultaat is wat de andere
376
- tools als `project` verwachten.
406
+ Lists the projects that are in reach, with for each project whether PHPStan is installed and
407
+ which config file it has. Start herethe `path` from the result is what the other tools
408
+ expect as `project`.
377
409
 
378
410
  ### `analyse_project`
379
411
 
380
- | Argument | Verplicht | Betekenis |
412
+ | Argument | Required | Meaning |
381
413
  | --- | --- | --- |
382
- | `project` | ja | absoluut pad naar de projectroot |
383
- | `paths` | nee | paden binnen het project; leeg betekent: volg `phpstan.neon` |
384
- | `level` | nee | level-override, `0` t/m `10` of `max` |
385
- | `config` | nee | ander configbestand, bijv. `phpstan-strict.neon`; moet binnen het project liggen |
386
- | `memoryLimit` | nee | bijv. `1G`, als PHPStan op de PHP-geheugenlimiet klapt |
387
- | `timeoutSeconds` | nee | standaard 300 |
388
- | `identifiers` | nee | alleen deze identifiers in de pagina, bijv. `["argument.type"]` |
389
- | `offset` / `limit` | nee | welk deel van de bevindingen je terugkrijgt |
390
-
391
- `config` gaat door dezelfde padcontrole als `project`. Dat is geen formaliteit: een `.neon`
392
- kan `bootstrapFiles` en extensies aanwijzen, dus een vrij te kiezen configpad zou een route
393
- zijn om willekeurige PHP-code te laten uitvoeren langs de whitelist heen.
394
-
395
- `identifiers` filtert alleen de *pagina*; de samenvatting blijft over alles tellen, zodat
396
- zichtbaar blijft wat je wegliet. Een waarde matcht exact (`argument.type`) of als groep
397
- (`missingType` matcht `missingType.return` en `missingType.parameter`, maar `argument` matcht
398
- niet `argumentx.iets`).
414
+ | `project` | yes | absolute path to the project root |
415
+ | `paths` | no | paths inside the project; empty means: follow `phpstan.neon` |
416
+ | `level` | no | level override, `0` to `10` or `max` |
417
+ | `config` | no | a different config file, e.g. `phpstan-strict.neon`; must lie inside the project |
418
+ | `memoryLimit` | no | e.g. `1G`, if PHPStan hits the PHP memory limit |
419
+ | `timeoutSeconds` | no | 300 by default |
420
+ | `identifiers` | no | only these identifiers in the page, e.g. `["argument.type"]` |
421
+ | `offset` / `limit` | no | which part of the findings you get back |
422
+ | `runId` | no | from an earlier response: the next page from the same result (see [What you get back](#what-you-get-back)) |
423
+
424
+ `config` goes through the same path check as `project`. That is not a formality: a `.neon`
425
+ can point to `bootstrapFiles` and extensions, so a freely chosen config path would be a way
426
+ to have arbitrary PHP code executed around the whitelist.
427
+
428
+ `identifiers` only filters the *page*; the summary keeps counting everything, so you can
429
+ still see what you left out. A value matches exactly (`argument.type`) or as a group
430
+ (`missingType` matches `missingType.return` and `missingType.parameter`, but `argument` does
431
+ not match `argumentx.something`).
399
432
 
400
433
  ### `analyse_file`
401
434
 
402
- Zelfde argumenten, plus een verplichte `path` — één bestand of map binnen het project. Het
403
- pad mag relatief aan de projectroot zijn of absoluut, maar moet er wel binnen liggen.
435
+ Same arguments, plus a required `path` — a single file or directory inside the project. The
436
+ path may be relative to the project root or absolute, but it has to lie inside it.
404
437
 
405
438
  ### `analyse_changed`
406
439
 
407
- Analyseert de PHP-bestanden die volgens git gewijzigd zijn. Bedoeld om te zien of een eigen
408
- wijziging fouten oplevert, zonder het hele project door te rekenen en zonder dat het
409
- antwoord volloopt met wat er al jaren stond.
440
+ Analyses the PHP files that git considers changed. Meant for checking whether your own
441
+ change introduces errors, without analysing the whole project — and without the response
442
+ filling up with what has been there for years.
410
443
 
411
- | Argument | Verplicht | Betekenis |
444
+ | Argument | Required | Meaning |
412
445
  | --- | --- | --- |
413
- | `project` | ja | absoluut pad naar de projectroot |
414
- | `base` | nee | commit, branch of tag om tegen te vergelijken (standaard `HEAD`) |
415
- | `staged` | nee | alleen wat gestaged is (`git diff --cached`), voor een check vóór een commit |
416
- | `onlyChangedLines` | nee | alleen bevindingen op regels die gewijzigd zijn |
417
-
418
- Verder dezelfde argumenten als `analyse_project`, op `paths` na. Met `base=main` krijg je de
419
- hele branch in plaats van alleen je onopgeslagen werk.
420
-
421
- Elke bevinding zegt met `onChangedLine` of hij op een gewijzigde regel staat; in de tekst
422
- staat er een `+` voor. Een gewijzigd bestand bevat vaak ook fouten die er al stonden, en
423
- zonder dat onderscheid ga je die repareren of stop je te vroeg. Let wel: een bevinding op een
424
- ongewijzigde regel kan nog steeds uit je wijziging volgeneen aangepaste signatuur geeft
425
- fouten bij de aanroepen, en die regels heb je niet aangeraakt.
426
-
427
- Twee dingen die het meeneemt en die je makkelijk mist: **nieuwe bestanden die nog niet aan
428
- git toegevoegd zijn** (`git diff` kent die niet, dus er is een tweede uitvraag voor) en
429
- **niets uit `resources/views`** — een `.blade.php` eindigt op `.php`, maar is een template
430
- die PHPStan bij een normale run nooit ziet, omdat de config die map niet in `paths` noemt.
431
-
432
- Elk pad dat git noemt gaat door dezelfde controle als een pad dat het model zelf geeft. Wat
433
- daar niet doorkomteen kapotte symlink, een bestand dat buiten het project uitwijst komt
434
- als "overgeslagen" bij het antwoord te staan in plaats van er stil uit te vallen.
435
-
436
- Staat het project niet onder git, dan zegt de tool dat en verwijst hij naar `analyse_project`
437
- met expliciete `paths`.
446
+ | `project` | yes | absolute path to the project root |
447
+ | `base` | no | commit, branch or tag to compare against (`HEAD` by default) |
448
+ | `staged` | no | only what is staged (`git diff --cached`), for a check before a commit |
449
+ | `onlyChangedLines` | no | only findings on lines that were changed |
450
+
451
+ Otherwise the same arguments as `analyse_project`, except `paths`. With `base=main` you get
452
+ the whole branch instead of just your uncommitted work.
453
+
454
+ Each finding says with `onChangedLine` whether it is on a changed line; in the text it is
455
+ prefixed with a `+`. A changed file often also contains errors that were already there, and
456
+ without that distinction you either start fixing those or stop too early. Keep in mind: a
457
+ finding on an unchanged line can still follow from your change a modified signature causes
458
+ errors at the call sites, and you didn't touch those lines.
459
+
460
+ Two things it includes that are easy to miss: **new files not yet added to git** (`git diff`
461
+ doesn't know them, so there is a second query for them) and **nothing from
462
+ `resources/views`** — a `.blade.php` ends in `.php`, but it is a template that PHPStan never
463
+ sees in a normal run, because the config doesn't list that directory in `paths`.
464
+
465
+ Every path git reports goes through the same check as a path the model supplies itself.
466
+ Whatever doesn't passa broken symlink, a file pointing outside the project — is listed as
467
+ "skipped" in the response instead of silently dropping out.
468
+
469
+ If the project is not under git, the tool says so and points to `analyse_project` with
470
+ explicit `paths`.
471
+
472
+ **What the change hides** is listed under "Ter info" (for your information): new
473
+ `@phpstan-ignore` comments (with file, line, identifier and reason) and ignored errors added
474
+ to a baseline that is included — even if no PHP file changed otherwise. An ignore whose reason
475
+ is the only thing that changed does not count as new. The notice doesn't pass judgement: an
476
+ ignore can be justified, the point is that it doesn't happen silently. Ignored errors that
477
+ disappeared from the baseline are listed too.
438
478
 
439
479
  ### `server_config`
440
480
 
441
- Geeft terug waarmee déze server draait: de projectmappen die binnen bereik liggen, de
442
- standaard geheugenlimiet, hoeveel analyses er tegelijk mogen, waar de tijdelijke bestanden
443
- heen gaan en of schrijvende tools aanstaan.
481
+ Returns what *this* server is running with: the project directories in reach, the default
482
+ memory limit, how many analyses may run at once, where temporary files go and whether
483
+ writing tools are enabled.
444
484
 
445
485
  ```
446
- phpstan-mcp 0.7.1
486
+ phpstan-mcp 0.15.0
447
487
 
448
488
  Projectmappen:
449
- /Users/jij/projecten
450
- /bestaat/niet — bestaat niet
489
+ /Users/you/projects
490
+ /does/not/exist — bestaat niet
451
491
 
452
492
  Geheugenlimiet: 2G
453
493
  Tegelijk: 2
454
494
  Tijdelijke map: project
455
495
  Schrijvende tools: uit
496
+ Eigen omgeving: geen
497
+
498
+ Standaarden per project (een tool-argument gaat voor):
499
+ /Users/you/projects/shop: geheugen 4G, timeout 900s
456
500
  ```
457
501
 
458
- Die informatie stond tot nu toe alleen in de opstartregel op stderr — zichtbaar in je
459
- clientlog, onzichtbaar voor het model. Een geweigerde instelling wordt gemeld in plaats van
460
- weggelaten: staat `PHPSTAN_MCP_MAX_PARALLEL` op onzin, dan is dat veld `null` en staat de
461
- reden onder "Geweigerde instellingen", terwijl de rest gewoon leesbaar blijft.
502
+ Until now that information was only in the startup line on stderr — visible in your client
503
+ log, invisible to the model. A rejected setting is reported instead of omitted: if
504
+ `PHPSTAN_MCP_MAX_PARALLEL` is set to nonsense, that field is `null` and the reason appears
505
+ under "Geweigerde instellingen" (rejected settings), while the rest stays readable.
462
506
 
463
- Heeft een project eigen standaarden (`PHPSTAN_MCP_PROJECTS`), dan staan die er per project
464
- onder.
507
+ If a project has its own defaults (`PHPSTAN_MCP_PROJECTS`), they are listed per project.
465
508
 
466
- Let op wat er *niet* in staat: dit is een vaste lijst van instellingen en geen doorgeefluik
467
- naar de omgeving. In dezelfde omgeving zitten tokens; die horen niet in een
468
- toolantwoord.
509
+ Note what is *not* in there: this is a fixed list of settings and not a pass-through to the
510
+ environment. The same environment holds tokens; those don't belong in a tool response.
469
511
 
470
512
  ### `diagnose_project`
471
513
 
472
- Vraagt PHPStan zelf om zijn opstelling: PHP- en PHPStan-versie, de configs die
473
- Composer-pakketten meebrengen (zoals Larastan) en of de turbo-extensie aanstaat. Draait geen
474
- analyse.
514
+ Asks PHPStan itself for its setup: PHP and PHPStan version, the configs that Composer
515
+ packages bring along (such as Larastan) and whether the turbo extension is enabled. Does not
516
+ run an analysis.
517
+
518
+ Use this when one project behaves differently from another, before you start doubting the
519
+ code. That is not theoretical: this is how it turned out that one project had a dependency
520
+ (`laravel/pao`) that intercepted PHPStan's output.
475
521
 
476
- Gebruik dit als een project zich anders gedraagt dan een ander project, vóórdat je aan de
477
- code gaat twijfelen. Dat is niet theoretisch: hiermee bleek dat één project een dependency
478
- had (`laravel/pao`) die de uitvoer van PHPStan onderschepte.
522
+ Below PHPStan's own text comes the configuration as PHPStan ends up using it:
523
+
524
+ ```
525
+ Uiteindelijke configuratie, volgens PHPStan zelf:
526
+ Level: 10
527
+ Paden: app, tests
528
+ Uitgesloten: geen
529
+ Negeerregels: 1312 (verouderde worden níet gemeld; unmatched_ignores zoekt ze op)
530
+ Tijdelijke map: /tmp/phpstan-mcp/cms-0d03c8f34a81/phpstan
531
+ Bootstrap: vendor/larastan/larastan/bootstrap.php
532
+ ```
533
+
534
+ The level shows up here even when it is set in a shared config from `vendor/`, and the temp
535
+ directory is the one `clear_result_cache` clears. If the application doesn't boot (Larastan
536
+ without the extensions the app needs), this part is missing, with the reason stated.
479
537
 
480
538
  ### `clear_result_cache`
481
539
 
482
- Leegt PHPStans resultcache, zodat de volgende analyse alles opnieuw doorrekent. Nuttig als
483
- een uitslag niet klopt met de code — bijvoorbeeld na een wijziging aan een extensie of een
484
- geïncludeerde config.
540
+ Clears PHPStan's result cache, so the next analysis recomputes everything. Useful when a
541
+ result doesn't match the code — for example after a change to an extension or an included
542
+ config.
485
543
 
486
- Die cache ligt standaard **niet** in het project maar in de tijdelijke map van het systeem
487
- (`sys_get_temp_dir()/phpstan`), en wordt dan gedeeld met elk ander project dat geen eigen
488
- `tmpDir` zet. Bovendien volgt die map `TMPDIR`, dus wat de server leegt hoeft niet dezelfde
489
- map te zijn als wat je terminal gebruikt. Het antwoord noemt daarom de map die geleegd is:
544
+ By default that cache does **not** live in the project but in the system temp directory
545
+ (`sys_get_temp_dir()/phpstan`), and is then shared with every other project that doesn't set
546
+ its own `tmpDir`. On top of that, the directory follows `TMPDIR`, so what the server clears
547
+ need not be the same directory your terminal uses. The response therefore names the directory
548
+ that was cleared:
490
549
 
491
550
  ```
492
551
  Result cache cleared from directory:
493
552
  /var/tmp/phpstan
494
553
  ```
495
554
 
496
- Wil je per project een eigen cache, zet dan `tmpDir` in je `phpstan.neon`.
555
+ If you want a separate cache per project, set `tmpDir` in your `phpstan.neon`.
497
556
 
498
557
  ### `baseline_summary`
499
558
 
500
- Leest de `phpstan-baseline*.neon` van een project en telt wat er genegeerd wordt, per
501
- identifier en per bestand. Draait geen analyse, dus het antwoord is er meteen.
559
+ Reads a project's `phpstan-baseline*.neon` and counts what is being ignored, per identifier
560
+ and per file. Does not run an analysis, so the answer is immediate.
561
+
562
+ Note the distinction: what is in here is exactly what `analyse_project` does *not* report. A
563
+ project that includes its baseline only reports the errors that are new — PHPStan already
564
+ makes that comparison itself.
502
565
 
503
- Let op het onderscheid: wat hier in staat is precies wat `analyse_project` *niet* meldt. Een
504
- project dat zijn baseline includet, rapporteert alleen de fouten die er nieuw bij komen
505
- die vergelijking maakt PHPStan zelf al.
566
+ With `path` (e.g. `"app/Models/User.php"`) you also get the entries for that one file: per
567
+ entry the identifier, the count and the pattern. That file doesn't have to exist any more
568
+ it is precisely a deleted file that the entries doing nothing belong to.
506
569
 
507
570
  ### `unmatched_ignores`
508
571
 
509
- Zoekt de negeerregels die niets meer doen: baseline-regels voor fouten die allang opgelost
510
- zijn, en regels waarvan de `count` hoger is dan wat er nog voorkomt. Veel projecten zetten
511
- `reportUnmatchedIgnoredErrors: false`, en dan groeit dat ongemerktop één project bleken
512
- 554 van de 2116 baseline-regels niets meer te matchen.
572
+ Finds the ignore rules that no longer do anything: baseline entries for errors that were
573
+ fixed long ago, and entries whose `count` is higher than what still occurs. Many projects set
574
+ `reportUnmatchedIgnoredErrors: false`, and then this grows unnoticedon one project, 554 of
575
+ the 2116 baseline entries turned out to match nothing any more.
513
576
 
514
- | Argument | Verplicht | Betekenis |
577
+ | Argument | Required | Meaning |
515
578
  | --- | --- | --- |
516
- | `project` | ja | absoluut pad naar de projectroot |
517
- | `memoryLimit` | nee | dit is een volledige analyse, dus die kan meer nodig hebben |
518
- | `timeoutSeconds` | nee | standaard 300 |
519
- | `offset` / `limit` | nee | welk deel van de regels je terugkrijgt |
579
+ | `project` | yes | absolute path to the project root |
580
+ | `memoryLimit` | no | this is a full analysis, so it may need more |
581
+ | `timeoutSeconds` | no | 300 by default |
582
+ | `offset` / `limit` | no | which part of the entries you get back |
520
583
 
521
- Dit is een volledige analyse, met een tijdelijke config die die van het project includet en
522
- de vlag omzet. Die config staat in de tijdelijke map van de server en niet in het project,
523
- dus er verandert niets aan je repository. PHPStans resultcache blijft bruikbaar: de vlag
524
- verandert niets aan wat er geanalyseerd wordt, dus de volgende gewone analyse is net zo snel.
584
+ This is a full analysis, with a temporary config that includes the project's config and flips
585
+ the flag. That config lives in the server's temp directory and not in the project, so nothing
586
+ in your repository changes. PHPStan's result cache stays usable: the flag doesn't change what
587
+ is analysed, so the next regular analysis is just as fast.
525
588
 
526
- Per regel staat erbij of hij uit de baseline komt. Patronen zonder pad komen apart; die staan
527
- in `ignoreErrors` van je config of van een config die een pakket meebrengt, en daar helpt een
528
- nieuwe baseline niet.
589
+ Each entry says whether it comes from the baseline. Patterns without a path are listed
590
+ separately; they live in `ignoreErrors` of your config or of a config a package brings along,
591
+ and a new baseline doesn't help there.
529
592
 
530
- Er is bewust geen `level` en geen `paths`: op een ander niveau zijn het andere fouten, en bij
531
- een deel van het project lijkt alles daarbuiten verouderd.
593
+ There is deliberately no `level` and no `paths`: on a different level the errors are
594
+ different, and with only part of the project, everything outside it looks stale.
532
595
 
533
- Werkt niet op een project met een eigen uitvoeringsomgeving (`PHPSTAN_MCP_RUNNERS`): de
534
- tijdelijke config staat op de host en is in een container niet te zien. De tool zegt dat met
535
- reden `unsupported_with_runner`.
596
+ Does not work on a project with its own execution environment (`PHPSTAN_MCP_RUNNERS`): the
597
+ temporary config lives on the host and can't be seen inside a container. The tool says so
598
+ with the reason `unsupported_with_runner`.
536
599
 
537
- ### `generate_baseline` (alleen met schrijfrechten)
600
+ ### `generate_baseline` (only with write access)
538
601
 
539
- Zet alle huidige fouten in een baseline, zodat een volgende analyse alleen meldt wat er nieuw
540
- bij komt. Bedoeld om een bestaand project onder PHPStan te krijgen niet om fouten weg te
541
- werken.
602
+ Puts all current errors into a baseline, so that the next analysis only reports what is new.
603
+ Meant for bringing an existing project under PHPStan not for making errors go away.
542
604
 
543
- Deze tool bestaat alleen als de server met `PHPSTAN_MCP_ALLOW_WRITES=1` gestart is:
605
+ This tool only exists if the server was started with `PHPSTAN_MCP_ALLOW_WRITES=1`:
544
606
 
545
607
  ```bash
546
608
  claude mcp add phpstan --scope user \
547
- --env PHPSTAN_MCP_ROOTS=/pad/naar/projecten \
609
+ --env PHPSTAN_MCP_ROOTS=/path/to/projects \
548
610
  --env PHPSTAN_MCP_ALLOW_WRITES=1 \
549
611
  -- phpstan-mcp-mx
550
612
  ```
551
613
 
552
- Zonder die variabele wordt hij niet geregistreerd hij staat dan dus ook niet in de
553
- toollijst, in plaats van dat hij bestaat en bij elke aanroep weigert. Alle andere tools
554
- blijven read-only.
614
+ Without that variable it isn't registeredso it doesn't appear in the tool list at all,
615
+ rather than existing and refusing every call. All other tools stay read-only.
555
616
 
556
- | Argument | Verplicht | Betekenis |
617
+ | Argument | Required | Meaning |
557
618
  | --- | --- | --- |
558
- | `project` | ja | absoluut pad naar de projectroot |
559
- | `file` | nee | doelbestand binnen het project (standaard `phpstan-baseline.neon`) |
560
- | `allowEmpty` | nee | ook schrijven als er niets te negeren valt |
619
+ | `project` | yes | absolute path to the project root |
620
+ | `file` | no | target file inside the project (`phpstan-baseline.neon` by default) |
621
+ | `allowEmpty` | no | write even if there is nothing to ignore |
561
622
 
562
- Verder dezelfde `paths`, `level`, `config` en `memoryLimit` als een analyse. Het antwoord
563
- zegt erbij wat er nog moet gebeuren, want een baseline die nergens ingesloten wordt doet
564
- niets:
623
+ Otherwise the same `paths`, `level`, `config` and `memoryLimit` as an analysis. The response
624
+ says what still needs to happen, because a baseline that isn't included anywhere does
625
+ nothing:
565
626
 
566
627
  ```
567
628
  5 fout(en) in phpstan-baseline.neon gezet.
@@ -575,23 +636,37 @@ includes:
575
636
 
576
637
  ## Prompts
577
638
 
578
- Twee werkpatronen die alleen bestaande tools combineren. Een prompt en geen tool, omdat een
579
- tool context kost in elke sessie en een prompt alleen als je hem kiest.
580
-
581
- - **`phpstan_wijziging_nakijken`** — je eigen wijziging nakijken met `analyse_changed`,
582
- beginnen bij de bevindingen op gewijzigde regels, en herhalen tot het schoon is (zonder
583
- `runId`, anders zie je de uitslag van vóór je reparatie).
584
- - **`phpstan_level_verhogen`** wat een level hoger kost: twee tellingen met `limit: 1` en
585
- het verschil per soort. Het level staat vaak in een gedeelde config in `vendor/`, en
586
- `ignore.*`-bevindingen tellen niet mee: dat zijn inline `@phpstan-ignore`-regels die op een
587
- ander level niets te negeren hebben.
588
-
589
- Beide nemen een optioneel `project`; de tweede ook een doel-`level`.
590
-
591
- ## Als een tool mislukt
592
-
593
- Een mislukte aanroep geeft twee blokken terug: de melding in gewone taal, en een JSON-blok
594
- met velden waarop je kunt sturen.
639
+ Workflows that only combine existing tools. A prompt rather than a tool, because a tool costs
640
+ context in every session and a prompt only when you pick it. Every guardrail in the texts
641
+ comes from a trial run on a real project that went wrong without that sentence.
642
+
643
+ - **`phpstan_wijziging_nakijken`** (review your change) review your own change with
644
+ `analyse_changed`, start with the findings on changed lines, and repeat until it is clean
645
+ (without `runId`, otherwise you see the result from before your fix). With `base` (e.g.
646
+ `main`) for a whole branch before a PR.
647
+ - **`phpstan_level_verhogen`** (raise the level) what one level higher costs: two counts
648
+ with `limit: 1` and the difference per kind. The level is often set in a shared config in
649
+ `vendor/`, and `ignore.*` findings don't count: those are inline `@phpstan-ignore` comments
650
+ that have nothing to ignore on a different level.
651
+ - **`phpstan_baseline_afbouwen`** (shrink the baseline) — what in the baseline is stale
652
+ (`unmatched_ignores`), what a new baseline would hide (`analyse_project`), and the cheapest
653
+ real reduction. Cleaning up and hiding stay two separate things: `generate_baseline` is only
654
+ suggested when there is nothing to hide, and never called on its own initiative.
655
+ - **`phpstan_soort_wegwerken`** (clear one kind) — a clean-up round for one identifier or a
656
+ group such as `missingType`, with the `runId` within the round and without `runId` after
657
+ every fix. Without `identifier` the model picks a kind: first what can be fixed at the
658
+ source, otherwise a kind that sits in one or two files.
659
+ - **`phpstan_projecten_vergelijken`** (compare projects) — two projects side by side
660
+ (`diagnose_project`, level, baseline, runners and defaults), showing only the differences.
661
+ If no difference explains the behaviour, a stale result cache is the next suspect.
662
+
663
+ All prompts take an optional `project` (for the comparison, `projectA` and `projectB`), and a
664
+ client that supports completion gets a picklist from `list_projects` there.
665
+
666
+ ## When a tool fails
667
+
668
+ A failed call returns two blocks: the message in plain language, and a JSON block with fields
669
+ you can act on.
595
670
 
596
671
  ```json
597
672
  {
@@ -604,20 +679,20 @@ met velden waarop je kunt sturen.
604
679
  }
605
680
  ```
606
681
 
607
- `reason` komt uit een gesloten verzameling, zodat je erop kunt matchen zonder de tekst te
608
- lezen: `no_roots_configured`, `project_outside_roots`, `project_not_found`,
609
- `path_outside_project`, `path_not_found`, `phpstan_missing`, `config_missing`,
610
- `bootstrap_failed`, `baseline_unreadable`, `invalid_setting`, `memory_limit`, `timeout`,
611
- `cancelled`, `git_missing`, `not_a_git_repository`, `unknown_base`, `unsupported_with_runner`,
612
- `unusable_output` en `phpstan_failed`.
682
+ `reason` comes from a closed set, so you can match on it without reading the text:
683
+ `no_roots_configured`, `project_outside_roots`, `project_not_found`, `path_outside_project`,
684
+ `path_not_found`, `phpstan_missing`, `config_missing`, `bootstrap_failed`,
685
+ `baseline_unreadable`, `invalid_setting`, `memory_limit`, `timeout`, `cancelled`,
686
+ `git_missing`, `not_a_git_repository`, `unknown_base`, `unsupported_with_runner`,
687
+ `unusable_output` and `phpstan_failed`.
613
688
 
614
- De velden staan bewust in `content` en niet in `structuredContent`: dat laatste wordt door
615
- MCP-clients gevalideerd tegen het schema van het *geslaagde* antwoord.
689
+ The fields are deliberately in `content` and not in `structuredContent`: MCP clients validate
690
+ the latter against the schema of the *successful* response.
616
691
 
617
- ## Wat je terugkrijgt
692
+ ## What you get back
618
693
 
619
- Een analyse levert eerst tellingen over álle bevindingen en daarna één pagina met de
620
- bevindingen zelf (standaard 50, hoogstens 500):
694
+ An analysis first returns counts over *all* findings and then one page with the findings
695
+ themselves (50 by default, 500 at most):
621
696
 
622
697
  ```
623
698
  25 bevinding(en) in 11 bestand(en).
@@ -635,43 +710,43 @@ src/Support/Filters/DealFilters.php
635
710
  42 argument.type Parameter #1 $id expects int, string given.
636
711
  ```
637
712
 
638
- Bij meer resultaten eindigt de tekst met de letterlijke vervolgaanroep:
713
+ When there are more results, the text ends with the literal follow-up call:
639
714
 
640
715
  ```
641
716
  Nog 12 bevinding(en). Roep dezelfde tool opnieuw aan met offset=50 en runId="kX3v9aQp"; dan
642
717
  komt de rest uit dit resultaat in plaats van uit een nieuwe analyse.
643
718
  ```
644
719
 
645
- Met die `runId` komt de volgende pagina uit hetzelfde resultaat. Zonder zou elke pagina een
646
- nieuwe analyse zijn, en wie tussendoor een bestand aanpast krijgt dan pagina's met
647
- verschillende totalen. Alleen `offset`, `limit`, `identifiers` en `onlyChangedLines` mogen
648
- anders zijn. Hoort de `runId` bij andere argumenten, is hij verlopen (na tien minuten) of
649
- onbekend, dan volgt een nieuwe analyse met een melding erbij in plaats van een fout.
720
+ With that `runId`, the next page comes from the same result. Without it, every page would be a
721
+ new analysis, and if you change a file in between you get pages with different totals. Only
722
+ `offset`, `limit`, `identifiers` and `onlyChangedLines` may differ. If the `runId` belongs to
723
+ different arguments, has expired (after ten minutes) or is unknown, a new analysis follows
724
+ with a notice instead of an error.
650
725
 
651
- Een pagina uit een eerder resultaat noemt hoe oud dat resultaat is. Analyseer na een
652
- reparatie dus zónder `runId`: anders zie je de uitslag van vóór de reparatie.
726
+ A page from an earlier result states how old that result is. So after a fix, analyse
727
+ *without* `runId`: otherwise you see the result from before the fix.
653
728
 
654
- ### Annuleren
729
+ ### Cancelling
655
730
 
656
- Annuleert je client het verzoek, dan wordt de hele procesgroep van PHPStan opgeruimd in
657
- plaats van dat hij doorrekent voor een antwoord dat niemand meer leest. Een analyse die nog
658
- in de rij stond wordt niet meer gestart.
731
+ If your client cancels the request, PHPStan's whole process group is cleaned up instead of
732
+ it computing on for an answer nobody will read. An analysis still waiting in the queue is not
733
+ started.
659
734
 
660
- ### Als de analyse niet volledig is
735
+ ### When the analysis is incomplete
661
736
 
662
- PHPStan kan geldige JSON teruggeven en er tegelijk op stderr bij zeggen dat hij het niet
663
- heeft afgemaakt. Dat komt bovenaan het antwoord te staan, vóór alles:
737
+ PHPStan can return valid JSON and at the same time say on stderr that it didn't finish. That
738
+ goes at the very top of the response, before anything else:
664
739
 
665
740
  ```
666
741
  LET OP — PHPStan meldde dit naast het resultaat:
667
742
  - Result is incomplete because of severe errors.
668
743
  ```
669
744
 
670
- Dat onderscheid is wezenlijk: zonder die regel is een lege uitslag niet te onderscheiden van
671
- een schone. Staat er een waarschuwing, dan leest het antwoord ook niet meer "Geen
672
- bevindingen" maar "Verder geen bevindingen".
745
+ That distinction is essential: without that line, an empty result can't be told apart from a
746
+ clean one. When there is a warning, the response also no longer reads "Geen bevindingen" (no
747
+ findings) but "Verder geen bevindingen" (no further findings).
673
748
 
674
- Doet er een baseline mee, dan staat dat er ook bij:
749
+ If a baseline is included, that is stated too:
675
750
 
676
751
  ```
677
752
  Ter info:
@@ -679,12 +754,12 @@ Ter info:
679
754
  rapport. Gebruik baseline_summary om te zien wat er in staat.
680
755
  ```
681
756
 
682
- Dat is het verschil tussen "geen bevindingen" en "geen bevindingen, plus duizend die niet
683
- gemeld worden". De check kijkt of je config het bestand echt includeerteen baseline die
684
- naast `phpstan.neon` ligt zonder erin te staan, doet niets.
757
+ That is the difference between "no findings" and "no findings, plus a thousand that aren't
758
+ reported". The check looks at whether your config really includes the filea baseline
759
+ sitting next to `phpstan.neon` without being included does nothing.
685
760
 
686
- PHPStans eigen aanwijzingen bij het lezen van bevindingen staan apart in `instructions` en
687
- worden onder het rapport getoond:
761
+ PHPStan's own guidance for reading findings is kept separately in `instructions` and shown
762
+ below the report:
688
763
 
689
764
  ```
690
765
  PHPStan zegt hierbij:
@@ -692,45 +767,54 @@ PHPStan zegt hierbij:
692
767
  Do not add type casts just to silence errors.
693
768
  ```
694
769
 
695
- Dat blok is elke run hetzelfde en zegt niets over déze analyse, dus het telt niet mee als
696
- waarschuwingen het wordt niet afgekapt, want een half blok aanwijzingen helpt niemand.
770
+ That block is the same every run and says nothing about *this* analysis, so it doesn't count
771
+ as a warning and it isn't truncated, because half a block of guidance helps nobody.
697
772
 
698
- Het `warnings`-veld staat los van `generalErrors`: dat tweede is PHPStans eigen
699
- `errors`-array uit de JSON. Beide lijsten zijn begrensd op tien meldingen van elk hoogstens
700
- 1000 tekens een mislukte Laravel-bootstrap levert anders een lap tekst op die het hele
701
- antwoord vult. Hoeveel er weggelaten is staat in `omitted`, en in de tekst als "nog N niet
702
- getoond".
773
+ The `warnings` field is separate from `generalErrors`: the latter is PHPStan's own `errors`
774
+ array from the JSON. Both lists are capped at ten messages of at most 1000 characters each —
775
+ a failed Laravel bootstrap otherwise produces a wall of text that fills the whole response.
776
+ How many were left out is in `omitted`, and in the text as "nog N niet getoond" (N more not
777
+ shown).
703
778
 
704
- ## Vereisten
779
+ ## Requirements
705
780
 
706
- - Node 20 of nieuwer
707
- - PHPStan geïnstalleerd in het project zelf (`vendor/bin/phpstan`); de server brengt geen
708
- eigen PHPStan mee, want de versie en de extensies horen bij het project
781
+ - Node 20 or newer
782
+ - PHPStan installed in the project itself (`vendor/bin/phpstan`); the server doesn't bring
783
+ its own PHPStan, because the version and the extensions belong to the project
709
784
 
710
- ## Bekende scherpe randen
785
+ ## Known sharp edges
711
786
 
712
- - **De PHP-geheugenlimiet.** PHPStan klapt daar met een groot project makkelijk overheen;
713
- `memoryLimit: "1G"` lost dat meestal op. De crash komt terug als een gewone fout, met
714
- PHPStans eigen uitleg erbij.
715
- - **Larastan start je Laravel-applicatie.** Lukt dat nietontbrekende `.env`, database niet
716
- bereikbaardan faalt de analyse met de uitleg van Larastan zelf.
717
- - **Analyses in hetzelfde project draaien na elkaar,** omdat PHPStans resultcache in het
718
- project ligt. Verschillende projecten draaien wel tegelijk.
787
+ - **The PHP memory limit.** PHPStan easily exceeds it on a large project;
788
+ `memoryLimit: "1G"` usually solves that. The crash comes back as a regular error, with
789
+ PHPStan's own explanation attached.
790
+ - **Larastan boots your Laravel application.** If that failsmissing `.env`, database not
791
+ reachablethe analysis fails with Larastan's own explanation.
792
+ - **Analyses in the same project run one after another,** because they share one result cache
793
+ (per project, in the temp directory not in the project itself). Different projects do run
794
+ in parallel.
795
+ - **A stale result cache** can make PHPStan crash with a message that seems to make no sense.
796
+ After a `composer update` that no longer happens, because the cache directory follows
797
+ `composer.lock`; if it happens some other way anyway, `clear_result_cache` fixes it. The
798
+ error message itself points to it.
799
+ - **If it works in your terminal but not through the client,** the server is running with a
800
+ different environment. The Claude app, for example, starts it without `TMPDIR`, and then the
801
+ per-project cache lives under `/tmp/phpstan-mcp/` instead of under your shell's temp
802
+ directory.
719
803
 
720
- ## Ontwikkelen
804
+ ## Development
721
805
 
722
806
  ```bash
723
807
  npm run build # tsc → dist/
724
- npm run typecheck # inclusief de tests
808
+ npm run typecheck # including the tests
725
809
  npm run lint
726
810
  npm test
727
- npm run inspect # MCP Inspector tegen de gebouwde server
811
+ npm run inspect # MCP Inspector against the built server
728
812
  ```
729
813
 
730
- Dezelfde vier stappen draaien in CI op Node 24, en nog een keer bij een release. `engines`
731
- belooft `>=20`; die ondergrens is bij het opzetten van CI eenmalig in Docker nagelopen en
732
- draait niet mee bij elke build.
814
+ The same four steps run in CI on Node 24, and once more on a release. `engines` promises
815
+ `>=20`; that lower bound was checked once in Docker when CI was set up and does not run with
816
+ every build.
733
817
 
734
- ## Licentie
818
+ ## License
735
819
 
736
820
  MIT