# Upstream credentials

Once a server knows who is calling it, a second question remains: which credential it presents to the API behind it. There are two answers, and a server can use both at once for different upstreams.

- **A credential per end user.** The platform acts as an OAuth client of the upstream service and holds an access token and refresh token for each person. A Linear tool call runs as that person in Linear.
- **One credential for the whole server.** An API key held once and used for every caller, supplied as an environment variable on a built server or as an upstream header on a remote one.

Per-user credentials require [user sessions](/docs/ai-control-plane/mcp-gateway/access/user-sessions), since a token has to be stored against a known person. Shared credentials work on any server, including an anonymous public one.

On a remote server, the **Identity** section of the **Settings** tab makes this one choice: **User Identity** for a credential per end user, **Agent Identity** for one shared credential, or **No Identity**. See [Picking an authentication model](/docs/ai-control-plane/mcp-gateway/access#picking-an-authentication-model).

Upstream credentials decide what the server can do upstream, not who may call it. Which people and agents may connect, and which tools they reach, is set separately on the server's [Team Access](/docs/ai-control-plane/mcp-gateway/access/team-access) tab.

## Access requirements

> Viewing a server's upstream credential settings requires the `mcp:read` scope and changing them requires `mcp:write`. Creating and managing remote identity providers requires `org:admin`, which only the default [Admin role](/docs/ai-control-plane/org-admin/roles-and-permissions) holds.

## Credentials per end user

Per-user upstream credentials come from a remote identity provider: an OAuth authorization server such as Linear, GitHub, or Slack that the platform delegates to on the end user's behalf. Tokens are encrypted at rest and refreshed silently.

### Attaching a provider

On a remote server, select **User Identity** and pick the **Identity provider**, as described in [Picking an authentication model](/docs/ai-control-plane/mcp-gateway/access#picking-an-authentication-model).

On a built server, open the **Authentication** tab. On a tunneled server or a gateway, open the **Authentication** section of the **Settings** tab. Find **Remote Identity Providers** and click **Attach Provider**. The sheet asks for two things:

- **Identity Provider**, described as the upstream OAuth authorization server the platform delegates to. Either select an existing provider or add a new one by issuer URL, which is validated by fetching its metadata.
- **Session Client**, described as the OAuth client the platform registers and uses with this provider.

A session client can be created three ways:

| Client type | Use when |
| --- | --- |
| Dynamic Client Registration | The provider supports DCR and can mint a client automatically |
| Client ID Metadata Document | The provider accepts a hosted metadata URL as the client identity, with no registration step |
| Manual | A client ID and secret already exist, created by hand in the provider's console |

Providers and clients live at three tiers: project, organization, and platform. A lookup prefers a project-scoped record, then an organization-scoped one, then the platform default, taking the oldest match within a tier. Platform-tier providers are what makes a common service work with no setup at all.

A server can attach one client per identity provider, and an individual server holds at most one provider. Two clients for the same issuer on the same server is not a supported configuration. A [gateway](/docs/ai-control-plane/mcp-gateway/gateway-endpoints) is the exception: it can hold several providers, one per member that needs its own upstream grant.

> Attaching a new remote identity provider invalidates every existing consent for that server. Consent is recorded against the exact set of providers in force when it was given, so every connected user is prompted again on their next connection. Plan provider changes the same way as a breaking change.

### What the end user sees

Attached providers appear on the consent screen under **Connect required services**, one card per upstream. Each card shows one of three states:

- Connected, with a check mark.
- Expired, prompting a reconnect.
- Not connected, which is simply the absence of any stored credential rather than a state of its own.

Clicking **Connect** or **Reconnect** sends the user through the upstream's own OAuth flow and returns them to the consent screen. Access cannot be granted until at least one service is connected, and an expired card does not satisfy that requirement.

> Expiry is all or nothing. One expired upstream credential fails every tool served through that issuer, not only the tools that need it. Reconnecting restores all of them at once.

### Refresh and revocation

The platform refreshes upstream tokens when they are needed, and can also refresh idle connections in the background depending on the organization's policy. See [Session length and refresh](/docs/ai-control-plane/mcp-gateway/access/session-refresh#automatic-session-refresh).

Stored credentials carry only two statuses, active and expired. Disconnecting an upstream service deletes the stored credential, which puts the card back to not connected and forces a reconnect on the next consent screen. Revoking a person's session in the platform does not revoke the grant at the provider, as described in [Revoking access](/docs/ai-control-plane/mcp-gateway/access/user-sessions#revoking-access). The upstream providers each connection reaches are listed on the server's [Clients and Sessions](/docs/ai-control-plane/mcp-gateway/access/clients-and-sessions) tab.

Provider and client records are managed at the organization level in [Remote identity providers](/docs/ai-control-plane/identity/remote-identity-providers), under **Identity** in the project sidebar. Setup walkthroughs for individual services live in the [guides](/docs/ai-control-plane/guides).

## One credential for the whole server

Shared credentials reach the upstream two different ways depending on the backend.

Built servers use an [environment](/docs/ai-control-plane/mcp-gateway/environments) attached to the MCP server. Remote servers use upstream headers configured on the server itself, encrypted at rest.

### Environment variables on built servers

Each variable declared by a source appears in the server's configuration with one of three modes:

| Mode | Meaning |
| --- | --- |
| System | Value is stored securely and injected by the system |
| User | User must provide this value when connecting |
| Omit | Variable is not included in the configuration |

Only variables set to **System** are pulled from the attached environment. A variable set to **User** becomes part of the install snippet the connecting client has to fill in, which is how a server can be shared without sharing its credentials.

Values are resolved in layers, each overriding the last: the source's own environment, then the environment attached to the server's tool selection, then the environment attached to the MCP server for system variables, then the authenticated caller's selected environment, then `MCP-` prefixed request headers, then any OAuth token injection, and finally environment variables passed in the tool call arguments.

> A public server does serve its System environment variables and configured upstream headers to anonymous callers. That is the design, not an oversight. These are the server's own credentials and are used for every caller, so public means anonymous callers get to spend them, not that the credentials are withheld. A credential that should not be spent by strangers does not belong on a public server.

Two further behaviors apply:

- Every system environment variable that is not consumed as a security scheme is still sent on HTTP tool calls, as a header named after the variable in title case with dashes. `MY_CUSTOM_THING` arrives at the upstream as `My-Custom-Thing`.
- A missing credential is not an error. The request is made without the header and the upstream answers, usually with a 401 that surfaces to the model as a normal tool result.

A related guard: a user-supplied server URL is refused whenever the server has any system environment variable set, which stops a caller from redirecting a credentialed request to a host of their choosing.

### Upstream headers on remote servers

Remote servers are not built from sources and therefore have no environment. A shared credential for the `Authorization` header is set by selecting **Agent Identity** in the **Identity** section of the **Settings** tab and entering the **Agent credential**, described as one credential every caller shares and sent upstream as the `Authorization` header. Other shared headers go in the collapsible **Custom Headers** row of the same section, described as upstream headers sent with every request.

The identity choice owns the `Authorization` header. Agent Identity writes it, and User Identity and No Identity do not allow one to be added under **Custom Headers**. Upstream headers are applied to every proxied request, last in the chain.

A header whose configured value resolves to empty removes the header instead of sending a blank one, which is the way to strip a header the client sent rather than replace it.

### Headers on tunneled servers

Tunneled servers have no **Custom Headers** setting, because the server runs inside the organization's network and holds its own configuration. Requests forwarded through the tunnel still carry two platform headers: `Authorization` with the caller's upstream OAuth credential when a provider is attached, and, on private tunneled servers, `X-Speakeasy-Identity` with a signed JWT describing the authenticated caller. The server can verify that JWT to apply its own policy. See [Signed caller identity](/docs/ai-control-plane/mcp-gateway/tunneled-servers/internal-mcp#signed-caller-identity).
