Skip to main content

iap-mcp-proxy

A client-side bridge that lets generic MCP clients (Claude Desktop, Claude Code, Cursor, ...) connect to remote MCP servers protected by Google Cloud Identity-Aware Proxy (IAP).

The Google Cloud counterpart to aws/mcp-proxy-for-aws.

┌──────────────┐        stdio          ┌───────────────┐   HTTPS + ID token   ┌─────┐      ┌────────────┐
│  MCP client  │ ────────────────────► │ iap-mcp-proxy │ ───────────────────► │ IAP │ ───► │ MCP server │
│ (Claude etc.)│                       │  (this tool)  │  Proxy-Authorization │     │      │ (Cloud Run)│
└──────────────┘                       └───────────────┘                      └─────┘      └────────────┘

IAP expects a Google-issued OIDC ID token (or, for managed Cloud Run IAP, a self-signed service-account JWT). The MCP spec's OAuth 2.1 flow cannot produce these tokens, so generic clients get a 401/redirect and stop. This proxy runs locally, obtains and refreshes Google credentials, attaches them as Proxy-Authorization (IAP consumes and strips this header), and forwards MCP traffic (Streamable HTTP) upstream.

Install

From PyPI:

pip install iap-mcp-proxy
# or
uv pip install iap-mcp-proxy

ExtensibilityAI also publishes the same versions to the private Artifact Registry PyPI index for internal consumers.

Or install from source with uv:

git clone https://github.com/ExtensibilityAI/iap-mcp-proxy.git
cd iap-mcp-proxy
uv sync
uv run iap-mcp-proxy --version

Quick start

  1. Make sure you have credentials IAP will accept (see Credentials):

    gcloud auth application-default login
    
  2. Add the proxy to your MCP client config. Cursor / Claude Desktop:

    {
      "mcpServers": {
        "internal-tools": {
          "command": "iap-mcp-proxy",
          "args": [
            "--audience", "1234567890-abc.apps.googleusercontent.com",
            "https://mcp.internal.example.com/mcp"
          ]
        }
      }
    }
    

    Claude Code:

    claude mcp add internal-tools -- iap-mcp-proxy \
      --audience 1234567890-abc.apps.googleusercontent.com \
      https://mcp.internal.example.com/mcp
    

Usage

iap-mcp-proxy [flags] <UPSTREAM_URL>
Flag Env var Default Description
--audience IAP_MCP_AUDIENCE origin of UPSTREAM_URL OIDC token audience. LB-backed IAP: the IAP OAuth client ID (NNN.apps.googleusercontent.com). Direct Cloud Run IAP: depends on the OAuth client — see Supported IAP configurations.
--credentials IAP_MCP_CREDENTIALS auto auto, adc, impersonate, oauth, signjwt.
--impersonate-service-account IAP_MCP_IMPERSONATE_SA Target SA email. impersonate mints an ID token as it; signjwt signs a self-signed JWT as it.
--downstream-auth IAP_MCP_DOWNSTREAM_AUTH Value forwarded as the upstream Authorization header. Supports env:VAR_NAME indirection so secrets stay out of client config files.
--refresh-margin IAP_MCP_REFRESH_MARGIN 5m Refresh the ID token this long before expiry.
--timeout IAP_MCP_TIMEOUT 120s Upstream timeout: total for JSON responses, idle (time between reads) for SSE streams — so long-running streaming tool calls are not killed while data or keepalives keep arriving.
--log-level IAP_MCP_LOG warn debug / info / warn / error. Logs go to stderr only.
--version Print version and exit.

Credentials

With --credentials=auto (the default), sources are tried in this order:

  1. Impersonation — if --impersonate-service-account is set, mint ID tokens via the IAM Credentials API (generateIdToken) using your ADC as the base identity. Requires roles/iam.serviceAccountTokenCreator on the target SA. Best for CI and shared team setups.
  2. ADC — if Application Default Credentials are a service account key or workload credential, mint an ID token directly.
  3. Desktop OAuth — gcloud user credentials cannot mint arbitrary-audience ID tokens, so the proxy falls back to an installed-app OAuth flow: first run opens a browser for Google sign-in; the refresh token is stored in your OS keychain (fallback: 0600 file under your user config dir). Requires a desktop OAuth client in the same project as the IAP resource, supplied via IAP_MCP_OAUTH_CLIENT_ID / IAP_MCP_OAUTH_CLIENT_SECRET — see Google's docs on programmatic IAP authentication.

These OIDC modes need IAP to use a custom OAuth client (LB-backed IAP, or a managed-client service with a separate allow-listed OAuth client). For modern managed-client direct Cloud Run IAP, --credentials=signjwt is the simplest route — it needs no OAuth client (see Supported IAP configurations).

