Skip to content

OAuth for MCP Tools

OAuth bake-in means: any MCP component generated from an OpenAPI spec that declares an oauth2 security scheme automatically gets a working sign-in card and token handling — with no OAuth code in your flow and no bundled OAuth provider component.

When a flow calls an OAuth-aware tool and there is no token yet, the tool self-gates: instead of calling the API it returns a “Connect” card with a single Authorize button. The user clicks it, signs in with the provider, and the runtime persists the token. The next call proceeds normally — and refreshes the token automatically when it expires.

The flow spans three pieces, each owning one part:

Generator

greentic-mcp-gen reads the OpenAPI oauth2 scheme and emits an OAuth declaration into the tool’s metadata (provider, authorize/token URLs, scopes, the setup fields the operator must supply).

MCP adapter (in the component)

On every call it checks for a token. No token → render the Connect card (single Authorize button built from the operator’s client_id). Token present → call the API.

Greentic runtime

Serves /oauth/callback/<provider>, builds the consent URL (PKCE + redirect_uri per provider profile), signs a CSRF state, exchanges the code, persists the token, and refreshes it on read.

OpenAPI (oauth2)
│ greentic-mcp-gen
MCP component ──compose──► node component (self-gate)
▼ (flow calls the tool, no token)
"Connect <provider>" card ──Authorize──► provider consent
│ │
│ /oauth/callback/<provider> ◄──────────┘
▼ (runtime: verify state, exchange code, persist token)
flow call proceeds ──► real API response

OAuth bake-in needs one thing from you up front: an OAuth client registered with the provider, so you have a Client ID (and, for some providers, a Client Secret). Greentic never creates these — they belong to your provider account.

Whether you need a Client Secret depends on the client type, not just the provider. Public clients (native mobile, SPA) use PKCE with no secret; confidential clients (web-server, daemon, and Google’s Desktop/“installed” apps) require a secret. The refresh-token mechanism varies too:

ProviderAuthor setupSecret?Refresh token viaRegister / docs
Google (Sheets/Drive/Gmail)client_id + client_secret (Web/Desktop client) + PKCEYes ¹access_type=offline + prompt=consentCloud Credentials
Microsoft Graphclient_id + tenantPublic: No · Web/daemon: Yesoffline_access scopeApp registrations · docs
Okta / Auth0 / Keycloakclient_id + domain/issuerPublic: No · Confidential: Yesoffline_access scopeOkta · Auth0 · Keycloak
Slackclient_id + client_secretYestoken rotation (opt-in)Slack apps
GitHub (OAuth App)client_id + client_secretYesclassic = long-lived; GitHub App = refreshOAuth Apps

¹ Google’s Web and Desktop (“installed app”) clients require the client_secret on the token exchange — Google mandates it even for Desktop apps (where it isn’t treated as truly secret). Only native iOS/Android clients are secretless — so a Web/Desktop Google client requires both the Client ID and the Secret.

The per-provider claims above come from each vendor’s documentation:

  1. Register an OAuth client with the provider (e.g. Google Cloud Console → APIs & Services → Credentials → Create OAuth client ID; GitHub → Settings → Developer settings → OAuth Apps).

  2. Register the redirect URI. Greentic serves the callback at:

    http://127.0.0.1:8080/oauth/callback/<provider>

    e.g. http://127.0.0.1:8080/oauth/callback/google. For a public deployment, set GREENTIC_PUBLIC_BASE_URL and register <base>/oauth/callback/<provider> instead. The exact URI to register is also shown in the setup question’s help text.

  3. Enable the API(s) the tool uses in the provider’s project. This is separate from OAuth scopes — granting a scope does not enable an API. For Google, enable each API in the API Library (e.g. Google Drive API, Google Sheets API). A disabled API returns 403 SERVICE_DISABLED.

  4. Add test users (providers in “testing”/unverified mode). For Google, OAuth consent screen → Test users → Add your account, or sign-in fails with Error 403: access_denied.

  5. Keep the Client ID + Secret handy — you’ll paste them during gtc setup (next section). You do not put them in any file by hand.

