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.
- devops_mcp-1.0.0/LICENSE +21 -0
- devops_mcp-1.0.0/PKG-INFO +319 -0
- devops_mcp-1.0.0/README.md +291 -0
- devops_mcp-1.0.0/pyproject.toml +80 -0
- devops_mcp-1.0.0/setup.cfg +4 -0
- devops_mcp-1.0.0/src/devops_mcp/__init__.py +1 -0
- devops_mcp-1.0.0/src/devops_mcp/_app.py +40 -0
- devops_mcp-1.0.0/src/devops_mcp/client.py +893 -0
- devops_mcp-1.0.0/src/devops_mcp/models.py +954 -0
- devops_mcp-1.0.0/src/devops_mcp/server.py +30 -0
- devops_mcp-1.0.0/src/devops_mcp/tools/__init__.py +1 -0
- devops_mcp-1.0.0/src/devops_mcp/tools/pipelines.py +412 -0
- devops_mcp-1.0.0/src/devops_mcp/tools/pull_requests.py +1038 -0
- devops_mcp-1.0.0/src/devops_mcp/tools/repositories.py +182 -0
- devops_mcp-1.0.0/src/devops_mcp/tools/work_items.py +495 -0
- devops_mcp-1.0.0/src/devops_mcp.egg-info/PKG-INFO +319 -0
- devops_mcp-1.0.0/src/devops_mcp.egg-info/SOURCES.txt +27 -0
- devops_mcp-1.0.0/src/devops_mcp.egg-info/dependency_links.txt +1 -0
- devops_mcp-1.0.0/src/devops_mcp.egg-info/entry_points.txt +2 -0
- devops_mcp-1.0.0/src/devops_mcp.egg-info/requires.txt +4 -0
- devops_mcp-1.0.0/src/devops_mcp.egg-info/top_level.txt +1 -0
- devops_mcp-1.0.0/tests/test_auth_lock_timeout.py +199 -0
- devops_mcp-1.0.0/tests/test_build_url.py +79 -0
- devops_mcp-1.0.0/tests/test_create_pull_request_work_item_linking.py +310 -0
- devops_mcp-1.0.0/tests/test_model_validation.py +283 -0
- devops_mcp-1.0.0/tests/test_paginate_results.py +203 -0
- devops_mcp-1.0.0/tests/test_request_with_retry.py +479 -0
- devops_mcp-1.0.0/tests/test_smoke.py +10 -0
- devops_mcp-1.0.0/tests/test_token_cache.py +314 -0
devops_mcp-1.0.0/LICENSE
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/devops-mcp/)
|
|
32
|
+
[](https://pypi.org/project/devops-mcp/)
|
|
33
|
+
[](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
|
+
[](https://pypi.org/project/devops-mcp/)
|
|
4
|
+
[](https://pypi.org/project/devops-mcp/)
|
|
5
|
+
[](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`).
|