Skip to content

Commit 9b0c035

Browse files
authored
Merge pull request #108 from smart-mcp-proxy/fix/include-tray-binary-in-all-platforms
fix: include mcpproxy-tray binary in Windows archives
2 parents 09a763a + 41dc8a0 commit 9b0c035

File tree

4 files changed

+167
-30
lines changed

4 files changed

+167
-30
lines changed

.github/workflows/pr-build.yml

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,16 @@ jobs:
6363
cgo: "0"
6464
name: mcpproxy-linux-arm64
6565
archive_format: tar.gz
66-
- os: ubuntu-latest
66+
- os: windows-latest
6767
goos: windows
6868
goarch: amd64
69-
cgo: "0"
69+
cgo: "1"
7070
name: mcpproxy-windows-amd64.exe
7171
archive_format: zip
72-
- os: ubuntu-latest
72+
- os: windows-latest
7373
goos: windows
7474
goarch: arm64
75-
cgo: "0"
75+
cgo: "1"
7676
name: mcpproxy-windows-arm64.exe
7777
archive_format: zip
7878
- os: macos-14
@@ -129,15 +129,26 @@ jobs:
129129
run: cd frontend && npm run build
130130

131131
- name: Copy frontend dist for embedding
132+
shell: bash
132133
run: |
133134
rm -rf web/frontend
134135
mkdir -p web/frontend
135136
cp -r frontend/dist web/frontend/
136137
137138
- name: Run tests (skip binary E2E tests - not compatible with cross-compilation)
138-
run: go test -tags nogui -v -skip "E2E|Binary|MCPProtocol" ./...
139+
# Skip tests for Windows ARM64 (cross-compilation - can't run ARM64 binaries on AMD64 runner)
140+
if: '!(matrix.goos == ''windows'' && matrix.goarch == ''arm64'')'
141+
shell: bash
142+
run: |
143+
# On Windows, skip internal/server tests (E2E tests timeout after 11 minutes)
144+
if [ "${{ matrix.goos }}" = "windows" ]; then
145+
go test -tags nogui -v -skip "E2E|Binary|MCPProtocol" $(go list ./... | grep -v 'internal/server$')
146+
else
147+
go test -tags nogui -v -skip "E2E|Binary|MCPProtocol" ./...
148+
fi
139149
140150
- name: Build binary and create archives
151+
shell: bash
141152
env:
142153
CGO_ENABLED: ${{ matrix.cgo }}
143154
GOOS: ${{ matrix.goos }}
@@ -161,10 +172,18 @@ jobs:
161172
# Create clean core binary for archive
162173
go build -ldflags "${LDFLAGS}" -o ${CLEAN_BINARY} ./cmd/mcpproxy
163174
164-
# Build tray binary for macOS
165-
if [ "${{ matrix.goos }}" = "darwin" ]; then
166-
echo "Building mcpproxy-tray for macOS..."
167-
go build -ldflags "${LDFLAGS}" -o mcpproxy-tray ./cmd/mcpproxy-tray
175+
# Build tray binary for platforms with GUI support (macOS and Windows)
176+
if [ "${{ matrix.goos }}" = "darwin" ] || [ "${{ matrix.goos }}" = "windows" ]; then
177+
echo "Building mcpproxy-tray for ${{ matrix.goos }}..."
178+
179+
# Determine tray binary name
180+
if [ "${{ matrix.goos }}" = "windows" ]; then
181+
TRAY_BINARY="mcpproxy-tray.exe"
182+
else
183+
TRAY_BINARY="mcpproxy-tray"
184+
fi
185+
186+
go build -ldflags "${LDFLAGS}" -o ${TRAY_BINARY} ./cmd/mcpproxy-tray
168187
fi
169188
170189
# Ad-hoc sign macOS binaries (development only)
@@ -182,12 +201,31 @@ jobs:
182201
# Create archive with version info
183202
ARCHIVE_BASE="mcpproxy-${VERSION#v}-${{ matrix.goos }}-${{ matrix.goarch }}"
184203
204+
# Determine files to include in archive
205+
FILES_TO_ARCHIVE="${CLEAN_BINARY}"
206+
207+
# Add tray binary if it exists (Windows and macOS)
208+
if [ "${{ matrix.goos }}" = "windows" ] && [ -f "mcpproxy-tray.exe" ]; then
209+
FILES_TO_ARCHIVE="${FILES_TO_ARCHIVE} mcpproxy-tray.exe"
210+
echo "Including mcpproxy-tray.exe in archive"
211+
elif [ "${{ matrix.goos }}" = "darwin" ] && [ -f "mcpproxy-tray" ]; then
212+
FILES_TO_ARCHIVE="${FILES_TO_ARCHIVE} mcpproxy-tray"
213+
echo "Including mcpproxy-tray in archive"
214+
fi
215+
185216
if [ "${{ matrix.archive_format }}" = "zip" ]; then
186-
# Create archive
187-
zip "${ARCHIVE_BASE}.zip" ${CLEAN_BINARY}
217+
# Create ZIP archive (Windows)
218+
# Use PowerShell Compress-Archive on Windows since zip command isn't available
219+
if [ "${{ matrix.goos }}" = "windows" ]; then
220+
# Convert space-separated list to comma-separated for PowerShell
221+
PS_FILES=$(echo ${FILES_TO_ARCHIVE} | sed 's/ /,/g')
222+
powershell -Command "Compress-Archive -Path ${PS_FILES} -DestinationPath '${ARCHIVE_BASE}.zip'"
223+
else
224+
zip "${ARCHIVE_BASE}.zip" ${FILES_TO_ARCHIVE}
225+
fi
188226
else
189-
# Create archive
190-
tar -czf "${ARCHIVE_BASE}.tar.gz" ${CLEAN_BINARY}
227+
# Create tar.gz archive (Unix)
228+
tar -czf "${ARCHIVE_BASE}.tar.gz" ${FILES_TO_ARCHIVE}
191229
fi
192230
193231
- name: Create .icns icon (macOS)

