#
tokens: 14029/50000 32/32 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Build output
build/
dist/
*.tsbuildinfo

# IDE and editor files
.idea/
.vscode/
*.swp
*.swo
*~

# Environment variables
.env
.env.local
.env.*.local

# Operating System
.DS_Store
Thumbs.db
```

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

```markdown
# Azure DevOps MCP Server for Cline
[![smithery badge](https://smithery.ai/badge/@stefanskiasan/azure-devops-mcp-server)](https://smithery.ai/server/@stefanskiasan/azure-devops-mcp-server)

This Model Context Protocol (MCP) server provides integration with Azure DevOps, allowing Cline to interact with Azure DevOps services.

## Prerequisites

- Node.js (v20 LTS or higher)
- npm (comes with Node.js)
- A Cline installation
- Azure DevOps account with access tokens

## Installation

### Installing via Smithery

To install Azure DevOps Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@stefanskiasan/azure-devops-mcp-server):

```bash
npx -y @smithery/cli install @stefanskiasan/azure-devops-mcp-server --client claude
```

### Manual Installation
1. Clone this repository:
```bash
git clone https://github.com/stefanskiasan/azure-devops-mcp-server.git
cd azure-devops-mcp-server
```

2. Install dependencies:
```bash
npm install
```

3. Build the server:
```bash
npm run build
```

Note: The build output (`build/` directory) is not included in version control. You must run the build command after cloning the repository.

## Configuration

### 1. Get Azure DevOps Personal Access Token (PAT)

1. Go to Azure DevOps and sign in
2. Click on your profile picture in the top right
3. Select "Security"
4. Click "New Token"
5. Give your token a name and select the required scopes:
   - `Code (read, write)` - For Pull Request operations
   - `Work Items (read, write)` - For Work Item management
   - `Build (read, execute)` - For Pipeline operations
   - `Wiki (read, write)` - For Wiki operations
   - `Project and Team (read)` - For Project and Board information
6. Copy the generated token

### 2. Configure Cline MCP Settings

Add the server configuration to your Cline MCP settings file:

- For VSCode extension: `%APPDATA%/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json`
- For Claude desktop app: `%LOCALAPPDATA%/Claude/claude_desktop_config.json`

Add the following configuration to the `mcpServers` object:

```json
{
  "mcpServers": {
    "azure-devops": {
      "command": "node",
      "args": ["/absolute/path/to/azure-devops-server/build/index.js"],
      "env": {
        "AZURE_DEVOPS_ORG": "your-organization",
        "AZURE_DEVOPS_PAT": "your-personal-access-token",
        "AZURE_DEVOPS_PROJECT": "your-project-name"
      },
      "disabled": false,
      "autoApprove": []
    }
  }
}
```

Replace the following values:
- `/absolute/path/to/azure-devops-server`: The absolute path to where you cloned this repository
- `your-organization`: Your Azure DevOps organization name
- `your-project-name`: Your Azure DevOps project name
- `your-personal-access-token`: The PAT you generated in step 1

## Available Tools

### Work Items
- `get_work_item`: Get a work item by ID
- `list_work_items`: Query work items using WIQL
- `create_work_item`: Create a new work item (Bug, Task, User Story)
- `update_work_item`: Update an existing work item

### Boards
- `get_boards`: Get available boards in the project

### Pipelines
- `list_pipelines`: List all pipelines in the project
- `trigger_pipeline`: Execute a pipeline

### Pull Requests
- `list_pull_requests`: List pull requests
- `create_pull_request`: Create a new pull request
- `update_pull_request`: Update a pull request
- `get_pull_request`: Get pull request details

### Wiki
- `get_wikis`: List all wikis in the project
- `get_wiki_page`: Get a wiki page
- `create_wiki`: Create a new wiki
- `update_wiki_page`: Create or update a wiki page

### Projects
- `list_projects`: List all projects in the Azure DevOps organization

## Verification

1. Restart Cline (or VSCode) after adding the configuration
2. The Azure DevOps MCP server should now be listed in Cline's capabilities
3. You can verify the installation using the MCP Inspector:
```bash
npm run inspector
```

## Troubleshooting

1. If the server isn't connecting:
   - Check that the path in your MCP settings is correct
   - Verify your Azure DevOps credentials
   - Check the Cline logs for any error messages

2. If you get authentication errors:
   - Verify your PAT hasn't expired
   - Ensure the PAT has all necessary scopes
   - Double-check the organization and project names

3. For other issues:
   - Run the inspector tool to verify the server is working correctly
   - Check the server logs for any error messages

## Development

To modify or extend the server:

1. Make your changes in the `src` directory
2. Run `npm run watch` for development
3. Build with `npm run build` when ready
4. Test using the inspector: `npm run inspector`

## License

MIT License - See [LICENSE](LICENSE) for details

```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

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

```yaml
name: CI

on:
  pull_request:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Build project
      run: npm run build
```

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

```typescript
import { listProjects } from './list.js';
import { AzureDevOpsConfig } from '../../config/environment.js';

const definitions = [
  {
    name: 'list_projects',
    description: 'List all projects in the Azure DevOps organization',
    inputSchema: {
      type: 'object',
      properties: {},
      required: [],
    },
  },
];

export const projectTools = {
  initialize: (config: AzureDevOpsConfig) => ({
    listProjects: (args?: Record<string, unknown>) => listProjects(args, config),
    definitions,
  }),
  definitions,
};
```

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

```typescript
import { getBoards } from './get.js';
import { AzureDevOpsConfig } from '../../config/environment.js';

const definitions = [
  {
    name: 'get_boards',
    description: 'List available boards in the project',
    inputSchema: {
      type: 'object',
      properties: {
        team: {
          type: 'string',
          description: 'Team name (optional)',
        },
      },
    },
  },
];

export const boardTools = {
  initialize: (config: AzureDevOpsConfig) => ({
    getBoards: (args: any) => getBoards(args, config),
    definitions,
  }),
  definitions,
};
```

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

```typescript
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';

interface GetBoardsArgs {
  team?: string;
}

export async function getBoards(args: GetBoardsArgs, config: AzureDevOpsConfig) {
  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const workApi = await connection.getWorkApi();
  
  const teamContext = {
    project: config.project,
    team: args.team || `${config.project} Team`,
  };

  const boards = await workApi.getBoards(teamContext);

  return {
    content: [
      {
        type: 'text',
        text: JSON.stringify(boards, null, 2),
      },
    ],
  };
}
```

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

```json
{
  "name": "azure-devops-server",
  "license": "MIT",
  "version": "0.1.0",
  "description": "A Model Context Protocol server",
  "private": true,
  "type": "module",
  "bin": {
    "azure-devops-server": "./build/index.js"
  },
  "files": [
    "build"
  ],
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
    "prepare": "npm run build",
    "watch": "tsc --watch",
    "inspector": "npx @modelcontextprotocol/inspector build/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "0.6.0",
    "@types/node-fetch": "^2.6.12",
    "azure-devops-node-api": "^14.1.0",
    "node-fetch": "^2.7.0"
  },
  "devDependencies": {
    "@types/node": "^20.11.24",
    "typescript": "^5.3.3"
  }
}

