Skip to main content
Glama

Azure DevOps Wiki MCP Server

by uright
server.ts10.6 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from '@modelcontextprotocol/sdk/types.js'; import { AzureDevOpsWikiClient } from './azure-client.js'; import { WikiSearchRequestSchema, WikiPageTreeRequestSchema, WikiGetPageRequestSchema, WikiUpdatePageRequestSchema, WikiListRequestSchema, EnvironmentConfigSchema, ServerConfig } from './types.js'; export class AzureDevOpsWikiServer { private client: AzureDevOpsWikiClient | null = null; private config: ServerConfig; constructor(private server: Server) { this.config = this.loadConfiguration(); } async initialize(): Promise<void> { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'search_wiki', description: 'Search across wiki content using Azure DevOps Search API', inputSchema: { type: 'object', properties: { organization: { type: 'string', description: 'Azure DevOps organization name' }, project: { type: 'string', description: 'Project name' }, searchText: { type: 'string', description: 'Search query string' }, wikiId: { type: 'string', description: 'Optional specific wiki identifier' } }, required: ['searchText'] } }, { name: 'wiki_get_page_tree', description: 'Retrieve hierarchical page structure from wiki', inputSchema: { type: 'object', properties: { organization: { type: 'string', description: 'Azure DevOps organization name' }, project: { type: 'string', description: 'Project name' }, wikiId: { type: 'string', description: 'Wiki identifier' }, depth: { type: 'number', description: 'Optional maximum depth to retrieve' } }, required: ['wikiId'] } }, { name: 'wiki_get_page', description: 'Get content of a specific wiki page', inputSchema: { type: 'object', properties: { organization: { type: 'string', description: 'Azure DevOps organization name' }, project: { type: 'string', description: 'Project name' }, wikiId: { type: 'string', description: 'Wiki identifier' }, path: { type: 'string', description: 'Page path or page ID' } }, required: ['wikiId', 'path'] } }, { name: 'wiki_update_page', description: 'Update content of an existing wiki page or create a new page if it does not exist', inputSchema: { type: 'object', properties: { organization: { type: 'string', description: 'Azure DevOps organization name' }, project: { type: 'string', description: 'Project name' }, wikiId: { type: 'string', description: 'Wiki identifier' }, path: { type: 'string', description: 'Page path or page ID' }, content: { type: 'string', description: 'New page content (Markdown)' } }, required: ['wikiId', 'path', 'content'] } }, { name: 'list_wiki', description: 'List all wikis in a project', inputSchema: { type: 'object', properties: { organization: { type: 'string', description: 'Azure DevOps organization name' }, project: { type: 'string', description: 'Project name' } }, required: [] } } ] as Tool[] }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'search_wiki': return await this.handleSearchWiki(args); case 'wiki_get_page_tree': return await this.handleGetPageTree(args); case 'wiki_get_page': return await this.handleGetPage(args); case 'wiki_update_page': return await this.handleUpdatePage(args); case 'list_wiki': return await this.handleListWiki(args); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } }); } private loadConfiguration(): ServerConfig { try { const envConfig = EnvironmentConfigSchema.parse(process.env); return { azureDevOpsUrl: envConfig.AZURE_DEVOPS_URL, defaultProject: envConfig.AZURE_DEVOPS_PROJECT, defaultOrganization: envConfig.AZURE_DEVOPS_ORGANIZATION, personalAccessToken: envConfig.AZURE_DEVOPS_PAT }; } catch (error) { console.error('Environment configuration validation failed:', error); return {}; } } private async getClient(organization: string, project?: string): Promise<AzureDevOpsWikiClient> { if (!this.client) { const clientConfig = { organization, project: project || this.config.defaultProject || '', personalAccessToken: this.config.personalAccessToken, azureDevOpsUrl: this.config.azureDevOpsUrl }; this.client = new AzureDevOpsWikiClient(clientConfig); await this.client.initialize(); } return this.client; } private async handleSearchWiki(args: any) { const request = WikiSearchRequestSchema.parse(args); const organization = request.organization || this.config.defaultOrganization; const project = request.project || this.config.defaultProject; if (!organization) { throw new Error('Organization is required either as parameter or in server configuration'); } if (!project) { throw new Error('Project is required either as parameter or in server configuration'); } const client = await this.getClient(organization, project); const results = await client.searchWiki(request); return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] }; } private async handleGetPageTree(args: any) { const request = WikiPageTreeRequestSchema.parse(args); const organization = request.organization || this.config.defaultOrganization; const project = request.project || this.config.defaultProject; if (!organization) { throw new Error('Organization is required either as parameter or in server configuration'); } if (!project) { throw new Error('Project is required either as parameter or in server configuration'); } const client = await this.getClient(organization, project); const tree = await client.getPageTree(request); return { content: [{ type: 'text', text: JSON.stringify(tree, null, 2) }] }; } private async handleGetPage(args: any) { const request = WikiGetPageRequestSchema.parse(args); const organization = request.organization || this.config.defaultOrganization; const project = request.project || this.config.defaultProject; if (!organization) { throw new Error('Organization is required either as parameter or in server configuration'); } if (!project) { throw new Error('Project is required either as parameter or in server configuration'); } const client = await this.getClient(organization, project); const page = await client.getPage(request); return { content: [{ type: 'text', text: JSON.stringify(page, null, 2) }] }; } private async handleUpdatePage(args: any) { const request = WikiUpdatePageRequestSchema.parse(args); const organization = request.organization || this.config.defaultOrganization; const project = request.project || this.config.defaultProject; if (!organization) { throw new Error('Organization is required either as parameter or in server configuration'); } if (!project) { throw new Error('Project is required either as parameter or in server configuration'); } const client = await this.getClient(organization, project); const result = await client.updatePage(request); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } private async handleListWiki(args: any) { const request = WikiListRequestSchema.parse(args); const organization = request.organization || this.config.defaultOrganization; const project = request.project || this.config.defaultProject; if (!organization) { throw new Error('Organization is required either as parameter or in server configuration'); } if (!project) { throw new Error('Project is required either as parameter or in server configuration'); } const client = await this.getClient(organization, project); const wikis = await client.listWikis(request); return { content: [{ type: 'text', text: JSON.stringify(wikis, null, 2) }] }; } }

Implementation Reference

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

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