#
tokens: 19262/50000 32/32 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .github
│   └── workflows
│       └── ci.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── api
│   │   ├── connection.ts
│   │   └── wiki.ts
│   ├── config
│   │   └── environment.ts
│   ├── errors.ts
│   ├── index.ts
│   └── tools
│       ├── board
│       │   ├── get.ts
│       │   └── index.ts
│       ├── pipeline
│       │   ├── get.ts
│       │   ├── index.ts
│       │   └── trigger.ts
│       ├── project
│       │   ├── index.ts
│       │   └── list.ts
│       ├── pull-request
│       │   ├── create.ts
│       │   ├── get.ts
│       │   ├── index.ts
│       │   └── update.ts
│       ├── wiki
│       │   ├── create.ts
│       │   ├── get.ts
│       │   ├── index.ts
│       │   └── update.ts
│       └── work-item
│           ├── create.ts
│           ├── get.ts
│           ├── index.ts
│           ├── list.ts
│           └── update.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Dependencies
 2 | node_modules/
 3 | npm-debug.log*
 4 | yarn-debug.log*
 5 | yarn-error.log*
 6 | 
 7 | # Build output
 8 | build/
 9 | dist/
10 | *.tsbuildinfo
11 | 
12 | # IDE and editor files
13 | .idea/
14 | .vscode/
15 | *.swp
16 | *.swo
17 | *~
18 | 
19 | # Environment variables
20 | .env
21 | .env.local
22 | .env.*.local
23 | 
24 | # Operating System
25 | .DS_Store
26 | Thumbs.db
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Azure DevOps MCP Server for Cline
  2 | [![smithery badge](https://smithery.ai/badge/@stefanskiasan/azure-devops-mcp-server)](https://smithery.ai/server/@stefanskiasan/azure-devops-mcp-server)
  3 | 
  4 | This Model Context Protocol (MCP) server provides integration with Azure DevOps, allowing Cline to interact with Azure DevOps services.
  5 | 
  6 | ## Prerequisites
  7 | 
  8 | - Node.js (v20 LTS or higher)
  9 | - npm (comes with Node.js)
 10 | - A Cline installation
 11 | - Azure DevOps account with access tokens
 12 | 
 13 | ## Installation
 14 | 
 15 | ### Installing via Smithery
 16 | 
 17 | To install Azure DevOps Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@stefanskiasan/azure-devops-mcp-server):
 18 | 
 19 | ```bash
 20 | npx -y @smithery/cli install @stefanskiasan/azure-devops-mcp-server --client claude
 21 | ```
 22 | 
 23 | ### Manual Installation
 24 | 1. Clone this repository:
 25 | ```bash
 26 | git clone https://github.com/stefanskiasan/azure-devops-mcp-server.git
 27 | cd azure-devops-mcp-server
 28 | ```
 29 | 
 30 | 2. Install dependencies:
 31 | ```bash
 32 | npm install
 33 | ```
 34 | 
 35 | 3. Build the server:
 36 | ```bash
 37 | npm run build
 38 | ```
 39 | 
 40 | Note: The build output (`build/` directory) is not included in version control. You must run the build command after cloning the repository.
 41 | 
 42 | ## Configuration
 43 | 
 44 | ### 1. Get Azure DevOps Personal Access Token (PAT)
 45 | 
 46 | 1. Go to Azure DevOps and sign in
 47 | 2. Click on your profile picture in the top right
 48 | 3. Select "Security"
 49 | 4. Click "New Token"
 50 | 5. Give your token a name and select the required scopes:
 51 |    - `Code (read, write)` - For Pull Request operations
 52 |    - `Work Items (read, write)` - For Work Item management
 53 |    - `Build (read, execute)` - For Pipeline operations
 54 |    - `Wiki (read, write)` - For Wiki operations
 55 |    - `Project and Team (read)` - For Project and Board information
 56 | 6. Copy the generated token
 57 | 
 58 | ### 2. Configure Cline MCP Settings
 59 | 
 60 | Add the server configuration to your Cline MCP settings file:
 61 | 
 62 | - For VSCode extension: `%APPDATA%/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json`
 63 | - For Claude desktop app: `%LOCALAPPDATA%/Claude/claude_desktop_config.json`
 64 | 
 65 | Add the following configuration to the `mcpServers` object:
 66 | 
 67 | ```json
 68 | {
 69 |   "mcpServers": {
 70 |     "azure-devops": {
 71 |       "command": "node",
 72 |       "args": ["/absolute/path/to/azure-devops-server/build/index.js"],
 73 |       "env": {
 74 |         "AZURE_DEVOPS_ORG": "your-organization",
 75 |         "AZURE_DEVOPS_PAT": "your-personal-access-token",
 76 |         "AZURE_DEVOPS_PROJECT": "your-project-name"
 77 |       },
 78 |       "disabled": false,
 79 |       "autoApprove": []
 80 |     }
 81 |   }
 82 | }
 83 | ```
 84 | 
 85 | Replace the following values:
 86 | - `/absolute/path/to/azure-devops-server`: The absolute path to where you cloned this repository
 87 | - `your-organization`: Your Azure DevOps organization name
 88 | - `your-project-name`: Your Azure DevOps project name
 89 | - `your-personal-access-token`: The PAT you generated in step 1
 90 | 
 91 | ## Available Tools
 92 | 
 93 | ### Work Items
 94 | - `get_work_item`: Get a work item by ID
 95 | - `list_work_items`: Query work items using WIQL
 96 | - `create_work_item`: Create a new work item (Bug, Task, User Story)
 97 | - `update_work_item`: Update an existing work item
 98 | 
 99 | ### Boards
100 | - `get_boards`: Get available boards in the project
101 | 
102 | ### Pipelines
103 | - `list_pipelines`: List all pipelines in the project
104 | - `trigger_pipeline`: Execute a pipeline
105 | 
106 | ### Pull Requests
107 | - `list_pull_requests`: List pull requests
108 | - `create_pull_request`: Create a new pull request
109 | - `update_pull_request`: Update a pull request
110 | - `get_pull_request`: Get pull request details
111 | 
112 | ### Wiki
113 | - `get_wikis`: List all wikis in the project
114 | - `get_wiki_page`: Get a wiki page
115 | - `create_wiki`: Create a new wiki
116 | - `update_wiki_page`: Create or update a wiki page
117 | 
118 | ### Projects
119 | - `list_projects`: List all projects in the Azure DevOps organization
120 | 
121 | ## Verification
122 | 
123 | 1. Restart Cline (or VSCode) after adding the configuration
124 | 2. The Azure DevOps MCP server should now be listed in Cline's capabilities
125 | 3. You can verify the installation using the MCP Inspector:
126 | ```bash
127 | npm run inspector
128 | ```
129 | 
130 | ## Troubleshooting
131 | 
132 | 1. If the server isn't connecting:
133 |    - Check that the path in your MCP settings is correct
134 |    - Verify your Azure DevOps credentials
135 |    - Check the Cline logs for any error messages
136 | 
137 | 2. If you get authentication errors:
138 |    - Verify your PAT hasn't expired
139 |    - Ensure the PAT has all necessary scopes
140 |    - Double-check the organization and project names
141 | 
142 | 3. For other issues:
143 |    - Run the inspector tool to verify the server is working correctly
144 |    - Check the server logs for any error messages
145 | 
146 | ## Development
147 | 
148 | To modify or extend the server:
149 | 
150 | 1. Make your changes in the `src` directory
151 | 2. Run `npm run watch` for development
152 | 3. Build with `npm run build` when ready
153 | 4. Test using the inspector: `npm run inspector`
154 | 
155 | ## License
156 | 
157 | MIT License - See [LICENSE](LICENSE) for details
158 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: CI
 2 | 
 3 | on:
 4 |   pull_request:
 5 |     branches: [ master ]
 6 | 
 7 | jobs:
 8 |   build:
 9 |     runs-on: ubuntu-latest
10 | 
11 |     steps:
12 |     - uses: actions/checkout@v4
13 |     
14 |     - name: Setup Node.js
15 |       uses: actions/setup-node@v4
16 |       with:
17 |         node-version: '20'
18 |         cache: 'npm'
19 |     
20 |     - name: Install dependencies
21 |       run: npm ci
22 |     
23 |     - name: Build project
24 |       run: npm run build
```

--------------------------------------------------------------------------------
/src/tools/project/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { listProjects } from './list.js';
 2 | import { AzureDevOpsConfig } from '../../config/environment.js';
 3 | 
 4 | const definitions = [
 5 |   {
 6 |     name: 'list_projects',
 7 |     description: 'List all projects in the Azure DevOps organization',
 8 |     inputSchema: {
 9 |       type: 'object',
10 |       properties: {},
11 |       required: [],
12 |     },
13 |   },
14 | ];
15 | 
16 | export const projectTools = {
17 |   initialize: (config: AzureDevOpsConfig) => ({
18 |     listProjects: (args?: Record<string, unknown>) => listProjects(args, config),
19 |     definitions,
20 |   }),
21 |   definitions,
22 | };
```

--------------------------------------------------------------------------------
/src/tools/board/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { getBoards } from './get.js';
 2 | import { AzureDevOpsConfig } from '../../config/environment.js';
 3 | 
 4 | const definitions = [
 5 |   {
 6 |     name: 'get_boards',
 7 |     description: 'List available boards in the project',
 8 |     inputSchema: {
 9 |       type: 'object',
10 |       properties: {
11 |         team: {
12 |           type: 'string',
13 |           description: 'Team name (optional)',
14 |         },
15 |       },
16 |     },
17 |   },
18 | ];
19 | 
20 | export const boardTools = {
21 |   initialize: (config: AzureDevOpsConfig) => ({
22 |     getBoards: (args: any) => getBoards(args, config),
23 |     definitions,
24 |   }),
25 |   definitions,
26 | };
```

--------------------------------------------------------------------------------
/src/tools/board/get.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { AzureDevOpsConnection } from '../../api/connection.js';
 2 | import { AzureDevOpsConfig } from '../../config/environment.js';
 3 | 
 4 | interface GetBoardsArgs {
 5 |   team?: string;
 6 | }
 7 | 
 8 | export async function getBoards(args: GetBoardsArgs, config: AzureDevOpsConfig) {
 9 |   AzureDevOpsConnection.initialize(config);
10 |   const connection = AzureDevOpsConnection.getInstance();
11 |   const workApi = await connection.getWorkApi();
12 |   
13 |   const teamContext = {
14 |     project: config.project,
15 |     team: args.team || `${config.project} Team`,
16 |   };
17 | 
18 |   const boards = await workApi.getBoards(teamContext);
19 | 
20 |   return {
21 |     content: [
22 |       {
23 |         type: 'text',
24 |         text: JSON.stringify(boards, null, 2),
25 |       },
26 |     ],
27 |   };
28 | }
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "azure-devops-server",
 3 |   "license": "MIT",
 4 |   "version": "0.1.0",
 5 |   "description": "A Model Context Protocol server",
 6 |   "private": true,
 7 |   "type": "module",
 8 |   "bin": {
 9 |     "azure-devops-server": "./build/index.js"
10 |   },
11 |   "files": [
12 |     "build"
13 |   ],
14 |   "scripts": {
15 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
16 |     "prepare": "npm run build",
17 |     "watch": "tsc --watch",
18 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js"
19 |   },
20 |   "dependencies": {
21 |     "@modelcontextprotocol/sdk": "0.6.0",
22 |     "@types/node-fetch": "^2.6.12",
23 |     "azure-devops-node-api": "^14.1.0",
24 |     "node-fetch": "^2.7.0"
25 |   },
26 |   "devDependencies": {
27 |     "@types/node": "^20.11.24",
28 |     "typescript": "^5.3.3"
29 |   }
30 | }
31 | 
```

--------------------------------------------------------------------------------
/src/tools/work-item/list.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | import { Wiql } from 'azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js';
 5 | 
 6 | export async function listWorkItems(args: Wiql, config: AzureDevOpsConfig) {
 7 |   if (!args.query) {
 8 |     throw new McpError(ErrorCode.InvalidParams, 'Invalid WIQL query');
 9 |   }
10 | 
11 |   AzureDevOpsConnection.initialize(config);
12 |   const connection = AzureDevOpsConnection.getInstance();
13 |   const workItemTrackingApi = await connection.getWorkItemTrackingApi();
14 |   
15 |   const queryResult = await workItemTrackingApi.queryByWiql(
16 |     args,
17 |     { project: config.project }
18 |   );
19 | 
20 |   return {
21 |     content: [
22 |       {
23 |         type: 'text',
24 |         text: JSON.stringify(queryResult, null, 2),
25 |       },
26 |     ],
27 |   };
28 | }
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
 2 | # Start with a Node.js image with npm pre-installed
 3 | FROM node:16-alpine AS builder
 4 | 
 5 | # Create and set the working directory
 6 | WORKDIR /app
 7 | 
 8 | # Copy all necessary files
 9 | COPY . .
10 | 
11 | # Install the dependencies
12 | RUN npm install
13 | 
14 | # Build the server
15 | RUN npm run build
16 | 
17 | # Create a new image for the actual server
18 | FROM node:16-alpine
19 | 
20 | # Set the working directory
21 | WORKDIR /app
22 | 
23 | # Copy only the necessary files from the builder image
24 | COPY --from=builder /app/build /app/build
25 | COPY --from=builder /app/node_modules /app/node_modules
26 | COPY --from=builder /app/package.json /app/package.json
27 | 
28 | # Define environment variables for Azure DevOps
29 | ENV AZURE_DEVOPS_ORG=your-organization
30 | ENV AZURE_DEVOPS_PROJECT=your-project
31 | ENV AZURE_DEVOPS_TOKEN=your-personal-access-token
32 | 
33 | # Start the server
34 | ENTRYPOINT ["node", "build/index.js"]
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - azureDevOpsOrg
10 |       - azureDevOpsProject
11 |       - azureDevOpsPat
12 |     properties:
13 |       azureDevOpsOrg:
14 |         type: string
15 |         description: Your Azure DevOps organization name.
16 |       azureDevOpsProject:
17 |         type: string
18 |         description: Your Azure DevOps project name.
19 |       azureDevOpsPat:
20 |         type: string
21 |         description: Your Azure DevOps Personal Access Token.
22 |   commandFunction:
23 |     # A function that produces the CLI command to start the MCP on stdio.
24 |     |-
25 |     (config) => ({ command: 'node', args: ['build/index.js'], env: { AZURE_DEVOPS_ORG: config.azureDevOpsOrg, AZURE_DEVOPS_PROJECT: config.azureDevOpsProject, AZURE_DEVOPS_PAT: config.azureDevOpsPat } })
```

--------------------------------------------------------------------------------
/src/tools/project/list.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | 
 5 | export async function listProjects(args: Record<string, unknown> | undefined, config: AzureDevOpsConfig) {
 6 |   AzureDevOpsConnection.initialize(config);
 7 |   const connection = AzureDevOpsConnection.getInstance();
 8 |   const coreApi = await connection.getCoreApi();
 9 | 
10 |   try {
11 |     const projects = await coreApi.getProjects();
12 | 
13 |     return {
14 |       content: [
15 |         {
16 |           type: 'text',
17 |           text: JSON.stringify(projects, null, 2),
18 |         },
19 |       ],
20 |     };
21 |   } catch (error: unknown) {
22 |     if (error instanceof McpError) throw error;
23 |     const errorMessage = error instanceof Error ? error.message : 'Unknown error';
24 |     throw new McpError(
25 |       ErrorCode.InternalError,
26 |       `Failed to list projects: ${errorMessage}`
27 |     );
28 |   }
29 | }
```

--------------------------------------------------------------------------------
/src/tools/work-item/create.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | import { JsonPatchOperation } from 'azure-devops-node-api/interfaces/common/VSSInterfaces.js';
 5 | 
 6 | export async function createWorkItem(args: { type: string; document: JsonPatchOperation[] }, config: AzureDevOpsConfig) {
 7 |   if (!args.type || !args.document || !args.document.length) {
 8 |     throw new McpError(ErrorCode.InvalidParams, 'Work item type and patch document are required');
 9 |   }
10 | 
11 |   AzureDevOpsConnection.initialize(config);
12 |   const connection = AzureDevOpsConnection.getInstance();
13 |   const workItemTrackingApi = await connection.getWorkItemTrackingApi();
14 | 
15 |   const workItem = await workItemTrackingApi.createWorkItem(
16 |     undefined,
17 |     args.document,
18 |     config.project,
19 |     args.type
20 |   );
21 | 
22 |   return {
23 |     content: [
24 |       {
25 |         type: 'text',
26 |         text: JSON.stringify(workItem, null, 2),
27 |       },
28 |     ],
29 |   };
30 | }
```

--------------------------------------------------------------------------------
/src/tools/pipeline/get.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | 
 5 | interface GetPipelinesArgs {
 6 |   folder?: string;
 7 |   name?: string;
 8 | }
 9 | 
10 | export async function getPipelines(args: GetPipelinesArgs, config: AzureDevOpsConfig) {
11 |   AzureDevOpsConnection.initialize(config);
12 |   const connection = AzureDevOpsConnection.getInstance();
13 |   const pipelineApi = await connection.getBuildApi();
14 | 
15 |   try {
16 |     const pipelines = await pipelineApi.getDefinitions(
17 |       config.project,
18 |       args.name,
19 |       args.folder
20 |     );
21 | 
22 |     return {
23 |       content: [
24 |         {
25 |           type: 'text',
26 |           text: JSON.stringify(pipelines, null, 2),
27 |         },
28 |       ],
29 |     };
30 |   } catch (error: unknown) {
31 |     if (error instanceof McpError) throw error;
32 |     const errorMessage = error instanceof Error ? error.message : 'Unknown error';
33 |     throw new McpError(
34 |       ErrorCode.InternalError,
35 |       `Failed to get pipelines: ${errorMessage}`
36 |     );
37 |   }
38 | }
```

--------------------------------------------------------------------------------
/src/tools/work-item/get.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | import { WorkItemBatchGetRequest, WorkItemExpand, WorkItemErrorPolicy } from 'azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js';
 5 | 
 6 | export async function getWorkItem(args: WorkItemBatchGetRequest, config: AzureDevOpsConfig) {
 7 |   if (!args.ids || !args.ids.length) {
 8 |     throw new McpError(ErrorCode.InvalidParams, 'Invalid work item ID');
 9 |   }
10 | 
11 |   AzureDevOpsConnection.initialize(config);
12 |   const connection = AzureDevOpsConnection.getInstance();
13 |   const workItemTrackingApi = await connection.getWorkItemTrackingApi();
14 |   const workItems = await workItemTrackingApi.getWorkItems(
15 |     args.ids,
16 |     args.fields || ['System.Id', 'System.Title', 'System.State', 'System.Description'],
17 |     args.asOf,
18 |     WorkItemExpand.All,
19 |     args.errorPolicy,
20 |     config.project
21 |   );
22 | 
23 |   return {
24 |     content: [
25 |       {
26 |         type: 'text',
27 |         text: JSON.stringify(workItems, null, 2),
28 |       },
29 |     ],
30 |   };
31 | }
```

--------------------------------------------------------------------------------
/src/tools/work-item/update.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | import { JsonPatchOperation } from 'azure-devops-node-api/interfaces/common/VSSInterfaces.js';
 5 | import { WorkItemUpdate } from 'azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js';
 6 | 
 7 | export async function updateWorkItem(args: { id: number; document: JsonPatchOperation[] }, config: AzureDevOpsConfig) {
 8 |   if (!args.id || !args.document || !args.document.length) {
 9 |     throw new McpError(ErrorCode.InvalidParams, 'Work item ID and patch document are required');
10 |   }
11 | 
12 |   AzureDevOpsConnection.initialize(config);
13 |   const connection = AzureDevOpsConnection.getInstance();
14 |   const workItemTrackingApi = await connection.getWorkItemTrackingApi();
15 | 
16 |   const workItem = await workItemTrackingApi.updateWorkItem(
17 |     undefined,
18 |     args.document,
19 |     args.id,
20 |     config.project
21 |   );
22 | 
23 |   return {
24 |     content: [
25 |       {
26 |         type: 'text',
27 |         text: JSON.stringify(workItem, null, 2),
28 |       },
29 |     ],
30 |   };
31 | }
```

--------------------------------------------------------------------------------
/src/config/environment.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { env } from 'process';
 2 | import { ConfigurationError } from '../errors.js';
 3 | 
 4 | export interface AzureDevOpsConfig {
 5 |   pat: string;
 6 |   org: string;
 7 |   project: string;
 8 |   orgUrl: string;
 9 | }
10 | 
11 | function validateConfigValue(value: string | undefined, name: string): string {
12 |   if (!value || value.trim() === '') {
13 |     throw new ConfigurationError(
14 |       `${name} is required and must be provided either through environment variables or constructor options`
15 |     );
16 |   }
17 |   return value.trim();
18 | }
19 | 
20 | export function createConfig(options?: Partial<AzureDevOpsConfig>): AzureDevOpsConfig {
21 |   const PAT = validateConfigValue(
22 |     options?.pat ?? env.AZURE_DEVOPS_PAT,
23 |     'Personal Access Token (pat)'
24 |   );
25 |   const ORG = validateConfigValue(
26 |     options?.org ?? env.AZURE_DEVOPS_ORG,
27 |     'Organization (org)'
28 |   );
29 |   const PROJECT = validateConfigValue(
30 |     options?.project ?? env.AZURE_DEVOPS_PROJECT,
31 |     'Project (project)'
32 |   );
33 | 
34 |   if (!ORG.match(/^[a-zA-Z0-9-_]+$/)) {
35 |     throw new ConfigurationError(
36 |       'Organization name must contain only alphanumeric characters, hyphens, and underscores'
37 |     );
38 |   }
39 | 
40 |   return {
41 |     pat: PAT,
42 |     org: ORG,
43 |     project: PROJECT,
44 |     orgUrl: `https://dev.azure.com/${ORG}`,
45 |   };
46 | }
```

--------------------------------------------------------------------------------
/src/api/connection.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import * as azdev from 'azure-devops-node-api';
 2 | import { WebApi } from 'azure-devops-node-api';
 3 | import { AzureDevOpsConfig } from '../config/environment.js';
 4 | import { WikiApi } from './wiki.js';
 5 | 
 6 | export class AzureDevOpsConnection {
 7 |   private static instance: WebApi | null = null;
 8 |   private static config: AzureDevOpsConfig;
 9 |   private static wikiApi: WikiApi | null = null;
10 | 
11 |   public static initialize(config: AzureDevOpsConfig): void {
12 |     this.config = config;
13 |     // Reset instances when config changes
14 |     this.instance = null;
15 |     this.wikiApi = null;
16 |   }
17 | 
18 |   public static getInstance(): WebApi {
19 |     if (!this.config) {
20 |       throw new Error('AzureDevOpsConnection must be initialized with config before use');
21 |     }
22 | 
23 |     if (!this.instance) {
24 |       const authHandler = azdev.getPersonalAccessTokenHandler(this.config.pat);
25 |       this.instance = new azdev.WebApi(this.config.orgUrl, authHandler);
26 |     }
27 |     return this.instance;
28 |   }
29 | 
30 |   public static getWikiApi(): WikiApi {
31 |     if (!this.config) {
32 |       throw new Error('AzureDevOpsConnection must be initialized with config before use');
33 |     }
34 | 
35 |     if (!this.wikiApi) {
36 |       const connection = this.getInstance();
37 |       this.wikiApi = new WikiApi(connection, this.config);
38 |     }
39 |     return this.wikiApi;
40 |   }
41 | }
```

--------------------------------------------------------------------------------
/src/tools/wiki/create.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | import { WikiType } from 'azure-devops-node-api/interfaces/WikiInterfaces.js';
 5 | 
 6 | interface CreateWikiArgs {
 7 |   name: string;
 8 |   projectId?: string;
 9 |   mappedPath?: string;
10 | }
11 | 
12 | export async function createWiki(args: CreateWikiArgs, config: AzureDevOpsConfig) {
13 |   if (!args.name) {
14 |     throw new McpError(ErrorCode.InvalidParams, 'Wiki name is required');
15 |   }
16 | 
17 |   AzureDevOpsConnection.initialize(config);
18 |   const connection = AzureDevOpsConnection.getInstance();
19 |   const wikiApi = await connection.getWikiApi();
20 | 
21 |   try {
22 |     const wikiCreateParams = {
23 |       name: args.name,
24 |       projectId: args.projectId || config.project,
25 |       mappedPath: args.mappedPath || '/',
26 |       type: WikiType.ProjectWiki,
27 |     };
28 | 
29 |     const wiki = await wikiApi.createWiki(wikiCreateParams, config.project);
30 | 
31 |     return {
32 |       content: [
33 |         {
34 |           type: 'text',
35 |           text: JSON.stringify(wiki, null, 2),
36 |         },
37 |       ],
38 |     };
39 |   } catch (error: unknown) {
40 |     if (error instanceof McpError) throw error;
41 |     const errorMessage = error instanceof Error ? error.message : 'Unknown error';
42 |     throw new McpError(
43 |       ErrorCode.InternalError,
44 |       `Failed to create wiki: ${errorMessage}`
45 |     );
46 |   }
47 | }
```

--------------------------------------------------------------------------------
/src/tools/pipeline/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { getPipelines } from './get.js';
 2 | import { triggerPipeline } from './trigger.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | 
 5 | const definitions = [
 6 |   {
 7 |     name: 'list_pipelines',
 8 |     description: 'List all pipelines in the project',
 9 |     inputSchema: {
10 |       type: 'object',
11 |       properties: {
12 |         folder: {
13 |           type: 'string',
14 |           description: 'Filter pipelines by folder path (optional)',
15 |         },
16 |         name: {
17 |           type: 'string',
18 |           description: 'Filter pipelines by name (optional)',
19 |         },
20 |       },
21 |     },
22 |   },
23 |   {
24 |     name: 'trigger_pipeline',
25 |     description: 'Trigger a pipeline run',
26 |     inputSchema: {
27 |       type: 'object',
28 |       properties: {
29 |         pipelineId: {
30 |           type: 'number',
31 |           description: 'Pipeline ID to trigger',
32 |         },
33 |         branch: {
34 |           type: 'string',
35 |           description: 'Branch to run the pipeline on (optional, defaults to default branch)',
36 |         },
37 |         variables: {
38 |           type: 'object',
39 |           description: 'Pipeline variables to override (optional)',
40 |           additionalProperties: {
41 |             type: 'string',
42 |           },
43 |         },
44 |       },
45 |       required: ['pipelineId'],
46 |     },
47 |   },
48 | ];
49 | 
50 | export const pipelineTools = {
51 |   initialize: (config: AzureDevOpsConfig) => ({
52 |     getPipelines: (args: any) => getPipelines(args, config),
53 |     triggerPipeline: (args: any) => triggerPipeline(args, config),
54 |     definitions,
55 |   }),
56 |   definitions,
57 | };
```

--------------------------------------------------------------------------------
/src/tools/pull-request/create.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | import { GitPullRequest } from 'azure-devops-node-api/interfaces/GitInterfaces.js';
 5 | 
 6 | interface CreatePullRequestArgs {
 7 |   repositoryId: string;
 8 |   sourceRefName: string;
 9 |   targetRefName: string;
10 |   title: string;
11 |   description?: string;
12 |   reviewers?: string[];
13 | }
14 | 
15 | export async function createPullRequest(args: CreatePullRequestArgs, config: AzureDevOpsConfig) {
16 |   if (!args.repositoryId || !args.sourceRefName || !args.targetRefName || !args.title) {
17 |     throw new McpError(
18 |       ErrorCode.InvalidParams,
19 |       'Repository ID, source branch, target branch, and title are required'
20 |     );
21 |   }
22 | 
23 |   AzureDevOpsConnection.initialize(config);
24 |   const connection = AzureDevOpsConnection.getInstance();
25 |   const gitApi = await connection.getGitApi();
26 | 
27 |   try {
28 |     const pullRequestToCreate: GitPullRequest = {
29 |       sourceRefName: args.sourceRefName,
30 |       targetRefName: args.targetRefName,
31 |       title: args.title,
32 |       description: args.description,
33 |       reviewers: args.reviewers?.map(id => ({ id })),
34 |     };
35 | 
36 |     const createdPr = await gitApi.createPullRequest(
37 |       pullRequestToCreate,
38 |       args.repositoryId,
39 |       config.project
40 |     );
41 | 
42 |     return {
43 |       content: [
44 |         {
45 |           type: 'text',
46 |           text: JSON.stringify(createdPr, null, 2),
47 |         },
48 |       ],
49 |     };
50 |   } catch (error: unknown) {
51 |     if (error instanceof McpError) throw error;
52 |     const errorMessage = error instanceof Error ? error.message : 'Unknown error';
53 |     throw new McpError(
54 |       ErrorCode.InternalError,
55 |       `Failed to create pull request: ${errorMessage}`
56 |     );
57 |   }
58 | }
```

--------------------------------------------------------------------------------
/src/tools/pull-request/get.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | import { PullRequestStatus } from 'azure-devops-node-api/interfaces/GitInterfaces.js';
 5 | 
 6 | interface GetPullRequestsArgs {
 7 |   status?: 'active' | 'completed' | 'abandoned';
 8 |   creatorId?: string;
 9 |   repositoryId?: string;
10 | }
11 | 
12 | export async function getPullRequests(args: GetPullRequestsArgs, config: AzureDevOpsConfig) {
13 |   AzureDevOpsConnection.initialize(config);
14 |   const connection = AzureDevOpsConnection.getInstance();
15 |   const gitApi = await connection.getGitApi();
16 | 
17 |   try {
18 |     let statusFilter: PullRequestStatus | undefined;
19 |     if (args.status) {
20 |       switch (args.status) {
21 |         case 'active':
22 |           statusFilter = 1; // PullRequestStatus.Active
23 |           break;
24 |         case 'completed':
25 |           statusFilter = 3; // PullRequestStatus.Completed
26 |           break;
27 |         case 'abandoned':
28 |           statusFilter = 2; // PullRequestStatus.Abandoned
29 |           break;
30 |       }
31 |     }
32 | 
33 |     const searchCriteria = {
34 |       status: statusFilter,
35 |       creatorId: args.creatorId,
36 |       repositoryId: args.repositoryId,
37 |     };
38 | 
39 |     const pullRequests = await gitApi.getPullRequests(
40 |       args.repositoryId || config.project,
41 |       searchCriteria
42 |     );
43 | 
44 |     return {
45 |       content: [
46 |         {
47 |           type: 'text',
48 |           text: JSON.stringify(pullRequests, null, 2),
49 |         },
50 |       ],
51 |     };
52 |   } catch (error: unknown) {
53 |     if (error instanceof McpError) throw error;
54 |     const errorMessage = error instanceof Error ? error.message : 'Unknown error';
55 |     throw new McpError(
56 |       ErrorCode.InternalError,
57 |       `Failed to get pull requests: ${errorMessage}`
58 |     );
59 |   }
60 | }
```

--------------------------------------------------------------------------------
/src/errors.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export class BaseError extends Error {
 2 |   constructor(message: string) {
 3 |     super(message);
 4 |     this.name = this.constructor.name;
 5 |     // Restore prototype chain in Node.js
 6 |     Object.setPrototypeOf(this, BaseError.prototype);
 7 |   }
 8 | }
 9 | 
10 | export class ConfigurationError extends BaseError {
11 |   constructor(message: string) {
12 |     super(message);
13 |     Object.setPrototypeOf(this, ConfigurationError.prototype);
14 |   }
15 | }
16 | 
17 | export class ApiError extends BaseError {
18 |   constructor(
19 |     message: string,
20 |     public readonly statusCode?: number,
21 |     public readonly response?: unknown
22 |   ) {
23 |     super(message);
24 |     Object.setPrototypeOf(this, ApiError.prototype);
25 |   }
26 | }
27 | 
28 | export class WikiError extends ApiError {
29 |   constructor(
30 |     message: string,
31 |     statusCode?: number,
32 |     public readonly wikiId?: string,
33 |     public readonly path?: string,
34 |     response?: unknown
35 |   ) {
36 |     super(message, statusCode, response);
37 |     Object.setPrototypeOf(this, WikiError.prototype);
38 |   }
39 | }
40 | 
41 | export class WikiNotFoundError extends WikiError {
42 |   constructor(wikiId: string) {
43 |     super(`Wiki with ID ${wikiId} not found`, 404, wikiId);
44 |     Object.setPrototypeOf(this, WikiNotFoundError.prototype);
45 |   }
46 | }
47 | 
48 | export class WikiPageNotFoundError extends WikiError {
49 |   constructor(wikiId: string, path: string) {
50 |     super(`Wiki page not found at path ${path}`, 404, wikiId, path);
51 |     Object.setPrototypeOf(this, WikiPageNotFoundError.prototype);
52 |   }
53 | }
54 | 
55 | export class AuthenticationError extends ApiError {
56 |   constructor(message: string = 'Authentication failed') {
57 |     super(message, 401);
58 |     Object.setPrototypeOf(this, AuthenticationError.prototype);
59 |   }
60 | }
61 | 
62 | export class NotFoundError extends ApiError {
63 |   constructor(message: string) {
64 |     super(message, 404);
65 |     Object.setPrototypeOf(this, NotFoundError.prototype);
66 |   }
67 | }
```

--------------------------------------------------------------------------------
/src/tools/pipeline/trigger.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | 
 5 | interface TriggerPipelineArgs {
 6 |   pipelineId: number;
 7 |   branch?: string;
 8 |   variables?: Record<string, string>;
 9 | }
10 | 
11 | export async function triggerPipeline(args: TriggerPipelineArgs, config: AzureDevOpsConfig) {
12 |   if (!args.pipelineId) {
13 |     throw new McpError(ErrorCode.InvalidParams, 'Pipeline ID is required');
14 |   }
15 | 
16 |   AzureDevOpsConnection.initialize(config);
17 |   const connection = AzureDevOpsConnection.getInstance();
18 |   const pipelineApi = await connection.getBuildApi();
19 | 
20 |   try {
21 |     // Get pipeline definition first
22 |     const definition = await pipelineApi.getDefinition(
23 |       config.project,
24 |       args.pipelineId
25 |     );
26 | 
27 |     if (!definition) {
28 |       throw new McpError(
29 |         ErrorCode.InvalidParams,
30 |         `Pipeline with ID ${args.pipelineId} not found`
31 |       );
32 |     }
33 | 
34 |     // Create build parameters
35 |     const build = {
36 |       definition: {
37 |         id: args.pipelineId,
38 |       },
39 |       project: definition.project,
40 |       sourceBranch: args.branch || definition.repository?.defaultBranch || 'main',
41 |       parameters: args.variables ? JSON.stringify(args.variables) : undefined,
42 |     };
43 | 
44 |     // Queue new build
45 |     const queuedBuild = await pipelineApi.queueBuild(build, config.project);
46 | 
47 |     return {
48 |       content: [
49 |         {
50 |           type: 'text',
51 |           text: JSON.stringify(queuedBuild, null, 2),
52 |         },
53 |       ],
54 |     };
55 |   } catch (error: unknown) {
56 |     if (error instanceof McpError) throw error;
57 |     const errorMessage = error instanceof Error ? error.message : 'Unknown error';
58 |     throw new McpError(
59 |       ErrorCode.InternalError,
60 |       `Failed to trigger pipeline: ${errorMessage}`
61 |     );
62 |   }
63 | }
```

--------------------------------------------------------------------------------
/src/tools/wiki/update.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | 
 5 | interface UpdateWikiPageArgs {
 6 |   wikiIdentifier: string;
 7 |   path: string;
 8 |   content: string;
 9 |   comment?: string;
10 | }
11 | 
12 | export async function updateWikiPage(args: UpdateWikiPageArgs, config: AzureDevOpsConfig) {
13 |   if (!args.wikiIdentifier || !args.path || !args.content) {
14 |     throw new McpError(
15 |       ErrorCode.InvalidParams,
16 |       'Wiki identifier, page path, and content are required'
17 |     );
18 |   }
19 | 
20 |   AzureDevOpsConnection.initialize(config);
21 |   const connection = AzureDevOpsConnection.getInstance();
22 |   const wikiApi = await connection.getWikiApi();
23 | 
24 |   try {
25 |     const wiki = await wikiApi.getWiki(config.project, args.wikiIdentifier);
26 |     if (!wiki || !wiki.id) {
27 |       throw new McpError(
28 |         ErrorCode.InvalidParams,
29 |         `Wiki ${args.wikiIdentifier} not found`
30 |       );
31 |     }
32 | 
33 |     const updateParams = {
34 |       content: args.content,
35 |       comment: args.comment || `Updated page ${args.path}`,
36 |     };
37 | 
38 |     // Da die Wiki-API keine direkte Methode zum Aktualisieren von Seiten bietet,
39 |     // geben wir vorerst nur die Wiki-Informationen zurück
40 |     return {
41 |       content: [
42 |         {
43 |           type: 'text',
44 |           text: JSON.stringify({
45 |             wiki,
46 |             path: args.path,
47 |             message: 'Wiki page update is not supported in the current API version',
48 |             requestedUpdate: updateParams
49 |           }, null, 2),
50 |         },
51 |       ],
52 |     };
53 |   } catch (error: unknown) {
54 |     if (error instanceof McpError) throw error;
55 |     const errorMessage = error instanceof Error ? error.message : 'Unknown error';
56 |     throw new McpError(
57 |       ErrorCode.InternalError,
58 |       `Failed to update wiki page: ${errorMessage}`
59 |     );
60 |   }
61 | }
```

--------------------------------------------------------------------------------
/src/tools/wiki/get.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { AzureDevOpsConnection } from '../../api/connection.js';
 3 | import { AzureDevOpsConfig } from '../../config/environment.js';
 4 | 
 5 | interface GetWikiPageArgs {
 6 |   wikiIdentifier: string;
 7 |   path: string;
 8 |   version?: string;
 9 |   includeContent?: boolean;
10 | }
11 | 
12 | export async function getWikis(args: Record<string, never>, config: AzureDevOpsConfig) {
13 |   AzureDevOpsConnection.initialize(config);
14 |   const connection = AzureDevOpsConnection.getInstance();
15 |   const wikiApi = await connection.getWikiApi();
16 |   
17 |   const wikis = await wikiApi.getAllWikis(config.project);
18 | 
19 |   return {
20 |     content: [
21 |       {
22 |         type: 'text',
23 |         text: JSON.stringify(wikis, null, 2),
24 |       },
25 |     ],
26 |   };
27 | }
28 | 
29 | export async function getWikiPage(args: GetWikiPageArgs, config: AzureDevOpsConfig) {
30 |   if (!args.wikiIdentifier || !args.path) {
31 |     throw new McpError(
32 |       ErrorCode.InvalidParams,
33 |       'Wiki identifier and page path are required'
34 |     );
35 |   }
36 | 
37 |   AzureDevOpsConnection.initialize(config);
38 |   const connection = AzureDevOpsConnection.getInstance();
39 |   const wikiApi = await connection.getWikiApi();
40 | 
41 |   try {
42 |     // Get wiki information
43 |     const wiki = await wikiApi.getWiki(config.project, args.wikiIdentifier);
44 |     if (!wiki || !wiki.id) {
45 |       throw new McpError(
46 |         ErrorCode.InvalidParams,
47 |         `Wiki ${args.wikiIdentifier} not found`
48 |       );
49 |     }
50 | 
51 |     // For now, we can only return the wiki information since the page API is not available
52 |     return {
53 |       content: [
54 |         {
55 |           type: 'text',
56 |           text: JSON.stringify({
57 |             id: wiki.id,
58 |             name: wiki.name,
59 |             path: args.path,
60 |             message: 'Wiki page content retrieval is not supported in the current API version'
61 |           }, null, 2),
62 |         },
63 |       ],
64 |     };
65 |   } catch (error: unknown) {
66 |     if (error instanceof McpError) throw error;
67 |     const errorMessage = error instanceof Error ? error.message : 'Unknown error';
68 |     throw new McpError(
69 |       ErrorCode.InternalError,
70 |       `Failed to get wiki page: ${errorMessage}`
71 |     );
72 |   }
73 | }
```

--------------------------------------------------------------------------------
/src/tools/wiki/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { getWikis } from './get.js';
  2 | import { getWikiPage } from './get.js';
  3 | import { createWiki } from './create.js';
  4 | import { updateWikiPage } from './update.js';
  5 | import { AzureDevOpsConfig } from '../../config/environment.js';
  6 | 
  7 | const definitions = [
  8 |   {
  9 |     name: 'get_wikis',
 10 |     description: 'List all wikis in the project',
 11 |     inputSchema: {
 12 |       type: 'object',
 13 |       properties: {},
 14 |     },
 15 |   },
 16 |   {
 17 |     name: 'get_wiki_page',
 18 |     description: 'Get a wiki page by path',
 19 |     inputSchema: {
 20 |       type: 'object',
 21 |       properties: {
 22 |         wikiIdentifier: {
 23 |           type: 'string',
 24 |           description: 'Wiki identifier',
 25 |         },
 26 |         path: {
 27 |           type: 'string',
 28 |           description: 'Page path',
 29 |         },
 30 |         version: {
 31 |           type: 'string',
 32 |           description: 'Version (optional, defaults to main)',
 33 |         },
 34 |         includeContent: {
 35 |           type: 'boolean',
 36 |           description: 'Include page content (optional, defaults to true)',
 37 |         },
 38 |       },
 39 |       required: ['wikiIdentifier', 'path'],
 40 |     },
 41 |   },
 42 |   {
 43 |     name: 'create_wiki',
 44 |     description: 'Create a new wiki',
 45 |     inputSchema: {
 46 |       type: 'object',
 47 |       properties: {
 48 |         name: {
 49 |           type: 'string',
 50 |           description: 'Wiki name',
 51 |         },
 52 |         projectId: {
 53 |           type: 'string',
 54 |           description: 'Project ID (optional, defaults to current project)',
 55 |         },
 56 |         mappedPath: {
 57 |           type: 'string',
 58 |           description: 'Mapped path (optional, defaults to /)',
 59 |         },
 60 |       },
 61 |       required: ['name'],
 62 |     },
 63 |   },
 64 |   {
 65 |     name: 'update_wiki_page',
 66 |     description: 'Create or update a wiki page',
 67 |     inputSchema: {
 68 |       type: 'object',
 69 |       properties: {
 70 |         wikiIdentifier: {
 71 |           type: 'string',
 72 |           description: 'Wiki identifier',
 73 |         },
 74 |         path: {
 75 |           type: 'string',
 76 |           description: 'Page path',
 77 |         },
 78 |         content: {
 79 |           type: 'string',
 80 |           description: 'Page content in markdown format',
 81 |         },
 82 |         comment: {
 83 |           type: 'string',
 84 |           description: 'Comment for the update (optional)',
 85 |         },
 86 |       },
 87 |       required: ['wikiIdentifier', 'path', 'content'],
 88 |     },
 89 |   },
 90 | ];
 91 | 
 92 | export const wikiTools = {
 93 |   initialize: (config: AzureDevOpsConfig) => ({
 94 |     getWikis: (args: Record<string, never>) => getWikis(args, config),
 95 |     getWikiPage: (args: { wikiIdentifier: string; path: string; version?: string; includeContent?: boolean }) =>
 96 |       getWikiPage(args, config),
 97 |     createWiki: (args: { name: string; projectId?: string; mappedPath?: string }) =>
 98 |       createWiki(args, config),
 99 |     updateWikiPage: (args: { wikiIdentifier: string; path: string; content: string; comment?: string }) =>
100 |       updateWikiPage(args, config),
101 |     definitions,
102 |   }),
103 |   definitions,
104 | };
```

--------------------------------------------------------------------------------
/src/tools/pull-request/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { getPullRequests } from './get.js';
  2 | import { createPullRequest } from './create.js';
  3 | import { updatePullRequest } from './update.js';
  4 | import { AzureDevOpsConfig } from '../../config/environment.js';
  5 | 
  6 | const definitions = [
  7 |   {
  8 |     name: 'list_pull_requests',
  9 |     description: 'List all pull requests in the project',
 10 |     inputSchema: {
 11 |       type: 'object',
 12 |       properties: {
 13 |         status: {
 14 |           type: 'string',
 15 |           description: 'Filter by PR status (active, completed, abandoned)',
 16 |           enum: ['active', 'completed', 'abandoned'],
 17 |         },
 18 |         creatorId: {
 19 |           type: 'string',
 20 |           description: 'Filter by creator ID (optional)',
 21 |         },
 22 |         repositoryId: {
 23 |           type: 'string',
 24 |           description: 'Filter by repository ID (optional)',
 25 |         },
 26 |       },
 27 |     },
 28 |   },
 29 |   {
 30 |     name: 'create_pull_request',
 31 |     description: 'Create a new pull request',
 32 |     inputSchema: {
 33 |       type: 'object',
 34 |       properties: {
 35 |         repositoryId: {
 36 |           type: 'string',
 37 |           description: 'Repository ID',
 38 |         },
 39 |         sourceRefName: {
 40 |           type: 'string',
 41 |           description: 'Source branch name (e.g. refs/heads/feature)',
 42 |         },
 43 |         targetRefName: {
 44 |           type: 'string',
 45 |           description: 'Target branch name (e.g. refs/heads/main)',
 46 |         },
 47 |         title: {
 48 |           type: 'string',
 49 |           description: 'Pull request title',
 50 |         },
 51 |         description: {
 52 |           type: 'string',
 53 |           description: 'Pull request description',
 54 |         },
 55 |         reviewers: {
 56 |           type: 'array',
 57 |           description: 'List of reviewer IDs (optional)',
 58 |           items: {
 59 |             type: 'string',
 60 |           },
 61 |         },
 62 |       },
 63 |       required: ['repositoryId', 'sourceRefName', 'targetRefName', 'title'],
 64 |     },
 65 |   },
 66 |   {
 67 |     name: 'update_pull_request',
 68 |     description: 'Update an existing pull request',
 69 |     inputSchema: {
 70 |       type: 'object',
 71 |       properties: {
 72 |         pullRequestId: {
 73 |           type: 'number',
 74 |           description: 'Pull Request ID',
 75 |         },
 76 |         status: {
 77 |           type: 'string',
 78 |           description: 'New status (active, abandoned, completed)',
 79 |           enum: ['active', 'abandoned', 'completed'],
 80 |         },
 81 |         title: {
 82 |           type: 'string',
 83 |           description: 'New title (optional)',
 84 |         },
 85 |         description: {
 86 |           type: 'string',
 87 |           description: 'New description (optional)',
 88 |         },
 89 |         mergeStrategy: {
 90 |           type: 'string',
 91 |           description: 'Merge strategy (optional)',
 92 |           enum: ['squash', 'rebase', 'merge'],
 93 |         },
 94 |       },
 95 |       required: ['pullRequestId'],
 96 |     },
 97 |   },
 98 | ];
 99 | 
100 | export const pullRequestTools = {
101 |   initialize: (config: AzureDevOpsConfig) => ({
102 |     getPullRequests: (args: any) => getPullRequests(args, config),
103 |     createPullRequest: (args: any) => createPullRequest(args, config),
104 |     updatePullRequest: (args: any) => updatePullRequest(args, config),
105 |     definitions,
106 |   }),
107 |   definitions,
108 | };
```

--------------------------------------------------------------------------------
/src/tools/pull-request/update.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
  2 | import { AzureDevOpsConnection } from '../../api/connection.js';
  3 | import { AzureDevOpsConfig } from '../../config/environment.js';
  4 | import { 
  5 |   GitPullRequest, 
  6 |   PullRequestStatus,
  7 |   GitPullRequestMergeStrategy 
  8 | } from 'azure-devops-node-api/interfaces/GitInterfaces.js';
  9 | 
 10 | interface UpdatePullRequestArgs {
 11 |   pullRequestId: number;
 12 |   status?: 'active' | 'abandoned' | 'completed';
 13 |   title?: string;
 14 |   description?: string;
 15 |   mergeStrategy?: 'squash' | 'rebase' | 'merge';
 16 | }
 17 | 
 18 | export async function updatePullRequest(args: UpdatePullRequestArgs, config: AzureDevOpsConfig) {
 19 |   if (!args.pullRequestId) {
 20 |     throw new McpError(ErrorCode.InvalidParams, 'Pull Request ID is required');
 21 |   }
 22 | 
 23 |   AzureDevOpsConnection.initialize(config);
 24 |   const connection = AzureDevOpsConnection.getInstance();
 25 |   const gitApi = await connection.getGitApi();
 26 | 
 27 |   try {
 28 |     // Get current PR
 29 |     const currentPr = await gitApi.getPullRequestById(args.pullRequestId, config.project);
 30 |     if (!currentPr) {
 31 |       throw new McpError(
 32 |         ErrorCode.InvalidParams,
 33 |         `Pull Request with ID ${args.pullRequestId} not found`
 34 |       );
 35 |     }
 36 | 
 37 |     if (!currentPr.repository?.id) {
 38 |       throw new McpError(
 39 |         ErrorCode.InvalidParams,
 40 |         `Repository information not found for PR ${args.pullRequestId}`
 41 |       );
 42 |     }
 43 | 
 44 |     // Prepare update
 45 |     const prUpdate: GitPullRequest = {
 46 |       ...currentPr,
 47 |       title: args.title || currentPr.title,
 48 |       description: args.description || currentPr.description,
 49 |     };
 50 | 
 51 |     // Handle status changes
 52 |     if (args.status) {
 53 |       switch (args.status) {
 54 |         case 'active':
 55 |           prUpdate.status = 1 as PullRequestStatus; // Active
 56 |           break;
 57 |         case 'abandoned':
 58 |           prUpdate.status = 2 as PullRequestStatus; // Abandoned
 59 |           break;
 60 |         case 'completed':
 61 |           prUpdate.status = 3 as PullRequestStatus; // Completed
 62 |           if (args.mergeStrategy) {
 63 |             let mergeStrategyValue: GitPullRequestMergeStrategy;
 64 |             switch (args.mergeStrategy) {
 65 |               case 'squash':
 66 |                 mergeStrategyValue = 3; // GitPullRequestMergeStrategy.Squash
 67 |                 break;
 68 |               case 'rebase':
 69 |                 mergeStrategyValue = 2; // GitPullRequestMergeStrategy.Rebase
 70 |                 break;
 71 |               case 'merge':
 72 |                 mergeStrategyValue = 1; // GitPullRequestMergeStrategy.NoFastForward
 73 |                 break;
 74 |               default:
 75 |                 mergeStrategyValue = 1; // Default to no-fast-forward
 76 |             }
 77 |             
 78 |             prUpdate.completionOptions = {
 79 |               mergeStrategy: mergeStrategyValue,
 80 |               deleteSourceBranch: true,
 81 |               squashMerge: args.mergeStrategy === 'squash',
 82 |             };
 83 |           }
 84 |           break;
 85 |       }
 86 |     }
 87 | 
 88 |     // Update PR
 89 |     const updatedPr = await gitApi.updatePullRequest(
 90 |       prUpdate,
 91 |       currentPr.repository.id,
 92 |       args.pullRequestId,
 93 |       config.project
 94 |     );
 95 | 
 96 |     return {
 97 |       content: [
 98 |         {
 99 |           type: 'text',
100 |           text: JSON.stringify(updatedPr, null, 2),
101 |         },
102 |       ],
103 |     };
104 |   } catch (error: unknown) {
105 |     if (error instanceof McpError) throw error;
106 |     const errorMessage = error instanceof Error ? error.message : 'Unknown error';
107 |     throw new McpError(
108 |       ErrorCode.InternalError,
109 |       `Failed to update pull request: ${errorMessage}`
110 |     );
111 |   }
112 | }
```

--------------------------------------------------------------------------------
/src/tools/work-item/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { getWorkItem } from './get.js';
  2 | import { listWorkItems } from './list.js';
  3 | import { createWorkItem } from './create.js';
  4 | import { updateWorkItem } from './update.js';
  5 | import { AzureDevOpsConfig } from '../../config/environment.js';
  6 | import type { WorkItem, WorkItemBatchGetRequest, Wiql } from 'azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js';
  7 | import type { JsonPatchOperation } from 'azure-devops-node-api/interfaces/common/VSSInterfaces.js';
  8 | 
  9 | const definitions = [
 10 |   {
 11 |     name: 'get_work_item',
 12 |     description: 'Get work items by IDs',
 13 |     inputSchema: {
 14 |       type: 'object',
 15 |       properties: {
 16 |         ids: {
 17 |           type: 'array',
 18 |           items: {
 19 |             type: 'number'
 20 |           },
 21 |           description: 'Work item IDs',
 22 |         },
 23 |         fields: {
 24 |           type: 'array',
 25 |           items: {
 26 |             type: 'string'
 27 |           },
 28 |           description: 'Fields to include (e.g., "System.Title", "System.State")',
 29 |         },
 30 |         asOf: {
 31 |           type: 'string',
 32 |           format: 'date-time',
 33 |           description: 'As of a specific date (ISO 8601)',
 34 |         },
 35 |         $expand: {
 36 |           type: 'number',
 37 |           enum: [0, 1, 2, 3, 4],
 38 |           description: 'Expand options (None=0, Relations=1, Fields=2, Links=3, All=4)',
 39 |         },
 40 |         errorPolicy: {
 41 |           type: 'number',
 42 |           enum: [1, 2],
 43 |           description: 'Error policy (Fail=1, Omit=2)',
 44 |         }
 45 |       },
 46 |       required: ['ids'],
 47 |     },
 48 |   },
 49 |   {
 50 |     name: 'list_work_items',
 51 |     description: 'List work items from a board',
 52 |     inputSchema: {
 53 |       type: 'object',
 54 |       properties: {
 55 |         query: {
 56 |           type: 'string',
 57 |           description: 'WIQL query to filter work items',
 58 |         },
 59 |       },
 60 |       required: ['query'],
 61 |     },
 62 |   },
 63 |   {
 64 |     name: 'create_work_item',
 65 |     description: 'Create a new work item using JSON patch operations',
 66 |     inputSchema: {
 67 |       type: 'object',
 68 |       properties: {
 69 |         type: {
 70 |           type: 'string',
 71 |           description: 'Work item type (e.g., "Bug", "Task", "User Story")',
 72 |         },
 73 |         document: {
 74 |           type: 'array',
 75 |           items: {
 76 |             type: 'object',
 77 |             properties: {
 78 |               op: {
 79 |                 type: 'string',
 80 |                 enum: ['add', 'remove', 'replace', 'move', 'copy', 'test'],
 81 |                 description: 'The patch operation to perform',
 82 |               },
 83 |               path: {
 84 |                 type: 'string',
 85 |                 description: 'The path for the operation (e.g., /fields/System.Title)',
 86 |               },
 87 |               value: {
 88 |                 description: 'The value for the operation',
 89 |               },
 90 |             },
 91 |             required: ['op', 'path'],
 92 |           },
 93 |           description: 'Array of JSON patch operations to apply',
 94 |         },
 95 |       },
 96 |       required: ['type', 'document'],
 97 |     },
 98 |   },
 99 |   {
100 |     name: 'update_work_item',
101 |     description: 'Update an existing work item using JSON patch operations',
102 |     inputSchema: {
103 |       type: 'object',
104 |       properties: {
105 |         id: {
106 |           type: 'number',
107 |           description: 'ID of the work item to update',
108 |         },
109 |         document: {
110 |           type: 'array',
111 |           items: {
112 |             type: 'object',
113 |             properties: {
114 |               op: {
115 |                 type: 'string',
116 |                 enum: ['add', 'remove', 'replace', 'move', 'copy', 'test'],
117 |                 description: 'The patch operation to perform',
118 |               },
119 |               path: {
120 |                 type: 'string',
121 |                 description: 'The path for the operation (e.g., /fields/System.Title)',
122 |               },
123 |               value: {
124 |                 description: 'The value for the operation',
125 |               },
126 |             },
127 |             required: ['op', 'path'],
128 |           },
129 |           description: 'Array of JSON patch operations to apply',
130 |         },
131 |       },
132 |       required: ['id', 'document'],
133 |     },
134 |   },
135 | ];
136 | 
137 | export const workItemTools = {
138 |   initialize: (config: AzureDevOpsConfig) => ({
139 |     getWorkItem: (args: WorkItemBatchGetRequest) => getWorkItem(args, config),
140 |     listWorkItems: (args: Wiql) => listWorkItems(args, config),
141 |     createWorkItem: (args: { type: string; document: JsonPatchOperation[] }) => createWorkItem(args, config),
142 |     updateWorkItem: (args: { id: number; document: JsonPatchOperation[] }) => updateWorkItem(args, config),
143 |     definitions,
144 |   }),
145 |   definitions,
146 | };
```

--------------------------------------------------------------------------------
/src/api/wiki.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { WebApi } from 'azure-devops-node-api';
  2 | import { AzureDevOpsConfig } from '../config/environment.js';
  3 | import { WikiError, WikiNotFoundError, WikiPageNotFoundError } from '../errors.js';
  4 | import fetch from 'node-fetch';
  5 | import type { Wiki, WikiPage, WikiPageResponse, WikiType, WikiCreateParameters, WikiPageCreateOrUpdateParameters } from 'azure-devops-node-api/interfaces/WikiInterfaces.js';
  6 | 
  7 | interface WikiListResponse {
  8 |   count: number;
  9 |   value: Wiki[];
 10 | }
 11 | 
 12 | interface WikiCreateResponse extends WikiCreateParameters {
 13 |   id: string;
 14 |   createdBy: {
 15 |     id: string;
 16 |     displayName: string;
 17 |     uniqueName: string;
 18 |   };
 19 |   createdDate: string;
 20 | }
 21 | 
 22 | interface WikiPageUpdateResponse extends WikiPageResponse {
 23 |   lastUpdatedBy: {
 24 |     id: string;
 25 |     displayName: string;
 26 |     uniqueName: string;
 27 |   };
 28 |   lastUpdatedDate: string;
 29 | }
 30 | 
 31 | export class WikiApi {
 32 |   private connection: WebApi;
 33 |   private baseUrl: string;
 34 |   private config: AzureDevOpsConfig;
 35 | 
 36 |   constructor(connection: WebApi, config: AzureDevOpsConfig) {
 37 |     this.connection = connection;
 38 |     this.config = config;
 39 |     this.baseUrl = `${config.orgUrl}/${config.project}/_apis/wiki`;
 40 |   }
 41 | 
 42 |   private async getAuthHeader(): Promise<string> {
 43 |     const token = Buffer.from(`:${this.config.pat}`).toString('base64');
 44 |     return `Basic ${token}`;
 45 |   }
 46 | 
 47 |   async createWiki(name: string, projectId?: string, mappedPath?: string): Promise<WikiCreateResponse> {
 48 |     const authHeader = await this.getAuthHeader();
 49 |     const response = await fetch(`${this.baseUrl}?api-version=7.0`, {
 50 |       method: 'POST',
 51 |       headers: {
 52 |         'Content-Type': 'application/json',
 53 |         Authorization: authHeader,
 54 |       },
 55 |       body: JSON.stringify({
 56 |         name,
 57 |         projectId: projectId || this.config.project,
 58 |         type: 'projectWiki',
 59 |         mappedPath: mappedPath || '/',
 60 |       }),
 61 |     });
 62 | 
 63 |     if (!response.ok) {
 64 |       throw new WikiError(
 65 |         `Failed to create wiki: ${response.statusText}`,
 66 |         response.status,
 67 |         undefined,
 68 |         undefined,
 69 |         await response.text()
 70 |       );
 71 |     }
 72 | 
 73 |     return response.json();
 74 |   }
 75 | 
 76 |   async getAllWikis(): Promise<WikiListResponse> {
 77 |     const authHeader = await this.getAuthHeader();
 78 |     const response = await fetch(`${this.baseUrl}?api-version=7.0`, {
 79 |       headers: {
 80 |         Authorization: authHeader,
 81 |       },
 82 |     });
 83 | 
 84 |     if (!response.ok) {
 85 |       throw new WikiError(
 86 |         `Failed to get wikis: ${response.statusText}`,
 87 |         response.status,
 88 |         undefined,
 89 |         undefined,
 90 |         await response.text()
 91 |       );
 92 |     }
 93 | 
 94 |     return response.json();
 95 |   }
 96 | 
 97 |   async getWikiPage(wikiIdentifier: string, path: string): Promise<WikiPage> {
 98 |     const authHeader = await this.getAuthHeader();
 99 |     const encodedPath = encodeURIComponent(path);
100 |     const response = await fetch(
101 |       `${this.baseUrl}/${wikiIdentifier}/pages?path=${encodedPath}&api-version=7.0`,
102 |       {
103 |         headers: {
104 |           Authorization: authHeader,
105 |         },
106 |       }
107 |     );
108 | 
109 |     if (response.status === 404) {
110 |       if (response.statusText.includes('Wiki not found')) {
111 |         throw new WikiNotFoundError(wikiIdentifier);
112 |       }
113 |       throw new WikiPageNotFoundError(wikiIdentifier, path);
114 |     }
115 | 
116 |     if (!response.ok) {
117 |       throw new WikiError(
118 |         `Failed to get wiki page: ${response.statusText}`,
119 |         response.status,
120 |         wikiIdentifier,
121 |         path,
122 |         await response.text()
123 |       );
124 |     }
125 | 
126 |     return response.json();
127 |   }
128 | 
129 |   async updateWikiPage(
130 |     wikiIdentifier: string,
131 |     path: string,
132 |     content: string,
133 |     comment?: string
134 |   ): Promise<WikiPageUpdateResponse> {
135 |     const authHeader = await this.getAuthHeader();
136 |     const encodedPath = encodeURIComponent(path);
137 |     const response = await fetch(
138 |       `${this.baseUrl}/${wikiIdentifier}/pages?path=${encodedPath}&api-version=7.0`,
139 |       {
140 |         method: 'PUT',
141 |         headers: {
142 |           'Content-Type': 'application/json',
143 |           Authorization: authHeader,
144 |         },
145 |         body: JSON.stringify({
146 |           content,
147 |           comment: comment || `Updated page ${path}`,
148 |         }),
149 |       }
150 |     );
151 | 
152 |     if (response.status === 404) {
153 |       if (response.statusText.includes('Wiki not found')) {
154 |         throw new WikiNotFoundError(wikiIdentifier);
155 |       }
156 |       throw new WikiPageNotFoundError(wikiIdentifier, path);
157 |     }
158 | 
159 |     if (!response.ok) {
160 |       throw new WikiError(
161 |         `Failed to update wiki page: ${response.statusText}`,
162 |         response.status,
163 |         wikiIdentifier,
164 |         path,
165 |         await response.text()
166 |       );
167 |     }
168 | 
169 |     return response.json();
170 |   }
171 | }
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  4 | import {
  5 |   CallToolRequestSchema,
  6 |   ErrorCode,
  7 |   ListToolsRequestSchema,
  8 |   McpError,
  9 |   JSONRPCResponseSchema,
 10 |   JSONRPCResponse
 11 | } from '@modelcontextprotocol/sdk/types.js';
 12 | 
 13 | // Import all tools
 14 | import { workItemTools } from './tools/work-item/index.js';
 15 | import { boardTools } from './tools/board/index.js';
 16 | import { wikiTools } from './tools/wiki/index.js';
 17 | import { projectTools } from './tools/project/index.js';
 18 | import { pipelineTools } from './tools/pipeline/index.js';
 19 | import { pullRequestTools } from './tools/pull-request/index.js';
 20 | import { AzureDevOpsConfig, createConfig } from './config/environment.js';
 21 | 
 22 | import { ListToolsResult } from '@modelcontextprotocol/sdk/types.js';
 23 | 
 24 | // TODO: Use the proper ToolDefinition type from MCP SDK
 25 | // type ToolDefinition = ReturnType<typeof workItemTools.initialize>['definitions'][number];
 26 | type ToolDefinition = any;
 27 | 
 28 | // TODO: Use a proper ToolInstace definition from MCP SDK
 29 | //interface ToolInstances {
 30 | //  workItem: ReturnType<typeof workItemTools.initialize>;
 31 | //  board: ReturnType<typeof boardTools.initialize>;
 32 | //  wiki: ReturnType<typeof wikiTools.initialize>;
 33 | //  project: ReturnType<typeof projectTools.initialize>;
 34 | //  pipeline: ReturnType<typeof pipelineTools.initialize>;
 35 | //  pullRequest: ReturnType<typeof pullRequestTools.initialize>;
 36 | //}
 37 | type ToolInstances = any;
 38 | 
 39 | // Type Validations
 40 | function validateArgs<T>(args: Record<string, unknown> | undefined, errorMessage: string): T {
 41 |   if (!args) {
 42 |     throw new McpError(ErrorCode.InvalidParams, errorMessage);
 43 |   }
 44 |   return args as T;
 45 | }
 46 | 
 47 | type MCPResponse = JSONRPCResponse["result"]
 48 | 
 49 | // Response Formatting
 50 | function formatResponse(data: unknown): MCPResponse {
 51 |   if (data && typeof data === 'object' && 'content' in data) {
 52 |     return data as MCPResponse;
 53 |   }
 54 |   return {
 55 |     content: [
 56 |       {
 57 |         type: 'text',
 58 |         text: JSON.stringify(data, null, 2),
 59 |       },
 60 |     ],
 61 |   };
 62 | }
 63 | 
 64 | class AzureDevOpsServer {
 65 |   private server: Server;
 66 |   private config: AzureDevOpsConfig;
 67 |   private toolDefinitions: ToolDefinition[];
 68 | 
 69 |   constructor(options?: Partial<Omit<AzureDevOpsConfig, 'orgUrl'>>) {
 70 |     this.config = createConfig(options);
 71 |     
 72 |     // Initialize tools with config
 73 |     const toolInstances = {
 74 |       workItem: workItemTools.initialize(this.config),
 75 |       board: boardTools.initialize(this.config),
 76 |       wiki: wikiTools.initialize(this.config),
 77 |       project: projectTools.initialize(this.config),
 78 |       pipeline: pipelineTools.initialize(this.config),
 79 |       pullRequest: pullRequestTools.initialize(this.config),
 80 |     };
 81 | 
 82 |     // Combine all tool definitions
 83 |     this.toolDefinitions = [
 84 |       ...toolInstances.workItem.definitions,
 85 |       ...toolInstances.board.definitions,
 86 |       ...toolInstances.wiki.definitions,
 87 |       ...toolInstances.project.definitions,
 88 |       ...toolInstances.pipeline.definitions,
 89 |       ...toolInstances.pullRequest.definitions,
 90 |     ];
 91 | 
 92 |     this.server = new Server(
 93 |       {
 94 |         name: 'azure-devops-server',
 95 |         version: '0.1.0',
 96 |       },
 97 |       {
 98 |         capabilities: {
 99 |           tools: {},
100 |         },
101 |       }
102 |     );
103 | 
104 |     this.setupToolHandlers(toolInstances);
105 |     
106 |     // Error handling
107 |     this.server.onerror = (error) => console.error('[MCP Error]', error);
108 |     process.on('SIGINT', async () => {
109 |       await this.server.close();
110 |       process.exit(0);
111 |     });
112 |   }
113 | 
114 |   private setupToolHandlers(tools: ToolInstances) {
115 |     // List available tools
116 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
117 |       tools: this.toolDefinitions,
118 |     }));
119 | 
120 |     // Handle tool calls
121 |     this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
122 |       try {
123 |         let result;
124 |         switch (request.params.name) {
125 |           // Work Item Tools
126 |           case 'get_work_item':
127 |             result = await tools.workItem.getWorkItem(request.params.arguments);
128 |             break;
129 |           case 'list_work_items':
130 |             result = await tools.workItem.listWorkItems(request.params.arguments);
131 |             break;
132 |           
133 |           // Board Tools
134 |           case 'get_boards':
135 |             result = await tools.board.getBoards(request.params.arguments);
136 |             break;
137 |           
138 |           // Wiki Tools
139 |           case 'get_wikis':
140 |             result = await tools.wiki.getWikis(request.params.arguments);
141 |             break;
142 |           case 'get_wiki_page':
143 |             result = await tools.wiki.getWikiPage(request.params.arguments);
144 |             break;
145 |           case 'create_wiki':
146 |             result = await tools.wiki.createWiki(request.params.arguments);
147 |             break;
148 |           case 'update_wiki_page':
149 |             result = await tools.wiki.updateWikiPage(request.params.arguments);
150 |             break;
151 |           
152 |           // Project Tools
153 |           case 'list_projects':
154 |             result = await tools.project.listProjects(request.params.arguments);
155 |             break;
156 | 
157 |           // Pipeline Tools
158 |           case 'list_pipelines':
159 |             result = await tools.pipeline.getPipelines(
160 |               validateArgs(request.params.arguments, 'Pipeline arguments required')
161 |             );
162 |             break;
163 |           case 'trigger_pipeline':
164 |             result = await tools.pipeline.triggerPipeline(
165 |               validateArgs(request.params.arguments, 'Pipeline trigger arguments required')
166 |             );
167 |             break;
168 | 
169 |           // Pull Request Tools
170 |           case 'list_pull_requests':
171 |             result = await tools.pullRequest.getPullRequests(
172 |               validateArgs(request.params.arguments, 'Pull request list arguments required')
173 |             );
174 |             break;
175 |           case 'get_pull_request':
176 |             result = await tools.pullRequest.getPullRequest(
177 |               validateArgs(request.params.arguments, 'Pull request ID required')
178 |             );
179 |             break;
180 |           case 'create_pull_request':
181 |             result = await tools.pullRequest.createPullRequest(
182 |               validateArgs(request.params.arguments, 'Pull request creation arguments required')
183 |             );
184 |             break;
185 |           case 'update_pull_request':
186 |             result = await tools.pullRequest.updatePullRequest(
187 |               validateArgs(request.params.arguments, 'Pull request update arguments required')
188 |             );
189 |             break;
190 |           
191 |           default:
192 |             throw new McpError(
193 |               ErrorCode.MethodNotFound,
194 |               `Unknown tool: ${request.params.name}`
195 |             );
196 |         }
197 | 
198 |         // Ensure consistent response format
199 |         const response = formatResponse(result);
200 |         return {
201 |           _meta: request.params._meta,
202 |           ...response
203 |         };
204 |       } catch (error: unknown) {
205 |         if (error instanceof McpError) throw error;
206 |         const errorMessage = error instanceof Error ? error.message : 'Unknown error';
207 |         throw new McpError(
208 |           ErrorCode.InternalError,
209 |           `Azure DevOps API error: ${errorMessage}`
210 |         );
211 |       }
212 |     });
213 |   }
214 | 
215 |   async run() {
216 |     const transport = new StdioServerTransport();
217 |     await this.server.connect(transport);
218 |     console.error('Azure DevOps MCP server running on stdio');
219 |   }
220 | }
221 | 
222 | // Allow configuration through constructor or environment variables
223 | const server = new AzureDevOpsServer();
224 | server.run().catch(console.error);
225 | 
```