diff --git a/.env.example b/.env.example index 9981c6c..be75612 100644 --- a/.env.example +++ b/.env.example @@ -181,6 +181,12 @@ BYPASS_MODEL_ACCESS_CONTROL=true WEBUI_NAME=UltiAI AI_ASSISTANT_PUBLIC_PATH=/ai ULTIMAIL_MCP_URL=http://ultimail-mcp:3100 +# Public MCP endpoint (nginx → ultid → ultimail-mcp): /api/v1/ai/mcp +# OpenWebUI uses AI_GATEWAY_API_KEY + forwarded X-OpenWebUI-User-* headers for per-user tokens. +# Ultimail MCP auto-enabled for all users (see deploy/openwebui/docker-compose.openwebui.yml): +# DEFAULT_MODEL_PARAMS={"function_calling":"native"} +# DEFAULT_MODEL_METADATA={"toolIds":["server:mcp:ultimail"],...} +# TOOL_SERVER_CONNECTIONS with config.access_grants public read # OpenWebUI utilise POSTGRES_USER/POSTGRES_PASSWORD (base openwebui créée dans init-db.sh) # ----------------------------------------------------------------------------- diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 37faef0..ce601eb 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -6,6 +6,8 @@ services: - "80:80" volumes: - ./nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro + - ./nginx/openwebui-subfilters.conf:/etc/nginx/openwebui-subfilters.conf:ro + - ./nginx/patches/QGuclOcQ.js:/etc/nginx/patches/QGuclOcQ.js:ro environment: DOMAIN: ${DOMAIN:-localhost} MAIL_FRONTEND_UPSTREAM: ${MAIL_FRONTEND_UPSTREAM:-host.docker.internal:3004} diff --git a/deploy/nginx/default.conf.template b/deploy/nginx/default.conf.template index 908c9e0..d1b2fb9 100644 --- a/deploy/nginx/default.conf.template +++ b/deploy/nginx/default.conf.template @@ -180,6 +180,40 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } + location ^~ /api/v1/ai/mcp { + resolver 127.0.0.11 valid=10s ipv6=off; + set $ultid_upstream ultid:8080; + proxy_hide_header Access-Control-Allow-Origin; + proxy_hide_header Access-Control-Allow-Methods; + proxy_hide_header Access-Control-Allow-Headers; + proxy_hide_header Access-Control-Expose-Headers; + proxy_hide_header Access-Control-Max-Age; + proxy_hide_header Access-Control-Allow-Credentials; + proxy_hide_header Vary; + add_header Access-Control-Allow-Origin $cors_allow_origin always; + add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Mcp-Session-Id, Origin, X-Requested-With, X-Trace-Id, X-Ulti-Token, X-OpenWebUI-User-Email, X-OpenWebUI-User-Id, X-OpenWebUI-User-Name, X-OpenWebUI-User-Role" always; + add_header Access-Control-Expose-Headers "Mcp-Session-Id, X-Trace-Id" always; + add_header Access-Control-Max-Age 300 always; + add_header Vary Origin always; + proxy_pass http://$ultid_upstream; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Cookie $http_cookie; + proxy_set_header Authorization $http_authorization; + proxy_set_header X-Ulti-Token $http_x_ulti_token; + proxy_set_header X-OpenWebUI-User-Email $http_x_openwebui_user_email; + proxy_set_header X-OpenWebUI-User-Id $http_x_openwebui_user_id; + proxy_set_header X-OpenWebUI-User-Name $http_x_openwebui_user_name; + proxy_set_header X-OpenWebUI-User-Role $http_x_openwebui_user_role; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } location ^~ /api/v1/ai/ { resolver 127.0.0.11 valid=10s ipv6=off; set $ultid_upstream ultid:8080; @@ -192,16 +226,27 @@ server { proxy_hide_header Vary; add_header Access-Control-Allow-Origin $cors_allow_origin always; add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always; - add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always; - add_header Access-Control-Expose-Headers "X-Trace-Id" always; + add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Mcp-Session-Id, Origin, X-Requested-With, X-Trace-Id, X-Ulti-Token, X-OpenWebUI-User-Email, X-OpenWebUI-User-Id, X-OpenWebUI-User-Name, X-OpenWebUI-User-Role" always; + add_header Access-Control-Expose-Headers "Mcp-Session-Id, X-Trace-Id" always; add_header Access-Control-Max-Age 300 always; add_header Vary Origin always; proxy_pass http://$ultid_upstream; proxy_http_version 1.1; proxy_set_header Host $host; + proxy_set_header Cookie $http_cookie; + proxy_set_header Authorization $http_authorization; + proxy_set_header X-Ulti-Token $http_x_ulti_token; + proxy_set_header X-OpenWebUI-User-Email $http_x_openwebui_user_email; + proxy_set_header X-OpenWebUI-User-Id $http_x_openwebui_user_id; + proxy_set_header X-OpenWebUI-User-Name $http_x_openwebui_user_name; + proxy_set_header X-OpenWebUI-User-Role $http_x_openwebui_user_role; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; } location ^~ /api/v1/users/me { resolver 127.0.0.11 valid=10s ipv6=off; @@ -446,6 +491,27 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } + + # OpenWebUI socket.io under /ai prefix (SvelteKit base /ai — avoid embed-auth on /ai/) + location ^~ /ai/ws/socket.io { + resolver 127.0.0.11 valid=10s ipv6=off; + set $openwebui_upstream openwebui:8080; + rewrite ^/ai/ws/socket.io(.*)$ /ws/socket.io$1 break; + proxy_pass http://$openwebui_upstream; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_cache off; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } @@ -648,6 +714,13 @@ server { # SvelteKit base path — without this, /ai/ routes 404 (base "" expects site root) sub_filter 'base: ""' 'base: "/ai"'; sub_filter "base: ''" 'base: "/ai"'; + # Keep t="" so socket.io uses default namespace (/), not /ai — see openwebui-subfilters.conf + include /etc/nginx/openwebui-subfilters.conf; + sub_filter 'location.href="/auth"' 'location.href="/ai/auth"'; + sub_filter "location.href='/auth'" "location.href='/ai/auth'"; + sub_filter '"/auth?"' '"/ai/auth?"'; + sub_filter '"/auth"' '"/ai/auth"'; + sub_filter '