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

Metadata

Release files for iap-mcp-proxy 0.4.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for iap-mcp-proxy 0.4.3
File Size Uploaded Provenance Actions
iap_mcp_proxy-0.4.3.tar.gz 35.4 kB With provenance Details for iap_mcp_proxy-0.4.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for iap-mcp-proxy 0.4.3
File Interpreter ABI Platform Actions
iap_mcp_proxy-0.4.3-py3-none-any.whl Python 3 none any Details for iap_mcp_proxy-0.4.3-py3-none-any.whl

Total release size: 65.3 kB

Release files / iap_mcp_proxy-0.4.3.tar.gz

Download URL iap_mcp_proxy-0.4.3.tar.gz
Size 35.4 kB
Tags Source
SHA-256 checksum
How to use checksums
7a6ef514eccad9da21377f7a584a7d1f633a2b32fede3d9c465fd57d88fd6359
BLAKE2b-256 checksum
How to use checksums
5a43d3b2649e28b8be31e3ee4a4a9cab83e17fea4d39cee604082f911c78f8f3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.

Transparency log

Release files / iap_mcp_proxy-0.4.3-py3-none-any.whl

Download URL iap_mcp_proxy-0.4.3-py3-none-any.whl
Size 29.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
09bc7a6e2c2320c352d3a331a57aee1ed2f961df24995adef359b1a97e88f7f4
BLAKE2b-256 checksum
How to use checksums
beca038275a5315004b1ceaadc11d6ad0aa26d30f69e8da2899f15dc415392bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.

Transparency log

Security for iap-mcp-proxy 0.4.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for iap_mcp_proxy-0.4.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.4.3 This release

2 release files Full provenance

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