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).

This is a Python port of knwoop/iap-mcp-proxy.

┌──────────────┐        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.3.tar.gz (35.4 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.3-py3-none-any.whl (29.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: iap_mcp_proxy-0.4.3.tar.gz
  • Upload date:
  • Size: 35.4 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.3.tar.gz
Algorithm Hash digest
SHA256 7a6ef514eccad9da21377f7a584a7d1f633a2b32fede3d9c465fd57d88fd6359
MD5 371b2a7a5baa8a15741348784d082c7c
BLAKE2b-256 5a43d3b2649e28b8be31e3ee4a4a9cab83e17fea4d39cee604082f911c78f8f3

See more details on using hashes here.

Provenance

The following attestation bundles were made for iap_mcp_proxy-0.4.3.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.3-py3-none-any.whl.

File metadata

  • Download URL: iap_mcp_proxy-0.4.3-py3-none-any.whl
  • Upload date:
  • Size: 29.9 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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 09bc7a6e2c2320c352d3a331a57aee1ed2f961df24995adef359b1a97e88f7f4
MD5 fed6cfbea56a7157510258f8f3cfe096
BLAKE2b-256 beca038275a5315004b1ceaadc11d6ad0aa26d30f69e8da2899f15dc415392bb

See more details on using hashes here.

Provenance

The following attestation bundles were made for iap_mcp_proxy-0.4.3-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

This release

0.4.3 This release

2 files

0.4.1

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