Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

OAuth connections

OAuth 2.0 is available for REST connections and MCP connections using HTTP or SSE. The runtime acquires and refreshes bearer tokens during requests. Tokens and client secrets stay in encrypted credential storage. The connection's files declare the authentication method and, for a custom application, its settings; the approved configuration revision is pinned by the binding.

An authorized account belongs to the organization. It is shared only with connections that an administrator explicitly binds to that account. Each agent connection has one account assignment shared by its deployments and cloud-backed local development. Stdio MCP and external connection drivers do not support this OAuth transport.

Declare OAuth in the connection

Set auth in spec.json:

{
  "baseUrl": "https://gitlab.com/api/v4",
  "auth": { "type": "oauth2" },
  "test": { "path": "user" }
}

The application configuration comes from the first of:

  1. auth.provider, a managed application id on the platform. Managed applications require the platform; the CLI cannot use them with --local.
  2. oauth.json next to spec.json, for a custom application.
  3. Discovery from the server URL, for an MCP connection whose server publishes OAuth resource metadata.

An unbound REST OAuth connection with none of these is listed as needing configuration. Create oauth.json through the Application editor or configure the application explicitly through the CLI. An existing approved OAuth binding takes precedence over file-declared authentication, including a bearer token in an older connection specification.

oauth.json

oauth.json holds the application settings and never the client secret; a clientSecret key fails validation. label defaults to the connection name and resourceOrigins to the origin of baseUrl or url. The fields are grantType, issuer, discoveryUrl or the explicit authorizationUrl, tokenUrl, and revocationUrl, clientId, tokenAuthMethod, scopes, resourceOrigins, authorizationParameters, tokenParameters, accountUrl, and accountIdPath. The recipes below are complete files.

Omit clientId only for a public authorization-code client whose discovery document lists a registration endpoint: that file also needs discoveryUrl, "grantType": "authorization_code", and "tokenAuthMethod": "none". The broker then registers a client bound to the callback URL used by Amodal or the CLI.

Changing requested scopes, resource origins, or other compared application settings in oauth.json moves a connected connection to reconnect required. Discovery-derived endpoints and an omitted client ID are excluded from this comparison. Selecting Connect approves the edited configuration and requires authorizing an account again.

Add a connection in Amodal

Add connection on the Connections page saves configuration through the connected source. A GitHub source receives a commit on the selected branch; an Amodal source receives a source snapshot. See Where changes are saved.