```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';
import { Wiql } from 'azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js';

export async function listWorkItems(args: Wiql, config: AzureDevOpsConfig) {
  if (!args.query) {
    throw new McpError(ErrorCode.InvalidParams, 'Invalid WIQL query');
  }

  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const workItemTrackingApi = await connection.getWorkItemTrackingApi();
  
  const queryResult = await workItemTrackingApi.queryByWiql(
    args,
    { project: config.project }
  );

  return {
    content: [
      {
        type: 'text',
        text: JSON.stringify(queryResult, null, 2),
      },
    ],
  };
}
```

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

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# Start with a Node.js image with npm pre-installed
FROM node:16-alpine AS builder

# Create and set the working directory
WORKDIR /app

# Copy all necessary files
COPY . .

# Install the dependencies
RUN npm install

# Build the server
RUN npm run build

# Create a new image for the actual server
FROM node:16-alpine

# Set the working directory
WORKDIR /app

# Copy only the necessary files from the builder image
COPY --from=builder /app/build /app/build
COPY --from=builder /app/node_modules /app/node_modules
COPY --from=builder /app/package.json /app/package.json

# Define environment variables for Azure DevOps
ENV AZURE_DEVOPS_ORG=your-organization
ENV AZURE_DEVOPS_PROJECT=your-project
ENV AZURE_DEVOPS_TOKEN=your-personal-access-token

# Start the server
ENTRYPOINT ["node", "build/index.js"]
```

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

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - azureDevOpsOrg
      - azureDevOpsProject
      - azureDevOpsPat
    properties:
      azureDevOpsOrg:
        type: string
        description: Your Azure DevOps organization name.
      azureDevOpsProject:
        type: string
        description: Your Azure DevOps project name.
      azureDevOpsPat:
        type: string
        description: Your Azure DevOps Personal Access Token.
  commandFunction:
    # A function that produces the CLI command to start the MCP on stdio.
    |-
    (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
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';

export async function listProjects(args: Record<string, unknown> | undefined, config: AzureDevOpsConfig) {
  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const coreApi = await connection.getCoreApi();

  try {
    const projects = await coreApi.getProjects();

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(projects, null, 2),
        },
      ],
    };
  } catch (error: unknown) {
    if (error instanceof McpError) throw error;
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to list projects: ${errorMessage}`
    );
  }
}
```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';
import { JsonPatchOperation } from 'azure-devops-node-api/interfaces/common/VSSInterfaces.js';

export async function createWorkItem(args: { type: string; document: JsonPatchOperation[] }, config: AzureDevOpsConfig) {
  if (!args.type || !args.document || !args.document.length) {
    throw new McpError(ErrorCode.InvalidParams, 'Work item type and patch document are required');
  }

  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const workItemTrackingApi = await connection.getWorkItemTrackingApi();

  const workItem = await workItemTrackingApi.createWorkItem(
    undefined,
    args.document,
    config.project,
    args.type
  );

  return {
    content: [
      {
        type: 'text',
        text: JSON.stringify(workItem, null, 2),
      },
    ],
  };
}
```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';

interface GetPipelinesArgs {
  folder?: string;
  name?: string;
}

