Skip to main content

devops-mcp

CI PyPI Downloads License: MIT

An MCP server that gives LLMs your Azure DevOps: pipelines, repos, pull requests, work items, and more. 70 tools, read-only by default.

Runs over stdio, so it works with Claude Code, GitHub Copilot, Cursor, and any other MCP client.


Quick start

You don't start the server yourself — your MCP client launches it. Install uv (it provides uvx), run az login, then register the server.

Claude Code.mcp.json in your project root:

{
  "mcpServers": {
    "devops-mcp": {
      "command": "uvx",
      "args": ["devops-mcp"],
      "env": {
        "AZDO_ORGANIZATION": "<your-org>",
        "AZDO_PROJECT": "<your-project>"
      }
    }
  }
}

VS Code / GitHub Copilot.vscode/mcp.json, same thing under servers with an explicit type:

{
  "servers": {
    "devops-mcp": {
      "type": "stdio",
      "command": "uvx",
      "args": ["devops-mcp"],
      "env": {
        "AZDO_ORGANIZATION": "<your-org>",
        "AZDO_PROJECT": "<your-project>"
      }
    }
  }
}

Restart the client, then ask your agent to list my Azure DevOps projects. Reads work straight away; writes need a gate (below).


Configuration

Everything is set by environment variable. Nothing is hardcoded.

Variable Default What it does
AZDO_ORGANIZATION Default org. Any tool call can override it.
AZDO_PROJECT Default project. Any tool call can override it.
AZDO_AUTH_TYPE default default, azure_cli, interactive, client_secret, or managed_identity.
AZDO_TENANT_ID Required for client_secret. Recommended for interactive.
AZDO_CLIENT_ID Service principal ID (client_secret only).
AZDO_CLIENT_SECRET Service principal secret (client_secret only).
AZDO_ALLOW_WRITE off true registers the write tools.
AZDO_ALLOW_DELETE off true registers the delete tools.
AZDO_LOG_LEVEL INFO DEBUG, INFO, WARNING, or ERROR. Logs go to stderr.
AZDO_AUTH_TIMEOUT_SECONDS 30 Give up on credential acquisition after this long. Raise it for slow MFA.
AZDO_EPHEMERAL_TOKEN false interactive only. true keeps the token cache in memory, so every restart re-prompts.
AZDO_TOKEN_CACHE_PROFILE interactive only. Suffix ([A-Za-z0-9_-]) that keeps two tenants' caches apart on one host.
AZDO_ATTACHMENT_ROOT Confine attachment uploads to one directory tree.

Authentication

Microsoft Entra ID OAuth 2.0 via azure-identity.

AZDO_AUTH_TYPE How it signs in Use it for
default Tries env vars, Azure CLI, managed identity in order. Never prompts. Most setups
azure_cli Your az login session. Local dev
interactive Opens a browser. Supports MFA. Local dev without the CLI
client_secret Service principal. Needs tenant, client ID, and secret. CI/CD
managed_identity Azure managed identity. No credentials to store. Azure-hosted workloads

interactive prompts once. After that the token cache (encrypted by the OS secret store) makes restarts silent, until the refresh token expires. Two things worth knowing:

  • On headless Linux with no secret store, the cache falls back to memory and the server logs a warning. AZDO_EPHEMERAL_TOKEN=true silences it.
  • Two sessions signed in to different tenants share cache files and clobber each other. Give each a distinct AZDO_TOKEN_CACHE_PROFILE.

Security

Write and delete tools are off by default. They aren't registered, so the agent can't see them, let alone call them. Set AZDO_ALLOW_WRITE=true and AZDO_ALLOW_DELETE=true to opt in.

Deletes are recoverable by default. devops_delete_work_item sends the item to the recycle bin; destroy=true is opt-in per call, and Azure DevOps gates it behind a separate admin permission. devops_delete_query is always soft — restore with devops_update_query(undelete=true), though permissions on the item don't come back.

