1import"../chunks/Bzak7iHL.js";import{h as ce,s as r}from"../chunks/B2hhroed.js";import{ar as N,aq as g,e as ne,au as e,at as O,as as t,av as de,ax as s,aw as o,ai as pe,k as Y}from"../chunks/vP97m1hC.js";import{s as ue}from"../chunks/DuCc3f1Z.js";import{e as ve,i as me}from"../chunks/BMIIZ-SA.js";import{h as _e}from"../chunks/BGxF4ZAg.js";import{C as a}from"../chunks/C2-nan7q.js";import{C as he}from"../chunks/cNe8A89h.js";import{D as ke}from"../chunks/CW-5LRlf.js";var Ee=O('<meta name="description"/> <link rel="canonical"/> <meta property="og:title"/> <meta property="og:description"/> <meta property="og:url"/> <meta property="og:type" content="article"/> <meta property="og:image" content="https://workspacemcp.com/og-cover.png"/> <meta name="twitter:card" content="summary_large_image"/> <meta name="twitter:image" content="https://workspacemcp.com/og-cover.png"/> <!>',1),ge=O('<a class="svelte-15ttudk"> </a>'),Oe=O(`<header class="docs-hero svelte-15ttudk"><div class="container svelte-15ttudk"><nav class="crumbs svelte-15ttudk" aria-label="Breadcrumb"><a href="/" class="svelte-15ttudk">Home</a><span>/</span><a href="/guides" class="svelte-15ttudk">Guides</a><span>/</span> <span aria-current="page">Enterprise Deployment</span></nav> <div class="hero-grid svelte-15ttudk"><div><p class="eyebrow svelte-15ttudk">SRE / Platform</p> <h1 class="svelte-15ttudk"></h1> <p class="lede svelte-15ttudk">Select one supported identity and state model before writing deployment 2 manifests. These modes have different compatibility and scaling constraints; 3 their environment variables are not interchangeable.</p> <!></div> <aside class="prerequisites svelte-15ttudk"><strong class="svelte-15ttudk">Before deploying</strong> <ul class="svelte-15ttudk"><li>Choose one mode from the compatibility table.</li> <li>Register the final public callback URL in Google Cloud.</li> <li>Build optional storage dependencies into the image.</li> <li>Pin the image to a version or digest.</li></ul></aside></div></div></header> <div class="docs-body svelte-15ttudk"><div class="container article-layout svelte-15ttudk"><aside class="article-toc svelte-15ttudk" aria-label="Page contents"><p class="svelte-15ttudk">On this page</p> <!> <!></aside> <main class="article-body svelte-15ttudk"><section id="choose-mode" class="svelte-15ttudk"><h2 class="svelte-15ttudk">Choose a Deployment Mode</h2> <p class="svelte-15ttudk">Workspace MCP supports several authentication paths, but they do not all 4 compose. Treat this table as a configuration boundary.</p> <div class="table-wrap svelte-15ttudk"><table class="svelte-15ttudk"><thead><tr><th class="svelte-15ttudk">Mode</th><th class="svelte-15ttudk">Identity and credentials</th><th class="svelte-15ttudk">Scaling constraint</th></tr></thead><tbody><tr><td class="svelte-15ttudk"><strong>OAuth 2.1 stateless</strong></td><td class="svelte-15ttudk">The MCP server authenticates clients and holds encrypted upstream Google tokens in Valkey.</td><td class="svelte-15ttudk">Supports multiple replicas when Valkey is available.</td></tr><tr><td class="svelte-15ttudk"><strong>Trusted gateway</strong></td><td class="svelte-15ttudk">A proxy supplies a verified assertion; Google grants use the local credential directory.</td><td class="svelte-15ttudk">Use one replica; credential sharing alone does not make transport sessions portable.</td></tr><tr><td class="svelte-15ttudk"><strong>Gateway + DWD</strong></td><td class="svelte-15ttudk">The gateway identifies the user and a delegated service account impersonates that user.</td><td class="svelte-15ttudk">No grant store; transport sessions still require affinity unless separately validated.</td></tr><tr><td class="svelte-15ttudk"><strong>Stateful OAuth 2.1 + GCS</strong></td><td class="svelte-15ttudk">Per-user Google grants are stored in GCS with optional CMEK enforcement.</td><td class="svelte-15ttudk">GCS shares grants, not MCP transport sessions; replicas are not interchangeable by default.</td></tr></tbody></table></div> <div class="callout warning svelte-15ttudk"><strong class="svelte-15ttudk">Incompatible settings</strong> <code class="svelte-15ttudk">TRUST_GATEWAY_IDENTITY=true</code> cannot be combined with <code class="svelte-15ttudk">MCP_ENABLE_OAUTH21=true</code>. Stateless mode requires OAuth 2.1. 5 Service-account mode also cannot be combined with OAuth 2.1.</div></section> <section id="oauth-stateless" class="svelte-15ttudk"><h2 class="svelte-15ttudk">OAuth 2.1 Stateless Deployment</h2> <p class="svelte-15ttudk">This is the supported multi-replica mode. Set the public URL explicitly, 6 enable stateless HTTP, and use Valkey for FastMCP client registrations and 7 encrypted upstream token state. Do not configure GCS: stateless mode bypasses it.</p> <h3 class="svelte-15ttudk">Build the required dependency</h3> <p class="svelte-15ttudk">The current repository Dockerfile does not install the optional Valkey 8 dependency. Add it to your image build; otherwise the server warns and falls 9 back to its default storage.</p> <!> <h3 class="svelte-15ttudk">Valkey</h3> <!> <p class="svelte-15ttudk">Keep <code class="svelte-15ttudk">FASTMCP_SERVER_AUTH_GOOGLE_JWT_SIGNING_KEY</code> stable across 10 replicas and deployments. It participates in encrypting stored OAuth state.</p> <h3 class="svelte-15ttudk">Kubernetes</h3> <!> <!> <div class="callout info svelte-15ttudk"><strong class="svelte-15ttudk">Probe scope</strong> <code class="svelte-15ttudk">/health</code> confirms that the HTTP process is responding. It does 11 not test Valkey, GCS, Google APIs, or existing credentials. Monitor those 12 dependencies separately and alert on Valkey fallback warnings.</div> <h3 class="svelte-15ttudk">Cloud Run</h3> <p class="svelte-15ttudk">The public URL must match the Google OAuth callback registration. This example 13 allows requests through the Cloud Run IAM layer because Workspace MCP performs 14 protocol-level OAuth 2.1 authentication; ingress still restricts internet 15 traffic to the load balancer. If policy requires IAP, use trusted-gateway mode.</p> <!> <p class="svelte-15ttudk">Configure VPC routing, firewall rules, TLS, and the connector for your Valkey 16 service. Configure an HTTP startup probe for <code class="svelte-15ttudk">/health</code> explicitly in 17 Cloud Run YAML or Terraform if you want that path used.</p></section> <section id="gateway" class="svelte-15ttudk"><h2 class="svelte-15ttudk">Trusted-Gateway Identity</h2> <p class="svelte-15ttudk">In this mode an MCP-aware proxy authenticates each request and injects a signed
18 JWT assertion. Workspace MCP verifies the assertion before selecting that user's 19 Google grant.</p> <!> <ul class="svelte-15ttudk"><li class="svelte-15ttudk">Leave <code class="svelte-15ttudk">MCP_ENABLE_OAUTH21</code> and <code class="svelte-15ttudk">WORKSPACE_MCP_STATELESS_MODE</code> unset.</li> <li class="svelte-15ttudk">Set <code class="svelte-15ttudk">WORKSPACE_MCP_HOST=0.0.0.0</code> in a container; non-OAuth HTTP otherwise defaults to loopback.</li> <li class="svelte-15ttudk">Expose the service only through the proxy, which must overwrite the configured identity header.</li> <li class="svelte-15ttudk">Persist <code class="svelte-15ttudk">WORKSPACE_MCP_CREDENTIALS_DIR</code>; the current GCS backend cannot be used in gateway mode.</li></ul> <p class="svelte-15ttudk">See the <a href="/docs/deployment/gateway-identity" class="svelte-15ttudk">trusted-gateway reference</a> for provider-specific headers and algorithms.</p></section> <section id="dwd" class="svelte-15ttudk"><h2 class="svelte-15ttudk">Domain-Wide Delegation</h2> <p class="svelte-15ttudk">DWD removes per-user Google consent, but it does not authenticate callers to the 20 MCP endpoint. For user-facing HTTP deployments, combine it with trusted-gateway 21 identity so the verified gateway principal becomes the impersonation subject.</p> <!> <ul class="svelte-15ttudk"><li class="svelte-15ttudk">Set exactly one of <code class="svelte-15ttudk">GOOGLE_SERVICE_ACCOUNT_KEY_FILE</code> and <code class="svelte-15ttudk">GOOGLE_SERVICE_ACCOUNT_KEY_JSON</code>.</li> <li class="svelte-15ttudk"><code class="svelte-15ttudk">USER_GOOGLE_EMAIL</code> is required at startup and supplies the fallback subject.</li> <li class="svelte-15ttudk">Set <code class="svelte-15ttudk">DWD_ALLOWED_DOMAINS</code> to restrict request-selected subjects.</li> <li class="svelte-15ttudk">Authorize only the required Workspace scopes in the Admin console.</li></ul></section> <section id="gcs" class="svelte-15ttudk"><h2 class="svelte-15ttudk">GCS Credential Storage</h2> <p class="svelte-15ttudk">GCS stores per-user Google grants for stateful OAuth 2.1 deployments and uses 22 generation preconditions to reject conflicting writes. It is not the state store 23 for stateless OAuth proxy tokens or MCP transport sessions.</p> <!> <ul class="svelte-15ttudk"><li class="svelte-15ttudk">Build the image with <code class="svelte-15ttudk">--extra gcs</code>; the repository Dockerfile does not include it.</li> <li class="svelte-15ttudk">Grant the runtime identity <code class="svelte-15ttudk">roles/storage.objectUser</code> on the bucket.</li> <li class="svelte-15ttudk">For CMEK enforcement, also grant <code class="svelte-15ttudk">storage.buckets.get</code>, for example through <code class="svelte-15ttudk">roles/storage.bucketViewer</code>.</li> <li class="svelte-15ttudk">The startup check verifies the bucket's default KMS key; KMS permissions and rotation remain infrastructure responsibilities.</li></ul></section> <section id="observability" class="svelte-15ttudk"><h2 class="svelte-15ttudk">Operations and Observability</h2> <h3 class="svelte-15ttudk">Tracing</h3> <p class="svelte-15ttudk">The repository image includes the OpenTelemetry extra. Tracing remains off until 24 an OTLP endpoint is configured. Both gRPC and HTTP/protobuf are supported.</p> <!> <h3 class="svelte-15ttudk">Logging</h3> <p class="svelte-15ttudk">Console logs are human-readable text written to stdout or stderr; they are not 25 JSON. Set <code class="svelte-15ttudk">WORKSPACE_MCP_LOG_LEVEL</code> to <code class="svelte-15ttudk">CRITICAL</code>, <code class="svelte-15ttudk">ERROR</code>, <code class="svelte-15ttudk">WARNING</code>, <code class="svelte-15ttudk">INFO</code> (the default), or <code class="svelte-15ttudk">DEBUG</code>; invalid values fall back to <code class="svelte-15ttudk">INFO</code>. Stateless mode 26 disables file logging. Otherwise, <code class="svelte-15ttudk">WORKSPACE_MCP_LOG_DIR</code> enables a detailed local debug log. Treat <code class="svelte-15ttudk">DEBUG</code> output, detailed file logs, and optional <code class="svelte-15ttudk">user.email</code> span attributes as sensitive because they can include 27 user text.</p> <h3 class="svelte-15ttudk">Health and failure detection</h3> <p class="svelte-15ttudk"><code class="svelte-15ttudk">GET /health</code> returns process metadata only. Pair it with checks for 28 Valkey connectivity, OAuth failures, Google API error rates, latency, and storage 29 fallback warnings.</p></section> <section id="permissions" class="svelte-15ttudk"><h2 class="svelte-15ttudk">Permission Controls</h2> <!> <p class="svelte-15ttudk">Permission levels determine requested Google scopes and eligible tools. A tier 30 can narrow tools within those services. The disabled list wins over both, but an 31 unmatched name only warns; verify startup output after changing it.</p></section> <section id="security" class="svelte-15ttudk"><h2 class="svelte-15ttudk">Release Checklist</h2> <ul class="checklist svelte-15ttudk"><li class="svelte-15ttudk">The auth mode matches the compatibility table, and incompatible variables are absent.</li> <li class="svelte-15ttudk">
31The pinned image contains every optional backend dependency configured at runtime.</li> <li class="svelte-15ttudk"><code class="svelte-15ttudk">WORKSPACE_EXTERNAL_URL</code> and the Google callback use the final HTTPS hostname.</li> <li class="svelte-15ttudk">OAuth secrets, signing keys, Valkey credentials, and service-account material come from a secret manager.</li> <li class="svelte-15ttudk">Valkey survives instance replacement and is reachable from every stateless replica.</li> <li class="svelte-15ttudk">Gateway deployments cannot bypass the proxy, and incoming identity headers are overwritten.</li> <li class="svelte-15ttudk">DWD domains and Admin-console scopes are restricted to intended users and services.</li> <li class="svelte-15ttudk">Permission and blocklist settings have been checked against registered tools.</li> <li class="svelte-15ttudk">Dependency monitoring supplements the process-only <code class="svelte-15ttudk">/health</code> endpoint.</li> <li class="svelte-15ttudk">Restore, rotation, rollout, and rollback procedures have been exercised.</li></ul></section></main></div></div>`,1);function Le(x){const c="https://workspacemcp.com/guides/enterprise-deployment",y="Enterprise Deployment Guide",d="Choose and operate a supported Workspace MCP deployment mode, with tested configuration boundaries for OAuth 2.1, trusted gateways, domain-wide delegation, distributed state, and observability.",H=`# The repository Dockerfile includes disk and OpenTelemetry extras. 32# Add the Valkey extra before using the multi-replica examples below: 33RUN uv sync --frozen --no-dev --extra disk --extra valkey --extra otel`,V=`apiVersion: apps/v1 34kind: Deployment 35metadata: 36 name: workspace-mcp 37spec: 38 replicas: 3 39 selector: 40 matchLabels: 41 app: workspace-mcp 42 template: 43 metadata: 44 labels: 45 app: workspace-mcp 46 spec: 47 serviceAccountName: workspace-mcp 48 containers: 49 - name: workspace-mcp 50 # Use an immutable tag or digest. The image must include the valkey extra. 51 image: your-registry/workspace-mcp:1.25.0 52 ports: 53 - name: http 54 containerPort: 8000 55 env: 56 - name: MCP_ENABLE_OAUTH21 57 value: "true" 58 - name: WORKSPACE_EXTERNAL_URL 59 value: "https://mcp.corp.example.com" 60 - name: WORKSPACE_MCP_STATELESS_MODE 61 value: "true" 62 - name: WORKSPACE_MCP_OAUTH_PROXY_STORAGE_BACKEND 63 value: "valkey" 64 - name: WORKSPACE_MCP_OAUTH_PROXY_VALKEY_HOST 65 value: "valkey.internal.corp.example.com" 66 - name: WORKSPACE_MCP_OAUTH_PROXY_VALKEY_PORT 67 value: "6380" 68 - name: WORKSPACE_MCP_OAUTH_PROXY_VALKEY_USE_TLS 69 value: "true" 70 envFrom: 71 - secretRef: 72 # GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET, 73 # FASTMCP_SERVER_AUTH_GOOGLE_JWT_SIGNING_KEY, and Valkey password. 74 name: workspace-mcp-secrets 75 startupProbe: 76 httpGet: 77 path: /health 78 port: http 79 periodSeconds: 5 80 failureThreshold: 12 81 livenessProbe: 82 httpGet: 83 path: /health 84 port: http 85 periodSeconds: 30 86 readinessProbe: 87 httpGet: 88 path: /health 89 port: http 90 periodSeconds: 10 91 resources: 92 requests: 93 cpu: 250m 94 memory: 256Mi 95 limits: 96 cpu: "1" 97 memory: 512Mi`,X=`apiVersion: v1 98kind: Service 99metadata: 100 name: workspace-mcp 101spec: 102 type: ClusterIP 103 selector: 104 app: workspace-mcp 105 ports: 106 - name: http 107 port: 8000 108 targetPort: http`,B=`gcloud run deploy workspace-mcp \\ 109 --image=your-registry/workspace-mcp:1.25.0 \\ 110 --port=8000 \\ 111 --set-env-vars="MCP_ENABLE_OAUTH21=true" \\ 112 --set-env-vars="WORKSPACE_EXTERNAL_URL=https://mcp.corp.example.com" \\ 113 --set-env-vars="WORKSPACE_MCP_STATELESS_MODE=true" \\ 114 --set-env-vars="WORKSPACE_MCP_OAUTH_PROXY_STORAGE_BACKEND=valkey" \\ 115 --set-env-vars="WORKSPACE_MCP_OAUTH_PROXY_VALKEY_HOST=10.0.0.5" \\ 116 --set-env-vars="WORKSPACE_MCP_OAUTH_PROXY_VALKEY_PORT=6379" \\ 117 --set-secrets="GOOGLE_OAUTH_CLIENT_ID=mcp-client-id:latest" \\ 118 --set-secrets="GOOGLE_OAUTH_CLIENT_SECRET=mcp-client-secret:latest" \\ 119 --set-secrets="FASTMCP_SERVER_AUTH_GOOGLE_JWT_SIGNING_KEY=mcp-jwt-key:latest" \\ 120 --set-secrets="WORKSPACE_MCP_OAUTH_PROXY_VALKEY_PASSWORD=valkey-password:latest" \\ 121 --service-account=workspace-mcp@your-project.iam.gserviceaccount.com \\ 122 --vpc-connector=workspace-mcp \\ 123 --min-instances=1 \\ 124 --max-instances=10 \\ 125 --ingress=internal-and-cloud-load-balancing \\ 126 --allow-unauthenticated`,j=`# Trusted-gateway mode: do not enable OAuth 2.1 or stateless mode. 127TRUST_GATEWAY_IDENTITY=true 128WORKSPACE_MCP_HOST=0.0.0.0 129WORKSPACE_EXTERNAL_URL=https://mcp.corp.example.com 130GATEWAY_IDENTITY_JWKS_URL=https://authenticate.corp.example.com/.well-known/pomerium/jwks.json 131GATEWAY_IDENTITY_AUDIENCE=workspace-mcp.corp.example.com 132 133# Cloudflare Access overrides 134# GATEWAY_IDENTITY_HEADER=cf-access-jwt-assertion 135# GATEWAY_IDENTITY_ALGORITHMS=RS256`,q=`# DWD can be combined with trusted-gateway identity. 136GOOGLE_SERVICE_ACCOUNT_KEY_FILE=/secrets/service-account.json 137[email protected] 138DWD_ALLOWED_DOMAINS=corp.example.com,subsidiary.example.com`,F=`# GCS is supported for stateful OAuth 2.1 deployments. 139# Do not combine it with WORKSPACE_MCP_STATELESS_MODE=true. 140MCP_ENABLE_OAUTH21=true 141WORKSPACE_MCP_CREDENTIAL_STORE_BACKEND=gcs 142WORKSPACE_MCP_GCS_BUCKET=workspace-mcp-credentials 143WORKSPACE_MCP_GCS_PREFIX=production 144WORKSPACE_MCP_GCS_REQUIRE_CMEK=true`,J=`WORKSPACE_MCP_OAUTH_PROXY_STORAGE_BACKEND=valkey 145WORKSPACE_MCP_OAUTH_PROXY_VALKEY_HOST=valkey.internal.corp.example.com 146WORKSPACE_MCP_OAUTH_PROXY_VALKEY_PORT=6380 147WORKSPACE_MCP_OAUTH_PROXY_VALKEY_USE_TLS=true 148WORKSPACE_MCP_OAUTH_PROXY_VALKEY_USERNAME=workspace-mcp 149WORKSPACE_MCP_OAUTH_PROXY_VALKEY_PASSWORD=<from-secret-manager> 150 151# Optional for higher-latency remote or TLS endpoints 152WORKSPACE_MCP_OAUTH_PROXY_VALKEY_REQUEST_TIMEOUT_MS=5000 153WORKSPACE_MCP_OAUTH_PROXY_VALKEY_CONNECTION_TIMEOUT_MS=10000`,z=`OTEL_EXPORTER_OTLP_ENDPOINT=https://
153otel-collector.corp.example.com:4317 154OTEL_EXPORTER_OTLP_PROTOCOL=grpc 155OTEL_SERVICE_NAME=workspace-mcp-prod 156OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production 157 158# Optional PII: adds user.email to active spans 159# WORKSPACE_MCP_OTEL_USER_EMAIL=true`,$=`# Limit scopes and exposed tools by service. 160WORKSPACE_MCP_PERMISSIONS="gmail:send drive:full calendar:full" 161 162# Optionally narrow that selection to core-tier tools. 163WORKSPACE_MCP_TOOL_TIER=core 164 165# Names must match registered MCP tools exactly. 166WORKSPACE_MCP_DISABLED_TOOLS=send_gmail_message,create_drive_file`,Q=[{id:"choose-mode",label:"Choose a mode"},{id:"oauth-stateless",label:"OAuth 2.1 stateless"},{id:"gateway",label:"Trusted gateway"},{id:"dwd",label:"Domain-wide delegation"},{id:"gcs",label:"GCS credentials"},{id:"observability",label:"Operations"},{id:"permissions",label:"Permissions"},{id:"security",label:"Release checklist"}];var A=Oe();ce("15ttudk",E=>{var i=Ee(),l=N(i);r(l,"content",d);var n=e(l,2);r(n,"href",c);var D=e(n,2);r(D,"content",y);var W=e(D,2);r(W,"content",d);var U=e(W,2);r(U,"content",c);var ie=e(U,10);_e(ie,()=>`<script type="application/ld+json">${JSON.stringify({"@context":"https://schema.org","@graph":[{"@type":"BreadcrumbList",itemListElement:[{"@type":"ListItem",position:1,name:"Home",item:"https://workspacemcp.com"},{"@type":"ListItem",position:2,name:"Guides",item:"https://workspacemcp.com/guides"},{"@type":"ListItem",position:3,name:"Enterprise Deployment",item:c}]},{"@type":"TechArticle",headline:y,description:d,proficiencyLevel:"Expert",author:{"@type":"Organization",name:"Workspace MCP"},mainEntityOfPage:c}]})}<\/script>`),ne(()=>{de.title="Enterprise Deployment Guide - Workspace MCP"}),g(E,i)});var p=N(A),C=t(p),T=e(t(C),2),P=t(T),S=e(t(P),2);S.textContent="Enterprise Deployment Guide";var Z=e(S,4);ke(Z,{}),s(P),o(2),s(T),s(C),s(p);var R=e(p,2),b=t(R),u=t(b),f=e(t(u),2);ve(f,1,()=>Q,me,(E,i)=>{var l=ge(),n=t(l,!0);s(l),pe(()=>{r(l,"href",`#${Y(i).id??""}`),ue(n,Y(i).label)}),g(E,l)});var ee=e(f,2);he(ee,{placement:"enterprise-deployment-sidebar"}),s(u);var L=e(u,2),v=e(t(L),2),w=e(t(v),8);a(w,{code:H});var G=e(w,4);a(G,{code:J});var M=e(G,6);a(M,{code:V});var I=e(M,2);a(I,{code:X});var te=e(I,8);a(te,{code:B}),o(2),s(v);var m=e(v,2),se=e(t(m),4);a(se,{code:j}),o(4),s(m);var _=e(m,2),ae=e(t(_),4);a(ae,{code:q}),o(2),s(_);var h=e(_,2),oe=e(t(h),4);a(oe,{code:F}),o(2),s(h);var k=e(h,2),le=e(t(k),6);a(le,{code:z}),o(8),s(k);var K=e(k,2),re=e(t(K),2);a(re,{code:$}),o(2),s(K),o(2),s(L),s(b),s(R),g(x,A)}export{Le as component};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.