chore: oppdater opax-mcp server.py og yaml

This commit is contained in:
Chris Christiansen 2026-07-02 16:15:03 +00:00
parent 371965e2f4
commit 2a9138b290
3 changed files with 241 additions and 5 deletions

View File

@ -305,3 +305,110 @@ GCP-event → Jason → Delivery layer → Webhook (CG5a) / E-post (CG5b) / SMS
---
*OSVauco | propane-will-491900-m5 | us-central1 | Autoritativ fra 2026-05-31 | Oppdatert 2026-06-12*
---
## 13. Telefoni & AI-agent Plan — Phase E tillegg
### Konsept: Twilio som kommunikasjonsryggrad
Et Twilio-nummer er grunnmuren for SMS, tale og AI-agent telefoni.
Alt bygger på samme konto og nummer.
---
### Lag 1 — Grunnmur (gjøres NÅ, Phase D)
Allerede planlagt:
- Twilio-konto opprettet
- TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_NUMBER
lagret i Secret Manager
- send_sms fungerer via opax-mcp
---
### Lag 2 — Inngående AI-telefonsvarer (Phase E, Uke 3-4)
#### Konsept
Kunder/kontakter ringer Twilio-nummeret.
Emma-agenten tar samtalen, transkriberer og svarer med tale.
#### Teknisk stack
- Twilio Programmable Voice + Conversation Relay (WebSocket)
- Ollama (emma modell) på osvauco-dev-vm som LLM
- Whisper eller Twilio Speech Recognition for STT
- Twilio TTS (eller Google TTS) for tale tilbake
#### Flyt
Innringer → Twilio → WebSocket → opax-mcp → Ollama (Emma)
Emma svarer → TTS → Twilio → Innringer
#### Hva Emma kan gjøre under samtalen
- Svare på spørsmål om OSVauco-tjenester
- Slå opp kundedata i backend
- Booke møter og sende SMS-bekreftelse
- Si "ett øyeblikk" og hente live data via MCP-tools
- Eskalere til Chris eller Jason med full samtale-kontekst
#### Implementasjon
1. Legg til /voice webhook-endepunkt i opax-mcp
2. Konfigurer Twilio Voice URL til opax-mcp Cloud Run
3. Bygg ConversationRelay WebSocket-handler
4. Koble til Ollama via eksisterende MCP-infrastruktur
5. Definer Emma sin persona og instrukser for telefon
6. Test med internt nummer først
---
### Lag 3 — Utgående AI-anrop (Phase E, Uke 5-6)
#### Konsept
Emma ringer proaktivt — f.eks. kostnadsvarsler,
møtepåminnelser, oppfølging av kunder.
#### Flyt
Trigger (kostnadsspike / planlagt tid)
→ Emma initierer anrop via Twilio Outbound API
→ Samme ConversationRelay-stack som inngående
#### Bruksområder
- "Hei, jeg ringer fra Vauco — dere nærmer dere
budsjettgrensen på GCP denne måneden"
- Møtepåminnelse dagen før
- Oppfølging etter demo
---
### Lag 4 — Multikanal-orkestrering (Phase F)
#### Konsept
Emma bestemmer selv hvilken kanal som passer:
- Lav prioritet → SMS
- Medium → E-post (Gmail API, allerede oppe)
- Høy prioritet → Telefonanrop
- Intern → Google Chat webhook
#### Implementasjon
Legg til kanalvalg-logikk i Emma sin system-prompt:
"Velg kanal basert på hastegrad og brukerpreferanse"
---
### Personas per kanal
| Kanal | Avsender | Persona |
|---|---|---|
| SMS | +47-Twilio-nummer | Nøytral, kort |
| E-post | jason.vauger@vauco.no | Profesjonell |
| Telefon inngående | Emma | Vennlig, hjelpsom |
| Telefon utgående | Emma | Proaktiv, konsis |
---
### Kostnadsestimat Twilio
| Tjeneste | Kostnad |
|---|---|
| Telefonnummer | ~$1/mnd |
| SMS utgående (NO) | ~$0.07/SMS |
| Voice inngående | ~$0.0085/min |
| Voice utgående | ~$0.014/min |
| Trial credits | $15 gratis ved oppstart |

96
opax-mcp.yaml Normal file
View File