Attachment URLs are parsed, then thrown away. An <img src> from a work item description is untrusted input, so following it with a bearer token attached would leak that token. Instead the URL is validated (https, dev.azure.com or {org}.visualstudio.com, the attachment route, the right org), only the GUID and file name are kept, and the request is rebuilt against the resolved org. Rejections happen before a token is minted. A URL that fails is counted, never echoed back.

Uploads are images only, checked by bytes. devops_upload_work_item_attachment requires a .png/.jpg/.jpeg/.gif/.webp name and matching magic bytes, so id_rsa renamed to x.png is refused. AZDO_ATTACHMENT_ROOT restricts it further.

Secrets are never returned. Service connection authorization.parameters are filtered through an allowlist of non-secret fields; variable group secrets are dropped, as are non-secret variables with credential-shaped names. Withheld field names are reported so an agent knows a credential exists.

Stdout belongs to the MCP transport. All logs go to stderr. Don't redirect stdout.

Retries are safe by construction. 429 retries on any method; 502/503/504 retries only on reads, because a gateway error on a write may land after the write committed. Responses over 5 MB are refused with a hint to narrow the query.


Client setup

Every client launches the same stdio process; only the JSON wrapper differs. Claude and Cursor use mcpServers, VS Code uses servers plus "type": "stdio".

Client Config file
Claude Code .mcp.json in the project root, or ~/.claude.json for local scope
Claude Desktop %APPDATA%\Claude\claude_desktop_config.json (Windows), ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
VS Code / GitHub Copilot .vscode/mcp.json in the workspace root
Cursor .cursor/mcp.json, or ~/.cursor/mcp.json for all projects

Keep these files out of source control — they can hold a client secret. Restart the client after editing; the write and delete gates are read once at startup.

Enabling writes. Add "AZDO_ALLOW_WRITE": "true" to the env block, and "AZDO_ALLOW_DELETE": "true" for deletes.

Service principal (CI/CD). Swap the env block for:

{
  "AZDO_AUTH_TYPE": "client_secret",
  "AZDO_TENANT_ID": "<your-tenant-id>",
  "AZDO_CLIENT_ID": "<your-client-id>",
  "AZDO_CLIENT_SECRET": "<your-client-secret>",
  "AZDO_ORGANIZATION": "<your-org>",
  "AZDO_PROJECT": "<your-project>"
}

Local checkout. Point the client at your clone — uv syncs the environment before launching, and --directory means the client's working directory doesn't matter:

{
  "command": "uv",
  "args": ["--directory", "/path/to/devops-mcp", "run", "devops-mcp"]
}

Two tenants on one host. Register one entry per tenant, each with its own AZDO_TENANT_ID and AZDO_TOKEN_CACHE_PROFILE (prod, dev, …). Each signs in once, then restarts silently as its own account. One entry already reaches any org you have access to — organization and project are per-call arguments — so add a second only for a different account or tenant.


Tools

70 tools across 9 domains. The gate column says when a tool is registered: default always, write under AZDO_ALLOW_WRITE=true, delete under AZDO_ALLOW_DELETE=true.

Pipelines (10)

Tool Gate Description
devops_list_pipelines default List a project's pipelines
devops_list_pipeline_runs default List runs of one pipeline
devops_get_pipeline_run default Get one run
devops_get_build default Get a build by buildId
devops_list_run_logs default List a build's log files
devops_get_run_timeline default Failure-filtered timeline with inline errors — start here for "why did it fail"
devops_get_run_log_content default Read log text, paged (500 lines by default) with tail and line windows
devops_search_run_log default Grep a log; only matching lines come back
devops_list_build_artifacts default List a build's artifacts
devops_run_pipeline write Queue a run; override branch, parameters, variables, or skip stages

Repositories (8)

