diff --git a/intTest/domains/mcp.json b/intTest/domains/mcp.json new file mode 100644 index 00000000..1ed38060 --- /dev/null +++ b/intTest/domains/mcp.json @@ -0,0 +1,23 @@ +{ + "servers": { + "ado": { + "type": "stdio", + "command": "mcp-server-azuredevops", + "args": [ + "${input:ado_org}", + "-d", + "core", + "work", + "workitems" + // ... any other domain to enable, you can also use 'all' (which is already the default) + ] + } + }, + "inputs": [ + { + "id": "ado_org", + "type": "promptString", + "description": "Azure DevOps organization name (e.g. 'contoso')" + } + ] +} diff --git a/package-lock.json b/package-lock.json index d2c362fe..c5dc8b1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@azure-devops/mcp", - "version": "1.3.1", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@azure-devops/mcp", - "version": "1.3.1", + "version": "2.0.0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -3822,9 +3822,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -5147,9 +5147,9 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1466f255..21bd53ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@azure-devops/mcp", - "version": "1.3.1", + "version": "2.0.0", "description": "MCP server for interacting with Azure DevOps", "license": "MIT", "author": "Microsoft Corporation", diff --git a/src/index.ts b/src/index.ts index 79cc9a32..18f33850 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,18 +14,27 @@ import { configurePrompts } from "./prompts.js"; import { configureAllTools } from "./tools.js"; import { UserAgentComposer } from "./useragent.js"; import { packageVersion } from "./version.js"; +import { DomainsManager } from "./shared/domains.js"; // Parse command line arguments using yargs const argv = yargs(hideBin(process.argv)) .scriptName("mcp-server-azuredevops") .usage("Usage: $0 [options]") .version(packageVersion) - .command("$0 ", "Azure DevOps MCP Server", (yargs) => { + .command("$0 [options]", "Azure DevOps MCP Server", (yargs) => { yargs.positional("organization", { describe: "Azure DevOps organization name", type: "string", + demandOption: true, }); }) + .option("domains", { + alias: "d", + describe: "Domain(s) to enable: 'all' for everything, or specific domains like 'repositories builds work'. Defaults to 'all'.", + type: "string", + array: true, + default: "all", + }) .option("tenant", { alias: "t", describe: "Azure tenant ID (optional, required for multi-tenant scenarios)", @@ -34,10 +43,14 @@ const argv = yargs(hideBin(process.argv)) .help() .parseSync(); -export const orgName = argv.organization as string; const tenantId = argv.tenant; + +export const orgName = argv.organization as string; const orgUrl = "https://dev.azure.com/" + orgName; +const domainsManager = new DomainsManager(argv.domains); +export const enabledDomains = domainsManager.getEnabledDomains(); + async function getAzureDevOpsToken(): Promise { if (process.env.ADO_MCP_AZURE_TOKEN_CREDENTIALS) { process.env.AZURE_TOKEN_CREDENTIALS = process.env.ADO_MCP_AZURE_TOKEN_CREDENTIALS; @@ -84,7 +97,7 @@ async function main() { configurePrompts(server); - configureAllTools(server, getAzureDevOpsToken, getAzureDevOpsClient(userAgentComposer), () => userAgentComposer.userAgent); + configureAllTools(server, getAzureDevOpsToken, getAzureDevOpsClient(userAgentComposer), () => userAgentComposer.userAgent, enabledDomains); const transport = new StdioServerTransport(); await server.connect(transport); diff --git a/src/prompts.ts b/src/prompts.ts index e8f5f3b3..bbd7960f 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -4,7 +4,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { CORE_TOOLS } from "./tools/core.js"; -import { WORKITEM_TOOLS } from "./tools/workitems.js"; +import { WORKITEM_TOOLS } from "./tools/work-items.js"; function configurePrompts(server: McpServer) { server.prompt("Projects", "Lists all projects in the Azure DevOps organization.", {}, () => ({ diff --git a/src/shared/domains.ts b/src/shared/domains.ts new file mode 100644 index 00000000..b02e7d94 --- /dev/null +++ b/src/shared/domains.ts @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Available Azure DevOps MCP domains + */ +export enum Domain { + ADVANCED_SECURITY = "advanced-security", + BUILDS = "builds", + CORE = "core", + RELEASES = "releases", + REPOSITORIES = "repositories", + SEARCH = "search", + TEST_PLANS = "test-plans", + WIKI = "wiki", + WORK = "work", + WORK_ITEMS = "work-items", +} + +export const ALL_DOMAINS = "all"; + +/** + * Manages domain parsing and validation for Azure DevOps MCP server tools + */ +export class DomainsManager { + private static readonly AVAILABLE_DOMAINS = Object.values(Domain); + + private readonly enabledDomains: Set; + + constructor(domainsInput?: string | string[]) { + this.enabledDomains = new Set(); + const normalizedInput = DomainsManager.parseDomainsInput(domainsInput); + this.parseDomains(normalizedInput); + } + + /** + * Parse and validate domains from input + * @param domainsInput - Either "all", single domain name, array of domain names, or undefined (defaults to "all") + */ + private parseDomains(domainsInput?: string | string[]): void { + if (!domainsInput) { + this.enableAllDomains(); + return; + } + + if (Array.isArray(domainsInput)) { + this.handleArrayInput(domainsInput); + return; + } + + this.handleStringInput(domainsInput); + } + + private handleArrayInput(domainsInput: string[]): void { + if (domainsInput.length === 0 || domainsInput.includes(ALL_DOMAINS)) { + this.enableAllDomains(); + return; + } + + if (domainsInput.length === 1 && domainsInput[0] === ALL_DOMAINS) { + this.enableAllDomains(); + return; + } + + const domains = domainsInput.map((d) => d.trim().toLowerCase()); + this.validateAndAddDomains(domains); + } + + private handleStringInput(domainsInput: string): void { + if (domainsInput === ALL_DOMAINS) { + this.enableAllDomains(); + return; + } + + const domains = [domainsInput.trim().toLowerCase()]; + this.validateAndAddDomains(domains); + } + + private validateAndAddDomains(domains: string[]): void { + const availableDomainsAsStringArray = Object.values(Domain) as string[]; + domains.forEach((domain) => { + if (availableDomainsAsStringArray.includes(domain)) { + this.enabledDomains.add(domain); + } else if (domain === ALL_DOMAINS) { + this.enableAllDomains(); + } else { + console.error(`Error: Specified invalid domain '${domain}'. Please specify exactly as available domains: ${Object.values(Domain).join(", ")}`); + } + }); + + if (this.enabledDomains.size === 0) { + this.enableAllDomains(); + } + } + + private enableAllDomains(): void { + Object.values(Domain).forEach((domain) => this.enabledDomains.add(domain)); + } + + /** + * Check if a specific domain is enabled + * @param domain - Domain name to check + * @returns true if domain is enabled + */ + public isDomainEnabled(domain: string): boolean { + return this.enabledDomains.has(domain); + } + + /** + * Get all enabled domains + * @returns Set of enabled domain names + */ + public getEnabledDomains(): Set { + return new Set(this.enabledDomains); + } + + /** + * Get list of all available domains + * @returns Array of available domain names + */ + public static getAvailableDomains(): string[] { + return Object.values(Domain); + } + + /** + * Parse domains input from string or array to a normalized array of strings + * @param domainsInput - Domains input to parse + * @returns Normalized array of domain strings + */ + public static parseDomainsInput(domainsInput?: string | string[]): string[] { + if (!domainsInput) { + return []; + } + + if (typeof domainsInput === "string") { + return domainsInput.split(",").map((d) => d.trim().toLowerCase()); + } + + return domainsInput.map((d) => d.trim().toLowerCase()); + } +} diff --git a/src/tools.ts b/src/tools.ts index 31824655..34d75210 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -5,28 +5,35 @@ import { AccessToken } from "@azure/identity"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { WebApi } from "azure-devops-node-api"; -import { configureAdvSecTools } from "./tools/advsec.js"; +import { Domain } from "./shared/domains.js"; +import { configureAdvSecTools } from "./tools/advanced-security.js"; import { configureBuildTools } from "./tools/builds.js"; import { configureCoreTools } from "./tools/core.js"; import { configureReleaseTools } from "./tools/releases.js"; -import { configureRepoTools } from "./tools/repos.js"; +import { configureRepoTools } from "./tools/repositories.js"; import { configureSearchTools } from "./tools/search.js"; -import { configureTestPlanTools } from "./tools/testplans.js"; +import { configureTestPlanTools } from "./tools/test-plans.js"; import { configureWikiTools } from "./tools/wiki.js"; import { configureWorkTools } from "./tools/work.js"; -import { configureWorkItemTools } from "./tools/workitems.js"; +import { configureWorkItemTools } from "./tools/work-items.js"; -function configureAllTools(server: McpServer, tokenProvider: () => Promise, connectionProvider: () => Promise, userAgentProvider: () => string) { - configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); - configureWorkTools(server, tokenProvider, connectionProvider); - configureBuildTools(server, tokenProvider, connectionProvider, userAgentProvider); - configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider); - configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider); - configureReleaseTools(server, tokenProvider, connectionProvider); - configureWikiTools(server, tokenProvider, connectionProvider); - configureTestPlanTools(server, tokenProvider, connectionProvider); - configureSearchTools(server, tokenProvider, connectionProvider, userAgentProvider); - configureAdvSecTools(server, tokenProvider, connectionProvider); +function configureAllTools(server: McpServer, tokenProvider: () => Promise, connectionProvider: () => Promise, userAgentProvider: () => string, enabledDomains: Set) { + const configureIfDomainEnabled = (domain: string, configureFn: () => void) => { + if (enabledDomains.has(domain)) { + configureFn(); + } + }; + + configureIfDomainEnabled(Domain.CORE, () => configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider)); + configureIfDomainEnabled(Domain.WORK, () => configureWorkTools(server, tokenProvider, connectionProvider)); + configureIfDomainEnabled(Domain.BUILDS, () => configureBuildTools(server, tokenProvider, connectionProvider, userAgentProvider)); + configureIfDomainEnabled(Domain.REPOSITORIES, () => configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider)); + configureIfDomainEnabled(Domain.WORK_ITEMS, () => configureWorkItemTools(server, tokenProvider, connectionProvider, userAgentProvider)); + configureIfDomainEnabled(Domain.RELEASES, () => configureReleaseTools(server, tokenProvider, connectionProvider)); + configureIfDomainEnabled(Domain.WIKI, () => configureWikiTools(server, tokenProvider, connectionProvider)); + configureIfDomainEnabled(Domain.TEST_PLANS, () => configureTestPlanTools(server, tokenProvider, connectionProvider)); + configureIfDomainEnabled(Domain.SEARCH, () => configureSearchTools(server, tokenProvider, connectionProvider, userAgentProvider)); + configureIfDomainEnabled(Domain.ADVANCED_SECURITY, () => configureAdvSecTools(server, tokenProvider, connectionProvider)); } export { configureAllTools }; diff --git a/src/tools/advsec.ts b/src/tools/advanced-security.ts similarity index 100% rename from src/tools/advsec.ts rename to src/tools/advanced-security.ts diff --git a/src/tools/repos.ts b/src/tools/repositories.ts similarity index 100% rename from src/tools/repos.ts rename to src/tools/repositories.ts diff --git a/src/tools/testplans.ts b/src/tools/test-plans.ts similarity index 100% rename from src/tools/testplans.ts rename to src/tools/test-plans.ts diff --git a/src/tools/workitems.ts b/src/tools/work-items.ts similarity index 100% rename from src/tools/workitems.ts rename to src/tools/work-items.ts diff --git a/src/version.ts b/src/version.ts index 8b32b968..856af813 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const packageVersion = "1.3.1"; +export const packageVersion = "2.0.0"; diff --git a/test/src/domains.test.ts b/test/src/domains.test.ts new file mode 100644 index 00000000..31863eb9 --- /dev/null +++ b/test/src/domains.test.ts @@ -0,0 +1,233 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { DomainsManager } from "../../src/shared/domains"; + +describe("DomainsManager: backward compatibility and domain enabling", () => { + let errorSpy: jest.SpyInstance; + + beforeEach(() => { + errorSpy = jest.spyOn(console, "error").mockImplementation(); + }); + + afterEach(() => { + errorSpy.mockRestore(); + }); + + describe("constructor", () => { + it("enables all domains when no argument is provided (default behavior)", () => { + const manager = new DomainsManager(); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(10); + expect(enabledDomains.has("advanced-security")).toBe(true); + expect(enabledDomains.has("builds")).toBe(true); + expect(enabledDomains.has("core")).toBe(true); + expect(enabledDomains.has("releases")).toBe(true); + expect(enabledDomains.has("repositories")).toBe(true); + expect(enabledDomains.has("search")).toBe(true); + expect(enabledDomains.has("test-plans")).toBe(true); + expect(enabledDomains.has("wiki")).toBe(true); + expect(enabledDomains.has("work")).toBe(true); + expect(enabledDomains.has("work-items")).toBe(true); + }); + + it("enables all domains when undefined is passed as argument", () => { + const manager = new DomainsManager(undefined); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(10); + expect(Array.from(enabledDomains).sort()).toEqual(["advanced-security", "builds", "core", "releases", "repositories", "search", "test-plans", "wiki", "work", "work-items"]); + }); + + it("enables all domains when null is passed as argument (legacy support)", () => { + // @ts-ignore - Testing null input for backward compatibility + const manager = new DomainsManager(null); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(10); + }); + }); + + describe("string input handling", () => { + it("enables all domains when the string 'all' is passed", () => { + const manager = new DomainsManager("all"); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(10); + expect(enabledDomains.has("repositories")).toBe(true); + expect(enabledDomains.has("builds")).toBe(true); + }); + + it("enables only the specified domain when a valid domain name string is passed", () => { + const manager = new DomainsManager("repositories"); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(1); + expect(enabledDomains.has("repositories")).toBe(true); + }); + + it("enables only the specified domain when a valid domain name string is passed (case insensitive)", () => { + const manager = new DomainsManager("REPOSITORIES"); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(1); + expect(enabledDomains.has("repositories")).toBe(true); + }); + + it("Error and enables all domains when an invalid domain name string is passed", () => { + const manager = new DomainsManager("invalid-domain"); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(10); + expect(errorSpy).toHaveBeenCalledWith( + "Error: Specified invalid domain 'invalid-domain'. Please specify exactly as available domains: advanced-security, builds, core, releases, repositories, search, test-plans, wiki, work, work-items" + ); + }); + }); + + describe("array input handling", () => { + it("enables all domains when the array ['all'] is passed", () => { + const manager = new DomainsManager(["all"]); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(10); + expect(enabledDomains.has("repositories")).toBe(true); + expect(enabledDomains.has("builds")).toBe(true); + }); + + it("enables only the specified domains when an array of valid domain names is passed", () => { + const manager = new DomainsManager(["repositories", "builds", "work"]); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(3); + expect(enabledDomains.has("repositories")).toBe(true); + expect(enabledDomains.has("builds")).toBe(true); + expect(enabledDomains.has("work")).toBe(true); + expect(enabledDomains.has("wiki")).toBe(false); + }); + + it("enables all domains when an empty array is passed", () => { + const manager = new DomainsManager([]); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(10); + }); + + it("filters out invalid domains and enables only valid ones when mixed array is passed", () => { + const manager = new DomainsManager(["repositories", "invalid-domain", "builds"]); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(2); + expect(enabledDomains.has("repositories")).toBe(true); + expect(enabledDomains.has("builds")).toBe(true); + }); + + it("enables specified domains when array contains valid domain names in any case", () => { + const manager = new DomainsManager(["REPOSITORIES", "Builds", "work"]); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(3); + expect(enabledDomains.has("repositories")).toBe(true); + expect(enabledDomains.has("builds")).toBe(true); + expect(enabledDomains.has("work")).toBe(true); + }); + }); + + describe("isDomainEnabled method", () => { + it("returns true for domains that are enabled", () => { + const manager = new DomainsManager(["repositories", "builds"]); + + expect(manager.isDomainEnabled("repositories")).toBe(true); + expect(manager.isDomainEnabled("builds")).toBe(true); + expect(manager.isDomainEnabled("wiki")).toBe(false); + }); + + it("returns false for domains that are not enabled", () => { + const manager = new DomainsManager(["repositories"]); + + expect(manager.isDomainEnabled("builds")).toBe(false); + expect(manager.isDomainEnabled("wiki")).toBe(false); + }); + }); + + describe("getAvailableDomains method", () => { + it("returns the full list of available domains", () => { + const availableDomains = DomainsManager.getAvailableDomains(); + + expect(availableDomains).toEqual(["advanced-security", "builds", "core", "releases", "repositories", "search", "test-plans", "wiki", "work", "work-items"]); + expect(availableDomains.length).toBe(10); + }); + }); + + describe("getEnabledDomains method", () => { + it("returns a new Set instance each time (not a reference to internal set)", () => { + const manager = new DomainsManager(["repositories"]); + const enabledDomains1 = manager.getEnabledDomains(); + const enabledDomains2 = manager.getEnabledDomains(); + + expect(enabledDomains1).not.toBe(enabledDomains2); + expect(enabledDomains1).toEqual(enabledDomains2); + }); + + it("prevents external modification of enabled domains", () => { + const manager = new DomainsManager(["repositories"]); + const enabledDomains = manager.getEnabledDomains(); + + enabledDomains.add("builds"); + + expect(manager.isDomainEnabled("builds")).toBe(false); + expect(manager.getEnabledDomains().has("builds")).toBe(false); + }); + }); + + describe("edge cases", () => { + it("trims whitespace from domain names in input array", () => { + const manager = new DomainsManager([" repositories ", " builds "]); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(2); + expect(enabledDomains.has("repositories")).toBe(true); + expect(enabledDomains.has("builds")).toBe(true); + }); + + it("handles duplicate domain names in input array by enabling each only once", () => { + const manager = new DomainsManager(["repositories", "repositories", "builds"]); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(2); + expect(enabledDomains.has("repositories")).toBe(true); + expect(enabledDomains.has("builds")).toBe(true); + }); + + it("enables all domains when the string 'ALL' (any case) is passed", () => { + const manager = new DomainsManager("ALL"); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(10); + }); + }); + + describe("'all' domain enabling scenarios", () => { + it("enables all domains when only 'all' is passed in array", () => { + const manager = new DomainsManager(["all"]); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(10); + }); + + it("enables all domains when 'all' is passed together with other valid domains", () => { + const manager = new DomainsManager(["all", "builds"]); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(10); + }); + + it("enables all domains when 'all' is passed along with invalid domains", () => { + const manager = new DomainsManager(["a", "all", "wiki"]); + const enabledDomains = manager.getEnabledDomains(); + + expect(enabledDomains.size).toBe(10); + }); + }); +}); diff --git a/test/src/tools/advsec.test.ts b/test/src/tools/advanced-security.test.ts similarity index 99% rename from test/src/tools/advsec.test.ts rename to test/src/tools/advanced-security.test.ts index 7b3315a6..83f0af02 100644 --- a/test/src/tools/advsec.test.ts +++ b/test/src/tools/advanced-security.test.ts @@ -4,7 +4,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { WebApi } from "azure-devops-node-api"; import { Alert, AlertType, AlertValidityStatus, Confidence, Severity, State } from "azure-devops-node-api/interfaces/AlertInterfaces"; import { PagedList } from "azure-devops-node-api/interfaces/common/VSSInterfaces"; -import { configureAdvSecTools } from "../../../src/tools/advsec"; +import { configureAdvSecTools } from "../../../src/tools/advanced-security"; type TokenProviderMock = () => Promise; type ConnectionProviderMock = () => Promise; diff --git a/test/src/tools/repos.test.ts b/test/src/tools/repositories.test.ts similarity index 99% rename from test/src/tools/repos.test.ts rename to test/src/tools/repositories.test.ts index 984fced6..5b58a9bd 100644 --- a/test/src/tools/repos.test.ts +++ b/test/src/tools/repositories.test.ts @@ -4,8 +4,8 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { AccessToken } from "@azure/identity"; import { WebApi } from "azure-devops-node-api"; +import { configureRepoTools, REPO_TOOLS } from "../../../src/tools/repositories"; import { PullRequestStatus, GitVersionType, GitPullRequestQueryType, CommentThreadStatus } from "azure-devops-node-api/interfaces/GitInterfaces.js"; -import { configureRepoTools, REPO_TOOLS } from "../../../src/tools/repos"; import { getCurrentUserDetails, getUserIdFromEmail } from "../../../src/tools/auth"; // Mock the auth module diff --git a/test/src/tools/testplan.test.ts b/test/src/tools/test-plan.test.ts similarity index 99% rename from test/src/tools/testplan.test.ts rename to test/src/tools/test-plan.test.ts index 8d199167..0c1c59d4 100644 --- a/test/src/tools/testplan.test.ts +++ b/test/src/tools/test-plan.test.ts @@ -2,7 +2,7 @@ import { AccessToken } from "@azure/identity"; import { describe, expect, it } from "@jest/globals"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { WebApi } from "azure-devops-node-api"; -import { configureTestPlanTools } from "../../../src/tools/testplans"; +import { configureTestPlanTools } from "../../../src/tools/test-plans"; import { ITestPlanApi } from "azure-devops-node-api/TestPlanApi"; import { ITestResultsApi } from "azure-devops-node-api/TestResultsApi"; import { IWorkItemTrackingApi } from "azure-devops-node-api/WorkItemTrackingApi"; diff --git a/test/src/tools/workitems.test.ts b/test/src/tools/work-items.test.ts similarity index 99% rename from test/src/tools/workitems.test.ts rename to test/src/tools/work-items.test.ts index b8ed3cea..2ad3986d 100644 --- a/test/src/tools/workitems.test.ts +++ b/test/src/tools/work-items.test.ts @@ -1,7 +1,7 @@ import { AccessToken } from "@azure/identity"; import { describe, expect, it } from "@jest/globals"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { configureWorkItemTools } from "../../../src/tools/workitems"; +import { configureWorkItemTools } from "../../../src/tools/work-items"; import { WebApi } from "azure-devops-node-api"; import { QueryExpand } from "azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js"; import {