export async function getPipelines(args: GetPipelinesArgs, config: AzureDevOpsConfig) {
  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const pipelineApi = await connection.getBuildApi();

  try {
    const pipelines = await pipelineApi.getDefinitions(
      config.project,
      args.name,
      args.folder
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(pipelines, null, 2),
        },
      ],
    };
  } catch (error: unknown) {
    if (error instanceof McpError) throw error;
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to get pipelines: ${errorMessage}`
    );
  }
}
```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';
import { WorkItemBatchGetRequest, WorkItemExpand, WorkItemErrorPolicy } from 'azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js';

export async function getWorkItem(args: WorkItemBatchGetRequest, config: AzureDevOpsConfig) {
  if (!args.ids || !args.ids.length) {
    throw new McpError(ErrorCode.InvalidParams, 'Invalid work item ID');
  }

  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const workItemTrackingApi = await connection.getWorkItemTrackingApi();
  const workItems = await workItemTrackingApi.getWorkItems(
    args.ids,
    args.fields || ['System.Id', 'System.Title', 'System.State', 'System.Description'],
    args.asOf,
    WorkItemExpand.All,
    args.errorPolicy,
    config.project
  );

  return {
    content: [
      {
        type: 'text',
        text: JSON.stringify(workItems, null, 2),
      },
    ],
  };
}
```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';
import { JsonPatchOperation } from 'azure-devops-node-api/interfaces/common/VSSInterfaces.js';
import { WorkItemUpdate } from 'azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js';

export async function updateWorkItem(args: { id: number; document: JsonPatchOperation[] }, config: AzureDevOpsConfig) {
  if (!args.id || !args.document || !args.document.length) {
    throw new McpError(ErrorCode.InvalidParams, 'Work item ID and patch document are required');
  }

  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const workItemTrackingApi = await connection.getWorkItemTrackingApi();

  const workItem = await workItemTrackingApi.updateWorkItem(
    undefined,
    args.document,
    args.id,
    config.project
  );

  return {
    content: [
      {
        type: 'text',
        text: JSON.stringify(workItem, null, 2),
      },
    ],
  };
}
```

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

```typescript
import { env } from 'process';
import { ConfigurationError } from '../errors.js';

export interface AzureDevOpsConfig {
  pat: string;
  org: string;
  project: string;
  orgUrl: string;
}

function validateConfigValue(value: string | undefined, name: string): string {
  if (!value || value.trim() === '') {
    throw new ConfigurationError(
      `${name} is required and must be provided either through environment variables or constructor options`
    );
  }
  return value.trim();
}

export function createConfig(options?: Partial<AzureDevOpsConfig>): AzureDevOpsConfig {
  const PAT = validateConfigValue(
    options?.pat ?? env.AZURE_DEVOPS_PAT,
    'Personal Access Token (pat)'
  );
  const ORG = validateConfigValue(
    options?.org ?? env.AZURE_DEVOPS_ORG,
    'Organization (org)'
  );
  const PROJECT = validateConfigValue(
    options?.project ?? env.AZURE_DEVOPS_PROJECT,
    'Project (project)'
  );

  if (!ORG.match(/^[a-zA-Z0-9-_]+$/)) {
    throw new ConfigurationError(
      'Organization name must contain only alphanumeric characters, hyphens, and underscores'
    );
  }

  return {
    pat: PAT,
    org: ORG,
    project: PROJECT,
    orgUrl: `https://dev.azure.com/${ORG}`,
  };
}
```

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

```typescript
import * as azdev from 'azure-devops-node-api';
import { WebApi } from 'azure-devops-node-api';
import { AzureDevOpsConfig } from '../config/environment.js';
import { WikiApi } from './wiki.js';

export class AzureDevOpsConnection {
  private static instance: WebApi | null = null;
  private static config: AzureDevOpsConfig;
  private static wikiApi: WikiApi | null = null;

  public static initialize(config: AzureDevOpsConfig): void {
    this.config = config;
    // Reset instances when config changes
    this.instance = null;
    this.wikiApi = null;
  }

  public static getInstance(): WebApi {
    if (!this.config) {
      throw new Error('AzureDevOpsConnection must be initialized with config before use');
    }

    if (!this.instance) {
      const authHandler = azdev.getPersonalAccessTokenHandler(this.config.pat);
      this.instance = new azdev.WebApi(this.config.orgUrl, authHandler);
    }
    return this.instance;
  }

  public static getWikiApi(): WikiApi {
    if (!this.config) {
      throw new Error('AzureDevOpsConnection must be initialized with config before use');
    }

    if (!this.wikiApi) {
      const connection = this.getInstance();
      this.wikiApi = new WikiApi(connection, this.config);
    }
    return this.wikiApi;
  }
}
```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';
import { WikiType } from 'azure-devops-node-api/interfaces/WikiInterfaces.js';

interface CreateWikiArgs {
  name: string;
  projectId?: string;
  mappedPath?: string;
}

export async function createWiki(args: CreateWikiArgs, config: AzureDevOpsConfig) {
  if (!args.name) {
    throw new McpError(ErrorCode.InvalidParams, 'Wiki name is required');
  }

  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const wikiApi = await connection.getWikiApi();

  try {
    const wikiCreateParams = {
      name: args.name,
      projectId: args.projectId || config.project,
      mappedPath: args.mappedPath || '/',
      type: WikiType.ProjectWiki,
    };

    const wiki = await wikiApi.createWiki(wikiCreateParams, config.project);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(wiki, null, 2),
        },
      ],
    };
  } catch (error: unknown) {
    if (error instanceof McpError) throw error;
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to create wiki: ${errorMessage}`
    );
  }
}
```

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

```typescript
import { getPipelines } from './get.js';
import { triggerPipeline } from './trigger.js';
import { AzureDevOpsConfig } from '../../config/environment.js';

const definitions = [
  {
    name: 'list_pipelines',
    description: 'List all pipelines in the project',
    inputSchema: {
      type: 'object',
      properties: {
        folder: {
          type: 'string',
          description: 'Filter pipelines by folder path (optional)',
        },
        name: {
          type: 'string',
          description: 'Filter pipelines by name (optional)',
        },
      },
    },
  },
  {
    name: 'trigger_pipeline',
    description: 'Trigger a pipeline run',
    inputSchema: {
      type: 'object',
      properties: {
        pipelineId: {
          type: 'number',
          description: 'Pipeline ID to trigger',
        },
        branch: {
          type: 'string',
          description: 'Branch to run the pipeline on (optional, defaults to default branch)',
        },
        variables: {
          type: 'object',
          description: 'Pipeline variables to override (optional)',
          additionalProperties: {
            type: 'string',
          },
        },
      },
      required: ['pipelineId'],
    },
  },
];