The principal must hold roles/iap.httpsResourceAccessor on the IAP resource.

Supported IAP configurations

IAP deployment OAuth client Token the proxy sends --audience Status
Behind an external HTTPS load balancer Custom OIDC ID token (impersonate / adc / oauth) IAP OAuth client ID (NNN.apps.googleusercontent.com) Supported
Direct Cloud Run IAP Google-managed (default) Self-signed service-account JWT (signjwt) exact *.run.app endpoint (or /*) Supported — use --credentials=signjwt
Direct Cloud Run IAP Custom / allow-listed OIDC ID token OAuth client ID Expected to work, not yet verified
  • Since Google shut down the IAP OAuth Admin API (March 2026), newly-secured apps default to a Google-managed OAuth client, which rejects Google-issued OIDC ID tokens. Use --credentials=signjwt --impersonate-service-account=<SA>.
  • Managed-client IAP can also accept OIDC ID tokens via a separate allow-listed OAuth client; then --credentials=impersonate/adc work with that client ID as --audience.

Notes

  • The IAP token travels in Proxy-Authorization, which IAP consumes and strips — your app never sees it. If your app has its own auth, pass it with --downstream-auth and it is forwarded verbatim as Authorization.
  • On a 401 (or a 302 into Google sign-in) the proxy refreshes the token and retries once; a second failure is surfaced to the MCP client as a JSON-RPC error with an actionable message on stderr.
  • If the upstream reports the session expired (HTTP 404 — e.g. after a Cloud Run redeploy), the proxy transparently replays the cached initialize handshake to obtain a fresh session and retries the request; the stdio client never notices.
  • If a streaming (SSE) response drops mid-tool-call and the server tags events with IDs, the proxy resumes it with Last-Event-ID instead of losing the response.
  • After initialize, the proxy opens the standalone GET SSE stream so server-initiated messages reach the client, reconnecting with Last-Event-ID if the stream drops.
  • Exit codes: 0 clean shutdown, 1 fatal error, 2 auth bootstrap failure.

See docs/setup-gcp.md for setting up IAP in both deployment modes.

Development

uv sync --extra dev
uv run pytest
uv run ruff check
uv build

Releasing

Public PyPI uploads run only when a GitHub Release is published (publish-pypi.yml + Trusted Publishing).

  1. Bump version in pyproject.toml and __version__ in src/iap_mcp_proxy/__init__.py on main.
  2. Merge to main (Deploy may publish that version to Artifact Registry as usual).
  3. Create tag vX.Y.Z and Publish a GitHub Release for that tag.
  4. Approve the pypi GitHub Environment job if required reviewers are set.
  5. Verify: pip install iap-mcp-proxy==X.Y.Z from a clean environment (no Artifact Registry auth).

Routine merges to main do not publish to public PyPI.

License

Apache-2.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

iap_mcp_proxy-0.4.1.tar.gz (31.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

iap_mcp_proxy-0.4.1-py3-none-any.whl (25.7 kB view details)

Uploaded Python 3

File details

Details for the file iap_mcp_proxy-0.4.1.tar.gz.

File metadata

  • Download URL: iap_mcp_proxy-0.4.1.tar.gz
  • Upload date:
  • Size: 31.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for iap_mcp_proxy-0.4.1.tar.gz
Algorithm Hash digest
SHA256 90c32d7821f6fc848818b885a4826424d6dd8236761367e295859bd17d34915d
MD5 b3aab0a356e5669d5466c9e2b79b01f6
BLAKE2b-256 77140998cac3f6ebcdf0b457f05efed5a36e3c0c6628cfa6442f74405171d428

See more details on using hashes here.

Provenance

The following attestation bundles were made for iap_mcp_proxy-0.4.1.tar.gz:

Publisher: publish-pypi.yml on ExtensibilityAI/iap-mcp-proxy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iap_mcp_proxy-0.4.1-py3-none-any.whl.

File metadata

  • Download URL: iap_mcp_proxy-0.4.1-py3-none-any.whl
  • Upload date:
  • Size: 25.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for iap_mcp_proxy-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c11073d3bcd833a749a0a682226a96d9fe6a99e92c52355590a79cd5271d3557
MD5 52d4394dd50f5b9ec5f32861c0463301
BLAKE2b-256 ae52fbb4ba0f51795b5a8efbb5ad1a62ae2cd636b652eda07c7ca239edb4143f

See more details on using hashes here.

Provenance

The following attestation bundles were made for iap_mcp_proxy-0.4.1-py3-none-any.whl:

Publisher: publish-pypi.yml on ExtensibilityAI/iap-mcp-proxy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.4.3

2 files

This release

0.4.1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page