Ado Gateway
БесплатноНе проверенA self-hosted remote MCP server for Azure DevOps that bridges OAuth 2.1 between MCP clients and Microsoft Entra ID, enabling any OAuth-capable client (e.g., Cla
Описание
A self-hosted remote MCP server for Azure DevOps that bridges OAuth 2.1 between MCP clients and Microsoft Entra ID, enabling any OAuth-capable client (e.g., Claude Code, Claude Desktop) to access Azure DevOps resources.
README
A self-hosted remote MCP server for Azure DevOps that works with any OAuth-capable MCP client — Claude Code, Claude Desktop, ChatGPT, custom agents — today.
Microsoft ships an official remote server at https://mcp.dev.azure.com/{org},
but it only supports VS Code and Visual Studio. Every other client is blocked on
Microsoft Entra ID shipping OAuth Dynamic Client Registration
(official FAQ),
and Entra DCR is
"not in our roadmap for now".
This gateway bridges the gap with the OAuth-proxy / simulated-DCR pattern: it speaks full MCP OAuth 2.1 (with DCR) to the client, and confidential-client OAuth to Entra, translating between the two.
MCP client ──OAuth 2.1 (simulated DCR + PKCE)──▶ Gateway ──Entra login──▶ user token (aud = gateway app)
│
├── On-Behalf-Of ──▶ ADO-audience token, per user
└── stdio ──▶ @azure-devops/mcp ──▶ dev.azure.com
- Front door — FastMCP
AzureProviderexposes/register(RFC 7591), Protected Resource Metadata (RFC 9728), Authorization Server Metadata (RFC 8414) and PKCE. Clients self-register against the gateway; Entra only ever sees one static app registration. - Backend — microsoft/azure-devops-mcp (stdio) proxied to Streamable HTTP, one subprocess per user token, so every Azure DevOps call runs under the calling user's identity (real per-user audit in ADO).
Quick start
1. Register one Entra application (once)
Choose the sign-in audience to match your use:
- Internal / single-tenant —
--sign-in-audience AzureADMyOrg, and setADO_GW_TENANTto your directory GUID. Only accounts in that directory can sign in. - Multi-tenant —
--sign-in-audience AzureADMultipleOrgs, and leaveADO_GW_TENANT=organizations. Any Entra work/school account can sign in.
az ad app create --display-name "ADO MCP Gateway" \
--web-redirect-uris "http://localhost:8090/auth/callback" \
--sign-in-audience AzureADMyOrg # or AzureADMultipleOrgs
az ad app credential reset --id <APP_ID> --append # → client secret
az ad sp create --id <APP_ID>
# Delegated permission: Azure DevOps / user_impersonation
az ad app permission add --id <APP_ID> \
--api 499b84ac-1321-427f-aa17-267ca6975798 \
--api-permissions ee69721e-6c3a-468f-a9ec-302d16a4c599=Scope
# Expose the app's OWN scope and force v2 access tokens (see Trap 1)
az rest --method PATCH \
--url "https://graph.microsoft.com/v1.0/applications/<OBJECT_ID>" \
--headers 'Content-Type=application/json' --body "{
\"identifierUris\": [\"api://<APP_ID>\"],
\"api\": {
\"requestedAccessTokenVersion\": 2,
\"oauth2PermissionScopes\": [{
\"id\": \"$(uuidgen)\", \"value\": \"user_impersonation\", \"type\": \"User\",
\"isEnabled\": true,
\"adminConsentDisplayName\": \"Access the ADO MCP gateway\",
\"adminConsentDescription\": \"Access the ADO MCP gateway on behalf of the signed-in user\",
\"userConsentDisplayName\": \"Access the ADO MCP gateway\",
\"userConsentDescription\": \"Access the ADO MCP gateway on your behalf\"
}]
}
}"
Optional: az ad app permission admin-consent --id <APP_ID> (needs an Entra
admin) grants the app for the whole directory, so users never see a consent
screen.
2. Configure
cp .env.example .env # fill in client id / secret / org / tenant
| Variable | Required | Notes |
|---|---|---|
ADO_GW_CLIENT_ID / ADO_GW_CLIENT_SECRET |
yes | The Entra app above |
ADO_GW_ORG |
yes | Azure DevOps organization (positional arg of the backend) |
ADO_GW_TENANT |
no | Login directory. A tenant GUID for single-tenant/internal apps; organizations (default) for multi-tenant. See "Login directory" |
ADO_GW_BASE_URL |
prod | Public HTTPS URL of the gateway (defaults to http://localhost:8090) |
ADO_GW_TOKEN_MODE |
no | passthrough (default) or phantom. See "Token modes" |
3. Run
Local (uv):
uv sync
set -a; source .env; set +a
uv run python server.py
Docker — pull the prebuilt image (published to GHCR by CI on every push to
main):
docker run -p 8090:8090 --env-file .env ghcr.io/baodq97/ado-mcp-gateway:latest
Or build it yourself:
docker build -t ado-mcp-gateway .
docker run -p 8090:8090 --env-file .env ado-mcp-gateway
The image bakes @azure-devops/mcp and a Node runtime, so there is no
per-request npx download.
4. Connect a client
claude mcp add --transport http ado-gateway http://localhost:8090/mcp
# then: /mcp → ado-gateway → Authenticate
Any MCP client that supports the OAuth 2.1 authorization flow works the same way
— point it at <base-url>/mcp.
Token modes
The gateway can hand the client two different kinds of token. Pick with
ADO_GW_TOKEN_MODE.
passthrough (default) |
phantom |
|
|---|---|---|
| Client receives | The real Entra access + refresh tokens | Gateway-minted JWTs (aud = gateway URL) |
| Gateway state | None — restarts don't log anyone out; replicas need no shared store | Token store (needs a fixed jwt_signing_key + shared client_storage for multi-replica) |
| Revocation | At Entra (revoke sessions / Conditional Access) | At the gateway |
RFC 9728 aud == resource clients |
Rejected — token aud is the Entra app, not the gateway URL |
Accepted |
Passthrough is stateless and delegates the whole token lifecycle to Entra
(revocation) and Azure DevOps (permissions) — the right default when the client
is a trusted developer tool. It overrides four OAuthProxy methods, so pin the
fastmcp version.
Phantom is FastMCP's designed path: the gateway mints its own JWTs and keeps the upstream tokens server-side. Use it for clients that enforce RFC 9728 (access-token audience must equal the resource) — those reject passthrough tokens.
Login directory (multi-tenant accounts)
There is no tenant picker: organizations always signs into the account's
home tenant. If the ADO organization lives in a directory where your users are
guests, set ADO_GW_TENANT to that directory's GUID. Discover which tenant backs
an org without logging in:
curl -sI https://dev.azure.com/{org}/_apis/connectionData | grep -i x-vss-resourcetenant
Traps worth knowing
Each of these cost real debugging time; they are baked into the code but explained here so you can adapt it.
Never request the ADO scope as
required_scopes. Asking for499b84ac-.../user_impersonationdirectly makes Entra mint a v1 token withaud = Azure DevOps(iss = sts.windows.net/...) that the gateway cannot validate. Instead the app exposes its ownapi://{app_id}/user_impersonationscope (withrequestedAccessTokenVersion: 2); the ADO token is obtained later via On-Behalf-Of at point of use. The ADO scope goes inadditional_authorize_scopesonly, which records the consent OBO needs without changing the issued token's audience.Multi-tenant issuer validation. v2 tokens carry
iss = login.microsoftonline.com/{tenantGUID}/v2.0, which never equals the literal.../organizations/v2.0the provider derives, so the gateway disables the issuer check (the same thingAzureProvider.from_b2cdoes) — audience validation still rejects foreign tokens. OBO must likewise target the user's tenant (tidclaim), not/organizations, or Entra rejects the confidential-client grant.ESTS propagation lag. After PATCHing
identifierUris/oauth2PermissionScopes, Microsoft Graph shows the scope immediately but the token service keeps returningAADSTS65005for ~2–15 minutes. Don't re-debug config that is already correct — probe the authorize URL until the error clears.
Bonus: the async OnBehalfOfCredential needs the aiohttp extra
(azure.identity.aio fails at runtime without it, surfacing as a cryptic
JSON-RPC -32602), and @azure-devops/mcp requires the org positionally
(demandOption: true), so omitting it kills the backend at spawn.
HTTP Basic client auth
Some clients send client credentials as an Authorization: Basic header on
/token (client_secret_basic). RFC 6749 §2.3.1 requires token endpoints to
accept that, but the MCP SDK's handler only reads the form body. A small ASGI
shim (BasicAuthToFormShim) decodes the header into form fields so both styles
work.
Production checklist
-
ADO_GW_BASE_URLset to the public HTTPS URL; addhttps://<host>/auth/callbackto the app's redirect URIs. - Phantom mode + multiple replicas: set a fixed
jwt_signing_keyand a sharedclient_storage(e.g. Redis) wrapped inFernetEncryptionWrapper. Passthrough mode needs none of this. - Replace the client secret with a managed-identity federated credential (OBO supports client assertions), or at least store it in a vault.
- Tighten
allowed_client_redirect_uris(currently allow-all). - Budget ~150 MB per concurrent user (one Node subprocess each, LRU-capped).
How it works
server.py is the whole gateway (~250 lines):
PassthroughAzureProvider— the fourOAuthProxyoverrides for passthrough mode.build_auth()— configures the FastMCPAzureProvider(or the passthrough subclass) and disables issuer validation for the multi-tenant case.PerUserAdoBackends— per-user OBO exchange plus an LRU pool of@azure-devops/mcpsubprocesses, one per ADO token.BasicAuthToFormShim— the RFC 6749 §2.3.1 client-auth shim.
License
MIT.
Установка Ado Gateway
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/baodq97/ado-mcp-gatewayFAQ
Ado Gateway MCP бесплатный?
Да, Ado Gateway MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Ado Gateway?
Нет, Ado Gateway работает без API-ключей и переменных окружения.
Ado Gateway — hosted или self-hosted?
Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.
Как установить Ado Gateway в Claude Desktop, Claude Code или Cursor?
Открой Ado Gateway на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectCompare Ado Gateway with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