export const pipelineTools = {
  initialize: (config: AzureDevOpsConfig) => ({
    getPipelines: (args: any) => getPipelines(args, config),
    triggerPipeline: (args: any) => triggerPipeline(args, config),
    definitions,
  }),
  definitions,
};
```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';
import { GitPullRequest } from 'azure-devops-node-api/interfaces/GitInterfaces.js';

interface CreatePullRequestArgs {
  repositoryId: string;
  sourceRefName: string;
  targetRefName: string;
  title: string;
  description?: string;
  reviewers?: string[];
}

export async function createPullRequest(args: CreatePullRequestArgs, config: AzureDevOpsConfig) {
  if (!args.repositoryId || !args.sourceRefName || !args.targetRefName || !args.title) {
    throw new McpError(
      ErrorCode.InvalidParams,
      'Repository ID, source branch, target branch, and title are required'
    );
  }

  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const gitApi = await connection.getGitApi();

  try {
    const pullRequestToCreate: GitPullRequest = {
      sourceRefName: args.sourceRefName,
      targetRefName: args.targetRefName,
      title: args.title,
      description: args.description,
      reviewers: args.reviewers?.map(id => ({ id })),
    };

    const createdPr = await gitApi.createPullRequest(
      pullRequestToCreate,
      args.repositoryId,
      config.project
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(createdPr, null, 2),
        },
      ],
    };
  } catch (error: unknown) {
    if (error instanceof McpError) throw error;
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to create pull request: ${errorMessage}`
    );
  }
}
```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';
import { PullRequestStatus } from 'azure-devops-node-api/interfaces/GitInterfaces.js';

interface GetPullRequestsArgs {
  status?: 'active' | 'completed' | 'abandoned';
  creatorId?: string;
  repositoryId?: string;
}

export async function getPullRequests(args: GetPullRequestsArgs, config: AzureDevOpsConfig) {
  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const gitApi = await connection.getGitApi();

  try {
    let statusFilter: PullRequestStatus | undefined;
    if (args.status) {
      switch (args.status) {
        case 'active':
          statusFilter = 1; // PullRequestStatus.Active
          break;
        case 'completed':
          statusFilter = 3; // PullRequestStatus.Completed
          break;
        case 'abandoned':
          statusFilter = 2; // PullRequestStatus.Abandoned
          break;
      }
    }

    const searchCriteria = {
      status: statusFilter,
      creatorId: args.creatorId,
      repositoryId: args.repositoryId,
    };

    const pullRequests = await gitApi.getPullRequests(
      args.repositoryId || config.project,
      searchCriteria
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(pullRequests, null, 2),
        },
      ],
    };
  } catch (error: unknown) {
    if (error instanceof McpError) throw error;
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to get pull requests: ${errorMessage}`
    );
  }
}
```

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

```typescript
export class BaseError extends Error {
  constructor(message: string) {
    super(message);
    this.name = this.constructor.name;
    // Restore prototype chain in Node.js
    Object.setPrototypeOf(this, BaseError.prototype);
  }
}

export class ConfigurationError extends BaseError {
  constructor(message: string) {
    super(message);
    Object.setPrototypeOf(this, ConfigurationError.prototype);
  }
}

export class ApiError extends BaseError {
  constructor(
    message: string,
    public readonly statusCode?: number,
    public readonly response?: unknown
  ) {
    super(message);
    Object.setPrototypeOf(this, ApiError.prototype);
  }
}

export class WikiError extends ApiError {
  constructor(
    message: string,
    statusCode?: number,
    public readonly wikiId?: string,
    public readonly path?: string,
    response?: unknown
  ) {
    super(message, statusCode, response);
    Object.setPrototypeOf(this, WikiError.prototype);
  }
}

export class WikiNotFoundError extends WikiError {
  constructor(wikiId: string) {
    super(`Wiki with ID ${wikiId} not found`, 404, wikiId);
    Object.setPrototypeOf(this, WikiNotFoundError.prototype);
  }
}

export class WikiPageNotFoundError extends WikiError {
  constructor(wikiId: string, path: string) {
    super(`Wiki page not found at path ${path}`, 404, wikiId, path);
    Object.setPrototypeOf(this, WikiPageNotFoundError.prototype);
  }
}

export class AuthenticationError extends ApiError {
  constructor(message: string = 'Authentication failed') {
    super(message, 401);
    Object.setPrototypeOf(this, AuthenticationError.prototype);
  }
}

export class NotFoundError extends ApiError {
  constructor(message: string) {
    super(message, 404);
    Object.setPrototypeOf(this, NotFoundError.prototype);
  }
}
```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';

interface TriggerPipelineArgs {
  pipelineId: number;
  branch?: string;
  variables?: Record<string, string>;
}

export async function triggerPipeline(args: TriggerPipelineArgs, config: AzureDevOpsConfig) {
  if (!args.pipelineId) {
    throw new McpError(ErrorCode.InvalidParams, 'Pipeline ID is required');
  }

  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const pipelineApi = await connection.getBuildApi();

  try {
    // Get pipeline definition first
    const definition = await pipelineApi.getDefinition(
      config.project,
      args.pipelineId
    );

    if (!definition) {
      throw new McpError(
        ErrorCode.InvalidParams,
        `Pipeline with ID ${args.pipelineId} not found`
      );
    }

    // Create build parameters
    const build = {
      definition: {
        id: args.pipelineId,
      },
      project: definition.project,
      sourceBranch: args.branch || definition.repository?.defaultBranch || 'main',
      parameters: args.variables ? JSON.stringify(args.variables) : undefined,
    };

    // Queue new build
    const queuedBuild = await pipelineApi.queueBuild(build, config.project);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(queuedBuild, null, 2),
        },
      ],
    };
  } catch (error: unknown) {
    if (error instanceof McpError) throw error;
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to trigger pipeline: ${errorMessage}`
    );
  }
}
```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';

interface UpdateWikiPageArgs {
  wikiIdentifier: string;
  path: string;
  content: string;
  comment?: string;
}

export async function updateWikiPage(args: UpdateWikiPageArgs, config: AzureDevOpsConfig) {
  if (!args.wikiIdentifier || !args.path || !args.content) {
    throw new McpError(
      ErrorCode.InvalidParams,
      'Wiki identifier, page path, and content are required'
    );
  }

  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const wikiApi = await connection.getWikiApi();

  try {
    const wiki = await wikiApi.getWiki(config.project, args.wikiIdentifier);
    if (!wiki || !wiki.id) {
      throw new McpError(
        ErrorCode.InvalidParams,
        `Wiki ${args.wikiIdentifier} not found`
      );
    }

    const updateParams = {
      content: args.content,
      comment: args.comment || `Updated page ${args.path}`,
    };

    // Da die Wiki-API keine direkte Methode zum Aktualisieren von Seiten bietet,
    // geben wir vorerst nur die Wiki-Informationen zurück
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            wiki,
            path: args.path,
            message: 'Wiki page update is not supported in the current API version',
            requestedUpdate: updateParams
          }, null, 2),
        },
      ],
    };
  } catch (error: unknown) {
    if (error instanceof McpError) throw error;
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to update wiki page: ${errorMessage}`
    );
  }
}
```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';

interface GetWikiPageArgs {
  wikiIdentifier: string;
  path: string;
  version?: string;
  includeContent?: boolean;
}

export async function getWikis(args: Record<string, never>, config: AzureDevOpsConfig) {
  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const wikiApi = await connection.getWikiApi();
  
  const wikis = await wikiApi.getAllWikis(config.project);

  return {
    content: [
      {
        type: 'text',
        text: JSON.stringify(wikis, null, 2),
      },
    ],
  };
}

export async function getWikiPage(args: GetWikiPageArgs, config: AzureDevOpsConfig) {
  if (!args.wikiIdentifier || !args.path) {
    throw new McpError(
      ErrorCode.InvalidParams,
      'Wiki identifier and page path are required'
    );
  }

  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const wikiApi = await connection.getWikiApi();

  try {
    // Get wiki information
    const wiki = await wikiApi.getWiki(config.project, args.wikiIdentifier);
    if (!wiki || !wiki.id) {
      throw new McpError(
        ErrorCode.InvalidParams,
        `Wiki ${args.wikiIdentifier} not found`
      );
    }

    // For now, we can only return the wiki information since the page API is not available
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            id: wiki.id,
            name: wiki.name,
            path: args.path,
            message: 'Wiki page content retrieval is not supported in the current API version'
          }, null, 2),
        },
      ],
    };
  } catch (error: unknown) {
    if (error instanceof McpError) throw error;
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to get wiki page: ${errorMessage}`
    );
  }
}
```

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

```typescript
import { getWikis } from './get.js';
import { getWikiPage } from './get.js';
import { createWiki } from './create.js';
import { updateWikiPage } from './update.js';
import { AzureDevOpsConfig } from '../../config/environment.js';