The dialog has four stages: Choose service, Set up, Save, and Connect. A service with nothing to fill in, an MCP preset or a managed provider that asks for no value, has three: the picker opens Save, where you edit its name. A managed provider without selection fields shows its exact scopes on Save under the collapsed Requested scopes list.

  1. Choose the service. The picker lists the managed providers the platform hosts (Google Analytics 4 on a platform with its client ID and client secret configured) and the remote MCP servers Amodal ships as presets: Linear, Jira and Confluence, Notion, monday.com, GitLab, Sentry, Supabase, Airtable, Stripe, PayPal, Miro, Datadog, Hugging Face, Perplexity, Netlify, Grafana, Mixpanel, Todoist, Retool, Contentful, and Resend. A preset needs no application registration: its server publishes OAuth metadata and issues a client during setup. Each tile carries the service logo, or the first letter of the name when no art ships for it. Search matches names and aliases such as GA4 or tickets. Custom connection below the tiles offers an API key, your own OAuth app, or an MCP server.
  2. Set up the connection. This stage belongs to the services that ask for a value and to every custom connection. The name defaults from the service (google-analytics, linear) or from the host of the URL you enter, with a numeric suffix when the name is taken; it must match [A-Za-z0-9_-]{1,100} and be unique, and the form shows the directory it creates. Google Analytics 4 asks for the numeric property ID and describes the access it requests, with the exact scopes under Requested scopes; spec.json gets auth: {"type": "oauth2", "provider": "google-analytics"} with the provider's base URL and test operation, and there is no oauth.json and no client secret. An API key connection takes the base URL, the credential placement, and the key itself, with the secret name under Advanced settings; the key is stored as an agent secret right after the files are saved. Leaving it empty defers it to the Connect stage. An OAuth app starts with the redirect URL to register and Copy, then takes the base URL, the application settings written to oauth.json, and the client secret, which is stored encrypted and never written to the repository. A custom MCP server takes its URL and Sign in with OAuth or Bearer token. Continue stays disabled while a field is missing or invalid and names what it waits for, such as Enter the base URL to continue.
  3. Save. A service that has no Set up stage edits its name here, above the summary, under the same rules, and Save connection stays disabled while the name is empty or invalid and names what it waits for. An MCP preset writes an ordinary OAuth MCP definition; its server address and the access it describes appear in the summary. The summary shows the service, the name, the resource, and the access, and a Next line names what follows the save: adding the credential, connecting the application, or deploying and signing in with the provider. Expand Configuration files to inspect the generated files. Amodal shows the branch selector and commit message, defaulting to the production branch. Save connection saves the files through the connected source. A refused GitHub commit shows the App permission to change with a link to the installation's settings page.
  4. Connect. The stage lists the remaining steps in order, numbered, with one button for the next of them: deploy the saved files and sign in or add the credential. A note below the list explains that chats can use the connection once its saved files are in an active deployment and its credentials are set. A secrets connection whose key was typed on Set up arrives with that credential already stored. Otherwise, or when storing it failed and the step carries the error, the credential step shows one password input per env: reference; Save secret stores the value as an agent secret and marks it Set. Saving a secret enables Finish even before deployment; closing the dialog does not deploy the saved files. Amodal shows Saved ยท Deployment required. Deploy and connect deploys the saved commit with its other changes. Managed providers can start sign-in while the deployment runs; custom applications wait for the deployment to become active because their settings come from its bundle. Select Continue to sign in to approve access in another tab. A failed deployment retains View build logs. Connect later closes the dialog and leaves Finish setup on the row. A branch that deploys automatically is followed without a second deploy request. With manual production promotion, a completed production build stays inactive and the dialog offers Make production to continue setup. This applies both to builds requested in the dialog and to automatic builds it follows. Without deploy access, the stage says so and someone with access deploys first. A confidential custom application keeps its client secret input for a retry. Once consent completes, the sign-in step shows the authorized account and its scopes; while the deployment is still running, Finish stays disabled and the dialog says so. Once the deployment is active, the stage shows the connection as added and Finish closes the dialog.
  5. Amodal shows Restart agent after credential changes. With the connection's files in an active deployment and its credentials set, new chats can use the connection. The restart makes chats already open, channels, and MCP sessions pick up credential changes. A connection added to the repository directly keeps Connect or Add credential on its row and page.

An MCP server without published resource metadata, or one that requires a pre-registered client ID, needs oauth.json. Register the exact callback URL displayed in Amodal; changing the callback address can require registering another client.

Connections page

Each row shows the name, the protocol and authentication method with the service host, the authorized account when known, one state, and the action that completes setup: Finish setup, Fix configuration, Configure OAuth, Add credential, Connect, Reconnect, Restart agent, Test connection, or Open. Selecting the row opens the connection page; a saved connection that no deployment loads opens its files instead. A connection whose files declare no authentication reads No credentials rather than Ready: it sends no credential, and only a test shows whether the service accepts that. The summary line above the list counts the connections that need attention, and Needs attention lists everything that is not Ready or Verified, a pending runtime update included. Finish setup on a saved, undeployed connection reopens the setup dialog at its Connect stage; a hosted agent deploys the latest commit on the production branch there. Connect on a row configures the connection from its files and opens the provider's consent page in another tab. The row then waits for approval and offers Reopen sign-in and Cancel; a browser that blocks the tab shows Continue to sign in instead. A confidential OAuth application asks for its client secret on the connection page. Test connection runs the safe test operation declared in spec.json, or lists the tools of an MCP server, from both the list and the connection page. A failed test reports the upstream status and the service's own explanation on one line. A REST connection without a test operation offers Open instead. REST OAuth tests save a verified result on the binding. Manual REST and MCP tests report their result without saving that OAuth verification state. A runtime error opens the connection page, which shows the error with the test and reconnect actions. The row menu opens the connection page, the inspector, or the files, and Remove deletes the directory and its OAuth binding after a confirmation. Removal stops when code or configuration files still call the connection. When only Markdown files mention it, or when the scan could not read every file, the warning offers Remove anyway. Removing a binding leaves the authorized account intact, even when no other connection uses it. Use Disconnect account on the connection page before removal if you also want to disable the account; this affects every connection sharing it.

