> ## Documentation Index
> Fetch the complete documentation index at: https://docs.genesiscomputing.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hosted MCP for Claude Code

> Enable the Genesis remote MCP gateway for Claude Code and coworker laptops when Genesis is deployed behind HTTPS.

## Overview

Genesis exposes a hosted remote MCP endpoint for Claude Code and other MCP clients:

```text theme={null}
https://<your-genesis-host>/mcp/
```

For customer deployments, use this hosted endpoint. Do not ask coworkers to
configure stdio MCP, device-grant login, local token files, or local Python
paths. Genesis also exposes `https://<your-genesis-host>/mcp/v2/` as the
explicit versioned v2 URL and keeps `https://<your-genesis-host>/mcp/v1/` for
the legacy gateway flow.

This page is the admin setup for a hosted deployment. To connect a client (Claude
Code, Claude Desktop, Cursor or Cortex Code), including on the Snowflake Native
App, see [Connect coding agents over MCP](/docs/Getting-Started/Connect-Coding-Agents-MCP).
The deployed **Config → Agent Gateways** page also provides the Genesis Cortex
Code skill download; its install commands are in that client guide.

## Prerequisites

* Genesis is reachable through a public or VPN-accessible HTTPS hostname.
* Genesis authentication is enabled with Auth0 or another OIDC provider.
* The same issuer and audience used by Genesis are accepted by the MCP endpoint.
* Coworkers can open the Genesis hostname from their laptops.
* The OAuth app/client allows one redirect URI. **Which one depends on
  `GENESIS_OAUTH_PROXY_ENABLED`** — see OAuth Callback Setup below.

## Turn on sign-in

A Genesis started with sign-in off (`AUTH_ENABLED=false`, the local default) already
serves MCP: **Config > Agent Gateways** reports it as **Local only**, and
`claude mcp add --transport http genesis-local http://127.0.0.1:<port>/mcp` connects
from the same machine without a credential. Coworkers and remote clients need sign-in.

Sign-in works with any OpenID Connect identity provider, including Okta, Microsoft
Entra ID, Google, Keycloak and Auth0. The settings are deployment configuration, not
an in-app page: set them in the Genesis server's environment (Helm: under
`config.extraEnv`) and restart Genesis.

1. In your identity provider, register an application for Genesis and note its issuer
   URL, signing-keys (JWKS) URL, client ID, and the audience its access tokens carry.
2. Add `https://<your-genesis-host>/oauth/callback` to that application's allowed
   redirect URIs. It is the only callback to register.
3. Set:

```bash theme={null}
GENESIS__AUTH__ENABLED=true
GENESIS__AUTH__PROVIDER=oidc
GENESIS__AUTH__ISSUER=<issuer URL from your identity provider>
GENESIS__AUTH__JWKS_URL=<the issuer's signing-keys (JWKS) URL>
GENESIS__AUTH__AUDIENCE=<audience your access tokens carry>
GENESIS__AUTH__CLIENT_ID=<client ID of the app you registered>
GENESIS_PUBLIC_BASE_URL=https://<your-genesis-host>
GENESIS_OAUTH_PROXY_ENABLED=true
```

4. Restart Genesis and reload **Config > Agent Gateways**: each client tab now shows
   the sign-in commands to give coworkers.

| Identity provider | Issuer (`GENESIS__AUTH__ISSUER`) | Signing keys (`GENESIS__AUTH__JWKS_URL`) |
| - | - | - |
| Okta (custom authorization server) | `https://<domain>.okta.com/oauth2/default` | `https://<domain>.okta.com/oauth2/default/v1/keys` |
| Microsoft Entra ID | `https://login.microsoftonline.com/<tenant-id>/v2.0` | `https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys` |
| Google | `https://accounts.google.com` | `https://www.googleapis.com/oauth2/v3/certs` |
| Keycloak | `https://<host>/realms/<realm>` | `https://<host>/realms/<realm>/protocol/openid-connect/certs` |
| Auth0 | `https://<tenant>.auth0.com/` (keep the trailing slash) | `https://<tenant>.auth0.com/.well-known/jwks.json` |

