diff --git a/docs/memory/architecture.md b/docs/memory/architecture.md index cab109e9b..dc2fd2a34 100644 --- a/docs/memory/architecture.md +++ b/docs/memory/architecture.md @@ -610,6 +610,14 @@ Services that run continuously in the backend process: | GET | `/api/nevermined/settlement-failures` | Admin | Failed settlements | | POST | `/api/nevermined/retry-settlement/{log_id}` | Admin | Retry settlement | +### Platform Settings (3 endpoints) + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/settings/mcp-url` | Get configured MCP server URL (any auth user) | +| PUT | `/api/settings/mcp-url` | Set MCP server URL (admin-only) | +| DELETE | `/api/settings/mcp-url` | Reset to auto-detect (admin-only) | + --- ## Architectural Invariants diff --git a/docs/memory/feature-flows.md b/docs/memory/feature-flows.md index be5a10a19..b1c218761 100644 --- a/docs/memory/feature-flows.md +++ b/docs/memory/feature-flows.md @@ -44,6 +44,7 @@ | 2026-03-26 | EVT-001 | Agent Event Subscriptions — lightweight pub/sub for inter-agent pipelines | [agent-event-subscriptions.md](feature-flows/agent-event-subscriptions.md) | | 2026-03-25 | #129 | Active watchdog — reconcile DB state against agent process registries, recover orphans, auto-terminate timeouts | [cleanup-service.md](feature-flows/cleanup-service.md) | | 2026-03-25 | #148 | Fix silent subscription registration failure — encryption key auto-generation, status endpoint, frontend warning | [subscription-management.md](feature-flows/subscription-management.md) | +| 2026-03-25 | #76 | Configurable MCP Server URL in Admin Settings | [platform-settings.md](feature-flows/platform-settings.md), [api-keys-page.md](feature-flows/api-keys-page.md) | | 2026-03-25 | #74 | Auto-assign subscription to new agents (round-robin, rate-limit aware) | [subscription-management.md](feature-flows/subscription-management.md) | | 2026-03-23 | VOICE-001 | Voice Chat — real-time voice conversations with agents via Gemini Live API | [voice-chat.md](feature-flows/voice-chat.md) | | 2026-03-23 | SLACK-002 | Channel adapter abstraction + multi-agent Slack routing | [slack-channel-routing.md](feature-flows/slack-channel-routing.md) | diff --git a/docs/memory/feature-flows/api-keys-page.md b/docs/memory/feature-flows/api-keys-page.md index b4e0cf337..81b7fb424 100644 --- a/docs/memory/feature-flows/api-keys-page.md +++ b/docs/memory/feature-flows/api-keys-page.md @@ -47,14 +47,15 @@ As a Trinity user, I want to manage my MCP API keys from a dedicated page so tha ### Lifecycle (onMounted) ```javascript -// ApiKeys.vue:553-558 +// ApiKeys.vue onMounted(async () => { - await fetchUserRole() // Check if user is admin - await fetchApiKeys() // Load existing keys + await Promise.all([fetchMcpUrl(), fetchUserRole(), fetchApiKeys()]) await ensureDefaultKey() // Auto-create default key for first-time users }) ``` +**MCP URL**: `fetchMcpUrl()` calls `GET /api/settings/mcp-url` to get the admin-configured URL. If set, `mcpServerUrl` computed uses it; otherwise falls back to auto-detect from `window.location.hostname`. (#76) + ### Step 1: Fetch User Role ```javascript // ApiKeys.vue:376-390 diff --git a/docs/memory/feature-flows/platform-settings.md b/docs/memory/feature-flows/platform-settings.md index c6158e751..b99d9f23f 100644 --- a/docs/memory/feature-flows/platform-settings.md +++ b/docs/memory/feature-flows/platform-settings.md @@ -2,7 +2,7 @@ ## Overview -Admin-only page for managing system-wide configuration including API keys (Anthropic, GitHub), Trinity Prompt, email whitelist, SSH access toggle, ops configuration settings, GitHub template configuration (TMPL-001), and default avatar generation (AVATAR-003). +Admin-only page for managing system-wide configuration including API keys (Anthropic, GitHub), Trinity Prompt, email whitelist, SSH access toggle, ops configuration settings, GitHub template configuration (TMPL-001), MCP Server URL (#76), and default avatar generation (AVATAR-003). ## User Stories @@ -14,6 +14,7 @@ Admin-only page for managing system-wide configuration including API keys (Anthr | SET-011 | As an admin, I want to update ops settings so that I can tune context warnings, cost limits, and other thresholds | Implemented | | SET-012 | As an admin, I want to reset ops settings to defaults so that I can restore standard configuration | Implemented | | AVATAR-003 | As an admin, I want to generate default avatars for all agents so that agents without custom avatars get AI-generated ones | Implemented | +| MCP-URL-001 | As an admin, I want to configure the external MCP server URL so that API Keys page shows the correct URL for production deployments | Implemented | ## Entry Points @@ -30,6 +31,9 @@ Admin-only page for managing system-wide configuration including API keys (Anthr - `GET /api/settings/github-templates` - Get GitHub templates config (TMPL-001) - `PUT /api/settings/github-templates` - Set GitHub templates (TMPL-001) - `DELETE /api/settings/github-templates` - Reset templates to defaults (TMPL-001) + - `GET /api/settings/mcp-url` - Get MCP URL config (any auth user) (#76) + - `PUT /api/settings/mcp-url` - Set custom MCP URL (admin-only) (#76) + - `DELETE /api/settings/mcp-url` - Reset MCP URL to auto-detect (admin-only) (#76) - `POST /api/agents/avatars/generate-defaults` - Generate avatars for agents without one (AVATAR-003, see [agent-avatars.md](agent-avatars.md)) --- @@ -47,6 +51,7 @@ Admin-only page for managing system-wide configuration including API keys (Anthr | Trinity Prompt | 224-289 | Textarea for custom agent instructions | | Email Whitelist | 291-390 | Table of whitelisted emails with add/remove | | SSH Access Toggle | 392-430 | Toggle switch for enabling SSH access | +| MCP Server URL | 1089-1149 | Configure external MCP URL with custom/auto-detect badge (#76) | | Default Avatars | 1054-1092 | Generate AI avatars for agents without custom ones (AVATAR-003) | **Key Reactive State** diff --git a/docs/memory/requirements.md b/docs/memory/requirements.md index 3b3ce8085..0f2201a93 100644 --- a/docs/memory/requirements.md +++ b/docs/memory/requirements.md @@ -233,6 +233,14 @@ Trinity is autonomous agent orchestration and infrastructure — sovereign infra - **Key Features**: `list_recent_executions`, `get_execution_result`, `get_agent_activity_summary`; enables async polling pattern for agent-to-agent collaboration beyond 60s MCP timeout - **Spec**: `docs/requirements/MCP_EXECUTION_QUERY_TOOLS.md` +### 7.4 Configurable MCP Server URL (MCP-URL-001) +- **Status**: ✅ Implemented (2026-03-25) +- **Requirement ID**: MCP-URL-001 +- **GitHub Issue**: #76 +- **Description**: Admin-configurable MCP server URL displayed on the API Keys page connection snippets. Replaces hardcoded `http://{hostname}:8080/mcp` which is wrong for production deployments where MCP is proxied through nginx. +- **Key Features**: `GET/PUT/DELETE /api/settings/mcp-url` endpoints, URL validation (requires `http(s)://` and `/mcp` suffix), Settings UI section with save/reset, auto-detect fallback when not configured +- **Flow**: `docs/memory/feature-flows/platform-settings.md` + --- ## 8. Infrastructure diff --git a/src/backend/routers/settings.py b/src/backend/routers/settings.py index 0d5d4636f..037d5c362 100644 --- a/src/backend/routers/settings.py +++ b/src/backend/routers/settings.py @@ -9,6 +9,7 @@ import re import httpx from typing import List, Dict, Any, Optional +from urllib.parse import urlparse from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel @@ -968,6 +969,114 @@ async def delete_github_templates( } +# ============================================================================ +# MCP Server URL Configuration (#76) +# ============================================================================ + +MCP_URL_SETTING_KEY = "mcp_external_url" + + +class McpUrlUpdate(BaseModel): + """Request body for updating the MCP server URL.""" + url: str + + +def _get_default_mcp_url(request: Request) -> str: + """Compute the auto-detected MCP URL from the request hostname.""" + host = request.headers.get("host", "localhost:8080") + hostname = host.split(":")[0] + if hostname in ("localhost", "127.0.0.1"): + return "http://localhost:8080/mcp" + return f"http://{hostname}:8080/mcp" + + +def _validate_mcp_url(url: str) -> str: + """Validate and normalize MCP URL. Returns normalized URL or raises HTTPException.""" + url = url.strip().rstrip("/") + + parsed = urlparse(url) + if parsed.scheme not in ("http", "https"): + raise HTTPException( + status_code=422, + detail="URL must start with http:// or https://" + ) + if not parsed.netloc: + raise HTTPException( + status_code=422, + detail="Invalid URL format" + ) + if not parsed.path.endswith("/mcp"): + raise HTTPException( + status_code=422, + detail="URL must end with /mcp" + ) + + return url + + +@router.get("/mcp-url") +async def get_mcp_url( + request: Request, + current_user: User = Depends(get_current_user) +): + """ + Get the configured MCP server URL. + + Any authenticated user can read this (used by API Keys page). + Returns both the stored custom URL (if any) and the auto-detected default. + """ + stored_url = db.get_setting_value(MCP_URL_SETTING_KEY) + default_url = _get_default_mcp_url(request) + + return { + "url": stored_url, + "default_url": default_url + } + + +@router.put("/mcp-url") +async def update_mcp_url( + body: McpUrlUpdate, + request: Request, + current_user: User = Depends(get_current_user) +): + """ + Set a custom MCP server URL. + + Admin-only. Validates URL format (must be http/https, must end with /mcp). + """ + require_admin(current_user) + + validated_url = _validate_mcp_url(body.url) + db.set_setting(MCP_URL_SETTING_KEY, validated_url) + + return { + "success": True, + "url": validated_url + } + + +@router.delete("/mcp-url") +async def delete_mcp_url( + request: Request, + current_user: User = Depends(get_current_user) +): + """ + Reset MCP server URL to auto-detect. + + Admin-only. Removes the custom URL, reverting to hostname-based auto-detection. + """ + require_admin(current_user) + + deleted = db.delete_setting(MCP_URL_SETTING_KEY) + + return { + "success": True, + "deleted": deleted, + "message": "MCP server URL reset to auto-detect" + } + + # ============================================================================ # Generic Settings CRUD - /{key} catch-all routes # NOTE: These must come AFTER specific routes like /api-keys diff --git a/src/frontend/src/views/ApiKeys.vue b/src/frontend/src/views/ApiKeys.vue index 5362d9a38..635c304ed 100644 --- a/src/frontend/src/views/ApiKeys.vue +++ b/src/frontend/src/views/ApiKeys.vue @@ -309,6 +309,7 @@