const definitions = [
  {
    name: 'get_wikis',
    description: 'List all wikis in the project',
    inputSchema: {
      type: 'object',
      properties: {},
    },
  },
  {
    name: 'get_wiki_page',
    description: 'Get a wiki page by path',
    inputSchema: {
      type: 'object',
      properties: {
        wikiIdentifier: {
          type: 'string',
          description: 'Wiki identifier',
        },
        path: {
          type: 'string',
          description: 'Page path',
        },
        version: {
          type: 'string',
          description: 'Version (optional, defaults to main)',
        },
        includeContent: {
          type: 'boolean',
          description: 'Include page content (optional, defaults to true)',
        },
      },
      required: ['wikiIdentifier', 'path'],
    },
  },
  {
    name: 'create_wiki',
    description: 'Create a new wiki',
    inputSchema: {
      type: 'object',
      properties: {
        name: {
          type: 'string',
          description: 'Wiki name',
        },
        projectId: {
          type: 'string',
          description: 'Project ID (optional, defaults to current project)',
        },
        mappedPath: {
          type: 'string',
          description: 'Mapped path (optional, defaults to /)',
        },
      },
      required: ['name'],
    },
  },
  {
    name: 'update_wiki_page',
    description: 'Create or update a wiki page',
    inputSchema: {
      type: 'object',
      properties: {
        wikiIdentifier: {
          type: 'string',
          description: 'Wiki identifier',
        },
        path: {
          type: 'string',
          description: 'Page path',
        },
        content: {
          type: 'string',
          description: 'Page content in markdown format',
        },
        comment: {
          type: 'string',
          description: 'Comment for the update (optional)',
        },
      },
      required: ['wikiIdentifier', 'path', 'content'],
    },
  },
];

export const wikiTools = {
  initialize: (config: AzureDevOpsConfig) => ({
    getWikis: (args: Record<string, never>) => getWikis(args, config),
    getWikiPage: (args: { wikiIdentifier: string; path: string; version?: string; includeContent?: boolean }) =>
      getWikiPage(args, config),
    createWiki: (args: { name: string; projectId?: string; mappedPath?: string }) =>
      createWiki(args, config),
    updateWikiPage: (args: { wikiIdentifier: string; path: string; content: string; comment?: string }) =>
      updateWikiPage(args, config),
    definitions,
  }),
  definitions,
};
```

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

```typescript
import { getPullRequests } from './get.js';
import { createPullRequest } from './create.js';
import { updatePullRequest } from './update.js';
import { AzureDevOpsConfig } from '../../config/environment.js';

const definitions = [
  {
    name: 'list_pull_requests',
    description: 'List all pull requests in the project',
    inputSchema: {
      type: 'object',
      properties: {
        status: {
          type: 'string',
          description: 'Filter by PR status (active, completed, abandoned)',
          enum: ['active', 'completed', 'abandoned'],
        },
        creatorId: {
          type: 'string',
          description: 'Filter by creator ID (optional)',
        },
        repositoryId: {
          type: 'string',
          description: 'Filter by repository ID (optional)',
        },
      },
    },
  },
  {
    name: 'create_pull_request',
    description: 'Create a new pull request',
    inputSchema: {
      type: 'object',
      properties: {
        repositoryId: {
          type: 'string',
          description: 'Repository ID',
        },
        sourceRefName: {
          type: 'string',
          description: 'Source branch name (e.g. refs/heads/feature)',
        },
        targetRefName: {
          type: 'string',
          description: 'Target branch name (e.g. refs/heads/main)',
        },
        title: {
          type: 'string',
          description: 'Pull request title',
        },
        description: {
          type: 'string',
          description: 'Pull request description',
        },
        reviewers: {
          type: 'array',
          description: 'List of reviewer IDs (optional)',
          items: {
            type: 'string',
          },
        },
      },
      required: ['repositoryId', 'sourceRefName', 'targetRefName', 'title'],
    },
  },
  {
    name: 'update_pull_request',
    description: 'Update an existing pull request',
    inputSchema: {
      type: 'object',
      properties: {
        pullRequestId: {
          type: 'number',
          description: 'Pull Request ID',
        },
        status: {
          type: 'string',
          description: 'New status (active, abandoned, completed)',
          enum: ['active', 'abandoned', 'completed'],
        },
        title: {
          type: 'string',
          description: 'New title (optional)',
        },
        description: {
          type: 'string',
          description: 'New description (optional)',
        },
        mergeStrategy: {
          type: 'string',
          description: 'Merge strategy (optional)',
          enum: ['squash', 'rebase', 'merge'],
        },
      },
      required: ['pullRequestId'],
    },
  },
];

