diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 52ef665d..295d20b8 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -225,21 +225,21 @@ jobs:
cgo: "1"
name: mcpproxy-darwin-arm64
archive_format: tar.gz
- # Teams edition (Linux only) — uncomment when teams MVP is ready
+ # Server edition (Linux only) — uncomment when server MVP is ready
# - os: ubuntu-latest
# goos: linux
# goarch: amd64
# cgo: "0"
- # name: mcpproxy-teams-linux-amd64
+ # name: mcpproxy-server-linux-amd64
# archive_format: tar.gz
- # edition: teams
+ # edition: server
# - os: ubuntu-latest
# goos: linux
# goarch: arm64
# cgo: "0"
- # name: mcpproxy-teams-linux-arm64
+ # name: mcpproxy-server-linux-arm64
# archive_format: tar.gz
- # edition: teams
+ # edition: server
runs-on: ${{ matrix.os }}
@@ -389,9 +389,9 @@ jobs:
# Determine clean binary name and build flags
EDITION="${{ matrix.edition }}"
BUILD_TAGS=""
- if [ "$EDITION" = "teams" ]; then
- CLEAN_BINARY="mcpproxy-teams"
- BUILD_TAGS="-tags teams"
+ if [ "$EDITION" = "server" ]; then
+ CLEAN_BINARY="mcpproxy-server"
+ BUILD_TAGS="-tags server"
elif [ "${{ matrix.goos }}" = "windows" ]; then
CLEAN_BINARY="mcpproxy.exe"
else
@@ -402,7 +402,7 @@ jobs:
go build ${BUILD_TAGS} -ldflags "${LDFLAGS}" -o ${CLEAN_BINARY} ./cmd/mcpproxy
# Build tray binary for platforms with GUI support (macOS and Windows, personal only)
- if [ "$EDITION" != "teams" ] && { [ "${{ matrix.goos }}" = "darwin" ] || [ "${{ matrix.goos }}" = "windows" ]; }; then
+ if [ "$EDITION" != "server" ] && { [ "${{ matrix.goos }}" = "darwin" ] || [ "${{ matrix.goos }}" = "windows" ]; }; then
echo "Building mcpproxy-tray for ${{ matrix.goos }}..."
# Determine tray binary name
@@ -418,14 +418,14 @@ jobs:
# Code sign macOS binaries
if [ "${{ matrix.goos }}" = "darwin" ]; then
echo "Code signing macOS binary..."
-
+
# Debug: List all available certificates
echo "Available certificates:"
security find-identity -v -p codesigning
-
+
# Find the Developer ID certificate identity
CERT_IDENTITY=$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -1 | grep -o '"[^"]*"' | tr -d '"')
-
+
# Verify we found a valid certificate
if [ -n "${CERT_IDENTITY}" ]; then
echo "✅ Found Developer ID certificate: ${CERT_IDENTITY}"
@@ -434,7 +434,7 @@ jobs:
CERT_IDENTITY="${{ secrets.APPLE_TEAM_ID }}"
echo "⚠️ Using fallback identity: ${CERT_IDENTITY}"
fi
-
+
# Validate entitlements file formatting (Apple's recommendation)
echo "=== Validating entitlements file ==="
if [ -f "scripts/entitlements.plist" ]; then
@@ -445,14 +445,14 @@ jobs:
echo "❌ Entitlements file has formatting issues"
exit 1
fi
-
+
# Convert to XML format if needed (Apple's recommendation)
plutil -convert xml1 scripts/entitlements.plist
echo "✅ Entitlements converted to XML format"
else
echo "⚠️ No entitlements file found"
fi
-
+
# Sign both binaries with proper Developer ID certificate, hardened runtime, and timestamp
echo "=== Signing binaries with hardened runtime ==="
@@ -527,7 +527,7 @@ jobs:
echo "❌ All tray binary signing attempts failed"
exit 1
fi
-
+
# Verify signing, hardened runtime, and timestamp using Apple's recommended methods
echo "=== Verifying binary signatures (Apple's recommended verification) ==="
@@ -558,7 +558,7 @@ jobs:
echo "❌ Strict verification FAILED - will not pass notarization"
exit 1
fi
-
+
# Check for secure timestamp (Apple's recommended check)
echo "=== Checking for secure timestamp ==="
TIMESTAMP_CHECK=$(codesign -dvv ${CLEAN_BINARY} 2>&1)
@@ -570,14 +570,14 @@ jobs:
echo "Full output:"
echo "$TIMESTAMP_CHECK"
fi
-
+
# Display detailed signature info
codesign --display --verbose=4 ${CLEAN_BINARY}
-
+
# Check entitlements formatting (Apple's recommendation)
echo "=== Checking entitlements formatting ==="
codesign --display --entitlements - ${CLEAN_BINARY} | head -10
-
+
# Verify with spctl (Gatekeeper assessment) - expected to fail before notarization
echo "=== Gatekeeper assessment (expected to fail before notarization) ==="
if spctl --assess --verbose ${CLEAN_BINARY}; then
@@ -586,7 +586,7 @@ jobs:
echo "⚠️ Gatekeeper assessment: REJECTED (expected - binary needs notarization)"
echo "This is normal - the binary will pass after Apple completes notarization"
fi
-
+
echo "✅ Binary signed successfully with hardened runtime and timestamp"
fi
@@ -859,7 +859,7 @@ jobs:
if [ -f .keychain_name ]; then
TEMP_KEYCHAIN=$(cat .keychain_name)
echo "Cleaning up keychain: ${TEMP_KEYCHAIN}"
-
+
# Remove from search list and delete
security delete-keychain "$TEMP_KEYCHAIN" 2>/dev/null || echo "Keychain already cleaned up"
rm -f .keychain_name
@@ -988,7 +988,7 @@ jobs:
build-docker:
runs-on: ubuntu-latest
needs: [build]
- # Disabled until teams MVP is ready — uncomment to enable
+ # Disabled until server MVP is ready — uncomment to enable
if: false && startsWith(github.ref, 'refs/tags/v')
permissions:
contents: read
@@ -1010,11 +1010,11 @@ jobs:
COMMIT=${{ github.sha }}
BUILD_DATE=${{ github.event.head_commit.timestamp }}
tags: |
- ghcr.io/smart-mcp-proxy/mcpproxy-teams:${{ github.ref_name }}
- ghcr.io/smart-mcp-proxy/mcpproxy-teams:latest
+ ghcr.io/smart-mcp-proxy/mcpproxy-server:${{ github.ref_name }}
+ ghcr.io/smart-mcp-proxy/mcpproxy-server:latest
release:
- needs: [build, sign-windows, generate-notes] # add build-docker when teams MVP is ready
+ needs: [build, sign-windows, generate-notes] # add build-docker when server MVP is ready
runs-on: ubuntu-latest
environment: production
diff --git a/CLAUDE.md b/CLAUDE.md
index 9813f978..10ba8079 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -8,7 +8,7 @@ When operating to complete a task, adhere strictly to the following constraints
### 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.
+- **Test-Driven Progress**: You must 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.
@@ -28,23 +28,30 @@ Only halt execution and ask a human IF:
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.
-## Editions (Personal & Teams)
+## Editions (Personal & Server)
MCPProxy is built in two editions from the same codebase using Go build tags:
| Edition | Build Command | Binary | Distribution |
|---------|--------------|--------|-------------|
| **Personal** (default) | `go build ./cmd/mcpproxy` | `mcpproxy` | macOS DMG, Windows installer, Linux tar.gz |
-| **Teams** | `go build -tags teams ./cmd/mcpproxy` | `mcpproxy-teams` | Docker image, .deb package, 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 teams |
-| `cmd/mcpproxy/teams_register.go` | Teams feature registration entry point |
-| `internal/teams/` | Teams-only code (all files have `//go:build teams`) |
+| `cmd/mcpproxy/edition_teams.go` | Build-tagged override for server edition |
+| `cmd/mcpproxy/teams_register.go` | Server feature registration entry point |
+| `internal/teams/` | Server-only code (all files have `//go:build server`) |
+| `internal/teams/auth/` | OAuth authentication, session management, JWT tokens, middleware |
+| `internal/teams/users/` | User/session models, BBolt store, user server management |
+| `internal/teams/workspace/` | Per-user workspace manager for personal upstream servers |
+| `internal/teams/multiuser/` | Multi-user router, tool filtering, activity isolation |
+| `internal/teams/api/` | Server REST API endpoints (user, admin, auth) |
| `native/macos/` | Future Swift tray app (placeholder) |
| `native/windows/` | Future C# tray app (placeholder) |
@@ -54,6 +61,66 @@ The binary self-identifies its edition:
- `mcpproxy version` → `MCPProxy v0.21.0 (personal) darwin/arm64`
- `/api/v1/status` → `{"edition": "personal", ...}`
+## Server Multi-User Authentication (Spec 024)
+
+Server edition supports OAuth-based multi-user authentication with Google, GitHub, or Microsoft identity providers.
+
+### Server Configuration
+
+```json
+{
+ "teams": {
+ "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/teams/... -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
@@ -66,11 +133,11 @@ The binary self-identifies its edition:
### Build
```bash
go build -o mcpproxy ./cmd/mcpproxy # Core server (personal)
-go build -tags teams -o mcpproxy-teams ./cmd/mcpproxy # Core server (teams)
+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-teams # Frontend and backend (teams)
-make build-docker # Teams Docker image
+make build-server # Frontend and backend (server)
+make build-docker # Server Docker image
./scripts/build.sh # Cross-platform build
```
@@ -546,6 +613,8 @@ See `docs/prerelease-builds.md` for download instructions.
- `~/.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)
## Recent Changes
- 001-update-version-display: Added Go 1.24 (toolchain go1.24.10)
diff --git a/Dockerfile b/Dockerfile
index b1b15d7c..0418491b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,12 +13,12 @@ COPY . .
RUN cd frontend && npm ci && npm run build
RUN mkdir -p web/frontend && cp -r frontend/dist web/frontend/
-# Build teams binary
+# Build server edition binary
ARG VERSION=dev
ARG COMMIT=unknown
ARG BUILD_DATE=unknown
RUN CGO_ENABLED=0 go build \
- -tags teams \
+ -tags server \
-ldflags "-X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${BUILD_DATE} -X github.com/smart-mcp-proxy/mcpproxy-go/internal/httpapi.buildVersion=${VERSION} -s -w" \
-o /mcpproxy ./cmd/mcpproxy
diff --git a/Makefile b/Makefile
index 782d9423..b8cf7ef9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
# MCPProxy Makefile
-.PHONY: help build build-teams build-docker build-deb swagger swagger-verify frontend-build frontend-dev backend-dev clean test test-coverage test-e2e test-e2e-oauth lint dev-setup docs-setup docs-dev docs-build docs-clean
+.PHONY: help build build-server build-docker build-deb swagger swagger-verify frontend-build frontend-dev backend-dev clean test test-coverage test-e2e test-e2e-oauth lint dev-setup docs-setup docs-dev docs-build docs-clean
SWAGGER_BIN ?= $(HOME)/go/bin/swag
SWAGGER_OUT ?= oas
@@ -23,10 +23,10 @@ help:
@echo " make lint - Run linter"
@echo " make dev-setup - Install development dependencies (swag, frontend, Playwright)"
@echo ""
- @echo "Teams Edition:"
- @echo " make build-teams - Build Teams edition binary (with -tags teams)"
- @echo " make build-docker - Build Teams Docker image"
- @echo " make build-deb - Build Teams .deb package (TODO)"
+ @echo "Server Edition:"
+ @echo " make build-server - Build Server edition binary (with -tags server)"
+ @echo " make build-docker - Build Server Docker image"
+ @echo " make build-deb - Build Server .deb package (TODO)"
@echo ""
@echo "Documentation Commands:"
@echo " make docs-setup - Install documentation dependencies"
@@ -91,28 +91,28 @@ backend-dev:
@echo "🚀 Run: ./mcpproxy-dev serve"
@echo "🌐 In dev mode, make sure frontend dev server is running on port 3000"
-# Build Teams edition
-build-teams: swagger frontend-build
- @echo "🔨 Building Teams edition binary (version: $(VERSION))..."
- go build -tags teams -ldflags "$(LDFLAGS)" -o mcpproxy-teams ./cmd/mcpproxy
- @echo "✅ Teams build completed! Run: ./mcpproxy-teams serve"
+# Build Server edition
+build-server: swagger frontend-build
+ @echo "🔨 Building Server edition binary (version: $(VERSION))..."
+ go build -tags server -ldflags "$(LDFLAGS)" -o mcpproxy-server ./cmd/mcpproxy
+ @echo "✅ Server build completed! Run: ./mcpproxy-server serve"
-# Build Teams Docker image
+# Build Server Docker image
build-docker:
- @echo "🐳 Building Teams Docker image (version: $(VERSION))..."
- docker build --build-arg VERSION=$(VERSION) --build-arg COMMIT=$(COMMIT) --build-arg BUILD_DATE=$(BUILD_DATE) -t mcpproxy-teams:$(VERSION) -t mcpproxy-teams:latest .
- @echo "✅ Docker image built: mcpproxy-teams:$(VERSION)"
+ @echo "🐳 Building Server Docker image (version: $(VERSION))..."
+ docker build --build-arg VERSION=$(VERSION) --build-arg COMMIT=$(COMMIT) --build-arg BUILD_DATE=$(BUILD_DATE) -t mcpproxy-server:$(VERSION) -t mcpproxy-server:latest .
+ @echo "✅ Docker image built: mcpproxy-server:$(VERSION)"
-# Build Teams .deb package (placeholder)
+# Build Server .deb package (placeholder)
build-deb:
- @echo "📦 Building Teams .deb package..."
+ @echo "📦 Building Server .deb package..."
@echo "⚠️ TODO: Implement deb package build (nfpm or dpkg-deb)"
@echo " See: https://nfpm.goreleaser.com/"
# Clean build artifacts
clean:
@echo "🧹 Cleaning build artifacts..."
- rm -f mcpproxy mcpproxy-dev mcpproxy-tray mcpproxy-teams
+ rm -f mcpproxy mcpproxy-dev mcpproxy-tray mcpproxy-server
rm -rf frontend/dist frontend/node_modules web/frontend
go clean
@echo "✅ Cleanup completed"
diff --git a/cmd/mcpproxy/edition_teams.go b/cmd/mcpproxy/edition_teams.go
index 15fc8f1d..d64af852 100644
--- a/cmd/mcpproxy/edition_teams.go
+++ b/cmd/mcpproxy/edition_teams.go
@@ -1,7 +1,7 @@
-//go:build teams
+//go:build server
package main
func init() {
- Edition = "teams"
+ Edition = "server"
}
diff --git a/cmd/mcpproxy/status_cmd.go b/cmd/mcpproxy/status_cmd.go
index 8448d283..a461e2ac 100644
--- a/cmd/mcpproxy/status_cmd.go
+++ b/cmd/mcpproxy/status_cmd.go
@@ -367,7 +367,7 @@ func printStatusTable(info *StatusInfo) {
if info.TeamsInfo != nil {
fmt.Println()
- fmt.Println("Teams")
+ fmt.Println("Server Edition")
fmt.Printf(" %-12s %s\n", "OAuth:", info.TeamsInfo.OAuthProvider)
fmt.Printf(" %-12s %s\n", "Admins:", strings.Join(info.TeamsInfo.AdminEmails, ", "))
}
diff --git a/cmd/mcpproxy/status_teams.go b/cmd/mcpproxy/status_teams.go
index f77755ce..298a900d 100644
--- a/cmd/mcpproxy/status_teams.go
+++ b/cmd/mcpproxy/status_teams.go
@@ -1,4 +1,4 @@
-//go:build teams
+//go:build server
package main
diff --git a/cmd/mcpproxy/status_teams_stub.go b/cmd/mcpproxy/status_teams_stub.go
index 327de7e7..5cc41507 100644
--- a/cmd/mcpproxy/status_teams_stub.go
+++ b/cmd/mcpproxy/status_teams_stub.go
@@ -1,4 +1,4 @@
-//go:build !teams
+//go:build !server
package main
diff --git a/cmd/mcpproxy/teams_register.go b/cmd/mcpproxy/teams_register.go
index d2647b0d..3fa7ae7b 100644
--- a/cmd/mcpproxy/teams_register.go
+++ b/cmd/mcpproxy/teams_register.go
@@ -1,11 +1,11 @@
-//go:build teams
+//go:build server
package main
-// Teams features are registered via init() functions in their respective
-// packages. The actual setup happens when the server calls teams.SetupAll()
-// during HTTP server initialization (see internal/server/teams_wire.go).
+// Server edition features are registered via init() functions in their
+// respective packages. The actual setup happens when the server calls
+// teams.SetupAll() during HTTP server initialization (see internal/server/teams_wire.go).
//
// This file imports the teams package for its init() side effects,
-// which register feature modules in the teams registry.
+// which register feature modules in the server registry.
import _ "github.com/smart-mcp-proxy/mcpproxy-go/internal/teams"
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index babaa029..d6d6bece 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -96,7 +96,7 @@ function handleAuthError(event: APIAuthEvent) {
}
onMounted(async () => {
- // Initialize auth state (needed for teams edition role-based nav)
+ // Initialize auth state (needed for server edition role-based nav)
await authStore.checkAuth()
// Set up API error listener
diff --git a/frontend/src/components/SidebarNav.vue b/frontend/src/components/SidebarNav.vue
index ddc6db76..27eecca2 100644
--- a/frontend/src/components/SidebarNav.vue
+++ b/frontend/src/components/SidebarNav.vue
@@ -8,14 +8,14 @@
Team overview and system health
+Server overview and system health