.github/workflows/release.yml

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,16 @@ jobs:
3131
cgo: "0"
3232
name: mcpproxy-linux-arm64
3333
archive_format: tar.gz
34-
- os: ubuntu-latest
34+
- os: windows-latest
3535
goos: windows
3636
goarch: amd64
37-
cgo: "0"
37+
cgo: "1"
3838
name: mcpproxy-windows-amd64.exe
3939
archive_format: zip
40-
- os: ubuntu-latest
40+
- os: windows-latest
4141
goos: windows
4242
goarch: arm64
43-
cgo: "0"
43+
cgo: "1"
4444
name: mcpproxy-windows-arm64.exe
4545
archive_format: zip
4646
- os: macos-14
@@ -92,6 +92,7 @@ jobs:
9292
run: cd frontend && npm run build
9393

9494
- name: Copy frontend dist to embed location
95+
shell: bash
9596
run: |
9697
rm -rf web/frontend
9798
mkdir -p web/frontend
@@ -176,6 +177,7 @@ jobs:
176177
echo "✅ Certificate import completed"
177178
178179
- name: Build binary and create archives
180+
shell: bash
179181
env:
180182
CGO_ENABLED: ${{ matrix.cgo }}
181183
GOOS: ${{ matrix.goos }}
@@ -199,10 +201,18 @@ jobs:
199201
# Create clean core binary for archive
200202
go build -ldflags "${LDFLAGS}" -o ${CLEAN_BINARY} ./cmd/mcpproxy
201203
202-
# Build tray binary for macOS
203-
if [ "${{ matrix.goos }}" = "darwin" ]; then
204-
echo "Building mcpproxy-tray for macOS..."
205-
go build -ldflags "${LDFLAGS}" -o mcpproxy-tray ./cmd/mcpproxy-tray
204+
# Build tray binary for platforms with GUI support (macOS and Windows)
205+
if [ "${{ matrix.goos }}" = "darwin" ] || [ "${{ matrix.goos }}" = "windows" ]; then
206+
echo "Building mcpproxy-tray for ${{ matrix.goos }}..."
207+
208+
# Determine tray binary name
209+
if [ "${{ matrix.goos }}" = "windows" ]; then
210+
TRAY_BINARY="mcpproxy-tray.exe"
211+
else
212+
TRAY_BINARY="mcpproxy-tray"
213+
fi
214+
215+
go build -ldflags "${LDFLAGS}" -o ${TRAY_BINARY} ./cmd/mcpproxy-tray
206216
fi
207217
208218
# Code sign macOS binaries
@@ -384,16 +394,37 @@ jobs:
384394
ARCHIVE_BASE="mcpproxy-${VERSION#v}-${{ matrix.goos }}-${{ matrix.goarch }}"
385395
LATEST_ARCHIVE_BASE="mcpproxy-latest-${{ matrix.goos }}-${{ matrix.goarch }}"
386396
397+
# Determine files to include in archive
398+
FILES_TO_ARCHIVE="${CLEAN_BINARY}"
399+
400+
# Add tray binary if it exists (Windows and macOS)
401+
if [ "${{ matrix.goos }}" = "windows" ] && [ -f "mcpproxy-tray.exe" ]; then
402+
FILES_TO_ARCHIVE="${FILES_TO_ARCHIVE} mcpproxy-tray.exe"
403+
echo "Including mcpproxy-tray.exe in archive"
404+
elif [ "${{ matrix.goos }}" = "darwin" ] && [ -f "mcpproxy-tray" ]; then
405+
FILES_TO_ARCHIVE="${FILES_TO_ARCHIVE} mcpproxy-tray"
406+
echo "Including mcpproxy-tray in archive"
407+
fi
408+
387409
if [ "${{ matrix.archive_format }}" = "zip" ]; then
388-
# Create versioned archive
389-
zip "${ARCHIVE_BASE}.zip" ${CLEAN_BINARY}
390-
# Create latest archive
391-
zip "${LATEST_ARCHIVE_BASE}.zip" ${CLEAN_BINARY}
410+
# Create ZIP archive (Windows)
411+
# Use PowerShell Compress-Archive on Windows since zip command isn't available
412+
if [ "${{ matrix.goos }}" = "windows" ]; then
413+
# Convert space-separated list to comma-separated for PowerShell
414+
PS_FILES=$(echo ${FILES_TO_ARCHIVE} | sed 's/ /,/g')
415+
powershell -Command "Compress-Archive -Path ${PS_FILES} -DestinationPath '${ARCHIVE_BASE}.zip'"
416+
powershell -Command "Compress-Archive -Path ${PS_FILES} -DestinationPath '${LATEST_ARCHIVE_BASE}.zip'"
417+
else
418+
# Create versioned archive
419+
zip "${ARCHIVE_BASE}.zip" ${FILES_TO_ARCHIVE}
420+
# Create latest archive
421+
zip "${LATEST_ARCHIVE_BASE}.zip" ${FILES_TO_ARCHIVE}
422+
fi
392423
else
393424
# Create versioned archive
394-
tar -czf "${ARCHIVE_BASE}.tar.gz" ${CLEAN_BINARY}
395-
# Create latest archive
396-
tar -czf "${LATEST_ARCHIVE_BASE}.tar.gz" ${CLEAN_BINARY}
425+
tar -czf "${ARCHIVE_BASE}.tar.gz" ${FILES_TO_ARCHIVE}
426+
# Create latest archive
427+
tar -czf "${LATEST_ARCHIVE_BASE}.tar.gz" ${FILES_TO_ARCHIVE}
397428
fi
398429
399430
- name: Create .icns icon (macOS)