export const pullRequestTools = {
  initialize: (config: AzureDevOpsConfig) => ({
    getPullRequests: (args: any) => getPullRequests(args, config),
    createPullRequest: (args: any) => createPullRequest(args, config),
    updatePullRequest: (args: any) => updatePullRequest(args, config),
    definitions,
  }),
  definitions,
};
```

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

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { AzureDevOpsConnection } from '../../api/connection.js';
import { AzureDevOpsConfig } from '../../config/environment.js';
import { 
  GitPullRequest, 
  PullRequestStatus,
  GitPullRequestMergeStrategy 
} from 'azure-devops-node-api/interfaces/GitInterfaces.js';

interface UpdatePullRequestArgs {
  pullRequestId: number;
  status?: 'active' | 'abandoned' | 'completed';
  title?: string;
  description?: string;
  mergeStrategy?: 'squash' | 'rebase' | 'merge';
}

export async function updatePullRequest(args: UpdatePullRequestArgs, config: AzureDevOpsConfig) {
  if (!args.pullRequestId) {
    throw new McpError(ErrorCode.InvalidParams, 'Pull Request ID is required');
  }

  AzureDevOpsConnection.initialize(config);
  const connection = AzureDevOpsConnection.getInstance();
  const gitApi = await connection.getGitApi();

  try {
    // Get current PR
    const currentPr = await gitApi.getPullRequestById(args.pullRequestId, config.project);
    if (!currentPr) {
      throw new McpError(
        ErrorCode.InvalidParams,
        `Pull Request with ID ${args.pullRequestId} not found`
      );
    }

    if (!currentPr.repository?.id) {
      throw new McpError(
        ErrorCode.InvalidParams,
        `Repository information not found for PR ${args.pullRequestId}`
      );
    }

    // Prepare update
    const prUpdate: GitPullRequest = {
      ...currentPr,
      title: args.title || currentPr.title,
      description: args.description || currentPr.description,
    };

    // Handle status changes
    if (args.status) {
      switch (args.status) {
        case 'active':
          prUpdate.status = 1 as PullRequestStatus; // Active
          break;
        case 'abandoned':
          prUpdate.status = 2 as PullRequestStatus; // Abandoned
          break;
        case 'completed':
          prUpdate.status = 3 as PullRequestStatus; // Completed
          if (args.mergeStrategy) {
            let mergeStrategyValue: GitPullRequestMergeStrategy;
            switch (args.mergeStrategy) {
              case 'squash':
                mergeStrategyValue = 3; // GitPullRequestMergeStrategy.Squash
                break;
              case 'rebase':
                mergeStrategyValue = 2; // GitPullRequestMergeStrategy.Rebase
                break;
              case 'merge':
                mergeStrategyValue = 1; // GitPullRequestMergeStrategy.NoFastForward
                break;
              default:
                mergeStrategyValue = 1; // Default to no-fast-forward
            }
            
            prUpdate.completionOptions = {
              mergeStrategy: mergeStrategyValue,
              deleteSourceBranch: true,
              squashMerge: args.mergeStrategy === 'squash',
            };
          }
          break;
      }
    }

    // Update PR
    const updatedPr = await gitApi.updatePullRequest(
      prUpdate,
      currentPr.repository.id,
      args.pullRequestId,
      config.project
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(updatedPr, null, 2),
        },
      ],
    };
  } catch (error: unknown) {
    if (error instanceof McpError) throw error;
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to update pull request: ${errorMessage}`
    );
  }
}
```

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

```typescript
import { getWorkItem } from './get.js';
import { listWorkItems } from './list.js';
import { createWorkItem } from './create.js';
import { updateWorkItem } from './update.js';
import { AzureDevOpsConfig } from '../../config/environment.js';
import type { WorkItem, WorkItemBatchGetRequest, Wiql } from 'azure-devops-node-api/interfaces/WorkItemTrackingInterfaces.js';
import type { JsonPatchOperation } from 'azure-devops-node-api/interfaces/common/VSSInterfaces.js';

const definitions = [
  {
    name: 'get_work_item',
    description: 'Get work items by IDs',
    inputSchema: {
      type: 'object',
      properties: {
        ids: {
          type: 'array',
          items: {
            type: 'number'
          },
          description: 'Work item IDs',
        },
        fields: {
          type: 'array',
          items: {
            type: 'string'
          },
          description: 'Fields to include (e.g., "System.Title", "System.State")',
        },
        asOf: {
          type: 'string',
          format: 'date-time',
          description: 'As of a specific date (ISO 8601)',
        },
        $expand: {
          type: 'number',
          enum: [0, 1, 2, 3, 4],
          description: 'Expand options (None=0, Relations=1, Fields=2, Links=3, All=4)',
        },
        errorPolicy: {
          type: 'number',
          enum: [1, 2],
          description: 'Error policy (Fail=1, Omit=2)',
        }
      },
      required: ['ids'],
    },
  },
  {
    name: 'list_work_items',
    description: 'List work items from a board',
    inputSchema: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: 'WIQL query to filter work items',
        },
      },
      required: ['query'],
    },
  },
  {
    name: 'create_work_item',
    description: 'Create a new work item using JSON patch operations',
    inputSchema: {
      type: 'object',
      properties: {
        type: {
          type: 'string',
          description: 'Work item type (e.g., "Bug", "Task", "User Story")',
        },
        document: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              op: {
                type: 'string',
                enum: ['add', 'remove', 'replace', 'move', 'copy', 'test'],
                description: 'The patch operation to perform',
              },
              path: {
                type: 'string',
                description: 'The path for the operation (e.g., /fields/System.Title)',
              },
              value: {
                description: 'The value for the operation',
              },
            },
            required: ['op', 'path'],
          },
          description: 'Array of JSON patch operations to apply',
        },
      },
      required: ['type', 'document'],
    },
  },
  {
    name: 'update_work_item',
    description: 'Update an existing work item using JSON patch operations',
    inputSchema: {
      type: 'object',
      properties: {
        id: {
          type: 'number',
          description: 'ID of the work item to update',
        },
        document: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              op: {
                type: 'string',
                enum: ['add', 'remove', 'replace', 'move', 'copy', 'test'],
                description: 'The patch operation to perform',
              },
              path: {
                type: 'string',
                description: 'The path for the operation (e.g., /fields/System.Title)',
              },
              value: {
                description: 'The value for the operation',
              },
            },
            required: ['op', 'path'],
          },
          description: 'Array of JSON patch operations to apply',
        },
      },
      required: ['id', 'document'],
    },
  },
];