Connection page

The header shows the state with its explanation, the service, and the host. The Account and access card lists one password input per env: reference of a secrets connection with a Set marker; values are never displayed back. An OAuth connection shows the authorized account and its scopes with Connect or Reconnect, Test, and Disconnect account. Each agent connection has one account assignment shared by its deployments and cloud-backed local development; the disconnect confirmation names the other connections bound to the same account, because all of them lose access. When oauth.json no longer matches the approved application, the card says so and Reconnect approves the file and requires authorizing an account again.

Removal saves the file deletion in the same place as creation: a commit on the saved branch for a Git source or a source snapshot for an Amodal source. Removal does not request a deployment. A deployed connection that is absent from saved source stays visible as Pending removal with Open deploys until you deploy the saved source. Its menu retains Inspect. If the files were removed but the OAuth binding could not be deleted, Retry binding removal retries that cleanup without deleting the files again.

The Tools card of an MCP connection lists the discovered tools; the API operations card of a REST connection lists the documented endpoints with a Run action for each.

The Settings card of a managed provider connection shows the provider's name, the scopes it grants, and the selection read from spec.json. When the platform no longer lists the provider, the card says so and Connect is unavailable until an operator restores the entry. For a custom OAuth connection the card shows oauth.json and an Edit button. Saving writes the file through the connected source. Deploy the saved source, then connect. A connection declared with auth.type: "oauth2" and no oauth.json opens the editor to create the file. Register the exact redirect URL displayed in the editor with the provider. Other connections show their URL and authentication method.

Files, collapsed, links every file of the connection directory into the file editor.

Authorization code prepares a consent link with PKCE and opens it in another tab, which the connection page reopens on demand. Client credentials obtains a token directly. Cloud consent may first require signing into Amodal as the administrator who started the connection. Connected status confirms credential acquisition; the resource test separately confirms access to the selected resource. Consent denial keeps the saved configuration. Reconnect with unchanged configuration requires the same provider account when account identification is configured. If the saved account cannot be verified, select Authorize a new account. Amodal asks you to confirm replacing this connection's saved account; confidential applications also require the client secret. Replace account and authorize configures a fresh binding from the connection files and starts authorization. This connection loses its current access until authorization succeeds. Other connections using the saved account keep their access. The replaced account is disconnected when no other connection is bound to it.

Resource origins are exact origins, such as https://api.example.com. They are not URL prefixes. Include a separate account lookup origin when needed. Hosted OAuth requires public HTTPS destinations. Local development additionally permits explicit loopback URLs. Resource redirects are rejected, so configure the final API origin.

Authorization and token parameters accept access_type, prompt, audience, resource, login_hint, and include_granted_scopes. Protected protocol fields, such as state and redirect URI, are generated by the broker. The supported token authentication methods are client secret in the request body, HTTP Basic, and public-client authentication for authorization code.

Use the CLI

Commands address the connection name, such as analytics, rather than its npm package name.

add writes the same connection files as the Amodal dialog into the working tree at --path (amodal/connections/<name>/ when that directory holds connections, else connections/<name>/) without committing, and refuses a name that already exists. It takes one of --rest <baseUrl>, --mcp <url>, or --provider <id> (with an optional --rest <baseUrl>):

amodal connections add crm --rest https://api.example.com
amodal connections add shop --rest https://shop.example.com --auth header --header X-Shop-Token --secret-name SHOP_TOKEN
amodal connections add analytics --rest https://analyticsdata.googleapis.com --auth oauth2 --oauth ./oauth.json
amodal connections add drive --provider google-drive --rest https://www.googleapis.com/drive/v3
amodal connections add linear --mcp https://mcp.linear.app/mcp
amodal connections add notion --mcp https://mcp.notion.example --auth bearer

