Repository navigation
Remote HTTP Mode
Besides the classic stdio transport (used by Claude Desktop, Antigravity, Smithery installs, etc.), the server can expose its tools over HTTP, so that one running instance can be shared by several MCP clients or run inside a container.
Caution
Destructive actions: in HTTP mode the server exposes exactly the same tools (including the generic api_call client) as in stdio mode. Anyone who can reach the endpoint and pass authentication can act on Azure DevOps with the configured credentials. Never expose the server on a public network without authentication and HTTPS.
The HTTP listener is enabled when a port is provided:
# from the repository folder (dist/ is already built and committed)
node dist/index.js --port 8080Port resolution order: --port <n> β PORT β MCP_PORT.
Listen address: 127.0.0.1 by default, so only clients on the same machine can connect. To accept connections from other machines (or inside a container) set --host 0.0.0.0 or MCP_HOST=0.0.0.0, and always enable authentication in that case:
node dist/index.js --port 8080 --host 0.0.0.0 --auth-token a-long-random-secretNote
On Windows the HTTP listener only starts when a port is set explicitly. On Linux/macOS (and inside containers) it defaults to port 8080 when no port is set, still bound to 127.0.0.1 unless MCP_HOST / --host says otherwise. The stdio transport is always active as well, so the same process can still be used by a local stdio client.
| Endpoint | Purpose |
|---|---|
POST/GET/DELETE /mcp |
Streamable HTTP transport (current MCP specification, protocol 2025-03-26+) |
GET /sse + POST /messages?sessionId=β¦
|
Legacy SSE transport (protocol 2024-11-05, for older clients) |
GET /.well-known/mcp/server-card.json |
Server card (name, version, tool list) |
GET /.well-known/oauth-protected-resource |
OAuth 2.1 protected-resource metadata (RFC 9728) |
GET /.well-known/oauth-authorization-server |
OAuth 2.0 authorization-server metadata (RFC 8414, also served at /.well-known/openid-configuration) |
GET /oauth/authorize + POST /oauth/token
|
OAuth 2.0 proxy endpoints for Microsoft Entra ID v2.0 (strip RFC 8707 resource parameter and normalize Azure DevOps scopes) |
GET / (any other path) |
Health check, returns OK
|
Authentication protects /mcp, /sse and /messages. You can combine the options below.
| Variable / Flag | Description |
|---|---|
MCP_AUTH_TOKEN / --auth-token
|
Static shared secret. Clients must send Authorization: Bearer <token>. Compared in constant time. |
ENTRA_TENANT_ID / --tenant-id
|
Enables Microsoft Entra ID token validation. One tenant ID, a comma-separated list, or common / organizations. |
ENTRA_CLIENT_ID / --client-id
|
Your Entra app registration (client) ID. Tokens whose audience is this ID (or api://<id>) are accepted, in addition to Azure DevOps tokens. |
ENTRA_ALLOWED_USERS / --allowed-users
|
Optional comma-separated allow-list of user e-mails/UPNs. |
If none of them is set, the server starts in open mode (and logs a warning). Use open mode only on localhost or for testing.
Brute-force protection: more than 5 failed authentication attempts (requests carrying an invalid Authorization header) from the same IP within 10 minutes lock that IP out for 30 minutes (HTTP 429 with Retry-After). Unauthenticated requests without an Authorization header are treated as RFC 9728 OAuth discovery probes and return 401 WWW-Authenticate without counting toward the lockout.
- Tokens are validated locally (RS256 signature against Microsoft's public keys, expiry, tenant, audience, optional user allow-list).
- If the token was issued for the Azure DevOps resource (
499b84ac-1321-427f-aa17-267ca6975798), it is also used to call the Azure DevOps APIs on behalf of the signed-in user, so no PAT is needed. Provide the organization via theX-Azure-DevOps-Orgheader or theAZURE_DEVOPS_ORGvariable. - MCP clients that support OAuth discover the sign-in flow automatically via
/.well-known/oauth-protected-resource(returned in theWWW-Authenticateheader of a401response) and/.well-known/oauth-authorization-server. - Because Microsoft Entra ID v2.0 rejects the RFC 8707
resourceparameter (AADSTS9010010) sent by standard MCP OAuth clients, the built-in/oauth/authorizeand/oauth/tokenendpoints stripresourceand normalize thescopeparameter (499b84ac-1321-427f-aa17-267ca6975798/.default offline_access) before redirecting/proxying to Microsoft Entra ID.
Credentials are resolved per MCP session (and per authenticated user), in this order of preference:
-
Per-session / per-authenticated-user credentials sent by the client (multi-user):
-
Headers (recommended):
X-Azure-DevOps-Org,X-Azure-DevOps-PAT,X-Azure-DevOps-Username(optional),X-Azure-DevOps-Project(optional). - Query parameters
organization,pat,username,defaultProject, orconfig=<base64 JSON>(Smithery format). Avoid them when possible: query strings can end up in proxy/access logs. - The
connection_configuretool: in HTTP mode credentials are kept in memory only and are never written to disk. When signed in via Microsoft Entra ID OAuth, onlyurlis required (usernameandtokenare optional β omittokento use your Microsoft OAuth session, or provide a custom PAT to override the OAuth identity and connect as a different DevOps user). - A Microsoft Entra ID token for Azure DevOps (see above).
-
Headers (recommended):
-
Server-wide credentials (single-user deployment):
AZURE_DEVOPS_ORG,AZURE_DEVOPS_PAT,AZURE_DEVOPS_USERNAME,AZURE_DEVOPS_PROJECT(or the flags--org,--pat,--username,--project).
Warning
If you set server-wide credentials, always enable MCP_AUTH_TOKEN or Entra ID: otherwise anyone reaching the endpoint can use your PAT.
Stateless HTTP clients & session lifecycle:
- For authenticated users (identified by verified Microsoft Entra ID UPN or hashed
Authorization: Bearertoken), credentials configured viaconnection_configureor headers are kept in an in-memory per-user store (12-hour TTL). This allows stateless MCP HTTP clientsβwhich open a new MCP session or send direct tool calls without reusingMcp-Session-Idβto retain their configured organization, project, and optional custom PAT across tool calls while preserving strict isolation between users. - When unauthenticated, credentials remain strictly scoped to the
Mcp-Session-Idsession. - Streamable HTTP
Mcp-Session-Idsessions idle for more than 30 minutes are dropped. If a client sends a request with an expired/unknownMcp-Session-Id(e.g., after a container restart), the server transparently handles the request using a stateless transport backed by the authenticated user's in-memory context.
The repository contains a multi-stage Dockerfile (Node 22, runs as the unprivileged node user, listens on 0.0.0.0:8080 inside the container via MCP_HOST=0.0.0.0):
docker build -t mcp-azure-devops .
# Multi-user: every client sends its own X-Azure-DevOps-* headers
docker run -p 8080:8080 -e MCP_AUTH_TOKEN=a-long-random-secret mcp-azure-devops
# Single-user: server-wide credentials (authentication is mandatory here)
docker run -p 8080:8080 \
-e MCP_AUTH_TOKEN=a-long-random-secret \
-e AZURE_DEVOPS_ORG=my-org \
-e AZURE_DEVOPS_PAT=<your-pat> \
mcp-azure-devopsClient endpoint: http://localhost:8080/mcp
If you publish the container beyond your machine, put it behind a reverse proxy that terminates HTTPS.
Most modern clients accept a URL plus custom headers. The exact key name depends on the client (url, serverUrl, β¦):
{
"mcpServers": {
"azure-devops": {
"url": "http://localhost:8080/mcp",
"headers": {
"Authorization": "Bearer a-long-random-secret",
"X-Azure-DevOps-Org": "my-org",
"X-Azure-DevOps-PAT": "<your-pat>"
}
}
}
}Use the mcp-remote bridge:
{
"mcpServers": {
"azure-devops": {
"command": "npx",
"args": [
"-y", "mcp-remote", "http://localhost:8080/mcp",
"--header", "Authorization: Bearer a-long-random-secret",
"--header", "X-Azure-DevOps-Org: my-org",
"--header", "X-Azure-DevOps-PAT: <your-pat>"
]
}
}
}curl -i -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer a-long-random-secret" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'The response contains an Mcp-Session-Id header. Send it with every following request.