The `GENESIS__AUTH__*` form overrides any value stored in Genesis configuration; the
older `AUTH_ISSUER`, `AUTH_AUDIENCE`, `AUTH_JWKS_URL` and `AUTH0_CLIENT_ID` variables
apply only while no value is stored.

## OAuth Callback Setup

Claude Code uses a local loopback callback during browser OAuth. This does not
require inbound internet access to the coworker's laptop: after sign-in, the
user's browser redirects to `localhost` on the same machine where Claude Code is
running.

### With the OAuth proxy on (`GENESIS_OAUTH_PROXY_ENABLED` truthy) — recommended

Genesis is the authorization server, and it completes the provider redirect
itself. Register exactly one redirect URI on the OAuth app, for the whole
deployment:

```text theme={null}
https://<your-genesis-host>/oauth/callback
```

That is the only callback the provider ever sees. Genesis holds each client's
own callback, sends the provider its own callback and its own PKCE challenge,
and hands the client a Genesis authorization code on the way back. **No
coworker's laptop and no individual MCP client — Claude Code, Cursor, Claude
Desktop — is ever added to the identity provider.** Loopback callbacks are
accepted on any port without configuration, which is what lets a client bind
whichever port is free (RFC 8252 §7.3).

The fixed callbacks of the major MCP clients that do not use loopback are also
accepted without configuration, matched exactly:

| Client | Callback |
| - | - |
| Claude custom connectors (claude.ai, Claude Desktop) | `https://claude.ai/api/mcp/auth_callback`, `https://claude.com/api/mcp/auth_callback` |
| Cursor | `cursor://anysphere.cursor-mcp/oauth/callback`, `https://www.cursor.com/agents/mcp/oauth/callback` |
| VS Code / GitHub Copilot | `https://vscode.dev/redirect`, `https://insiders.vscode.dev/redirect` |

Any other client whose callback is *not* a loopback address — a hosted console,
for example — must be listed verbatim in `auth.mcp_client_redirect_uris`, or
`/authorize` refuses it. Matching is exact: no prefix, wildcard, case or
trailing-slash tolerance. A callback on an app's own URL scheme (RFC 8252 §7.1,
such as `myapp://callback`) is accepted only when it is one of the known
callbacks above or listed there verbatim; `javascript:`, `data:`, `file:` and
`vbscript:` callbacks and any callback carrying a `#` fragment delimiter are
always refused.

Dynamic client registration (`POST /register`) checks callbacks for structure
only: it accepts any well-formed https or loopback callback, and an app-scheme
callback on the same exact-match terms as above. It decides nothing about where
a code lands, so a registered https callback that is neither built in nor
listed is still refused later, at `/authorize`.

### With the OAuth proxy off (OPRM-style delegation)

`/authorize`, `/oauth/callback` and `/oauth/token` return `404`; clients run
their code flow directly against the upstream issuer, so the provider must allow
each client's own loopback redirect URI:

```text theme={null}
http://localhost:31318/callback
```

Then use the matching fixed callback port in the Claude Code command:

```bash theme={null}
claude mcp add --transport http \
  --client-id <oauth-client-id> \
  --callback-port 31318 \
  genesis \
  https://<your-genesis-host>/mcp \
  -s local
```

Provider notes (proxy **off** only — with the proxy on, register
`https://<your-genesis-host>/oauth/callback` instead of any of these):

* **Azure / Microsoft Entra ID:** add the Genesis web origin as a
  **Single-page application** redirect and add
  `http://localhost:31318/callback` as a **Mobile and desktop applications**
  redirect on the same app registration.
* **Auth0, Okta, and other strict OIDC providers:** add the exact callback URI
  your Claude Code command uses. Keeping the documented `31318` port makes this
  deterministic for admins and coworkers.

## Load Balancer / Ingress Checklist

1. Terminate TLS at the load balancer, ingress, Nginx, Caddy, or equivalent
   edge layer.