--auth selects bearer (the default for --rest), api_key, basic, header (with --header <name>), or oauth2 (the default for --mcp and the only method for --provider). A token method writes auth.token: "env:<NAME>_API_KEY" (<NAME>_BASIC_AUTH for basic); --secret-name picks another variable. An MCP server with --auth bearer gets auth: {type: "bearer", token: "env:<NAME>_TOKEN"}. The secret holds the token; the runtime adds the Bearer prefix. --oauth <file> copies a custom application's oauth.json, validated like the loader validates it. After add, set the secret in .env or run configure and connect below.

remove deletes the connection directory when every file in it is one the source editor lists; it refuses dotfiles and ignored paths and leaves the directory in place. With --local it also deletes the local OAuth binding. Without --local, removal leaves cloud bindings and accounts unchanged. disconnect --agent disables the account and attempts revocation while retaining its bindings; it does not delete source files.

configure requires an explicit --config unless selecting --grant or --managed; it does not automatically load the connection's oauth.json. --config reads application settings such as the recipes below. Supply a confidential application's secret through standard input:

amodal connections configure analytics --agent AGENT_ID --config oauth.json --secret-stdin
amodal connections connect analytics --agent AGENT_ID
amodal connections status analytics --agent AGENT_ID --json
amodal connections test analytics --agent AGENT_ID --path ./agent
amodal connections list --agent AGENT_ID --json

--secret-stdin waits for standard input to end. Use a password manager's output or an interactive input source. Do not put the secret in shell arguments or the configuration file.

connect prints the authorization link and opens it in the browser. Pass --no-open to skip the browser. The command polls until completion, denial, cancellation, or a ten-minute timeout. Press Ctrl+C to interrupt. Sign into the same Amodal account in the browser for cloud authorization.

Bind an existing authorized account explicitly with configure analytics --grant GRANT_ID. Select a trusted managed application with configure analytics --managed APPLICATION_ID. Amodal has no authentication switch. Edit spec.json through Files to change its declared method. An existing OAuth binding still takes precedence; changing the file alone does not remove it or restore manual authentication. Use disconnect analytics to disable the account. Disconnect blocks every binding of that account and attempts provider revocation when a revocation endpoint is configured.

Local and hosted execution

For credentials stored on this machine, add --local --path ./agent to configure, connect, status, test, list, and disconnect. The authorization-code callback is http://127.0.0.1:8788/callback; use --callback-port to select another registered port. Amodal displays the platform callback URL, so register both URLs when using Amodal and the local CLI with one provider application.

Local credentials are encrypted under ~/.amodal/oauth/, with a repository-specific store and an owner-only key file. They are not copied into the repository or deployment bundle. Keep this directory private. A missing encryption key requires reauthorization.

amodal dev uses the local repository's OAuth bindings and listens on loopback by default. amodal dev --oauth-cloud uses the selected cloud agent's bindings through the authenticated platform broker. Select the intended agent with amodal login or amodal agent first.

amodal dev watches the local bundle and environment files for changes by default. Use --no-watch to disable file watching.

Hosted runtimes use their deployment identity to resolve the named connection for their agent. The connection's files must be in the active deployment before hosted sessions can use it. A credential change does not restart a hosted runtime; Amodal shows Restart agent, which restarts the deployment so sessions already open, channels, and MCP sessions resolve the new credentials; sessions created after the change resolve them without it. Configuration overlays change only authentication; API specifications and policies remain in the bundle. REST requests read current bindings at request time. HTTP MCP sessions initialize with their approved bindings. Create a fresh session after changing an MCP application's scopes, resource origins, or account binding. Token refresh and reconnect for the same approved grant work without rebuilding the session.

The request tool retains its permission and confirmation checks. Custom code using ctx.request() must enforce its own operation authorization. Both request paths enforce the approved OAuth resource origin. A failed resource request is not automatically replayed.

Google Analytics 4

Register a Google OAuth web application and enable the Analytics Data API. Use this oauth.json, replacing the client ID:

