From 2b13e22ce83cb1732ed2ad527d64764ac4214956 Mon Sep 17 00:00:00 2001 From: Algis Dumbris Date: Thu, 18 Jun 2026 06:59:36 +0300 Subject: [PATCH] docs(claude): slim CLAUDE.md to orientation+behavior; move niche sections to docs/ Cuts the per-session/per-heartbeat instruction tax (CLAUDE.md is loaded into every Claude Code session and every Paperclip agent heartbeat). 779 -> 155 lines / 40KB -> 11KB by moving reference-heavy sections into docs/ and pointing to them, deleting auto-generated noise, and fixing a stale linter note. ## Changes - Move to docs/development/: web-ui-verification.md (Playwright sweep), macos-tray.md (tray build + ui-test), server-edition-multiuser-auth.md (Spec 024) - Compact CLI/endpoint/health/sensitive-data sections to summaries + doc links - Delete auto-generated 'Active Technologies' (~45 lines) + 'Recent Changes' - Fix linter guidance: CI uses golangci-lint v2 (.github/.golangci.yml), stricter than the local v1.x scripts/run-linter.sh - Patch .specify/scripts/bash/update-agent-context.sh so 'Active Technologies' no longer regrows on every speckit run (clears new_tech_entries) - Preserve all behavioral directives (autonomy, TDD, stop conditions) verbatim --- .specify/scripts/bash/update-agent-context.sh | 165 ++-- CLAUDE.md | 792 ++---------------- docs/development/macos-tray.md | 52 ++ .../server-edition-multiuser-auth.md | 74 ++ docs/development/web-ui-verification.md | 45 + 5 files changed, 341 insertions(+), 787 deletions(-) create mode 100644 docs/development/macos-tray.md create mode 100644 docs/development/server-edition-multiuser-auth.md create mode 100644 docs/development/web-ui-verification.md diff --git a/.specify/scripts/bash/update-agent-context.sh b/.specify/scripts/bash/update-agent-context.sh index 2a44c68a1..3ab624d39 100755 --- a/.specify/scripts/bash/update-agent-context.sh +++ b/.specify/scripts/bash/update-agent-context.sh @@ -2,7 +2,7 @@ # Update agent context files with information from plan.md # -# This script maintains AI agent context files by parsing feature specifications +# This script maintains AI agent context files by parsing feature specifications # and updating agent-specific configuration files with project information. # # MAIN FUNCTIONS: @@ -58,7 +58,7 @@ eval $(get_feature_paths) NEW_PLAN="$IMPL_PLAN" # Alias for compatibility with existing code AGENT_TYPE="${1:-}" -# Agent-specific file paths +# Agent-specific file paths CLAUDE_FILE="$REPO_ROOT/CLAUDE.md" GEMINI_FILE="$REPO_ROOT/GEMINI.md" COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md" @@ -128,7 +128,7 @@ validate_environment() { fi exit 1 fi - + # Check if plan.md exists if [[ ! -f "$NEW_PLAN" ]]; then log_error "No plan.md found at $NEW_PLAN" @@ -138,7 +138,7 @@ validate_environment() { fi exit 1 fi - + # Check if template exists (needed for new files) if [[ ! -f "$TEMPLATE_FILE" ]]; then log_warning "Template file not found at $TEMPLATE_FILE" @@ -153,7 +153,7 @@ validate_environment() { extract_plan_field() { local field_pattern="$1" local plan_file="$2" - + grep "^\*\*${field_pattern}\*\*: " "$plan_file" 2>/dev/null | \ head -1 | \ sed "s|^\*\*${field_pattern}\*\*: ||" | \ @@ -164,39 +164,39 @@ extract_plan_field() { parse_plan_data() { local plan_file="$1" - + if [[ ! -f "$plan_file" ]]; then log_error "Plan file not found: $plan_file" return 1 fi - + if [[ ! -r "$plan_file" ]]; then log_error "Plan file is not readable: $plan_file" return 1 fi - + log_info "Parsing plan data from $plan_file" - + NEW_LANG=$(extract_plan_field "Language/Version" "$plan_file") NEW_FRAMEWORK=$(extract_plan_field "Primary Dependencies" "$plan_file") NEW_DB=$(extract_plan_field "Storage" "$plan_file") NEW_PROJECT_TYPE=$(extract_plan_field "Project Type" "$plan_file") - + # Log what we found if [[ -n "$NEW_LANG" ]]; then log_info "Found language: $NEW_LANG" else log_warning "No language information found in plan" fi - + if [[ -n "$NEW_FRAMEWORK" ]]; then log_info "Found framework: $NEW_FRAMEWORK" fi - + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then log_info "Found database: $NEW_DB" fi - + if [[ -n "$NEW_PROJECT_TYPE" ]]; then log_info "Found project type: $NEW_PROJECT_TYPE" fi @@ -206,11 +206,11 @@ format_technology_stack() { local lang="$1" local framework="$2" local parts=() - + # Add non-empty parts [[ -n "$lang" && "$lang" != "NEEDS CLARIFICATION" ]] && parts+=("$lang") [[ -n "$framework" && "$framework" != "NEEDS CLARIFICATION" && "$framework" != "N/A" ]] && parts+=("$framework") - + # Join with proper formatting if [[ ${#parts[@]} -eq 0 ]]; then echo "" @@ -232,7 +232,7 @@ format_technology_stack() { get_project_structure() { local project_type="$1" - + if [[ "$project_type" == *"web"* ]]; then echo "backend/\\nfrontend/\\ntests/" else @@ -242,7 +242,7 @@ get_project_structure() { get_commands_for_language() { local lang="$1" - + case "$lang" in *"Python"*) echo "cd src && pytest && ruff check ." @@ -269,40 +269,40 @@ create_new_agent_file() { local temp_file="$2" local project_name="$3" local current_date="$4" - + if [[ ! -f "$TEMPLATE_FILE" ]]; then log_error "Template not found at $TEMPLATE_FILE" return 1 fi - + if [[ ! -r "$TEMPLATE_FILE" ]]; then log_error "Template file is not readable: $TEMPLATE_FILE" return 1 fi - + log_info "Creating new agent context file from template..." - + if ! cp "$TEMPLATE_FILE" "$temp_file"; then log_error "Failed to copy template file" return 1 fi - + # Replace template placeholders local project_structure project_structure=$(get_project_structure "$NEW_PROJECT_TYPE") - + local commands commands=$(get_commands_for_language "$NEW_LANG") - + local language_conventions language_conventions=$(get_language_conventions "$NEW_LANG") - + # Perform substitutions with error checking using safer approach # Escape special characters for sed by using a different delimiter or escaping local escaped_lang=$(printf '%s\n' "$NEW_LANG" | sed 's/[\[\.*^$()+{}|]/\\&/g') local escaped_framework=$(printf '%s\n' "$NEW_FRAMEWORK" | sed 's/[\[\.*^$()+{}|]/\\&/g') local escaped_branch=$(printf '%s\n' "$CURRENT_BRANCH" | sed 's/[\[\.*^$()+{}|]/\\&/g') - + # Build technology stack and recent change strings conditionally local tech_stack if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then @@ -335,7 +335,7 @@ create_new_agent_file() { "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|" "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|$recent_change|" ) - + for substitution in "${substitutions[@]}"; do if ! sed -i.bak -e "$substitution" "$temp_file"; then log_error "Failed to perform substitution: $substitution" @@ -343,14 +343,14 @@ create_new_agent_file() { return 1 fi done - + # Convert \n sequences to actual newlines newline=$(printf '\n') sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file" - + # Clean up backup files rm -f "$temp_file.bak" "$temp_file.bak2" - + return 0 } @@ -360,49 +360,57 @@ create_new_agent_file() { update_existing_agent_file() { local target_file="$1" local current_date="$2" - + log_info "Updating existing agent context file..." - + # Use a single temporary file for atomic update local temp_file temp_file=$(mktemp) || { log_error "Failed to create temporary file" return 1 } - + # Process the file in one pass local tech_stack=$(format_technology_stack "$NEW_LANG" "$NEW_FRAMEWORK") local new_tech_entries=() local new_change_entry="" - + # Prepare new technology entries if [[ -n "$tech_stack" ]] && ! grep -q "$tech_stack" "$target_file"; then new_tech_entries+=("- $tech_stack ($CURRENT_BRANCH)") fi - + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]] && ! grep -q "$NEW_DB" "$target_file"; then new_tech_entries+=("- $NEW_DB ($CURRENT_BRANCH)") fi - + # Prepare new change entry if [[ -n "$tech_stack" ]]; then new_change_entry="- $CURRENT_BRANCH: Added $tech_stack" elif [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]]; then new_change_entry="- $CURRENT_BRANCH: Added $NEW_DB" fi - + # Check if sections exist in the file local has_active_technologies=0 local has_recent_changes=0 - + if grep -q "^## Active Technologies" "$target_file" 2>/dev/null; then has_active_technologies=1 fi - + if grep -q "^## Recent Changes" "$target_file" 2>/dev/null; then has_recent_changes=1 fi - + + # MCPProxy: stop the unbounded "## Active Technologies" growth in CLAUDE.md. + # That per-spec tech list is noise re-loaded into every agent/heartbeat + # context; the stack is documented statically in CLAUDE.md instead. Clearing + # this array makes the in-loop appends (guarded on its length) and the + # post-loop section creation no-op, so the section is neither grown nor + # recreated. "Recent Changes" is left intact (already capped to 2 entries). + new_tech_entries=() + # Process file line by line local in_tech_section=false local in_changes_section=false @@ -410,7 +418,7 @@ update_existing_agent_file() { local changes_entries_added=false local existing_changes_count=0 local file_ended=false - + while IFS= read -r line || [[ -n "$line" ]]; do # Handle Active Technologies section if [[ "$line" == "## Active Technologies" ]]; then @@ -435,7 +443,7 @@ update_existing_agent_file() { echo "$line" >> "$temp_file" continue fi - + # Handle Recent Changes section if [[ "$line" == "## Recent Changes" ]]; then echo "$line" >> "$temp_file" @@ -458,7 +466,7 @@ update_existing_agent_file() { fi continue fi - + # Update timestamp if [[ "$line" =~ \*\*Last\ updated\*\*:.*[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] ]]; then echo "$line" | sed "s/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/$current_date/" >> "$temp_file" @@ -466,13 +474,13 @@ update_existing_agent_file() { echo "$line" >> "$temp_file" fi done < "$target_file" - + # Post-loop check: if we're still in the Active Technologies section and haven't added new entries if [[ $in_tech_section == true ]] && [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" tech_entries_added=true fi - + # If sections don't exist, add them at the end of the file if [[ $has_active_technologies -eq 0 ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then echo "" >> "$temp_file" @@ -480,21 +488,21 @@ update_existing_agent_file() { printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" tech_entries_added=true fi - + if [[ $has_recent_changes -eq 0 ]] && [[ -n "$new_change_entry" ]]; then echo "" >> "$temp_file" echo "## Recent Changes" >> "$temp_file" echo "$new_change_entry" >> "$temp_file" changes_entries_added=true fi - + # Move temp file to target atomically if ! mv "$temp_file" "$target_file"; then log_error "Failed to update target file" rm -f "$temp_file" return 1 fi - + return 0 } #============================================================================== @@ -504,19 +512,19 @@ update_existing_agent_file() { update_agent_file() { local target_file="$1" local agent_name="$2" - + if [[ -z "$target_file" ]] || [[ -z "$agent_name" ]]; then log_error "update_agent_file requires target_file and agent_name parameters" return 1 fi - + log_info "Updating $agent_name context file: $target_file" - + local project_name project_name=$(basename "$REPO_ROOT") local current_date current_date=$(date +%Y-%m-%d) - + # Create directory if it doesn't exist local target_dir target_dir=$(dirname "$target_file") @@ -526,7 +534,7 @@ update_agent_file() { return 1 fi fi - + if [[ ! -f "$target_file" ]]; then # Create new file from template local temp_file @@ -534,7 +542,7 @@ update_agent_file() { log_error "Failed to create temporary file" return 1 } - + if create_new_agent_file "$target_file" "$temp_file" "$project_name" "$current_date"; then if mv "$temp_file" "$target_file"; then log_success "Created new $agent_name context file" @@ -554,12 +562,12 @@ update_agent_file() { log_error "Cannot read existing file: $target_file" return 1 fi - + if [[ ! -w "$target_file" ]]; then log_error "Cannot write to existing file: $target_file" return 1 fi - + if update_existing_agent_file "$target_file" "$current_date"; then log_success "Updated existing $agent_name context file" else @@ -567,7 +575,7 @@ update_agent_file() { return 1 fi fi - + return 0 } @@ -577,7 +585,7 @@ update_agent_file() { update_specific_agent() { local agent_type="$1" - + case "$agent_type" in claude) update_agent_file "$CLAUDE_FILE" "Claude Code" @@ -631,43 +639,43 @@ update_specific_agent() { update_all_existing_agents() { local found_agent=false - + # Check each possible agent file and update if it exists if [[ -f "$CLAUDE_FILE" ]]; then update_agent_file "$CLAUDE_FILE" "Claude Code" found_agent=true fi - + if [[ -f "$GEMINI_FILE" ]]; then update_agent_file "$GEMINI_FILE" "Gemini CLI" found_agent=true fi - + if [[ -f "$COPILOT_FILE" ]]; then update_agent_file "$COPILOT_FILE" "GitHub Copilot" found_agent=true fi - + if [[ -f "$CURSOR_FILE" ]]; then update_agent_file "$CURSOR_FILE" "Cursor IDE" found_agent=true fi - + if [[ -f "$QWEN_FILE" ]]; then update_agent_file "$QWEN_FILE" "Qwen Code" found_agent=true fi - + if [[ -f "$AGENTS_FILE" ]]; then update_agent_file "$AGENTS_FILE" "Codex/opencode" found_agent=true fi - + if [[ -f "$WINDSURF_FILE" ]]; then update_agent_file "$WINDSURF_FILE" "Windsurf" found_agent=true fi - + if [[ -f "$KILOCODE_FILE" ]]; then update_agent_file "$KILOCODE_FILE" "Kilo Code" found_agent=true @@ -677,7 +685,7 @@ update_all_existing_agents() { update_agent_file "$AUGGIE_FILE" "Auggie CLI" found_agent=true fi - + if [[ -f "$ROO_FILE" ]]; then update_agent_file "$ROO_FILE" "Roo Code" found_agent=true @@ -692,7 +700,7 @@ update_all_existing_agents() { update_agent_file "$Q_FILE" "Amazon Q Developer CLI" found_agent=true fi - + # If no agent files exist, create a default Claude file if [[ "$found_agent" == false ]]; then log_info "No existing agent files found, creating default Claude file..." @@ -702,19 +710,19 @@ update_all_existing_agents() { print_summary() { echo log_info "Summary of changes:" - + if [[ -n "$NEW_LANG" ]]; then echo " - Added language: $NEW_LANG" fi - + if [[ -n "$NEW_FRAMEWORK" ]]; then echo " - Added framework: $NEW_FRAMEWORK" fi - + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then echo " - Added database: $NEW_DB" fi - + echo log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|q]" @@ -727,18 +735,18 @@ print_summary() { main() { # Validate environment before proceeding validate_environment - + log_info "=== Updating agent context files for feature $CURRENT_BRANCH ===" - + # Parse the plan file to extract project information if ! parse_plan_data "$NEW_PLAN"; then log_error "Failed to parse plan data" exit 1 fi - + # Process based on agent type argument local success=true - + if [[ -z "$AGENT_TYPE" ]]; then # No specific agent provided - update all existing agent files log_info "No agent specified, updating all existing agent files..." @@ -752,10 +760,10 @@ main() { success=false fi fi - + # Print summary print_summary - + if [[ "$success" == true ]]; then log_success "Agent context update completed successfully" exit 0 @@ -769,4 +777,3 @@ main() { if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi - diff --git a/CLAUDE.md b/CLAUDE.md index 34839684a..4097238e4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,779 +1,155 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - +Guidance for Claude Code / AI agents working in this repo. **This file is loaded into every session and every Paperclip heartbeat — keep it lean.** It is orientation + behavior only; detailed reference lives in `docs/`. ## Autonomous Operation Constraints -When operating to complete a task, adhere strictly to the following constraints to ensure zero-interruption execution: ### Must-Do (Defaults & Assumptions) -- **Zero Interruption Policy**: If a decision is needed and no explicit instruction exists, you MUST make an informed, safe assumption based on idiomatic Go best practices and document it in the PR/commit. Do NOT ask for human clarification mid-task. -- **Test-Driven Progress**: You must write a failing Go test (`_test.go`) for every sub-task before implementing the feature. +- **Zero Interruption Policy**: If a decision is needed and no explicit instruction exists, make an informed, safe assumption based on idiomatic Go best practices and document it in the PR/commit. Do NOT ask for human clarification mid-task. +- **Test-Driven Progress**: Write a failing Go test (`_test.go`) for every sub-task before implementing the feature. - **Graceful Fallbacks**: If an API or dependency lacks documentation, use mock interfaces or a simplified implementation rather than blocking the task. -- **Continuous Logging**: Document every step completed in an `execution_log.md` within the current working directory to maintain state. ### Must-Nots - **Do NOT ask for plan approval**: Once a plan/spec is generated, begin execution immediately. -- **Do NOT stop for code style choices**: Run `gofmt` or `goimports` and strictly follow standard Go conventions. +- **Do NOT stop for code style choices**: Run `gofmt`/`goimports` and follow standard Go conventions. ### Escalation Triggers (Stop Conditions) -Only halt execution and ask a human IF: -1. You need to perform destructive data operations or delete core proxy logic that cannot be mocked. -2. A required environment variable is missing from `.env` and cannot be mocked for the scope of the task. +Only halt and ask a human IF: +1. You need destructive data operations or to delete core proxy logic that cannot be mocked. +2. A required environment variable is missing from `.env` and cannot be mocked for the task's scope. 3. You are stuck in an error loop for the same `go test` failing after 5 consecutive attempts. - - ## Project Overview -MCPProxy is a Go-based desktop application that acts as a smart proxy for AI agents using the Model Context Protocol (MCP). It provides intelligent tool discovery, massive token savings, and built-in security quarantine against malicious MCP servers. +MCPProxy is a Go desktop application that acts as a smart proxy for AI agents using the Model Context Protocol (MCP): intelligent tool discovery, massive token savings, and built-in security quarantine against malicious MCP servers. + +**Stack**: Go 1.24 (backend) · TypeScript 5.9 / Vue 3.5 (frontend) · Swift 5.9 (macOS tray). Storage: BBolt (`config.db`) + Bleve (search index). Avoid new dependencies without clear need. ## Editions (Personal & Server) -MCPProxy is built in two editions from the same codebase using Go build tags: +Built in two editions from one codebase via Go build tags: -| Edition | Build Command | Binary | Distribution | -|---------|--------------|--------|-------------| +| Edition | Build | Binary | Distribution | +|---------|-------|--------|--------------| | **Personal** (default) | `go build ./cmd/mcpproxy` | `mcpproxy` | macOS DMG, Windows installer, Linux tar.gz | -| **Server** | `go build -tags server ./cmd/mcpproxy` | `mcpproxy-server` | Docker image, .deb package, Linux tar.gz | - -> Every feature decision should ask: "Does this make the personal edition so good that developers tell their teammates about it?" - -### Key Directories - -| Directory | Purpose | -|-----------|---------| -| `cmd/mcpproxy/edition.go` | Default edition = "personal" | -| `cmd/mcpproxy/edition_teams.go` | Build-tagged override for server edition | -| `cmd/mcpproxy/serveredition_register.go` | Server feature registration entry point | -| `internal/serveredition/` | Server-only code (all files have `//go:build server`) | -| `internal/serveredition/auth/` | OAuth, sessions, JWT tokens, middleware | -| `internal/serveredition/users/` | User/session models, BBolt store | -| `internal/serveredition/workspace/` | Per-user workspace for personal upstreams | -| `internal/serveredition/multiuser/` | Multi-user router, tool filtering, activity isolation | -| `internal/serveredition/api/` | Server REST API endpoints (user, admin, auth) | -| `native/macos/MCPProxy/` | Swift macOS tray app (SwiftUI, macOS 13+) | -| `native/macos/MCPProxyUITest/` | Swift MCP server for UI testing (accessibility + screenshots) | -| `native/windows/` | Future C# tray app (placeholder) | - -### Edition Detection +| **Server** | `go build -tags server ./cmd/mcpproxy` | `mcpproxy-server` | Docker image, .deb, Linux tar.gz | -The binary self-identifies its edition: -- `mcpproxy version` → `MCPProxy v0.21.0 (personal) darwin/arm64` -- `/api/v1/status` → `{"edition": "personal", ...}` +All server code is behind `//go:build server` in `internal/serveredition/`; the personal edition is unaffected. The binary self-identifies (`mcpproxy version`, `/api/v1/status` → `"edition"`). Server multi-user OAuth (Spec 024): see [docs/development/server-edition-multiuser-auth.md](docs/development/server-edition-multiuser-auth.md). -## Server Multi-User Authentication (Spec 024) - -Server edition supports OAuth-based multi-user authentication with Google, GitHub, or Microsoft identity providers. - -### Server Configuration - -```json -{ - "server_edition": { - "enabled": true, - "admin_emails": ["admin@company.com"], - "oauth": { - "provider": "google", - "client_id": "xxx.apps.googleusercontent.com", - "client_secret": "GOCSPX-xxx", - "tenant_id": "", - "allowed_domains": ["company.com"] - }, - "session_ttl": "24h", - "bearer_token_ttl": "24h", - "workspace_idle_timeout": "30m", - "max_user_servers": 20 - } -} -``` - -### Server API Endpoints - -| Endpoint | Auth | Description | -|----------|------|-------------| -| `GET /api/v1/auth/login` | Public | Initiate OAuth login flow | -| `GET /api/v1/auth/callback` | Public | OAuth callback (creates session) | -| `GET /api/v1/auth/me` | Session/JWT | Get current user profile | -| `POST /api/v1/auth/token` | Session | Generate JWT bearer token for MCP | -| `POST /api/v1/auth/logout` | Session | Invalidate session | -| `GET /api/v1/user/servers` | Session/JWT | List user's servers (personal + shared) | -| `POST /api/v1/user/servers` | Session/JWT | Add personal upstream server | -| `GET /api/v1/user/activity` | Session/JWT | User's activity log | -| `GET /api/v1/user/diagnostics` | Session/JWT | Server health for user's servers | -| `GET /api/v1/admin/users` | Admin | List all users | -| `POST /api/v1/admin/users/{id}/disable` | Admin | Disable a user | -| `GET /api/v1/admin/activity` | Admin | All users' activity logs | -| `GET /api/v1/admin/sessions` | Admin | List active sessions | - -### Server Architecture - -- **Auth flow**: OAuth 2.0 + PKCE → Session cookie (Web UI) + JWT bearer (MCP/API) -- **Server types**: Shared (config file, single connection) + Personal (DB, per-user connections) -- **Isolation**: Users see only shared + own personal servers. Activity logs user-scoped. -- **Admin**: Identified by `admin_emails` config. Sees all activity, manages users. -- **Build tag**: All server code behind `//go:build server`. Personal edition unaffected. - -### Server Testing - -```bash -go test -tags server ./internal/serveredition/... -v -race # All server unit + integration tests -go build -tags server ./cmd/mcpproxy # Build server edition -go build ./cmd/mcpproxy # Verify personal edition unaffected -``` - -## Architecture: Core + Tray Split - -- **Core Server** (`mcpproxy`): Headless HTTP API server with MCP proxy functionality -- **Tray Application** (`mcpproxy-tray`): Standalone GUI application that manages the core server - -**Key Benefits**: Auto-start, port conflict resolution, independent operation, real-time sync via SSE. - -## Development Commands - -### Build -```bash -go build -o mcpproxy ./cmd/mcpproxy # Core server (personal) -go build -tags server -o mcpproxy-server ./cmd/mcpproxy # Core server (server edition) -GOOS=darwin CGO_ENABLED=1 go build -o mcpproxy-tray ./cmd/mcpproxy-tray # Tray app -make build # Frontend and backend (personal) -make build-server # Frontend and backend (server) -make build-docker # Server Docker image -./scripts/build.sh # Cross-platform build -``` - -### Testing - -**IMPORTANT: Always run tests before committing changes!** - -```bash -./scripts/test-api-e2e.sh # Quick API E2E test (required) -./scripts/verify-oas-coverage.sh # OpenAPI coverage (if modifying REST endpoints) -./scripts/run-all-tests.sh # Full test suite -go test ./internal/... -v # Unit tests -go test -race ./internal/... -v # Race detection -./scripts/run-oauth-e2e.sh # OAuth E2E tests -``` - -**E2E Prerequisites**: Node.js, npm, jq, built mcpproxy binary. - -### Verifying Web UI changes (Playwright + rich HTML report) - -When you modify the Web UI (any Vue file under `frontend/src/`), verify it end-to-end with a Playwright sweep that captures screenshots and packages them into a self-contained HTML report. This is the same workflow used to verify Spec 046 v2 — see `specs/046-local-first-onboarding/verification/` for a worked example. - -The pattern, in order: - -1. **Stand up a fresh mcpproxy.** Use a throwaway data-dir so persisted state doesn't bleed between runs: - ```bash - pkill -f 'mcpproxy serve.*' 2>/dev/null; sleep 1 - rm -rf /tmp/mcpproxy-uitest/{config.db,index.bleve,logs} 2>/dev/null - cat > /tmp/mcpproxy-uitest/mcp_config.json <<'EOF' - { "listen": "127.0.0.1:18081", "data_dir": "/tmp/mcpproxy-uitest", "api_key": "uitest", "enable_web_ui": true, "enable_socket": false, "telemetry": {"enabled": false}, "mcpServers": [] } - EOF - ./mcpproxy serve --config=/tmp/mcpproxy-uitest/mcp_config.json --listen=127.0.0.1:18081 --log-level=info > /tmp/mcpproxy-uitest/server.log 2>&1 & - until curl -sf -H "X-API-Key: uitest" http://127.0.0.1:18081/api/v1/status >/dev/null; do sleep 1; done - ``` -2. **Reuse the existing Playwright install.** `e2e/playwright/node_modules` already has Playwright + Chromium 1217. Symlink it into your scratch dir: - ```bash - mkdir -p /tmp/uitest && cd /tmp/uitest - ln -sfn /Users/user/repos/mcpproxy-go/e2e/playwright/node_modules ./node_modules - ``` -3. **Pin the Chromium binary in `playwright.config.ts`** so Playwright doesn't try to download a different version: - ```ts - import { defineConfig } from '@playwright/test'; - export default defineConfig({ - testDir: '.', timeout: 30000, fullyParallel: false, workers: 1, retries: 0, - use: { - headless: true, - viewport: { width: 1440, height: 900 }, - launchOptions: { - executablePath: '/Users/user/Library/Caches/ms-playwright/chromium-1217/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing', - }, - }, - }); - ``` -4. **Write the spec.** Use `data-test` attributes already on the components (the project convention). For new components, add them. Drive scenarios with `page.locator('[data-test="..."]')`. Always use `page.waitForLoadState('domcontentloaded')` — `networkidle` hangs because of the SSE channel. Snapshot each state with `page.screenshot({ path: ... })`. Number screenshots in execution order so the report renders left-to-right. -5. **Run.** `./node_modules/.bin/playwright test --reporter=list`. Iterate until green. -6. **Build the rich HTML report.** A short Python script that base64-embeds each PNG and wraps it in a styled `
` per scenario produces a single self-contained HTML file the user can open offline. Pattern: top summary card with pass/fail counts, then one collapsible per scenario with `Expected` / `Observed` / inline screenshot. The reference implementation is `/tmp/wizard-v2-verify/build-report.py` from the v2 work — clone it and update the `SCENARIOS` list. Output goes to `specs//verification/report.html`. -7. **Drop screenshots + report alongside the spec.** Always commit them with the spec changes — they're part of the trace. -8. **Surface the report.** End your reply with `open ` so the user can review without re-running the suite. - -Key gotchas: -- The wizard's `` element renders as `[open]` only when the Vue store sets it. To assert open/closed state robustly, query the dialog property in `page.evaluate()`, not aria-hidden or styling. -- `pkill -f 'mcpproxy ... '` matches the merge-detection regex in user hooks. Ignore the `Merge-detection trigger` notifications when no actual merge happened. -- The default config from a stub file does NOT trigger `applyFirstRunDockerIsolation` — that only runs when the config file is absent at boot. To test the "Docker auto-enabled" path, either let mcpproxy create the config or pre-set `docker_isolation.enabled: true` in your stub. -- For browser-driven verification of subtle states (badge counts, empty/loaded transitions), prefer the Playwright spec over ad-hoc screenshots from the chrome-in-chrome MCP — the spec is reproducible and a CI agent can re-run it. - -### Linting -```bash -./scripts/run-linter.sh # Requires golangci-lint v1.59.1+ -``` - -### Running -```bash -./mcpproxy serve # Start core (localhost:8080) -./mcpproxy serve --listen :8080 # All interfaces (CAUTION) -./mcpproxy serve --log-level=debug # Debug mode -./mcpproxy-tray # Start tray (auto-starts core) -``` - -### CLI Management -```bash -mcpproxy upstream list # List all servers -mcpproxy upstream logs # View logs (--tail, --follow) -mcpproxy upstream restart # Restart server (supports --all) -mcpproxy upstream inspect # Inspect tool approval status (Spec 032) -mcpproxy upstream approve # Approve pending/changed tools (Spec 032) -mcpproxy doctor # Run health checks -``` - -See [docs/cli-management-commands.md](docs/cli-management-commands.md) for complete reference. - -Global tools (Spec 050): `mcpproxy tools list` is global when `--server` is omitted; `tools enable|disable ` for batch curation. - -### Activity Log CLI -```bash -mcpproxy activity list # List recent activity -mcpproxy activity list --type tool_call --status error # Filter by type/status -mcpproxy activity list --request-id # Filter by HTTP request ID (for error correlation) -mcpproxy activity watch # Real-time activity stream -mcpproxy activity show # View activity details -mcpproxy activity summary # Show 24h statistics -mcpproxy activity export --output audit.jsonl # Export for compliance -``` - -See [docs/cli/activity-commands.md](docs/cli/activity-commands.md) for complete reference. - -### Agent Token CLI -```bash -mcpproxy token create --name deploy-bot --servers github,gitlab --permissions read,write -mcpproxy token list # List all agent tokens -mcpproxy token show deploy-bot # Show token details -mcpproxy token revoke deploy-bot # Revoke a token -mcpproxy token regenerate deploy-bot # Regenerate token secret -``` - -See [docs/features/agent-tokens.md](docs/features/agent-tokens.md) for complete reference. - -### Telemetry CLI -```bash -mcpproxy telemetry status # Show telemetry status and anonymous ID -mcpproxy telemetry enable # Enable anonymous usage telemetry -mcpproxy telemetry disable # Disable telemetry (no data sent) -``` - -### Feedback CLI -```bash -mcpproxy feedback "message" # Submit bug report -mcpproxy feedback --category feature "Add SAML" # Feature request -mcpproxy feedback --category bug --email me@x.com "Crash" # With contact email -``` - -See [docs/features/telemetry.md](docs/features/telemetry.md) for telemetry details and privacy policy. - -### CLI Output Formatting -```bash -mcpproxy upstream list -o json # JSON output for scripting -mcpproxy upstream list -o yaml # YAML output -mcpproxy upstream list --json # Shorthand for -o json -mcpproxy --help-json # Machine-readable help for AI agents -``` - -**Formats**: `table` (default), `json`, `yaml` -**Environment**: `MCPPROXY_OUTPUT=json` sets default format - -See [docs/cli-output-formatting.md](docs/cli-output-formatting.md) for complete reference. +> Every feature decision should ask: "Does this make the personal edition so good that developers tell their teammates about it?" -## Architecture Overview +## Architecture -### Core Components +**Core + Tray split**: `mcpproxy` (headless HTTP API + MCP proxy) and `mcpproxy-tray` (GUI that manages the core). The tray is a UI controller — it holds no state; it reads/writes core config via REST + SSE. Tray↔core over a Unix socket (`~/.mcpproxy/mcpproxy.sock`) / named pipe on Windows; socket connections bypass the API key (OS-level auth), TCP requires it. | Directory | Purpose | |-----------|---------| -| `cmd/mcpproxy/` | CLI entry point, Cobra commands | -| `cmd/mcpproxy-tray/` | System tray application with state machine | -| `internal/cli/output/` | CLI output formatters (table, JSON, YAML) | +| `cmd/mcpproxy/` | CLI entry point (Cobra) | +| `cmd/mcpproxy-tray/` | System tray app (state machine) | | `internal/runtime/` | Lifecycle, event bus, background services | | `internal/server/` | HTTP server, MCP proxy | -| `internal/httpapi/` | REST API endpoints (`/api/v1`) | +| `internal/httpapi/` | REST API (`/api/v1`) | | `internal/upstream/` | 3-layer client: core/managed/cli | | `internal/config/` | Configuration management | | `internal/index/` | Bleve BM25 search index | | `internal/storage/` | BBolt database | -| `internal/management/` | Centralized server management | -| `internal/oauth/` | OAuth 2.1 with PKCE | -| `internal/logs/` | Structured logging with per-server files | +| `internal/oauth/` | OAuth 2.1 + PKCE | +| `internal/security/` | Sensitive-data detection + quarantine | +| `internal/serveredition/` | Server-only code (`//go:build server`) | +| `native/macos/MCPProxy/` | Swift macOS tray app | + +See [docs/architecture.md](docs/architecture.md) and [docs/socket-communication.md](docs/socket-communication.md). + +## Development Commands + +```bash +# Build +go build -o mcpproxy ./cmd/mcpproxy # core (personal) +go build -tags server -o mcpproxy-server ./cmd/mcpproxy # core (server edition) +make build # frontend + backend +make build-docker # server Docker image + +# Test — ALWAYS run before committing +./scripts/test-api-e2e.sh # quick API E2E (required) +go test -race ./internal/... -v # unit + race +go test -tags server ./internal/serveredition/... -race # server edition +./scripts/run-all-tests.sh # full suite -See [docs/architecture.md](docs/architecture.md) for diagrams and details. +# Lint — CI uses golangci-lint v2 with .github/.golangci.yml, which is STRICTER +# than the local scripts/run-linter.sh (v1.x) and catches things it misses. +# Run the v2 binary before pushing: +/opt/homebrew/bin/golangci-lint run --config .github/.golangci.yml ./... -### Tray-Core Communication +# Run +./mcpproxy serve [--listen :8080] [--log-level=debug] # core (localhost:8080) +./mcpproxy-tray # tray (auto-starts core) +``` -- **Unix sockets** (macOS/Linux): `~/.mcpproxy/mcpproxy.sock` -- **Named pipes** (Windows): `\\.\pipe\mcpproxy-` -- Socket connections bypass API key (OS-level auth) -- TCP connections require API key authentication +**CLI management** — `mcpproxy upstream|tools|activity|token|telemetry|feedback|doctor …`. Output: `-o json|yaml`, `MCPPROXY_OUTPUT=json`, `--help-json` (machine-readable for agents). References: [docs/cli-management-commands.md](docs/cli-management-commands.md) · [docs/cli/activity-commands.md](docs/cli/activity-commands.md) · [docs/features/agent-tokens.md](docs/features/agent-tokens.md) · [docs/cli-output-formatting.md](docs/cli-output-formatting.md). -See [docs/socket-communication.md](docs/socket-communication.md) for details. +**Verifying Web-UI changes** (Playwright sweep + HTML report) — required when touching `frontend/src/`: [docs/development/web-ui-verification.md](docs/development/web-ui-verification.md). ## Configuration -**Default Locations**: -- **Config**: `~/.mcpproxy/mcp_config.json` -- **Data**: `~/.mcpproxy/config.db` (BBolt database) -- **Index**: `~/.mcpproxy/index.bleve/` (search index) -- **Logs**: `~/.mcpproxy/logs/` (main.log + per-server logs) +Default locations: Config `~/.mcpproxy/mcp_config.json` · Data `~/.mcpproxy/config.db` (BBolt) · Index `~/.mcpproxy/index.bleve/` · Logs `~/.mcpproxy/logs/`. -### Example Configuration ```json { "listen": "127.0.0.1:8080", - "api_key": "your-secret-api-key-here", + "api_key": "auto-generated-if-empty", "require_mcp_auth": false, "enable_socket": true, "enable_web_ui": true, "mcpServers": [ - { - "name": "github-server", - "url": "https://api.github.com/mcp", - "protocol": "http", - "enabled": true - }, - { - "name": "ast-grep-project", - "command": "npx", - "args": ["ast-grep-mcp"], - "working_dir": "/home/user/projects/myproject", - "protocol": "stdio", - "enabled": true - } + { "name": "github", "url": "https://api.github.com/mcp", "protocol": "http", "enabled": true }, + { "name": "ast-grep", "command": "npx", "args": ["ast-grep-mcp"], "working_dir": "/path", "protocol": "stdio", "enabled": true } ] } ``` -### Environment Variables - -- `MCPPROXY_LISTEN` - Override network binding (e.g., `127.0.0.1:8080`) -- `MCPPROXY_API_KEY` - Set API key for REST API authentication -- `MCPPROXY_DEBUG` - Enable debug mode -- `MCPPROXY_TELEMETRY` - Set to `false` to disable anonymous telemetry (overrides config) -- `HEADLESS` - Run in headless mode (no browser launching) - -See [docs/configuration.md](docs/configuration.md) for complete reference. +Env vars: `MCPPROXY_LISTEN`, `MCPPROXY_API_KEY`, `MCPPROXY_DEBUG`, `MCPPROXY_TELEMETRY=false`, `HEADLESS`. Full reference: [docs/configuration.md](docs/configuration.md). ## MCP Protocol -### Built-in Tools -- **`retrieve_tools`** - BM25 keyword search across all upstream tools, returns annotations and recommended tool variant. Spec 049: opt-in `include_disabled:true` adds a `disabled[]`+`remediation` view of locked tools (5-state `status`); default output unchanged. -- **`call_tool_read`** - Proxy read-only tool calls to upstream servers (Spec 018) -- **`call_tool_write`** - Proxy write tool calls to upstream servers (Spec 018) -- **`call_tool_destructive`** - Proxy destructive tool calls to upstream servers (Spec 018) -- **`code_execution`** - Execute JavaScript to orchestrate multiple tools (disabled by default) -- **`upstream_servers`** - CRUD for server management (Spec 049). -- **`quarantine_security`** - Security quarantine: list/inspect quarantined servers, inspect/approve/approve-all tools, and block/block-all (atomic approve+disable) (Spec 032). +**Built-in tools**: `retrieve_tools` (BM25 search across upstream tools; Spec 049 opt-in `include_disabled`) · `call_tool_read|write|destructive` (Spec 018 intent variants; operation type inferred from the variant) · `code_execution` (sandboxed JS, off by default) · `upstream_servers` (CRUD, Spec 049) · `quarantine_security` (Spec 032). **Tool format**: `:` (e.g. `github:create_issue`). -**Tool Format**: `:` (e.g., `github:create_issue`) +**REST API** base `/api/v1`, auth via `X-API-Key` header or `?apikey=`. MCP endpoints (`/mcp`) stay unprotected for client compatibility; the REST API always requires a key (auto-generated if absent). All responses carry `X-Request-Id` (correlate with `mcpproxy activity list --request-id `). Live updates via SSE at `/events`. Full endpoint list: `oas/swagger.yaml` + [docs/api/rest-api.md](docs/api/rest-api.md). -**Intent Declaration (Spec 018)**: Tool variants enable granular IDE permission control; `operation_type` is inferred from the variant (`call_tool_read` → "read"). Optional `intent` fields for audit: -```json -{ - "intent": { - "data_sensitivity": "public", - "reason": "User requested list of repositories" - } -} -``` - -### HTTP API Endpoints - -**Base Path**: `/api/v1` - -| Endpoint | Description | -|----------|-------------| -| `GET /api/v1/status` | Server status and statistics | -| `GET /api/v1/servers` | List all upstream servers | -| `POST /api/v1/servers/{name}/enable` | Enable server | -| `POST /api/v1/servers/{name}/disable` | Disable server | -| `POST /api/v1/servers/{name}/quarantine` | Quarantine a server | -| `POST /api/v1/servers/{name}/unquarantine` | Unquarantine a server | -| `GET /api/v1/tools` | Global tools overview: all tools + stats (spec 050) | -| `GET /api/v1/index/search` | Search tools across servers (`?q=query&limit=N`) | -| `GET /api/v1/activity` | List activity records with filtering | -| `GET /api/v1/activity/{id}` | Get activity record details | -| `GET /api/v1/activity/export` | Export activity records (JSON/CSV) | -| `POST /api/v1/tokens` | Create agent token | -| `GET /api/v1/tokens` | List agent tokens | -| `GET /api/v1/tokens/{name}` | Get agent token details | -| `DELETE /api/v1/tokens/{name}` | Revoke agent token | -| `POST /api/v1/tokens/{name}/regenerate` | Regenerate agent token secret | -| `POST /api/v1/servers/{id}/tools/approve` | Approve pending/changed tools (Spec 032) | -| `POST /api/v1/servers/{id}/tools/block` | Block (approve+disable) tools (Spec 032) | -| `GET /api/v1/servers/{id}/tools/{tool}/diff` | View tool description/schema changes (Spec 032) | -| `GET /api/v1/servers/{id}/tools/export` | Export tool approval records (Spec 032) | -| `POST /api/v1/feedback` | Submit feedback/bug report (proxied to GitHub Issues) | -| `GET /events` | SSE stream for live updates | - -**Authentication**: Use `X-API-Key` header or `?apikey=` query parameter. - -**Request ID Tracking**: All responses include `X-Request-Id` header. Error responses include `request_id` in JSON body. Use for log correlation: `mcpproxy activity list --request-id `. - -**Real-time Updates**: -- `GET /events` - Server-Sent Events (SSE) stream for live updates -- Streams both status changes and runtime events (`servers.changed`, `config.reloaded`) -- Used by web UI and tray for real-time synchronization - -**API Authentication Examples**: -```bash -# Using X-API-Key header (recommended for curl) -curl -H "X-API-Key: your-api-key" http://127.0.0.1:8080/api/v1/servers - -# Using query parameter (for browser/SSE) -curl "http://127.0.0.1:8080/api/v1/servers?apikey=your-api-key" - -# SSE with API key -curl "http://127.0.0.1:8080/events?apikey=your-api-key" - -# Open Web UI with API key (tray app does this automatically) -open "http://127.0.0.1:8080/ui/?apikey=your-api-key" -``` - -**Security Notes**: -- **MCP endpoints (`/mcp`, `/mcp/`)** remain **unprotected** for client compatibility -- **REST API** requires authentication - API key is always enforced (auto-generated if not provided) -- **Secure by default**: Empty or missing API keys trigger automatic generation and persistence to config - -See [docs/api/rest-api.md](docs/api/rest-api.md) and `oas/swagger.yaml` for API reference. - -### Unified Health Status - -All server responses include a `health` field that provides consistent status information across all interfaces (CLI, web UI, tray, MCP tools): - -```json -{ - "health": { - "level": "healthy|degraded|unhealthy", - "admin_state": "enabled|disabled|quarantined", - "summary": "Human-readable status summary", - "detail": "Additional context about the status", - "action": "login|restart|enable|approve|view_logs|" - } -} -``` - -**Health Levels**: -- `healthy`: Server is connected and functioning normally -- `degraded`: Server has warnings (e.g., OAuth token expiring soon) -- `unhealthy`: Server has errors or is not functioning - -**Admin States**: -- `enabled`: Normal operation -- `disabled`: User disabled the server -- `quarantined`: Server pending security approval - -**Actions**: Suggested remediation action for the current state. Empty when no action is needed. - -**Configuration**: Token expiry warning threshold can be configured: -```json -{ - "oauth_expiry_warning_hours": 24 -} -``` - -## JavaScript Code Execution - -The `code_execution` tool enables orchestrating multiple upstream MCP tools in a single request using sandboxed JavaScript (ES2020+). Modern syntax is fully supported: arrow functions, const/let, template literals, destructuring, classes, for-of, optional chaining (?.), nullish coalescing (??), spread/rest, Promises, Symbols, Map/Set, Proxy/Reflect, and generators. - -### Configuration - -```json -{ - "enable_code_execution": true, - "code_execution_timeout_ms": 120000, - "code_execution_max_tool_calls": 0, - "code_execution_pool_size": 10 -} -``` - -### CLI Usage - -```bash -mcpproxy code exec --code="({ result: input.value * 2 })" --input='{"value": 21}' -mcpproxy code exec --code="call_tool('github', 'get_user', {username: input.user})" --input='{"user":"octocat"}' -``` - -### Documentation - -See `docs/code_execution/` for complete guides: -- `overview.md` - Architecture and best practices -- `examples.md` - 13 working code samples -- `api-reference.md` - Complete schema documentation -- `troubleshooting.md` - Common issues and solutions +All server responses include a unified `health` field: `level` (healthy|degraded|unhealthy), `admin_state` (enabled|disabled|quarantined), plus `summary`/`detail`/`action`. ## Security Model -- **Localhost-only by default**: Core server binds to `127.0.0.1:8080` -- **API key always required**: Auto-generated if not provided -- **Agent tokens**: Scoped credentials for AI agents with server and permission restrictions (`mcp_agt_` prefix, HMAC-SHA256 hashed) -- **`require_mcp_auth`**: When enabled, `/mcp` endpoint rejects unauthenticated requests (default: false for backward compatibility) -- **Quarantine system**: New servers quarantined until manually approved -- **Tool Poisoning Attack (TPA) protection**: Automatic detection of malicious descriptions -- **Tool-level quarantine (Spec 032)**: SHA-256 hash-based change detection for individual tool descriptions/schemas. New tools start as "pending", changed tools marked as "changed" (rug pull detection). Configurable via `quarantine_enabled` (global) and `skip_quarantine` (per-server). - -See [docs/features/agent-tokens.md](docs/features/agent-tokens.md) and [docs/features/security-quarantine.md](docs/features/security-quarantine.md) for details. - -## Sensitive Data Detection - -Automatic scanning of tool call arguments and responses for secrets, credentials, and sensitive data. Enabled by default and integrates with the activity log for security auditing. - -### Detection Categories - -| Category | Examples | Severity | -|----------|----------|----------| -| `cloud_credentials` | AWS keys, GCP API keys, Azure storage keys | critical | -| `private_key` | RSA, EC, DSA, OpenSSH, PGP private keys | critical | -| `api_token` | GitHub, GitLab, Stripe, Slack, OpenAI, Anthropic, Google AI, xAI, Groq, HuggingFace, Replicate, Perplexity, Fireworks, Anyscale, Mistral, Cohere, DeepSeek, Together AI tokens | critical | -| `database_credential` | MySQL, PostgreSQL, MongoDB connection strings | critical/high | -| `credit_card` | Visa, Mastercard, Amex (Luhn validated) | high | -| `sensitive_file` | Paths to `.ssh/`, `.aws/`, `.env` files | high/medium | -| `high_entropy` | Base64/hex strings with high Shannon entropy | medium | - -### Key Files - -| File | Purpose | -|------|---------| -| `internal/security/detector.go` | Main detector with `Scan()` method | -| `internal/security/types.go` | Detection, Result, Severity, Category types | -| `internal/security/patterns/` | Pattern definitions by category | -| `internal/security/patterns/cloud.go` | AWS, GCP, Azure credential patterns | -| `internal/security/patterns/keys.go` | Private key detection patterns | -| `internal/security/patterns/tokens.go` | API token patterns | -| `internal/security/patterns/database.go` | Database connection string patterns | -| `internal/security/patterns/creditcard.go` | Credit card patterns with Luhn validation | -| `internal/security/entropy.go` | High-entropy string detection | -| `internal/security/paths.go` | Sensitive file path patterns | -| `internal/runtime/activity_service.go` | Integration point via `SetDetector()` | - -### CLI Commands - -```bash -mcpproxy activity list --sensitive-data # Show only activities with detections -mcpproxy activity list --severity critical # Filter by severity level -mcpproxy activity list --detection-type aws_access_key # Filter by detection type -mcpproxy activity show # View detection details -mcpproxy activity export --sensitive-data --output audit.jsonl # Export for compliance -``` - -### Configuration +- **Localhost-only by default** (`127.0.0.1:8080`); **API key always required** (auto-generated and persisted if not provided). +- **Agent tokens**: scoped credentials for AI agents (`mcp_agt_` prefix, HMAC-SHA256 hashed). See [docs/features/agent-tokens.md](docs/features/agent-tokens.md). +- **Quarantine**: new servers quarantined until approved; Tool Poisoning Attack (TPA) detection on descriptions. **Tool-level quarantine (Spec 032)**: SHA-256 hashes detect new ("pending") and changed ("changed", rug-pull) tools. Config: `quarantine_enabled` (global), `skip_quarantine` (per-server). See [docs/features/security-quarantine.md](docs/features/security-quarantine.md). +- **`require_mcp_auth`**: when enabled, `/mcp` rejects unauthenticated requests (default off, for back-compat). +- **Sensitive-data detection** (`internal/security/`): scans tool args/responses for secrets (cloud creds, private keys, API tokens, DB strings, Luhn-validated cards, sensitive file paths, high-entropy strings). On by default; integrates with the activity log. Config under `sensitive_data_detection`. See [docs/features/sensitive-data-detection.md](docs/features/sensitive-data-detection.md). -```json -{ - "sensitive_data_detection": { - "enabled": true, - "scan_requests": true, - "scan_responses": true, - "max_payload_size_kb": 1024, - "entropy_threshold": 4.5, - "categories": { - "cloud_credentials": true, - "private_key": true, - "api_token": true, - "database_credential": true, - "credit_card": true, - "high_entropy": true - }, - "custom_patterns": [ - { - "name": "internal_api_key", - "regex": "INTERNAL-[A-Z0-9]{32}", - "severity": "high", - "category": "custom" - } - ], - "sensitive_keywords": ["password", "secret"] - } -} -``` - -See [docs/features/sensitive-data-detection.md](docs/features/sensitive-data-detection.md) for complete reference. - -### Exit Codes - -| Code | Meaning | -|------|---------| -| `0` | Success | -| `1` | General error | -| `2` | Port conflict | -| `3` | Database locked | -| `4` | Config error | -| `5` | Permission error | - -## macOS Tray App (native/macos/) - -### Building the Tray App -```bash -cd native/macos/MCPProxy -SDK=$(xcrun --sdk macosx --show-sdk-path) -swiftc -target arm64-apple-macosx13.0 -sdk "$SDK" -module-name MCPProxy -emit-executable -O \ - -o /tmp/MCPProxy-new \ - $(find MCPProxy -name "*.swift" -not -path "*/Tests/*" | sort | tr '\n' ' ') -# Replace in .app bundle: -cp /tmp/MCPProxy-new /tmp/MCPProxy.app/Contents/MacOS/MCPProxy -``` - -### Building the UI Test Tool -```bash -cd native/macos/MCPProxyUITest -SDK=$(xcrun --sdk macosx --show-sdk-path) -swiftc -target arm64-apple-macosx13.0 -sdk "$SDK" -O -o /tmp/mcpproxy-ui-test Sources/main.swift -``` - -### Testing with mcpproxy-ui-test (MCP Server) - -The `mcpproxy-ui-test` MCP server provides 7 tools for automated UI verification: - -| Tool | Description | -|------|-------------| -| `check_accessibility` | Verify Accessibility API permissions | -| `list_running_apps` | List running macOS apps with bundle IDs | -| `list_menu_items` | Read status bar menu tree | -| `click_menu_item` | Click menu items by path | -| `read_status_bar` | Read status bar item info | -| `screenshot_window` | Capture app window or full screen (CGWindowListCreateImage) | -| `screenshot_status_bar_menu` | Open tray menu, capture screenshot, close menu | - -**After every macOS tray code change, verify by:** -1. Build the tray binary (see above) -2. Replace in `/tmp/MCPProxy.app/Contents/MacOS/MCPProxy` and restart -3. Use `screenshot_window` to capture the window and visually verify -4. Use `click_menu_item` + `list_menu_items` to verify tray menu behavior -5. Use `screenshot_status_bar_menu` for tray menu visual verification +## Key Implementation Details -**MCP config** (in Claude Code settings or `~/.claude/settings.json`): -```json -{ - "mcpServers": { - "mcpproxy-ui-test": { - "command": "/tmp/mcpproxy-ui-test", - "args": ["--bundle-id", "com.smartmcpproxy.mcpproxy.dev"] - } - } -} -``` +- **Docker isolation**: runtime detection (uvx→Python, npx→Node), image selection, container lifecycle. [docs/docker-isolation.md](docs/docker-isolation.md) +- **OAuth**: dynamic port allocation, RFC 8252 + PKCE, `internal/oauth/coordinator.go`, automatic token refresh. [docs/oauth-resource-autodetect.md](docs/oauth-resource-autodetect.md) +- **Code execution**: sandboxed JavaScript (ES2020+) orchestrating multiple upstream tools in one request. [docs/code_execution/overview.md](docs/code_execution/overview.md) +- **Connection management**: exponential backoff; state machine Disconnected → Connecting → Authenticating → Ready. +- **Tool indexing**: full rebuild on server changes, hash-based change detection, background indexing. +- **Tool-level quarantine (Spec 032)** key files: `internal/storage/models.go` & `bbolt.go`, `internal/runtime/tool_quarantine.go`, `internal/runtime/lifecycle.go` (`applyDifferentialToolUpdate`), `internal/server/mcp.go`, `internal/config/config.go`, `frontend/src/views/ServerDetail.vue`. +- **Signal handling**: graceful shutdown, context cancellation, Docker cleanup, double-shutdown protection. **Before running the core, kill existing instances — it locks the DB.** ## Debugging ```bash -mcpproxy doctor # Quick diagnostics -mcpproxy upstream list # Server status -mcpproxy upstream logs # Server logs (--tail, --follow) -tail -f ~/Library/Logs/mcpproxy/main.log # Main log (macOS) -tail -f ~/.mcpproxy/logs/main.log # Main log (Linux) +mcpproxy doctor # quick diagnostics +mcpproxy upstream list # server status +mcpproxy upstream logs --follow # per-server logs +tail -f ~/Library/Logs/mcpproxy/main.log # main log (macOS; Linux: ~/.mcpproxy/logs/main.log) ``` -## Development Guidelines - -- **File Organization**: Use `internal/` subdirectories, follow Go conventions -- **Testing**: Unit tests in `*_test.go`, E2E in `internal/server/e2e_test.go` -- **Error Handling**: Structured logging (zap), context wrapping, graceful degradation -- **Config**: Update both storage and file system, use file watcher for hot reload - -## Key Implementation Details - -### Docker Security Isolation -Runtime detection (uvx→Python, npx→Node.js), image selection, environment passing, container lifecycle management. See [docs/docker-isolation.md](docs/docker-isolation.md). - -### OAuth Implementation -Dynamic port allocation, RFC 8252 + PKCE, flow coordinator (`internal/oauth/coordinator.go`), automatic token refresh. See [docs/oauth-resource-autodetect.md](docs/oauth-resource-autodetect.md). - -### Code Execution -Sandboxed JavaScript (ES2020+), orchestrates multiple upstream tools in single request. See [docs/code_execution/overview.md](docs/code_execution/overview.md). +**Exit codes**: 0 success · 1 general · 2 port conflict · 3 DB locked · 4 config · 5 permission. -### Connection Management -Exponential backoff, separate contexts for app vs server lifecycle, state machine: Disconnected → Connecting → Authenticating → Ready. - -### Tool Indexing -Full rebuild on server changes, hash-based change detection, background indexing. - -### Tool-Level Quarantine (Spec 032) -SHA-256 hash-based approval system for individual tools. Key files: -- `internal/storage/models.go` - `ToolApprovalRecord` model and `ToolApprovalBucket` -- `internal/storage/bbolt.go` - CRUD operations for tool approvals -- `internal/runtime/tool_quarantine.go` - Hash calculation, approval checking, blocking logic -- `internal/runtime/lifecycle.go` - Integration in `applyDifferentialToolUpdate()` -- `internal/server/mcp.go` - Tool-level blocking in `handleCallToolVariant()` and MCP tool operations -- `internal/httpapi/server.go` - REST API endpoints for inspection/approval -- `internal/config/config.go` - `QuarantineEnabled` (global) and `SkipQuarantine` (per-server) -- `frontend/src/views/ServerDetail.vue` - Web UI quarantine panel - -### Signal Handling -Graceful shutdown, context cancellation, Docker cleanup, double shutdown protection. - -**Important**: Before running mcpproxy core, kill all existing instances as it locks the database. - -## Windows Installer - -```bash -# Using Inno Setup (recommended) -.\scripts\build-windows-installer.ps1 -Version "v1.0.0" -Arch "amd64" - -# Using WiX Toolset -wix build -arch x64 -d Version=1.0.0.0 -d BinPath=dist\windows-amd64 wix\Package.wxs -``` +## Development Guidelines -See `docs/github-actions-windows-wix-research.md` for CI setup. - -## Prerelease Builds - -- **`main` branch**: Stable releases -- **`next` branch** + `v*-rc.*` tags: GitHub pre-releases, opt-in, NOT on stable channels; RC tags skip `release.yml` (brew/linux/registry) - -See `docs/prerelease-builds.md` for download instructions. - -## Active Technologies -- Go 1.24 (toolchain go1.24.10) (001-update-version-display) -- In-memory only for version cache (no persistence per clarification) (001-update-version-display) -- Go 1.24 (toolchain go1.24.10) + Cobra CLI framework, encoding/json, gopkg.in/yaml.v3 (014-cli-output-formatting) -- N/A (CLI output only) (014-cli-output-formatting) -- Go 1.24 (toolchain go1.24.10) + BBolt (storage), Chi router (HTTP), Zap (logging), existing event bus (016-activity-log-backend) -- BBolt database (existing `~/.mcpproxy/config.db`) (016-activity-log-backend) -- Go 1.24 (toolchain go1.24.10) + Cobra CLI framework, encoding/json, internal/cli/output (spec 014), internal/cliclien (017-activity-cli-commands) -- N/A (CLI layer only - uses REST API from spec 016) (017-activity-cli-commands) -- Go 1.24 (toolchain go1.24.10) + Cobra CLI, Chi router, BBolt (storage), Zap (logging), mark3labs/mcp-go (MCP protocol) (018-intent-declaration) -- BBolt database (`~/.mcpproxy/config.db`) - ActivityRecord extended with intent metadata (018-intent-declaration) -- TypeScript 5.9, Vue 3.5, Go 1.24 (backend already exists) + Vue 3, Vue Router 4, Pinia 2, Tailwind CSS 3, DaisyUI 4, Vite 5 (019-activity-webui) -- N/A (frontend consumes REST API from backend) (019-activity-webui) -- Go 1.24 (toolchain go1.24.10) + Cobra (CLI), Chi router (HTTP), Zap (logging), mark3labs/mcp-go (MCP protocol) (020-oauth-login-feedback) -- Go 1.24 (toolchain go1.24.10) + Cobra (CLI), Chi router (HTTP), Zap (logging), google/uuid (ID generation) (021-request-id-logging) -- BBolt database (`~/.mcpproxy/config.db`) - activity log extended with request_id field (021-request-id-logging) -- Go 1.24 (toolchain go1.24.10) + mcp-go v0.43.1 (OAuth client), BBolt (storage), Prometheus (metrics), Zap (logging) (023-oauth-state-persistence) -- BBolt database (`~/.mcpproxy/config.db`) - `oauth_tokens` bucket with `OAuthTokenRecord` model (023-oauth-state-persistence) -- Go 1.24 (toolchain go1.24.10) + TypeScript 5.x / Vue 3.5 + Cobra CLI, Chi router, BBolt storage, Zap logging, mark3labs/mcp-go, Vue 3, Tailwind CSS, DaisyUI (024-expand-activity-log) -- BBolt database (`~/.mcpproxy/config.db`) - ActivityRecord model (024-expand-activity-log) -- Go 1.24 (toolchain go1.24.10) + BBolt (storage), Chi router (HTTP), Zap (logging), regexp (stdlib), existing ActivityService (026-pii-detection) -- BBolt database (`~/.mcpproxy/config.db`) - ActivityRecord.Metadata extension (026-pii-detection) -- Go 1.24 (toolchain go1.24.10) + Cobra (CLI), Chi router (HTTP), Zap (logging), existing cliclient, socket detection, config loader (027-status-command) -- `~/.mcpproxy/mcp_config.json` (config file), `~/.mcpproxy/config.db` (BBolt - not directly used) (027-status-command) -- Go 1.24 (toolchain go1.24.10) + Cobra (CLI), Chi router (HTTP), BBolt (storage), Zap (logging), mcp-go (MCP protocol), crypto/hmac + crypto/sha256 (token hashing) (028-agent-tokens) -- BBolt database (`~/.mcpproxy/config.db`) — new `agent_tokens` bucket (028-agent-tokens) -- Go 1.24 (toolchain go1.24.10) + TypeScript 5.9 / Vue 3.5 + Chi router, BBolt, Zap logging, mcp-go, golang-jwt/jwt/v5, Vue 3, Pinia, DaisyUI (024-teams-multiuser-oauth) -- BBolt database (`~/.mcpproxy/config.db`) - new buckets for users, sessions, user servers (024-teams-multiuser-oauth) -- Go 1.24 (toolchain go1.24.10) + `github.com/dop251/goja` (existing JS sandbox), `github.com/evanw/esbuild` (new - TypeScript transpilation), `github.com/mark3labs/mcp-go` (MCP protocol), `github.com/spf13/cobra` (CLI) (033-typescript-code-execution) -- N/A (no new storage requirements) (033-typescript-code-execution) -- Swift 5.9+ / Xcode 15+ + SwiftUI, AppKit (escape hatches), Sparkle 2.x (SPM), Foundation (URLSession, Process, UNUserNotificationCenter) (037-macos-swift-tray) -- N/A (tray reads all state from core via REST API — no local persistence per Constitution III) (037-macos-swift-tray) -- Go 1.24 (toolchain go1.24.10) — primary; Swift 5.9 — macOS tray header change only + `github.com/google/uuid` (existing), `github.com/go-chi/chi/v5` (existing, for `RoutePattern()`), `github.com/spf13/cobra` (existing, new subcommand), `go.uber.org/zap` (existing), stdlib `sync/atomic`, `sync`, `os` (042-telemetry-tier2) -- Config file `~/.mcpproxy/mcp_config.json` only — counters live in memory and are never persisted between restarts (privacy constraint). No BBolt buckets, no new files. (042-telemetry-tier2) -- Bash / GitHub Actions YAML for the CI job; Astro 4.x for the website; Markdown for docs. No Go code changes required. (043-linux-package-repos) -- Go 1.24 (toolchain go1.24.10), Swift 5.9+ (macOS tray only), Bash (DMG post-install script) + `go.etcd.io/bbolt` (existing), `go.uber.org/zap` (existing), `github.com/mark3labs/mcp-go` (existing MCP protocol lib), `github.com/google/uuid` (existing). macOS: `ServiceManagement.framework` (SMAppService, macOS 13+), existing `native/macos/MCPProxy` module. No new external dependencies. (044-retention-telemetry-v3) -- BBolt (`~/.mcpproxy/config.db`) — new `activation` bucket alongside existing buckets; no migration required because absence of bucket means "fresh install, all flags false". (044-retention-telemetry-v3) -- Go 1.24 (toolchain go1.24.10), TypeScript 5.9 / Vue 3.5, Swift 5.9 (macOS 13+) (044-diagnostics-taxonomy) -- No new persistent storage. Diagnostic state lives on in-memory stateview snapshot. Fix-attempt audit rows reuse existing activity log (`ActivityBucket` in BBolt). Telemetry counters are in-memory only (consistent with spec 042). (044-diagnostics-taxonomy) -- Markdown (agent instruction files, wiki articles); optionally shell or AppleScript helpers for bootstrap idempotency + Paperclip AI (paperclipai/paperclip, MIT) running locally on loopback :3100; Synapbus on kubic; Anthropic API via Paperclip's Claude Code subprocess adapter (045-paperclip-cockpit) -- Paperclip's embedded Postgres (existing, port 54329); Synapbus DB (existing); no new storage in mcpproxy-go (045-paperclip-cockpit) -- Go 1.24 (toolchain go1.24.10); Swift 5.9 (macOS 13+); TypeScript 5.9 / Vue 3.5 (frontend) + `go.etcd.io/bbolt` (existing), `go.uber.org/zap` (existing), `github.com/mark3labs/mcp-go` (existing). No new deps. (047-cpu-hotpath-fix) -- BBolt (`~/.mcpproxy/config.db`) — read-only on the hot path; no schema change. (047-cpu-hotpath-fix) -- Swift 5.9 (macOS 13+); Go 1.24 only for the verification harness, no Go changes in scope. + SwiftUI/AppKit (existing), Combine (existing for the periodic timer pattern). No new deps. (048-tray-refetch-elimination) -- None. Pure in-memory state. (048-tray-refetch-elimination) -- Go 1.24 / Vue 3.5; no new deps; read-only BBolt reuse, no schema change (050-global-tools-page) - -## Recent Changes -- 001-update-version-display: Added Go 1.24 (toolchain go1.24.10) +- File organization: `internal/` subdirectories, Go conventions. Tests: `*_test.go`; E2E in `internal/server/e2e_test.go`. E2E prereqs: Node.js, npm, jq, a built `mcpproxy` binary. +- Error handling: structured logging (zap), context wrapping, graceful degradation. +- Config changes: update both storage and file system; the file watcher hot-reloads. +- **macOS tray dev** (build / replace / verify with `mcpproxy-ui-test`): [docs/development/macos-tray.md](docs/development/macos-tray.md). +- **Windows installer**: [docs/github-actions-windows-wix-research.md](docs/github-actions-windows-wix-research.md). **Prerelease** (`next` branch + `v*-rc.*` tags, opt-in, off stable channels): [docs/prerelease-builds.md](docs/prerelease-builds.md). diff --git a/docs/development/macos-tray.md b/docs/development/macos-tray.md new file mode 100644 index 000000000..8aa5a6f92 --- /dev/null +++ b/docs/development/macos-tray.md @@ -0,0 +1,52 @@ +# macOS Tray App development (`native/macos/`) + +## Building the Tray App +```bash +cd native/macos/MCPProxy +SDK=$(xcrun --sdk macosx --show-sdk-path) +swiftc -target arm64-apple-macosx13.0 -sdk "$SDK" -module-name MCPProxy -emit-executable -O \ + -o /tmp/MCPProxy-new \ + $(find MCPProxy -name "*.swift" -not -path "*/Tests/*" | sort | tr '\n' ' ') +# Replace in .app bundle: +cp /tmp/MCPProxy-new /tmp/MCPProxy.app/Contents/MacOS/MCPProxy +``` + +## Building the UI Test Tool +```bash +cd native/macos/MCPProxyUITest +SDK=$(xcrun --sdk macosx --show-sdk-path) +swiftc -target arm64-apple-macosx13.0 -sdk "$SDK" -O -o /tmp/mcpproxy-ui-test Sources/main.swift +``` + +## Testing with mcpproxy-ui-test (MCP Server) + +The `mcpproxy-ui-test` MCP server provides 7 tools for automated UI verification: + +| Tool | Description | +|------|-------------| +| `check_accessibility` | Verify Accessibility API permissions | +| `list_running_apps` | List running macOS apps with bundle IDs | +| `list_menu_items` | Read status bar menu tree | +| `click_menu_item` | Click menu items by path | +| `read_status_bar` | Read status bar item info | +| `screenshot_window` | Capture app window or full screen (CGWindowListCreateImage) | +| `screenshot_status_bar_menu` | Open tray menu, capture screenshot, close menu | + +**After every macOS tray code change, verify by:** +1. Build the tray binary (see above) +2. Replace in `/tmp/MCPProxy.app/Contents/MacOS/MCPProxy` and restart +3. Use `screenshot_window` to capture the window and visually verify +4. Use `click_menu_item` + `list_menu_items` to verify tray menu behavior +5. Use `screenshot_status_bar_menu` for tray menu visual verification + +**MCP config** (in Claude Code settings or `~/.claude/settings.json`): +```json +{ + "mcpServers": { + "mcpproxy-ui-test": { + "command": "/tmp/mcpproxy-ui-test", + "args": ["--bundle-id", "com.smartmcpproxy.mcpproxy.dev"] + } + } +} +``` diff --git a/docs/development/server-edition-multiuser-auth.md b/docs/development/server-edition-multiuser-auth.md new file mode 100644 index 000000000..4ae01a2dc --- /dev/null +++ b/docs/development/server-edition-multiuser-auth.md @@ -0,0 +1,74 @@ +# Server Multi-User Authentication (Spec 024) + +Server edition supports OAuth-based multi-user authentication with Google, GitHub, or Microsoft identity providers. All server code is behind `//go:build server`; the personal edition is unaffected. + +## Server Configuration + +```json +{ + "server_edition": { + "enabled": true, + "admin_emails": ["admin@company.com"], + "oauth": { + "provider": "google", + "client_id": "xxx.apps.googleusercontent.com", + "client_secret": "GOCSPX-xxx", + "tenant_id": "", + "allowed_domains": ["company.com"] + }, + "session_ttl": "24h", + "bearer_token_ttl": "24h", + "workspace_idle_timeout": "30m", + "max_user_servers": 20 + } +} +``` + +## Server API Endpoints + +| Endpoint | Auth | Description | +|----------|------|-------------| +| `GET /api/v1/auth/login` | Public | Initiate OAuth login flow | +| `GET /api/v1/auth/callback` | Public | OAuth callback (creates session) | +| `GET /api/v1/auth/me` | Session/JWT | Get current user profile | +| `POST /api/v1/auth/token` | Session | Generate JWT bearer token for MCP | +| `POST /api/v1/auth/logout` | Session | Invalidate session | +| `GET /api/v1/user/servers` | Session/JWT | List user's servers (personal + shared) | +| `POST /api/v1/user/servers` | Session/JWT | Add personal upstream server | +| `GET /api/v1/user/activity` | Session/JWT | User's activity log | +| `GET /api/v1/user/diagnostics` | Session/JWT | Server health for user's servers | +| `GET /api/v1/admin/users` | Admin | List all users | +| `POST /api/v1/admin/users/{id}/disable` | Admin | Disable a user | +| `GET /api/v1/admin/activity` | Admin | All users' activity logs | +| `GET /api/v1/admin/sessions` | Admin | List active sessions | + +## Server Architecture + +- **Auth flow**: OAuth 2.0 + PKCE → Session cookie (Web UI) + JWT bearer (MCP/API) +- **Server types**: Shared (config file, single connection) + Personal (DB, per-user connections) +- **Isolation**: Users see only shared + own personal servers. Activity logs user-scoped. +- **Admin**: Identified by `admin_emails` config. Sees all activity, manages users. +- **Build tag**: All server code behind `//go:build server`. Personal edition unaffected. + +## Key Directories + +| Directory | Purpose | +|-----------|---------| +| `cmd/mcpproxy/edition.go` | Default edition = "personal" | +| `cmd/mcpproxy/edition_teams.go` | Build-tagged override for server edition | +| `cmd/mcpproxy/serveredition_register.go` | Server feature registration entry point | +| `internal/serveredition/auth/` | OAuth, sessions, JWT tokens, middleware | +| `internal/serveredition/users/` | User/session models, BBolt store | +| `internal/serveredition/workspace/` | Per-user workspace for personal upstreams | +| `internal/serveredition/multiuser/` | Multi-user router, tool filtering, activity isolation | +| `internal/serveredition/api/` | Server REST API endpoints (user, admin, auth) | + +## Server Testing + +```bash +go test -tags server ./internal/serveredition/... -v -race # All server unit + integration tests +go build -tags server ./cmd/mcpproxy # Build server edition +go build ./cmd/mcpproxy # Verify personal edition unaffected +``` + +> Note: server-edition `//go:build server` routes are invisible to `swag` / `verify-oas-coverage.sh` / CI lint (which don't pass `--build-tags server`). Lint locally with the tag and document endpoints here. diff --git a/docs/development/web-ui-verification.md b/docs/development/web-ui-verification.md new file mode 100644 index 000000000..4621dc38b --- /dev/null +++ b/docs/development/web-ui-verification.md @@ -0,0 +1,45 @@ +# Verifying Web UI changes (Playwright + rich HTML report) + +When you modify the Web UI (any Vue file under `frontend/src/`), verify it end-to-end with a Playwright sweep that captures screenshots and packages them into a self-contained HTML report. This is the same workflow used to verify Spec 046 v2 — see `specs/046-local-first-onboarding/verification/` for a worked example. + +The pattern, in order: + +1. **Stand up a fresh mcpproxy.** Use a throwaway data-dir so persisted state doesn't bleed between runs: + ```bash + pkill -f 'mcpproxy serve.*' 2>/dev/null; sleep 1 + rm -rf /tmp/mcpproxy-uitest/{config.db,index.bleve,logs} 2>/dev/null + cat > /tmp/mcpproxy-uitest/mcp_config.json <<'EOF' + { "listen": "127.0.0.1:18081", "data_dir": "/tmp/mcpproxy-uitest", "api_key": "uitest", "enable_web_ui": true, "enable_socket": false, "telemetry": {"enabled": false}, "mcpServers": [] } + EOF + ./mcpproxy serve --config=/tmp/mcpproxy-uitest/mcp_config.json --listen=127.0.0.1:18081 --log-level=info > /tmp/mcpproxy-uitest/server.log 2>&1 & + until curl -sf -H "X-API-Key: uitest" http://127.0.0.1:18081/api/v1/status >/dev/null; do sleep 1; done + ``` +2. **Reuse the existing Playwright install.** `e2e/playwright/node_modules` already has Playwright + Chromium 1217. Symlink it into your scratch dir: + ```bash + mkdir -p /tmp/uitest && cd /tmp/uitest + ln -sfn /Users/user/repos/mcpproxy-go/e2e/playwright/node_modules ./node_modules + ``` +3. **Pin the Chromium binary in `playwright.config.ts`** so Playwright doesn't try to download a different version: + ```ts + import { defineConfig } from '@playwright/test'; + export default defineConfig({ + testDir: '.', timeout: 30000, fullyParallel: false, workers: 1, retries: 0, + use: { + headless: true, + viewport: { width: 1440, height: 900 }, + launchOptions: { + executablePath: '/Users/user/Library/Caches/ms-playwright/chromium-1217/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing', + }, + }, + }); + ``` +4. **Write the spec.** Use `data-test` attributes already on the components (the project convention). For new components, add them. Drive scenarios with `page.locator('[data-test="..."]')`. Always use `page.waitForLoadState('domcontentloaded')` — `networkidle` hangs because of the SSE channel. Snapshot each state with `page.screenshot({ path: ... })`. Number screenshots in execution order so the report renders left-to-right. +5. **Run.** `./node_modules/.bin/playwright test --reporter=list`. Iterate until green. +6. **Build the rich HTML report.** A short Python script that base64-embeds each PNG and wraps it in a styled `
` per scenario produces a single self-contained HTML file the user can open offline. Pattern: top summary card with pass/fail counts, then one collapsible per scenario with `Expected` / `Observed` / inline screenshot. The reference implementation is `/tmp/wizard-v2-verify/build-report.py` from the v2 work — clone it and update the `SCENARIOS` list. Output goes to `specs//verification/report.html`. +7. **Drop screenshots + report alongside the spec.** Always commit them with the spec changes — they're part of the trace. +8. **Surface the report.** End your reply with `open ` so the user can review without re-running the suite. + +Key gotchas: +- The wizard's `` element renders as `[open]` only when the Vue store sets it. To assert open/closed state robustly, query the dialog property in `page.evaluate()`, not aria-hidden or styling. +- The default config from a stub file does NOT trigger `applyFirstRunDockerIsolation` — that only runs when the config file is absent at boot. To test the "Docker auto-enabled" path, either let mcpproxy create the config or pre-set `docker_isolation.enabled: true` in your stub. +- For browser-driven verification of subtle states (badge counts, empty/loaded transitions), prefer the Playwright spec over ad-hoc screenshots from the chrome-in-chrome MCP — the spec is reproducible and a CI agent can re-run it.