Skip to main content
Glama

Azure DevOps MCP Server

searchRepositoryCode.ts8.17 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import * as azdev from 'azure-devops-node-api'; import { z } from 'zod'; import { McpTool } from '../types'; interface CodeSearchMatch { charOffset?: number; length?: number; line?: string; lineNumber?: number; text?: string; } interface CodeSearchMatches { content?: CodeSearchMatch[]; fileName?: CodeSearchMatch[]; } interface CodeSearchResult { fileName?: string; path?: string; matches?: CodeSearchMatches; repository?: { name?: string; id?: string; webUrl?: string; }; project?: { name?: string; id?: string; }; versions?: Array<{ branchName?: string; changeId?: string; }>; } interface CodeSearchResponse { count: number; results: CodeSearchResult[]; } interface FormattedSearchResult { fileName: string; path: string; repository: string | undefined; project: string | undefined; branch: string; matches: number; webUrl: string | null; snippets: Array<{ line: string | undefined; content: string | undefined; lineNumber: number | undefined; }>; } /** * Tool for searching code in Azure DevOps repositories */ export class SearchRepositoryCodeTool implements McpTool { public name = 'search_repository_code'; public description = 'Search for code in repositories'; /** * Register the tool with the MCP server * * @param server The MCP server * @param connection The Azure DevOps connection */ public register(server: McpServer, connection: azdev.WebApi | null): void { server.tool( this.name, { searchText: z.string().describe('The text to search for in the code'), project: z .string() .optional() .describe('The project containing the repositories to search'), repository: z .string() .optional() .describe('The specific repository to search in'), path: z .string() .optional() .describe('The path within the repository to search (e.g., "/src")'), branch: z .string() .optional() .describe('The branch to search in (defaults to the default branch)'), fileExtension: z .string() .optional() .describe('Filter by file extension (e.g., ".ts", ".cs")'), maxResults: z .number() .optional() .describe('Maximum number of results to return (default: 20)'), }, async (args, _extras) => { try { if (!connection) { throw new Error('No connection to Azure DevOps'); } // Get the organization URL from the connection const orgUrl = connection.serverUrl; if (!orgUrl) { throw new Error('Unable to determine organization URL'); } // Extract organization name from URL const orgMatch = orgUrl.match(/https:\/\/dev\.azure\.com\/([^/]+)/i); const organization = orgMatch ? orgMatch[1] : null; if (!organization) { throw new Error('Unable to determine organization name from URL'); } // Prepare the search request const searchRequest = { searchText: args.searchText, $skip: 0, $top: args.maxResults || 20, filters: { Project: args.project ? [args.project] : undefined, Repository: args.repository ? [args.repository] : undefined, Path: args.path ? [args.path] : undefined, Branch: args.branch ? [args.branch] : undefined, Extension: args.fileExtension ? [ args.fileExtension.startsWith('.') ? args.fileExtension : `.${args.fileExtension}`, ] : undefined, }, includeFacets: false, }; // Get the personal access token from environment variables const pat = process.env.AZURE_DEVOPS_PAT; if (!pat) { throw new Error( 'Unable to get authentication token. Please set AZURE_DEVOPS_PAT environment variable.', ); } // Make the REST API call const projectScope = args.project || ''; const searchUrl = `https://almsearch.dev.azure.com/${organization}/${projectScope}/_apis/search/codesearchresults?api-version=5.1-preview.1`; const response = await fetch(searchUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Basic ${Buffer.from(`:${pat}`).toString('base64')}`, }, body: JSON.stringify(searchRequest), }); if (!response.ok) { const errorText = await response.text(); throw new Error( `Search API request failed: ${response.status} ${response.statusText} - ${errorText}`, ); } const searchResults = (await response.json()) as CodeSearchResponse; if ( !searchResults || !searchResults.results || !searchResults.count ) { return { content: [ { type: 'text', text: 'No results found.', }, ], }; } // Format the results const formattedResults: FormattedSearchResult[] = searchResults.results.map((result) => ({ fileName: result.fileName || '', path: result.path || '', repository: result.repository?.name, project: result.project?.name, branch: result.versions?.[0]?.branchName || 'unknown', matches: result.matches?.content?.length || 0, webUrl: result.repository?.webUrl ? `${result.repository.webUrl}/blob/${ result.versions?.[0]?.branchName || 'master' }${result.path}` : null, snippets: (result.matches?.content || []).map((match) => ({ line: match.line, content: match.text, lineNumber: match.lineNumber, })), })); // Build the response text let responseText = `# Code Search Results\n\n`; responseText += `Found ${searchResults.count} results for "${args.searchText}"`; if (args.project) responseText += ` in project "${args.project}"`; if (args.repository) responseText += ` in repository "${args.repository}"`; responseText += '\n\n'; // Add each result with its details formattedResults.forEach((result, index) => { responseText += `## Result ${index + 1}\n`; responseText += `**File**: ${result.fileName}\n`; responseText += `**Path**: ${result.path}\n`; responseText += `**Repository**: ${result.repository}\n`; responseText += `**Project**: ${result.project}\n`; responseText += `**Branch**: ${result.branch}\n`; responseText += `**URL**: ${result.webUrl}\n\n`; if (result.snippets && result.snippets.length > 0) { responseText += '### Matches\n'; result.snippets.forEach((snippet) => { responseText += `\`\`\`\nLine ${snippet.lineNumber}: ${snippet.content}\n\`\`\`\n`; }); } responseText += '\n'; }); return { content: [ { type: 'text', text: responseText, }, ], }; } catch (error: any) { console.error('Error searching code:', error); return { content: [ { type: 'text', text: `Error searching code: ${error.message}`, }, ], }; } }, ); } }

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/kevinmeyvaert/azure-devops-mcp'

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