Skip to content

Integrate with Airtable MCP server#43

Merged
yosrixp merged 1 commit into
mainfrom
airtable
Jun 3, 2026
Merged

Integrate with Airtable MCP server#43
yosrixp merged 1 commit into
mainfrom
airtable

Conversation

@yosrixp

@yosrixp yosrixp commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds airtable as a new L2-promoted upstream MCP provider, exposed at /v1/airtable/mcp on the MCP gateway. Modeled on Figma (confidential PKCE + direct flat OIDC userinfo) with Oracle EPM's rotating-RT handling, but uses HTTP Basic on the token endpoint per Airtable's docs (curl -u client_id:client_secret).

Architecture

sequenceDiagram
  participant C as MCP Client
  participant M as MoP /authorize
  participant A as AirtableResource
  participant Q as Quarkus OIDC tenant=airtable
  participant Up as airtable.com OAuth2
  participant UI as api.airtable.com/whoami
  participant L2 as L2 upstream-tokens

  C->>M: GET /authorize?resource=.../v1/airtable/mcp + PKCE
  M->>M: Okta leg + mint MoP code
  M->>A: redirect /airtable/authorize?state=mopCode
  A->>Q: "@Authenticated -> upstream OIDC PKCE"
  Q->>Up: /authorize?...&resource=https://mcp.airtable.com
  Up->>Q: callback /airtable/authorize/callback?code=
  Q->>Up: POST /token (Basic auth, code+verifier)
  Up->>Q: AT 1h + RT 60d rotating
  Q->>UI: GET whoami (Bearer AT)
  UI->>Q: "{ id, email, scopes }"
  Q->>A: restore-path
  A->>L2: storeTokens(airtable, AT=3600, rotated RT)
  A->>C: redirect with MoP code
Loading

Changes

New Java (3 files):

  • AirtableResource/airtable/authorize callback. Uses the 8-arg AuthorizerService.storeTokens overload so the L1 bare row TTL is provider-pinned to the documented 1h AT. Includes the RT-borrow-from-L2 fallback for second/third MCP-client window relogins (Airtable rotates its RT on every refresh, so the L2-borrowed RT is the freshest known-good value).
  • AirtableUpstreamRefreshClient — L2 canonical refresh path. Uses ClientSecretBasic (HTTP Basic on the Authorization header) per Airtable's documented curl. Persists the rotated RT verbatim; defensive carry-forward only when upstream anomalously omits a new RT. invalid_grant → OktaTokenRevokedException (matches UpstreamRefreshService's revoke-on-invalid-grant contract).
  • TokenExchangeServiceAirtableImpl — legacy/native fallback path used by AuthorizerService.refreshUpstreamAndGetToken. Same Basic-auth wire shape and rotation semantics.

Java modifications (4 files):

  • K8SSecretsProviderSECRET_DATA_KEY_AIRTABLE_CLIENT_SECRET = "airtable-client-secret" constant + map entry.
  • UpstreamProviderClassifierAIRTABLE_PROVIDER constant, PROMOTED_PROVIDERS membership, injected client, resolveRefreshTokenClient branch, javadoc bullet.
  • TokenExchangeServiceProducer@Inject + case "airtable" switch arm.
  • OauthProviderLabelAIRTABLE constant + normalize() enum case.

Configuration (all 7 surfaces):

Surface File
App baseline mcp-oauth-proxy/src/main/resources/application.yaml
Helm template mcp-oauth-proxy/deploy/mcp-oauth-proxy/templates/configmap.yaml
Chart defaults mcp-oauth-proxy/deploy/mcp-oauth-proxy/values.yaml

Each got the airtable tenant block (HTTP Basic, direct whoami userinfo, extra-params.resource=https://mcp.airtable.com), the L2 TTL cap (15552000 = 6 months), the server.token-exchange.airtable entry, the remote-servers.endpoints entry where applicable, and a resource-mapping entry pointing at the correct gateway host (mcp-gateway for prod, dev-mcp-gateway for stage/local). Same clientId across all envs; secret read from K8s key airtable-client-secret.

Tests (3 new, 4 extended) — all passing:

  • New: AirtableResourceTest, AirtableUpstreamRefreshClientTest (includes regression-guard test asserting Authorization: Basic <base64(id:secret)> header is set AND client_id/client_secret are NOT in the form body), TokenExchangeServiceAirtableImplTest.
  • Extended: UpstreamProviderClassifierTest (promoted + resolve + Google-Workspace disambiguation + typo guards), TokenExchangeServiceProducerTest, UpstreamTokenConfigTest (exact-match cap + default fallback + Google-Workspace group-key isolation), K8SSecretsProviderTest.

Key behavior

  • AT lifetime 1h (expires_in=3600); L1 bare-row TTL pinned to this via 8-arg storeTokens.
  • RT lifetime 60d, rotates every refresh — response RT is persisted verbatim.
  • L2 row TTL capped at 6 months (operator-tunable via MOP_UPSTREAM_TOKEN_EXPIRY_SECONDS_AIRTABLE).
  • Userinfo: Quarkus calls https://api.airtable.com/v0/meta/whoami directly (flat {id, email, scopes}); no synthetic proxy.
  • resource=https://mcp.airtable.com RFC 8707 indicator on /authorize (same mechanism as Datadog's extra-params.resource).

Signed-off-by: yosrixp <yosrixp@yahoo.com>
@yosrixp
yosrixp merged commit 5d5b3be into main Jun 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants