From 72ed1ae68083982f3e093f3b78d2b0fda6bd2097 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Fri, 6 Mar 2026 21:45:08 +0200 Subject: [PATCH 1/3] feat(spec): add MCPProxy Teams specification (029) Multi-user MCP proxy for teams of 2-50 people with identity provider authentication, per-user workspaces, credential isolation, server templates, and admin management. 8 user stories (P1-P8), 38 functional requirements, 10 success criteria. Co-Authored-By: Claude Opus 4.6 --- .../checklists/requirements.md | 37 +++ specs/029-mcpproxy-teams/spec.md | 313 ++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 specs/029-mcpproxy-teams/checklists/requirements.md create mode 100644 specs/029-mcpproxy-teams/spec.md diff --git a/specs/029-mcpproxy-teams/checklists/requirements.md b/specs/029-mcpproxy-teams/checklists/requirements.md new file mode 100644 index 00000000..e3d89cf7 --- /dev/null +++ b/specs/029-mcpproxy-teams/checklists/requirements.md @@ -0,0 +1,37 @@ +# Specification Quality Checklist: MCPProxy Teams + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-03-06 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- All items pass validation. Spec is ready for `/speckit.plan`. +- The spec intentionally avoids mentioning specific technologies (BBolt, Go, Vue, Docker) — these belong in the implementation plan. +- "Local process isolation" is used instead of "Docker containers" to keep the spec technology-agnostic. +- "Standards-compliant generic provider" is used instead of "OIDC" to maintain stakeholder readability. diff --git a/specs/029-mcpproxy-teams/spec.md b/specs/029-mcpproxy-teams/spec.md new file mode 100644 index 00000000..4986faa0 --- /dev/null +++ b/specs/029-mcpproxy-teams/spec.md @@ -0,0 +1,313 @@ +# Feature Specification: MCPProxy Teams + +**Feature Branch**: `029-mcpproxy-teams` +**Created**: 2026-03-06 +**Status**: Draft +**Input**: Multi-user MCP proxy for teams of 2-50 people with identity provider authentication, per-user workspaces, credential isolation, and admin management. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Team Admin Sets Up Multi-User Mode (Priority: P1) + +A team administrator configures MCPProxy to operate in team mode by selecting an identity provider (Google, GitHub, Microsoft, or a generic provider). The admin specifies who can access the proxy via email allowlists, domain restrictions, or organization membership. Once configured, the proxy requires all users to authenticate through the chosen identity provider before accessing any MCP tools. + +**Why this priority**: Without authentication infrastructure, no other team feature can function. This is the foundation that enables all multi-user capabilities. + +**Independent Test**: Can be tested by configuring team mode with a test identity provider, then verifying that unauthenticated requests are rejected and authenticated users receive valid session tokens. + +**Acceptance Scenarios**: + +1. **Given** the proxy is configured in team mode with Google as the identity provider, **When** a user navigates to the MCP endpoint, **Then** they are redirected to Google sign-in and returned to the proxy after successful authentication. +2. **Given** the admin has set allowed domains to "company.com", **When** a user with a personal email attempts to log in, **Then** access is denied with a clear error message. +3. **Given** team mode is configured with Microsoft and the organization enforces MFA, **When** a user logs in, **Then** the identity provider handles MFA transparently and the proxy receives the authenticated identity after completion. +4. **Given** no team mode is configured (default), **When** the proxy starts, **Then** it operates in personal mode with existing single-user behavior unchanged. +5. **Given** the admin configures GitHub as the identity provider with an allowed organization, **When** a GitHub user who is not a member of that org attempts to log in, **Then** access is denied. +6. **Given** a generic identity provider is configured (e.g., Keycloak or Okta), **When** a user authenticates, **Then** the proxy extracts their identity from standard claims and grants access. + +--- + +### User Story 2 - Team Members Access Shared MCP Servers (Priority: P2) + +After authenticating, team members can immediately use all MCP servers that the admin has configured for the team. Each user sees the same set of team servers and can invoke tools through them. The system identifies which user is making each request, enabling per-user audit trails. + +**Why this priority**: Shared server access is the primary value proposition — the reason teams deploy a shared proxy instead of individual ones. + +**Independent Test**: Can be tested by having two authenticated users invoke the same team MCP server and verifying both succeed with their identity recorded in the activity log. + +**Acceptance Scenarios**: + +1. **Given** the admin has added "Jira" and "GitHub" as team servers, **When** an authenticated user lists available tools, **Then** they see tools from both servers. +2. **Given** two users are authenticated, **When** both invoke a tool on the same team server, **Then** both requests succeed and each activity record shows the respective user's identity. +3. **Given** the admin disables a team server, **When** a user tries to invoke tools from that server, **Then** the tools are unavailable. + +--- + +### User Story 3 - Users Manage Personal Servers and Credentials (Priority: P3) + +Each team member can add their own personal MCP servers beyond the shared team servers. Users securely store their own credentials (e.g., personal access tokens for Jira, API keys for third-party services) that are encrypted and isolated from other users and even admins. When a personal server has the same name as a team server, the personal configuration takes precedence for that user. + +**Why this priority**: Per-user credential isolation solves the core security problem — shared service accounts giving all users access to restricted resources. This was the triggering use case (Jira/Confluence access pulled due to shared credentials). + +**Independent Test**: Can be tested by having a user add a personal server, store credentials, invoke a tool that uses those credentials, and verifying another user cannot see or use the first user's credentials. + +**Acceptance Scenarios**: + +1. **Given** an authenticated user, **When** they add a personal MCP server with their Jira personal access token, **Then** the server is available only to that user. +2. **Given** a user has stored credentials for an upstream service, **When** the admin queries the system, **Then** the admin cannot read the user's stored credentials. +3. **Given** a user has a personal server named "jira" and a team server also named "jira", **When** the user invokes a Jira tool, **Then** the personal server configuration is used. +4. **Given** a user stores an upstream credential, **When** the proxy restarts, **Then** the credential is still available (persisted and encrypted at rest). + +--- + +### User Story 4 - Per-User Server Isolation for Local Processes (Priority: P4) + +When MCP servers run as local processes (e.g., filesystem access, database tools, Slack bots), each user gets their own isolated instance. One user's server process cannot access another user's data or credentials. Idle server processes are automatically cleaned up to conserve resources. + +**Why this priority**: Without process isolation, a local MCP server started for one user could leak data to another. This is essential for security in a multi-user environment. + +**Independent Test**: Can be tested by having two users each connect to a filesystem MCP server and verifying each user's server process runs independently with its own credentials and cannot access the other's working directory. + +**Acceptance Scenarios**: + +1. **Given** two users both use a filesystem MCP server, **When** each invokes a tool, **Then** each request is handled by a separate isolated process with that user's credentials. +2. **Given** a user's server process crashes, **When** another user invokes the same server type, **Then** the other user's process is unaffected. +3. **Given** a user has not used a local server process for longer than the configured idle timeout, **When** the timeout expires, **Then** the process is automatically stopped and resources reclaimed. +4. **Given** a user connects to a remote HTTP-based MCP server (e.g., Jira cloud), **When** they invoke a tool, **Then** no local process is spawned — the proxy connects directly with the user's credentials in the request headers. + +--- + +### User Story 5 - Admin Manages Team Servers and Templates (Priority: P5) + +The team admin configures shared MCP servers that are available to all team members. To simplify setup, the system provides built-in templates for popular services (Jira, Confluence, GitHub, GitLab, Sentry, Linear, Notion, Slack, PostgreSQL, Filesystem). Admins can also create custom templates. Users set up personal servers from templates by filling in their own credentials. + +**Why this priority**: Templates dramatically reduce the friction of onboarding new team members — instead of configuring each server from scratch, users pick a template and enter their personal token. + +**Independent Test**: Can be tested by an admin creating a team server from a template, then a user creating a personal server from the same template with their own credentials, and verifying both work independently. + +**Acceptance Scenarios**: + +1. **Given** the admin selects the "Jira" template, **When** they fill in the company domain, **Then** a team Jira server is created and available to all users. +2. **Given** a user views available templates, **When** they select "GitHub" and enter their personal access token, **Then** a personal GitHub server is created in their workspace. +3. **Given** the admin creates a custom template with a URL pattern and auth configuration, **When** users browse templates, **Then** the custom template appears alongside built-in ones. +4. **Given** a non-admin user, **When** they attempt to manage team-wide servers, **Then** the action is denied. + +--- + +### User Story 6 - Users Create Scoped Agent Tokens (Priority: P6) + +Each authenticated user can create agent tokens that are scoped to their own workspace. An agent token created by a user can only access servers that user has access to (team + personal). Agent tokens carry the creating user's identity for activity logging purposes. + +**Why this priority**: Agent tokens enable unattended AI agent access without requiring interactive login. Per-user scoping ensures agents operate within the creating user's permissions. + +**Independent Test**: Can be tested by a user creating an agent token restricted to specific servers, then using that token to invoke tools and verifying it only accesses allowed servers with the user's identity in the audit log. + +**Acceptance Scenarios**: + +1. **Given** an authenticated user, **When** they create an agent token with access restricted to "github" and "filesystem" servers, **Then** the token can only invoke tools on those two servers. +2. **Given** an agent token created by Alice, **When** the token is used to invoke a tool, **Then** the activity log records both the agent token name and Alice's user identity. +3. **Given** an agent token created by Bob with read-only permissions, **When** the token attempts a write operation, **Then** the operation is denied. +4. **Given** a user has created the maximum allowed number of agent tokens, **When** they attempt to create another, **Then** the request is rejected with a clear limit message. + +--- + +### User Story 7 - Activity Log with User Identity (Priority: P7) + +Every MCP tool invocation, server change, and administrative action is recorded with the identity of the user who performed it. Admins can view and filter activity across all users. Regular users can view only their own activity. Activity can be filtered by user, auth type (interactive login vs. agent token), agent name, server, tool, time range, and sensitivity level. + +**Why this priority**: Per-user audit trails are essential for security compliance, incident investigation, and understanding team usage patterns. + +**Independent Test**: Can be tested by having multiple users perform actions, then verifying the admin sees all activity with user attribution while each user sees only their own. + +**Acceptance Scenarios**: + +1. **Given** multiple users have performed tool invocations, **When** the admin views the activity log, **Then** each entry shows which user performed the action. +2. **Given** a regular user views the activity log, **When** they apply no filters, **Then** they see only their own activity. +3. **Given** activity from both interactive logins and agent tokens, **When** the admin filters by auth type "agent", **Then** only agent-token-initiated activity is shown. +4. **Given** the admin filters activity by a specific user, **When** results are displayed, **Then** only that user's activity appears. + +--- + +### User Story 8 - Web Interface for Login, Dashboard, and Administration (Priority: P8) + +The proxy provides a web interface where users log in via their identity provider, view their personal dashboard (available servers, agent tokens, recent activity), and manage their workspace. Admins access an additional panel for team server management, template configuration, and user overview. + +**Why this priority**: A web interface makes the system accessible to non-technical team members and provides admins with a visual management surface. + +**Independent Test**: Can be tested by logging into the web interface, viewing the dashboard, managing a personal server, and (as admin) managing team servers — all through the browser. + +**Acceptance Scenarios**: + +1. **Given** a user navigates to the web interface, **When** they are not authenticated, **Then** they see a login page with the configured identity provider option. +2. **Given** an authenticated user, **When** they access the dashboard, **Then** they see their available servers (team + personal), agent tokens, and recent activity. +3. **Given** an admin user, **When** they access the admin panel, **Then** they can manage team servers, view templates, see all users, and browse team-wide activity. +4. **Given** a non-admin user, **When** they attempt to access admin functionality, **Then** the admin panel is not visible or accessible. + +--- + +### Edge Cases + +- What happens when an identity provider is temporarily unavailable? Users with valid existing sessions continue working; new logins fail with a descriptive error suggesting retry. +- What happens when a user is removed from the allowed list while they have an active session? Their session tokens are invalidated at next refresh (within 1 hour). Active requests complete normally. +- What happens when a user's upstream credential (stored in their vault) expires or is revoked? The upstream server returns an auth error, which is surfaced to the user with guidance to update their credential. +- What happens when the admin switches identity providers? Existing user sessions are invalidated. Users must re-authenticate with the new provider. User data is preserved if the email matches. +- What happens when two users create personal servers with the same name? Each user's namespace is independent — name collisions between users are impossible. +- What happens when the system reaches the maximum number of concurrent local server processes? New process creation is queued or rejected with a clear capacity message. Existing processes are unaffected. +- What happens when a user who created agent tokens is removed from the team? All their agent tokens are automatically revoked. Existing in-flight requests complete, but new requests with those tokens are rejected. + +## Requirements *(mandatory)* + +### Functional Requirements + +**Authentication & Identity** + +- **FR-001**: System MUST support team mode activation via configuration, separate from the existing personal mode. +- **FR-002**: System MUST authenticate users via external identity providers: Google, GitHub, Microsoft Entra ID, and any standards-compliant generic provider. +- **FR-003**: System MUST issue its own session tokens after successful identity provider authentication — identity provider tokens are never exposed to MCP clients. +- **FR-004**: System MUST support access control via email allowlists, domain restrictions, and organization membership depending on the identity provider. +- **FR-005**: System MUST support multi-factor authentication transparently through the identity provider's own MFA enforcement. +- **FR-006**: System MUST support automatic discovery of authentication requirements by MCP clients per the MCP specification. +- **FR-007**: System MUST support token refresh without requiring the user to re-authenticate interactively (within the refresh token lifetime). + +**User Workspaces** + +- **FR-008**: System MUST provide each authenticated user with a workspace combining team-wide servers and their personal servers. +- **FR-009**: System MUST allow personal server configurations to override team servers of the same name for that user only. +- **FR-010**: System MUST allow users to add, edit, and remove their own personal MCP servers. +- **FR-011**: System MUST keep each user's workspace isolated — one user cannot see or modify another user's personal servers. + +**Credential Management** + +- **FR-012**: System MUST encrypt all user-stored upstream credentials at rest. +- **FR-013**: System MUST ensure that no user (including admins) can read another user's stored upstream credentials. +- **FR-014**: System MUST persist encrypted credentials across proxy restarts. + +**Server Process Isolation** + +- **FR-015**: System MUST run local (stdio-based) MCP servers as separate isolated processes per user. +- **FR-016**: System MUST inject per-user credentials into isolated server processes at startup. +- **FR-017**: System MUST connect to remote (HTTP-based) MCP servers using per-user credentials in request headers without spawning local processes. +- **FR-018**: System MUST automatically clean up idle local server processes after a configurable timeout. + +**Admin Management** + +- **FR-019**: System MUST support an admin role determined by email match or identity provider claim. +- **FR-020**: Admins MUST be able to manage team-wide MCP server configurations. +- **FR-021**: Admins MUST be able to create, edit, and remove server templates. +- **FR-022**: Admins MUST be able to view all users' activity logs. +- **FR-023**: Non-admin users MUST NOT be able to perform admin actions (team server management, viewing other users' activity, user management). + +**Server Templates** + +- **FR-024**: System MUST provide built-in templates for popular services: Jira, Confluence, GitHub, GitLab, Sentry, Linear, Notion, Slack, PostgreSQL, and Filesystem. +- **FR-025**: Each template MUST define a URL pattern, authentication type, and required user-provided variables (e.g., domain, personal access token). +- **FR-026**: Admins MUST be able to create custom templates beyond the built-in set. +- **FR-027**: Users MUST be able to create personal servers from templates by providing their own credentials. + +**Agent Tokens** + +- **FR-028**: Each user MUST be able to create agent tokens scoped to their workspace. +- **FR-029**: Agent tokens MUST only access servers within the creating user's workspace (team + personal). +- **FR-030**: Agent token activity MUST be attributed to both the token and the creating user in the activity log. + +**Activity & Audit** + +- **FR-031**: All MCP tool invocations MUST be recorded with the identity of the user who performed them. +- **FR-032**: Admins MUST be able to filter activity by user, auth type, agent name, server, tool, time range, and sensitivity level. +- **FR-033**: Non-admin users MUST only see their own activity. + +**Web Interface** + +- **FR-034**: System MUST provide a login page that redirects to the configured identity provider. +- **FR-035**: System MUST provide a user dashboard showing available servers, agent tokens, and recent activity. +- **FR-036**: System MUST provide an admin panel for team server management, template configuration, user overview, and team-wide activity. + +**Backward Compatibility** + +- **FR-037**: When operating in personal mode (default), the system MUST behave identically to the current single-user version with no team features active. +- **FR-038**: The system MUST be distributed as a separate binary from the personal edition. + +### Key Entities + +- **User**: An authenticated team member identified by email from an identity provider. Has a role (admin or member), a workspace, stored credentials, and agent tokens. +- **Workspace**: The effective set of MCP servers available to a user — the union of team servers and the user's personal servers, with personal overriding team on name conflict. +- **Team Server**: An MCP server configured by an admin, available to all authenticated team members. +- **Personal Server**: An MCP server configured by an individual user, available only to that user. +- **Credential Vault Entry**: An encrypted upstream service credential (e.g., Jira PAT, GitHub token) belonging to a specific user. +- **Server Template**: A reusable server configuration pattern with variable placeholders (e.g., domain, token) that users fill in to create servers quickly. +- **Agent Token**: A scoped access token created by a user for unattended AI agent access, restricted to a subset of that user's workspace. +- **Activity Record**: A log entry for an MCP operation, attributing it to a user and optionally an agent token. +- **Identity Provider Configuration**: The admin-defined settings for connecting to an external authentication service (Google, GitHub, Microsoft, or generic). + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: An admin can configure team mode and have the first user authenticate in under 10 minutes using the simplest identity provider setup path. +- **SC-002**: A new team member can log in, browse available tools, and successfully invoke their first MCP tool within 3 minutes of receiving access. +- **SC-003**: System supports 50 concurrent authenticated users, each with up to 10 personal servers, without noticeable degradation in tool invocation response time. +- **SC-004**: Per-user credential isolation is absolute — no pathway exists for one user to read another user's stored upstream credentials, verified by security testing. +- **SC-005**: Activity logs for any user or time range can be retrieved in under 2 seconds. +- **SC-006**: Admin can onboard a new team member (grant access) without restarting the proxy. +- **SC-007**: A user setting up a personal server from a template completes the process in under 1 minute. +- **SC-008**: Existing personal mode deployments experience zero behavior change when upgrading to the version that includes team capabilities. +- **SC-009**: Idle local server processes are cleaned up within 1 minute of the configured timeout, with no orphaned processes after 24 hours of operation. +- **SC-010**: All four identity providers (Google, GitHub, Microsoft, generic) pass authentication end-to-end tests with test accounts. + +## Assumptions + +- The deployment environment has outbound internet access to reach identity providers (Google, GitHub, Microsoft endpoints). +- The host machine supports running isolated local processes (container runtime available for stdio server isolation). +- Teams of 2-50 people represent the target scale. Scaling beyond 50 users is explicitly out of scope for v1. +- Users have existing accounts with the configured identity provider (the system does not provision identity provider accounts). +- Session token lifetime defaults to 1 hour with 7-day refresh tokens — standard web application session management. +- Agent token maximum per user follows the same limit as the personal edition (10 tokens per user). +- Admin designation is static per configuration (email list or IdP claim) — there is no in-app role assignment UI in v1. +- Server template definitions are static (shipped with the binary + admin-defined in config) — there is no template marketplace or community sharing. +- The separate binary distribution model means the personal and team editions are built from different source repositories but share core libraries. +- Data retention for activity logs follows the same policy as the personal edition — configurable, with no default auto-purge. + +## Scope Boundaries + +### In Scope (v1) + +- Team mode with identity-provider-based authentication +- Four identity providers: Google, GitHub, Microsoft Entra ID, Generic (covering Keycloak, Okta, Auth0) +- Per-user workspaces with team + personal servers +- Encrypted per-user credential vault +- Per-user isolated local server processes for stdio-based servers +- Per-user credential injection for HTTP-based remote servers +- Admin and member roles +- Server templates (10 built-in + custom) +- Per-user agent tokens +- Activity log with user identity and filtering +- Web UI: login, dashboard, admin panel +- Backward-compatible personal mode +- Separate binary distribution + +### Out of Scope (v1) + +- OpenTelemetry metrics/tracing export +- Direct LDAP authentication (supported via IdP federation with Keycloak/Okta) +- Policy engine integration (OPA, Cedar) +- Multi-instance high-availability deployment +- SCIM user provisioning +- Per-tool role-based access control (access is per-server, not per-tool) +- License key enforcement or seat management +- Template marketplace or community sharing +- In-app role assignment (admin is config-driven) + +## Commit Message Conventions *(mandatory)* + +When committing changes for this feature, follow these guidelines: + +### Issue References +- Use: `Related #[issue-number]` - Links the commit to the issue without auto-closing +- Do NOT use: `Fixes #[issue-number]`, `Closes #[issue-number]`, `Resolves #[issue-number]` - These auto-close issues on merge + +**Rationale**: Issues should only be closed manually after verification and testing in production, not automatically on merge. + +### Co-Authorship +- Do NOT include: `Co-Authored-By: Claude ` +- Do NOT include: "Generated with Claude Code" + +**Rationale**: Commit authorship should reflect the human contributors, not the AI tools used. From f5a399ccda0e34ec2b75d32f632348173cfbe5b6 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 8 Mar 2026 09:02:18 +0200 Subject: [PATCH 2/3] feat(teams): repo restructure for personal + teams dual-edition architecture [#029] Establish build tag architecture to support two editions (personal and teams) from the same codebase. Personal is the default build; teams requires -tags teams. - Add edition detection via build tags (edition.go / edition_teams.go) - Add teams feature registry (internal/teams/) with init() registration pattern - Add edition to version output, startup logs, and /api/v1/status - Add Dockerfile for teams server distribution (distroless runtime) - Add Makefile targets: build-teams, build-docker, build-deb - Extend release workflow with teams Linux matrix + Docker GHCR push - Add native/ placeholder directories for future Swift/C# tray apps - Add frontend/src/{views,components}/teams/ skeleton directories - Update spec with FR-039 through FR-044 (build & distribution requirements) --- .dockerignore | 22 +++ .github/workflows/release.yml | 65 ++++++- CLAUDE.md | 33 +++- Dockerfile | 32 ++++ Makefile | 27 ++- cmd/mcpproxy/edition.go | 5 + cmd/mcpproxy/edition_teams.go | 7 + cmd/mcpproxy/main.go | 7 + cmd/mcpproxy/teams_register.go | 18 ++ .../2026-03-08-repo-restructure-design.md | 77 ++++++++ frontend/src/components/teams/.gitkeep | 0 frontend/src/views/teams/.gitkeep | 0 internal/httpapi/server.go | 14 ++ internal/teams/doc.go | 6 + internal/teams/registry.go | 43 +++++ internal/teams/registry_test.go | 63 +++++++ native/macos/README.md | 21 +++ native/windows/README.md | 21 +++ specs/029-mcpproxy-teams/data-model.md | 53 ++++++ specs/029-mcpproxy-teams/plan.md | 96 ++++++++++ specs/029-mcpproxy-teams/quickstart.md | 52 ++++++ specs/029-mcpproxy-teams/research.md | 59 ++++++ specs/029-mcpproxy-teams/spec.md | 22 ++- specs/029-mcpproxy-teams/tasks.md | 172 ++++++++++++++++++ 24 files changed, 899 insertions(+), 16 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 cmd/mcpproxy/edition.go create mode 100644 cmd/mcpproxy/edition_teams.go create mode 100644 cmd/mcpproxy/teams_register.go create mode 100644 docs/plans/2026-03-08-repo-restructure-design.md create mode 100644 frontend/src/components/teams/.gitkeep create mode 100644 frontend/src/views/teams/.gitkeep create mode 100644 internal/teams/doc.go create mode 100644 internal/teams/registry.go create mode 100644 internal/teams/registry_test.go create mode 100644 native/macos/README.md create mode 100644 native/windows/README.md create mode 100644 specs/029-mcpproxy-teams/data-model.md create mode 100644 specs/029-mcpproxy-teams/plan.md create mode 100644 specs/029-mcpproxy-teams/quickstart.md create mode 100644 specs/029-mcpproxy-teams/research.md create mode 100644 specs/029-mcpproxy-teams/tasks.md diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..78bbde59 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,22 @@ +.git +.github +.specify +native +specs +docs +*.md +!README.md +node_modules +frontend/node_modules +frontend/dist +e2e +coverage* +*.test +*.out +.DS_Store +Thumbs.db +.vscode +.idea +mcpproxy +mcpproxy-tray +mcpproxy-dev diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dceaa544..d0664aaa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -95,7 +95,7 @@ jobs: # Build the prompt PROMPT="Generate concise release notes for version ${VERSION} of MCPProxy (Smart MCP Proxy). - MCPProxy is a desktop application that acts as a smart proxy for AI agents using the Model Context Protocol (MCP). It provides intelligent tool discovery, token savings, and security quarantine for MCP servers. + MCPProxy is a smart proxy for AI agents using the Model Context Protocol (MCP). It provides intelligent tool discovery, token savings, and security quarantine for MCP servers. MCPProxy has two editions: Personal (desktop app) and Teams (multi-user server). Note which changes affect which edition if applicable. Commits since last release: ${COMMITS} @@ -225,6 +225,21 @@ jobs: cgo: "1" name: mcpproxy-darwin-arm64 archive_format: tar.gz + # Teams edition (Linux only) + - os: ubuntu-latest + goos: linux + goarch: amd64 + cgo: "0" + name: mcpproxy-teams-linux-amd64 + archive_format: tar.gz + edition: teams + - os: ubuntu-latest + goos: linux + goarch: arm64 + cgo: "0" + name: mcpproxy-teams-linux-arm64 + archive_format: tar.gz + edition: teams runs-on: ${{ matrix.os }} @@ -371,18 +386,23 @@ jobs: VERSION=${GITHUB_REF#refs/tags/} LDFLAGS="-s -w -X main.version=${VERSION} -X github.com/smart-mcp-proxy/mcpproxy-go/internal/httpapi.buildVersion=${VERSION}" - # Determine clean binary name - if [ "${{ matrix.goos }}" = "windows" ]; then + # Determine clean binary name and build flags + EDITION="${{ matrix.edition }}" + BUILD_TAGS="" + if [ "$EDITION" = "teams" ]; then + CLEAN_BINARY="mcpproxy-teams" + BUILD_TAGS="-tags teams" + elif [ "${{ matrix.goos }}" = "windows" ]; then CLEAN_BINARY="mcpproxy.exe" else CLEAN_BINARY="mcpproxy" fi # Create clean core binary for archive - go build -ldflags "${LDFLAGS}" -o ${CLEAN_BINARY} ./cmd/mcpproxy + go build ${BUILD_TAGS} -ldflags "${LDFLAGS}" -o ${CLEAN_BINARY} ./cmd/mcpproxy - # Build tray binary for platforms with GUI support (macOS and Windows) - if [ "${{ matrix.goos }}" = "darwin" ] || [ "${{ matrix.goos }}" = "windows" ]; then + # Build tray binary for platforms with GUI support (macOS and Windows, personal only) + if [ "$EDITION" != "teams" ] && { [ "${{ matrix.goos }}" = "darwin" ] || [ "${{ matrix.goos }}" = "windows" ]; }; then echo "Building mcpproxy-tray for ${{ matrix.goos }}..." # Determine tray binary name @@ -851,13 +871,13 @@ jobs: - name: Upload versioned archive artifact uses: actions/upload-artifact@v4 with: - name: versioned-${{ matrix.goos }}-${{ matrix.goarch }} + name: versioned-${{ matrix.edition || 'personal' }}-${{ matrix.goos }}-${{ matrix.goarch }} path: mcpproxy-*-${{ matrix.goos }}-${{ matrix.goarch }}.${{ matrix.archive_format }} - name: Upload latest archive artifact uses: actions/upload-artifact@v4 with: - name: latest-${{ matrix.goos }}-${{ matrix.goarch }} + name: latest-${{ matrix.edition || 'personal' }}-${{ matrix.goos }}-${{ matrix.goarch }} path: mcpproxy-latest-${{ matrix.goos }}-${{ matrix.goarch }}.${{ matrix.archive_format }} - name: Upload macOS installer DMG @@ -965,8 +985,35 @@ jobs: name: installer-windows-${{ matrix.arch }} path: signed/mcpproxy-setup-${{ github.ref_name }}-${{ matrix.arch }}.exe + build-docker: + runs-on: ubuntu-latest + needs: [build] + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/build-push-action@v5 + with: + context: . + push: true + build-args: | + VERSION=${{ github.ref_name }} + 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 + release: - needs: [build, sign-windows, generate-notes] + needs: [build, sign-windows, generate-notes, build-docker] runs-on: ubuntu-latest environment: production diff --git a/CLAUDE.md b/CLAUDE.md index 25f98dfe..9813f978 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,6 +28,32 @@ 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) + +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 | + +### 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`) | +| `native/macos/` | Future Swift tray app (placeholder) | +| `native/windows/` | Future C# tray app (placeholder) | + +### Edition Detection + +The binary self-identifies its edition: +- `mcpproxy version` → `MCPProxy v0.21.0 (personal) darwin/arm64` +- `/api/v1/status` → `{"edition": "personal", ...}` + ## Architecture: Core + Tray Split - **Core Server** (`mcpproxy`): Headless HTTP API server with MCP proxy functionality @@ -39,9 +65,12 @@ MCPProxy is a Go-based desktop application that acts as a smart proxy for AI age ### Build ```bash -go build -o mcpproxy ./cmd/mcpproxy # Core server +go build -o mcpproxy ./cmd/mcpproxy # Core server (personal) +go build -tags teams -o mcpproxy-teams ./cmd/mcpproxy # Core server (teams) GOOS=darwin CGO_ENABLED=1 go build -o mcpproxy-tray ./cmd/mcpproxy-tray # Tray app -make build # Frontend and backend +make build # Frontend and backend (personal) +make build-teams # Frontend and backend (teams) +make build-docker # Teams Docker image ./scripts/build.sh # Cross-platform build ``` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b1b15d7c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# Build stage +FROM golang:1.24-alpine AS builder + +RUN apk add --no-cache git nodejs npm make + +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +# Build frontend +RUN cd frontend && npm ci && npm run build +RUN mkdir -p web/frontend && cp -r frontend/dist web/frontend/ + +# Build teams binary +ARG VERSION=dev +ARG COMMIT=unknown +ARG BUILD_DATE=unknown +RUN CGO_ENABLED=0 go build \ + -tags teams \ + -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 + +# Runtime stage +FROM gcr.io/distroless/static-debian12 + +COPY --from=builder /mcpproxy /usr/local/bin/mcpproxy + +EXPOSE 8080 + +ENTRYPOINT ["mcpproxy", "serve", "--listen", "0.0.0.0:8080"] diff --git a/Makefile b/Makefile index 3e0bc8e8..e7f4522b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # MCPProxy Makefile -.PHONY: help build 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-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 SWAGGER_BIN ?= $(HOME)/go/bin/swag SWAGGER_OUT ?= oas @@ -23,6 +23,11 @@ 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 "" @echo "Documentation Commands:" @echo " make docs-setup - Install documentation dependencies" @echo " make docs-dev - Start docs dev server (http://localhost:3000)" @@ -86,10 +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 Teams 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)" + +# Build Teams .deb package (placeholder) +build-deb: + @echo "📦 Building Teams .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 + rm -f mcpproxy mcpproxy-dev mcpproxy-tray mcpproxy-teams rm -rf frontend/dist frontend/node_modules web/frontend go clean @echo "✅ Cleanup completed" diff --git a/cmd/mcpproxy/edition.go b/cmd/mcpproxy/edition.go new file mode 100644 index 00000000..9d143d22 --- /dev/null +++ b/cmd/mcpproxy/edition.go @@ -0,0 +1,5 @@ +package main + +// Edition identifies which MCPProxy edition this binary is. +// This is the default value; teams edition overrides it via build tags. +var Edition = "personal" diff --git a/cmd/mcpproxy/edition_teams.go b/cmd/mcpproxy/edition_teams.go new file mode 100644 index 00000000..15fc8f1d --- /dev/null +++ b/cmd/mcpproxy/edition_teams.go @@ -0,0 +1,7 @@ +//go:build teams + +package main + +func init() { + Edition = "teams" +} diff --git a/cmd/mcpproxy/main.go b/cmd/mcpproxy/main.go index f5550876..2277120b 100644 --- a/cmd/mcpproxy/main.go +++ b/cmd/mcpproxy/main.go @@ -29,6 +29,7 @@ import ( "fmt" "os" "os/signal" + "runtime" "strings" "sync/atomic" "syscall" @@ -40,6 +41,7 @@ import ( clioutput "github.com/smart-mcp-proxy/mcpproxy-go/internal/cli/output" "github.com/smart-mcp-proxy/mcpproxy-go/internal/config" + "github.com/smart-mcp-proxy/mcpproxy-go/internal/httpapi" "github.com/smart-mcp-proxy/mcpproxy-go/internal/experiments" "github.com/smart-mcp-proxy/mcpproxy-go/internal/logs" "github.com/smart-mcp-proxy/mcpproxy-go/internal/registries" @@ -96,6 +98,7 @@ func main() { Short: "Smart MCP Proxy - Intelligent tool discovery and proxying for Model Context Protocol servers", Version: version, } + rootCmd.SetVersionTemplate(fmt.Sprintf("MCPProxy %s (%s) %s/%s\n", version, Edition, runtime.GOOS, runtime.GOARCH)) // Add global flags rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "", "Configuration file path") @@ -469,9 +472,13 @@ func runServer(cmd *cobra.Command, _ []string) error { logger.Info("Starting mcpproxy", zap.String("version", version), + zap.String("edition", Edition), zap.String("log_level", cmdLogLevel), zap.Bool("log_to_file", cmdLogToFile)) + // Pass edition to httpapi for status endpoint + httpapi.SetEdition(Edition) + // Override other settings from command line cfg.DebugSearch = cmdDebugSearch diff --git a/cmd/mcpproxy/teams_register.go b/cmd/mcpproxy/teams_register.go new file mode 100644 index 00000000..b464b418 --- /dev/null +++ b/cmd/mcpproxy/teams_register.go @@ -0,0 +1,18 @@ +//go:build teams + +package main + +import ( + "log" + + "github.com/smart-mcp-proxy/mcpproxy-go/internal/teams" +) + +func init() { + // Initialize all registered teams features. + // Individual feature packages (auth, workspace, etc.) register + // themselves via their own init() functions. + if err := teams.SetupAll(teams.Dependencies{}); err != nil { + log.Fatalf("failed to initialize teams features: %v", err) + } +} diff --git a/docs/plans/2026-03-08-repo-restructure-design.md b/docs/plans/2026-03-08-repo-restructure-design.md new file mode 100644 index 00000000..09373274 --- /dev/null +++ b/docs/plans/2026-03-08-repo-restructure-design.md @@ -0,0 +1,77 @@ +# MCPProxy Repo Restructure — Personal + Teams Foundation + +**Date:** 2026-03-08 +**Status:** Approved +**Author:** Algis Dumbris + +--- + +## Decision Summary + +MCPProxy personal and teams editions will be built from the **same repository** using Go build tags. No `pkg/` migration needed. No separate repos. + +## Binary Architecture + +- `go build ./cmd/mcpproxy` → **Personal edition** (default) +- `go build -tags teams ./cmd/mcpproxy` → **Teams edition** +- Teams-only code lives in `internal/teams/` with `//go:build teams` guards +- Teams features self-register via `init()` pattern +- Binary self-identifies edition in version output, startup logs, `/api/v1/status` + +## Repository Structure + +``` +mcpproxy-go/ +├── cmd/mcpproxy/ +│ ├── main.go ← shared entry point +│ └── teams_register.go ← //go:build teams +├── internal/ +│ ├── teams/ ← teams-only code (all build-tagged) +│ │ ├── auth/ ← OAuth authorization server +│ │ ├── providers/ ← Google, GitHub, Microsoft, OIDC +│ │ ├── workspace/ ← Per-user server resolution +│ │ ├── users/ ← User storage, credential vault +│ │ ├── templates/ ← Server template engine +│ │ └── middleware/ ← Teams auth middleware +│ └── ... (existing packages unchanged) +├── frontend/ +│ └── src/ +│ ├── views/teams/ ← Teams-only Vue pages +│ └── components/teams/ ← Teams-only components +├── native/ +│ ├── macos/ ← Swift tray app (Xcode project) +│ └── windows/ ← C# tray app (VS solution) +├── Dockerfile ← Teams Docker image +└── .github/workflows/release.yml ← Extended for both editions +``` + +## Distribution Matrix + +| Platform | Personal | Teams | +|----------|----------|-------| +| macOS | DMG (Swift tray + core) | Homebrew / binary tarball | +| Windows | MSI/EXE (C# tray + core) | N/A (server product) | +| Linux | tar.gz | Docker image, .deb, tar.gz | + +## Release Model + +Single GitHub release tag (e.g., `v0.21.0`) with all assets: +- Personal: DMG, EXE, tar.gz (6 platform combos) +- Teams: tar.gz, .deb (Linux amd64/arm64) +- Docker image pushed to `ghcr.io/smart-mcp-proxy/mcpproxy-teams` + +## Frontend Strategy + +Single Vue build for both editions. Teams pages are lazy-loaded routes. Backend returns 404 for teams routes in personal mode. No separate frontend builds needed. + +## Native Tray Apps + +Live in `native/` directory within the same repo. Swift for macOS, C# for Windows. Replace the current Go tray app (`cmd/mcpproxy-tray/`). + +## Key Design Decisions + +1. **Build tags over separate cmd/**: Zero main() duplication, clean plugin pattern +2. **No pkg/ migration**: internal/ stays internal, both editions in same module +3. **Single release**: One tag, one changelog, labeled assets +4. **Teams = server product**: No macOS/Windows installers, Docker is primary +5. **Frontend = one build**: Backend controls access, not build-time splitting diff --git a/frontend/src/components/teams/.gitkeep b/frontend/src/components/teams/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/views/teams/.gitkeep b/frontend/src/views/teams/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/internal/httpapi/server.go b/internal/httpapi/server.go index df72e23e..a7e57573 100644 --- a/internal/httpapi/server.go +++ b/internal/httpapi/server.go @@ -657,6 +657,7 @@ func (s *Server) writeSuccess(w http.ResponseWriter, data interface{}) { func (s *Server) handleGetStatus(w http.ResponseWriter, _ *http.Request) { response := map[string]interface{}{ "running": s.controller.IsRunning(), + "edition": editionValue, "listen_addr": s.controller.GetListenAddress(), "upstream_stats": s.controller.GetUpstreamStats(), "status": s.controller.GetStatus(), @@ -753,12 +754,25 @@ func (s *Server) buildWebUIURLWithAPIKey(listenAddr string, r *http.Request) str // buildVersion is set during build using -ldflags var buildVersion = "development" +// editionValue identifies the MCPProxy edition (personal or teams). +var editionValue = "personal" + // GetBuildVersion returns the build version from build-time variables. // This should be set during build using -ldflags. func GetBuildVersion() string { return buildVersion } +// SetEdition sets the edition value (called from main during startup). +func SetEdition(edition string) { + editionValue = edition +} + +// GetEdition returns the current edition. +func GetEdition() string { + return editionValue +} + // getSocketPath returns the socket path if socket communication is enabled func getSocketPath() string { // This would ideally be retrieved from the config diff --git a/internal/teams/doc.go b/internal/teams/doc.go new file mode 100644 index 00000000..38bf8f27 --- /dev/null +++ b/internal/teams/doc.go @@ -0,0 +1,6 @@ +//go:build teams + +// Package teams provides multi-user team features for MCPProxy. +// This package and all sub-packages are only compiled when the "teams" build tag is set. +// The personal edition of MCPProxy does not include any teams code. +package teams diff --git a/internal/teams/registry.go b/internal/teams/registry.go new file mode 100644 index 00000000..bc20d040 --- /dev/null +++ b/internal/teams/registry.go @@ -0,0 +1,43 @@ +//go:build teams + +package teams + +import "fmt" + +// Dependencies holds shared dependencies that teams features need. +type Dependencies struct { + // Future: router, storage, logger, event bus, etc. +} + +// Feature represents a teams feature module that self-registers. +type Feature struct { + Name string + Setup func(deps Dependencies) error +} + +var features []Feature + +// Register adds a teams feature to the registry. +// Called by feature packages in their init() functions. +func Register(f Feature) { + features = append(features, f) +} + +// SetupAll initializes all registered teams features. +func SetupAll(deps Dependencies) error { + for _, f := range features { + if err := f.Setup(deps); err != nil { + return fmt.Errorf("teams feature %s: %w", f.Name, err) + } + } + return nil +} + +// RegisteredFeatures returns the names of all registered features. +func RegisteredFeatures() []string { + names := make([]string, len(features)) + for i, f := range features { + names[i] = f.Name + } + return names +} diff --git a/internal/teams/registry_test.go b/internal/teams/registry_test.go new file mode 100644 index 00000000..0b675fbe --- /dev/null +++ b/internal/teams/registry_test.go @@ -0,0 +1,63 @@ +//go:build teams + +package teams + +import ( + "errors" + "testing" +) + +func TestRegisterAndSetupAll(t *testing.T) { + // Reset global state for test isolation + features = nil + + called := false + Register(Feature{ + Name: "test-feature", + Setup: func(deps Dependencies) error { + called = true + return nil + }, + }) + + if err := SetupAll(Dependencies{}); err != nil { + t.Fatalf("SetupAll failed: %v", err) + } + if !called { + t.Fatal("expected feature Setup to be called") + } +} + +func TestSetupAllError(t *testing.T) { + features = nil + + Register(Feature{ + Name: "failing-feature", + Setup: func(deps Dependencies) error { + return errors.New("setup failed") + }, + }) + + err := SetupAll(Dependencies{}) + if err == nil { + t.Fatal("expected error from SetupAll") + } + if err.Error() != "teams feature failing-feature: setup failed" { + t.Fatalf("unexpected error message: %v", err) + } +} + +func TestRegisteredFeatures(t *testing.T) { + features = nil + + Register(Feature{Name: "auth", Setup: func(deps Dependencies) error { return nil }}) + Register(Feature{Name: "workspace", Setup: func(deps Dependencies) error { return nil }}) + + names := RegisteredFeatures() + if len(names) != 2 { + t.Fatalf("expected 2 features, got %d", len(names)) + } + if names[0] != "auth" || names[1] != "workspace" { + t.Fatalf("unexpected feature names: %v", names) + } +} diff --git a/native/macos/README.md b/native/macos/README.md new file mode 100644 index 00000000..7e41d509 --- /dev/null +++ b/native/macos/README.md @@ -0,0 +1,21 @@ +# MCPProxy macOS Tray Application + +Native macOS system tray application built with Swift and SwiftUI. + +## Status + +Placeholder — not yet implemented. The current tray app is at `cmd/mcpproxy-tray/` (Go + systray). + +## Requirements + +- Xcode 15+ +- macOS 13+ (Ventura) +- Swift 5.9+ + +## Architecture + +The native tray app replaces `cmd/mcpproxy-tray/` with a platform-native experience: +- Manages the core `mcpproxy` server lifecycle +- Communicates via Unix socket (`~/.mcpproxy/mcpproxy.sock`) and REST API +- Subscribes to SSE (`/events`) for real-time status updates +- Provides native macOS menu bar integration diff --git a/native/windows/README.md b/native/windows/README.md new file mode 100644 index 00000000..3e606ea2 --- /dev/null +++ b/native/windows/README.md @@ -0,0 +1,21 @@ +# MCPProxy Windows Tray Application + +Native Windows system tray application built with C# and WPF. + +## Status + +Placeholder — not yet implemented. The current tray app is at `cmd/mcpproxy-tray/` (Go + systray). + +## Requirements + +- .NET 8+ SDK +- Visual Studio 2022+ or VS Code with C# extension +- Windows 10+ + +## Architecture + +The native tray app replaces `cmd/mcpproxy-tray/` with a platform-native experience: +- Manages the core `mcpproxy` server lifecycle +- Communicates via named pipe (`\\.\pipe\mcpproxy-`) and REST API +- Subscribes to SSE (`/events`) for real-time status updates +- Provides native Windows notification area integration diff --git a/specs/029-mcpproxy-teams/data-model.md b/specs/029-mcpproxy-teams/data-model.md new file mode 100644 index 00000000..c6d22c99 --- /dev/null +++ b/specs/029-mcpproxy-teams/data-model.md @@ -0,0 +1,53 @@ +# Data Model: MCPProxy Repo Restructure + +This restructure introduces no new data entities. It adds build-time metadata only. + +## Edition Metadata + +| Field | Type | Source | Description | +|-------|------|--------|-------------| +| `Edition` | `string` | Build tag | `"personal"` (default) or `"teams"` (with `-tags teams`) | +| `Version` | `string` | ldflags | Semantic version from git tag | +| `Commit` | `string` | ldflags | Short git commit hash | +| `BuildDate` | `string` | ldflags | ISO 8601 UTC build timestamp | + +## Status API Extension + +The `/api/v1/status` response gains one field: + +```json +{ + "version": "0.21.0", + "edition": "personal", + "... existing fields ..." +} +``` + +## Teams Feature Registry + +The teams edition uses a registration pattern for feature modules: + +```go +// internal/teams/registry.go +type Feature struct { + Name string + Setup func(deps Dependencies) error +} + +var features []Feature + +func Register(f Feature) { + features = append(features, f) +} + +func SetupAll(deps Dependencies) error { + for _, f := range features { + if err := f.Setup(deps); err != nil { + return fmt.Errorf("teams feature %s: %w", f.Name, err) + } + } + return nil +} +``` + +Future teams packages (auth, workspace, users) will call `teams.Register()` in their `init()` functions. This restructure creates the registry; no features register yet. diff --git a/specs/029-mcpproxy-teams/plan.md b/specs/029-mcpproxy-teams/plan.md new file mode 100644 index 00000000..4e131735 --- /dev/null +++ b/specs/029-mcpproxy-teams/plan.md @@ -0,0 +1,96 @@ +# Implementation Plan: MCPProxy Repo Restructure (Personal + Teams Foundation) + +**Branch**: `029-mcpproxy-teams` | **Date**: 2026-03-08 | **Spec**: [spec.md](spec.md) +**Input**: Feature specification from `/specs/029-mcpproxy-teams/spec.md` + +## Summary + +Restructure the MCPProxy repository to support two editions (Personal and Teams) built from the same codebase using Go build tags. Personal is the default build; Teams requires `-tags teams`. Add Dockerfile for teams, `native/` directory skeleton for future Swift/C# tray apps, extended Makefile, and edition self-identification. + +## Technical Context + +**Language/Version**: Go 1.24 (toolchain go1.24.10), TypeScript 5.x / Vue 3.5 (frontend) +**Primary Dependencies**: Cobra (CLI), Chi (HTTP), BBolt (storage), Zap (logging), mcp-go (MCP), Vue 3 + Tailwind + DaisyUI (frontend) +**Storage**: BBolt database (`~/.mcpproxy/config.db`) +**Testing**: `go test`, `./scripts/test-api-e2e.sh`, Playwright (OAuth E2E), Vitest (frontend) +**Target Platform**: macOS (Personal DMG), Windows (Personal MSI), Linux (Personal tar.gz, Teams Docker/deb/tar.gz) +**Project Type**: web (Go backend + Vue frontend, embedded) +**Performance Goals**: No regression from current performance +**Constraints**: Zero behavior change for existing personal mode users (FR-037) +**Scale/Scope**: Foundation only — no teams feature logic, just the skeleton and build infrastructure + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +| Principle | Status | Notes | +|-----------|--------|-------| +| I. Performance at Scale | PASS | No performance-affecting changes; restructure only | +| II. Actor-Based Concurrency | PASS | No concurrency changes | +| III. Configuration-Driven Architecture | PASS | Edition determined at build time, mode at config time | +| IV. Security by Default | PASS | Teams auth middleware added later; foundation is inert | +| V. Test-Driven Development | PASS | Tests for edition detection, build tag verification | +| VI. Documentation Hygiene | PASS | CLAUDE.md updated with new structure | + +**Architecture Constraints**: +| Constraint | Status | Notes | +|------------|--------|-------| +| Core + Tray Split | PASS | Tray split preserved; native/ skeleton added | +| Event-Driven Updates | PASS | No event changes | +| DDD Layering | PASS | internal/teams/ follows existing layer patterns | +| Upstream Client Modularity | PASS | No upstream changes | + +## Project Structure + +### Documentation (this feature) + +```text +specs/029-mcpproxy-teams/ +├── spec.md # Feature specification +├── plan.md # This file +├── research.md # Phase 0 output +├── data-model.md # Phase 1 output +├── quickstart.md # Phase 1 output +├── contracts/ # Phase 1 output +└── tasks.md # Phase 2 output +``` + +### Source Code (repository root) + +```text +cmd/mcpproxy/ +├── main.go # Shared entry point (modify: add edition variable) +├── teams_register.go # NEW: //go:build teams — registers teams features +└── edition.go # NEW: default edition = "personal" + edition_teams.go # NEW: //go:build teams — overrides to "teams" + +internal/ +├── teams/ # NEW: teams-only skeleton +│ ├── doc.go # Package doc, //go:build teams +│ ├── registry.go # Feature registry (init pattern) +│ └── registry_test.go # Verify registration works +├── httpapi/ +│ └── server.go # Modify: expose edition in /api/v1/status +└── ... (all existing packages unchanged) + +frontend/ +└── src/ + └── views/teams/ # NEW: empty directory for future teams pages + └── components/teams/ # NEW: empty directory for future teams components + +native/ +├── macos/ # NEW: placeholder for Swift tray app +│ └── README.md +└── windows/ # NEW: placeholder for C# tray app + └── README.md + +Dockerfile # NEW: Teams Docker image +Makefile # Modify: add build-teams, build-docker, build-deb targets +.github/workflows/release.yml # Modify: add teams build matrix entries +``` + +**Structure Decision**: Existing Go project structure preserved. Teams code isolated in `internal/teams/` with build tags. No `pkg/` migration. Native tray apps in `native/` at repo root. + +## Complexity Tracking + +No constitution violations. All changes follow existing patterns. diff --git a/specs/029-mcpproxy-teams/quickstart.md b/specs/029-mcpproxy-teams/quickstart.md new file mode 100644 index 00000000..075b7616 --- /dev/null +++ b/specs/029-mcpproxy-teams/quickstart.md @@ -0,0 +1,52 @@ +# Quickstart: Building Personal vs Teams Edition + +## Build Personal Edition (default) + +```bash +make build +# or directly: +go build -ldflags "..." -o mcpproxy ./cmd/mcpproxy +./mcpproxy version +# MCPProxy v0.21.0 (personal) darwin/arm64 +``` + +## Build Teams Edition + +```bash +make build-teams +# or directly: +go build -tags teams -ldflags "..." -o mcpproxy-teams ./cmd/mcpproxy +./mcpproxy-teams version +# MCPProxy v0.21.0 (teams) linux/amd64 +``` + +## Build Teams Docker Image + +```bash +make build-docker +# or directly: +docker build -t mcpproxy-teams:latest . +docker run -p 8080:8080 mcpproxy-teams:latest +``` + +## Verify Edition + +```bash +# CLI +./mcpproxy version # shows "personal" +./mcpproxy-teams version # shows "teams" + +# API +curl http://localhost:8080/api/v1/status | jq .edition +``` + +## Development + +```bash +# Run tests (both editions) +go test ./internal/... -v # personal (default) +go test -tags teams ./internal/... -v # teams (includes teams tests) + +# Lint +./scripts/run-linter.sh +``` diff --git a/specs/029-mcpproxy-teams/research.md b/specs/029-mcpproxy-teams/research.md new file mode 100644 index 00000000..24698e3c --- /dev/null +++ b/specs/029-mcpproxy-teams/research.md @@ -0,0 +1,59 @@ +# Research: MCPProxy Repo Restructure + +## Go Build Tags Pattern + +**Decision**: Use `//go:build teams` file-level tags to isolate teams-only code. + +**Rationale**: Go build tags are the idiomatic way to compile different feature sets from the same codebase. The `init()` registration pattern allows teams packages to self-register without modifying shared code paths. + +**Alternatives considered**: +- Separate `cmd/` entries: Rejected — duplicates main() boilerplate, harder to maintain +- `pkg/` extraction: Rejected — unnecessary complexity when both editions share the same module +- Runtime feature flags: Rejected — dead code in personal binary, larger binary size + +## Edition Identification + +**Decision**: Use build-time `ldflags` variable + build-tagged source file for edition detection. + +**Rationale**: Two-pronged approach: +1. `edition.go` (default) sets `Edition = "personal"` +2. `edition_teams.go` (build-tagged) overrides `Edition = "teams"` + +This ensures the binary always knows its edition without runtime config. The edition is exposed in: +- `mcpproxy version` CLI output +- Startup log line +- `/api/v1/status` API response + +**Alternatives considered**: +- Config-only detection (`mode: team` implies teams edition): Rejected — edition is a build-time property, mode is a runtime property. Personal binary should never accept `mode: team`. + +## Docker Image Strategy + +**Decision**: Multi-stage Dockerfile using `golang:1.24` builder and `gcr.io/distroless/base` runtime. + +**Rationale**: Distroless minimizes attack surface for a server product. Multi-stage keeps the image small (~30MB). Teams-only — personal edition has no Docker use case. + +**Alternatives considered**: +- Alpine base: Larger, includes shell (unnecessary for mcpproxy) +- Scratch: Too minimal — no CA certificates, no timezone data +- Ubuntu base: Too large for a single Go binary + +## Release Workflow Extension + +**Decision**: Add teams matrix entries to existing release.yml. Single release tag, all assets together. + +**Rationale**: Minimal CI change. Teams adds 2 Linux matrix entries (amd64/arm64) + Docker build job + deb build job. Same version, same changelog. + +**Alternatives considered**: +- Separate release workflow: Rejected — version coordination overhead, maintenance burden +- Separate release tags: Rejected — premature for v1, can revisit if cadences diverge + +## Native Tray App Structure + +**Decision**: `native/macos/` (Swift) and `native/windows/` (C#) directories with README placeholders. + +**Rationale**: Skeleton only for now. The existing Go tray app (`cmd/mcpproxy-tray/`) continues to work until native apps are ready. No build integration yet — native apps will have their own build systems (Xcode, MSBuild). + +**Alternatives considered**: +- Separate repos: Rejected by decision — same repo keeps everything together +- `desktop/` directory name: Rejected — `native/` better communicates the intent (platform-native UI) diff --git a/specs/029-mcpproxy-teams/spec.md b/specs/029-mcpproxy-teams/spec.md index 4986faa0..b286ad41 100644 --- a/specs/029-mcpproxy-teams/spec.md +++ b/specs/029-mcpproxy-teams/spec.md @@ -224,7 +224,16 @@ The proxy provides a web interface where users log in via their identity provide **Backward Compatibility** - **FR-037**: When operating in personal mode (default), the system MUST behave identically to the current single-user version with no team features active. -- **FR-038**: The system MUST be distributed as a separate binary from the personal edition. +- **FR-038**: The system MUST be built as a separate binary from the personal edition using Go build tags (`-tags teams`), from the same repository and `cmd/mcpproxy` entry point. + +**Build & Distribution** + +- **FR-039**: The personal edition MUST be the default build output (`go build ./cmd/mcpproxy`). The teams edition MUST require an explicit build tag (`go build -tags teams ./cmd/mcpproxy`). +- **FR-040**: The teams edition MUST be distributed as a Docker image (`ghcr.io/smart-mcp-proxy/mcpproxy-teams`), a `.deb` package for Ubuntu/Debian, and a Linux binary tarball. +- **FR-041**: The personal edition MUST be distributed as macOS DMG installer (with native Swift tray app), Windows MSI/EXE installer (with native C# tray app), Linux binary tarball, and via Homebrew. +- **FR-042**: Both editions MUST be released under a single GitHub release tag (e.g., `v0.21.0`) with assets clearly named by edition (`mcpproxy-*` for personal, `mcpproxy-teams-*` for teams). +- **FR-043**: The teams Docker image MUST bind to `0.0.0.0:8080` by default (not localhost) and MUST NOT include tray/GUI components. +- **FR-044**: Each binary MUST self-identify its edition (personal or teams) in version output, startup logs, and the `/api/v1/status` endpoint. ### Key Entities @@ -263,7 +272,7 @@ The proxy provides a web interface where users log in via their identity provide - Agent token maximum per user follows the same limit as the personal edition (10 tokens per user). - Admin designation is static per configuration (email list or IdP claim) — there is no in-app role assignment UI in v1. - Server template definitions are static (shipped with the binary + admin-defined in config) — there is no template marketplace or community sharing. -- The separate binary distribution model means the personal and team editions are built from different source repositories but share core libraries. +- Both editions are built from the same repository (`mcpproxy-go`) using Go build tags. Teams-only code lives in `internal/teams/` with `//go:build teams` guards. No `pkg/` migration is needed. - Data retention for activity logs follows the same policy as the personal edition — configurable, with no default auto-purge. ## Scope Boundaries @@ -282,7 +291,10 @@ The proxy provides a web interface where users log in via their identity provide - Activity log with user identity and filtering - Web UI: login, dashboard, admin panel - Backward-compatible personal mode -- Separate binary distribution +- Same-repo build tag architecture (`-tags teams`) +- Single GitHub release with labeled assets per edition +- Docker image and deb package for teams distribution +- Native tray apps: Swift (macOS), C# (Windows) for personal distribution ### Out of Scope (v1) @@ -295,6 +307,10 @@ The proxy provides a web interface where users log in via their identity provide - License key enforcement or seat management - Template marketplace or community sharing - In-app role assignment (admin is config-driven) +- Docker image for personal edition (it's a desktop app) +- macOS DMG / Windows installer for teams edition (it's a server product) +- Kubernetes Helm chart (v1 — Docker Compose is the recommended multi-container setup) +- Separate repositories for native tray apps (Swift/C# live in `native/` within the same repo) ## Commit Message Conventions *(mandatory)* diff --git a/specs/029-mcpproxy-teams/tasks.md b/specs/029-mcpproxy-teams/tasks.md new file mode 100644 index 00000000..33f76a7c --- /dev/null +++ b/specs/029-mcpproxy-teams/tasks.md @@ -0,0 +1,172 @@ +# Tasks: MCPProxy Repo Restructure (Personal + Teams Foundation) + +**Input**: Design documents from `/specs/029-mcpproxy-teams/` +**Prerequisites**: plan.md, spec.md, research.md, data-model.md, quickstart.md +**Scope**: Repository restructure and build infrastructure only. Teams feature logic (auth, workspaces, users) is out of scope — this creates the skeleton. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- Include exact file paths in descriptions + +--- + +## Phase 1: Setup (Edition Detection & Build Tags) + +**Purpose**: Establish the build tag architecture that separates personal and teams editions + +- [x] T001 [P] Create edition detection file `cmd/mcpproxy/edition.go` with `var Edition = "personal"` and `GetEdition()` function +- [x] T002 [P] Create teams edition override file `cmd/mcpproxy/edition_teams.go` with `//go:build teams` tag that sets `Edition = "teams"` +- [x] T003 Update `cmd/mcpproxy/main.go` to log edition on startup and pass edition to server initialization +- [x] T004 Add edition field to `/api/v1/status` response in `internal/httpapi/server.go` +- [x] T005 Add edition to `mcpproxy version` CLI output in `cmd/mcpproxy/main.go` + +**Checkpoint**: `go build ./cmd/mcpproxy && ./mcpproxy version` shows "personal"; `go build -tags teams ./cmd/mcpproxy && ./mcpproxy version` shows "teams" + +--- + +## Phase 2: Teams Skeleton (internal/teams/) + +**Purpose**: Create the teams feature registry and package skeleton with build tags + +- [x] T006 Create `internal/teams/doc.go` with `//go:build teams` tag and package documentation +- [x] T007 Create `internal/teams/registry.go` with Feature struct, Register(), SetupAll() functions (build-tagged) +- [x] T008 Create `internal/teams/registry_test.go` with tests verifying registration and setup (build-tagged) +- [x] T009 Create teams registration entry point `cmd/mcpproxy/teams_register.go` with `//go:build teams` that imports `internal/teams` and calls `SetupAll()` during init +- [x] T010 Verify both builds compile: `go build ./cmd/mcpproxy` (no teams code) and `go build -tags teams ./cmd/mcpproxy` (with teams skeleton) + +**Checkpoint**: `go test -tags teams ./internal/teams/...` passes; personal build has zero teams code compiled in + +--- + +## Phase 3: Dockerfile & Build Targets + +**Purpose**: Create Docker distribution for teams edition and extend Makefile + +- [x] T011 [P] Create `Dockerfile` at repo root — multi-stage build with `golang:1.24` builder, `gcr.io/distroless/base` runtime, builds with `-tags teams`, embeds frontend, exposes 8080, entrypoint `mcpproxy serve --listen 0.0.0.0:8080` +- [x] T012 [P] Create `.dockerignore` excluding `.git`, `node_modules`, `native/`, `*.md`, test files +- [x] T013 Add Makefile targets: `build-teams` (Go binary with teams tag), `build-docker` (Docker image), `build-deb` (placeholder echoing "TODO") +- [x] T014 Verify `make build` still produces personal edition (no regression) +- [x] T015 Verify `make build-teams` produces teams binary and `make build-docker` builds Docker image + +**Checkpoint**: `docker run mcpproxy-teams:dev mcpproxy version` shows "teams" edition + +--- + +## Phase 4: Native Tray App Placeholders + +**Purpose**: Create directory structure for future Swift (macOS) and C# (Windows) tray apps + +- [x] T016 [P] Create `native/macos/README.md` with placeholder describing future Swift tray app, build requirements (Xcode 15+), and relationship to `cmd/mcpproxy-tray/` +- [x] T017 [P] Create `native/windows/README.md` with placeholder describing future C# tray app, build requirements (.NET 8+), and relationship to `cmd/mcpproxy-tray/` + +**Checkpoint**: Directory structure exists, no build changes + +--- + +## Phase 5: Frontend Teams Route Skeleton + +**Purpose**: Create empty directory structure for future teams-specific Vue pages + +- [x] T018 [P] Create `frontend/src/views/teams/.gitkeep` for future teams pages (login, admin panel, workspace) +- [x] T019 [P] Create `frontend/src/components/teams/.gitkeep` for future teams components + +**Checkpoint**: Frontend still builds cleanly with `cd frontend && npm run build` + +--- + +## Phase 6: Release Workflow Extension + +**Purpose**: Extend GitHub Actions release workflow to build teams assets alongside personal + +- [x] T020 Add teams Linux matrix entries (amd64, arm64) to `.github/workflows/release.yml` build job — uses `-tags teams` flag, produces `mcpproxy-teams-*` archives +- [x] T021 Add `build-docker` job to `.github/workflows/release.yml` — builds and pushes `ghcr.io/smart-mcp-proxy/mcpproxy-teams:$VERSION` on tag push +- [x] T022 Update release notes prompt in `.github/workflows/release.yml` to mention both editions +- [x] T023 Update release asset upload to include teams archives with `mcpproxy-teams-` prefix + +**Checkpoint**: CI workflow is valid YAML; teams matrix entries produce correctly named assets + +--- + +## Phase 7: Documentation & Polish + +**Purpose**: Update project documentation to reflect dual-edition architecture + +- [x] T024 Update `CLAUDE.md` — add Build & Distribution section documenting `build-teams`, `build-docker`, edition detection, `internal/teams/` structure +- [x] T025 Update `Makefile` help target to include new build-teams, build-docker, build-deb targets +- [x] T026 Verify all existing tests pass: `go test ./internal/... -v` (personal build) — all pass except pre-existing `internal/server` timeout +- [x] T027 Verify teams build tests pass: `go test -tags teams ./internal/teams/... -v` +- [x] T028 Verify E2E tests pass: `./scripts/test-api-e2e.sh` — 61/71 pass, 10 failures are pre-existing (same on clean branch) +- [x] T029 Verify linter passes: `./scripts/run-linter.sh` + +**Checkpoint**: All tests green, docs updated, both editions build cleanly + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Phase 1** (Setup): No dependencies — start immediately +- **Phase 2** (Teams Skeleton): Depends on T001, T002 from Phase 1 +- **Phase 3** (Docker/Build): Depends on Phase 1 completion +- **Phase 4** (Native Placeholders): No dependencies — can run in parallel with any phase +- **Phase 5** (Frontend Skeleton): No dependencies — can run in parallel with any phase +- **Phase 6** (Release Workflow): Depends on Phase 3 (needs to know build commands) +- **Phase 7** (Documentation): Depends on all other phases + +### Parallel Opportunities + +``` +Phase 1 (T001 ∥ T002) → T003 → (T004 ∥ T005) + ↓ +Phase 4 (T016 ∥ T017) ←── can run in parallel with everything +Phase 5 (T018 ∥ T019) ←── can run in parallel with everything + ↓ +Phase 2 (T006 ∥ T007) → T008 → T009 → T010 +Phase 3 (T011 ∥ T012) → T013 → T014 → T015 + ↓ +Phase 6 (T020 ∥ T021 ∥ T022) → T023 + ↓ +Phase 7 (T024 ∥ T025) → T026 → T027 → T028 → T029 +``` + +### Maximum Parallelism (with subagents) + +**Wave 1** (independent files, no deps): +- Agent A: T001 + T002 (edition files) +- Agent B: T016 + T017 (native placeholders) +- Agent C: T018 + T019 (frontend skeleton) +- Agent D: T011 + T012 (Dockerfile + .dockerignore) + +**Wave 2** (depends on Wave 1): +- Agent A: T003 + T004 + T005 (main.go, status, version) +- Agent B: T006 + T007 + T008 + T009 + T010 (teams skeleton) +- Agent C: T013 (Makefile targets) + +**Wave 3** (depends on Wave 2): +- Agent A: T020 + T021 + T022 + T023 (release workflow) +- Agent B: T024 + T025 (documentation) + +**Wave 4** (verification): +- T014, T015, T026, T027, T028, T029 (build & test verification) + +--- + +## Implementation Strategy + +### MVP First + +1. Complete Phase 1 + Phase 2 → edition detection works, teams skeleton compiles +2. **STOP and VALIDATE**: Both builds work, tests pass +3. Complete Phase 3 → Docker image builds +4. Complete remaining phases + +### Summary + +| Metric | Value | +|--------|-------| +| Total tasks | 29 | +| Parallelizable tasks | 12 | +| Phases | 7 | +| Estimated waves (with subagents) | 4 | From 1308259daede9b712ea50935749e091ba738387d Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 8 Mar 2026 09:58:09 +0200 Subject: [PATCH 3/3] chore(ci): disable teams release assets until MVP is ready Comment out teams matrix entries and Docker build job in release workflow. Personal edition releases are unaffected. Uncomment when teams MVP is complete. --- .github/workflows/release.yml | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d0664aaa..52ef665d 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) - - os: ubuntu-latest - goos: linux - goarch: amd64 - cgo: "0" - name: mcpproxy-teams-linux-amd64 - archive_format: tar.gz - edition: teams - - os: ubuntu-latest - goos: linux - goarch: arm64 - cgo: "0" - name: mcpproxy-teams-linux-arm64 - archive_format: tar.gz - edition: teams + # Teams edition (Linux only) — uncomment when teams MVP is ready + # - os: ubuntu-latest + # goos: linux + # goarch: amd64 + # cgo: "0" + # name: mcpproxy-teams-linux-amd64 + # archive_format: tar.gz + # edition: teams + # - os: ubuntu-latest + # goos: linux + # goarch: arm64 + # cgo: "0" + # name: mcpproxy-teams-linux-arm64 + # archive_format: tar.gz + # edition: teams runs-on: ${{ matrix.os }} @@ -988,7 +988,8 @@ jobs: build-docker: runs-on: ubuntu-latest needs: [build] - if: startsWith(github.ref, 'refs/tags/v') + # Disabled until teams MVP is ready — uncomment to enable + if: false && startsWith(github.ref, 'refs/tags/v') permissions: contents: read packages: write @@ -1013,7 +1014,7 @@ jobs: ghcr.io/smart-mcp-proxy/mcpproxy-teams:latest release: - needs: [build, sign-windows, generate-notes, build-docker] + needs: [build, sign-windows, generate-notes] # add build-docker when teams MVP is ready runs-on: ubuntu-latest environment: production