fix(mcp): enforce app-level auth, fix trigger_build SDK, and scaffold docs
This commit is contained in:
parent
2a6890bb22
commit
4f2cf3e28b
8
docs/osvx/mcp/CONNECTOR_REGISTRY.md
Normal file
8
docs/osvx/mcp/CONNECTOR_REGISTRY.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# OSVx MCP Connector Registry
|
||||
|
||||
This document is the single source of truth for all clients and connectors that interact with the OSVx MCP.
|
||||
|
||||
| Connector Name | Owner | Target Endpoint URL | Authentication Method | Status |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| Perplexity | OSVx Team | UNKNOWN | UNKNOWN | Misaligned |
|
||||
|
||||
9
docs/osvx/mcp/DEPLOYMENT_MATRIX.md
Normal file
9
docs/osvx/mcp/DEPLOYMENT_MATRIX.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# OSVx MCP Deployment Matrix
|
||||
|
||||
This document tracks which service revisions and container images are deployed to each environment.
|
||||
|
||||
| Environment | Service Name | Cloud Run Revision | Image Digest | Deployed At | Git Commit |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| Production | `opax-mcp` | `opax-mcp-00134-jb7` | `sha256:8adbd3f7...` | 2026-09-01 | UNKNOWN |
|
||||
| Staging | `osvx-mcp-dev`| `osvx-mcp-dev-00001-n24`| `sha256:b5ac84f4...` | 2026-09-01 | UNKNOWN |
|
||||
|
||||
10
docs/osvx/mcp/README.md
Normal file
10
docs/osvx/mcp/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# OSVx Model Context Protocol (MCP) Architecture
|
||||
|
||||
This directory contains the canonical documentation for the OSVx MCP, which governs how AI agents and connectors interact with the OSVx platform.
|
||||
|
||||
## Key Documents
|
||||
|
||||
- [Connector Registry](./CONNECTOR_REGISTRY.md): A list of all clients that connect to the MCP.
|
||||
- [Tool Catalog](./TOOL_CATALOG.md): The master list of all available tools.
|
||||
- [Tool Policy](./TOOL_POLICY.md): Defines the security and exposure policies for tools.
|
||||
- [Deployment Matrix](./DEPLOYMENT_MATRIX.md): Tracks which service versions are deployed to which environments.
|
||||
50
docs/osvx/mcp/TOOL_CATALOG.md
Normal file
50
docs/osvx/mcp/TOOL_CATALOG.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# OSVx MCP Tool Catalog
|
||||
|
||||
This document lists all 32 tools available in the current production deployment of the OSVx MCP.
|
||||
|
||||
## Billing Tools
|
||||
- `get_billing_summary`
|
||||
- `get_billing_forecast`
|
||||
- `get_billing_credits`
|
||||
- `get_billing_anomalies`
|
||||
- `get_billing_history`
|
||||
- `get_billing_budget`
|
||||
- `get_telemetry`
|
||||
- `set_billing_budget`
|
||||
|
||||
## Onboarding Tools
|
||||
- `create_invite`
|
||||
|
||||
## Notification Tools
|
||||
- `send_webhook`
|
||||
- `send_email`
|
||||
- `send_sms`
|
||||
- `get_notify_channels`
|
||||
|
||||
## AI Agent Tools
|
||||
- `run_jason`
|
||||
- `run_emma`
|
||||
- `list_emma_models`
|
||||
|
||||
## System & Ops Tools
|
||||
- `get_health`
|
||||
- `get_build_status`
|
||||
- `trigger_build`
|
||||
- `get_state`
|
||||
- `list_customers`
|
||||
- `run_terminal`
|
||||
|
||||
## VCS Tools (Gitea)
|
||||
- `list_commits`
|
||||
- `get_file`
|
||||
|
||||
## Workspace Tools (Google Workspace)
|
||||
- `create_email_alias`
|
||||
- `list_user_aliases`
|
||||
- `delete_email_alias`
|
||||
- `send_email_as`
|
||||
- `get_workspace_user`
|
||||
- `list_workspace_users`
|
||||
- `get_emails`
|
||||
- `list_calendar_events`
|
||||
|
||||
24
docs/osvx/mcp/TOOL_POLICY.md
Normal file
24
docs/osvx/mcp/TOOL_POLICY.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# OSVx MCP Tool Policy
|
||||
|
||||
This document defines the security classification and exposure tiers for all MCP tools.
|
||||
|
||||
## Tier 1: Safe Reads
|
||||
- **Description:** Public, cacheable, low-risk data.
|
||||
- **Tools:** `list_emma_models`
|
||||
|
||||
## Tier 2: Scoped Reads
|
||||
- **Description:** Requires authentication; reads sensitive but non-modifying data.
|
||||
- **Tools:** `get_billing_history`, `get_file`, `get_emails`
|
||||
|
||||
## Tier 3: Compute Tools
|
||||
- **Description:** Requires authentication; triggers non-state-changing computations.
|
||||
- **Tools:** `run_jason`, `run_emma`
|
||||
|
||||
## Tier 4: Confirmed Actions
|
||||
- **Description:** State-changing actions that should require user confirmation (HITL).
|
||||
- **Tools:** `create_issue`, `set_billing_budget`
|
||||
|
||||
## Tier 5: Restricted Admin
|
||||
- **Description:** High-risk administrative tools that must be protected by strict, server-side authorization.
|
||||
- **Tools:** `run_terminal`, `trigger_build`, `delete_email_alias`
|
||||
|
||||
|
|
@ -5,3 +5,4 @@ google-auth>=2.29.0
|
|||
google-api-python-client>=2.120.0
|
||||
google-cloud-firestore>=2.16.0
|
||||
google-cloud-secret-manager>=2.18.0
|
||||
google-cloud-build>=2.0.0
|
||||
|
|
|
|||
|
|
@ -68,10 +68,20 @@ logger.info(f"OLLAMA_BASE_URL: {OLLAMA_BASE_URL}")
|
|||
async def _verify_auth(request: Request) -> None:
|
||||
secret = os.getenv("MCP_SECRET")
|
||||
if not secret:
|
||||
logger.error("MCP_SECRET is not configured on the server.")
|
||||
raise HTTPException(status_code=500, detail="MCP_SECRET not configured")
|
||||
auth = request.headers.get("Authorization", "")
|
||||
if not auth.startswith("Bearer ") or auth[7:] != secret:
|
||||
raise HTTPException(status_code=401, detail="Invalid or missing token")
|
||||
|
||||
# Perplexity sends 'api-key', others might send 'X-MCP-Secret' or 'Authorization: Bearer ...'
|
||||
token = request.headers.get("api-key")
|
||||
if not token:
|
||||
token = request.headers.get("X-MCP-Secret")
|
||||
if not token:
|
||||
auth_header = request.headers.get("Authorization", "")
|
||||
if auth_header.startswith("Bearer "):
|
||||
token = auth_header[7:]
|
||||
|
||||
if not token or token != secret:
|
||||
raise HTTPException(status_code=401, detail="Invalid or missing API key")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -228,31 +238,33 @@ async def list_calendar_events(p: dict) -> dict:
|
|||
return {"events": formatted_events}
|
||||
|
||||
async def trigger_build(p: dict) -> dict:
|
||||
"""Trigger en Cloud Build manuelt ved å sende kildekode fra Gitea."""
|
||||
import subprocess
|
||||
repo = p.get("repo", "OSVauco")
|
||||
"""Triggers a Cloud Build manually by its trigger ID."""
|
||||
from google.cloud.devtools import cloudbuild_v1
|
||||
|
||||
project_id = GOOGLE_CLOUD_PROJECT
|
||||
trigger_id = p.get("trigger_id")
|
||||
branch = p.get("branch", "main")
|
||||
config = p.get("config", "cloudbuild.mcp.yaml")
|
||||
|
||||
result = subprocess.run([
|
||||
"gcloud", "builds", "submit",
|
||||
f"/home/chris_christiansen/OSVauco",
|
||||
f"--config={config}",
|
||||
f"--project={GOOGLE_CLOUD_PROJECT}"
|
||||
], capture_output=True, text=True)
|
||||
substitutions = p.get("substitutions", {})
|
||||
|
||||
if result.returncode != 0:
|
||||
error_message = result.stderr.strip()
|
||||
logger.error(f"Cloud Build trigger failed: {error_message}")
|
||||
return {"status": "error", "message": error_message}
|
||||
if not trigger_id:
|
||||
raise ValueError("Missing required parameter: 'trigger_id'")
|
||||
|
||||
build_id = "Not found in output"
|
||||
for line in result.stdout.split('\n'):
|
||||
if "ID:" in line:
|
||||
build_id = line.split("ID:")[1].strip()
|
||||
break
|
||||
|
||||
return {"status": "triggered", "build_id": build_id}
|
||||
try:
|
||||
client = cloudbuild_v1.CloudBuildClient()
|
||||
source = cloudbuild_v1.RepoSource(branch_name=branch, substitutions=substitutions)
|
||||
|
||||
response = client.run_build_trigger(
|
||||
project_id=project_id,
|
||||
trigger_id=trigger_id,
|
||||
source=source,
|
||||
)
|
||||
|
||||
build_id = response.metadata.build.id
|
||||
logger.info(f"Successfully triggered Cloud Build. Build ID: {build_id}")
|
||||
return {"status": "success", "build_id": build_id}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to trigger Cloud Build: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to trigger Cloud Build: {e}")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ollama helpers — direkte mot emma-gpu-vm
|
||||
|
|
@ -463,9 +475,9 @@ def _jsonrpc_err(req_id, code, message):
|
|||
|
||||
@app.post("/")
|
||||
async def mcp_handler(request: Request):
|
||||
# ... (beholdt uendret)
|
||||
_verify_auth(request)
|
||||
try: body = await request.json()
|
||||
await _verify_auth(request)
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception: return JSONResponse(_jsonrpc_err(None, -32700, "Parse error"), status_code=400)
|
||||
method, req_id, params = body.get("method", ""), body.get("id"), body.get("params", {})
|
||||
if method == "initialize":
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user