Skip to main content
Glama
server.py7.09 kB
"""Azure DevOps MCP Server implementation.""" import asyncio import os from typing import Any from dotenv import load_dotenv from mcp.server.models import InitializationOptions from mcp.server import NotificationOptions, Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent from .ado_client import ADOClient # Load environment variables from .env file load_dotenv() # Create MCP server instance server = Server("ado-mcp") # Global ADO client instance ado_client: ADOClient = None @server.list_tools() async def handle_list_tools() -> list[Tool]: """List available tools for Azure DevOps integration.""" return [ Tool( name="get_work_item", description="Get detailed information about an Azure DevOps work item including description, steps to reproduce, comments, status, and other metadata. Works with all work item types (Bugs, Tasks, User Stories, etc.)", inputSchema={ "type": "object", "properties": { "work_item_id": { "type": "integer", "description": "The ID of the work item to retrieve", } }, "required": ["work_item_id"], }, ), Tool( name="update_work_item_status", description="Update the state/status of an Azure DevOps work item. Common states include: New, Active, Resolved, Closed, Removed. Note: Available states depend on your work item type and process template.", inputSchema={ "type": "object", "properties": { "work_item_id": { "type": "integer", "description": "The ID of the work item to update", }, "new_state": { "type": "string", "description": "The new state to set (e.g., 'Active', 'Resolved', 'Closed'). Must be a valid state for the work item type.", }, }, "required": ["work_item_id", "new_state"], }, ), ] @server.call_tool() async def handle_call_tool(name: str, arguments: dict) -> list[TextContent]: """Handle tool execution requests.""" global ado_client # Initialize ADO client on first use if ado_client is None: try: ado_client = ADOClient() except ValueError as e: return [ TextContent( type="text", text=f"Error: {str(e)}\n\nPlease ensure ADO_ORGANIZATION, ADO_PROJECT, and ADO_PAT environment variables are set.", ) ] except Exception as e: return [ TextContent( type="text", text=f"Failed to initialize Azure DevOps client: {str(e)}", ) ] try: if name == "get_work_item": work_item_id = arguments.get("work_item_id") if not work_item_id: return [ TextContent( type="text", text="Error: work_item_id is required", ) ] # Get work item details work_item = ado_client.get_work_item(work_item_id) # Format the response response_lines = [ f"Work Item #{work_item['id']}: {work_item['title']}", f"Type: {work_item['type']}", f"State: {work_item['state']}", f"Assigned To: {work_item['assigned_to']}", f"Created: {work_item['created_date']} by {work_item['created_by']}", f"Last Changed: {work_item['changed_date']}", f"Area Path: {work_item['area_path']}", f"Iteration: {work_item['iteration_path']}", ] if work_item.get("tags"): response_lines.append(f"Tags: {work_item['tags']}") response_lines.append("\n--- Description ---") response_lines.append(work_item.get("description", "No description")) if work_item.get("steps_to_reproduce"): response_lines.append("\n--- Steps to Reproduce ---") response_lines.append(work_item["steps_to_reproduce"]) # Add comments if work_item["comment_count"] > 0: response_lines.append(f"\n--- Comments ({work_item['comment_count']}) ---") for i, comment in enumerate(work_item["comments"], 1): response_lines.append( f"\nComment {i} by {comment['created_by']} on {comment['created_date']}:" ) response_lines.append(comment["text"]) else: response_lines.append("\n--- Comments ---") response_lines.append("No comments") return [TextContent(type="text", text="\n".join(response_lines))] elif name == "update_work_item_status": work_item_id = arguments.get("work_item_id") new_state = arguments.get("new_state") if not work_item_id or not new_state: return [ TextContent( type="text", text="Error: work_item_id and new_state are required", ) ] # Update the work item state updated_work_item = ado_client.update_work_item_state(work_item_id, new_state) response = f"Successfully updated work item #{work_item_id}\n" response += f"Title: {updated_work_item['title']}\n" response += f"Previous State -> New State: {updated_work_item['state']}\n" response += f"Type: {updated_work_item['type']}\n" response += f"Assigned To: {updated_work_item['assigned_to']}" return [TextContent(type="text", text=response)] else: return [ TextContent( type="text", text=f"Unknown tool: {name}", ) ] except Exception as e: return [ TextContent( type="text", text=f"Error executing {name}: {str(e)}", ) ] async def main(): """Run the MCP server.""" async with stdio_server() as (read_stream, write_stream): await server.run( read_stream, write_stream, InitializationOptions( server_name="ado-mcp", server_version="0.1.0", capabilities=server.get_capabilities( notification_options=NotificationOptions(), experimental_capabilities={}, ), ), ) def run(): """Entry point for the server.""" asyncio.run(main()) if __name__ == "__main__": run()

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/Akshay-Quorum2145/ADO-MCP'

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