STABILITY_UPSTREAM.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Upstream Stability Investigation Plan
2+
3+
## Observed Symptoms
4+
- JetBrains MCP over SSE never surfaces tools, while stdio loads but drops connections; when this happens the dashboard freezes or the daemon hangs until forced termination (reported on Xubuntu 25.04 running as a user-scoped systemd service).
5+
- Intermittent upstream instability is suspected to stem from SSE transport behaviour and OAuth refresh handling.
6+
7+
## Working Hypotheses (code references)
8+
1. **SSE timeout ejects long-lived streams**`internal/transport/http.go:225-279` hard-codes `http.Client{Timeout: 180 * time.Second}` for SSE. Go’s client timeout covers the entire request, so an otherwise healthy SSE stream is forcibly closed every three minutes, likely leaving the proxy in a bad state when the upstream cannot recover quickly.
9+
2. **Endpoint bootstrap deadline too aggressive** – the SSE transport waits only 30s for the `endpoint` event (`github.com/mark3labs/mcp-go@v0.38.0/client/transport/sse.go:176-187`). If JetBrains (or other) servers delay emitting the endpoint while doing OAuth/device checks, we fail before tools load.
10+
3. **OAuth browser flow races with remote UX** – manual OAuth waits 30s for the callback (`internal/upstream/core/connection.go:1722-1759`). In a remote/systemd scenario the user may need more time (or use an out-of-band browser), causing repeated failures and triggering connection churn.
11+
4. **Connection-loss handling gaps** – we never register `Client.OnConnectionLost(...)` on SSE transports, so HTTP/2 idle resets or GOAWAY frames (which JetBrains emits) go unnoticed until the next RPC, amplifying freeze perceptions. This also limits our ability to surface diagnostics in logs/UI.
12+
13+
## Phase 1 – Reproduce & Capture Baseline
14+
- Configure two JetBrains upstreams (SSE and stdio) with `log.level=debug` and, if possible, `transport` trace logging.
15+
- Exercising `scripts/run-web-smoke.sh` and manual UI navigation, collect:
16+
- Upstream-specific logs under `~/.mcpproxy/logs/<server>.log`.
17+
- HTTP traces for `/events` (SSE) and `/api/v1` from the proxy (e.g. `MITM_PROXY=1 go run ./cmd/mcpproxy` or curl with `--trace-time`).
18+
- OAuth callback timing from `internal/oauth` logs to confirm 30s deadline trigger frequency.
19+
- Inspect BoltDB (`bbolt` CLI or `scripts/db-dump.go`) for stored OAuth tokens to see if refresh metadata is present/updated.
20+
21+
**Verification checklist**
22+
- [ ] Baseline reproduction yields “timeout waiting for endpoint” or “context deadline exceeded” in logs when SSE fails.
23+
- [ ] Confirm whether OAuth callback timeout entries align with user interaction delays.
24+
- [ ] Identify whether SSE stream closes almost exactly at 180s uptime.
25+
26+
## Phase 2 – SSE Transport Hardening
27+
- Audit the full SSE pipeline:
28+
- Replace the global `http.Client.Timeout` with per-request contexts or keepalive idle deadlines; ensure this does not regress HTTP fallback.
29+
- Capture GOAWAY/NO_ERROR disconnects by wiring `client.OnConnectionLost` inside `core.connectSSE` and propagate them to the managed client.
30+
- Revisit the 30s endpoint wait; consider JetBrains-specific delay or signal logging (e.g. log the time between `Start` and first `endpoint` frame).
31+
- Develop instrumentation hooks:
32+
- Record SSE connection uptime, retry counters, and last-error state in `StateManager`.
33+
- Emit structured events (e.g., `EventTypeUpstreamTransport`) with transport diagnostics for `/events`.
34+
35+
**Verification checklist**
36+
- [ ] Stress an SSE upstream for >10 minutes and confirm no forced disconnect occurs due to client timeout.
37+
- [ ] Simulate endpoint delay (e.g., proxy that waits 90s before emitting) and confirm new logic handles it or logs actionable warnings.
38+
- [ ] Ensure managed state transitions (`ready``error``reconnecting`) align with injected connection-lost scenarios.
39+
40+
## Phase 3 – OAuth Token Lifecycle Review
41+
- Trace refresh flow end-to-end:
42+
- Instrument `PersistentTokenStore.SaveToken/GetToken` to log token expiry deltas (guarded by debug level).
43+
- Validate `MarkOAuthCompletedWithDB` propagation by queuing fake events in BoltDB and ensuring `Manager.processOAuthEvents` consumes them without double-processing.
44+
- Explore extending the OAuth callback wait window and providing CLI guidance for headless setups (e.g., print verification URL without failing immediately).
45+
- Consider tooling to introspect OAuth state (`/api/v1/oauth/status` or tray dialog) so users can identify expired/invalid tokens.
46+
47+
**Verification checklist**
48+
- [ ] Refreshing an OAuth token updates BoltDB and triggers a reconnect without manual intervention.
49+
- [ ] Extending callback timeout (experimentally) eliminates repeated “OAuth authorization timeout” messages for remote environments.
50+
- [ ] Cross-process completion events always drive a reconnect within the expected polling window (≤5s default).
51+
52+
## Phase 4 – Introspection & User-Facing Diagnostics
53+
- Design lightweight diagnostics:
54+
- CLI subcommand (e.g., `mcpproxy debug upstream <name>`) to dump current transport stats, token expiry, last error, and SSE uptime.
55+
- Optional `/api/v1/diagnostics/upstream` endpoint returning same payload for UI integration.
56+
- Expand logging guidance in `MANUAL_TESTING.md` for capturing SSE issues (e.g., enabling trace on `transport` logger, how to tail upstream logs).
57+
- Evaluate adding Prometheus-style counters (connection retries, OAuth failures) to aid longer-term monitoring.
58+
59+
**Verification checklist**
60+
- [ ] Diagnostics output surfaces enough context for a user to determine whether the issue is OAuth, SSE transport, or upstream crash.
61+
- [ ] UI/tray can surface a human-readable warning when SSE drops repeatedly (without freezing).
62+
- [ ] Documentation changes tested by a fresh install following the guide reproduce the troubleshooting steps successfully.
63+

internal/server/info_shutdown_e2e_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"os/exec"
1212
"path/filepath"
13+
"runtime"
1314
"syscall"
1415
"testing"
1516
"time"
@@ -289,8 +290,12 @@ func TestSocketInfoEndpoint(t *testing.T) {
289290
func buildTestBinary(t *testing.T) (string, func()) {
290291
t.Helper()
291292

292-
// Build binary
293-
binaryPath := filepath.Join(t.TempDir(), "mcpproxy-test")
293+
// Build binary with proper extension for Windows
294+
binaryName := "mcpproxy-test"
295+
if runtime.GOOS == "windows" {
296+
binaryName += ".exe"
297+
}
298+
binaryPath := filepath.Join(t.TempDir(), binaryName)
294299

295300
buildCmd := exec.Command("go", "build",
296301
"-o", binaryPath,

0 commit comments

Comments
 (0)