export const workItemTools = {
  initialize: (config: AzureDevOpsConfig) => ({
    getWorkItem: (args: WorkItemBatchGetRequest) => getWorkItem(args, config),
    listWorkItems: (args: Wiql) => listWorkItems(args, config),
    createWorkItem: (args: { type: string; document: JsonPatchOperation[] }) => createWorkItem(args, config),
    updateWorkItem: (args: { id: number; document: JsonPatchOperation[] }) => updateWorkItem(args, config),
    definitions,
  }),
  definitions,
};
```

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

```typescript
import { WebApi } from 'azure-devops-node-api';
import { AzureDevOpsConfig } from '../config/environment.js';
import { WikiError, WikiNotFoundError, WikiPageNotFoundError } from '../errors.js';
import fetch from 'node-fetch';
import type { Wiki, WikiPage, WikiPageResponse, WikiType, WikiCreateParameters, WikiPageCreateOrUpdateParameters } from 'azure-devops-node-api/interfaces/WikiInterfaces.js';

interface WikiListResponse {
  count: number;
  value: Wiki[];
}

interface WikiCreateResponse extends WikiCreateParameters {
  id: string;
  createdBy: {
    id: string;
    displayName: string;
    uniqueName: string;
  };
  createdDate: string;
}

interface WikiPageUpdateResponse extends WikiPageResponse {
  lastUpdatedBy: {
    id: string;
    displayName: string;
    uniqueName: string;
  };
  lastUpdatedDate: string;
}

export class WikiApi {
  private connection: WebApi;
  private baseUrl: string;
  private config: AzureDevOpsConfig;

  constructor(connection: WebApi, config: AzureDevOpsConfig) {
    this.connection = connection;
    this.config = config;
    this.baseUrl = `${config.orgUrl}/${config.project}/_apis/wiki`;
  }

  private async getAuthHeader(): Promise<string> {
    const token = Buffer.from(`:${this.config.pat}`).toString('base64');
    return `Basic ${token}`;
  }

  async createWiki(name: string, projectId?: string, mappedPath?: string): Promise<WikiCreateResponse> {
    const authHeader = await this.getAuthHeader();
    const response = await fetch(`${this.baseUrl}?api-version=7.0`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: authHeader,
      },
      body: JSON.stringify({
        name,
        projectId: projectId || this.config.project,
        type: 'projectWiki',
        mappedPath: mappedPath || '/',
      }),
    });

    if (!response.ok) {
      throw new WikiError(
        `Failed to create wiki: ${response.statusText}`,
        response.status,
        undefined,
        undefined,
        await response.text()
      );
    }

    return response.json();
  }

  async getAllWikis(): Promise<WikiListResponse> {
    const authHeader = await this.getAuthHeader();
    const response = await fetch(`${this.baseUrl}?api-version=7.0`, {
      headers: {
        Authorization: authHeader,
      },
    });

    if (!response.ok) {
      throw new WikiError(
        `Failed to get wikis: ${response.statusText}`,
        response.status,
        undefined,
        undefined,
        await response.text()
      );
    }

    return response.json();
  }

  async getWikiPage(wikiIdentifier: string, path: string): Promise<WikiPage> {
    const authHeader = await this.getAuthHeader();
    const encodedPath = encodeURIComponent(path);
    const response = await fetch(
      `${this.baseUrl}/${wikiIdentifier}/pages?path=${encodedPath}&api-version=7.0`,
      {
        headers: {
          Authorization: authHeader,
        },
      }
    );

    if (response.status === 404) {
      if (response.statusText.includes('Wiki not found')) {
        throw new WikiNotFoundError(wikiIdentifier);
      }
      throw new WikiPageNotFoundError(wikiIdentifier, path);
    }

    if (!response.ok) {
      throw new WikiError(
        `Failed to get wiki page: ${response.statusText}`,
        response.status,
        wikiIdentifier,
        path,
        await response.text()
      );
    }

    return response.json();
  }

  async updateWikiPage(
    wikiIdentifier: string,
    path: string,
    content: string,
    comment?: string
  ): Promise<WikiPageUpdateResponse> {
    const authHeader = await this.getAuthHeader();
    const encodedPath = encodeURIComponent(path);
    const response = await fetch(
      `${this.baseUrl}/${wikiIdentifier}/pages?path=${encodedPath}&api-version=7.0`,
      {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
          Authorization: authHeader,
        },
        body: JSON.stringify({
          content,
          comment: comment || `Updated page ${path}`,
        }),
      }
    );

    if (response.status === 404) {
      if (response.statusText.includes('Wiki not found')) {
        throw new WikiNotFoundError(wikiIdentifier);
      }
      throw new WikiPageNotFoundError(wikiIdentifier, path);
    }

    if (!response.ok) {
      throw new WikiError(
        `Failed to update wiki page: ${response.statusText}`,
        response.status,
        wikiIdentifier,
        path,
        await response.text()
      );
    }

    return response.json();
  }
}
```

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

```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
  JSONRPCResponseSchema,
  JSONRPCResponse
} from '@modelcontextprotocol/sdk/types.js';

