@azure-devops/mcp 2.2.2 → 2.3.0-nightly.20251203

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.
@@ -81,20 +81,39 @@ function configureWorkTools(server, _, connectionProvider) {
81
81
  server.tool(WORK_TOOLS.list_iterations, "List all iterations in a specified Azure DevOps project.", {
82
82
  project: z.string().describe("The name or ID of the Azure DevOps project."),
83
83
  depth: z.number().default(2).describe("Depth of children to fetch."),
84
- }, async ({ project, depth }) => {
84
+ excludedIds: z.array(z.number()).optional().describe("An optional array of iteration IDs, and thier children, that should not be returned."),
85
+ }, async ({ project, depth, excludedIds: ids }) => {
85
86
  try {
86
87
  const connection = await connectionProvider();
87
88
  const workItemTrackingApi = await connection.getWorkItemTrackingApi();
89
+ let results = [];
88
90
  if (depth === undefined) {
89
91
  depth = 1;
90
92
  }
91
- const results = await workItemTrackingApi.getClassificationNodes(project, [], depth);
93
+ results = await workItemTrackingApi.getClassificationNodes(project, [], depth);
92
94
  // Handle null or undefined results
93
95
  if (!results) {
94
96
  return { content: [{ type: "text", text: "No iterations were found" }], isError: true };
95
97
  }
96
98
  // Filter out items with structureType=0 (Area nodes), only keep structureType=1 (Iteration nodes)
97
- const filteredResults = results.filter((node) => node.structureType === TreeNodeStructureType.Iteration);
99
+ let filteredResults = results.filter((node) => node.structureType === TreeNodeStructureType.Iteration);
100
+ // If specific IDs are provided, filter them out recursively (exclude matching nodes and their children)
101
+ if (ids && ids.length > 0) {
102
+ const filterOutIds = (nodes) => {
103
+ return nodes
104
+ .filter((node) => !node.id || !ids.includes(node.id))
105
+ .map((node) => {
106
+ if (node.children && node.children.length > 0) {
107
+ return {
108
+ ...node,
109
+ children: filterOutIds(node.children),
110
+ };
111
+ }
112
+ return node;
113
+ });
114
+ };
115
+ filteredResults = filterOutIds(filteredResults);
116
+ }
98
117
  if (filteredResults.length === 0) {
99
118
  return { content: [{ type: "text", text: "No iterations were found" }], isError: true };
100
119
  }
package/dist/tools.js CHANGED
File without changes
package/dist/useragent.js CHANGED
File without changes
package/dist/utils.js CHANGED
File without changes
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "2.2.2";
1
+ export const packageVersion = "2.3.0-nightly.20251203";
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@azure-devops/mcp",
3
- "version": "2.2.2",
3
+ "version": "2.3.0-nightly.20251203",
4
+ "mcpName": "microsoft.com/azure-devops",
4
5
  "description": "MCP server for interacting with Azure DevOps",
5
6
  "license": "MIT",
6
7
  "author": "Microsoft Corporation",
@@ -25,7 +26,7 @@
25
26
  "prebuild": "node -p \"'export const packageVersion = ' + JSON.stringify(require('./package.json').version) + ';\\n'\" > src/version.ts && prettier --write src/version.ts",
26
27
  "validate-tools": "tsc --noEmit && node scripts/build-validate-tools.js",
27
28
  "build": "tsc && shx chmod +x dist/*.js",
28
- "prepare": "npm run build",
29
+ "prepare": "npm run build && husky",
29
30
  "watch": "tsc --watch",
30
31
  "inspect": "ALLOWED_ORIGINS=http://127.0.0.1:6274 npx @modelcontextprotocol/inspector node dist/index.js",
31
32
  "start": "node -r tsconfig-paths/register dist/index.js",
@@ -39,10 +40,11 @@
39
40
  "dependencies": {
40
41
  "@azure/identity": "^4.10.0",
41
42
  "@azure/msal-node": "^3.6.0",
42
- "@modelcontextprotocol/sdk": "1.21.0",
43
+ "@modelcontextprotocol/sdk": "1.24.0",
43
44
  "azure-devops-extension-api": "^4.252.0",
44
45
  "azure-devops-extension-sdk": "^4.0.2",
45
46
  "azure-devops-node-api": "^15.1.0",
47
+ "winston": "^3.18.3",
46
48
  "yargs": "^18.0.0",
47
49
  "zod": "^3.25.63",
48
50
  "zod-to-json-schema": "^3.24.5"
@@ -50,17 +52,24 @@
50
52
  "devDependencies": {
51
53
  "@modelcontextprotocol/inspector": "^0.17.0",
52
54
  "@types/jest": "^30.0.0",
53
- "@types/node": "^22",
55
+ "@types/node": "^22.19.1",
54
56
  "eslint-config-prettier": "10.1.8",
55
57
  "eslint-plugin-header": "^3.1.1",
56
- "glob": "^11.0.3",
58
+ "glob": "^13.0.0",
59
+ "husky": "^9.1.7",
57
60
  "jest": "^30.0.2",
58
61
  "jest-extended": "^7.0.0",
59
- "prettier": "3.6.2",
62
+ "lint-staged": "^16.2.7",
63
+ "prettier": "3.7.3",
60
64
  "shx": "^0.4.0",
61
65
  "ts-jest": "^29.4.0",
62
66
  "tsconfig-paths": "^4.2.0",
63
67
  "typescript": "^5.9.3",
64
- "typescript-eslint": "^8.45.0"
68
+ "typescript-eslint": "^8.48.0"
69
+ },
70
+ "lint-staged": {
71
+ "**/*.(js|ts|jsx|tsx|json|css|md)": [
72
+ "npm run format"
73
+ ]
65
74
  }
66
75
  }
package/dist/domains.js DELETED
@@ -1 +0,0 @@
1
- export {};
package/dist/http.js DELETED
@@ -1,52 +0,0 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT License.
3
- import express from "express";
4
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
- import { serverBuildAndConnect } from "./server.js";
6
- import { packageVersion } from "./version.js";
7
- const app = express();
8
- app.use(express.json());
9
- app.post('/mcp/:orgName', async (req, res) => {
10
- // In stateless mode, create a new instance of transport and server for each request
11
- // to ensure complete isolation. A single instance would cause request ID collisions
12
- // when multiple clients connect concurrently.
13
- try {
14
- const transport = new StreamableHTTPServerTransport({
15
- sessionIdGenerator: undefined,
16
- });
17
- const server = await serverBuildAndConnect(req.params.orgName, transport);
18
- res.on('close', () => {
19
- transport.close();
20
- server.close();
21
- });
22
- await transport.handleRequest(req, res, req.body);
23
- }
24
- catch (error) {
25
- console.error('Error handling MCP request:', error);
26
- if (!res.headersSent) {
27
- res.status(500).json({
28
- jsonrpc: '2.0',
29
- error: {
30
- code: -32603,
31
- message: 'Internal server error',
32
- },
33
- id: null,
34
- });
35
- }
36
- }
37
- });
38
- app.get('/mcp/:orgName', async (req, res) => {
39
- console.log('Received GET MCP request');
40
- res.writeHead(405).end(JSON.stringify({
41
- jsonrpc: "2.0",
42
- error: {
43
- code: -32000,
44
- message: "Method not allowed."
45
- },
46
- id: null
47
- }));
48
- });
49
- const PORT = 3000;
50
- app.listen(PORT, () => {
51
- console.log(`Azure DevOps MCP Server with http transport listening on port ${PORT}. Version: ${packageVersion}`);
52
- });
@@ -1,73 +0,0 @@
1
- import * as fs from 'fs/promises';
2
- import * as os from 'os';
3
- import * as path from 'path';
4
- const CACHE_FILE = path.join(os.homedir(), '.ado_orgs.cache');
5
- const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 1 week in milliseconds
6
- async function loadCache() {
7
- try {
8
- const cacheData = await fs.readFile(CACHE_FILE, 'utf-8');
9
- return JSON.parse(cacheData);
10
- }
11
- catch (error) {
12
- // Cache file doesn't exist or is invalid, return empty cache
13
- return {};
14
- }
15
- }
16
- async function trySavingCache(cache) {
17
- try {
18
- await fs.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), 'utf-8');
19
- }
20
- catch (error) {
21
- console.error('Failed to save org tenants cache:', error);
22
- }
23
- }
24
- async function fetchTenantFromApi(orgName) {
25
- const url = `https://vssps.dev.azure.com/${orgName}`;
26
- try {
27
- const response = await fetch(url, { method: 'HEAD' });
28
- if (response.status !== 404) {
29
- throw new Error(`Expected status 404, got ${response.status}`);
30
- }
31
- const tenantId = response.headers.get('x-vss-resourcetenant');
32
- if (!tenantId) {
33
- throw new Error('x-vss-resourcetenant header not found in response');
34
- }
35
- return tenantId;
36
- }
37
- catch (error) {
38
- throw new Error(`Failed to fetch tenant for organization ${orgName}: ${error}`);
39
- }
40
- }
41
- function isCacheEntryExpired(entry) {
42
- return Date.now() - entry.refreshedOn > CACHE_TTL_MS;
43
- }
44
- export async function getOrgTenant(orgName) {
45
- // Load cache
46
- const cache = await loadCache();
47
- // Check if tenant is cached and not expired
48
- const cachedEntry = cache[orgName];
49
- if (cachedEntry && !isCacheEntryExpired(cachedEntry)) {
50
- return cachedEntry.tenantId;
51
- }
52
- // Try to fetch fresh tenant from API
53
- try {
54
- const tenantId = await fetchTenantFromApi(orgName);
55
- // Cache the result
56
- cache[orgName] = {
57
- tenantId,
58
- refreshedOn: Date.now()
59
- };
60
- await trySavingCache(cache);
61
- return tenantId;
62
- }
63
- catch (error) {
64
- // If we have an expired cache entry, return it as fallback
65
- if (cachedEntry) {
66
- console.error(`Failed to fetch fresh tenant for ADO org ${orgName}, using expired cache entry:`, error);
67
- return cachedEntry.tenantId;
68
- }
69
- // No cache entry available, log and return empty result
70
- console.error(`Failed to fetch tenant for ADO org ${orgName}:`, error);
71
- return undefined;
72
- }
73
- }
package/dist/server.js DELETED
@@ -1,36 +0,0 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT License.
3
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import * as azdev from "azure-devops-node-api";
5
- import { DefaultAzureCredential } from "@azure/identity";
6
- import { configurePrompts } from "./prompts.js";
7
- import { configureAllTools } from "./tools.js";
8
- import { userAgent } from "./utils.js";
9
- import { packageVersion } from "./version.js";
10
- async function getAzureDevOpsToken() {
11
- process.env.AZURE_TOKEN_CREDENTIALS = "dev";
12
- const credential = new DefaultAzureCredential(); // CodeQL [SM05138] resolved by explicitly setting AZURE_TOKEN_CREDENTIALS
13
- const token = await credential.getToken("499b84ac-1321-427f-aa17-267ca6975798/.default");
14
- return token;
15
- }
16
- async function getAzureDevOpsClient(orgUrl) {
17
- const token = await getAzureDevOpsToken();
18
- const authHandler = azdev.getBearerHandler(token.token);
19
- const connection = new azdev.WebApi(orgUrl, authHandler, undefined, {
20
- productName: "AzureDevOps.MCP",
21
- productVersion: packageVersion,
22
- userAgent: userAgent
23
- });
24
- return connection;
25
- }
26
- export async function serverBuildAndConnect(orgName, transport) {
27
- const server = new McpServer({
28
- name: "Azure DevOps MCP Server",
29
- version: packageVersion,
30
- });
31
- const orgUrl = "https://dev.azure.com/" + orgName;
32
- configurePrompts(server);
33
- configureAllTools(server, () => orgName, getAzureDevOpsToken, () => getAzureDevOpsClient(orgUrl));
34
- await server.connect(transport);
35
- return server;
36
- }
package/dist/tenant.js DELETED
@@ -1,73 +0,0 @@
1
- import * as fs from 'fs/promises';
2
- import * as os from 'os';
3
- import * as path from 'path';
4
- const CACHE_FILE = path.join(os.homedir(), '.ado_orgs.cache');
5
- const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 1 week in milliseconds
6
- async function loadCache() {
7
- try {
8
- const cacheData = await fs.readFile(CACHE_FILE, 'utf-8');
9
- return JSON.parse(cacheData);
10
- }
11
- catch (error) {
12
- // Cache file doesn't exist or is invalid, return empty cache
13
- return {};
14
- }
15
- }
16
- async function saveCache(cache) {
17
- try {
18
- await fs.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), 'utf-8');
19
- }
20
- catch (error) {
21
- console.warn('Failed to save cache:', error);
22
- }
23
- }
24
- async function fetchTenantFromApi(orgName) {
25
- const url = `https://vssps.dev.azure.com/${orgName}`;
26
- try {
27
- const response = await fetch(url, { method: 'HEAD' });
28
- if (response.status !== 404) {
29
- throw new Error(`Expected status 404, got ${response.status}`);
30
- }
31
- const tenantId = response.headers.get('x-vss-resourcetenant');
32
- if (!tenantId) {
33
- throw new Error('x-vss-resourcetenant header not found in response');
34
- }
35
- return tenantId;
36
- }
37
- catch (error) {
38
- throw new Error(`Failed to fetch tenant for organization ${orgName}: ${error}`);
39
- }
40
- }
41
- function isCacheEntryExpired(entry) {
42
- return Date.now() - entry.refreshedOn > CACHE_TTL_MS;
43
- }
44
- export async function getOrgTenant(orgName) {
45
- // Load cache
46
- const cache = await loadCache();
47
- // Check if tenant is cached and not expired
48
- const cachedEntry = cache[orgName];
49
- if (cachedEntry && !isCacheEntryExpired(cachedEntry)) {
50
- return cachedEntry.tenantId;
51
- }
52
- // Try to fetch fresh tenant from API
53
- try {
54
- const tenantId = await fetchTenantFromApi(orgName);
55
- // Cache the fresh result
56
- cache[orgName] = {
57
- tenantId,
58
- refreshedOn: Date.now()
59
- };
60
- await saveCache(cache);
61
- return tenantId;
62
- }
63
- catch (error) {
64
- // If we have an expired cache entry, return it as fallback
65
- if (cachedEntry) {
66
- console.error(`Failed to fetch fresh tenant for ADO org ${orgName}, using expired cache entry:`, error);
67
- return cachedEntry.tenantId;
68
- }
69
- // No cache entry available, re-throw the error
70
- console.error(`Failed to fetch tenant for ADO org ${orgName}:`, error);
71
- return undefined;
72
- }
73
- }
@@ -1,108 +0,0 @@
1
- // Copyright (c) Microsoft Corporation.
2
- // Licensed under the MIT License.
3
- import { AlertType, AlertValidityStatus, Confidence, Severity, State } from "azure-devops-node-api/interfaces/AlertInterfaces.js";
4
- import { z } from "zod";
5
- import { getEnumKeys, mapStringArrayToEnum, mapStringToEnum } from "../utils.js";
6
- const ADVSEC_TOOLS = {
7
- get_alerts: "advsec_get_alerts",
8
- get_alert_details: "advsec_get_alert_details",
9
- };
10
- function configureAdvSecTools(server, tokenProvider, connectionProvider) {
11
- server.tool(ADVSEC_TOOLS.get_alerts, "Retrieve Advanced Security alerts for a repository.", {
12
- project: z.string().describe("The name or ID of the Azure DevOps project."),
13
- repository: z.string().describe("The name or ID of the repository to get alerts for."),
14
- alertType: z
15
- .enum(getEnumKeys(AlertType))
16
- .optional()
17
- .describe("Filter alerts by type. If not specified, returns all alert types."),
18
- states: z
19
- .array(z.enum(getEnumKeys(State)))
20
- .optional()
21
- .describe("Filter alerts by state. If not specified, returns alerts in any state."),
22
- severities: z
23
- .array(z.enum(getEnumKeys(Severity)))
24
- .optional()
25
- .describe("Filter alerts by severity level. If not specified, returns alerts at any severity."),
26
- ruleId: z.string().optional().describe("Filter alerts by rule ID."),
27
- ruleName: z.string().optional().describe("Filter alerts by rule name."),
28
- toolName: z.string().optional().describe("Filter alerts by tool name."),
29
- ref: z.string().optional().describe("Filter alerts by git reference (branch). If not provided and onlyDefaultBranch is true, only includes alerts from default branch."),
30
- onlyDefaultBranch: z.boolean().optional().default(true).describe("If true, only return alerts found on the default branch. Defaults to true."),
31
- confidenceLevels: z
32
- .array(z.enum(getEnumKeys(Confidence)))
33
- .optional()
34
- .default(["high", "other"])
35
- .describe("Filter alerts by confidence levels. Only applicable for secret alerts. Defaults to both 'high' and 'other'."),
36
- validity: z
37
- .array(z.enum(getEnumKeys(AlertValidityStatus)))
38
- .optional()
39
- .describe("Filter alerts by validity status. Only applicable for secret alerts."),
40
- top: z.number().optional().default(100).describe("Maximum number of alerts to return. Defaults to 100."),
41
- orderBy: z.enum(["id", "firstSeen", "lastSeen", "fixedOn", "severity"]).optional().default("severity").describe("Order results by specified field. Defaults to 'severity'."),
42
- continuationToken: z.string().optional().describe("Continuation token for pagination."),
43
- }, async ({ project, repository, alertType, states, severities, ruleId, ruleName, toolName, ref, onlyDefaultBranch, confidenceLevels, validity, top, orderBy, continuationToken }) => {
44
- try {
45
- const connection = await connectionProvider();
46
- const alertApi = await connection.getAlertApi();
47
- const isSecretAlert = !alertType || alertType.toLowerCase() === "secret";
48
- const criteria = {
49
- ...(alertType && { alertType: mapStringToEnum(alertType, AlertType) }),
50
- ...(states && { states: mapStringArrayToEnum(states, State) }),
51
- ...(severities && { severities: mapStringArrayToEnum(severities, Severity) }),
52
- ...(ruleId && { ruleId }),
53
- ...(ruleName && { ruleName }),
54
- ...(toolName && { toolName }),
55
- ...(ref && { ref }),
56
- ...(onlyDefaultBranch !== undefined && { onlyDefaultBranch }),
57
- ...(isSecretAlert && confidenceLevels && { confidenceLevels: mapStringArrayToEnum(confidenceLevels, Confidence) }),
58
- ...(isSecretAlert && validity && { validity: mapStringArrayToEnum(validity, AlertValidityStatus) }),
59
- };
60
- const result = await alertApi.getAlerts(project, repository, top, orderBy, criteria, undefined, // expand parameter
61
- continuationToken);
62
- return {
63
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
64
- };
65
- }
66
- catch (error) {
67
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
68
- return {
69
- content: [
70
- {
71
- type: "text",
72
- text: `Error fetching Advanced Security alerts: ${errorMessage}`,
73
- },
74
- ],
75
- isError: true,
76
- };
77
- }
78
- });
79
- server.tool(ADVSEC_TOOLS.get_alert_details, "Get detailed information about a specific Advanced Security alert.", {
80
- project: z.string().describe("The name or ID of the Azure DevOps project."),
81
- repository: z.string().describe("The name or ID of the repository containing the alert."),
82
- alertId: z.number().describe("The ID of the alert to retrieve details for."),
83
- ref: z.string().optional().describe("Git reference (branch) to filter the alert."),
84
- }, async ({ project, repository, alertId, ref }) => {
85
- try {
86
- const connection = await connectionProvider();
87
- const alertApi = await connection.getAlertApi();
88
- const result = await alertApi.getAlert(project, alertId, repository, ref, undefined // expand parameter
89
- );
90
- return {
91
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
92
- };
93
- }
94
- catch (error) {
95
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
96
- return {
97
- content: [
98
- {
99
- type: "text",
100
- text: `Error fetching alert details: ${errorMessage}`,
101
- },
102
- ],
103
- isError: true,
104
- };
105
- }
106
- });
107
- }
108
- export { ADVSEC_TOOLS, configureAdvSecTools };