Skip to main content
Glama

Azure DevOps MCP Server with PAT Authentication

by ennuiii
core.ts6.15 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { AccessToken } from "@azure/identity"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { WebApi } from "azure-devops-node-api"; import { z } from "zod"; import { apiVersion } from "../utils.js"; import type { ProjectInfo } from "azure-devops-node-api/interfaces/CoreInterfaces.js"; import { IdentityBase } from "azure-devops-node-api/interfaces/IdentitiesInterfaces.js"; const CORE_TOOLS = { list_project_teams: "core_list_project_teams", list_projects: "core_list_projects", get_identity_ids: "core_get_identity_ids", }; function filterProjectsByName(projects: ProjectInfo[], projectNameFilter: string): ProjectInfo[] { const lowerCaseFilter = projectNameFilter.toLowerCase(); return projects.filter((project) => project.name?.toLowerCase().includes(lowerCaseFilter)); } function configureCoreTools(server: McpServer, tokenProvider: () => Promise<AccessToken>, connectionProvider: () => Promise<WebApi>, userAgentProvider: () => string) { server.tool( CORE_TOOLS.list_project_teams, "Retrieve a list of teams for the specified Azure DevOps project.", { project: z.string().describe("The name or ID of the Azure DevOps project."), mine: z.boolean().optional().describe("If true, only return teams that the authenticated user is a member of."), top: z.number().optional().describe("The maximum number of teams to return. Defaults to 100."), skip: z.number().optional().describe("The number of teams to skip for pagination. Defaults to 0."), }, async ({ project, mine, top, skip }) => { try { const connection = await connectionProvider(); const coreApi = await connection.getCoreApi(); const teams = await coreApi.getTeams(project, mine, top, skip, false); if (!teams) { return { content: [{ type: "text", text: "No teams found" }], isError: true }; } return { content: [{ type: "text", text: JSON.stringify(teams, null, 2) }], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return { content: [{ type: "text", text: `Error fetching project teams: ${errorMessage}` }], isError: true, }; } } ); server.tool( CORE_TOOLS.list_projects, "Retrieve a list of projects in your Azure DevOps organization.", { stateFilter: z.enum(["all", "wellFormed", "createPending", "deleted"]).default("wellFormed").describe("Filter projects by their state. Defaults to 'wellFormed'."), top: z.number().optional().describe("The maximum number of projects to return. Defaults to 100."), skip: z.number().optional().describe("The number of projects to skip for pagination. Defaults to 0."), continuationToken: z.number().optional().describe("Continuation token for pagination. Used to fetch the next set of results if available."), projectNameFilter: z.string().optional().describe("Filter projects by name. Supports partial matches."), }, async ({ stateFilter, top, skip, continuationToken, projectNameFilter }) => { try { const connection = await connectionProvider(); const coreApi = await connection.getCoreApi(); const projects = await coreApi.getProjects(stateFilter, top, skip, continuationToken, false); if (!projects) { return { content: [{ type: "text", text: "No projects found" }], isError: true }; } const filteredProject = projectNameFilter ? filterProjectsByName(projects, projectNameFilter) : projects; return { content: [{ type: "text", text: JSON.stringify(filteredProject, null, 2) }], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return { content: [{ type: "text", text: `Error fetching projects: ${errorMessage}` }], isError: true, }; } } ); server.tool( CORE_TOOLS.get_identity_ids, "Retrieve Azure DevOps identity IDs for a provided search filter.", { searchFilter: z.string().describe("Search filter (unique namme, display name, email) to retrieve identity IDs for."), }, async ({ searchFilter }) => { try { const token = await tokenProvider(); const connection = await connectionProvider(); const orgName = connection.serverUrl.split("/")[3]; const baseUrl = `https://vssps.dev.azure.com/${orgName}/_apis/identities`; const params = new URLSearchParams({ "api-version": apiVersion, "searchFilter": "General", "filterValue": searchFilter, }); const response = await fetch(`${baseUrl}?${params}`, { headers: { "Authorization": `Bearer ${token.token}`, "Content-Type": "application/json", "User-Agent": userAgentProvider(), }, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP ${response.status}: ${errorText}`); } const identities = await response.json(); if (!identities || identities.value?.length === 0) { return { content: [{ type: "text", text: "No identities found" }], isError: true }; } const identitiesTrimmed = identities.value?.map((identity: IdentityBase) => { return { id: identity.id, displayName: identity.providerDisplayName, descriptor: identity.descriptor, }; }); return { content: [{ type: "text", text: JSON.stringify(identitiesTrimmed, null, 2) }], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return { content: [{ type: "text", text: `Error fetching identities: ${errorMessage}` }], isError: true, }; } } ); } export { CORE_TOOLS, configureCoreTools };

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/ennuiii/DevOpsMcpPAT'

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