# Connect Azure DevOps to Prodpeek Profile: `azuredevops/read-only` · connection kind: `azuredevops` · tier 2 Credential: A Personal Access Token with Read scopes only Suggested URL: `https://dev.azure.com/your-org` Projects, repositories, pull requests, pipelines, builds and their logs, environments and deployments, classic releases, work items and agent pools — thirty-two tools, one Personal Access Token, nothing to install. Azure DevOps is where "what was deployed, and which step failed" is answered. ## What you must not do You cannot create this credential on the user's behalf — it needs their login and, usually, an approval step. Walk them through it and verify the result. Never ask them to paste the credential into the chat; it goes straight into Prodpeek's console, which encrypts it at rest. Do not suggest Microsoft's Azure DevOps MCP server — its hosted form is OAuth as a signed-in user, so a gateway cannot use it. Prodpeek has a native Azure DevOps adapter; the upstream is the organisation URL (https://dev.azure.com/), never a project or repository URL. Ask for a PAT with Custom defined Read scopes; refuse Full access. ## Grant exactly these permissions - `Custom defined scopes — never Full access` — Full access is every scope in the organisation, including service connections and token administration. Custom defined lets you tick exactly the Read scopes below and nothing else. - `Build · Read, Code · Read, Project and Team · Read` — Pipelines, builds, build logs and timelines; repositories, branches, commits, pull requests and files; the project list every other tool needs. - `Work Items · Read, Release · Read, Variable Groups · Read, Agent Pools · Read` — Tickets and saved queries; classic releases; which variables exist (their values are blanked by the adapter); whether the self-hosted agent is online. - `Environment · Read & manage — only if you want deployment records` — `list_environments` and `list_environment_deployments` need it, and Azure DevOps offers no Read-only form of this scope. It lets the holder manage environments and their approvals. Leave it off unless "what is in prod" matters more to you than that; the other tools work without it. - `The organisation in the URL, and an expiry of 90 days or less` — A PAT belongs to one organisation. Pick the same one you enter as the upstream, or every call looks like a rejected PAT. ## Refuse these, and say why if the user asks for them - `Full access` — Every scope, including the ones that manage service connections and mint tokens. The gateway would become the only fence in front of the whole organisation. - `Any Read & write, Read & execute or Read, write & manage scope` — The profile denies every write and the adapter issues only GET, so it would change nothing about what Prodpeek does — but it removes the vendor's half of the fence, and that half is the one that still holds if this one has a bug. Build · Read & execute is the one people tick without thinking: it queues builds, and a build that deploys is a production change. - `Service Connections` — Service connections are stored credentials for Azure, AWS, registries and clusters. The adapter has no tool for them; the PAT should not be able to reach them either. - `Tokens` — Token administration. A PAT that can manage PATs can mint a credential that never passes through Prodpeek. ## Verify before the credential is used - The PAT's organisation is the one in the upstream URL. - Scopes says Custom defined, and every ticked scope is Read (Environment excepted, if you chose it). - The expiry is 90 days or less. - Test connection shows list_builds under Allowed. - Calling `connection_data` returns the identity you created the PAT as. ## Then, in Prodpeek 1. Services → Add a service → choose the profile `azuredevops/read-only`. 2. Connection kind `azuredevops`, URL `https://dev.azure.com/your-org`. 3. Paste the credential. It is encrypted in the store and never shown again. 4. Run **Test connection**. It lists every tool the upstream advertises and how the profile classifies each one. Anything under "not in the policy" is denied by default — report that list rather than assuming it is fine. ## Full human walkthrough ## The whole setup 1. In Azure DevOps, open **User settings → Personal access tokens → New Token**. 2. Name it `prodpeek`. **Organization**: the one you will enter as the upstream. **Expiration**: 90 days or less. 3. **Scopes: Custom defined**. Tick only these: | Scope | Permission | |---|---| | Build | **Read** | | Code | **Read** | | Project and Team | **Read** | | Work Items | **Read** | | Release | **Read** | | Variable Groups | **Read** | | Agent Pools | **Read** | | Environment | Read & manage — *optional*, see below | 4. **Create**, and copy the token. 5. In Prodpeek: **Services → Add a service**, pick `azuredevops/read-only`, set the upstream to `https://dev.azure.com/your-org`, paste the PAT, **Test connection**. !!! warning "Environment has no Read-only scope" `list_environments` and `list_environment_deployments` answer "what is in prod right now", and they need **Environment · Read & manage** — Azure DevOps has no Read form of it. That scope can also manage environments and their approval checks. If that trade is not worth it to you, leave it off: those two tools will be refused by Azure DevOps and everything else works. The upstream is the **organisation**, not a project. If you paste `https://dev.azure.com/acme/Shop/_git/api`, Prodpeek says so and tells you to use `https://dev.azure.com/acme`. `https://acme.visualstudio.com` and an on-premises collection URL (`https://tfs.example.com/tfs/DefaultCollection`) work too. ## Why there is no MCP server to install Microsoft publishes an Azure DevOps MCP server. Its hosted form authenticates with OAuth as a signed-in user — the same model as Grafana Cloud's and Cloudflare's, and unusable for the same reason: Prodpeek is a server-side gateway holding a stored team credential, with no browser and no signed-in user. The Azure DevOps REST API takes a Personal Access Token, which is exactly the shape Prodpeek is built around. So it speaks it directly. ## What your agent can then do ``` azuredevops__list_builds did the last deploy fail, and on which branch azuredevops__get_build_timeline which step failed, and the error it raised azuredevops__get_build_log the end of that step's log, as plain text azuredevops__list_environment_deployments what is in prod right now, and since when azuredevops__list_pipeline_runs recent runs of one pipeline azuredevops__list_commits what changed just before this broke azuredevops__list_pull_requests which PR carried that change, and who approved it azuredevops__get_item the deploy config at that commit azuredevops__list_releases classic releases, and each environment's status azuredevops__list_variable_groups whether DB_HOST is even set (values removed) azuredevops__list_agents whether the self-hosted agent is online azuredevops__get_work_item the incident ticket, with its links ``` `get_build_timeline` is the one to start with: one call names the failing step and the error it raised, and `get_build_log` then reads just that log. Read the end of the log first — `list_build_logs` gives the line count, and each call returns at most 2000 lines. ## What it refuses, and the ones that matter **Service connections have no tool.** `_apis/serviceendpoint/endpoints` is the map of every credential the organisation holds for Azure, AWS, container registries and clusters. There is no code path in the adapter that builds that path. **Secure files, PATs and agent registration tokens have no tool.** Signing keys and kubeconfigs; the organisation's other tokens; the token that lets a machine join your build pool. **Variable values are removed.** `list_variable_groups` and `get_release` keep every variable's **name** and whether it is secret, and replace every **value**. Secret values arrive empty from Azure DevOps anyway; the others are frequently secrets somebody forgot to tick. `list_agents` never asks for an agent's capabilities, which are its environment variables. **Free-form WIQL has no tool.** It is a POST. `run_query` runs a *saved* query by id instead, which is a GET. **Queuing a build has no tool, and it is the one people ask for.** "Just re-run the pipeline" sounds harmless. A pipeline that deploys is a production change. ## Why Tier 2 The Tier 1 half is real: a PAT made from Read scopes refuses every write in this profile *at Azure DevOps*, independently of the gateway. Keep it that way. It is Tier 2 anyway. The Environment scope has no Read-only form, and several allowed reads return source code and build logs, which hold whatever was committed or printed. The roll-up decides the label. Azure DevOps also offers no honest introspection: a PAT cannot ask which scopes it holds. So this profile carries **no scope probe**, and [`prove`](../use/prove.md) reports the scope as *not introspectable* rather than claiming a check it never made. ## Checking the credential really cannot write ```bash ORG=https://dev.azure.com/your-org PROJECT="Your Project" # Should succeed, and name the identity the PAT belongs to curl -sS -u ":$PAT" "$ORG/_apis/connectionData?api-version=7.1-preview" | head -c 300 # Should be REFUSED: queue a build of a definition that does not exist curl -sS -o /dev/null -w '%{http_code}\n' -u ":$PAT" -X POST \ -H 'Content-Type: application/json' -d '{"definition":{"id":999999999}}' \ "$ORG/$(printf %s "$PROJECT" | sed 's/ /%20/g')/_apis/build/builds?api-version=7.1" ``` A `401` or `403` on the second is the vendor fence doing its half. A `404` ("definition not found") means the PAT **could** queue builds and only the made-up id stopped it — remake it without Build · Read & execute. The definition id is deliberately one that cannot exist, so the check never queues anything.