Skip to content

Commit d5cc024

Browse files
gfraiteurclaude
andcommitted
Add standalone MCP Approval Server GUI application with history window
Move MCP server from BuildTools to a standalone WPF application with: - System tray integration with status icons (green/orange/blue) - Individual approval windows for each pending request - Command history window showing pending and completed requests - HTTP-based MCP server for Docker container communication - Risk analysis with AI and regex-based assessment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 89f6ab8 commit d5cc024

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3401
-2467
lines changed

CLAUDE.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,22 @@ Before any work in this repo, read these skills:
1818

1919
When running inside a Docker container, you have access to the `host-approval` MCP server for executing privileged commands on the host machine. These commands require human approval before execution.
2020

21+
### Starting the MCP Approval Server
22+
23+
The MCP Approval Server is now a standalone GUI application with system tray integration. Before running Claude in Docker mode, start the server:
24+
25+
1. Build the solution: `dotnet build`
26+
2. Run the GUI application: `.\src\PostSharp.Engineering.McpApprovalServer\bin\Debug\net8.0-windows\PostSharp.Engineering.McpApprovalServer.exe`
27+
3. The server will appear as a tray icon (green = ready, orange = pending requests)
28+
4. Now run `DockerBuild.ps1 -Claude` - it will detect the running server automatically
29+
2130
### Security Model
2231

23-
The MCP server uses token-based authentication:
24-
- DockerBuild.ps1 generates a random 128-bit secret when starting the MCP server
25-
- The secret is passed to the container via the `MCP_APPROVAL_SERVER_TOKEN` environment variable
26-
- All MCP requests include this secret as a URL parameter for authentication
27-
- The MCP server validates the token before processing any commands
32+
The MCP server uses localhost-only binding for security:
33+
- Server binds exclusively to `localhost:9847` (not exposed to the network)
34+
- Docker containers access via the host gateway IP
35+
- No authentication tokens needed since only local processes can connect
36+
- Human approval required for all non-low-risk commands via the GUI
2837

2938
### When to Use the MCP Server
3039

Directory.Packages.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,10 @@
4545
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.5.0-preview.1" />
4646
<PackageVersion Include="Octokit" Version="13.0.1" />
4747
<PackageVersion Include="Octokit.GraphQL" Version="0.2.0-beta" />
48+
<!-- MCP Approval Server GUI dependencies -->
49+
<PackageVersion Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
50+
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.2.2" />
51+
<PackageVersion Include="AvalonEdit" Version="6.3.0.90" />
52+
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
4853
</ItemGroup>
4954
</Project>

DockerBuild.ps1

Lines changed: 51 additions & 440 deletions
Large diffs are not rendered by default.

PostSharp.Engineering.sln