2. Forward traffic for `/mcp/`, `/.well-known/*` and `/api/mcp/artifacts/*` to
   the Genesis backend. `/api/mcp/artifacts/<id>?token=…` is the signed,
   single-use download link that tool results hand to an MCP client for a file
   they produced. Its token authenticates it, so it needs no other credential.
   With `GENESIS_OAUTH_PROXY_ENABLED` on, also forward `/authorize`,
   `/register` and `/oauth/*` (`/oauth/callback`, `/oauth/token`): the metadata
   advertises them at the host root. If the rest of the host sits behind a login
   gate such as oauth2-proxy (an nginx `auth-url` annotation), route every one of
   these paths through an ingress without that gate. An MCP client has no browser
   session yet, so a gated path answers with a login redirect instead of Genesis.
   `/register` is the one a gate most often misses. Without it, clients that use
   dynamic client registration cannot connect, while Claude Code still can,
   because it uses a client ID metadata document.
3. Ensure static/frontend catch-all routing does not intercept `/mcp/` or
   `/.well-known/`.
4. Preserve forwarding headers:

```text theme={null}
X-Forwarded-Proto: https
X-Forwarded-Host: <your-genesis-host>
```

5. If using Helm values or environment variables for a public base URL, set it
   to the HTTPS hostname customers use.

### Deployment environment variables