// Import all tools
import { workItemTools } from './tools/work-item/index.js';
import { boardTools } from './tools/board/index.js';
import { wikiTools } from './tools/wiki/index.js';
import { projectTools } from './tools/project/index.js';
import { pipelineTools } from './tools/pipeline/index.js';
import { pullRequestTools } from './tools/pull-request/index.js';
import { AzureDevOpsConfig, createConfig } from './config/environment.js';

import { ListToolsResult } from '@modelcontextprotocol/sdk/types.js';

// TODO: Use the proper ToolDefinition type from MCP SDK
// type ToolDefinition = ReturnType<typeof workItemTools.initialize>['definitions'][number];
type ToolDefinition = any;

// TODO: Use a proper ToolInstace definition from MCP SDK
//interface ToolInstances {
//  workItem: ReturnType<typeof workItemTools.initialize>;
//  board: ReturnType<typeof boardTools.initialize>;
//  wiki: ReturnType<typeof wikiTools.initialize>;
//  project: ReturnType<typeof projectTools.initialize>;
//  pipeline: ReturnType<typeof pipelineTools.initialize>;
//  pullRequest: ReturnType<typeof pullRequestTools.initialize>;
//}
type ToolInstances = any;

// Type Validations
function validateArgs<T>(args: Record<string, unknown> | undefined, errorMessage: string): T {
  if (!args) {
    throw new McpError(ErrorCode.InvalidParams, errorMessage);
  }
  return args as T;
}

type MCPResponse = JSONRPCResponse["result"]

// Response Formatting
function formatResponse(data: unknown): MCPResponse {
  if (data && typeof data === 'object' && 'content' in data) {
    return data as MCPResponse;
  }
  return {
    content: [
      {
        type: 'text',
        text: JSON.stringify(data, null, 2),
      },
    ],
  };
}

class AzureDevOpsServer {
  private server: Server;
  private config: AzureDevOpsConfig;
  private toolDefinitions: ToolDefinition[];

  constructor(options?: Partial<Omit<AzureDevOpsConfig, 'orgUrl'>>) {
    this.config = createConfig(options);
    
    // Initialize tools with config
    const toolInstances = {
      workItem: workItemTools.initialize(this.config),
      board: boardTools.initialize(this.config),
      wiki: wikiTools.initialize(this.config),
      project: projectTools.initialize(this.config),
      pipeline: pipelineTools.initialize(this.config),
      pullRequest: pullRequestTools.initialize(this.config),
    };

    // Combine all tool definitions
    this.toolDefinitions = [
      ...toolInstances.workItem.definitions,
      ...toolInstances.board.definitions,
      ...toolInstances.wiki.definitions,
      ...toolInstances.project.definitions,
      ...toolInstances.pipeline.definitions,
      ...toolInstances.pullRequest.definitions,
    ];

    this.server = new Server(
      {
        name: 'azure-devops-server',
        version: '0.1.0',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );

    this.setupToolHandlers(toolInstances);
    
    // Error handling
    this.server.onerror = (error) => console.error('[MCP Error]', error);
    process.on('SIGINT', async () => {
      await this.server.close();
      process.exit(0);
    });
  }

  private setupToolHandlers(tools: ToolInstances) {
    // List available tools
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: this.toolDefinitions,
    }));

    // Handle tool calls
    this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
      try {
        let result;
        switch (request.params.name) {
          // Work Item Tools
          case 'get_work_item':
            result = await tools.workItem.getWorkItem(request.params.arguments);
            break;
          case 'list_work_items':
            result = await tools.workItem.listWorkItems(request.params.arguments);
            break;
          
          // Board Tools
          case 'get_boards':
            result = await tools.board.getBoards(request.params.arguments);
            break;
          
          // Wiki Tools
          case 'get_wikis':
            result = await tools.wiki.getWikis(request.params.arguments);
            break;
          case 'get_wiki_page':
            result = await tools.wiki.getWikiPage(request.params.arguments);
            break;
          case 'create_wiki':
            result = await tools.wiki.createWiki(request.params.arguments);
            break;
          case 'update_wiki_page':
            result = await tools.wiki.updateWikiPage(request.params.arguments);
            break;
          
          // Project Tools
          case 'list_projects':
            result = await tools.project.listProjects(request.params.arguments);
            break;

          // Pipeline Tools
          case 'list_pipelines':
            result = await tools.pipeline.getPipelines(
              validateArgs(request.params.arguments, 'Pipeline arguments required')
            );
            break;
          case 'trigger_pipeline':
            result = await tools.pipeline.triggerPipeline(
              validateArgs(request.params.arguments, 'Pipeline trigger arguments required')
            );
            break;

          // Pull Request Tools
          case 'list_pull_requests':
            result = await tools.pullRequest.getPullRequests(
              validateArgs(request.params.arguments, 'Pull request list arguments required')
            );
            break;
          case 'get_pull_request':
            result = await tools.pullRequest.getPullRequest(
              validateArgs(request.params.arguments, 'Pull request ID required')
            );
            break;
          case 'create_pull_request':
            result = await tools.pullRequest.createPullRequest(
              validateArgs(request.params.arguments, 'Pull request creation arguments required')
            );
            break;
          case 'update_pull_request':
            result = await tools.pullRequest.updatePullRequest(
              validateArgs(request.params.arguments, 'Pull request update arguments required')
            );
            break;
          
          default:
            throw new McpError(
              ErrorCode.MethodNotFound,
              `Unknown tool: ${request.params.name}`
            );
        }

        // Ensure consistent response format
        const response = formatResponse(result);
        return {
          _meta: request.params._meta,
          ...response
        };
      } catch (error: unknown) {
        if (error instanceof McpError) throw error;
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        throw new McpError(
          ErrorCode.InternalError,
          `Azure DevOps API error: ${errorMessage}`
        );
      }
    });
  }

  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('Azure DevOps MCP server running on stdio');
  }
}

// Allow configuration through constructor or environment variables
const server = new AzureDevOpsServer();
server.run().catch(console.error);

```