devops-mcp 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. devops_mcp-1.0.0/LICENSE +21 -0
  2. devops_mcp-1.0.0/PKG-INFO +319 -0
  3. devops_mcp-1.0.0/README.md +291 -0
  4. devops_mcp-1.0.0/pyproject.toml +80 -0
  5. devops_mcp-1.0.0/setup.cfg +4 -0
  6. devops_mcp-1.0.0/src/devops_mcp/__init__.py +1 -0
  7. devops_mcp-1.0.0/src/devops_mcp/_app.py +40 -0
  8. devops_mcp-1.0.0/src/devops_mcp/client.py +893 -0
  9. devops_mcp-1.0.0/src/devops_mcp/models.py +954 -0
  10. devops_mcp-1.0.0/src/devops_mcp/server.py +30 -0
  11. devops_mcp-1.0.0/src/devops_mcp/tools/__init__.py +1 -0
  12. devops_mcp-1.0.0/src/devops_mcp/tools/pipelines.py +412 -0
  13. devops_mcp-1.0.0/src/devops_mcp/tools/pull_requests.py +1038 -0
  14. devops_mcp-1.0.0/src/devops_mcp/tools/repositories.py +182 -0
  15. devops_mcp-1.0.0/src/devops_mcp/tools/work_items.py +495 -0
  16. devops_mcp-1.0.0/src/devops_mcp.egg-info/PKG-INFO +319 -0
  17. devops_mcp-1.0.0/src/devops_mcp.egg-info/SOURCES.txt +27 -0
  18. devops_mcp-1.0.0/src/devops_mcp.egg-info/dependency_links.txt +1 -0
  19. devops_mcp-1.0.0/src/devops_mcp.egg-info/entry_points.txt +2 -0
  20. devops_mcp-1.0.0/src/devops_mcp.egg-info/requires.txt +4 -0
  21. devops_mcp-1.0.0/src/devops_mcp.egg-info/top_level.txt +1 -0
  22. devops_mcp-1.0.0/tests/test_auth_lock_timeout.py +199 -0
  23. devops_mcp-1.0.0/tests/test_build_url.py +79 -0
  24. devops_mcp-1.0.0/tests/test_create_pull_request_work_item_linking.py +310 -0
  25. devops_mcp-1.0.0/tests/test_model_validation.py +283 -0
  26. devops_mcp-1.0.0/tests/test_paginate_results.py +203 -0
  27. devops_mcp-1.0.0/tests/test_request_with_retry.py +479 -0
  28. devops_mcp-1.0.0/tests/test_smoke.py +10 -0
  29. devops_mcp-1.0.0/tests/test_token_cache.py +314 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ryan James
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,319 @@
1
+ Metadata-Version: 2.4
2
+ Name: devops-mcp
3
+ Version: 1.0.0
4
+ Summary: An MCP server for interacting with Azure DevOps
5
+ Author: Ryan James
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ryanmichaeljames/devops-mcp
8
+ Project-URL: Repository, https://github.com/ryanmichaeljames/devops-mcp
9
+ Project-URL: Issues, https://github.com/ryanmichaeljames/devops-mcp/issues
10
+ Project-URL: Changelog, https://github.com/ryanmichaeljames/devops-mcp/blob/main/CHANGELOG.md
11
+ Keywords: mcp,model-context-protocol,azure-devops,devops,copilot,llm
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: azure-identity>=1.16.0
24
+ Requires-Dist: httpx<1.0,>=0.20.0
25
+ Requires-Dist: mcp[cli]>=1.27.0
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Dynamic: license-file
28
+
29
+ # devops-mcp
30
+
31
+ [![PyPI](https://img.shields.io/pypi/v/devops-mcp)](https://pypi.org/project/devops-mcp/)
32
+ [![Python](https://img.shields.io/pypi/pyversions/devops-mcp)](https://pypi.org/project/devops-mcp/)
33
+ [![License: MIT](https://img.shields.io/github/license/ryanmichaeljames/devops-mcp)](LICENSE)
34
+
35
+ An [MCP](https://modelcontextprotocol.io/) server that exposes Azure DevOps as tools for LLMs — pipelines, repositories, pull requests, and work items. Built with [FastMCP](https://github.com/modelcontextprotocol/python-sdk) over stdio transport.
36
+
37
+ Communicates over **stdio** and works with GitHub Copilot, Claude Code, and any MCP-compatible client.
38
+
39
+ ---
40
+
41
+ ## Quick Start
42
+
43
+ **1. Install dependencies**
44
+
45
+ ```bash
46
+ uv sync
47
+ ```
48
+
49
+ **2. Configure** — add to your MCP client config (see [MCP Client Setup](#mcp-client-setup) below).
50
+
51
+ **3. Run the server**
52
+
53
+ ```bash
54
+ uv run devops-mcp
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Installation
60
+
61
+ ### Prerequisites
62
+
63
+ - Python `>=3.10`
64
+ - [uv](https://docs.astral.sh/uv/) (recommended)
65
+ - A Microsoft Entra ID identity with access to Azure DevOps
66
+
67
+ ### Install dependencies
68
+
69
+ ```bash
70
+ uv sync
71
+ ```
72
+
73
+ ### Build the package
74
+
75
+ ```bash
76
+ uv run python -m build
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Configuration
82
+
83
+ All configuration is driven by environment variables — no secrets in code, no hardcoded org names or tenant IDs.
84
+
85
+ | Variable | Required? | Default | Description |
86
+ |---|---|---|---|
87
+ | `AZDO_AUTH_TYPE` | No | `default` | Authentication method. One of: `default`, `azure_cli`, `interactive`, `client_secret`, `managed_identity`. `default` tries all credential sources in order (environment variables, Azure CLI, managed identity) and is the right choice for local development. See [Authentication](#authentication). |
88
+ | `AZDO_ORGANIZATION` | No | — | Default Azure DevOps organization name. Can be overridden per tool call. Required if not supplied per call. |
89
+ | `AZDO_PROJECT` | No | — | Default Azure DevOps project name. Can be overridden per tool call. Required if not supplied per call. |
90
+ | `AZDO_TENANT_ID` | Conditional | — | Microsoft Entra ID tenant ID. Required for `client_secret`. Recommended for `interactive` to constrain sign-in to the correct tenant. |
91
+ | `AZDO_CLIENT_ID` | `client_secret` only | — | Service principal client ID. |
92
+ | `AZDO_CLIENT_SECRET` | `client_secret` only | — | Service principal client secret. |
93
+ | `AZDO_LOG_LEVEL` | No | `INFO` | Log verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR`. All logs go to stderr; stdout is reserved for MCP stdio transport. |
94
+ | `AZDO_ALLOW_WRITE` | No | off | Set `true` to register create, update, tag, link, and comment (write) tools. When unset the server is read-only — write tools are not visible to the agent at all. |
95
+ | `AZDO_ALLOW_DELETE` | No | off | Set `true` to register delete tools. When unset, delete tools are not visible to the agent. |
96
+ | `AZDO_EPHEMERAL_TOKEN` | No | `false` | **Interactive auth only.** When `false` (the default), the MSAL token cache is persisted to disk via the OS secret store (Windows DPAPI, macOS Keychain, Linux libsecret), and an `AuthenticationRecord` sidecar is written to `~/.devops-mcp/auth-record.json` so subsequent server restarts authenticate silently without a new browser prompt. Set `true`, `1`, or `yes` to use an in-memory-only cache (no disk cache, no sidecar) — re-prompts on every restart. Invalid values fall back to `false` with a logged warning. Has no effect on any auth type other than `interactive`. |
97
+ | `AZDO_TOKEN_CACHE_PROFILE` | No | — | **Interactive auth only.** A filename-safe suffix (`[A-Za-z0-9_-]`) appended to the MSAL cache name and the `AuthenticationRecord` sidecar so two server instances signed in to **different tenants/accounts** on the same host keep separate caches instead of overwriting each other's pinned account. Omit (or leave empty) for a single-tenant setup — the original shared filenames are used. Characters outside `[A-Za-z0-9_-]` raise an error rather than being silently dropped (sanitizing could collapse two distinct profiles into one shared cache). |
98
+ | `AZDO_AUTH_TIMEOUT_SECONDS` | No | `30` | Maximum seconds to wait for credential acquisition before failing with an auth error. Applies to all auth types. Invalid or non-positive values fall back to `30`. Increase this in slow-network or MFA-heavy environments. |
99
+
100
+ ---
101
+
102
+ ## Authentication
103
+
104
+ The server uses **Microsoft Entra ID (Azure AD) OAuth 2.0** via the [`azure-identity`](https://pypi.org/project/azure-identity/) library. Set `AZDO_AUTH_TYPE` to select a method.
105
+
106
+ | `AZDO_AUTH_TYPE` | Description | Best for |
107
+ |---|---|---|
108
+ | `default` *(default)* | `DefaultAzureCredential` — tries environment variables, Azure CLI session, managed identity, and other sources in order. Does not prompt in-process. | **Recommended — works everywhere** |
109
+ | `azure_cli` | Uses the active Azure CLI session (`az login`). Does not prompt in-process. | Local development with an existing CLI session |
110
+ | `interactive` | Opens a browser for interactive sign-in. Supports MFA and multi-account use. Benefits from the persistent token cache (on by default; disable with `AZDO_EPHEMERAL_TOKEN=true`): the first launch prompts; subsequent restarts reuse the cached refresh token silently while it remains valid. | Local development without a CLI session |
111
+ | `client_secret` | Service principal with client secret. Requires `AZDO_TENANT_ID`, `AZDO_CLIENT_ID`, and `AZDO_CLIENT_SECRET`. | CI/CD, unattended automation |
112
+ | `managed_identity` | Azure Managed Identity. No credentials to manage. | Azure-hosted workloads (VMs, Functions, Container Apps) |
113
+
114
+ **For `default` / local dev:** run `az login` once — `DefaultAzureCredential` will pick it up automatically.
115
+
116
+ **For `interactive`:** a browser window opens on first use. Set `AZDO_TENANT_ID` to constrain sign-in to a specific Entra ID tenant (recommended when multiple accounts are in use). The persistent token cache is on by default, so subsequent restarts are silent; set `AZDO_EPHEMERAL_TOKEN=true` to opt out.
117
+
118
+ **For `client_secret`:** also set `AZDO_TENANT_ID`, `AZDO_CLIENT_ID`, and `AZDO_CLIENT_SECRET`.
119
+
120
+ ---
121
+
122
+ ## Security
123
+
124
+ ### Safe-by-default write and delete gates
125
+
126
+ Write and delete tools are **not registered by default** — they do not appear to the agent at all until explicitly enabled. The server is read-only until `AZDO_ALLOW_WRITE=true` and/or `AZDO_ALLOW_DELETE=true` are set. Each flag is independent; set only the ones you need.
127
+
128
+ ### Env-driven configuration
129
+
130
+ All configuration is supplied via environment variables. No secrets, org names, project names, or tenant IDs are hardcoded. `.vscode/mcp.json` is gitignored because it may contain credentials.
131
+
132
+ ### Stdout reserved for MCP transport
133
+
134
+ Stdout is exclusively reserved for MCP stdio transport messages. All server logs (including auth events) go to stderr via the Python `logging` module. Never redirect stdout to a log file.
135
+
136
+ ### Token cache caveats (`interactive` auth)
137
+
138
+ By default the MSAL token cache is encrypted at rest using the OS secret store (Windows DPAPI, macOS Keychain, Linux libsecret). The `AuthenticationRecord` sidecar stored at `~/.devops-mcp/auth-record.json` contains only account metadata (home account ID, tenant, authority, username) — no tokens or client secrets.
139
+
140
+ On headless Linux without a secret store (e.g., no GNOME Keyring / libsecret installed), the OS-encrypted cache may be unavailable. The server logs an actionable warning and falls back to an in-memory-only cache. Set `AZDO_EPHEMERAL_TOKEN=true` to suppress the warning and always use in-memory cache on such hosts.
141
+
142
+ ### Multiple tenants/accounts on one host (`interactive` auth)
143
+
144
+ The default cache and sidecar filenames (`devops-mcp.cache`, `~/.devops-mcp/auth-record.json`) are shared per host, so two `interactive` sessions signed in to **different tenants/accounts** would overwrite each other's pinned account. Give each session a distinct `AZDO_TOKEN_CACHE_PROFILE` (e.g. `prod`, `dev`) to keep their caches and `AuthenticationRecord` sidecars separate. The profile is a tenant-wide cache key: each entry signs in once (its own browser prompt) and then restarts silently as its own account, while tools still receive the specific `organization`/`project` per call. The profiles never collide.
145
+
146
+ Register two server entries, each with its own profile and (recommended) matching `AZDO_TENANT_ID`:
147
+
148
+ ```json
149
+ {
150
+ "servers": {
151
+ "devops-mcp-prod": {
152
+ "type": "stdio",
153
+ "command": "uv",
154
+ "args": ["run", "devops-mcp"],
155
+ "env": {
156
+ "AZDO_AUTH_TYPE": "interactive",
157
+ "AZDO_TENANT_ID": "<prod-tenant-id>",
158
+ "AZDO_TOKEN_CACHE_PROFILE": "prod"
159
+ }
160
+ },
161
+ "devops-mcp-dev": {
162
+ "type": "stdio",
163
+ "command": "uv",
164
+ "args": ["run", "devops-mcp"],
165
+ "env": {
166
+ "AZDO_AUTH_TYPE": "interactive",
167
+ "AZDO_TENANT_ID": "<dev-tenant-id>",
168
+ "AZDO_TOKEN_CACHE_PROFILE": "dev"
169
+ }
170
+ }
171
+ }
172
+ }
173
+ ```
174
+
175
+ ### Resilience behavior
176
+
177
+ These behaviors are built in and require no configuration:
178
+
179
+ - **Automatic retries** — requests that receive `429` (throttling) or transient gateway errors (`502`, `503`, `504`) are retried automatically with back-off and `Retry-After` header honoring. **Non-idempotent writes (POST, PATCH) are not retried on `5xx`** — a gateway error on a write may arrive after the server has already committed the operation; only `429` (which guarantees the request was rejected before processing) is safe to retry on all methods.
180
+ - **Response size cap** — responses larger than **5 MB** are replaced with an error asking the agent to narrow the query. For large pipeline logs use `devops_get_run_log_content`'s `start_line`/`end_line` parameters to slice the content at the API level.
181
+ - **Auth timeout** — credential acquisition is bounded by `AZDO_AUTH_TIMEOUT_SECONDS` (default 30 s). A slow or hung auth call releases the per-scope lock so subsequent callers are not serialized indefinitely.
182
+
183
+ ---
184
+
185
+ ## MCP Client Setup
186
+
187
+ ### GitHub Copilot (VS Code)
188
+
189
+ Add to `.vscode/mcp.json` in your project root. Note: `.vscode/mcp.json` is gitignored because it may contain secrets.
190
+
191
+ **Default / local dev (recommended):**
192
+
193
+ ```json
194
+ {
195
+ "servers": {
196
+ "devops-mcp": {
197
+ "type": "stdio",
198
+ "command": "uv",
199
+ "args": ["run", "devops-mcp"],
200
+ "env": {
201
+ "AZDO_ORGANIZATION": "<your-org>",
202
+ "AZDO_PROJECT": "<your-project>"
203
+ }
204
+ }
205
+ }
206
+ }
207
+ ```
208
+
209
+ **With write tools enabled:**
210
+
211
+ ```json
212
+ {
213
+ "servers": {
214
+ "devops-mcp": {
215
+ "type": "stdio",
216
+ "command": "uv",
217
+ "args": ["run", "devops-mcp"],
218
+ "env": {
219
+ "AZDO_ORGANIZATION": "<your-org>",
220
+ "AZDO_PROJECT": "<your-project>",
221
+ "AZDO_ALLOW_WRITE": "true"
222
+ }
223
+ }
224
+ }
225
+ }
226
+ ```
227
+
228
+ **Service principal (CI/CD):**
229
+
230
+ ```json
231
+ {
232
+ "servers": {
233
+ "devops-mcp": {
234
+ "type": "stdio",
235
+ "command": "uv",
236
+ "args": ["run", "devops-mcp"],
237
+ "env": {
238
+ "AZDO_AUTH_TYPE": "client_secret",
239
+ "AZDO_TENANT_ID": "<your-tenant-id>",
240
+ "AZDO_CLIENT_ID": "<your-client-id>",
241
+ "AZDO_CLIENT_SECRET": "<your-client-secret>",
242
+ "AZDO_ORGANIZATION": "<your-org>",
243
+ "AZDO_PROJECT": "<your-project>"
244
+ }
245
+ }
246
+ }
247
+ }
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Tools
253
+
254
+ **31 tools** across 4 domains. Tools marked with a gate are only registered when the corresponding env flag is set.
255
+
256
+ | Gate | Meaning |
257
+ |---|---|
258
+ | `default` | Always registered (reads and safe queries). |
259
+ | `write` | Registered only when `AZDO_ALLOW_WRITE=true`. |
260
+ | `delete` | Registered only when `AZDO_ALLOW_DELETE=true`. |
261
+
262
+ ### Pipelines (7 tools)
263
+
264
+ | Tool | Gate | Description |
265
+ |---|---|---|
266
+ | `devops_list_pipelines` | default | List pipelines defined in a project |
267
+ | `devops_list_pipeline_runs` | default | List runs for a specific pipeline |
268
+ | `devops_get_pipeline_run` | default | Get details of a specific pipeline run |
269
+ | `devops_get_build` | default | Get build details by `buildId` (resolves a build URL to pipeline info) |
270
+ | `devops_list_run_logs` | default | List log metadata for a build by `buildId` |
271
+ | `devops_get_run_log_content` | default | Get plain-text content of a specific log; use `start_line`/`end_line` to slice large logs |
272
+ | `devops_list_build_artifacts` | default | List artifacts produced by a build |
273
+
274
+ ### Repositories (3 tools)
275
+
276
+ | Tool | Gate | Description |
277
+ |---|---|---|
278
+ | `devops_list_repositories` | default | List Git repositories in a project |
279
+ | `devops_get_repository` | default | Get details of a specific repository |
280
+ | `devops_list_branches` | default | List branches in a repository |
281
+
282
+ ### Pull Requests (14 tools)
283
+
284
+ | Tool | Gate | Description |
285
+ |---|---|---|
286
+ | `devops_get_pull_request` | default | Get details of a specific pull request |
287
+ | `devops_list_pull_requests` | default | List pull requests with optional filters (status, branch, creator, reviewer, labels) |
288
+ | `devops_create_pull_request` | write | Create a new pull request, optionally linking work items |
289
+ | `devops_update_pull_request` | write | Update title, description, status, draft state, target branch, auto-complete, or completion options |
290
+ | `devops_tag_pull_request` | write | Add labels/tags to a pull request |
291
+ | `devops_link_work_items_to_pull_request` | write | Link Azure Boards work items to a pull request |
292
+ | `devops_list_pull_request_threads` | default | List comment threads on a pull request |
293
+ | `devops_get_pull_request_thread` | default | Get a single comment thread with its comments |
294
+ | `devops_create_pull_request_thread` | write | Start a comment thread — general, or inline on a file/line via thread context |
295
+ | `devops_set_pull_request_thread_status` | write | Set a thread's status (`active`, `fixed`, `wontFix`, `closed`, `byDesign`, `pending`) |
296
+ | `devops_add_pull_request_comment` | write | Reply to an existing comment thread |
297
+ | `devops_update_pull_request_comment` | write | Edit the text of an existing comment |
298
+ | `devops_list_pull_request_iterations` | default | List a pull request's iterations (push history) |
299
+ | `devops_get_pull_request_changes` | default | List changed files for a PR iteration (path + change type) |
300
+
301
+ ### Work Items (7 tools)
302
+
303
+ | Tool | Gate | Description |
304
+ |---|---|---|
305
+ | `devops_get_work_item` | default | Get a single work item by ID |
306
+ | `devops_list_work_items` | default | Bulk-fetch up to 200 work items by ID |
307
+ | `devops_query_work_items` | default | Query work items with WIQL, auto-fetching full details |
308
+ | `devops_create_work_item` | write | Create a new work item |
309
+ | `devops_update_work_item` | write | Update fields on an existing work item |
310
+ | `devops_add_work_item_comment` | write | Add a comment to a work item |
311
+ | `devops_update_work_item_comment` | write | Update an existing work item comment |
312
+
313
+ ---
314
+
315
+ ## API Reference
316
+
317
+ All tools use the [Azure DevOps REST API](https://learn.microsoft.com/en-us/rest/api/azure/devops/). Pipeline, repository, work item read tools, and the PR comment-thread and diff tools use **v7.1**. The remaining pull request tools (get/list/create/update/tag/link) and work item write operations use **v7.2-preview**.
318
+
319
+ **Note:** `run_id` and `build_id` share the same numeric value — a Pipelines API `run_id` is identical to the Build API `buildId` for the same run. This enables cross-API calls (e.g., use `devops_list_run_logs` to get log IDs, then `devops_get_run_log_content` with the same `build_id`).
@@ -0,0 +1,291 @@
1
+ # devops-mcp
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/devops-mcp)](https://pypi.org/project/devops-mcp/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/devops-mcp)](https://pypi.org/project/devops-mcp/)
5
+ [![License: MIT](https://img.shields.io/github/license/ryanmichaeljames/devops-mcp)](LICENSE)
6
+
7
+ An [MCP](https://modelcontextprotocol.io/) server that exposes Azure DevOps as tools for LLMs — pipelines, repositories, pull requests, and work items. Built with [FastMCP](https://github.com/modelcontextprotocol/python-sdk) over stdio transport.
8
+
9
+ Communicates over **stdio** and works with GitHub Copilot, Claude Code, and any MCP-compatible client.
10
+
11
+ ---
12
+
13
+ ## Quick Start
14
+
15
+ **1. Install dependencies**
16
+
17
+ ```bash
18
+ uv sync
19
+ ```
20
+
21
+ **2. Configure** — add to your MCP client config (see [MCP Client Setup](#mcp-client-setup) below).
22
+
23
+ **3. Run the server**
24
+
25
+ ```bash
26
+ uv run devops-mcp
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Installation
32
+
33
+ ### Prerequisites
34
+
35
+ - Python `>=3.10`
36
+ - [uv](https://docs.astral.sh/uv/) (recommended)
37
+ - A Microsoft Entra ID identity with access to Azure DevOps
38
+
39
+ ### Install dependencies
40
+
41
+ ```bash
42
+ uv sync
43
+ ```
44
+
45
+ ### Build the package
46
+
47
+ ```bash
48
+ uv run python -m build
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Configuration
54
+
55
+ All configuration is driven by environment variables — no secrets in code, no hardcoded org names or tenant IDs.
56
+
57
+ | Variable | Required? | Default | Description |
58
+ |---|---|---|---|
59
+ | `AZDO_AUTH_TYPE` | No | `default` | Authentication method. One of: `default`, `azure_cli`, `interactive`, `client_secret`, `managed_identity`. `default` tries all credential sources in order (environment variables, Azure CLI, managed identity) and is the right choice for local development. See [Authentication](#authentication). |
60
+ | `AZDO_ORGANIZATION` | No | — | Default Azure DevOps organization name. Can be overridden per tool call. Required if not supplied per call. |
61
+ | `AZDO_PROJECT` | No | — | Default Azure DevOps project name. Can be overridden per tool call. Required if not supplied per call. |
62
+ | `AZDO_TENANT_ID` | Conditional | — | Microsoft Entra ID tenant ID. Required for `client_secret`. Recommended for `interactive` to constrain sign-in to the correct tenant. |
63
+ | `AZDO_CLIENT_ID` | `client_secret` only | — | Service principal client ID. |
64
+ | `AZDO_CLIENT_SECRET` | `client_secret` only | — | Service principal client secret. |
65
+ | `AZDO_LOG_LEVEL` | No | `INFO` | Log verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR`. All logs go to stderr; stdout is reserved for MCP stdio transport. |
66
+ | `AZDO_ALLOW_WRITE` | No | off | Set `true` to register create, update, tag, link, and comment (write) tools. When unset the server is read-only — write tools are not visible to the agent at all. |
67
+ | `AZDO_ALLOW_DELETE` | No | off | Set `true` to register delete tools. When unset, delete tools are not visible to the agent. |
68
+ | `AZDO_EPHEMERAL_TOKEN` | No | `false` | **Interactive auth only.** When `false` (the default), the MSAL token cache is persisted to disk via the OS secret store (Windows DPAPI, macOS Keychain, Linux libsecret), and an `AuthenticationRecord` sidecar is written to `~/.devops-mcp/auth-record.json` so subsequent server restarts authenticate silently without a new browser prompt. Set `true`, `1`, or `yes` to use an in-memory-only cache (no disk cache, no sidecar) — re-prompts on every restart. Invalid values fall back to `false` with a logged warning. Has no effect on any auth type other than `interactive`. |
69
+ | `AZDO_TOKEN_CACHE_PROFILE` | No | — | **Interactive auth only.** A filename-safe suffix (`[A-Za-z0-9_-]`) appended to the MSAL cache name and the `AuthenticationRecord` sidecar so two server instances signed in to **different tenants/accounts** on the same host keep separate caches instead of overwriting each other's pinned account. Omit (or leave empty) for a single-tenant setup — the original shared filenames are used. Characters outside `[A-Za-z0-9_-]` raise an error rather than being silently dropped (sanitizing could collapse two distinct profiles into one shared cache). |
70
+ | `AZDO_AUTH_TIMEOUT_SECONDS` | No | `30` | Maximum seconds to wait for credential acquisition before failing with an auth error. Applies to all auth types. Invalid or non-positive values fall back to `30`. Increase this in slow-network or MFA-heavy environments. |
71
+
72
+ ---
73
+
74
+ ## Authentication
75
+
76
+ The server uses **Microsoft Entra ID (Azure AD) OAuth 2.0** via the [`azure-identity`](https://pypi.org/project/azure-identity/) library. Set `AZDO_AUTH_TYPE` to select a method.
77
+
78
+ | `AZDO_AUTH_TYPE` | Description | Best for |
79
+ |---|---|---|
80
+ | `default` *(default)* | `DefaultAzureCredential` — tries environment variables, Azure CLI session, managed identity, and other sources in order. Does not prompt in-process. | **Recommended — works everywhere** |
81
+ | `azure_cli` | Uses the active Azure CLI session (`az login`). Does not prompt in-process. | Local development with an existing CLI session |
82
+ | `interactive` | Opens a browser for interactive sign-in. Supports MFA and multi-account use. Benefits from the persistent token cache (on by default; disable with `AZDO_EPHEMERAL_TOKEN=true`): the first launch prompts; subsequent restarts reuse the cached refresh token silently while it remains valid. | Local development without a CLI session |
83
+ | `client_secret` | Service principal with client secret. Requires `AZDO_TENANT_ID`, `AZDO_CLIENT_ID`, and `AZDO_CLIENT_SECRET`. | CI/CD, unattended automation |
84
+ | `managed_identity` | Azure Managed Identity. No credentials to manage. | Azure-hosted workloads (VMs, Functions, Container Apps) |
85
+
86
+ **For `default` / local dev:** run `az login` once — `DefaultAzureCredential` will pick it up automatically.
87
+
88
+ **For `interactive`:** a browser window opens on first use. Set `AZDO_TENANT_ID` to constrain sign-in to a specific Entra ID tenant (recommended when multiple accounts are in use). The persistent token cache is on by default, so subsequent restarts are silent; set `AZDO_EPHEMERAL_TOKEN=true` to opt out.
89
+
90
+ **For `client_secret`:** also set `AZDO_TENANT_ID`, `AZDO_CLIENT_ID`, and `AZDO_CLIENT_SECRET`.
91
+
92
+ ---
93
+
94
+ ## Security
95
+
96
+ ### Safe-by-default write and delete gates
97
+
98
+ Write and delete tools are **not registered by default** — they do not appear to the agent at all until explicitly enabled. The server is read-only until `AZDO_ALLOW_WRITE=true` and/or `AZDO_ALLOW_DELETE=true` are set. Each flag is independent; set only the ones you need.
99
+
100
+ ### Env-driven configuration
101
+
102
+ All configuration is supplied via environment variables. No secrets, org names, project names, or tenant IDs are hardcoded. `.vscode/mcp.json` is gitignored because it may contain credentials.
103
+
104
+ ### Stdout reserved for MCP transport
105
+
106
+ Stdout is exclusively reserved for MCP stdio transport messages. All server logs (including auth events) go to stderr via the Python `logging` module. Never redirect stdout to a log file.
107
+
108
+ ### Token cache caveats (`interactive` auth)
109
+
110
+ By default the MSAL token cache is encrypted at rest using the OS secret store (Windows DPAPI, macOS Keychain, Linux libsecret). The `AuthenticationRecord` sidecar stored at `~/.devops-mcp/auth-record.json` contains only account metadata (home account ID, tenant, authority, username) — no tokens or client secrets.
111
+
112
+ On headless Linux without a secret store (e.g., no GNOME Keyring / libsecret installed), the OS-encrypted cache may be unavailable. The server logs an actionable warning and falls back to an in-memory-only cache. Set `AZDO_EPHEMERAL_TOKEN=true` to suppress the warning and always use in-memory cache on such hosts.
113
+
114
+ ### Multiple tenants/accounts on one host (`interactive` auth)
115
+
116
+ The default cache and sidecar filenames (`devops-mcp.cache`, `~/.devops-mcp/auth-record.json`) are shared per host, so two `interactive` sessions signed in to **different tenants/accounts** would overwrite each other's pinned account. Give each session a distinct `AZDO_TOKEN_CACHE_PROFILE` (e.g. `prod`, `dev`) to keep their caches and `AuthenticationRecord` sidecars separate. The profile is a tenant-wide cache key: each entry signs in once (its own browser prompt) and then restarts silently as its own account, while tools still receive the specific `organization`/`project` per call. The profiles never collide.
117
+
118
+ Register two server entries, each with its own profile and (recommended) matching `AZDO_TENANT_ID`:
119
+
120
+ ```json
121
+ {
122
+ "servers": {
123
+ "devops-mcp-prod": {
124
+ "type": "stdio",
125
+ "command": "uv",
126
+ "args": ["run", "devops-mcp"],
127
+ "env": {
128
+ "AZDO_AUTH_TYPE": "interactive",
129
+ "AZDO_TENANT_ID": "<prod-tenant-id>",
130
+ "AZDO_TOKEN_CACHE_PROFILE": "prod"
131
+ }
132
+ },
133
+ "devops-mcp-dev": {
134
+ "type": "stdio",
135
+ "command": "uv",
136
+ "args": ["run", "devops-mcp"],
137
+ "env": {
138
+ "AZDO_AUTH_TYPE": "interactive",
139
+ "AZDO_TENANT_ID": "<dev-tenant-id>",
140
+ "AZDO_TOKEN_CACHE_PROFILE": "dev"
141
+ }
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
147
+ ### Resilience behavior
148
+
149
+ These behaviors are built in and require no configuration:
150
+
151
+ - **Automatic retries** — requests that receive `429` (throttling) or transient gateway errors (`502`, `503`, `504`) are retried automatically with back-off and `Retry-After` header honoring. **Non-idempotent writes (POST, PATCH) are not retried on `5xx`** — a gateway error on a write may arrive after the server has already committed the operation; only `429` (which guarantees the request was rejected before processing) is safe to retry on all methods.
152
+ - **Response size cap** — responses larger than **5 MB** are replaced with an error asking the agent to narrow the query. For large pipeline logs use `devops_get_run_log_content`'s `start_line`/`end_line` parameters to slice the content at the API level.
153
+ - **Auth timeout** — credential acquisition is bounded by `AZDO_AUTH_TIMEOUT_SECONDS` (default 30 s). A slow or hung auth call releases the per-scope lock so subsequent callers are not serialized indefinitely.
154
+
155
+ ---
156
+
157
+ ## MCP Client Setup
158
+
159
+ ### GitHub Copilot (VS Code)
160
+
161
+ Add to `.vscode/mcp.json` in your project root. Note: `.vscode/mcp.json` is gitignored because it may contain secrets.
162
+
163
+ **Default / local dev (recommended):**
164
+
165
+ ```json
166
+ {
167
+ "servers": {
168
+ "devops-mcp": {
169
+ "type": "stdio",
170
+ "command": "uv",
171
+ "args": ["run", "devops-mcp"],
172
+ "env": {
173
+ "AZDO_ORGANIZATION": "<your-org>",
174
+ "AZDO_PROJECT": "<your-project>"
175
+ }
176
+ }
177
+ }
178
+ }
179
+ ```
180
+
181
+ **With write tools enabled:**
182
+
183
+ ```json
184
+ {
185
+ "servers": {
186
+ "devops-mcp": {
187
+ "type": "stdio",
188
+ "command": "uv",
189
+ "args": ["run", "devops-mcp"],
190
+ "env": {
191
+ "AZDO_ORGANIZATION": "<your-org>",
192
+ "AZDO_PROJECT": "<your-project>",
193
+ "AZDO_ALLOW_WRITE": "true"
194
+ }
195
+ }
196
+ }
197
+ }
198
+ ```
199
+
200
+ **Service principal (CI/CD):**
201
+
202
+ ```json
203
+ {
204
+ "servers": {
205
+ "devops-mcp": {
206
+ "type": "stdio",
207
+ "command": "uv",
208
+ "args": ["run", "devops-mcp"],
209
+ "env": {
210
+ "AZDO_AUTH_TYPE": "client_secret",
211
+ "AZDO_TENANT_ID": "<your-tenant-id>",
212
+ "AZDO_CLIENT_ID": "<your-client-id>",
213
+ "AZDO_CLIENT_SECRET": "<your-client-secret>",
214
+ "AZDO_ORGANIZATION": "<your-org>",
215
+ "AZDO_PROJECT": "<your-project>"
216
+ }
217
+ }
218
+ }
219
+ }
220
+ ```
221
+
222
+ ---
223
+
224
+ ## Tools
225
+
226
+ **31 tools** across 4 domains. Tools marked with a gate are only registered when the corresponding env flag is set.
227
+
228
+ | Gate | Meaning |
229
+ |---|---|
230
+ | `default` | Always registered (reads and safe queries). |
231
+ | `write` | Registered only when `AZDO_ALLOW_WRITE=true`. |
232
+ | `delete` | Registered only when `AZDO_ALLOW_DELETE=true`. |
233
+
234
+ ### Pipelines (7 tools)
235
+
236
+ | Tool | Gate | Description |
237
+ |---|---|---|
238
+ | `devops_list_pipelines` | default | List pipelines defined in a project |
239
+ | `devops_list_pipeline_runs` | default | List runs for a specific pipeline |
240
+ | `devops_get_pipeline_run` | default | Get details of a specific pipeline run |
241
+ | `devops_get_build` | default | Get build details by `buildId` (resolves a build URL to pipeline info) |
242
+ | `devops_list_run_logs` | default | List log metadata for a build by `buildId` |
243
+ | `devops_get_run_log_content` | default | Get plain-text content of a specific log; use `start_line`/`end_line` to slice large logs |
244
+ | `devops_list_build_artifacts` | default | List artifacts produced by a build |
245
+
246
+ ### Repositories (3 tools)
247
+
248
+ | Tool | Gate | Description |
249
+ |---|---|---|
250
+ | `devops_list_repositories` | default | List Git repositories in a project |
251
+ | `devops_get_repository` | default | Get details of a specific repository |
252
+ | `devops_list_branches` | default | List branches in a repository |
253
+
254
+ ### Pull Requests (14 tools)
255
+
256
+ | Tool | Gate | Description |
257
+ |---|---|---|
258
+ | `devops_get_pull_request` | default | Get details of a specific pull request |
259
+ | `devops_list_pull_requests` | default | List pull requests with optional filters (status, branch, creator, reviewer, labels) |
260
+ | `devops_create_pull_request` | write | Create a new pull request, optionally linking work items |
261
+ | `devops_update_pull_request` | write | Update title, description, status, draft state, target branch, auto-complete, or completion options |
262
+ | `devops_tag_pull_request` | write | Add labels/tags to a pull request |
263
+ | `devops_link_work_items_to_pull_request` | write | Link Azure Boards work items to a pull request |
264
+ | `devops_list_pull_request_threads` | default | List comment threads on a pull request |
265
+ | `devops_get_pull_request_thread` | default | Get a single comment thread with its comments |
266
+ | `devops_create_pull_request_thread` | write | Start a comment thread — general, or inline on a file/line via thread context |
267
+ | `devops_set_pull_request_thread_status` | write | Set a thread's status (`active`, `fixed`, `wontFix`, `closed`, `byDesign`, `pending`) |
268
+ | `devops_add_pull_request_comment` | write | Reply to an existing comment thread |
269
+ | `devops_update_pull_request_comment` | write | Edit the text of an existing comment |
270
+ | `devops_list_pull_request_iterations` | default | List a pull request's iterations (push history) |
271
+ | `devops_get_pull_request_changes` | default | List changed files for a PR iteration (path + change type) |
272
+
273
+ ### Work Items (7 tools)
274
+
275
+ | Tool | Gate | Description |
276
+ |---|---|---|
277
+ | `devops_get_work_item` | default | Get a single work item by ID |
278
+ | `devops_list_work_items` | default | Bulk-fetch up to 200 work items by ID |
279
+ | `devops_query_work_items` | default | Query work items with WIQL, auto-fetching full details |
280
+ | `devops_create_work_item` | write | Create a new work item |
281
+ | `devops_update_work_item` | write | Update fields on an existing work item |
282
+ | `devops_add_work_item_comment` | write | Add a comment to a work item |
283
+ | `devops_update_work_item_comment` | write | Update an existing work item comment |
284
+
285
+ ---
286
+
287
+ ## API Reference
288
+
289
+ All tools use the [Azure DevOps REST API](https://learn.microsoft.com/en-us/rest/api/azure/devops/). Pipeline, repository, work item read tools, and the PR comment-thread and diff tools use **v7.1**. The remaining pull request tools (get/list/create/update/tag/link) and work item write operations use **v7.2-preview**.
290
+
291
+ **Note:** `run_id` and `build_id` share the same numeric value — a Pipelines API `run_id` is identical to the Build API `buildId` for the same run. This enables cross-API calls (e.g., use `devops_list_run_logs` to get log IDs, then `devops_get_run_log_content` with the same `build_id`).