| Variable | Default | Purpose |
| - | - | - |
| `GENESIS_OAUTH_PROXY_ENABLED` | off | When set to `true`, `1`, `yes`, or `on`, Genesis advertises itself as the OAuth authorization server in MCP metadata and acts as one. `GET /authorize` records the client's callback, `state` and PKCE challenge and redirects to the upstream issuer with **Genesis's own** `redirect_uri` (`{base}/oauth/callback`), state and PKCE challenge, plus `audience` and default `scope` injected when missing; `GET /oauth/callback` mints a Genesis authorization code and redirects to the client's callback with the client's original `state`; `POST /oauth/token` redeems that code (verifying the client's PKCE verifier, single use, bound to the callback and client id it was issued for) and exchanges the upstream code, and forwards a refresh-token grant or a code Genesis did not issue unchanged. The identity provider therefore needs one registered callback per deployment rather than one per MCP client. Use this for Anthropic Console **Connect** and other clients that expect the MCP host to be the authorization server. When unset or false, `/authorize`, `/oauth/callback` and `/oauth/token` return `404` and metadata delegates to the upstream issuer (OPRM-style). |
| `auth.mcp_client_redirect_uris` (APP\_CONFIG) | `[]` | Additional non-loopback client callbacks `/authorize` will return an authorization code to, matched verbatim, including app-scheme callbacks such as `myapp://callback`. Only read when the OAuth proxy is on. Loopback callbacks are accepted on any port, and the Claude, Cursor and VS Code callbacks listed above are built in, so those clients need no entry. |
| `GENESIS_MCP_PUBLIC_URL` | — | Preferred explicit HTTPS base URL for MCP OAuth metadata when `X-Forwarded-Proto` / `X-Forwarded-Host` are missing or wrong. |
| `GENESIS_PUBLIC_URL` | — | Fallback public-base URL (checked after `GENESIS_MCP_PUBLIC_URL`). |
| `GENESIS_PUBLIC_BASE_URL` | — | Fallback public-base URL. |
| `GENESIS_EXTERNAL_URL` | — | Fallback public-base URL. |
| `SERVICE_BASE_URL` | — | Fallback public-base URL. |
| `NGROK_BASE_URL`, `FRONTEND_BASE_URL` | — | Additional fallbacks when none of the above are set. |

<Note>
  Browser OAuth consent must reach Genesis on a **publicly resolvable** hostname.
  An Anthropic MCP tunnel hostname alone is not sufficient for Console **Connect**
  sign-in; use your production Genesis URL, a named Cloudflare tunnel, or another
  public HTTPS origin, and set `GENESIS_MCP_PUBLIC_URL` (or preserve forwarding
  headers) so metadata does not fall back to `localhost`.
</Note>

## Verify Metadata

Verify these URLs from a laptop:

```bash theme={null}
curl -i https://<your-genesis-host>/mcp/
curl -s https://<your-genesis-host>/.well-known/oauth-protected-resource
curl -s https://<your-genesis-host>/.well-known/oauth-authorization-server
```

The `/mcp/` request should return `401 Unauthorized` with a
`WWW-Authenticate` header that includes `resource_metadata=`.

If you have repository access, you can run the packaged smoke checker:

```bash theme={null}
python scripts/check_hosted_mcp.py --base-url https://<your-genesis-host>
```

For OIDC-only deployments, `scopes_supported` should be an array such as
`["openid", "profile", "email", "offline_access"]`. Genesis omits the MCP
resource indicator in that mode so identity providers do not receive an
incompatible resource/scope combination.

When `GENESIS_OAUTH_PROXY_ENABLED` is truthy, `authorization_servers` and the
authorization-server metadata `issuer` point at the Genesis public base URL,
with `authorization_endpoint` at `{base}/authorize` and `token_endpoint` at
`{base}/oauth/token`; `{base}/oauth/callback` is the deployment's own callback
and is the single redirect URI the identity provider needs. (It is unrelated to
`{base}/api/oauth/callback`, which serves the in-app connection popup.) JWKS and
userinfo still reference the upstream issuer
(for example Auth0 or Microsoft Entra ID v2.0 endpoints when `auth.issuer` is
`login.microsoftonline.com`).

## Access tokens Genesis cannot verify (`/userinfo` fallback)

With `auth.provider` set to `oidc` or `auth0`, Genesis first verifies a bearer token as a
signed JWT against `auth.issuer`, `auth.audience` and `auth.jwks_url`. When that fails
for any reason — the token is opaque or encrypted (JWE), or its issuer, audience,
signature or expiry does not check out — Genesis sends the token to your identity
provider's userinfo endpoint and, if the provider answers `200` with a `sub`, signs the
request in as that user. This applies to the web API and to the hosted MCP endpoint; the
WebSocket and the `hosted` and `snowflake` providers accept only a verified token.

What this trusts:

* **Your identity provider's userinfo endpoint is the whole check.** Genesis cannot read
  an audience, client or expiry from an opaque token, so it checks none on this path.
  Any access token your provider's userinfo endpoint accepts signs in its user —
  including one issued to another application in the same tenant. User admission
  (SCIM, when it is enabled) still applies. Use a tenant, or an Auth0 tenant, whose
  users are the people who may use this deployment.
* **The endpoint comes from configuration, never from the token:** `auth.userinfo_endpoint`
  when set, else the `userinfo_endpoint` from OIDC discovery on `auth.issuer`, else
  `<auth.issuer>/userinfo`. A token from a different provider is sent to yours, which
  refuses it.
* **Answers are cached per token.** A successful answer is reused for one hour without
  asking the provider again, so a revoked token keeps working until its entry expires;
  a `401` is remembered for 60 seconds. Each cache holds at most 4,096 entries and drops
  the oldest first.

Why it exists: Auth0 issues an opaque or encrypted access token to a client that requests
no `audience`, which is what MCP clients do when `GENESIS_OAUTH_PROXY_ENABLED` is off, and
what the web UI does when `auth.audience` is empty. With the proxy off, Microsoft Entra ID
gives hosted MCP clients Microsoft Graph tokens, which Genesis cannot verify against your
tenant's keys.

To keep your users on verified tokens, set `auth.audience` and turn on
`GENESIS_OAUTH_PROXY_ENABLED`, which adds `audience` to every MCP sign-in. The fallback
stays on for tokens that still cannot be verified.

## MCP v2 threads and request context

The default `/mcp/` v2 gateway creates durable Genesis threads with ids such as
`mcp_<32-char-hex>`. Genesis tags these threads with `mcp_origin` and records
the authenticated MCP caller as owner when OAuth resolves a real user. Reuse the
returned `genesis_thread_id` on every subsequent `thread_continue`, `thread_get`,
`call_tool`, and related v2 action.

MCP clients may send optional header `X-Genesis-Thread-Id` on HTTP MCP requests
for request-scoped gateway context (not applied to `resources/list`).

When a v2 action cannot proceed (for example `thread_create` with an
inaccessible `agent_id`), the `genesis` tool returns structured error content
with MCP `isError` set so clients surface a failed tool call instead of a
silent success.

## Admin Setup

1. Sign in to Genesis as an admin.
2. Open **Config > Agent Gateways**.
3. Find **Genesis MCP Gateway**.
4. Confirm readiness checks.
5. Open the collapsed **Admin setup (once per deployment)** section under any
   client tab and copy the **OAuth redirect URI for admins** into your OAuth
   app/client if it is not already registered.
6. If your role includes Config Panel access, click **Test metadata** and
   **Test auth challenge**.
7. If callers will omit `agent_id`, set **Default MCP agent** and choose the
   **If default is unavailable** policy.
8. Copy the coworker invite text.

The same checks are available from a terminal with:

```bash theme={null}
python scripts/check_hosted_mcp.py --base-url https://<your-genesis-host>
```

**Current** under **Default MCP agent** shows the agent hosted MCP actually uses,
including one set by the deployment setting `GENESIS_MCP_DEFAULT_AGENT_ID`; an
agent chosen on the page overrides that setting.
When a configured default is unavailable to the caller, `require_selection`
asks them to choose from accessible agents, while `first_available` lets
Genesis assign their first accessible agent automatically.

## Coworker Setup

Give coworkers the remote MCP URL and the OAuth client ID:

```text theme={null}
https://<your-genesis-host>/mcp/
```

They add it as a remote MCP server in Claude Code, complete the browser OAuth
sign-in, then run:

```text theme={null}
Use Genesis MCP and call genesis(action="list_agents"), then create a thread with genesis(action="thread_create", initial_prompt="Say hello and confirm which Genesis agent is handling this thread.").
```

Coworkers whose coding agents need to message each other (Claude Code or Cursor
sessions on different laptops) can use rooms over this same endpoint. Rooms need
sign-in turned on. See [MCP Rooms for Coding Agents](/docs/deployments/MCP-Rooms-For-Coding-Agents).

## Troubleshooting

| Symptom | Likely cause | Fix |
| - | - | - |
| `/mcp/` returns `404` | Ingress/frontend routing swallowed the path | Route `/mcp/` to the backend before static catch-all routes. |
| `/mcp/` returns `200` without auth in production | Auth is disabled | Enable Auth0/OIDC before exposing MCP. |
| Claude cannot discover OAuth metadata | Well-known URLs are not reachable or forwarded host/proto is wrong | Verify `/.well-known/oauth-protected-resource` from the laptop and preserve forwarding headers. |
| Microsoft reports `AADSTS50011` | The registered redirect URI does not match what Genesis sent | With `GENESIS_OAUTH_PROXY_ENABLED` on, register `https://<your-genesis-host>/oauth/callback` on the app registration — that is the only callback the provider sees. With the proxy off, add `http://localhost:31318/callback` as a public/native redirect URI instead. |
| `/authorize` returns `400` naming `redirect_uri` | The client's callback is neither loopback, a known MCP client callback, nor allowlisted | Loopback and the known Claude, Cursor and VS Code callbacks need no configuration; add any other callback verbatim to `auth.mcp_client_redirect_uris`. |
| `/oauth/token` returns `{"error":"invalid_grant"}` | The Genesis authorization code was already redeemed, expired, sent without its `client_id`, or its PKCE verifier did not match — or it was issued by a different Genesis process (another replica, or before a restart), in which case Genesis does not recognise it, forwards it upstream, and the refusal comes from the identity provider | Restart the sign-in from the client. A Genesis code is single use and short-lived by design. Codes are held in the serving process, so a deployment running more than one replica must pin the OAuth flow to one of them (sticky sessions). |
| Microsoft reports `AADSTS9010010` | The provider received an incompatible resource/scope combination | Use a Genesis version that publishes OIDC-only scopes as individual values and omits the MCP resource indicator for OIDC-only deployments. |
| OAuth succeeds but tool calls fail | Issuer/audience mismatch | Align Genesis `auth.issuer` and `auth.audience` with the OAuth application used by the MCP client. |
| Anthropic Console **Connect** cannot finish sign-in | Browser cannot reach Genesis `/authorize` on a public hostname, or OAuth proxy is disabled | Use a publicly resolvable Genesis HTTPS URL, set `GENESIS_MCP_PUBLIC_URL` (or correct forwarding headers), and set `GENESIS_OAUTH_PROXY_ENABLED=true` when the client expects Genesis to be the authorization server. |
| URL in the admin panel is `http://` | Proxy did not forward HTTPS scheme | Set `X-Forwarded-Proto=https` at the load balancer, ingress, or reverse proxy. |

## Legacy stdio note

The older stdio MCP sidecar is for developer-only local testing. It is not the
recommended customer setup for hosted deployments.
