windows-exe-decompiler-mcp-server 0.1.0 → 0.1.3

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 (134) hide show
  1. package/CLAUDE_INSTALLATION.md +129 -0
  2. package/CODEX_INSTALLATION.md +25 -1
  3. package/COPILOT_INSTALLATION.md +29 -1
  4. package/LICENSE +21 -21
  5. package/README.md +232 -110
  6. package/dist/analysis-provenance.d.ts +50 -2
  7. package/dist/analysis-provenance.js +1 -0
  8. package/dist/analysis-task-runner.js +92 -0
  9. package/dist/config.d.ts +35 -16
  10. package/dist/config.js +37 -3
  11. package/dist/database.js +115 -115
  12. package/dist/decompiler-worker.d.ts +10 -5
  13. package/dist/decompiler-worker.js +240 -58
  14. package/dist/dynamic-trace.d.ts +6 -0
  15. package/dist/dynamic-trace.js +52 -1
  16. package/dist/ghidra-config.js +17 -1
  17. package/dist/index.js +38 -8
  18. package/dist/pe-runtime-functions.d.ts +90 -0
  19. package/dist/pe-runtime-functions.js +452 -0
  20. package/dist/prompts/module-reconstruction-review.d.ts +5 -0
  21. package/dist/prompts/module-reconstruction-review.js +65 -0
  22. package/dist/runtime-correlation.d.ts +5 -0
  23. package/dist/runtime-correlation.js +179 -2
  24. package/dist/sample-workspace.d.ts +28 -0
  25. package/dist/sample-workspace.js +186 -0
  26. package/dist/semantic-name-suggestion-artifacts.d.ts +56 -0
  27. package/dist/semantic-name-suggestion-artifacts.js +87 -0
  28. package/dist/server.js +1 -1
  29. package/dist/setup-guidance.d.ts +73 -0
  30. package/dist/setup-guidance.js +211 -0
  31. package/dist/tools/artifact-read.d.ts +10 -10
  32. package/dist/tools/artifacts-diff.d.ts +8 -8
  33. package/dist/tools/artifacts-list.d.ts +16 -16
  34. package/dist/tools/attack-map.d.ts +8 -8
  35. package/dist/tools/binary-role-profile.d.ts +1197 -0
  36. package/dist/tools/binary-role-profile.js +575 -0
  37. package/dist/tools/code-function-explain-apply.d.ts +12 -12
  38. package/dist/tools/code-function-explain-prepare.d.ts +12 -12
  39. package/dist/tools/code-function-explain-review.d.ts +58 -58
  40. package/dist/tools/code-function-rename-apply.d.ts +12 -12
  41. package/dist/tools/code-function-rename-prepare.d.ts +14 -14
  42. package/dist/tools/code-function-rename-review.d.ts +66 -66
  43. package/dist/tools/code-functions-define.d.ts +337 -0
  44. package/dist/tools/code-functions-define.js +337 -0
  45. package/dist/tools/code-functions-list.js +1 -1
  46. package/dist/tools/code-functions-rank.js +1 -1
  47. package/dist/tools/code-functions-reconstruct.d.ts +368 -58
  48. package/dist/tools/code-functions-reconstruct.js +130 -10
  49. package/dist/tools/code-functions-smart-recover.d.ts +352 -0
  50. package/dist/tools/code-functions-smart-recover.js +190 -0
  51. package/dist/tools/code-module-review-apply.d.ts +233 -0
  52. package/dist/tools/code-module-review-apply.js +209 -0
  53. package/dist/tools/code-module-review-prepare.d.ts +756 -0
  54. package/dist/tools/code-module-review-prepare.js +343 -0
  55. package/dist/tools/code-module-review.d.ts +474 -0
  56. package/dist/tools/code-module-review.js +566 -0
  57. package/dist/tools/code-reconstruct-export.d.ts +193 -21
  58. package/dist/tools/code-reconstruct-export.js +578 -21
  59. package/dist/tools/code-reconstruct-plan.d.ts +20 -20
  60. package/dist/tools/com-role-profile.d.ts +353 -0
  61. package/dist/tools/com-role-profile.js +186 -0
  62. package/dist/tools/dll-export-profile.d.ts +625 -0
  63. package/dist/tools/dll-export-profile.js +230 -0
  64. package/dist/tools/dotnet-metadata-extract.d.ts +122 -122
  65. package/dist/tools/dotnet-reconstruct-export.d.ts +49 -49
  66. package/dist/tools/dotnet-types-list.d.ts +66 -66
  67. package/dist/tools/dynamic-dependencies.d.ts +144 -8
  68. package/dist/tools/dynamic-dependencies.js +20 -1
  69. package/dist/tools/dynamic-memory-import.js +105 -2
  70. package/dist/tools/ghidra-analyze.d.ts +20 -0
  71. package/dist/tools/ghidra-analyze.js +10 -2
  72. package/dist/tools/ghidra-health.d.ts +177 -0
  73. package/dist/tools/ghidra-health.js +28 -0
  74. package/dist/tools/ioc-export.d.ts +8 -8
  75. package/dist/tools/packer-detect.d.ts +14 -14
  76. package/dist/tools/packer-detect.js +6 -9
  77. package/dist/tools/pe-exports-extract.d.ts +8 -8
  78. package/dist/tools/pe-exports-extract.js +6 -9
  79. package/dist/tools/pe-fingerprint.d.ts +36 -36
  80. package/dist/tools/pe-imports-extract.d.ts +8 -8
  81. package/dist/tools/pe-imports-extract.js +6 -9
  82. package/dist/tools/pe-pdata-extract.d.ts +448 -0
  83. package/dist/tools/pe-pdata-extract.js +219 -0
  84. package/dist/tools/pe-symbols-recover.d.ts +227 -0
  85. package/dist/tools/pe-symbols-recover.js +316 -0
  86. package/dist/tools/report-generate.d.ts +15 -1
  87. package/dist/tools/report-generate.js +139 -16
  88. package/dist/tools/report-summarize.d.ts +2242 -8
  89. package/dist/tools/report-summarize.js +246 -10
  90. package/dist/tools/runtime-detect.d.ts +8 -8
  91. package/dist/tools/runtime-detect.js +2 -12
  92. package/dist/tools/rust-binary-analyze.d.ts +2047 -0
  93. package/dist/tools/rust-binary-analyze.js +443 -0
  94. package/dist/tools/sample-profile-get.d.ts +135 -16
  95. package/dist/tools/sample-profile-get.js +35 -32
  96. package/dist/tools/sandbox-execute.d.ts +10 -10
  97. package/dist/tools/strings-extract.d.ts +8 -8
  98. package/dist/tools/strings-extract.js +2 -12
  99. package/dist/tools/strings-floss-decode.d.ts +8 -8
  100. package/dist/tools/system-health.d.ts +172 -36
  101. package/dist/tools/system-health.js +14 -0
  102. package/dist/tools/system-setup-guide.d.ts +179 -0
  103. package/dist/tools/system-setup-guide.js +62 -0
  104. package/dist/tools/tool-help.d.ts +16 -16
  105. package/dist/tools/tool-help.js +172 -0
  106. package/dist/tools/yara-scan.d.ts +8 -8
  107. package/dist/types.d.ts +2 -0
  108. package/dist/workflows/function-explanation-review.d.ts +3034 -76
  109. package/dist/workflows/function-explanation-review.js +172 -42
  110. package/dist/workflows/function-index-recover.d.ts +333 -0
  111. package/dist/workflows/function-index-recover.js +291 -0
  112. package/dist/workflows/module-reconstruction-review.d.ts +3537 -0
  113. package/dist/workflows/module-reconstruction-review.js +354 -0
  114. package/dist/workflows/reconstruct.d.ts +2182 -223
  115. package/dist/workflows/reconstruct.js +575 -32
  116. package/dist/workflows/semantic-name-review.d.ts +1436 -85
  117. package/dist/workflows/semantic-name-review.js +150 -58
  118. package/dist/workflows/triage.d.ts +8 -8
  119. package/ghidra_scripts/DecompileFunction.java +487 -487
  120. package/ghidra_scripts/ExtractCFG.java +256 -256
  121. package/ghidra_scripts/ExtractFunctions.java +442 -442
  122. package/ghidra_scripts/README.md +112 -112
  123. package/ghidra_scripts/SearchFunctionReferences.java +380 -380
  124. package/helpers/DotNetMetadataProbe/DotNetMetadataProbe.csproj +9 -9
  125. package/helpers/DotNetMetadataProbe/Program.cs +566 -566
  126. package/install-to-claude.ps1 +432 -0
  127. package/install-to-codex.ps1 +256 -178
  128. package/install-to-copilot.ps1 +389 -303
  129. package/package.json +4 -2
  130. package/requirements.txt +9 -9
  131. package/workers/requirements-dynamic.txt +11 -11
  132. package/workers/requirements.txt +8 -8
  133. package/workers/speakeasy_compat.py +175 -175
  134. package/workers/static_worker.py +3474 -3474