@ -0,0 +1,96 @@
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
annotations:
run.googleapis.com/build-enable-automatic-updates: 'false'
run.googleapis.com/build-id: 3deeb5f6-0351-4580-8dab-2dcad7e478dc
run.googleapis.com/build-image-uri: us-central1-docker.pkg.dev/propane-will-491900-m5/cloud-run-source-deploy/opax-mcp
run.googleapis.com/build-name: projects/357036551735/locations/us-central1/builds/3deeb5f6-0351-4580-8dab-2dcad7e478dc
run.googleapis.com/build-source-location: gs://run-sources-propane-will-491900-m5-us-central1/services/opax-mcp/1782754930.916629-ba7acea7e3534f21b658e90dd918d952.zip#1782754931015737
run.googleapis.com/ingress: all
run.googleapis.com/ingress-status: all
run.googleapis.com/maxScale: '20'
run.googleapis.com/operation-id: 931f0012-fb7c-422c-b857-8e75cc298a5f
run.googleapis.com/urls: '["https://opax-mcp-357036551735.us-central1.run.app","https://opax-mcp-zjbqp3prqq-uc.a.run.app"]'
serving.knative.dev/creator: 357036551735-compute@developer.gserviceaccount.com
serving.knative.dev/lastModifier: chris.christiansen@vauco.no
creationTimestamp: '2026-06-10T01:03:48.098765Z'
generation: 57
labels:
cloud.googleapis.com/location: us-central1
name: opax-mcp
namespace: '357036551735'
resourceVersion: AAZVlY5b+Ro
selfLink: /apis/serving.knative.dev/v1/namespaces/357036551735/services/opax-mcp
uid: c95aae34-95db-474f-93dc-f8d89b55b2dd
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/maxScale: '5'
run.googleapis.com/client-name: gcloud
run.googleapis.com/client-version: 570.0.0
run.googleapis.com/startup-cpu-boost: 'true'
labels:
client.knative.dev/nonce: zcdkbqbeua
run.googleapis.com/startupProbeType: Default
spec:
containerConcurrency: 80
containers:
- env:
- name: GOOGLE_CLOUD_PROJECT
value: propane-will-491900-m5
- name: STATIC_BUCKET
value: opax-vauco-static
- name: CLOUD_RUN_SERVICE
value: osvauco-agent
- name: CLOUD_BUILD_TRIGGER_ID
value: 38423976-91ff-4ff4-859e-1f262344c609
- name: OPAX_IAP_CLIENT_ID
value: 357036551735-kq8nt7ld38hfqlcfb3n52ef7tala4meo.apps.googleusercontent.com
- name: GITEA_URL
value: http://34.158.137.245:3000
- name: GITEA_TOKEN
valueFrom:
secretKeyRef:
key: latest
name: gitea-token
- name: GMAIL_DEFAULT_SENDER
value: jason.vauger@vauco.no
- name: GMAIL_ALLOWED_SENDERS
value: jason.vauger@vauco.no,emma.vauger@vauco.no
- name: TWILIO_ACCOUNT_SID
valueFrom:
secretKeyRef:
key: latest
name: TWILIO_ACCOUNT_SID
- name: TWILIO_AUTH_TOKEN
valueFrom:
secretKeyRef:
key: latest
name: TWILIO_AUTH_TOKEN
- name: TWILIO_FROM_NUMBER
valueFrom:
secretKeyRef:
key: latest
name: TWILIO_FROM_NUMBER
image: us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest
name: opax-mcp-1
ports:
- containerPort: 8080
name: http1
resources:
limits:
cpu: '1'
memory: 512Mi
startupProbe:
failureThreshold: 1
periodSeconds: 240
tcpSocket:
port: 8080
timeoutSeconds: 240
serviceAccountName: jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com
timeoutSeconds: 60
traffic:
- latestRevision: true
percent: 100

View File

