Connecting Azure DevOps to Claude
Last updated: November 2, 2026
This guide connects Azure DevOps (ADO) to the Claude desktop app on a Mac or Windows PC. Once it's connected, Claude can read your repos, work items, pull requests and pipelines, and it can create branches, commit changes and open pull requests for you.
If your code is on GitHub instead, see Connecting GitHub to Claude.
Claude signs in with your own Microsoft account, so it can only see and change what your Azure DevOps account already has access to.
Before you start
You'll need:
The Claude desktop app on a Mac or Windows PC.
Azure DevOps access at the Basic level or higher. Stakeholder access can't see Repos.
Node.js version 20 or later. Step 1 checks this for you.
Step 1: Check Node.js
Open Terminal on a Mac or PowerShell on Windows.
Mac: press Cmd + Space, type
Terminaland press Enter.Windows: press the Windows key, type
PowerShelland press Enter.
Type
node -vand press Enter.Check the number. If it's
v20or higher, go to Step 2. If you see an error or a lower number, install the LTS version from nodejs.org, then reopen Terminal or PowerShell.
Step 2: Add Azure DevOps to Claude
Choose one option. Option A is the easiest.
Option A: Run a command (easiest)
Open the command for your computer below and copy all of it.
Mac command (for Terminal)
node -e '
const fs = require(`fs`), path = require(`path`);
const say = (m) => console.log(`\n` + m + `\n`);
const isObj = (x) => Object.prototype.toString.call(x) === `[object Object]`;
if (parseInt(process.versions.node, 10) < 20) {
say(`Your Node.js version is too old. Install the LTS version from nodejs.org, then run this again. Nothing was changed.`);
process.exit(1);
}
const file = path.join(process.env.HOME, `Library`, `Application Support`, `Claude`, `claude_desktop_config.json`);
const rl = require(`readline`).createInterface({ input: process.stdin, output: process.stdout });
rl.question(`Paste your Azure DevOps link or organization name, then press Enter: `, (answer) => {
rl.close();
let org = String(answer || ``).trim().replace(/^[\x22\x27<\s]+|[\x22\x27>\/\s]+$/g, ``);
const m = org.match(/dev\.azure\.com\/([^\/?#\s]+)/i) || org.match(/^(?:https?:\/\/)?([^.\/\s]+)\.visualstudio\.com/i);
if (m) { org = decodeURIComponent(m[1]); }
if (/^[A-Za-z0-9][A-Za-z0-9-]*$/.test(org) === false) {
say(`That does not look like an Azure DevOps organization name. Nothing was changed.`);
process.exit(1);
}
const real = fs.existsSync(file) ? fs.realpathSync(file) : file;
let config = {};
if (fs.existsSync(real)) {
let text = fs.readFileSync(real, `utf8`);
if (text.charCodeAt(0) === 65279) { text = text.slice(1); }
if (text.trim()) {
try { config = JSON.parse(text); } catch (e) {
say(`Your Claude settings file has a formatting problem, so it was left as is. Nothing was changed.`);
process.exit(1);
}
}
}
if (isObj(config) === false || (config.mcpServers === undefined ? false : isObj(config.mcpServers) === false)) {
say(`Your Claude settings file is not in the expected format, so it was left as is. Nothing was changed.`);
process.exit(1);
}
const servers = config.mcpServers || {};
if (Object.keys(servers).some((k) => k === `ado` || String(JSON.stringify(servers[k])).includes(`@azure-devops/mcp`))) {
say(`Azure DevOps is already set up in Claude, so nothing was changed.`);
process.exit(0);
}
config.mcpServers = servers;
servers.ado = { command: `npx`, args: [`-y`, `@azure-devops/mcp`, org] };
let backup = ``;
const tmp = real + `.tmp`;
try {
fs.mkdirSync(path.dirname(real), { recursive: true });
if (fs.existsSync(real)) {
backup = real.replace(/\.json$/, ``) + `.backup-` + new Date().toISOString().slice(0, 19).replace(/[:T]/g, `-`) + `.json`;
fs.copyFileSync(real, backup);
}
fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + `\n`);
JSON.parse(fs.readFileSync(tmp, `utf8`));
fs.renameSync(tmp, real);
} catch (e) {
try { fs.unlinkSync(tmp); } catch (x) {}
say(`Could not save your Claude settings (${e.message}). Nothing was changed.`);
process.exit(1);
}
say(`Done. Azure DevOps (${org}) is now added to Claude.` + (backup ? ` A backup of your old settings was saved as ${path.basename(backup)}.` : ``) + ` Now quit Claude with Cmd + Q and open it again.`);
});
'Windows command (for PowerShell)
node -e '
const fs = require(`fs`), path = require(`path`);
const say = (m) => console.log(`\n` + m + `\n`);
const isObj = (x) => Object.prototype.toString.call(x) === `[object Object]`;
const ls = (d) => { try { return fs.readdirSync(d); } catch (e) { return []; } };
if (parseInt(process.versions.node, 10) < 20) {
say(`Your Node.js version is too old. Install the LTS version from nodejs.org, then run this again. Nothing was changed.`);
process.exit(1);
}
const local = process.env.LOCALAPPDATA || ``;
const roaming = process.env.APPDATA || ``;
if (roaming === ``) {
say(`Could not find your user folder. Nothing was changed.`);
process.exit(1);
}
const places = [
...ls(path.join(local, `Claude-Data`)).filter((n) => /^claude_/i.test(n)).map((n) => path.join(local, `Claude-Data`, n, `claude_desktop_config.json`)),
...ls(path.join(local, `Packages`)).filter((n) => /^Claude_pzs8sxrjxfjjc$/i.test(n)).map((n) => path.join(local, `Packages`, n, `LocalCache`, `Roaming`, `Claude`, `claude_desktop_config.json`)),
path.join(roaming, `Claude`, `claude_desktop_config.json`)
];
let files = places.filter((f) => fs.existsSync(f));
if (files.length === 0) { files = [places.find((f) => fs.existsSync(path.dirname(f))) || places[places.length - 1]]; }
const rl = require(`readline`).createInterface({ input: process.stdin, output: process.stdout });
rl.question(`Paste your Azure DevOps link or organization name, then press Enter: `, (answer) => {
rl.close();
let org = String(answer || ``).trim().replace(/^[\x22\x27<\s]+|[\x22\x27>\/\s]+$/g, ``);
const m = org.match(/dev\.azure\.com\/([^\/?#\s]+)/i) || org.match(/^(?:https?:\/\/)?([^.\/\s]+)\.visualstudio\.com/i);
if (m) { org = decodeURIComponent(m[1]); }
if (/^[A-Za-z0-9][A-Za-z0-9-]*$/.test(org) === false) {
say(`That does not look like an Azure DevOps organization name. Nothing was changed.`);
process.exit(1);
}
const jobs = [];
for (const file of files) {
let config = {};
if (fs.existsSync(file)) {
let text = fs.readFileSync(file, `utf8`);
if (text.charCodeAt(0) === 65279) { text = text.slice(1); }
if (text.trim()) {
try { config = JSON.parse(text); } catch (e) {
say(`Your Claude settings file has a formatting problem, so it was left as is. Nothing was changed. File: ${file}`);
process.exit(1);
}
}
}
if (isObj(config) === false || (config.mcpServers === undefined ? false : isObj(config.mcpServers) === false)) {
say(`Your Claude settings file is not in the expected format, so it was left as is. Nothing was changed. File: ${file}`);
process.exit(1);
}
jobs.push({ file, config });
}
const todo = jobs.filter((j) => Object.keys(j.config.mcpServers || {}).some((k) => k === `ado` || String(JSON.stringify(j.config.mcpServers[k])).includes(`@azure-devops/mcp`)) === false);
if (todo.length === 0) {
say(`Azure DevOps is already set up in Claude, so nothing was changed.`);
process.exit(0);
}
const backups = [];
for (const j of todo) {
j.config.mcpServers = j.config.mcpServers || {};
j.config.mcpServers.ado = { command: `cmd`, args: [`/c`, `npx`, `-y`, `@azure-devops/mcp`, org] };
const tmp = j.file + `.tmp`;
try {
fs.mkdirSync(path.dirname(j.file), { recursive: true });
if (fs.existsSync(j.file)) {
const backup = j.file.replace(/\.json$/, ``) + `.backup-` + new Date().toISOString().slice(0, 19).replace(/[:T]/g, `-`) + `.json`;
fs.copyFileSync(j.file, backup);
backups.push(path.basename(backup));
}
fs.writeFileSync(tmp, JSON.stringify(j.config, null, 2) + `\n`);
JSON.parse(fs.readFileSync(tmp, `utf8`));
fs.renameSync(tmp, j.file);
} catch (e) {
try { fs.unlinkSync(tmp); } catch (x) {}
say(`Could not save your Claude settings (${e.message}). That file was not changed. File: ${j.file}`);
process.exit(1);
}
}
say(`Done. Azure DevOps (${org}) is now added to Claude.` + (backups.length ? ` A backup of your old settings was saved as ${backups.join(` and `)}.` : ``) + ` Now quit Claude from the system tray (right-click the Claude icon near the clock, then Quit) and open it again.`);
});
'Paste it into Terminal (Mac) or PowerShell (Windows) and press Enter. On Windows, use PowerShell, not Command Prompt.
When asked, paste your Azure DevOps link, for example
https://dev.azure.com/yourcompany, and press Enter.Wait for the message that starts with Done, then go to Step 3.
The command only adds Azure DevOps to Claude's settings, and it saves a backup first. If anything looks wrong, it stops and changes nothing.
Option B: Edit the settings file yourself
Open Claude's settings file.
Mac: in the menu bar, click Claude → Settings… → Developer → Edit Config.
Windows: in Claude, click File → Settings → Developer → Edit Config.
Open
claude_desktop_config.jsonin a code editor such as VS Code. On a Mac, don't use TextEdit, because it can break the file.Copy the block for your computer below and paste it inside the file's outer
{ }. Replaceyourcompanywith your organization name.
Mac block
"mcpServers": {
"ado": {
"command": "npx",
"args": ["-y", "@azure-devops/mcp", "yourcompany"]
}
}Windows block
"mcpServers": {
"ado": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@azure-devops/mcp", "yourcompany"]
}
}Check the commas. If the file already has
"mcpServers", add only the"ado"part inside it, and put a comma between it and the next entry.Save the file.
Important: If you installed Claude from the Microsoft Store, Edit Config can open a settings file that Claude doesn't read. Use Option A instead.
Step 3: Restart Claude and sign in
Quit Claude completely. Closing the window isn't enough.
Mac: press Cmd + Q.
Windows: right-click the Claude icon near the clock and click Quit.
Open Claude again and start a new chat.
Send
List my Azure DevOps projects. If Claude asks to use an Azure DevOps tool, allow it.Sign in with your Azure DevOps Microsoft account in the browser window that opens.
You're connected when Claude replies with a list of your projects.
What you can ask Claude
Replace the words in angle brackets with your own names.
Show my assigned work itemsList the repos in <project>Summarize the open pull requests in <repo>In <repo>, create a branch called <branch>, make this change: <change>, commit it and open a pull request into main
Working on a new branch with a pull request, as in the last example, lets you review the change before it reaches your main branch. Claude can't delete anything in Azure DevOps through this connection.
Troubleshooting
Problem | Fix |
|---|---|
| Install the LTS version of Node.js from nodejs.org, reopen Terminal or PowerShell, and run the command again. |
Red errors on Windows after pasting the command | You're in Command Prompt. Open PowerShell and run it again. Your settings weren't changed. |
The command says the settings file has a formatting problem | The file was already broken, so nothing was changed. Open it as in Option B. VS Code underlines the problem in red. |
The command says Azure DevOps is already set up, but it isn't working | Open the file as in Option B and check the organization name in the existing entry. |
Azure DevOps is missing in Claude, or Settings → Developer shows it as failed | Claude can't find Node.js. Install the LTS version, then quit Claude completely (Step 3) and reopen it. |
Claude can't see any repos | Your access level is probably Stakeholder. Ask your Azure DevOps admin to change it to Basic in Organization settings → Users. |
403 or "not authorized" error | You signed in with a different Microsoft account. Quit Claude completely, reopen it, and sign in with your Azure DevOps account. |
"Failed to find api location for area" | The organization name is wrong. It's case-sensitive and should be just the name, not a full link. Fix it in the settings file (Option B). |
If you're stuck, reach out to your Noibu contact.