Lines changed: 49 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,49 @@
1-
2-
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 17
4-
VisualStudioVersion = 17.0.31912.275
5-
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PostSharp.Engineering.BuildTools", "src\PostSharp.Engineering.BuildTools\PostSharp.Engineering.BuildTools.csproj", "{79C98592-B228-448D-B590-95FD9C8E3123}"
7-
EndProject
8-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PostSharp.Engineering.Sdk", "src\PostSharp.Engineering.Sdk\PostSharp.Engineering.Sdk.csproj", "{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}"
9-
EndProject
10-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostSharp.Engineering.DocFx", "src\PostSharp.Engineering.DocFx\PostSharp.Engineering.DocFx.csproj", "{53B2C52E-9095-4D49-8C34-5507BB941EE6}"
11-
EndProject
12-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostSharp.Engineering.SystemTypes", "src\PostSharp.Engineering.SystemTypes\PostSharp.Engineering.SystemTypes.csproj", "{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}"
13-
EndProject
14-
Global
15-
GlobalSection(SolutionConfigurationPlatforms) = preSolution
16-
Debug|Any CPU = Debug|Any CPU
17-
Release|Any CPU = Release|Any CPU
18-
EndGlobalSection
19-
GlobalSection(ProjectConfigurationPlatforms) = postSolution
20-
{79C98592-B228-448D-B590-95FD9C8E3123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21-
{79C98592-B228-448D-B590-95FD9C8E3123}.Debug|Any CPU.Build.0 = Debug|Any CPU
22-
{79C98592-B228-448D-B590-95FD9C8E3123}.Release|Any CPU.ActiveCfg = Release|Any CPU
23-
{79C98592-B228-448D-B590-95FD9C8E3123}.Release|Any CPU.Build.0 = Release|Any CPU
24-
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25-
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
26-
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
27-
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Release|Any CPU.Build.0 = Release|Any CPU
28-
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29-
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
30-
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
31-
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Release|Any CPU.Build.0 = Release|Any CPU
32-
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33-
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
34-
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
35-
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Release|Any CPU.Build.0 = Release|Any CPU
36-
EndGlobalSection
37-
GlobalSection(SolutionProperties) = preSolution
38-
HideSolutionNode = FALSE
39-
EndGlobalSection
40-
GlobalSection(ExtensibilityGlobals) = postSolution
41-
SolutionGuid = {EE2C48C5-7AD7-4852-A39D-A3C5C94000A9}
42-
EndGlobalSection
43-
EndGlobal
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.31912.275
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PostSharp.Engineering.BuildTools", "src\PostSharp.Engineering.BuildTools\PostSharp.Engineering.BuildTools.csproj", "{79C98592-B228-448D-B590-95FD9C8E3123}"
7+
EndProject
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PostSharp.Engineering.Sdk", "src\PostSharp.Engineering.Sdk\PostSharp.Engineering.Sdk.csproj", "{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostSharp.Engineering.DocFx", "src\PostSharp.Engineering.DocFx\PostSharp.Engineering.DocFx.csproj", "{53B2C52E-9095-4D49-8C34-5507BB941EE6}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostSharp.Engineering.SystemTypes", "src\PostSharp.Engineering.SystemTypes\PostSharp.Engineering.SystemTypes.csproj", "{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}"
13+
EndProject
14+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PostSharp.Engineering.McpApprovalServer", "src\PostSharp.Engineering.McpApprovalServer\PostSharp.Engineering.McpApprovalServer.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
15+
EndProject
16+
Global
17+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
18+
Debug|Any CPU = Debug|Any CPU
19+
Release|Any CPU = Release|Any CPU
20+
EndGlobalSection
21+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
22+
{79C98592-B228-448D-B590-95FD9C8E3123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23+
{79C98592-B228-448D-B590-95FD9C8E3123}.Debug|Any CPU.Build.0 = Debug|Any CPU
24+
{79C98592-B228-448D-B590-95FD9C8E3123}.Release|Any CPU.ActiveCfg = Release|Any CPU
25+
{79C98592-B228-448D-B590-95FD9C8E3123}.Release|Any CPU.Build.0 = Release|Any CPU
26+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
28+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
29+
{E7879F3E-FEE6-4E89-A265-52A1297BBCBC}.Release|Any CPU.Build.0 = Release|Any CPU
30+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
32+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
33+
{53B2C52E-9095-4D49-8C34-5507BB941EE6}.Release|Any CPU.Build.0 = Release|Any CPU
34+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
36+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
37+
{B6DB99A8-CF03-44CB-9C5E-2FEC7888DA3E}.Release|Any CPU.Build.0 = Release|Any CPU
38+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
41+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
42+
EndGlobalSection
43+
GlobalSection(SolutionProperties) = preSolution
44+
HideSolutionNode = FALSE
45+
EndGlobalSection
46+
GlobalSection(ExtensibilityGlobals) = postSolution
47+
SolutionGuid = {EE2C48C5-7AD7-4852-A39D-A3C5C94000A9}
48+
EndGlobalSection
49+
EndGlobal

StartMcp.ps1

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# StartMcp.ps1 - Builds and runs the MCP Approval Server from a temp folder to avoid file locks
2+
3+
param(
4+
[switch]$NoBuild,
5+
[switch]$Debug
6+
)
7+
8+
$ErrorActionPreference = "Stop"
9+
10+
$projectPath = "$PSScriptRoot\src\PostSharp.Engineering.McpApprovalServer\PostSharp.Engineering.McpApprovalServer.csproj"
11+
$outputPath = "$PSScriptRoot\src\PostSharp.Engineering.McpApprovalServer\bin\Debug\net8.0-windows"
12+
$tempPath = "$env:LOCALAPPDATA\PostSharp\McpApprovalServer\bin"
13+
$exeName = "PostSharp.Engineering.McpApprovalServer.exe"
14+
15+
# Kill any running instance
16+
$existingProcess = Get-Process -Name "PostSharp.Engineering.McpApprovalServer" -ErrorAction SilentlyContinue
17+
if ($existingProcess) {
18+
Write-Host "Stopping existing MCP server process..." -ForegroundColor Yellow
19+
$existingProcess | Stop-Process -Force
20+
Start-Sleep -Milliseconds 500
21+
}
22+
23+
# Build unless -NoBuild is specified
24+
if (-not $NoBuild) {
25+
Write-Host "Building MCP Approval Server..." -ForegroundColor Cyan
26+
$buildArgs = @("build", $projectPath, "-c", "Debug")
27+
if (-not $Debug) {
28+
$buildArgs += "-v:q"
29+
}
30+
& dotnet @buildArgs
31+
if ($LASTEXITCODE -ne 0) {
32+
Write-Host "Build failed!" -ForegroundColor Red
33+
exit 1
34+
}
35+
Write-Host "Build succeeded." -ForegroundColor Green
36+
}
37+
38+
# Ensure temp directory exists
39+
if (-not (Test-Path $tempPath)) {
40+
New-Item -ItemType Directory -Path $tempPath -Force | Out-Null
41+
}
42+
43+
# Copy all files to temp folder
44+
Write-Host "Copying to $tempPath..." -ForegroundColor Cyan
45+
Copy-Item -Path "$outputPath\*" -Destination $tempPath -Recurse -Force
46+
47+
# Start the server
48+
$exePath = Join-Path $tempPath $exeName
49+
Write-Host "Starting MCP server from $exePath..." -ForegroundColor Cyan
50+
Start-Process -FilePath $exePath
51+
52+
Write-Host "MCP Approval Server started." -ForegroundColor Green
53+
Write-Host "Logs: $env:LOCALAPPDATA\PostSharp\McpApprovalServer\audit\" -ForegroundColor Gray

eng/RunClaude.ps1

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,6 @@ if ($env:RUNNING_IN_DOCKER -ne "true")
2020
$mcpConfigArg = ""
2121
if ($McpPort -gt 0)
2222
{
23-
# Get MCP secret from environment variable
24-
$mcpSecret = $env:MCP_APPROVAL_SERVER_TOKEN
25-
if ( [string]::IsNullOrEmpty($mcpSecret))
26-
{
27-
Write-Error "MCP_APPROVAL_SERVER_TOKEN environment variable is not set. Cannot authenticate to MCP server."
28-
exit 1
29-
}
30-
3123
# On Windows containers, host.docker.internal doesn't resolve.
3224
# Use the default gateway IP which points to the host.
3325
$hostIp = (Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Select-Object -First 1).NextHop
@@ -38,20 +30,17 @@ if ($McpPort -gt 0)
3830
}
3931
Write-Host "Host IP (gateway): $hostIp" -ForegroundColor Cyan
4032

41-
# Use HTTP Streamable transport (not SSE) with Bearer token authentication
33+
# Use HTTP Streamable transport - no authentication needed (server binds to localhost)
4234
$mcpUrl = "http://${hostIp}:$McpPort"
43-
Write-Host "Configuring MCP approval server with Bearer token authentication" -ForegroundColor Cyan
35+
Write-Host "Configuring MCP approval server at $mcpUrl" -ForegroundColor Cyan
4436

45-
# Create temporary MCP config file with Bearer token authentication
37+
# Create temporary MCP config file (no authentication header - server binds to localhost only)
4638
$mcpConfigPath = "$env:TEMP\mcp-config.json"
4739
$mcpConfig = @{
4840
'mcpServers' = @{
4941
'host-approval' = @{
5042
'type' = 'http'
5143
'url' = $mcpUrl
52-
'headers' = @{
53-
'Authorization' = "Bearer $mcpSecret"
54-
}
5544
}
5645
}
5746
}

0 commit comments

Comments
 (0)