{
  "label": "Analytics account",
  "grantType": "authorization_code",
  "issuer": "https://accounts.google.com",
  "discoveryUrl": "https://accounts.google.com/.well-known/openid-configuration",
  "clientId": "YOUR_CLIENT_ID",
  "tokenAuthMethod": "client_secret_post",
  "scopes": ["openid", "https://www.googleapis.com/auth/analytics.readonly"],
  "resourceOrigins": ["https://analyticsdata.googleapis.com", "https://openidconnect.googleapis.com"],
  "authorizationParameters": { "access_type": "offline" },
  "accountUrl": "https://openidconnect.googleapis.com/v1/userinfo",
  "accountIdPath": "sub"
}

The openid scope supports stable account identification for reconnect. The broker uses the explicit account lookup; it does not treat an ID token as the agent user's identity. Request offline access to obtain refresh capability. Add prompt: "consent" only when explicit reconsent is needed. Provider registration, test-user restrictions, and consent verification remain Google operating requirements. See Google's web-server OAuth flow and OpenID Connect documentation.

Select the numeric GA4 property ID in the connection specification. For property 123456789, configure:

{
  "baseUrl": "https://analyticsdata.googleapis.com/v1beta",
  "auth": { "type": "oauth2" },
  "test": {
    "method": "POST",
    "path": "properties/123456789:runReport",
    "body": {
      "dateRanges": [{ "startDate": "yesterday", "endDate": "yesterday" }],
      "metrics": [{ "name": "eventCount" }],
      "limit": "1"
    }
  }
}

Keep the connection's surface and policy files. Use the selected property's ID, not 0 or a generic metadata endpoint. An empty successful report still verifies access. A Google account without access to that property fails the resource test even when OAuth connects successfully. The runReport reference describes the request.

GitLab

A GitLab application uses the same authorization-code lifecycle. Register the displayed callback and use this oauth.json:

{
  "label": "GitLab account",
  "grantType": "authorization_code",
  "issuer": "https://gitlab.com",
  "discoveryUrl": "https://gitlab.com/.well-known/openid-configuration",
  "clientId": "YOUR_CLIENT_ID",
  "tokenAuthMethod": "client_secret_post",
  "scopes": ["read_api", "read_user"],
  "resourceOrigins": ["https://gitlab.com"],
  "accountUrl": "https://gitlab.com/api/v4/user",
  "accountIdPath": "id"
}

A connection with base URL https://gitlab.com/api/v4 can use GET user as its safe test. GitLab documents OAuth grants, application scopes, and the current-user response.

Auth0 client credentials

Create a machine-to-machine application and authorize it for the intended API. Configure the application's token authentication as Client Secret (Post) and use this oauth.json:

{
  "label": "Reporting service",
  "grantType": "client_credentials",
  "issuer": "https://YOUR_TENANT.us.auth0.com/",
  "tokenUrl": "https://YOUR_TENANT.us.auth0.com/oauth/token",
  "clientId": "YOUR_CLIENT_ID",
  "tokenAuthMethod": "client_secret_post",
  "scopes": ["read:reports"],
  "resourceOrigins": ["https://YOUR_API_HOST"],
  "tokenParameters": { "audience": "YOUR_API_IDENTIFIER" }
}

Use scope names defined and granted by your API. The audience is the Auth0 API identifier. The resource origin is the actual API host. This grant has no human account lookup or browser consent. Expired tokens are reacquired with client credentials. See Auth0's client-credentials request and application credentials.

These recipes are checked against provider documentation. Live Google, GitLab, and Auth0 authorization require operator registrations and test accounts; deterministic HTTP fixtures do not establish live-provider success.

Operations

Platform operators configure PLATFORM_API_URL and the existing WorkOS application credentials. Register the platform's /api/oauth/callback URL with each provider. Register /api/oauth/authorize/callback as an allowed callback for the WorkOS application identified by WORKOS_CLIENT_ID; WORKOS_API_KEY must be configured. This login verifies that the browser belongs to the administrator who initiated authorization.

Managed providers ship with the platform as one JSON file each under packages/platform-api/src/oauth/managed-catalog/. A file holds {id, clientIdEnv, clientSecretEnv, config, catalog}: config uses the custom configuration schema without clientId, and the two Env fields name the environment variables that carry the client ID and the client secret. The provider reaches the editor only when both variables are set, so a picked provider cannot fail for a missing secret after its connection files are committed and deployed. Setting the client ID alone keeps the provider out of the picker; a connection already saved against it reports the missing secret when it tries to authorize. Neither value is ever returned to the editor.