@ -9,6 +9,7 @@ import httpx
import base64
import google.auth
import google.auth.transport.requests
import google.oauth2.id_token
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from typing import Any, Optional
@ -17,9 +18,10 @@ import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="opax-mcp", version="3.1.0")
app = FastAPI(title="opax-mcp", version="3.2.0")
OPAX_BASE_URL = os.environ.get("OPAX_BASE_URL", "https://opax.vauco.no")
OSVAUCO_AGENT_URL = os.environ.get("OSVAUCO_AGENT_URL", "")
OPAX_IAP_CLIENT_ID = os.environ.get("OPAX_IAP_CLIENT_ID", "")
MCP_SECRET = os.environ.get("MCP_SECRET", "")
GITEA_URL = os.environ.get("GITEA_URL", "http://34.59.131.162:3000")
@ -34,6 +36,7 @@ EMMA_FAST_MODEL = os.environ.get("EMMA_FAST_MODEL", "gemma3:4b")
EMMA_LIGHT_MODEL = os.environ.get("EMMA_LIGHT_MODEL", "qwen2.5:3b")
logger.info(f"OPAX_IAP_CLIENT_ID: {OPAX_IAP_CLIENT_ID[:10]}...")
logger.info(f"OSVAUCO_AGENT_URL: {OSVAUCO_AGENT_URL}")
logger.info(f"OLLAMA_BASE_URL: {OLLAMA_BASE_URL}")
logger.info(f"EMMA_MODEL: {EMMA_MODEL}")
@ -56,7 +59,7 @@ def _verify_auth(request: Request):
# ---------------------------------------------------------------------------
# OPAX helpers
# OPAX helpers (for opax.vauco.no via IAP)
# ---------------------------------------------------------------------------
def _opax_identity_token() -> str:
@ -93,6 +96,36 @@ async def _opax_post(path: str, body: dict) -> Any:
r.raise_for_status()
return r.json()
# ---------------------------------------------------------------------------
# OSVauco Agent helpers (for direct service-to-service calls)
# ---------------------------------------------------------------------------
def _osvauco_agent_identity_token() -> str:
"""Hent identity token for direkte kall til osvauco-agent."""
try:
audience = os.environ.get("OSVAUCO_AGENT_URL")
if not audience:
raise ValueError("OSVAUCO_AGENT_URL environment variable is not set.")
auth_req = google.auth.transport.requests.Request()
token = google.oauth2.id_token.fetch_id_token(auth_req, audience)
if not token:
raise ValueError("Empty token returned from fetch_id_token")
return token
except Exception as e:
logger.error(f"[SERVICE-TO-SERVICE TOKEN ERROR] {type(e).__name__}: {e}")
raise
def _osvauco_agent_headers() -> dict:
return {"Authorization": f"Bearer {_osvauco_agent_identity_token()}", "Content-Type": "application/json"}
async def _osvauco_agent_post(path: str, body: dict) -> Any:
"""Kaller osvauco-agent direkte med service-to-service auth."""
url = f"{os.environ.get('OSVAUCO_AGENT_URL')}{path}"
async with httpx.AsyncClient(timeout=30) as c:
r = await c.post(url, json=body, headers=_osvauco_agent_headers())
r.raise_for_status()
return r.json()
# ---------------------------------------------------------------------------
# Ollama helpers — direkte mot emma-gpu-vm
@ -195,7 +228,7 @@ async def send_email(p):
return await _opax_post("/notify/email", {"to": p.get("to"), "subject": p.get("subject"), "event": p.get("event", "digest")})
async def send_sms(p):
return await _opax_post("/notify/sms", {"to": p.get("to"), "body": p.get("message", p.get("body", "")), "event": p.get("event", "spike"), "tier": p.get("tier", "guard")})
return await _osvauco_agent_post("/notify/sms", {"to": p.get("to"), "body": p.get("message", p.get("body", "")), "event": p.get("event", "spike"), "tier": p.get("tier", "guard")})
async def get_notify_channels(p): return await _opax_get("/notify/channels")
@ -352,7 +385,7 @@ async def mcp_handler(request: Request):
return JSONResponse(_jsonrpc_ok(req_id, {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {"name": "opax-mcp", "version": "3.1.0"},
"serverInfo": {"name": "opax-mcp", "version": "3.2.0"},
}))
if method == "tools/list":
@ -389,4 +422,4 @@ async def mcp_handler(request: Request):
@app.get("/health")
async def health():
return {"status": "ok", "service": "opax-mcp", "version": "3.1.0", "ollama": OLLAMA_BASE_URL, "emma_model": EMMA_MODEL}
return {"status": "ok", "service": "opax-mcp", "version": "3.2.0", "ollama": OLLAMA_BASE_URL, "emma_model": EMMA_MODEL}