Generate the MCP component from an OpenAPI spec that declares an oauth2 scheme. The generator detects it and bakes in the OAuth declaration.

  1. Put the spec in an input directory:

    input/sheets_reports.yaml

    The spec must declare an oauth2 security scheme (with authorizationUrl, tokenUrl, and scopes) and reference it from the operations. That declaration is the only thing that turns on the bake-in:

    input/sheets_reports.yaml (excerpt)
    components:
    securitySchemes:
    googleOAuth: # <- becomes the provider/scheme in the card + state
    type: oauth2
    flows:
    authorizationCode:
    authorizationUrl: https://accounts.google.com/o/oauth2/v2/auth
    tokenUrl: https://oauth2.googleapis.com/token
    scopes:
    https://www.googleapis.com/auth/drive.readonly: Read your spreadsheets
    # Apply it (globally here, or per-operation) so the generated tools self-gate:
    security:
    - googleOAuth:
    - https://www.googleapis.com/auth/drive.readonly

    The generator reads this and emits the oauth declaration into each tool’s metadata (provider, authorize/token URLs, scopes, and the setup fields the operator supplies). No oauth2 scheme → no bake-in.

  2. Generate:

    Terminal window
    greentic-mcp-gen --input-dir ./input --output-dir ./output

    This produces a telemetry-free WASM component whose tool metadata carries the oauth declaration. (Telemetry is opt-in / off by default — an unsupported greentic:telemetry/logging import would otherwise fail to link.)

  3. Compose with the bundled adapter (gives you the self-gating node component). greentic-mcp compose ships the adapter, so you don’t manage the adapter wasm or run wac yourself:

    Terminal window
    greentic-mcp compose --output sheets_reports.node.wasm sheets_reports.component.wasm

2. Build the pack & surface the setup questions

Section titled “2. Build the pack & surface the setup questions”

When you build the pack, Greentic infers the operator setup questions (Client ID / Client Secret) from the OAuth declaration — surfaced in the gtc setup UI.

Terminal window
greentic-pack build --in ./google-sheets-app

Make sure the app’s assets/setup.yaml declares the OAuth questions (or they are inferred from the component’s oauth.setup_fields), e.g.:

id: google-sheets-app-setup
title: Google Sheets — OAuth setup
questions:
- name: auth.oauth2.googleOAuth.client_id
title: Google OAuth Client ID
kind: string
required: true
secret: false
help: >-
Register the redirect URI http://127.0.0.1:8080/oauth/callback/google and
enable the Google Drive API on this client.
- name: auth.oauth2.googleOAuth.client_secret
title: Google OAuth Client Secret
kind: string
required: true
secret: true

Run setup against the bundle and enter the credentials you prepared. They are stored encrypted, pack-scoped — never in plaintext.

Terminal window
gtc setup ./my-bundle --tenant demo --team default

The UI shows the two questions (Client ID, Client Secret) with the exact redirect URI to register in the help text. Fill them in and Execute.

Terminal window
gtc start ./my-bundle --tenant demo --team default
  1. Open the webchat and send a message that triggers the OAuth-aware tool.

  2. Because there is no token yet, the tool self-gates and you get a card:

    Connect google accountClick Authorize to sign in… I can only move forward to google after you’ve authorized.

    with a single Authorize button.

  3. Click Authorize → the provider’s consent screen → approve.

  4. The provider redirects to /oauth/callback/<provider>; the runtime verifies the signed state, exchanges the code (with PKCE + client secret as the provider requires), and persists the token.

  5. The conversation auto-advances — the runtime re-runs the flow with the token present and the next card (e.g. the spreadsheet picker) appears on its own. No re-typing. Subsequent calls reuse the token and refresh it automatically when it nears expiry.

SymptomCauseFix
Error 403: access_denied on the consent screenYour account isn’t a test userAdd it under the provider’s OAuth consent screen → Test users
403 SERVICE_DISABLED / “API has not been used in project…”The API isn’t enabled in the project (scopes ≠ enablement)Enable the API in the provider’s API Library, wait ~1–2 min
Card never appears / “Send failed” in webchatStale browser conversation (e.g. after a server restart)Open the webchat in a fresh/incognito window
redirect_uri_mismatchThe registered redirect URI doesn’t matchRegister exactly http://127.0.0.1:8080/oauth/callback/<provider> (or your GREENTIC_PUBLIC_BASE_URL form)