The shipped providers are all Google APIs, and all of them name the same pair, GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET. One Google Cloud OAuth client serves every Google API, so a variable per provider would only ask the operator to paste the same secret seven times. They therefore reach the picker together: setting the pair enables all seven, and clearing it withdraws all seven. To hold one back, point its file at a different variable name, which is data in the provider file and needs no code change.

ProviderScopes beyond openidSelection
Google Analytics 4analytics.readonlyGA4 property ID
Google SheetsspreadsheetsSpreadsheet ID
Google DocsdocumentsDocument ID
Google Drivedrive.filenone
Google Calendarcalendar.readonly, calendar.eventsCalendar ID
Google Search Consolewebmasters.readonlyURL encoded property
BigQuerybigqueryProject ID

Google classifies scopes as non-sensitive, sensitive, or restricted, and the Cloud console labels each one as it is added to the consent screen. A sensitive scope needs the consent screen reviewed. No shipped provider asks for a restricted scope, which would require the operator to pass an annual CASA security assessment. Drive uses the non-sensitive drive.file scope. Enable the matching API in the Cloud project before the pair is set: an approved scope still returns 403 while its API is off.

Drive's drive.file scope grants access to files created by, opened with, or shared with the Google app. This permission belongs to the Google app and account, not to an individual Amodal connection. Connections using the same GOOGLE_CLIENT_ID and Google account can access the same authorized files, including files created through another connection. Amodal's connection flow has no Google Picker integration, so it cannot select arbitrary existing files to authorize them for the app. Existing files already authorized for the app can be visible.

catalog is what Amodal's Add connection picker shows and writes:

  • name, description, icon: the picker tile.
  • aliases: search words beyond the name, such as GA4.
  • signIn: the provider named on the consent button, such as Google.
  • access: what the granted scopes let the agent do, in plain language, shown before sign-in.
  • selection: the values the user enters, each {key, label, description?, pattern?, placeholder?, invalid?}. pattern is a regular expression the value must match; invalid is the sentence shown when it does not.
  • spec: a template for the connection's spec.json (baseUrl, test, and any other field except auth).
  • policy: an optional policy.json template for endpoint access rules, including read intent for POST operations that query data.
  • surface: an optional surface.md template documenting operations and request bodies, written as an array of markdown lines.
  • rules: an optional rules.md template for model guidance, written as an array of markdown lines. These instructions are not an authorization boundary.

All four file templates support {{key}} placeholders for selection values, including endpoint keys in policy. A placeholder that names no selection key fails validation. The platform joins markdown arrays into strings with newline separators before returning the catalog; Amodal substitutes the selection values when it writes the connection files.

Adding a provider is a pull request with one file, plus the two variables on each deployment that offers it. Register the platform's /api/oauth/callback URL on the provider's application for every deployment origin.

Removing a provider file or changing its config disables the existing bindings of that provider until an administrator connects again. Rotating only the client secret variable uses the current value on subsequent exchanges. The CLI --managed option accepts a provider id.

GET /api/agents/:id/oauth/connections returns the enabled providers as catalog with their granted scopes and resource origins, without the client ID or the variable names.

MCP presets are not managed providers. Amodal ships them in packages/studio/src/lib/connection-catalog.ts as {id, name, description, aliases, url, access, signIn, icon}; a preset writes an ordinary OAuth MCP spec.json and carries no client identity. A server qualifies when it publishes RFC 9728 resource metadata and registers a public client through RFC 7591. A server that requires an allowlisted redirect URI, or a client ID registered in advance, needs a managed application or oauth.json instead. icon names a slug of the Iconify logos set; pnpm connection-icons:generate vendors that art into studio/src/lib/connection-icons.generated.ts, so the connection picker does not fetch a logo at runtime.

Organization key versions protect encrypted application secrets, grants, and transactions. Keep old key versions available until all encrypted rows use the intended version. Local storage uses a separate owner-only file key.

When a provider reports revoked credentials or reduced consent, reconnect the account. Configuration revision errors require approving the intended application configuration. Resource-permission failures require access to the selected resource. Error messages include safe codes and correlation IDs; token values are not part of diagnostics.