Skip to main content
Glama
core.test.ts24.2 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { AccessToken } from "@azure/identity"; import { describe, expect, it } from "@jest/globals"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { configureCoreTools } from "../../../src/tools/core"; import { WebApi } from "azure-devops-node-api"; type TokenProviderMock = () => Promise<string>; type ConnectionProviderMock = () => Promise<WebApi>; interface CoreApiMock { getTeams: jest.Mock; getProjects: jest.Mock; } describe("configureCoreTools", () => { let server: McpServer; let tokenProvider: TokenProviderMock; let connectionProvider: ConnectionProviderMock; let userAgentProvider: () => string; let mockConnection: { getCoreApi: jest.Mock }; let mockCoreApi: CoreApiMock; beforeEach(() => { server = { tool: jest.fn() } as unknown as McpServer; tokenProvider = jest.fn(); userAgentProvider = () => "Jest"; mockCoreApi = { getProjects: jest.fn(), getTeams: jest.fn(), }; mockConnection = { getCoreApi: jest.fn().mockResolvedValue(mockCoreApi), }; connectionProvider = jest.fn().mockResolvedValue(mockConnection); }); describe("tool registration", () => { it("registers core tools on the server", () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); expect(server.tool as jest.Mock).toHaveBeenCalled(); }); }); describe("list_projects tool", () => { it("should call getProjects API with the correct parameters and return the expected result", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_list_projects"); if (!call) throw new Error("core_list_projects tool not registered"); const [, , , handler] = call; (mockCoreApi.getProjects as jest.Mock).mockResolvedValue([ { id: "eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", name: "Fabrikam-Fiber-TFVC", description: "Team Foundation Version Control projects.", url: "https://dev.azure.com/fabrikam/_apis/projects/eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", state: "wellFormed", }, { id: "6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", name: "Fabrikam-Fiber-Git", description: "Git projects", url: "https://dev.azure.com/fabrikam/_apis/projects/6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", state: "wellFormed", }, { id: "281f9a5b-af0d-49b4-a1df-fe6f5e5f84d0", name: "TestGit", url: "https://dev.azure.com/fabrikam/_apis/projects/281f9a5b-af0d-49b4-a1df-fe6f5e5f84d0", state: "wellFormed", }, ]); const params = { stateFilter: "wellFormed", top: undefined, skip: undefined, continuationToken: undefined, getDefaultTeamImageUrl: undefined, }; const result = await handler(params); expect(mockCoreApi.getProjects).toHaveBeenCalledWith("wellFormed", undefined, undefined, undefined, false); expect(result.content[0].text).toBe( JSON.stringify( [ { id: "eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", name: "Fabrikam-Fiber-TFVC", description: "Team Foundation Version Control projects.", url: "https://dev.azure.com/fabrikam/_apis/projects/eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", state: "wellFormed", }, { id: "6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", name: "Fabrikam-Fiber-Git", description: "Git projects", url: "https://dev.azure.com/fabrikam/_apis/projects/6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", state: "wellFormed", }, { id: "281f9a5b-af0d-49b4-a1df-fe6f5e5f84d0", name: "TestGit", url: "https://dev.azure.com/fabrikam/_apis/projects/281f9a5b-af0d-49b4-a1df-fe6f5e5f84d0", state: "wellFormed", }, ], null, 2 ) ); }); it("should handle API errors correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_list_projects"); if (!call) throw new Error("core_list_projects tool not registered"); const [, , , handler] = call; const testError = new Error("API connection failed"); (mockCoreApi.getProjects as jest.Mock).mockRejectedValue(testError); const params = { stateFilter: "wellFormed", top: undefined, skip: undefined, continuationToken: undefined, }; const result = await handler(params); expect(mockCoreApi.getProjects).toHaveBeenCalled(); expect(result.isError).toBe(true); expect(result.content[0].text).toContain("Error fetching projects: API connection failed"); }); it("should handle null API results correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_list_projects"); if (!call) throw new Error("core_list_projects tool not registered"); const [, , , handler] = call; (mockCoreApi.getProjects as jest.Mock).mockResolvedValue(null); const params = { stateFilter: "wellFormed", top: undefined, skip: undefined, continuationToken: undefined, }; const result = await handler(params); expect(mockCoreApi.getProjects).toHaveBeenCalled(); expect(result.isError).toBe(true); expect(result.content[0].text).toBe("No projects found"); }); it("should handle unknown error type correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_list_projects"); if (!call) throw new Error("core_list_projects tool not registered"); const [, , , handler] = call; (mockCoreApi.getProjects as jest.Mock).mockRejectedValue("string error"); const params = { stateFilter: "wellFormed", top: undefined, skip: undefined, continuationToken: undefined, }; const result = await handler(params); expect(mockCoreApi.getProjects).toHaveBeenCalled(); expect(result.isError).toBe(true); expect(result.content[0].text).toContain("Error fetching projects: Unknown error occurred"); }); it("should filter projects by name when projectNameFilter is provided", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_list_projects"); if (!call) throw new Error("core_list_projects tool not registered"); const [, , , handler] = call; (mockCoreApi.getProjects as jest.Mock).mockResolvedValue([ { id: "eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", name: "Fabrikam-Fiber-TFVC", description: "Team Foundation Version Control projects.", url: "https://dev.azure.com/fabrikam/_apis/projects/eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", state: "wellFormed", }, { id: "6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", name: "Fabrikam-Fiber-Git", description: "Git projects", url: "https://dev.azure.com/fabrikam/_apis/projects/6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", state: "wellFormed", }, { id: "281f9a5b-af0d-49b4-a1df-fe6f5e5f84d0", name: "TestGit", url: "https://dev.azure.com/fabrikam/_apis/projects/281f9a5b-af0d-49b4-a1df-fe6f5e5f84d0", state: "wellFormed", }, ]); const params = { stateFilter: "wellFormed", top: undefined, skip: undefined, continuationToken: undefined, projectNameFilter: "Git", }; const result = await handler(params); expect(mockCoreApi.getProjects).toHaveBeenCalledWith("wellFormed", undefined, undefined, undefined, false); const filteredProjects = JSON.parse(result.content[0].text); expect(filteredProjects).toHaveLength(2); expect(filteredProjects[0].name).toBe("Fabrikam-Fiber-Git"); expect(filteredProjects[1].name).toBe("TestGit"); }); it("should handle case-insensitive filtering", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_list_projects"); if (!call) throw new Error("core_list_projects tool not registered"); const [, , , handler] = call; (mockCoreApi.getProjects as jest.Mock).mockResolvedValue([ { id: "eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", name: "Fabrikam-Fiber-TFVC", description: "Team Foundation Version Control projects.", url: "https://dev.azure.com/fabrikam/_apis/projects/eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", state: "wellFormed", }, { id: "6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", name: "Fabrikam-Fiber-Git", description: "Git projects", url: "https://dev.azure.com/fabrikam/_apis/projects/6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", state: "wellFormed", }, ]); const params = { stateFilter: "wellFormed", top: undefined, skip: undefined, continuationToken: undefined, projectNameFilter: "fabrikam", }; const result = await handler(params); const filteredProjects = JSON.parse(result.content[0].text); expect(filteredProjects).toHaveLength(2); }); }); describe("list_project_teams tool", () => { it("should call getTeams API with the correct parameters and return the expected result", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_list_project_teams"); if (!call) throw new Error("core_list_project_teams tool not registered"); const [, , , handler] = call; (mockCoreApi.getTeams as jest.Mock).mockResolvedValue([ { id: "564e8204-a90b-4432-883b-d4363c6125ca", name: "Quality assurance", url: "https://dev.azure.com/fabrikam/_apis/projects/eb6e4656-77fc-42a1-9181-4c6d8e9da5d1/teams/564e8204-a90b-4432-883b-d4363c6125ca", description: "Testing staff", identityUrl: "https://vssps.dev.azure.com/fabrikam/_apis/Identities/564e8204-a90b-4432-883b-d4363c6125ca", }, { id: "66df9be7-3586-467b-9c5f-425b29afedfd", name: "Fabrikam-Fiber-TFVC Team", url: "https://dev.azure.com/fabrikam/_apis/projects/eb6e4656-77fc-42a1-9181-4c6d8e9da5d1/teams/66df9be7-3586-467b-9c5f-425b29afedfd", description: "The default project team.", identityUrl: "https://vssps.dev.azure.com/fabrikam/_apis/Identities/66df9be7-3586-467b-9c5f-425b29afedfd", }, ]); const params = { project: "eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", mine: undefined, top: undefined, skip: undefined, expandIdentity: undefined, }; const result = await handler(params); expect(mockCoreApi.getTeams).toHaveBeenCalledWith("eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", undefined, undefined, undefined, false); expect(result.content[0].text).toBe( JSON.stringify( [ { id: "564e8204-a90b-4432-883b-d4363c6125ca", name: "Quality assurance", url: "https://dev.azure.com/fabrikam/_apis/projects/eb6e4656-77fc-42a1-9181-4c6d8e9da5d1/teams/564e8204-a90b-4432-883b-d4363c6125ca", description: "Testing staff", identityUrl: "https://vssps.dev.azure.com/fabrikam/_apis/Identities/564e8204-a90b-4432-883b-d4363c6125ca", }, { id: "66df9be7-3586-467b-9c5f-425b29afedfd", name: "Fabrikam-Fiber-TFVC Team", url: "https://dev.azure.com/fabrikam/_apis/projects/eb6e4656-77fc-42a1-9181-4c6d8e9da5d1/teams/66df9be7-3586-467b-9c5f-425b29afedfd", description: "The default project team.", identityUrl: "https://vssps.dev.azure.com/fabrikam/_apis/Identities/66df9be7-3586-467b-9c5f-425b29afedfd", }, ], null, 2 ) ); }); it("should handle API errors correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_list_project_teams"); if (!call) throw new Error("core_list_project_teams tool not registered"); const [, , , handler] = call; const testError = new Error("Team not found"); (mockCoreApi.getTeams as jest.Mock).mockRejectedValue(testError); const params = { project: "eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", mine: undefined, top: undefined, skip: undefined, }; const result = await handler(params); expect(mockCoreApi.getTeams).toHaveBeenCalled(); expect(result.isError).toBe(true); expect(result.content[0].text).toContain("Error fetching project teams: Team not found"); }); it("should handle null API results correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_list_project_teams"); if (!call) throw new Error("core_list_project_teams tool not registered"); const [, , , handler] = call; (mockCoreApi.getTeams as jest.Mock).mockResolvedValue(null); const params = { project: "eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", mine: undefined, top: undefined, skip: undefined, }; const result = await handler(params); expect(mockCoreApi.getTeams).toHaveBeenCalled(); expect(result.isError).toBe(true); expect(result.content[0].text).toBe("No teams found"); }); it("should handle unknown error type correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_list_project_teams"); if (!call) throw new Error("core_list_project_teams tool not registered"); const [, , , handler] = call; (mockCoreApi.getTeams as jest.Mock).mockRejectedValue("string error"); const params = { project: "eb6e4656-77fc-42a1-9181-4c6d8e9da5d1", mine: undefined, top: undefined, skip: undefined, }; const result = await handler(params); expect(mockCoreApi.getTeams).toHaveBeenCalled(); expect(result.isError).toBe(true); expect(result.content[0].text).toContain("Error fetching project teams: Unknown error occurred"); }); }); describe("get_identity_ids tool", () => { beforeEach(() => { // Mock fetch globally for these tests global.fetch = jest.fn(); }); afterEach(() => { jest.restoreAllMocks(); }); it("should fetch identity IDs with correct parameters and return expected result", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_get_identity_ids"); if (!call) throw new Error("core_get_identity_ids tool not registered"); const [, , , handler] = call; // Mock token provider (tokenProvider as jest.Mock).mockResolvedValue("fake-token"); // Mock connection with serverUrl const mockConnectionWithUrl = { ...mockConnection, serverUrl: "https://dev.azure.com/test-org", }; (connectionProvider as jest.Mock).mockResolvedValue(mockConnectionWithUrl); // Mock fetch response const mockIdentities = { value: [ { id: "user1-id", providerDisplayName: "John Doe", descriptor: "aad.user1-descriptor", }, { id: "user2-id", providerDisplayName: "Jane Smith", descriptor: "aad.user2-descriptor", }, ], }; (global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: jest.fn().mockResolvedValue(mockIdentities), }); const params = { searchFilter: "john.doe@example.com" }; const result = await handler(params); expect(global.fetch).toHaveBeenCalledWith("https://vssps.dev.azure.com/test-org/_apis/identities?api-version=7.2-preview.1&searchFilter=General&filterValue=john.doe%40example.com", { headers: { "Authorization": "Bearer fake-token", "Content-Type": "application/json", "User-Agent": "Jest", }, }); const expectedResult = [ { id: "user1-id", displayName: "John Doe", descriptor: "aad.user1-descriptor", }, { id: "user2-id", displayName: "Jane Smith", descriptor: "aad.user2-descriptor", }, ]; expect(result.content[0].text).toBe(JSON.stringify(expectedResult, null, 2)); expect(result.isError).toBeUndefined(); }); it("should handle HTTP error responses correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_get_identity_ids"); if (!call) throw new Error("core_get_identity_ids tool not registered"); const [, , , handler] = call; (tokenProvider as jest.Mock).mockResolvedValue("fake-token"); const mockConnectionWithUrl = { ...mockConnection, serverUrl: "https://dev.azure.com/test-org", }; (connectionProvider as jest.Mock).mockResolvedValue(mockConnectionWithUrl); // Mock failed HTTP response (global.fetch as jest.Mock).mockResolvedValue({ ok: false, status: 404, text: jest.fn().mockResolvedValue("Not Found"), }); const params = { searchFilter: "nonexistent@example.com" }; const result = await handler(params); expect(result.isError).toBe(true); expect(result.content[0].text).toBe("Error fetching identities: HTTP 404: Not Found"); }); it("should handle empty results correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_get_identity_ids"); if (!call) throw new Error("core_get_identity_ids tool not registered"); const [, , , handler] = call; (tokenProvider as jest.Mock).mockResolvedValue("fake-token"); const mockConnectionWithUrl = { ...mockConnection, serverUrl: "https://dev.azure.com/test-org", }; (connectionProvider as jest.Mock).mockResolvedValue(mockConnectionWithUrl); // Mock empty response (global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: jest.fn().mockResolvedValue({ value: [] }), }); const params = { searchFilter: "nobody@example.com" }; const result = await handler(params); expect(result.isError).toBe(true); expect(result.content[0].text).toBe("No identities found"); }); it("should handle null response correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_get_identity_ids"); if (!call) throw new Error("core_get_identity_ids tool not registered"); const [, , , handler] = call; (tokenProvider as jest.Mock).mockResolvedValue("fake-token"); const mockConnectionWithUrl = { ...mockConnection, serverUrl: "https://dev.azure.com/test-org", }; (connectionProvider as jest.Mock).mockResolvedValue(mockConnectionWithUrl); // Mock null response (global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: jest.fn().mockResolvedValue(null), }); const params = { searchFilter: "test@example.com" }; const result = await handler(params); expect(result.isError).toBe(true); expect(result.content[0].text).toBe("No identities found"); }); it("should handle network errors correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_get_identity_ids"); if (!call) throw new Error("core_get_identity_ids tool not registered"); const [, , , handler] = call; (tokenProvider as jest.Mock).mockResolvedValue("fake-token"); const mockConnectionWithUrl = { ...mockConnection, serverUrl: "https://dev.azure.com/test-org", }; (connectionProvider as jest.Mock).mockResolvedValue(mockConnectionWithUrl); // Mock network error (global.fetch as jest.Mock).mockRejectedValue(new Error("Network error")); const params = { searchFilter: "test@example.com" }; const result = await handler(params); expect(result.isError).toBe(true); expect(result.content[0].text).toBe("Error fetching identities: Network error"); }); it("should handle unknown error types correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_get_identity_ids"); if (!call) throw new Error("core_get_identity_ids tool not registered"); const [, , , handler] = call; (tokenProvider as jest.Mock).mockResolvedValue("fake-token"); const mockConnectionWithUrl = { ...mockConnection, serverUrl: "https://dev.azure.com/test-org", }; (connectionProvider as jest.Mock).mockResolvedValue(mockConnectionWithUrl); // Mock unknown error type (not an Error instance) (global.fetch as jest.Mock).mockRejectedValue("string error"); const params = { searchFilter: "test@example.com" }; const result = await handler(params); expect(result.isError).toBe(true); expect(result.content[0].text).toBe("Error fetching identities: Unknown error occurred"); }); it("should handle token provider errors correctly", async () => { configureCoreTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "core_get_identity_ids"); if (!call) throw new Error("core_get_identity_ids tool not registered"); const [, , , handler] = call; // Mock token provider error (tokenProvider as jest.Mock).mockRejectedValue(new Error("Token acquisition failed")); const params = { searchFilter: "test@example.com" }; const result = await handler(params); expect(result.isError).toBe(true); expect(result.content[0].text).toBe("Error fetching identities: Token acquisition failed"); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/magemaclean/azure-devops-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server