Tool Gate Description
devops_list_repositories default List repos
devops_get_repository default Get one repo
devops_list_branches default List branches
devops_get_file_content default Read a text file at a branch or commit
devops_get_repository_image default Read a committed image and return it as viewable content
devops_list_repository_items default Browse files and folders at a chosen depth
devops_list_commits default List commits, filtered by branch, author, or date
devops_get_commit default Get one commit, optionally with changed paths

Pull requests (17)

Tool Gate Description
devops_get_pull_request default Get one PR
devops_list_pull_requests default List PRs by status, branch, creator, reviewer, or label
devops_list_pull_request_threads default List comment threads
devops_get_pull_request_thread default Get one thread and its comments
devops_list_pull_request_iterations default List pushes to the PR
devops_get_pull_request_changes default List changed files in an iteration
devops_create_pull_request write Create a PR, optionally linking work items
devops_update_pull_request write Edit title, description, status, draft, target branch, or auto-complete
devops_complete_pull_request write Merge it — irreversible, so confirm strategy and branch deletion first
devops_abandon_pull_request write Abandon without merging
devops_vote_pull_request write Vote (10 approve, 5 with suggestions, 0 reset, -5 waiting, -10 reject)
devops_tag_pull_request write Add labels
devops_link_work_items_to_pull_request write Link work items
devops_create_pull_request_thread write Start a thread, general or inline on a file and line
devops_set_pull_request_thread_status write Set thread status (active, fixed, wontFix, …)
devops_add_pull_request_comment write Reply in a thread
devops_update_pull_request_comment write Edit a comment

Work items (15)

Tool Gate Description
devops_get_work_item default Get one work item
devops_list_work_items default Fetch up to 200 by ID
devops_query_work_items default Run WIQL and hydrate the results
devops_list_work_item_types default List types (Bug, Task, Epic) and reference names
devops_list_work_item_fields default List field definitions
devops_list_work_item_attachments default List attachments from both surfaces — the Attachments tab and images pasted into fields
devops_get_work_item_attachment default Download an attachment and return it as viewable image content
devops_create_work_item write Create one (large-text fields save as markdown)
devops_update_work_item write Update fields (large-text fields save as markdown)
devops_update_work_item_tags write Add or remove tags without replacing the whole set
devops_add_work_item_comment write Comment on a work item
devops_update_work_item_comment write Edit a comment
devops_upload_work_item_attachment write Upload an image and get paste-ready embed snippets
devops_link_work_item_attachment write Put an uploaded attachment on the Attachments tab
devops_delete_work_item delete Recycle-bin it; destroy=true erases it for good

Saved queries (7)

Address a query by GUID or by path — Shared Queries/Website team/All Bugs. The roots are Shared Queries and My Queries; neither can be renamed or moved here, because Azure DevOps allows it and it breaks path addressing for everything below.

Tool Gate Description
devops_list_queries default Browse both roots, or search by query name (name_filter)
devops_get_query default Get one query or folder with its WIQL, optionally with children
devops_run_query default Run a saved query and hydrate the results
devops_create_query write Save a new WIQL query; validate_only=true checks it without saving
devops_create_query_folder write Create a folder
devops_update_query write Rename, re-WIQL, move, or undelete. Last write wins — no ETag on this resource
devops_delete_query delete Soft-delete a query or folder (folders cascade)

To recover a deleted query: read the parent folder with devops_get_query(depth=1, include_deleted=true), take the child's GUID, then devops_update_query(query=<guid>, undelete=true). A deleted item answers to its GUID only — include_deleted on its path still returns 404.

Discovery (2)

Tool Gate Description
devops_list_projects default List an org's projects
devops_list_teams default List a project's teams; mine=true narrows to yours

Advanced Security (3)

Needs GitHub Advanced Security for Azure DevOps enabled on the repo.

