Skip to content

Instantly share code, notes, and snippets.

@tonnguyen
Last active March 30, 2026 12:54
Show Gist options
  • Select an option

  • Save tonnguyen/d7cd32e3b48b1054054b8df04321bef7 to your computer and use it in GitHub Desktop.

Select an option

Save tonnguyen/d7cd32e3b48b1054054b8df04321bef7 to your computer and use it in GitHub Desktop.
Azure DevOps Enhanced MCP Server Setup
#!/usr/bin/env node
'use strict';
// Setup @ax-ado-mcp/mcp-server-azure-devops-enhanced
// Supports: Claude Desktop, VS Code (GitHub Copilot), Visual Studio, Cursor
// Platforms: macOS, Linux, Windows — requires Node.js (already needed by the MCP server)
const fs = require('fs');
const path = require('path');
const os = require('os');
const readline = require('readline');
const PACKAGE_VERSION = '0.1.0';
// ── Config file paths per platform ───────────────────────────────────────────
function getConfigPaths() {
const home = os.homedir();
if (process.platform === 'darwin') {
const appSupport = path.join(home, 'Library', 'Application Support');
return {
claude: path.join(appSupport, 'Claude', 'claude_desktop_config.json'),
vscode: path.join(appSupport, 'Code', 'User', 'mcp.json'),
cursor: path.join(home, '.cursor', 'mcp.json'),
visualstudio: path.join(home, '.mcp.json'),
};
}
if (process.platform === 'win32') {
return {
claude: path.join(process.env.APPDATA, 'Claude', 'claude_desktop_config.json'),
vscode: path.join(process.env.APPDATA, 'Code', 'User', 'mcp.json'),
cursor: path.join(home, '.cursor', 'mcp.json'),
visualstudio: path.join(home, '.mcp.json'),
};
}
// Linux
const configDir = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
return {
claude: path.join(configDir, 'Claude', 'claude_desktop_config.json'),
vscode: path.join(configDir, 'Code', 'User', 'mcp.json'),
cursor: path.join(home, '.cursor', 'mcp.json'),
visualstudio: path.join(home, '.mcp.json'),
};
}
// ── Prompt helpers ────────────────────────────────────────────────────────────
function ask(rl, question) {
return new Promise(resolve => rl.question(question, resolve));
}
function askSecret(rl, question) {
return ask(rl, question);
}
// ── JSON helpers ──────────────────────────────────────────────────────────────
function readJson(file) {
if (!fs.existsSync(file)) return {};
const content = fs.readFileSync(file, 'utf8');
// Try plain JSON first; fall back to stripping JSONC line comments
// (only strips // at start of line or after whitespace, not inside strings)
try {
return JSON.parse(content);
} catch (_) {
const stripped = content.replace(/^[ \t]*\/\/[^\n]*/mg, '').replace(/\/\*[\s\S]*?\*\//g, '');
return JSON.parse(stripped);
}
}
function writeJson(file, obj) {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, JSON.stringify(obj, null, 2) + '\n', 'utf8');
}
// ── MCP server entry ──────────────────────────────────────────────────────────
function mcpEntry(orgUrl, defaultProject, pat) {
return {
type: 'stdio',
command: 'npx',
args: ['-y', `@ax-ado-mcp/mcp-server-azure-devops-enhanced@${PACKAGE_VERSION}`],
env: {
AZURE_DEVOPS_ORG_URL: orgUrl,
AZURE_DEVOPS_AUTH_METHOD: 'pat',
AZURE_DEVOPS_PAT: pat,
AZURE_DEVOPS_DEFAULT_PROJECT: defaultProject,
},
};
}
// ── Config writers ────────────────────────────────────────────────────────────
// Claude Desktop / Cursor — { "mcpServers": { ... } }
function configureMcpServers(file, orgUrl, defaultProject, pat) {
try {
const config = readJson(file);
if (!config.mcpServers) config.mcpServers = {};
config.mcpServers.azureDevOpsEnhanced = mcpEntry(orgUrl, defaultProject, pat);
writeJson(file, config);
return true;
} catch (e) {
console.error(` Error: ${e.message}`);
return false;
}
}
// VS Code mcp.json — { "servers": { ... } }
function configureVscode(file, orgUrl, defaultProject, pat) {
try {
const config = readJson(file);
if (!config.servers) config.servers = {};
config.servers.azureDevOpsEnhanced = mcpEntry(orgUrl, defaultProject, pat);
writeJson(file, config);
return true;
} catch (e) {
console.error(` Error: ${e.message}`);
return false;
}
}
// ── Main ──────────────────────────────────────────────────────────────────────
async function main() {
const paths = getConfigPaths();
console.log('================================================');
console.log(' Azure DevOps Enhanced MCP Server Setup');
console.log('================================================');
console.log('');
console.log('Which tool(s) do you want to configure?');
console.log(' 1) Claude Desktop');
console.log(' 2) VS Code (GitHub Copilot)');
console.log(' 3) Cursor');
console.log(' 4) Visual Studio');
console.log('');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const choicesRaw = await ask(rl, 'Enter choice(s), space-separated [default: 1]: ');
const choices = (choicesRaw.trim() || '1').split(/[\s,]+/).filter(Boolean);
console.log('');
const orgUrlRaw = await ask(rl, 'Azure DevOps org URL (e.g. https://dev.azure.com/myorg): ');
const orgUrl = orgUrlRaw.trim();
if (!orgUrl) {
console.error('\nError: Org URL cannot be empty. Aborting.');
rl.close();
process.exit(1);
}
const defaultProjectRaw = await ask(rl, 'Default project name: ');
const defaultProject = defaultProjectRaw.trim();
if (!defaultProject) {
console.error('\nError: Default project cannot be empty. Aborting.');
rl.close();
process.exit(1);
}
console.log('');
console.log('You need a Personal Access Token (PAT) with Read access.');
console.log('To create one, go to: <your-org-url>/_usersSettings/tokens');
console.log('Recommended scopes: Work Items (Read), Code (Read),');
console.log(' Pull Request Threads (Read & Write for PR reviews).');
console.log('');
const pat = await askSecret(rl, 'Enter your Personal Access Token (PAT): ');
rl.close();
if (!pat) {
console.error('\nError: PAT cannot be empty. Aborting.');
process.exit(1);
}
console.log('');
let configured = 0;
let failed = 0;
for (const choice of choices) {
switch (choice.trim()) {
case '1':
console.log('Configuring Claude Desktop...');
if (configureMcpServers(paths.claude, orgUrl, defaultProject, pat)) { console.log(' OK'); configured++; } else { failed++; }
break;
case '2':
console.log('Configuring VS Code (GitHub Copilot)...');
if (configureVscode(paths.vscode, orgUrl, defaultProject, pat)) { console.log(' OK'); configured++; } else { failed++; }
break;
case '3':
console.log('Configuring Cursor...');
if (configureMcpServers(paths.cursor, orgUrl, defaultProject, pat)) { console.log(' OK'); configured++; } else { failed++; }
break;
case '4':
console.log('Configuring Visual Studio...');
if (configureVscode(paths.visualstudio, orgUrl, defaultProject, pat)) { console.log(' OK'); configured++; } else { failed++; }
break;
default:
console.log(`Unknown choice '${choice}' — skipped.`);
}
}
console.log('');
console.log('================================================');
if (configured > 0) console.log(' Done! Restart configured tools to apply changes.');
if (failed > 0) console.log(` ${failed} configuration(s) failed — see errors above.`);
console.log('================================================');
process.exit(failed > 0 ? 1 : 0);
}
main().catch(e => {
console.error('Unexpected error:', e.message);
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment