Deployment Guide
Who is this for? Engineers deploying self-hosted Rosetta MCP infrastructure for their organization. This is optional — only needed if you specifically require centrally-managed instructions or an IDE with no Rosetta plugin; most teams should use Plugins instead.
When should I read this? When you’ve decided you need self-hosted MCP and want to stand up Rosetta Server (RAGFlow) and Rosetta MCP for your team. For single-user setup, see Quick Start. For client/IDE configuration, see Installation.
[!WARNING] Never expose RAGFlow or Rosetta MCP directly to the internet. Always place an API gateway, reverse proxy, or firewall in front of both services. Both have application-level authentication (RAGFlow: user accounts, OIDC/SSO, API keys; Rosetta MCP: OAuth 2.1), but network-level protection is still required as a defense-in-depth measure.
Deployment Modes
| Mode | RAGFlow | Rosetta MCP | Best for |
|---|---|---|---|
| Hosted | Cloud Kubernetes | Cloud Kubernetes (HTTP transport) | Teams, production |
| Local | Docker Compose | Docker Compose or STDIO | Development, evaluation |
| Limited Internet Access | Docker Compose (offline models) | STDIO (offline instructions) | Regulated environments |
Rosetta MCP connects to RAGFlow as its backend. Deploy RAGFlow first.
Part 1: Rosetta Server (RAGFlow)
RAGFlow provides document storage, embedding, retrieval, and the admin UI. It runs with Elasticsearch, Redis, and MinIO as supporting services, backed by an external MySQL database. For RAGFlow’s role in the system, see MCP Architecture — RAGFlow.
| Upstream docs: Configuration | Helm Chart | Build Docker Image | Admin UI | GitHub |
Docker Compose
For local development and evaluation.
# See the RAGFlow upstream docker-compose:
# https://github.com/infiniflow/ragflow
Set these environment variables in your .env:
MYSQL_HOST=<your-mysql-host>
MYSQL_USER=ragflow
MYSQL_DBNAME=rag_flow
MYSQL_PASSWORD=<generated>
Kubernetes / Helm
Use the official upstream RAGFlow Helm chart:
- Chart source: https://github.com/infiniflow/ragflow/tree/main/helm
- Upstream README: https://github.com/infiniflow/ragflow/blob/main/helm/README.md
- Upstream values: https://github.com/infiniflow/ragflow/blob/main/helm/values.yaml
Install:
git clone https://github.com/infiniflow/ragflow.git
cd ragflow/helm
helm upgrade --install ragflow . \
-n <namespace> \
--create-namespace \
-f values.override.yaml
Maintain your own values.override.yaml outside this repository and keep it aligned with the upstream chart version you deploy.
Upstream chart architecture:
- RAGFlow application (port 80 web, 9380 API, 9381 admin)
- Elasticsearch 8.11.3 (20Gi storage)
- MinIO (5Gi storage, document/object storage)
- Redis/Valkey 8 (5Gi storage, caching and sessions)
- MySQL is external (not deployed by the chart)
Helm Values Reference
Use the upstream chart’s values.yaml as the source of truth. The most important settings to review are:
| Key | Default | Description |
|---|---|---|
ragflow.image.tag |
v0.23.1 |
RAGFlow image version (use latest stable) |
env.DOC_ENGINE |
elasticsearch |
Document engine type |
env.MYSQL_HOST |
(none) | External MySQL host. Required. |
env.MYSQL_DBNAME |
(none) | MySQL database name |
env.MYSQL_USER |
(none) | MySQL user |
mysql.enabled |
false |
Internal MySQL (disabled, use external) |
redis.enabled |
true |
In-cluster Redis |
minio.enabled |
true |
In-cluster MinIO |
ingress.enabled |
true |
Enable ingress |
env.REGISTER_ENABLED |
(unset) | Set "0" to disable self-registration |
Typical environment-specific overrides:
| Setting | Dev | Prod |
|---|---|---|
| Ingress host | <development server URL> |
<production server URL> |
| MySQL database | ragflow-dev |
(base default) |
| MySQL user | ragflow-dev |
(base default) |
Security
Database credentials: Create Kubernetes secrets for all passwords. Never put credentials in values.yaml or commit them into this repository.
kubectl create secret generic ragflow-mysql \
--from-literal=MYSQL_PASSWORD="$(openssl rand -base64 32)" -n <namespace>
kubectl create secret generic ragflow-elastic \
--from-literal=ELASTIC_PASSWORD="$(openssl rand -base64 32)" -n <namespace>
kubectl create secret generic ragflow-redis \
--from-literal=REDIS_PASSWORD="$(openssl rand -base64 32)" -n <namespace>
kubectl create secret generic ragflow-minio \
--from-literal=MINIO_PASSWORD="$(openssl rand -base64 32)" -n <namespace>
For production, use External Secrets Operator (ESO) or HashiCorp Vault instead of manual secrets.
OIDC (SSO): RAGFlow supports OpenID Connect via local.service_conf.yaml. Store the config as a Kubernetes secret and mount it:
kubectl create secret generic ragflow-service-conf \
--from-file=local.service_conf.yaml -n <namespace>
Mount path: /app/conf/local.service_conf.yaml. See RAGFlow OIDC docs for the full schema.
Default models: Configure default LLM providers in local.service_conf.yaml so every user profile gets working models out of the box. This eliminates per-user model setup.
# Inside local.service_conf.yaml (mounted as a secret)
user_default_llm:
factory: "OpenAI"
api_key: "<OPENAI_API_KEY>"
base_url: "https://api.openai.com/v1"
default_models:
chat_model:
name: "claude-sonnet-4-5-20250929"
factory: "Anthropic"
api_key: "<ANTHROPIC_API_KEY>"
embedding_model:
name: "embedding-001"
factory: "Gemini"
api_key: "<GOOGLE_API_KEY>"
image2text_model:
name: "gemini-3-pro-preview"
factory: "Gemini"
api_key: "<GOOGLE_API_KEY>"
rerank_model:
name: "rerank-english-v3.0"
factory: "Cohere"
api_key: "<COHERE_API_KEY>"
asr_model:
name: "whisper-1"
factory: "OpenAI"
All model API keys are stored in the same ragflow-service-conf secret alongside OIDC config. Supported model types: chat, embedding, image-to-text, rerank, and ASR (speech-to-text).
Network: Place RAGFlow behind an API gateway or ingress controller with TLS termination. Disable self-registration (REGISTER_ENABLED=0) in all shared environments.
Verification
kubectl get pods -n <namespace> # All pods Running
kubectl get ingress -n <namespace> # Hosts and addresses assigned
Check the admin panel at https://<your-host>/admin. Verify document upload and retrieval work.
Part 2: Rosetta MCP
Rosetta MCP is the guiding layer between IDEs and the knowledge base. It exposes guardrails and common best practices, and provides a menu of instructions for coding agents to select on demand — delivering only what is needed. Manages sessions via Redis and handles OAuth authentication. See MCP Architecture for capabilities.
Docker Compose
For local development. Starts Rosetta MCP and Redis.
# docker-compose.yml (src/rosetta-mcp-server/)
services:
rosetta-mcp:
image: us-central1-docker.pkg.dev/.../rosetta-mcp:<tag>
ports: ["8000:8000"]
environment:
ROSETTA_API_KEY: "${ROSETTA_API_KEY}"
ROSETTA_SERVER_URL: "${ROSETTA_SERVER_URL}"
REDIS_URL: "redis://:${REDIS_PASSWORD}@redis:6379/2"
ROSETTA_TRANSPORT: http
ROSETTA_MODE: "${ROSETTA_MODE:-HARD}"
depends_on: [redis]
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
Required env vars: ROSETTA_API_KEY, ROSETTA_SERVER_URL, REDIS_PASSWORD.
Kubernetes / Helm
The repository ships the chart at src/helm-charts/rosetta-mcp-server/. It deploys rosetta-mcp in HTTP transport with ClusterIP Service, optional Ingress, optional HorizontalPodAutoscaler, and optional External Secrets Operator wiring. Use this path when you want a shared MCP endpoint behind your ingress and identity provider (see MCP Architecture — Rosetta MCP Server).
Docker image: griddynamics/rosetta-mcp (repository).
Prerequisites
- Kubernetes 1.25+ (for
autoscaling/v2HPA) - Rosetta Server (for example RAGFlow) reachable from the cluster (Part 1)
- A Kubernetes
Secret(or ExternalSecret) containing at leastROSETTA_API_KEY - If using the chart’s ESO toggle (
eso.enabled), External Secrets Operator and aClusterSecretStore/SecretStorethat matcheso.secretStoreRef - TLS for production: Base
values.yamlleavesingress.tlscommented out, so Ingress may expose the MCP endpoint over plain HTTP until you configure TLS. Before any production deployment, enable HTTPS at the Ingress (uncomment and populateingress.tlswith your certificateSecretand hostnames) or terminate TLS at your API gateway / load balancer in front of the chart. OAuth expects a public HTTPSROSETTA_OAUTH_BASE_URL; see also the guide banner on defense in depth.
Install
From OCI (Docker Hub)
After the workflow Publish Rosetta MCP Helm chart runs on main, the chart is published as a Helm OCI artifact under the Grid Dynamics Docker Hub organization (see Chart name and version).
helm registry login registry-1.docker.io
export CHART_OCI="oci://registry-1.docker.io/griddynamics/rosetta-mcp-helm-chart"
helm pull "${CHART_OCI}" --version "$(grep -E '^version:' src/helm-charts/rosetta-mcp-server/Chart.yaml | awk '{print $2}')"
Or install without pulling first (pin --version to the chart release you verified):
helm install rosetta-mcp "${CHART_OCI}" \
--version 0.2.0 \
-f my-values.yaml
Values must set ROSETTA_SERVER_URL, image pull credentials if your registry requires them, TLS (for production), and secrets such as ROSETTA_API_KEY. Chart name in OCI is rosetta-mcp-helm-chart (Helm package basename), while workload labels use nameOverride: rosetta-mcp from values.
From a local clone
helm dependency update # chart has no subcharts today; optional
helm install rosetta-mcp ./src/helm-charts/rosetta-mcp-server \
-f ./src/helm-charts/rosetta-mcp-server/values.yaml \
-f ./src/helm-charts/rosetta-mcp-server/values-prod.example.yaml # adapt or use your overlay
Required chart configuration
- Image —
image.repositorydefaults togriddynamics/rosetta-mcp; setimage.tagor rely on the chart defaulting the tag toappVersionin the Deployment template. - Rosetta backend — Set
env.varssoROSETTA_SERVER_URLresolves to Rosetta Server (in-cluster DNS or ingress URL). - API key — Supply
ROSETTA_API_KEYviaenv.secrets(secretKeyRef). Create the Kubernetes Secret first or useesoto sync it. -
Ingress — Set
ingress.host. The chart defaults to Traefik and also supports nginx as an alternative.Traefik (default) — rate limiting is enabled out of the box:
ingress: className: traefik host: rosetta.example.com traefik: rateLimit: average: 100 burst: 200 period: "1s"The chart creates a Traefik
Middlewarecustom resource (<fullname>-rate-limit) in the release namespace and wires it into the Ingress annotation automatically (requires Traefik CRDs to be installed in the cluster).To reference additional external middlewares (e.g. a platform-wide chain managed outside this chart), list them in
ingress.traefik.middlewares:ingress: traefik: middlewares: - "traefik-my-chain@kubernetescrd" rateLimit: average: 100 burst: 200 period: "1s"External middlewares are rendered first in the annotation, followed by the per-release rate limit.
nginx (alternative) — set
className: nginxand replace thetraefikblock with nginx annotations:ingress: className: nginx host: rosetta.example.com annotations: nginx.ingress.kubernetes.io/proxy-body-size: "10m" nginx.ingress.kubernetes.io/limit-rps: "100" nginx.ingress.kubernetes.io/limit-burst-multiplier: "2" - TLS (production) — Enable encrypted client traffic before production use. Uncomment and complete the
ingress.tlsblock in your overlay so Ingress terminates HTTPS with a TLSSecret(or terminate TLS upstream and align hostnames). HTTP-only defaults are unsuitable for production; OAuth and user trust depend on HTTPS.
Full environment-variable semantics for OAuth, Redis, analytics, and modes are the same as the application runtime; see rosetta-mcp-server — Configuration.
Example values overlays
The chart directory includes overlays you can copy and customize outside the repo:
src/helm-charts/rosetta-mcp-server/values-dev.example.yamlsrc/helm-charts/rosetta-mcp-server/values-prod.example.yaml
helm upgrade --install rosetta-mcp ./src/helm-charts/rosetta-mcp-server \
-f ./src/helm-charts/rosetta-mcp-server/values.yaml \
-f my-prod.yaml
Chart layout
| Path | Purpose |
|---|---|
templates/deployment.yaml |
Deployment, env, resources |
templates/service.yaml |
ClusterIP and session affinity |
templates/ingress.yaml |
Optional Ingress |
templates/traefik-middlewares.yaml |
Traefik Middleware CRDs (when className: traefik) |
templates/hpa.yaml |
Optional HPA |
templates/poddisruptionbudget.yaml |
Optional PDB (when replicaCount > 1) |
templates/external-secret.yaml |
Optional ExternalSecret (eso.*) |
templates/serviceaccount.yaml |
ServiceAccount |
tests/ |
helm-unittest test suites |
Deployment characteristics & defaults
The chart applies these behaviors by default unless you override values:
| Topic | Detail |
|---|---|
| Resources | Requests: CPU 250m, memory 512Mi. Limits: CPU 1000m, memory 1Gi. |
| Replicas & HPA | With autoscaling.enabled: false, replicaCount is honored. With HPA enabled, the Deployment initially uses autoscaling.minReplicas. When HPA is on, scaling is commonly 2–10 replicas (~70% CPU / ~80% memory targets). |
| Rolling updates | RollingUpdate with maxSurge: 1, maxUnavailable: 0. |
| Security context | Non-root UID/GID/fsGroup 1000, capabilities dropped, allowPrivilegeEscalation: false. |
Session affinity: The Service defaults to ClientIP with ~1h stickiness — important for Streamable HTTP when you run multiple replicas:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600
If ClientIP is insufficient behind certain proxies or high fan-out IPs, try ingress affinity on the MCP session header:
# nginx Ingress (when using className: nginx)
nginx.ingress.kubernetes.io/upstream-hash-by: "$http_mcp_session_id"
Start with chart defaults (ClientIP); introduce hash-by only when justified. Use shared Redis (REDIS_URL + secrets) for multi-replica OAuth and sessions (Redis below).
Helm Values Reference
Base keys in src/helm-charts/rosetta-mcp-server/values.yaml:
| Key | Default | Description |
|---|---|---|
ports |
[8000] |
Container/service port |
image.repository |
griddynamics/rosetta-mcp |
Container image; Deployment uses image.tag or falls back to Chart appVersion when tag is unset |
replicaCount |
1 |
Static replicas when HPA disabled |
autoscaling.enabled |
false |
HPA toggle |
ingress.enabled |
true |
Ingress resource |
ingress.className |
traefik |
Ingress controller (traefik or nginx) |
ingress.traefik.middlewares |
[] |
External Traefik middleware references |
ingress.traefik.rateLimit.average |
100 |
Rate limit — requests per period |
ingress.traefik.rateLimit.burst |
200 |
Rate limit — max burst size |
ingress.traefik.rateLimit.period |
"1s" |
Rate limit — period |
ingress.tls |
Commented in base values.yaml; enable for production |
HTTPS termination at Ingress |
service.sessionAffinity |
ClientIP |
Pod stickiness |
eso.enabled |
false |
External Secrets Operator sync |
Representative environment-specific overrides:
| Setting | Dev | Prod |
|---|---|---|
| Ingress host | rosetta-dev.example.com |
rosetta.example.com |
ROSETTA_SERVER_URL |
http://ragflow-dev.<cluster-domain>:80 |
http://ragflow-prod.<cluster-domain>:80 |
VERSION |
r3 |
r3 |
ROSETTA_MODE |
SOFT |
SOFT |
ROSETTA_OAUTH_MODE |
oauth |
oauth |
ROSETTA_OAUTH_REQUIRED_SCOPES |
offline_access |
offline_access |
ROSETTA_OAUTH_VALID_SCOPES |
(empty) | (empty) |
REDIS_DB |
2 |
2 |
FASTMCP_ENABLE_RICH_LOGGING |
false |
false |
FASTMCP_LOG_LEVEL |
DEBUG |
(unset) |
ROSETTA_DEBUG (alias: IMS_DEBUG) |
1 |
(unset) |
| Keycloak realm | <dev-realm> |
<prod-realm> |
| Service account | <dev-service-account> |
<prod-service-account> |
| ESO secret source | <dev-secret-source> |
<prod-secret-source> |
Redis
Rosetta MCP uses Redis for OAuth token storage, session state, and execution_controller execution plans. Configure the connection via REDIS_URL (provided as a secret) and REDIS_DB (logical database index, e.g. 2).
Database isolation: Use REDIS_DB to select a logical database within a shared Redis instance. Set different values per environment to avoid key collisions.
Data invalidation: Redis data is not schema-versioned and requires no migration scripts. However, existing sessions and stored plans become inaccessible after:
- Rotating
FERNET_KEY(tokens can no longer be decrypted) - Changing
REDIS_DB(data is in a different logical database) - Flushing the Redis database (
redis-cli -n <db> FLUSHDB)
Users must re-authenticate and in-flight plans are lost after any of these. Plan key rotations accordingly in production.
Security
OAuth 2.1: Rosetta MCP authenticates IDE clients via OAuthProxy, which bridges any OAuth provider with MCP’s authentication flow. Three modes are available, controlled by ROSETTA_OAUTH_MODE:
oauth mode (default) — generic OAuth 2.0 with token introspection:
| Env var | Purpose |
|---|---|
ROSETTA_OAUTH_AUTHORIZATION_ENDPOINT |
Upstream IdP authorization URL |
ROSETTA_OAUTH_TOKEN_ENDPOINT |
Upstream IdP token URL |
ROSETTA_OAUTH_INTROSPECTION_ENDPOINT |
Upstream IdP introspection URL |
ROSETTA_OAUTH_CLIENT_ID |
Pre-registered IdP client ID |
ROSETTA_OAUTH_CLIENT_SECRET |
IdP client secret |
ROSETTA_OAUTH_BASE_URL |
Public URL of Rosetta MCP |
ROSETTA_JWT_SIGNING_KEY |
Secret for signing FastMCP JWTs |
ROSETTA_OAUTH_REVOCATION_ENDPOINT |
(optional) Token revocation URL |
ROSETTA_OAUTH_CALLBACK_PATH |
(optional) Callback path (default: /auth/callback) |
ROSETTA_OAUTH_REQUIRED_SCOPES |
(optional) Scopes required on tokens |
ROSETTA_OAUTH_VALID_SCOPES |
(optional) Scopes advertised in .well-known |
ROSETTA_OAUTH_EXTRA_SCOPES |
(optional) Scopes forwarded to IdP authorize endpoint |
The offline_access scope is critical: it enables refresh tokens so users authenticate once instead of re-authenticating daily. Your OAuth provider must be configured to allow this scope.
oidc mode — OIDC auto-discovery with local JWT verification:
| Env var | Purpose |
|---|---|
ROSETTA_OAUTH_OIDC_CONFIG_URL |
IdP OIDC discovery URL (.well-known/openid-configuration) |
ROSETTA_OAUTH_CLIENT_ID |
Pre-registered IdP client ID |
ROSETTA_OAUTH_CLIENT_SECRET |
IdP client secret |
ROSETTA_OAUTH_BASE_URL |
Public URL of Rosetta MCP |
ROSETTA_JWT_SIGNING_KEY |
Secret for signing FastMCP JWTs |
ROSETTA_OAUTH_CALLBACK_PATH |
(optional) Callback path (default: /auth/callback) |
ROSETTA_OAUTH_REQUIRED_SCOPES |
(optional) Scopes required on tokens |
ROSETTA_OAUTH_EXTRA_SCOPES |
(optional) Scopes forwarded to IdP authorize endpoint |
github mode — GitHub OAuth with API-based token verification:
| Env var | Purpose |
|---|---|
ROSETTA_OAUTH_CLIENT_ID |
GitHub OAuth App Client ID |
ROSETTA_OAUTH_CLIENT_SECRET |
GitHub OAuth App Client Secret |
ROSETTA_OAUTH_BASE_URL |
Public URL of Rosetta MCP (HTTPS required) |
ROSETTA_JWT_SIGNING_KEY |
Secret for signing FastMCP JWTs |
ROSETTA_OAUTH_CALLBACK_PATH |
(optional) Callback path (default: /auth/callback) |
ROSETTA_OAUTH_REQUIRED_SCOPES |
(optional) Required GitHub scopes (default: user) |
GitHub endpoints are hardcoded. Create a GitHub OAuth App at github.com/settings/developers and set the callback URL to <ROSETTA_OAUTH_BASE_URL>/auth/callback.
Secrets (use ESO, Vault, or manual Kubernetes secrets):
| Secret | Purpose |
|---|---|
ROSETTA_API_KEY |
RAGFlow API key. Must belong to the owner of all datasets. |
REDIS_PASSWORD |
Redis session store access |
ROSETTA_OAUTH_CLIENT_ID |
OAuth client identifier |
ROSETTA_OAUTH_CLIENT_SECRET |
OAuth client secret |
ROSETTA_JWT_SIGNING_KEY |
JWT token signing. Required for production. |
FERNET_KEY |
Encrypts OAuth tokens stored in Redis. Required for production. |
POSTHOG_API_KEY |
Usage analytics — your PostHog project API key (opt-in, disabled by default) |
POSTHOG_HOST |
PostHog instance URL, e.g. https://posthog.internal.company.com (defaults to https://eu.i.posthog.com) |
ROSETTA_MODE:
HARD: Adds more content to context, stricter requirements. Allows to not usemcp-files-mode.md.SOFT: Lighter context, more agent independence, must be used withmcp-files-mode.md.
Network: Place Rosetta MCP behind an API gateway or ingress controller with TLS. The OAuth flow requires HTTPS.
Verification
# Check pods
kubectl get pods -n <namespace>
# Test the MCP endpoint
curl -s https://<your-host>/mcp | head
Connect an IDE client using Installation and run: “What can you do, Rosetta?”
Environment Management
Rosetta uses a three-file values hierarchy per component:
values.yaml # Base configuration (shared)
values-dev.yaml # Dev environment overrides
values-prod.yaml # Prod environment overrides
Key differences between environments:
- Namespaces:
<dev-namespace>vs<prod-namespace> - Ingress hosts:
rosetta-dev.example.comvsrosetta.example.com - Keycloak realms:
<dev-realm>(dev) vs<prod-realm>(prod) - Secret sources: environment-specific bundles in your secret manager
- Service accounts: environment-specific Kubernetes service accounts
- Debug flags:
ROSETTA_DEBUG=1in dev only
CI/CD flow (merge to main auto-deploys to dev):
- Build and publish image (
rosetta-mcp-build.yaml): Triggers on push to main when MCP source or Dockerfile changes. Runs typecheck, builds Docker image, pushes to container registry. - Publish instructions (
publish-instructions.yml): Triggers on push to main when instruction content changes. Syncs instructions to Rosetta Server so dev always has the latest rules, agents, and skills. - GitOps sync: Your CD tool (Argo, Flux, or similar) detects new image tags and applies rolling updates to the dev environment.
Production deploys require a manual image tag bump in values-prod.yaml.
Rosetta Images, Packages
- https://pypi.org/project/rosetta-mcp/ (retiring)
- https://pypi.org/project/rosetta-mcp/
- https://pypi.org/project/rosetta-cli/
- https://hub.docker.com/repository/docker/griddynamics/rosetta-mcp/general
Related Docs
- Quick Start - single-user setup (zero to working in minutes)
- Installation - client/IDE configuration, all transport modes
- Architecture - system structure and component relationships
- Troubleshooting - common issues and fixes
- Overview - mental model and terminology