Tool Gate Description
devops_list_advanced_security_alerts default List alerts by type, state, severity, rule, tool, or branch
devops_get_advanced_security_alert default Get one alert. expand=validationFingerprint can return secrets in cleartext
devops_update_advanced_security_alert write Dismiss, re-activate, or mark fixed

Service connections (2)

Tool Gate Description
devops_list_service_connections default List connections, filtered by type, name, or auth scheme
devops_get_service_connection default Get one, with credential values redacted

Health is three separate signals: is_ready, is_disabled, and is_outdated (stored config no longer matches the real resource — usually an expired secret). A ready connection can still fail to authenticate.

Variable groups (6)

Tool Gate Description
devops_list_variable_groups default List groups; values are omitted unless asked for
devops_get_variable_group default Get one group with redacted values
devops_set_variable_group_variables write Create or update named variables; everything else is left alone
devops_create_variable_group write Create a group with its initial variables
devops_remove_variable_group_variables delete Remove named variables; all-or-nothing unless ignore_missing=true
devops_delete_variable_group delete Delete a group — permanent, there is no recycle bin for Library items

Azure DevOps has no variable-level write API: the only update verb replaces the whole group, so the variable tools read, merge, and write it back for you. Two consequences. A secret you are not touching is re-sent without its value and survives, but a secret cannot be turned back into a plain variable unless you supply the new value in the same call. And there is no ETag or revision check on this API, so a change another actor makes between the read and the write is lost — compare modified_on if that matters.

Key Vault-backed groups are refused by the variable tools; change the secret in the vault instead. They can still be deleted, which removes nothing from the vault.


Notes

API versions. Pipelines, repos, discovery, work item schema, service connections, and variable groups use v7.1. PR tools, work item writes, and saved queries use v7.2-preview. Advanced Security uses v7.2-preview.1 on advsec.dev.azure.com. See the Azure DevOps REST API docs.

run_id and build_id are the same number. A Pipelines run ID is the Build API's buildId, so IDs from one API work in the other.

Reading logs cheaply. Try devops_get_run_timeline first — it often answers "why did it fail" with no log fetch at all. Then devops_search_run_log to grep. Only then devops_get_run_log_content, which pages rather than dumping.

Requires MCP Python SDK 2.0+. Releases up to 1.3.0 need SDK 1.x and won't start on 2.x — the SDK dropped mcp.server.fastmcp. Upgrade to 1.4.0 or later.


Development

uv sync                        # install
uv run pytest                  # tests (no network, no credentials)
uv run ruff check src tests    # lint
uv run python -m build         # build

Release files for devops-mcp 1.9.0

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

Source distribution (sdist)

Source distribution for devops-mcp 1.9.0
File Size Uploaded
devops_mcp-1.9.0.tar.gz 233.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for devops-mcp 1.9.0
File Interpreter ABI Platform
devops_mcp-1.9.0-py3-none-any.whl Python 3 none any Details

Total release size: 379.4 kB

Release files / devops_mcp-1.9.0.tar.gz

Download URL devops_mcp-1.9.0.tar.gz
Size 233.9 kB
Tags Source
SHA-256 checksum
How to use checksums
ad4fcc9fe488805d9fb4c6ff3b4038e7ec29a91098af62ae472db2a813acb0d7
BLAKE2b-256 checksum
How to use checksums
f2ec29d3fc3ce42684920298037348ca47600f8e1e742f269cb6f387e8292a99
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 27, 2026.

Transparency log

Release files / devops_mcp-1.9.0-py3-none-any.whl

Download URL devops_mcp-1.9.0-py3-none-any.whl
Size 145.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3f3db42cd74d7d92567d17cec874c8258ee96496f78a87d8e51b39aef937d5f9
BLAKE2b-256 checksum
How to use checksums
eaaba910c8f2274f6d5c1a2d2fa5d11515ec61e8fe8825eb3e575c8c8cb08337
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 27, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.9.0 This release

2 release files

1.8.0

2 release files

1.7.0

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release 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