@@ -0,0 +1,432 @@
1
+ param(
2
+ [string]$ServerName = "windows-exe-decompiler",
3
+ [string]$ProjectRoot = (Get-Location).Path,
4
+ [string]$NodePath,
5
+ [string]$GhidraPath = "",
6
+ [string]$WorkspaceRoot = "$env:USERPROFILE\\.windows-exe-decompiler-mcp-server\\workspaces",
7
+ [string]$DatabasePath = "$env:USERPROFILE\\.windows-exe-decompiler-mcp-server\\data\\database.db",
8
+ [string]$CacheRoot = "$env:USERPROFILE\\.windows-exe-decompiler-mcp-server\\cache",
9
+ [string]$AuditLogPath = "$env:USERPROFILE\\.windows-exe-decompiler-mcp-server\\audit.log",
10
+ [ValidateSet("local", "user", "project")]
11
+ [string]$Scope = "user",
12
+ [switch]$NoBackup
13
+ )
14
+
15
+ Set-StrictMode -Version Latest
16
+ $ErrorActionPreference = "Stop"
17
+
18
+ function Write-Step {
19
+ param([string]$Message)
20
+
21
+ Write-Host ""
22
+ Write-Host $Message -ForegroundColor Yellow
23
+ }
24
+
25
+ function Convert-ToConfigPath {
26
+ param(
27
+ [Parameter(Mandatory = $true)]
28
+ [string]$PathValue
29
+ )
30
+
31
+ $resolved = [System.IO.Path]::GetFullPath($PathValue)
32
+ return ($resolved -replace "\\", "/")
33
+ }
34
+
35
+ function Ensure-Directory {
36
+ param(
37
+ [Parameter(Mandatory = $true)]
38
+ [string]$FilePath
39
+ )
40
+
41
+ $directory = Split-Path -Path $FilePath -Parent
42
+ if ($directory -and -not (Test-Path -Path $directory)) {
43
+ New-Item -Path $directory -ItemType Directory -Force | Out-Null
44
+ }
45
+ }
46
+
47
+ function Backup-FileIfNeeded {
48
+ param(
49
+ [Parameter(Mandatory = $true)]
50
+ [string]$PathValue
51
+ )
52
+
53
+ if ($NoBackup -or -not (Test-Path -Path $PathValue)) {
54
+ return $null
55
+ }
56
+
57
+ $timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
58
+ $backupPath = "$PathValue.$timestamp.bak"
59
+ Copy-Item -Path $PathValue -Destination $backupPath -Force
60
+ return $backupPath
61
+ }
62
+
63
+ function Load-JsonConfig {
64
+ param(
65
+ [Parameter(Mandatory = $true)]
66
+ [string]$PathValue,
67
+
68
+ [string]$RootProperty
69
+ )
70
+
71
+ if (-not (Test-Path -Path $PathValue)) {
72
+ if ($RootProperty) {
73
+ return [pscustomobject]@{
74
+ $RootProperty = [pscustomobject]@{}
75
+ }
76
+ }
77
+
78
+ return [pscustomobject]@{}
79
+ }
80
+
81
+ $raw = Get-Content -Path $PathValue -Raw
82
+ if ([string]::IsNullOrWhiteSpace($raw)) {
83
+ if ($RootProperty) {
84
+ return [pscustomobject]@{
85
+ $RootProperty = [pscustomobject]@{}
86
+ }
87
+ }
88
+
89
+ return [pscustomobject]@{}
90
+ }
91
+
92
+ $config = $raw | ConvertFrom-Json
93
+ if ($null -eq $config) {
94
+ if ($RootProperty) {
95
+ return [pscustomobject]@{
96
+ $RootProperty = [pscustomobject]@{}
97
+ }
98
+ }
99
+
100
+ return [pscustomobject]@{}
101
+ }
102
+
103
+ if ($RootProperty -and -not ($config.PSObject.Properties.Name -contains $RootProperty)) {
104
+ $config | Add-Member -NotePropertyName $RootProperty -NotePropertyValue ([pscustomobject]@{})
105
+ }
106
+
107
+ return $config
108
+ }
109
+
110
+ function Save-JsonConfig {
111
+ param(
112
+ [Parameter(Mandatory = $true)]
113
+ [string]$PathValue,
114
+
115
+ [Parameter(Mandatory = $true)]
116
+ [object]$Config
117
+ )
118
+
119
+ Ensure-Directory -FilePath $PathValue
120
+ $json = $Config | ConvertTo-Json -Depth 30
121
+ [System.IO.File]::WriteAllText($PathValue, $json + [Environment]::NewLine, [System.Text.UTF8Encoding]::new($false))
122
+ }
123
+
124
+ function Set-NamedProperty {
125
+ param(
126
+ [Parameter(Mandatory = $true)]
127
+ [object]$Object,
128
+
129
+ [Parameter(Mandatory = $true)]
130
+ [string]$Name,
131
+
132
+ [Parameter(Mandatory = $true)]
133
+ [object]$Value
134
+ )
135
+
136
+ $existingProperty = $Object.PSObject.Properties[$Name]
137
+ if ($null -ne $existingProperty) {
138
+ $Object.$Name = $Value
139
+ return
140
+ }
141
+
142
+ $Object | Add-Member -NotePropertyName $Name -NotePropertyValue $Value
143
+ }
144
+
145
+ function Get-NodeExecutable {
146
+ if ($NodePath) {
147
+ if (-not (Test-Path -Path $NodePath)) {
148
+ throw "NodePath does not exist: $NodePath"
149
+ }
150
+
151
+ return [System.IO.Path]::GetFullPath($NodePath)
152
+ }
153
+
154
+ $nodeCommand = Get-Command node -ErrorAction SilentlyContinue
155
+ if ($null -eq $nodeCommand) {
156
+ throw "Node.js is not available in PATH. Install Node.js or pass -NodePath explicitly."
157
+ }
158
+
159
+ return $nodeCommand.Source
160
+ }
161
+
162
+ function Resolve-GhidraPath {
163
+ if ($GhidraPath) {
164
+ return $GhidraPath
165
+ }
166
+
167
+ if ($env:GHIDRA_PATH) {
168
+ return $env:GHIDRA_PATH
169
+ }
170
+
171
+ if ($env:GHIDRA_INSTALL_DIR) {
172
+ return $env:GHIDRA_INSTALL_DIR
173
+ }
174
+
175
+ return ""
176
+ }
177
+
178
+ function Resolve-WorkspaceRoot {
179
+ if ($WorkspaceRoot) {
180
+ return $WorkspaceRoot
181
+ }
182
+
183
+ if ($env:WORKSPACE_ROOT) {
184
+ return $env:WORKSPACE_ROOT
185
+ }
186
+
187
+ return "$env:USERPROFILE\\.windows-exe-decompiler-mcp-server\\workspaces"
188
+ }
189
+
190
+ function Resolve-DatabasePath {
191
+ if ($DatabasePath) {
192
+ return $DatabasePath
193
+ }
194
+
195
+ if ($env:DB_PATH) {
196
+ return $env:DB_PATH
197
+ }
198
+
199
+ return "$env:USERPROFILE\\.windows-exe-decompiler-mcp-server\\data\\database.db"
200
+ }
201
+
202
+ function Resolve-CacheRoot {
203
+ if ($CacheRoot) {
204
+ return $CacheRoot
205
+ }
206
+
207
+ if ($env:CACHE_ROOT) {
208
+ return $env:CACHE_ROOT
209
+ }
210
+
211
+ return "$env:USERPROFILE\\.windows-exe-decompiler-mcp-server\\cache"
212
+ }
213
+
214
+ function Resolve-AuditLogPath {
215
+ if ($AuditLogPath) {
216
+ return $AuditLogPath
217
+ }
218
+
219
+ if ($env:AUDIT_LOG_PATH) {
220
+ return $env:AUDIT_LOG_PATH
221
+ }
222
+
223
+ return "$env:USERPROFILE\\.windows-exe-decompiler-mcp-server\\audit.log"
224
+ }
225
+
226
+ function New-ClaudeServerConfig {
227
+ param(
228
+ [Parameter(Mandatory = $true)]
229
+ [string]$NodeExecutable,
230
+
231
+ [Parameter(Mandatory = $true)]
232
+ [string]$EntryPath,
233
+
234
+ [Parameter(Mandatory = $true)]
235
+ [string]$ProjectRootValue,
236
+
237
+ [Parameter(Mandatory = $true)]
238
+ [string]$WorkspaceRootValue,
239
+
240
+ [Parameter(Mandatory = $true)]
241
+ [string]$DatabasePathValue,
242
+
243
+ [Parameter(Mandatory = $true)]
244
+ [string]$CacheRootValue,
245
+
246
+ [Parameter(Mandatory = $true)]
247
+ [string]$AuditLogPathValue,
248
+
249
+ [string]$GhidraPathValue
250
+ )
251
+
252
+ $config = [ordered]@{
253
+ command = $NodeExecutable
254
+ args = @($EntryPath)
255
+ cwd = $ProjectRootValue
256
+ }
257
+
258
+ $config.env = [ordered]@{
259
+ WORKSPACE_ROOT = $WorkspaceRootValue
260
+ DB_PATH = $DatabasePathValue
261
+ CACHE_ROOT = $CacheRootValue
262
+ AUDIT_LOG_PATH = $AuditLogPathValue
263
+ }
264
+
265
+ if ($GhidraPathValue) {
266
+ $config.env.GHIDRA_PATH = $GhidraPathValue
267
+ $config.env.GHIDRA_INSTALL_DIR = $GhidraPathValue
268
+ }
269
+
270
+ return $config
271
+ }
272
+
273
+ function Ensure-ClaudeProjectEntry {
274
+ param(
275
+ [Parameter(Mandatory = $true)]
276
+ [object]$Config,
277
+
278
+ [Parameter(Mandatory = $true)]
279
+ [string]$ProjectKey
280
+ )
281
+
282
+ if (-not ($Config.PSObject.Properties.Name -contains "projects")) {
283
+ $Config | Add-Member -NotePropertyName "projects" -NotePropertyValue ([pscustomobject]@{})
284
+ }
285
+
286
+ if (-not ($Config.projects.PSObject.Properties.Name -contains $ProjectKey)) {
287
+ $projectRecord = [pscustomobject]@{
288
+ allowedTools = @()
289
+ mcpContextUris = @()
290
+ mcpServers = [pscustomobject]@{}
291
+ enabledMcpjsonServers = @()
292
+ disabledMcpjsonServers = @()
293
+ hasTrustDialogAccepted = $false
294
+ hasClaudeMdExternalIncludesApproved = $false
295
+ hasClaudeMdExternalIncludesWarningShown = $false
296
+ exampleFiles = @()
297
+ }
298
+
299
+ $Config.projects | Add-Member -NotePropertyName $ProjectKey -NotePropertyValue $projectRecord
300
+ } elseif (-not ($Config.projects.$ProjectKey.PSObject.Properties.Name -contains "mcpServers")) {
301
+ $Config.projects.$ProjectKey | Add-Member -NotePropertyName "mcpServers" -NotePropertyValue ([pscustomobject]@{})
302
+ }
303
+ }
304
+
305
+ function Get-ClaudeConfigPath {
306
+ return Join-Path -Path $HOME -ChildPath ".claude.json"
307
+ }
308
+
309
+ function Get-ProjectConfigPath {
310
+ param(
311
+ [Parameter(Mandatory = $true)]
312
+ [string]$ProjectRootValue
313
+ )
314
+
315
+ return Join-Path -Path $ProjectRootValue -ChildPath ".mcp.json"
316
+ }
317
+
318
+ Write-Host "=== Windows EXE Decompiler MCP Server - Claude Install ===" -ForegroundColor Cyan
319
+
320
+ Write-Step "Step 1: Validate build output and runtime"
321
+ $projectRootFull = [System.IO.Path]::GetFullPath($ProjectRoot)
322
+ $entryPath = Join-Path -Path $projectRootFull -ChildPath "dist/index.js"
323
+ if (-not (Test-Path -Path $entryPath)) {
324
+ throw "dist/index.js was not found. Run 'npm run build' from the project root first."
325
+ }
326
+
327
+ $nodeExecutable = Get-NodeExecutable
328
+ $nodeExecutableConfig = Convert-ToConfigPath -PathValue $nodeExecutable
329
+ $entryPathConfig = Convert-ToConfigPath -PathValue $entryPath
330
+ $projectRootConfig = Convert-ToConfigPath -PathValue $projectRootFull
331
+ $workspaceRootResolved = Resolve-WorkspaceRoot
332
+ $workspaceRootConfig = Convert-ToConfigPath -PathValue $workspaceRootResolved
333
+ $databasePathResolved = Resolve-DatabasePath
334
+ $databasePathConfig = Convert-ToConfigPath -PathValue $databasePathResolved
335
+ $cacheRootResolved = Resolve-CacheRoot
336
+ $cacheRootConfig = Convert-ToConfigPath -PathValue $cacheRootResolved
337
+ $auditLogPathResolved = Resolve-AuditLogPath
338
+ $auditLogPathConfig = Convert-ToConfigPath -PathValue $auditLogPathResolved
339
+ $ghidraPathResolved = Resolve-GhidraPath
340
+ $ghidraPathExists = $ghidraPathResolved -and (Test-Path -Path $ghidraPathResolved)
341
+ $ghidraPathConfig = if ($ghidraPathResolved) { Convert-ToConfigPath -PathValue $ghidraPathResolved } else { "" }
342
+ $serverConfig = New-ClaudeServerConfig -NodeExecutable $nodeExecutableConfig -EntryPath $entryPathConfig -ProjectRootValue $projectRootConfig -WorkspaceRootValue $workspaceRootConfig -DatabasePathValue $databasePathConfig -CacheRootValue $cacheRootConfig -AuditLogPathValue $auditLogPathConfig -GhidraPathValue $ghidraPathConfig
343
+
344
+ Write-Host "Project root: $projectRootConfig" -ForegroundColor Gray
345
+ Write-Host "Node path: $nodeExecutableConfig" -ForegroundColor Gray
346
+ Write-Host "Server entry: $entryPathConfig" -ForegroundColor Gray
347
+ Write-Host "Workspace root: $workspaceRootConfig" -ForegroundColor Gray
348
+ Write-Host "Database path: $databasePathConfig" -ForegroundColor Gray
349
+ Write-Host "Cache root: $cacheRootConfig" -ForegroundColor Gray
350
+ Write-Host "Audit log path: $auditLogPathConfig" -ForegroundColor Gray
351
+ Write-Host "Scope: $Scope" -ForegroundColor Gray
352
+ if ($ghidraPathConfig -and $ghidraPathExists) {
353
+ Write-Host "Ghidra path: $ghidraPathConfig" -ForegroundColor Gray
354
+ } elseif ($ghidraPathConfig) {
355
+ Write-Host "Warning: the Ghidra path does not exist. The script will still write it into the config." -ForegroundColor Yellow
356
+ } else {
357
+ Write-Host "No Ghidra path supplied. The config will be written without GHIDRA_PATH." -ForegroundColor Yellow
358
+ }
359
+
360
+ $backupPath = $null
361
+
362
+ if ($Scope -eq "project") {
363
+ Write-Step "Step 2: Write project-scoped Claude MCP config"
364
+ $configPath = Get-ProjectConfigPath -ProjectRootValue $projectRootFull
365
+ $backupPath = Backup-FileIfNeeded -PathValue $configPath
366
+ $config = Load-JsonConfig -PathValue $configPath -RootProperty "mcpServers"
367
+ Set-NamedProperty -Object $config.mcpServers -Name $ServerName -Value $serverConfig
368
+ Save-JsonConfig -PathValue $configPath -Config $config
369
+ Write-Host "Project config written: $configPath" -ForegroundColor Green
370
+ } elseif ($Scope -eq "user") {
371
+ Write-Step "Step 2: Write user-scoped Claude MCP config"
372
+ $configPath = Get-ClaudeConfigPath
373
+ $backupPath = Backup-FileIfNeeded -PathValue $configPath
374
+ $config = Load-JsonConfig -PathValue $configPath -RootProperty "mcpServers"
375
+ Set-NamedProperty -Object $config.mcpServers -Name $ServerName -Value $serverConfig
376
+ Save-JsonConfig -PathValue $configPath -Config $config
377
+ Write-Host "User config written: $configPath" -ForegroundColor Green
378
+ } else {
379
+ Write-Step "Step 2: Write local-scoped Claude MCP config"
380
+ $configPath = Get-ClaudeConfigPath
381
+ $backupPath = Backup-FileIfNeeded -PathValue $configPath
382
+ $projectKey = $projectRootConfig
383
+ $config = Load-JsonConfig -PathValue $configPath
384
+ Ensure-ClaudeProjectEntry -Config $config -ProjectKey $projectKey
385
+ Set-NamedProperty -Object $config.projects.$projectKey.mcpServers -Name $ServerName -Value $serverConfig
386
+ Save-JsonConfig -PathValue $configPath -Config $config
387
+ Write-Host "Local config written: $configPath" -ForegroundColor Green
388
+ Write-Host "Project key: $projectKey" -ForegroundColor Gray
389
+ }
390
+
391
+ if ($backupPath) {
392
+ Write-Host "Backup file: $backupPath" -ForegroundColor Gray
393
+ }
394
+
395
+ Write-Step "Step 3: Verify installation with Claude CLI"
396
+ $claudeCommand = Get-Command claude -ErrorAction SilentlyContinue
397
+ if ($null -eq $claudeCommand) {
398
+ Write-Host "Claude CLI was not found in PATH. The config file was written, but automatic verification was skipped." -ForegroundColor Yellow
399
+ exit 0
400
+ }
401
+
402
+ $verifyLocation = $projectRootFull
403
+ if ($Scope -eq "user") {
404
+ $verifyLocation = $env:USERPROFILE
405
+ }
406
+
407
+ Push-Location $verifyLocation
408
+ try {
409
+ & $claudeCommand.Source mcp get $ServerName
410
+ if ($LASTEXITCODE -ne 0) {
411
+ throw "Claude could not resolve MCP server '$ServerName' after writing the config."
412
+ }
413
+
414
+ if ($Scope -eq "user") {
415
+ Push-Location $projectRootFull
416
+ try {
417
+ $localOverride = & $claudeCommand.Source mcp get $ServerName 2>$null
418
+ if ($LASTEXITCODE -eq 0 -and ($localOverride -join "`n") -match "Scope:\s+Local config") {
419
+ Write-Host ""
420
+ Write-Host "Note: this repository also has a local Claude MCP registration. Inside this repo, Claude will prefer the local scope over the user scope." -ForegroundColor Yellow
421
+ }
422
+ } finally {
423
+ Pop-Location
424
+ }
425
+ }
426
+
427
+ Write-Host ""
428
+ Write-Host "Installation complete." -ForegroundColor Green
429
+ Write-Host "Next step: open Claude in this repo and ask it to call 'tool.help' or 'workflow.triage'." -ForegroundColor White
430
+ } finally {
431
+ Pop-Location
432
+ }