#
tokens: 49695/50000 154/281 files (page 1/8)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 8. Use http://codebase.md/tiberriver256/azure-devops-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .clinerules
├── .env.example
├── .eslintrc.json
├── .github
│   ├── FUNDING.yml
│   ├── release-please-config.json
│   ├── release-please-manifest.json
│   └── workflows
│       ├── main.yml
│       └── release-please.yml
├── .gitignore
├── .husky
│   ├── commit-msg
│   └── pre-commit
├── .kilocode
│   └── mcp.json
├── .prettierrc
├── .vscode
│   └── settings.json
├── CHANGELOG.md
├── commitlint.config.js
├── CONTRIBUTING.md
├── create_branch.sh
├── docs
│   ├── authentication.md
│   ├── azure-identity-authentication.md
│   ├── ci-setup.md
│   ├── examples
│   │   ├── azure-cli-authentication.env
│   │   ├── azure-identity-authentication.env
│   │   ├── pat-authentication.env
│   │   └── README.md
│   ├── testing
│   │   ├── README.md
│   │   └── setup.md
│   └── tools
│       ├── core-navigation.md
│       ├── organizations.md
│       ├── pipelines.md
│       ├── projects.md
│       ├── pull-requests.md
│       ├── README.md
│       ├── repositories.md
│       ├── resources.md
│       ├── search.md
│       ├── user-tools.md
│       ├── wiki.md
│       └── work-items.md
├── finish_task.sh
├── jest.e2e.config.js
├── jest.int.config.js
├── jest.unit.config.js
├── LICENSE
├── memory
│   └── tasks_memory_2025-05-26T16-18-03.json
├── package-lock.json
├── package.json
├── project-management
│   ├── planning
│   │   ├── architecture-guide.md
│   │   ├── azure-identity-authentication-design.md
│   │   ├── project-plan.md
│   │   ├── project-structure.md
│   │   ├── tech-stack.md
│   │   └── the-dream-team.md
│   ├── startup.xml
│   ├── tdd-cycle.xml
│   └── troubleshooter.xml
├── README.md
├── setup_env.sh
├── shrimp-rules.md
├── src
│   ├── clients
│   │   └── azure-devops.ts
│   ├── features
│   │   ├── organizations
│   │   │   ├── __test__
│   │   │   │   └── test-helpers.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-organizations
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── schemas.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── types.ts
│   │   ├── pipelines
│   │   │   ├── get-pipeline
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-pipelines
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── tool-definitions.ts
│   │   │   ├── trigger-pipeline
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   └── types.ts
│   │   ├── projects
│   │   │   ├── __test__
│   │   │   │   └── test-helpers.ts
│   │   │   ├── get-project
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-project-details
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-projects
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── schemas.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── types.ts
│   │   ├── pull-requests
│   │   │   ├── add-pull-request-comment
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   └── index.ts
│   │   │   ├── create-pull-request
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-pull-request-comments
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-pull-requests
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── schemas.ts
│   │   │   ├── tool-definitions.ts
│   │   │   ├── types.ts
│   │   │   └── update-pull-request
│   │   │       ├── feature.spec.int.ts
│   │   │       ├── feature.spec.unit.ts
│   │   │       ├── feature.ts
│   │   │       └── index.ts
│   │   ├── repositories
│   │   │   ├── __test__
│   │   │   │   └── test-helpers.ts
│   │   │   ├── get-all-repositories-tree
│   │   │   │   ├── __snapshots__
│   │   │   │   │   └── feature.spec.unit.ts.snap
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-file-content
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-repository
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-repository-details
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-repositories
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── schemas.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── types.ts
│   │   ├── search
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── schemas.ts
│   │   │   ├── search-code
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   └── index.ts
│   │   │   ├── search-wiki
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   └── index.ts
│   │   │   ├── search-work-items
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   └── index.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── types.ts
│   │   ├── users
│   │   │   ├── get-me
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── schemas.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── types.ts
│   │   ├── wikis
│   │   │   ├── create-wiki
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── create-wiki-page
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-wiki-page
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── get-wikis
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── index.spec.unit.ts
│   │   │   ├── index.ts
│   │   │   ├── list-wiki-pages
│   │   │   │   ├── feature.spec.int.ts
│   │   │   │   ├── feature.spec.unit.ts
│   │   │   │   ├── feature.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── schema.ts
│   │   │   ├── tool-definitions.ts
│   │   │   └── update-wiki-page
│   │   │       ├── feature.spec.int.ts
│   │   │       ├── feature.ts
│   │   │       ├── index.ts
│   │   │       └── schema.ts
│   │   └── work-items
│   │       ├── __test__
│   │       │   ├── fixtures.ts
│   │       │   ├── test-helpers.ts
│   │       │   └── test-utils.ts
│   │       ├── create-work-item
│   │       │   ├── feature.spec.int.ts
│   │       │   ├── feature.spec.unit.ts
│   │       │   ├── feature.ts
│   │       │   ├── index.ts
│   │       │   └── schema.ts
│   │       ├── get-work-item
│   │       │   ├── feature.spec.int.ts
│   │       │   ├── feature.spec.unit.ts
│   │       │   ├── feature.ts
│   │       │   ├── index.ts
│   │       │   └── schema.ts
│   │       ├── index.spec.unit.ts
│   │       ├── index.ts
│   │       ├── list-work-items
│   │       │   ├── feature.spec.int.ts
│   │       │   ├── feature.spec.unit.ts
│   │       │   ├── feature.ts
│   │       │   ├── index.ts
│   │       │   └── schema.ts
│   │       ├── manage-work-item-link
│   │       │   ├── feature.spec.int.ts
│   │       │   ├── feature.spec.unit.ts
│   │       │   ├── feature.ts
│   │       │   ├── index.ts
│   │       │   └── schema.ts
│   │       ├── schemas.ts
│   │       ├── tool-definitions.ts
│   │       ├── types.ts
│   │       └── update-work-item
│   │           ├── feature.spec.int.ts
│   │           ├── feature.spec.unit.ts
│   │           ├── feature.ts
│   │           ├── index.ts
│   │           └── schema.ts
│   ├── index.spec.unit.ts
│   ├── index.ts
│   ├── server.spec.e2e.ts
│   ├── server.ts
│   ├── shared
│   │   ├── api
│   │   │   ├── client.ts
│   │   │   └── index.ts
│   │   ├── auth
│   │   │   ├── auth-factory.ts
│   │   │   ├── client-factory.ts
│   │   │   └── index.ts
│   │   ├── config
│   │   │   ├── index.ts
│   │   │   └── version.ts
│   │   ├── enums
│   │   │   ├── index.spec.unit.ts
│   │   │   └── index.ts
│   │   ├── errors
│   │   │   ├── azure-devops-errors.ts
│   │   │   ├── handle-request-error.ts
│   │   │   └── index.ts
│   │   ├── test
│   │   │   └── test-helpers.ts
│   │   └── types
│   │       ├── config.ts
│   │       ├── index.ts
│   │       ├── request-handler.ts
│   │       └── tool-definition.ts
│   └── utils
│       ├── environment.spec.unit.ts
│       └── environment.ts
├── tasks.json
├── tests
│   └── setup.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
 1 | {
 2 |   "semi": true,
 3 |   "trailingComma": "all",
 4 |   "singleQuote": true,
 5 |   "printWidth": 80,
 6 |   "tabWidth": 2,
 7 |   "useTabs": false,
 8 |   "endOfLine": "lf"
 9 | }
10 | 
```

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

```
 1 | # Dependencies
 2 | node_modules/
 3 | npm-debug.log*
 4 | yarn-debug.log*
 5 | yarn-error.log*
 6 | 
 7 | # TypeScript
 8 | dist/
 9 | *.tsbuildinfo
10 | 
11 | # Environment variables
12 | .env
13 | .env.local
14 | .env.*.local
15 | 
16 | # IDE
17 | .vscode/*
18 | !.vscode/extensions.json
19 | !.vscode/settings.json
20 | .idea/
21 | *.swp
22 | *.swo
23 | 
24 | # OS
25 | .DS_Store
26 | Thumbs.db
27 | 
28 | # Test coverage
29 | coverage/
30 | 
31 | # Logs
32 | logs/
33 | *.log 
```

--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "parser": "@typescript-eslint/parser",
 3 |   "plugins": ["@typescript-eslint"],
 4 |   "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
 5 |   "env": {
 6 |     "node": true,
 7 |     "es6": true,
 8 |     "jest": true
 9 |   },
10 |   "rules": {
11 |     "@typescript-eslint/explicit-function-return-type": "off",
12 |     "@typescript-eslint/no-explicit-any": "warn",
13 |     "@typescript-eslint/no-unused-vars": [
14 |       "error",
15 |       { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
16 |     ]
17 |   },
18 |   "overrides": [
19 |     {
20 |       "files": ["**/*.spec.unit.ts", "tests/**/*.ts"],
21 |       "rules": {
22 |         "@typescript-eslint/no-explicit-any": "off"
23 |       }
24 |     }
25 |   ],
26 |   "parserOptions": {
27 |     "ecmaVersion": 2020,
28 |     "sourceType": "module"
29 |   },
30 |   "ignorePatterns": ["dist/**/*", "project-management/**/*"]
31 | }
32 | 
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
 1 | # Azure DevOps MCP Server - Environment Variables
 2 | 
 3 | # Azure DevOps Organization URL (required)
 4 | # e.g., https://dev.azure.com/your-organization
 5 | AZURE_DEVOPS_ORG_URL=https://dev.azure.com/your-organization
 6 | 
 7 | # Authentication Method (optional, defaults to 'azure-identity')
 8 | # Supported values: 'pat', 'azure-identity', 'azure-cli'
 9 | # - 'pat': Personal Access Token authentication
10 | # - 'azure-identity': Azure Identity authentication (DefaultAzureCredential)
11 | # - 'azure-cli': Azure CLI authentication (AzureCliCredential)
12 | AZURE_DEVOPS_AUTH_METHOD=azure-identity
13 | 
14 | # Azure DevOps Personal Access Token (required for PAT authentication)
15 | # Create one at: https://dev.azure.com/your-organization/_usersSettings/tokens
16 | # Required scopes: Code (Read & Write), Work Items (Read & Write), Build (Read & Execute),
17 | # Project and Team (Read), Graph (Read), Release (Read & Execute)
18 | AZURE_DEVOPS_PAT=your-personal-access-token
19 | 
20 | # Default Project to use when not specified (optional)
21 | AZURE_DEVOPS_DEFAULT_PROJECT=your-default-project
22 | 
23 | # API Version to use (optional, defaults to latest)
24 | # AZURE_DEVOPS_API_VERSION=6.0
25 | 
26 | # Note: This server uses stdio for communication, not HTTP
27 | # The following variables are not used by the server but might be used by scripts:
28 | 
29 | # Logging Level (debug, info, warn, error)
30 | LOG_LEVEL=info
31 | 
32 | # Azure Identity Credentials (for service principal authentication)
33 | # Required only when using azure-identity with service principals
34 | # AZURE_TENANT_ID=your-tenant-id
35 | # AZURE_CLIENT_ID=your-client-id
36 | # AZURE_CLIENT_SECRET=your-client-secret 
```

--------------------------------------------------------------------------------
/.clinerules:
--------------------------------------------------------------------------------

```
  1 | # MCP Server Development Protocol
  2 | 
  3 | ⚠️ CRITICAL: DO NOT USE attempt_completion BEFORE TESTING ⚠️
  4 | 
  5 | ## Commit Rules
  6 | 
  7 | ⚠️ MANDATORY: All git commits MUST adhere to the Conventional Commits specification (https://www.conventionalcommits.org/). Example: 'feat: implement user login' or 'fix: resolve calculation error'.
  8 | 
  9 | ⚠️ RECOMMENDED: Use 'npm run commit' to create commit messages interactively, ensuring compliance.
 10 | 
 11 | ## Step 1: Planning (PLAN MODE)
 12 | - What problem does this tool solve?
 13 | - What API/service will it use?
 14 | - What are the authentication requirements?
 15 |   □ Standard API key
 16 |   □ OAuth (requires separate setup script)
 17 |   □ Other credentials
 18 | 
 19 | ## Step 2: Implementation (ACT MODE)
 20 | 1. Bootstrap
 21 |    - For web services, JavaScript integration, or Node.js environments:
 22 |      ```bash
 23 |      npx @modelcontextprotocol/create-server my-server
 24 |      cd my-server
 25 |      npm install
 26 |      ```
 27 |    - For data science, ML workflows, or Python environments:
 28 |      ```bash
 29 |      pip install mcp
 30 |      # Or with uv (recommended)
 31 |      uv add "mcp[cli]"
 32 |      ```
 33 | 
 34 | 2. Core Implementation
 35 |    - Use MCP SDK
 36 |    - Implement comprehensive logging
 37 |      - TypeScript (for web/JS projects):
 38 |        ```typescript
 39 |        console.error('[Setup] Initializing server...');
 40 |        console.error('[API] Request to endpoint:', endpoint);
 41 |        console.error('[Error] Failed with:', error);
 42 |        ```
 43 |      - Python (for data science/ML projects):
 44 |        ```python
 45 |        import logging
 46 |        logging.error('[Setup] Initializing server...')
 47 |        logging.error(f'[API] Request to endpoint: {endpoint}')
 48 |        logging.error(f'[Error] Failed with: {str(error)}')
 49 |        ```
 50 |    - Add type definitions
 51 |    - Handle errors with context
 52 |    - Implement rate limiting if needed
 53 | 
 54 | 3. Configuration
 55 |    - Get credentials from user if needed
 56 |    - Add to MCP settings:
 57 |      - For TypeScript projects:
 58 |        ```json
 59 |        {
 60 |          "mcpServers": {
 61 |            "my-server": {
 62 |              "command": "node",
 63 |              "args": ["path/to/build/index.js"],
 64 |              "env": {
 65 |                "API_KEY": "key"
 66 |              },
 67 |              "disabled": false,
 68 |              "autoApprove": []
 69 |            }
 70 |          }
 71 |        }
 72 |        ```
 73 |      - For Python projects:
 74 |        ```bash
 75 |        # Directly with command line
 76 |        mcp install server.py -v API_KEY=key
 77 |        
 78 |        # Or in settings.json
 79 |        {
 80 |          "mcpServers": {
 81 |            "my-server": {
 82 |              "command": "python",
 83 |              "args": ["server.py"],
 84 |              "env": {
 85 |                "API_KEY": "key"
 86 |              },
 87 |              "disabled": false,
 88 |              "autoApprove": []
 89 |            }
 90 |          }
 91 |        }
 92 |        ```
 93 | 
 94 | ## Step 3: Testing (BLOCKER ⛔️)
 95 | 
 96 | <thinking>
 97 | BEFORE using attempt_completion, I MUST verify:
 98 | □ Have I tested EVERY tool?
 99 | □ Have I confirmed success from the user for each test?
100 | □ Have I documented the test results?
101 | 
102 | If ANY answer is "no", I MUST NOT use attempt_completion.
103 | </thinking>
104 | 
105 | 1. Test Each Tool (REQUIRED)
106 |    □ Test each tool with valid inputs
107 |    □ Verify output format is correct
108 |    ⚠️ DO NOT PROCEED UNTIL ALL TOOLS TESTED
109 | 
110 | ## Step 4: Completion
111 | ❗ STOP AND VERIFY:
112 | □ Every tool has been tested with valid inputs
113 | □ Output format is correct for each tool
114 | 
115 | Only after ALL tools have been tested can attempt_completion be used.
116 | 
117 | ## Key Requirements
118 | - ✓ Must use MCP SDK
119 | - ✓ Must have comprehensive logging
120 | - ✓ Must test each tool individually
121 | - ✓ Must handle errors gracefully
122 | - ⛔️ NEVER skip testing before completion
```

--------------------------------------------------------------------------------
/docs/examples/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Authentication Examples
 2 | 
 3 | This directory contains example `.env` files for different authentication methods supported by the Azure DevOps MCP Server.
 4 | 
 5 | ## Available Examples
 6 | 
 7 | 1. **[pat-authentication.env](./pat-authentication.env)** - Example configuration for Personal Access Token (PAT) authentication
 8 | 2. **[azure-identity-authentication.env](./azure-identity-authentication.env)** - Example configuration for Azure Identity (DefaultAzureCredential) authentication
 9 | 3. **[azure-cli-authentication.env](./azure-cli-authentication.env)** - Example configuration for Azure CLI authentication
10 | 
11 | ## How to Use These Examples
12 | 
13 | 1. Choose the authentication method that best suits your needs
14 | 2. Copy the corresponding example file to the root of your project as `.env`
15 | 3. Replace the placeholder values with your actual values
16 | 4. Start the Azure DevOps MCP Server
17 | 
18 | For example:
19 | 
20 | ```bash
21 | # Copy the PAT authentication example
22 | cp docs/examples/pat-authentication.env .env
23 | 
24 | # Edit the .env file with your values
25 | nano .env
26 | 
27 | # Start the server
28 | npm start
29 | ```
30 | 
31 | ## Additional Resources
32 | 
33 | For more detailed information about authentication methods, setup instructions, and troubleshooting, refer to the [Authentication Guide](../authentication.md).
34 | 
```

--------------------------------------------------------------------------------
/docs/tools/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Azure DevOps MCP Server Tools Documentation
 2 | 
 3 | This directory contains documentation for all tools available in the Azure DevOps MCP server. Each tool is documented with examples, parameters, response formats, and error handling information.
 4 | 
 5 | ## Navigation
 6 | 
 7 | - [Core Navigation Tools](./core-navigation.md) - Overview of tools for navigating Azure DevOps resources
 8 |   - [Organizations](./organizations.md) - Tools for working with organizations
 9 |   - [Projects](./projects.md) - Tools for working with projects
10 |   - [Repositories](./repositories.md) - Tools for working with Git repositories
11 |   - [Pull Requests](./pull-requests.md) - Tools for working with pull requests
12 |   - [Work Items](./work-items.md) - Tools for working with work items
13 |   - [Pipelines](./pipelines.md) - Tools for working with pipelines
14 | - [Resource URIs](./resources.md) - Documentation for accessing repository content via resource URIs
15 | 
16 | ## Tools by Category
17 | 
18 | ### Organization Tools
19 | 
20 | - [`list_organizations`](./organizations.md#list_organizations) - List all Azure DevOps organizations accessible to the user
21 | 
22 | ### Project Tools
23 | 
24 | - [`list_projects`](./projects.md#list_projects) - List all projects in the organization
25 | - [`get_project`](./projects.md#get_project) - Get details of a specific project
26 | 
27 | ### Repository Tools
28 | 
29 | - [`list_repositories`](./repositories.md#list_repositories) - List all repositories in a project
30 | - [`get_repository`](./repositories.md#get_repository) - Get details of a specific repository
31 | - [`get_repository_details`](./repositories.md#get_repository_details) - Get detailed information about a repository
32 | - [`get_file_content`](./repositories.md#get_file_content) - Get content of a file or directory from a repository
33 | 
34 | ### Pull Request Tools
35 | 
36 | - [`create_pull_request`](./pull-requests.md#create_pull_request) - Create a new pull request
37 | - [`list_pull_requests`](./pull-requests.md#list_pull_requests) - List pull requests in a repository
38 | - [`add_pull_request_comment`](./pull-requests.md#add_pull_request_comment) - Add a comment to a pull request
39 | - [`get_pull_request_comments`](./pull-requests.md#get_pull_request_comments) - Get comments from a pull request
40 | - [`update_pull_request`](./pull-requests.md#update_pull_request) - Update an existing pull request (title, description, status, draft state, reviewers, work items)
41 | 
42 | ### Work Item Tools
43 | 
44 | - [`get_work_item`](./work-items.md#get_work_item) - Retrieve a work item by ID
45 | - [`create_work_item`](./work-items.md#create_work_item) - Create a new work item
46 | - [`list_work_items`](./work-items.md#list_work_items) - List work items in a project
47 | 
48 | ### Pipeline Tools
49 | 
50 | - [`list_pipelines`](./pipelines.md#list_pipelines) - List all pipelines in a project
51 | - [`get_pipeline`](./pipelines.md#get_pipeline) - Get details of a specific pipeline
52 | 
53 | ## Tool Structure
54 | 
55 | Each tool documentation follows a consistent structure:
56 | 
57 | 1. **Description**: Brief explanation of what the tool does
58 | 2. **Parameters**: Required and optional parameters with explanations
59 | 3. **Response**: Expected response format with examples
60 | 4. **Error Handling**: Potential errors and how they're handled
61 | 5. **Example Usage**: Code examples showing how to use the tool
62 | 6. **Implementation Details**: Technical details about how the tool works
63 | 
64 | ## Examples
65 | 
66 | Examples of using multiple tools together can be found in the [Core Navigation Tools](./core-navigation.md#common-use-cases) documentation.
67 | 
```

--------------------------------------------------------------------------------
/docs/testing/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Testing Trophy Approach
  2 | 
  3 | ## Overview
  4 | 
  5 | This project follows the Testing Trophy approach advocated by Kent C. Dodds instead of the traditional Testing Pyramid. The Testing Trophy emphasizes tests that provide higher confidence with less maintenance cost, focusing on how users actually interact with our software.
  6 | 
  7 | ![Testing Trophy Diagram](https://res.cloudinary.com/kentcdodds-com/image/upload/f_auto,q_auto,w_1600/v1625032020/kentcdodds.com/blog/the-testing-trophy-and-testing-classifications/trophy_wx9aen.png)
  8 | 
  9 | ## Key Principles
 10 | 
 11 | 1. **"The more your tests resemble the way your software is used, the more confidence they can give you."** - Kent C. Dodds
 12 | 2. Focus on testing behavior and interfaces rather than implementation details
 13 | 3. Maximize return on investment where "return" is confidence and "investment" is time
 14 | 4. Use arrange/act/assert pattern for all tests
 15 | 5. Co-locate tests with the code they test following Feature Sliced Design
 16 | 
 17 | ## Test Types
 18 | 
 19 | ### Static Analysis (The Base)
 20 | 
 21 | - TypeScript for type checking
 22 | - ESLint for code quality and consistency
 23 | - Runtime type checking with Zod
 24 | - Formatter (Prettier)
 25 | 
 26 | These tools catch many issues before tests are even run and provide immediate feedback during development.
 27 | 
 28 | ### Unit Tests (Small Layer)
 29 | 
 30 | - Located in `*.spec.unit.ts` files
 31 | - Co-located with the code they test
 32 | - Focus on testing complex business logic in isolation
 33 | - Minimal mocking where necessary
 34 | - Run with `npm run test:unit`
 35 | 
 36 | Unit tests should be used sparingly for complex logic that requires isolated testing. We don't aim for 100% coverage with unit tests.
 37 | 
 38 | ### Integration Tests (Main Focus)
 39 | 
 40 | - Located in `*.spec.int.ts` files
 41 | - Co-located with the features they test
 42 | - Test how modules work together
 43 | - Focus on testing behavior, not implementation
 44 | - Run with `npm run test:int`
 45 | 
 46 | These provide the bulk of our test coverage and confidence. They verify that different parts of the system work together correctly.
 47 | 
 48 | ### End-to-End Tests (Small Cap)
 49 | 
 50 | - Located in `*.spec.e2e.ts` files
 51 | - **Only exists at the server level** (e.g., `server.spec.e2e.ts`) where they use the MCP client
 52 | - Test complete user flows across the entire application
 53 | - Provide the highest confidence but are slower and more costly to maintain
 54 | - Run with `npm run test:e2e`
 55 | 
 56 | End-to-end tests should only be created for critical user journeys that span the entire application. They should use the MCP client from `@modelcontextprotocol/sdk` to test the server as a black box, similar to how real users would interact with it.
 57 | 
 58 | For testing interactions with external APIs like Azure DevOps, use integration tests (`*.spec.int.ts`) instead, which are co-located with the feature implementations.
 59 | 
 60 | ## Test File Naming Convention
 61 | 
 62 | - `*.spec.unit.ts` - For minimal unit tests (essential logic only)
 63 | - `*.spec.int.ts` - For integration tests (main focus)
 64 | - `*.spec.e2e.ts` - For end-to-end tests
 65 | 
 66 | ## Test Location
 67 | 
 68 | We co-locate unit and integration tests with the code they're testing following Feature Sliced Design principles:
 69 | 
 70 | ```
 71 | src/
 72 |   features/
 73 |     feature-name/
 74 |       feature.ts
 75 |       feature.spec.unit.ts   # Unit tests
 76 |       feature.spec.int.ts    # Integration tests
 77 | ```
 78 | 
 79 | E2E tests are only located at the server level since they test the full application:
 80 | 
 81 | ```
 82 | src/
 83 |   server.ts
 84 |   server.spec.e2e.ts   # E2E tests using the MCP client
 85 | ```
 86 | 
 87 | This way, tests stay close to the code they're testing, making it easier to:
 88 | - Find tests when working on a feature
 89 | - Understand the relationship between tests and code
 90 | - Refactor code and tests together
 91 | - Maintain consistency between implementations and tests
 92 | 
 93 | ## The Arrange/Act/Assert Pattern
 94 | 
 95 | All tests should follow the Arrange/Act/Assert pattern:
 96 | 
 97 | ```typescript
 98 | test('should do something', () => {
 99 |   // Arrange - set up the test
100 |   const input = 'something';
101 |   
102 |   // Act - perform the action being tested
103 |   const result = doSomething(input);
104 |   
105 |   // Assert - check that the action had the expected result
106 |   expect(result).toBe('expected output');
107 | });
108 | ```
109 | 
110 | ## Running Tests
111 | 
112 | - Run all tests: `npm test`
113 | - Run unit tests: `npm run test:unit`
114 | - Run integration tests: `npm run test:int`
115 | - Run E2E tests: `npm run test:e2e`
116 | 
117 | ## CI/CD Integration
118 | 
119 | Our CI/CD pipeline runs all test levels to ensure code quality:
120 | 
121 | 1. Static analysis with TypeScript and ESLint
122 | 2. Unit tests
123 | 3. Integration tests
124 | 4. End-to-end tests
125 | 
126 | ## Best Practices
127 | 
128 | 1. Focus on integration tests for the bulk of your test coverage
129 | 2. Write unit tests only for complex business logic
130 | 3. Avoid testing implementation details
131 | 4. Use real dependencies when possible rather than mocks
132 | 5. Keep E2E tests focused on critical user flows
133 | 6. Use the arrange/act/assert pattern consistently
134 | 7. Co-locate tests with the code they're testing
135 | 
136 | ## References
137 | 
138 | - [The Testing Trophy and Testing Classifications](https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications) by Kent C. Dodds
139 | - [Testing of Microservices](https://engineering.atspotify.com/2018/01/testing-of-microservices/) (Testing Honeycomb approach) by Spotify Engineering
140 | - [Feature Sliced Design](https://feature-sliced.design/) for co-location of tests with feature implementations 
```

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

```markdown
  1 | # ℹ️ DISCUSSION: [Microsoft launched an official ADO MCP Server! 🎉🎉🎉](https://github.com/Tiberriver256/mcp-server-azure-devops/discussions/237)
  2 | 
  3 | # Azure DevOps MCP Server
  4 | 
  5 | A Model Context Protocol (MCP) server implementation for Azure DevOps, allowing AI assistants to interact with Azure DevOps APIs through a standardized protocol.
  6 | 
  7 | ## Overview
  8 | 
  9 | This server implements the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) for Azure DevOps, enabling AI assistants like Claude to interact with Azure DevOps resources securely. The server acts as a bridge between AI models and Azure DevOps APIs, providing a standardized way to:
 10 | 
 11 | - Access and manage projects, work items, repositories, and more
 12 | - Create and update work items, branches, and pull requests
 13 | - Execute common DevOps workflows through natural language
 14 | - Access repository content via standardized resource URIs
 15 | - Safely authenticate and interact with Azure DevOps resources
 16 | 
 17 | ## Server Structure
 18 | 
 19 | The server is structured around the Model Context Protocol (MCP) for communicating with AI assistants. It provides tools for interacting with Azure DevOps resources including:
 20 | 
 21 | - Projects
 22 | - Work Items
 23 | - Repositories
 24 | - Pull Requests
 25 | - Branches
 26 | - Pipelines
 27 | 
 28 | ### Core Components
 29 | 
 30 | - **AzureDevOpsServer**: Main server class that initializes the MCP server and registers tools
 31 | - **Feature Modules**: Organized by feature area (work-items, projects, repositories, etc.)
 32 | - **Request Handlers**: Each feature module provides request identification and handling functions
 33 | - **Tool Handlers**: Modular functions for each Azure DevOps operation
 34 | - **Configuration**: Environment-based configuration for organization URL, PAT, etc.
 35 | 
 36 | The server uses a feature-based architecture where each feature area (like work-items, projects, repositories) is encapsulated in its own module. This makes the codebase more maintainable and easier to extend with new features.
 37 | 
 38 | ## Getting Started
 39 | 
 40 | ### Prerequisites
 41 | 
 42 | - Node.js (v16+)
 43 | - npm or yarn
 44 | - Azure DevOps account with appropriate access
 45 | - Authentication credentials (see [Authentication Guide](docs/authentication.md) for details):
 46 |   - Personal Access Token (PAT), or
 47 |   - Azure Identity credentials, or
 48 |   - Azure CLI login
 49 | 
 50 | ### Running with NPX
 51 | 
 52 | ### Usage with Claude Desktop/Cursor AI
 53 | 
 54 | To integrate with Claude Desktop or Cursor AI, add one of the following configurations to your configuration file.
 55 | 
 56 | #### Azure Identity Authentication
 57 | 
 58 | Be sure you are logged in to Azure CLI with `az login` then add the following:
 59 | 
 60 | ```json
 61 | {
 62 |   "mcpServers": {
 63 |     "azureDevOps": {
 64 |       "command": "npx",
 65 |       "args": ["-y", "@tiberriver256/mcp-server-azure-devops"],
 66 |       "env": {
 67 |         "AZURE_DEVOPS_ORG_URL": "https://dev.azure.com/your-organization",
 68 |         "AZURE_DEVOPS_AUTH_METHOD": "azure-identity",
 69 |         "AZURE_DEVOPS_DEFAULT_PROJECT": "your-project-name"
 70 |       }
 71 |     }
 72 |   }
 73 | }
 74 | ```
 75 | 
 76 | #### Personal Access Token (PAT) Authentication
 77 | 
 78 | ```json
 79 | {
 80 |   "mcpServers": {
 81 |     "azureDevOps": {
 82 |       "command": "npx",
 83 |       "args": ["-y", "@tiberriver256/mcp-server-azure-devops"],
 84 |       "env": {
 85 |         "AZURE_DEVOPS_ORG_URL": "https://dev.azure.com/your-organization",
 86 |         "AZURE_DEVOPS_AUTH_METHOD": "pat",
 87 |         "AZURE_DEVOPS_PAT": "<YOUR_PAT>",
 88 |         "AZURE_DEVOPS_DEFAULT_PROJECT": "your-project-name"
 89 |       }
 90 |     }
 91 |   }
 92 | }
 93 | ```
 94 | 
 95 | For detailed configuration instructions and more authentication options, see the [Authentication Guide](docs/authentication.md).
 96 | 
 97 | ## Authentication Methods
 98 | 
 99 | This server supports multiple authentication methods for connecting to Azure DevOps APIs. For detailed setup instructions, configuration examples, and troubleshooting tips, see the [Authentication Guide](docs/authentication.md).
100 | 
101 | ### Supported Authentication Methods
102 | 
103 | 1. **Personal Access Token (PAT)** - Simple token-based authentication
104 | 2. **Azure Identity (DefaultAzureCredential)** - Flexible authentication using the Azure Identity SDK
105 | 3. **Azure CLI** - Authentication using your Azure CLI login
106 | 
107 | Example configuration files for each authentication method are available in the [examples directory](docs/examples/).
108 | 
109 | ## Environment Variables
110 | 
111 | For a complete list of environment variables and their descriptions, see the [Authentication Guide](docs/authentication.md#configuration-reference).
112 | 
113 | Key environment variables include:
114 | 
115 | | Variable                       | Description                                                                        | Required                     | Default          |
116 | | ------------------------------ | ---------------------------------------------------------------------------------- | ---------------------------- | ---------------- |
117 | | `AZURE_DEVOPS_AUTH_METHOD`     | Authentication method (`pat`, `azure-identity`, or `azure-cli`) - case-insensitive | No                           | `azure-identity` |
118 | | `AZURE_DEVOPS_ORG_URL`         | Full URL to your Azure DevOps organization                                         | Yes                          | -                |
119 | | `AZURE_DEVOPS_PAT`             | Personal Access Token (for PAT auth)                                               | Only with PAT auth           | -                |
120 | | `AZURE_DEVOPS_DEFAULT_PROJECT` | Default project if none specified                                                  | No                           | -                |
121 | | `AZURE_DEVOPS_API_VERSION`     | API version to use                                                                 | No                           | Latest           |
122 | | `AZURE_TENANT_ID`              | Azure AD tenant ID (for service principals)                                        | Only with service principals | -                |
123 | | `AZURE_CLIENT_ID`              | Azure AD application ID (for service principals)                                   | Only with service principals | -                |
124 | | `AZURE_CLIENT_SECRET`          | Azure AD client secret (for service principals)                                    | Only with service principals | -                |
125 | | `LOG_LEVEL`                    | Logging level (debug, info, warn, error)                                           | No                           | info             |
126 | 
127 | ## Troubleshooting Authentication
128 | 
129 | For detailed troubleshooting information for each authentication method, see the [Authentication Guide](docs/authentication.md#troubleshooting-authentication-issues).
130 | 
131 | Common issues include:
132 | 
133 | - Invalid or expired credentials
134 | - Insufficient permissions
135 | - Network connectivity problems
136 | - Configuration errors
137 | 
138 | ## Authentication Implementation Details
139 | 
140 | For technical details about how authentication is implemented in the Azure DevOps MCP server, see the [Authentication Guide](docs/authentication.md) and the source code in the `src/auth` directory.
141 | 
142 | ## Available Tools
143 | 
144 | The Azure DevOps MCP server provides a variety of tools for interacting with Azure DevOps resources. For detailed documentation on each tool, please refer to the corresponding documentation.
145 | 
146 | ### User Tools
147 | 
148 | - `get_me`: Get details of the authenticated user (id, displayName, email)
149 | 
150 | ### Organization Tools
151 | 
152 | - `list_organizations`: List all accessible organizations
153 | 
154 | ### Project Tools
155 | 
156 | - `list_projects`: List all projects in an organization
157 | - `get_project`: Get details of a specific project
158 | - `get_project_details`: Get comprehensive details of a project including process, work item types, and teams
159 | 
160 | ### Repository Tools
161 | 
162 | - `list_repositories`: List all repositories in a project
163 | - `get_repository`: Get details of a specific repository
164 | - `get_repository_details`: Get detailed information about a repository including statistics and refs
165 | - `get_file_content`: Get content of a file or directory from a repository
166 | 
167 | ### Work Item Tools
168 | 
169 | - `get_work_item`: Retrieve a work item by ID
170 | - `create_work_item`: Create a new work item
171 | - `update_work_item`: Update an existing work item
172 | - `list_work_items`: List work items in a project
173 | - `manage_work_item_link`: Add, remove, or update links between work items
174 | 
175 | ### Search Tools
176 | 
177 | - `search_code`: Search for code across repositories in a project
178 | - `search_wiki`: Search for content across wiki pages in a project
179 | - `search_work_items`: Search for work items across projects in Azure DevOps
180 | 
181 | ### Pipelines Tools
182 | 
183 | - `list_pipelines`: List pipelines in a project
184 | - `get_pipeline`: Get details of a specific pipeline
185 | - `trigger_pipeline`: Trigger a pipeline run with customizable parameters
186 | 
187 | ### Wiki Tools
188 | 
189 | - `get_wikis`: List all wikis in a project
190 | - `get_wiki_page`: Get content of a specific wiki page as plain text
191 | 
192 | ### Pull Request Tools
193 | 
194 | - [`create_pull_request`](docs/tools/pull-requests.md#create_pull_request) - Create a new pull request
195 | - [`list_pull_requests`](docs/tools/pull-requests.md#list_pull_requests) - List pull requests in a repository
196 | - [`add_pull_request_comment`](docs/tools/pull-requests.md#add_pull_request_comment) - Add a comment to a pull request
197 | - [`get_pull_request_comments`](docs/tools/pull-requests.md#get_pull_request_comments) - Get comments from a pull request
198 | - [`update_pull_request`](docs/tools/pull-requests.md#update_pull_request) - Update an existing pull request (title, description, status, draft state, reviewers, work items)
199 | 
200 | For comprehensive documentation on all tools, see the [Tools Documentation](docs/tools/).
201 | 
202 | ## Contributing
203 | 
204 | Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
205 | 
206 | ## Star History
207 | 
208 | [![Star History Chart](https://api.star-history.com/svg?repos=tiberriver256/mcp-server-azure-devops&type=Date)](https://www.star-history.com/#tiberriver256/mcp-server-azure-devops&Date)
209 | 
210 | ## License
211 | 
212 | MIT
213 | 
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Contributing to Azure DevOps MCP Server
  2 | 
  3 | We love your input! We want to make contributing to Azure DevOps MCP Server as easy and transparent as possible, whether it's:
  4 | 
  5 | - Reporting a bug
  6 | - Discussing the current state of the code
  7 | - Submitting a fix
  8 | - Proposing new features
  9 | - Becoming a maintainer
 10 | 
 11 | ## Development Process
 12 | 
 13 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests.
 14 | 
 15 | ## Pull Requests
 16 | 
 17 | 1. Fork the repository
 18 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
 19 | 3. Commit your changes (see Commit Message Guidelines below)
 20 | 4. Push to the branch (`git push origin feature/amazing-feature`)
 21 | 5. Open a Pull Request
 22 | 
 23 | ## Development Practices
 24 | 
 25 | This project follows Test-Driven Development practices. Each new feature should:
 26 | 
 27 | 1. Begin with a failing test
 28 | 2. Implement the minimal code to make the test pass
 29 | 3. Refactor while keeping tests green
 30 | 
 31 | ## Project Structure
 32 | 
 33 | The server is organized into feature-specific modules:
 34 | 
 35 | ```
 36 | src/
 37 |  └── features/
 38 |      ├── organizations/
 39 |      ├── pipelines/
 40 |      ├── projects/
 41 |      ├── pull-requests/
 42 |      ├── repositories/
 43 |      ├── search/
 44 |      ├── users/
 45 |      ├── wikis/
 46 |      └── work-items/
 47 | ```
 48 | 
 49 | Each feature module:
 50 | 1. Exports its schemas, types, and individual tool functions
 51 | 2. Provides an `is<FeatureName>Request` function to identify if a request is for this feature
 52 | 3. Provides a `handle<FeatureName>Request` function to handle requests for this feature
 53 | 
 54 | ### Adding a New Feature or Tool
 55 | 
 56 | When adding a new feature or tool:
 57 | 1. Create a new directory under `src/features/` if needed
 58 | 2. Implement your tool functions
 59 | 3. Update the feature's `index.ts` to export your functions and add them to the request handlers
 60 | 4. No changes to server.ts should be needed!
 61 | 
 62 | ## Testing
 63 | 
 64 | ### Unit Tests
 65 | 
 66 | Run unit tests with:
 67 | 
 68 | ```bash
 69 | npm run test:unit
 70 | ```
 71 | 
 72 | ### Integration Tests
 73 | 
 74 | Integration tests require a connection to a real Azure DevOps instance. To run them:
 75 | 
 76 | 1. Ensure your `.env` file is configured with valid Azure DevOps credentials:
 77 | 
 78 |    ```
 79 |    AZURE_DEVOPS_ORG_URL=https://dev.azure.com/your-organization
 80 |    AZURE_DEVOPS_PAT=your-personal-access-token
 81 |    AZURE_DEVOPS_DEFAULT_PROJECT=your-project-name
 82 |    ```
 83 | 
 84 | 2. Run the integration tests:
 85 |    ```bash
 86 |    npm run test:integration
 87 |    ```
 88 | 
 89 | ### CI Environment
 90 | 
 91 | For running tests in CI environments (like GitHub Actions), see [CI Environment Setup](docs/ci-setup.md) for instructions on configuring secrets.
 92 | 
 93 | ## Commit Message Guidelines
 94 | 
 95 | We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for our commit messages. This leads to more readable messages that are easy to follow when looking through the project history and enables automatic versioning and changelog generation.
 96 | 
 97 | ### Commit Message Format
 98 | 
 99 | Each commit message consists of a **header**, a **body**, and a **footer**. The header has a special format that includes a **type**, a **scope**, and a **subject**:
100 | 
101 | ```
102 | <type>(<scope>): <subject>
103 | <BLANK LINE>
104 | <body>
105 | <BLANK LINE>
106 | <footer>
107 | ```
108 | 
109 | The **header** is mandatory, while the **scope** of the header is optional.
110 | 
111 | ### Type
112 | 
113 | Must be one of the following:
114 | 
115 | - **feat**: A new feature
116 | - **fix**: A bug fix
117 | - **docs**: Documentation only changes
118 | - **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc)
119 | - **refactor**: A code change that neither fixes a bug nor adds a feature
120 | - **perf**: A code change that improves performance
121 | - **test**: Adding missing tests or correcting existing tests
122 | - **build**: Changes that affect the build system or external dependencies
123 | - **ci**: Changes to our CI configuration files and scripts
124 | - **chore**: Other changes that don't modify src or test files
125 | 
126 | ### Subject
127 | 
128 | The subject contains a succinct description of the change:
129 | 
130 | - Use the imperative, present tense: "change" not "changed" nor "changes"
131 | - Don't capitalize the first letter
132 | - No period (.) at the end
133 | 
134 | ### Body
135 | 
136 | The body should include the motivation for the change and contrast this with previous behavior.
137 | 
138 | ### Footer
139 | 
140 | The footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit closes.
141 | 
142 | Breaking Changes should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
143 | 
144 | ### Using the Interactive Tool
145 | 
146 | To simplify the process of creating correctly formatted commit messages, we've set up a tool that will guide you through the process. Simply use:
147 | 
148 | ```bash
149 | npm run commit
150 | ```
151 | 
152 | This will start an interactive prompt that will help you generate a properly formatted commit message.
153 | 
154 | ## Release Process
155 | 
156 | This project uses [Conventional Commits](https://www.conventionalcommits.org/) to automate versioning and changelog generation. When contributing, please follow the commit message convention.
157 | 
158 | To create a commit with the correct format, use:
159 | ```bash
160 | npm run commit
161 | ```
162 | 
163 | ## Automated Release Workflow
164 | 
165 | Our project uses [Release Please](https://github.com/googleapis/release-please) to automate releases based on Conventional Commits. This approach manages semantic versioning, changelog generation, and GitHub Releases creation.
166 | 
167 | The workflow is automatically triggered on pushes to the `main` branch and follows this process:
168 | 
169 | 1. Release Please analyzes commit messages since the last release
170 | 2. If releasable changes are detected, it creates or updates a Release PR
171 | 3. When the Release PR is merged, it:
172 |    - Updates the version in package.json
173 |    - Updates CHANGELOG.md with details of all changes
174 |    - Creates a Git tag and GitHub Release
175 |    - Publishes the package to npm
176 | 
177 | ### Release PR Process
178 | 
179 | 1. When commits with conventional commit messages are pushed to `main`, Release Please automatically creates a Release PR
180 | 2. The Release PR contains all the changes since the last release with proper version bump based on commit types:
181 |    - `feat:` commits trigger a minor version bump
182 |    - `fix:` commits trigger a patch version bump
183 |    - `feat!:` or `fix!:` commits with breaking changes trigger a major version bump
184 | 3. Review the Release PR to ensure the changelog and version bump are correct
185 | 4. Merge the Release PR to trigger the actual release
186 | 
187 | This automation ensures consistent and well-documented releases that accurately reflect the changes made since the previous release.
188 | 
189 | ## License
190 | 
191 | By contributing, you agree that your contributions will be licensed under the project's license.
```

--------------------------------------------------------------------------------
/.github/release-please-manifest.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   ".": "0.1.42"
3 | } 
```

--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------

```yaml
1 | github: Tiberriver256
2 | 
```

--------------------------------------------------------------------------------
/src/shared/api/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './client';
2 | 
```

--------------------------------------------------------------------------------
/src/shared/types/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './config';
2 | 
```

--------------------------------------------------------------------------------
/src/features/pull-requests/add-pull-request-comment/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './feature';
2 | 
```

--------------------------------------------------------------------------------
/src/features/pull-requests/get-pull-request-comments/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './feature';
2 | 
```

--------------------------------------------------------------------------------
/src/features/pull-requests/update-pull-request/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './feature';
2 | 
```

--------------------------------------------------------------------------------
/src/features/search/search-wiki/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './feature';
2 | 
```

--------------------------------------------------------------------------------
/src/features/search/search-work-items/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './feature';
2 | 
```

--------------------------------------------------------------------------------
/src/shared/config/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './version';
2 | 
```

--------------------------------------------------------------------------------
/src/shared/errors/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './azure-devops-errors';
2 | 
```

--------------------------------------------------------------------------------
/src/features/organizations/list-organizations/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/pipelines/get-pipeline/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './feature';
2 | export * from './schema';
3 | 
```

--------------------------------------------------------------------------------
/src/features/pipelines/list-pipelines/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './feature';
2 | export * from './schema';
3 | 
```

--------------------------------------------------------------------------------
/src/features/projects/get-project/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/projects/list-projects/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/pull-requests/create-pull-request/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/pull-requests/list-pull-requests/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './feature';
2 | export * from './schema';
3 | 
```

--------------------------------------------------------------------------------
/src/features/pull-requests/list-pull-requests/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | export { ListPullRequestsSchema } from '../schemas';
2 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/get-all-repositories-tree/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/get-file-content/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './feature';
2 | export * from './schema';
3 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/get-repository-details/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/get-repository/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/list-repositories/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/users/get-me/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/create-work-item/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/get-work-item/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/list-work-items/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/update-work-item/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './schema';
2 | export * from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/search/search-code/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './feature';
2 | export * from '../schemas';
3 | 
```

--------------------------------------------------------------------------------
/src/features/users/get-me/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { GetMeSchema } from '../schemas';
2 | 
3 | export { GetMeSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/projects/get-project/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { GetProjectSchema } from '../schemas';
2 | 
3 | export { GetProjectSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/get-work-item/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { GetWorkItemSchema } from '../schemas';
2 | 
3 | export { GetWorkItemSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/projects/list-projects/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { ListProjectsSchema } from '../schemas';
2 | 
3 | export { ListProjectsSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/get-wikis/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export { getWikis } from './feature';
2 | export { GetWikisSchema } from './schema';
3 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/get-repository/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { GetRepositorySchema } from '../schemas';
2 | 
3 | export { GetRepositorySchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/list-work-items/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { ListWorkItemsSchema } from '../schemas';
2 | 
3 | export { ListWorkItemsSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/create-work-item/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { CreateWorkItemSchema } from '../schemas';
2 | 
3 | export { CreateWorkItemSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/update-work-item/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { UpdateWorkItemSchema } from '../schemas';
2 | 
3 | export { UpdateWorkItemSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/get-wiki-page/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export { getWikiPage } from './feature';
2 | export { GetWikiPageSchema } from './schema';
3 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/list-repositories/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { ListRepositoriesSchema } from '../schemas';
2 | 
3 | export { ListRepositoriesSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/shared/config/version.ts:
--------------------------------------------------------------------------------

```typescript
1 | /**
2 |  * Current version of the Azure DevOps MCP server
3 |  */
4 | export const VERSION = '0.1.0';
5 | 
```

--------------------------------------------------------------------------------
/src/features/organizations/list-organizations/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { ListOrganizationsSchema } from '../schemas';
2 | 
3 | export { ListOrganizationsSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/projects/get-project-details/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { GetProjectDetailsSchema } from '../schemas';
2 | 
3 | export { GetProjectDetailsSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/pull-requests/create-pull-request/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { CreatePullRequestSchema } from '../schemas';
2 | 
3 | export { CreatePullRequestSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/list-wiki-pages/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export { ListWikiPagesSchema } from './schema';
2 | export { listWikiPages } from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/create-wiki-page/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export { createWikiPage } from './feature';
2 | export { CreateWikiPageSchema } from './schema';
3 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/manage-work-item-link/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { ManageWorkItemLinkSchema } from '../schemas';
2 | 
3 | export { ManageWorkItemLinkSchema };
4 | 
```

--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------

```javascript
1 | // commitlint.config.js
2 | module.exports = {
3 |   extends: ['@commitlint/config-conventional'],
4 | }; 
```

--------------------------------------------------------------------------------
/src/features/pipelines/trigger-pipeline/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export { triggerPipeline } from './feature';
2 | export { TriggerPipelineSchema } from './schema';
3 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/create-wiki/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export { createWiki } from './feature';
2 | export { CreateWikiSchema, WikiType } from './schema';
3 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/get-repository-details/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { GetRepositoryDetailsSchema } from '../schemas';
2 | 
3 | export { GetRepositoryDetailsSchema };
4 | 
```

--------------------------------------------------------------------------------
/src/features/projects/get-project-details/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export { GetProjectDetailsSchema } from './schema';
2 | export { getProjectDetails } from './feature';
3 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/manage-work-item-link/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export { manageWorkItemLink } from './feature';
2 | export { ManageWorkItemLinkSchema } from './schema';
3 | 
```

--------------------------------------------------------------------------------
/project-management/planning/project-plan.md:
--------------------------------------------------------------------------------

```markdown
1 | # Project Plan
2 | 
3 | The project plan has been moved to:
4 | 
5 | https://github.com/users/Tiberriver256/projects/1
6 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/update-wiki-page/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export { updateWikiPage, UpdateWikiPageOptions } from './feature';
2 | export { UpdateWikiPageSchema } from './schema';
3 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/get-file-content/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { GetFileContentSchema } from '../schemas';
2 | 
3 | // Export with explicit name to avoid conflicts
4 | export { GetFileContentSchema };
5 | 
```

--------------------------------------------------------------------------------
/src/features/users/schemas.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { z } from 'zod';
2 | 
3 | /**
4 |  * Schema for the get_me tool, which takes no parameters
5 |  */
6 | export const GetMeSchema = z.object({}).strict();
7 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/get-all-repositories-tree/schema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { GetAllRepositoriesTreeSchema } from '../schemas';
2 | 
3 | // Export with explicit name to avoid conflicts
4 | export { GetAllRepositoriesTreeSchema };
5 | 
```

--------------------------------------------------------------------------------
/src/features/organizations/schemas.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { z } from 'zod';
2 | 
3 | /**
4 |  * Schema for the list organizations request
5 |  * Note: This is an empty schema because the operation doesn't require any parameters
6 |  */
7 | export const ListOrganizationsSchema = z.object({});
8 | 
```

--------------------------------------------------------------------------------
/src/shared/types/tool-definition.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { JsonSchema7Type } from 'zod-to-json-schema';
 2 | 
 3 | /**
 4 |  * Represents a tool that can be listed in the ListTools response
 5 |  */
 6 | export interface ToolDefinition {
 7 |   name: string;
 8 |   description: string;
 9 |   inputSchema: JsonSchema7Type;
10 | }
11 | 
```

--------------------------------------------------------------------------------
/project-management/tdd-cycle.xml:
--------------------------------------------------------------------------------

```
1 | <tdd-cycle>
2 |     <red>Write a failing test for new functionality.</red>
3 |     <green>Implement minimal code to pass the test.</green>
4 |     <refactor>Refactor code, ensuring tests pass.</refactor>
5 |     <repeat>Repeat for each new test.</repeat>
6 | </tdd-cycle>
```

--------------------------------------------------------------------------------
/src/features/users/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * User profile information
 3 |  */
 4 | export interface UserProfile {
 5 |   /**
 6 |    * The ID of the user
 7 |    */
 8 |   id: string;
 9 | 
10 |   /**
11 |    * The display name of the user
12 |    */
13 |   displayName: string;
14 | 
15 |   /**
16 |    * The email address of the user
17 |    */
18 |   email: string;
19 | }
20 | 
```

--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "explorer.fileNesting.enabled": true,
3 |   "explorer.fileNesting.patterns": {
4 |     "*.ts": "${capture}.spec.unit.ts, ${capture}.spec.int.ts, ${capture}.spec.e2e.ts"
5 |   },
6 |   "typescript.preferences.importModuleSpecifier": "non-relative",
7 |   "typescript.updateImportsOnFileMove.enabled": "always"
8 | } 
```

--------------------------------------------------------------------------------
/src/features/projects/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { TeamProject } from 'azure-devops-node-api/interfaces/CoreInterfaces';
 2 | 
 3 | /**
 4 |  * Options for listing projects
 5 |  */
 6 | export interface ListProjectsOptions {
 7 |   stateFilter?: number;
 8 |   top?: number;
 9 |   skip?: number;
10 |   continuationToken?: number;
11 | }
12 | 
13 | // Re-export TeamProject type for convenience
14 | export type { TeamProject };
15 | 
```

--------------------------------------------------------------------------------
/src/features/organizations/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Organization interface
 3 |  */
 4 | export interface Organization {
 5 |   /**
 6 |    * The ID of the organization
 7 |    */
 8 |   id: string;
 9 | 
10 |   /**
11 |    * The name of the organization
12 |    */
13 |   name: string;
14 | 
15 |   /**
16 |    * The URL of the organization
17 |    */
18 |   url: string;
19 | }
20 | 
21 | /**
22 |  * Azure DevOps resource ID for token acquisition
23 |  */
24 | export const AZURE_DEVOPS_RESOURCE_ID = '499b84ac-1321-427f-aa17-267ca6975798';
25 | 
```

--------------------------------------------------------------------------------
/jest.int.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
 2 | module.exports = {
 3 |   preset: 'ts-jest',
 4 |   testEnvironment: 'node',
 5 |   roots: ['<rootDir>/src'],
 6 |   testMatch: ['**/*.spec.int.ts'],
 7 |   moduleNameMapper: {
 8 |     '^@/(.*)$': '<rootDir>/src/$1',
 9 |   },
10 |   collectCoverage: false,
11 |   verbose: true,
12 |   testPathIgnorePatterns: ['/node_modules/', '/dist/'],
13 |   setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
14 | }; 
```

--------------------------------------------------------------------------------
/jest.unit.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
 2 | module.exports = {
 3 |   preset: 'ts-jest',
 4 |   testEnvironment: 'node',
 5 |   roots: ['<rootDir>/src'],
 6 |   testMatch: ['**/*.spec.unit.ts'],
 7 |   moduleNameMapper: {
 8 |     '^@/(.*)$': '<rootDir>/src/$1',
 9 |   },
10 |   collectCoverage: false,
11 |   verbose: true,
12 |   testPathIgnorePatterns: ['/node_modules/', '/dist/'],
13 |   setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
14 | }; 
```

--------------------------------------------------------------------------------
/.github/release-please-config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "packages": {
 3 |     ".": {
 4 |       "release-type": "node",
 5 |       "package-name": "@tiberriver256/mcp-server-azure-devops",
 6 |       "changelog-path": "CHANGELOG.md",
 7 |       "bump-minor-pre-major": true,
 8 |       "bump-patch-for-minor-pre-major": true,
 9 |       "draft": false,
10 |       "prerelease": false
11 |     }
12 |   },
13 |   "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
14 | } 
```

--------------------------------------------------------------------------------
/src/features/users/tool-definitions.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { zodToJsonSchema } from 'zod-to-json-schema';
 2 | import { ToolDefinition } from '../../shared/types/tool-definition';
 3 | import { GetMeSchema } from './schemas';
 4 | 
 5 | /**
 6 |  * List of users tools
 7 |  */
 8 | export const usersTools: ToolDefinition[] = [
 9 |   {
10 |     name: 'get_me',
11 |     description:
12 |       'Get details of the authenticated user (id, displayName, email)',
13 |     inputSchema: zodToJsonSchema(GetMeSchema),
14 |   },
15 | ];
16 | 
```

--------------------------------------------------------------------------------
/src/shared/auth/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Authentication module for Azure DevOps
 3 |  *
 4 |  * This module provides authentication functionality for Azure DevOps API.
 5 |  * It supports multiple authentication methods:
 6 |  * - Personal Access Token (PAT)
 7 |  * - Azure Identity (DefaultAzureCredential)
 8 |  * - Azure CLI (AzureCliCredential)
 9 |  */
10 | 
11 | export {
12 |   AuthenticationMethod,
13 |   AuthConfig,
14 |   createAuthClient,
15 | } from './auth-factory';
16 | export { AzureDevOpsClient } from './client-factory';
17 | 
```

--------------------------------------------------------------------------------
/src/features/organizations/tool-definitions.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { zodToJsonSchema } from 'zod-to-json-schema';
 2 | import { ToolDefinition } from '../../shared/types/tool-definition';
 3 | import { ListOrganizationsSchema } from './schemas';
 4 | 
 5 | /**
 6 |  * List of organizations tools
 7 |  */
 8 | export const organizationsTools: ToolDefinition[] = [
 9 |   {
10 |     name: 'list_organizations',
11 |     description:
12 |       'List all Azure DevOps organizations accessible to the current authentication',
13 |     inputSchema: zodToJsonSchema(ListOrganizationsSchema),
14 |   },
15 | ];
16 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/get-wikis/schema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | import { defaultProject, defaultOrg } from '../../../utils/environment';
 4 | 
 5 | /**
 6 |  * Schema for listing wikis in an Azure DevOps project or organization
 7 |  */
 8 | export const GetWikisSchema = z.object({
 9 |   organizationId: z
10 |     .string()
11 |     .optional()
12 |     .nullable()
13 |     .describe(`The ID or name of the organization (Default: ${defaultOrg})`),
14 |   projectId: z
15 |     .string()
16 |     .optional()
17 |     .nullable()
18 |     .describe(`The ID or name of the project (Default: ${defaultProject})`),
19 | });
20 | 
```

--------------------------------------------------------------------------------
/jest.e2e.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
 2 | module.exports = {
 3 |   preset: 'ts-jest',
 4 |   testEnvironment: 'node',
 5 |   roots: ['<rootDir>/src'],
 6 |   testMatch: ['**/*.spec.e2e.ts'],
 7 |   moduleNameMapper: {
 8 |     '^@/(.*)$': '<rootDir>/src/$1',
 9 |   },
10 |   collectCoverage: false,
11 |   verbose: true,
12 |   testPathIgnorePatterns: ['/node_modules/', '/dist/'],
13 |   setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
14 |   testTimeout: 30000, // Longer timeout for E2E tests
15 |   passWithNoTests: true, // Allow tests to pass when no tests exist yet
16 | }; 
```

--------------------------------------------------------------------------------
/.kilocode/mcp.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "shrimp-task-manager": {
 4 |       "command": "npx",
 5 |       "args": [
 6 |         "-y",
 7 |         "mcp-shrimp-task-manager"
 8 |       ],
 9 |       "env": {
10 |         "DATA_DIR": "D:/mcp-server-azure-devops",
11 |         "TEMPLATES_USE": "en",
12 |         "ENABLE_GUI": "false"
13 |       },
14 |       "alwaysAllow": [
15 |         "init_project_rules",
16 |         "process_thought",
17 |         "plan_task",
18 |         "analyze_task",
19 |         "reflect_task",
20 |         "split_tasks",
21 |         "execute_task",
22 |         "verify_task",
23 |         "get_task_detail"
24 |       ]
25 |     }
26 |   }
27 | }
```

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

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "strict": true,
 7 |     "esModuleInterop": true,
 8 |     "skipLibCheck": true,
 9 |     "forceConsistentCasingInFileNames": true,
10 |     "resolveJsonModule": true,
11 |     "outDir": "dist",
12 |     "sourceMap": true,
13 |     "declaration": true,
14 |     "strictNullChecks": true,
15 |     "noImplicitAny": true,
16 |     "noUnusedLocals": true,
17 |     "noUnusedParameters": true,
18 |     "baseUrl": ".",
19 |     "paths": {
20 |       "@/*": ["src/*"]
21 |     }
22 |   },
23 |   "include": ["src/**/*"],
24 |   "exclude": ["node_modules", "dist"]
25 | }
26 | 
```

--------------------------------------------------------------------------------
/src/features/pipelines/list-pipelines/schema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { defaultProject } from '../../../utils/environment';
 3 | 
 4 | /**
 5 |  * Schema for the listPipelines function
 6 |  */
 7 | export const ListPipelinesSchema = z.object({
 8 |   // The project to list pipelines from
 9 |   projectId: z
10 |     .string()
11 |     .optional()
12 |     .describe(`The ID or name of the project (Default: ${defaultProject})`),
13 |   // Maximum number of pipelines to return
14 |   top: z.number().optional().describe('Maximum number of pipelines to return'),
15 |   // Order by field and direction
16 |   orderBy: z
17 |     .string()
18 |     .optional()
19 |     .describe('Order by field and direction (e.g., "createdDate desc")'),
20 | });
21 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/get-wiki-page/schema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | import { defaultProject, defaultOrg } from '../../../utils/environment';
 4 | 
 5 | /**
 6 |  * Schema for getting a wiki page from an Azure DevOps wiki
 7 |  */
 8 | export const GetWikiPageSchema = z.object({
 9 |   organizationId: z
10 |     .string()
11 |     .optional()
12 |     .nullable()
13 |     .describe(`The ID or name of the organization (Default: ${defaultOrg})`),
14 |   projectId: z
15 |     .string()
16 |     .optional()
17 |     .nullable()
18 |     .describe(`The ID or name of the project (Default: ${defaultProject})`),
19 |   wikiId: z.string().describe('The ID or name of the wiki'),
20 |   pagePath: z.string().describe('The path of the page within the wiki'),
21 | });
22 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/list-wiki-pages/schema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | import { defaultProject, defaultOrg } from '../../../utils/environment';
 4 | 
 5 | /**
 6 |  * Schema for listing wiki pages from an Azure DevOps wiki
 7 |  */
 8 | export const ListWikiPagesSchema = z.object({
 9 |   organizationId: z
10 |     .string()
11 |     .optional()
12 |     .nullable()
13 |     .describe(`The ID or name of the organization (Default: ${defaultOrg})`),
14 |   projectId: z
15 |     .string()
16 |     .optional()
17 |     .nullable()
18 |     .describe(`The ID or name of the project (Default: ${defaultProject})`),
19 |   wikiId: z.string().describe('The ID or name of the wiki'),
20 | });
21 | 
22 | export type ListWikiPagesOptions = z.infer<typeof ListWikiPagesSchema>;
23 | 
```

--------------------------------------------------------------------------------
/src/shared/types/request-handler.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   CallToolRequest,
 3 |   CallToolResult,
 4 | } from '@modelcontextprotocol/sdk/types.js';
 5 | import { WebApi } from 'azure-devops-node-api';
 6 | 
 7 | /**
 8 |  * Function type for identifying if a request belongs to a specific feature.
 9 |  */
10 | export interface RequestIdentifier {
11 |   (request: CallToolRequest): boolean;
12 | }
13 | 
14 | /**
15 |  * Function type for handling feature-specific requests.
16 |  * Returns either the standard MCP CallToolResult or a simplified response structure
17 |  * for backward compatibility.
18 |  */
19 | export interface RequestHandler {
20 |   (
21 |     connection: WebApi,
22 |     request: CallToolRequest,
23 |   ): Promise<
24 |     CallToolResult | { content: Array<{ type: string; text: string }> }
25 |   >;
26 | }
27 | 
```

--------------------------------------------------------------------------------
/src/features/pipelines/get-pipeline/schema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { defaultProject } from '../../../utils/environment';
 3 | 
 4 | /**
 5 |  * Schema for the getPipeline function
 6 |  */
 7 | export const GetPipelineSchema = z.object({
 8 |   // The project containing the pipeline
 9 |   projectId: z
10 |     .string()
11 |     .optional()
12 |     .describe(`The ID or name of the project (Default: ${defaultProject})`),
13 |   // The ID of the pipeline to retrieve
14 |   pipelineId: z
15 |     .number()
16 |     .int()
17 |     .positive()
18 |     .describe('The numeric ID of the pipeline to retrieve'),
19 |   // The version of the pipeline to retrieve
20 |   pipelineVersion: z
21 |     .number()
22 |     .int()
23 |     .positive()
24 |     .optional()
25 |     .describe(
26 |       'The version of the pipeline to retrieve (latest if not specified)',
27 |     ),
28 | });
29 | 
```

--------------------------------------------------------------------------------
/src/shared/types/config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { AuthenticationMethod } from '../auth/auth-factory';
 2 | 
 3 | /**
 4 |  * Azure DevOps configuration type definition
 5 |  */
 6 | export interface AzureDevOpsConfig {
 7 |   /**
 8 |    * The Azure DevOps organization URL (e.g., https://dev.azure.com/organization)
 9 |    */
10 |   organizationUrl: string;
11 | 
12 |   /**
13 |    * Authentication method to use (pat, azure-identity, azure-cli)
14 |    * @default 'azure-identity'
15 |    */
16 |   authMethod?: AuthenticationMethod;
17 | 
18 |   /**
19 |    * Personal Access Token for authentication (required for PAT authentication)
20 |    */
21 |   personalAccessToken?: string;
22 | 
23 |   /**
24 |    * Optional default project to use when not specified
25 |    */
26 |   defaultProject?: string;
27 | 
28 |   /**
29 |    * Optional API version to use (defaults to latest)
30 |    */
31 |   apiVersion?: string;
32 | }
33 | 
```

--------------------------------------------------------------------------------
/src/features/search/tool-definitions.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { zodToJsonSchema } from 'zod-to-json-schema';
 2 | import { ToolDefinition } from '../../shared/types/tool-definition';
 3 | import {
 4 |   SearchCodeSchema,
 5 |   SearchWikiSchema,
 6 |   SearchWorkItemsSchema,
 7 | } from './schemas';
 8 | 
 9 | /**
10 |  * List of search tools
11 |  */
12 | export const searchTools: ToolDefinition[] = [
13 |   {
14 |     name: 'search_code',
15 |     description: 'Search for code across repositories in a project',
16 |     inputSchema: zodToJsonSchema(SearchCodeSchema),
17 |   },
18 |   {
19 |     name: 'search_wiki',
20 |     description: 'Search for content across wiki pages in a project',
21 |     inputSchema: zodToJsonSchema(SearchWikiSchema),
22 |   },
23 |   {
24 |     name: 'search_work_items',
25 |     description: 'Search for work items across projects in Azure DevOps',
26 |     inputSchema: zodToJsonSchema(SearchWorkItemsSchema),
27 |   },
28 | ];
29 | 
```

--------------------------------------------------------------------------------
/src/features/projects/tool-definitions.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { zodToJsonSchema } from 'zod-to-json-schema';
 2 | import { ToolDefinition } from '../../shared/types/tool-definition';
 3 | import {
 4 |   ListProjectsSchema,
 5 |   GetProjectSchema,
 6 |   GetProjectDetailsSchema,
 7 | } from './schemas';
 8 | 
 9 | /**
10 |  * List of projects tools
11 |  */
12 | export const projectsTools: ToolDefinition[] = [
13 |   {
14 |     name: 'list_projects',
15 |     description: 'List all projects in an organization',
16 |     inputSchema: zodToJsonSchema(ListProjectsSchema),
17 |   },
18 |   {
19 |     name: 'get_project',
20 |     description: 'Get details of a specific project',
21 |     inputSchema: zodToJsonSchema(GetProjectSchema),
22 |   },
23 |   {
24 |     name: 'get_project_details',
25 |     description:
26 |       'Get comprehensive details of a project including process, work item types, and teams',
27 |     inputSchema: zodToJsonSchema(GetProjectDetailsSchema),
28 |   },
29 | ];
30 | 
```

--------------------------------------------------------------------------------
/docs/examples/pat-authentication.env:
--------------------------------------------------------------------------------

```
 1 | # Example .env file for Personal Access Token (PAT) authentication
 2 | # Replace the values with your own
 3 | 
 4 | # Authentication method (required)
 5 | AZURE_DEVOPS_AUTH_METHOD=pat
 6 | 
 7 | # Azure DevOps organization URL (required)
 8 | AZURE_DEVOPS_ORG_URL=https://dev.azure.com/your-organization
 9 | 
10 | # Personal Access Token (required for PAT authentication)
11 | # Create one at: https://dev.azure.com/your-organization/_usersSettings/tokens
12 | AZURE_DEVOPS_PAT=your-personal-access-token
13 | 
14 | # Default project to use when not specified (optional)
15 | AZURE_DEVOPS_DEFAULT_PROJECT=your-default-project
16 | 
17 | # API Version to use (optional, defaults to latest)
18 | # AZURE_DEVOPS_API_VERSION=6.0
19 | 
20 | # Logging Level (optional)
21 | LOG_LEVEL=info
22 | 
23 | # Note: This server uses stdio for communication with the MCP client,
24 | # not HTTP. It does not listen on a network port. 
```

--------------------------------------------------------------------------------
/src/features/pipelines/tool-definitions.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { zodToJsonSchema } from 'zod-to-json-schema';
 2 | import { ToolDefinition } from '../../shared/types/tool-definition';
 3 | import { ListPipelinesSchema } from './list-pipelines/schema';
 4 | import { GetPipelineSchema } from './get-pipeline/schema';
 5 | import { TriggerPipelineSchema } from './trigger-pipeline/schema';
 6 | 
 7 | /**
 8 |  * List of pipelines tools
 9 |  */
10 | export const pipelinesTools: ToolDefinition[] = [
11 |   {
12 |     name: 'list_pipelines',
13 |     description: 'List pipelines in a project',
14 |     inputSchema: zodToJsonSchema(ListPipelinesSchema),
15 |   },
16 |   {
17 |     name: 'get_pipeline',
18 |     description: 'Get details of a specific pipeline',
19 |     inputSchema: zodToJsonSchema(GetPipelineSchema),
20 |   },
21 |   {
22 |     name: 'trigger_pipeline',
23 |     description: 'Trigger a pipeline run',
24 |     inputSchema: zodToJsonSchema(TriggerPipelineSchema),
25 |   },
26 | ];
27 | 
```

--------------------------------------------------------------------------------
/docs/tools/user-tools.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Azure DevOps User Tools
 2 | 
 3 | This document describes the user-related tools provided by the Azure DevOps MCP server.
 4 | 
 5 | ## get_me
 6 | 
 7 | The `get_me` tool retrieves information about the currently authenticated user.
 8 | 
 9 | ### Input
10 | 
11 | This tool doesn't require any input parameters.
12 | 
13 | ```json
14 | {}
15 | ```
16 | 
17 | ### Output
18 | 
19 | The tool returns the user's profile information including:
20 | - `id`: The unique identifier for the user
21 | - `displayName`: The user's display name
22 | - `email`: The user's email address
23 | 
24 | #### Example Response
25 | 
26 | ```json
27 | {
28 |   "id": "01234567-89ab-cdef-0123-456789abcdef",
29 |   "displayName": "John Doe",
30 |   "email": "[email protected]"
31 | }
32 | ```
33 | 
34 | ### Error Handling
35 | 
36 | The tool may return the following errors:
37 | 
38 | - `AzureDevOpsAuthenticationError`: If authentication fails
39 | - `AzureDevOpsError`: For general errors when retrieving user information 
```

--------------------------------------------------------------------------------
/src/features/pipelines/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // Re-export the Pipeline interface from the Azure DevOps API
 2 | import {
 3 |   Pipeline,
 4 |   Run,
 5 | } from 'azure-devops-node-api/interfaces/PipelinesInterfaces';
 6 | 
 7 | /**
 8 |  * Options for listing pipelines
 9 |  */
10 | export interface ListPipelinesOptions {
11 |   projectId: string;
12 |   orderBy?: string;
13 |   top?: number;
14 |   continuationToken?: string;
15 | }
16 | 
17 | /**
18 |  * Options for getting a pipeline
19 |  */
20 | export interface GetPipelineOptions {
21 |   projectId: string;
22 |   organizationId?: string;
23 |   pipelineId: number;
24 |   pipelineVersion?: number;
25 | }
26 | 
27 | /**
28 |  * Options for triggering a pipeline
29 |  */
30 | export interface TriggerPipelineOptions {
31 |   projectId: string;
32 |   pipelineId: number;
33 |   branch?: string;
34 |   variables?: Record<string, { value: string; isSecret?: boolean }>;
35 |   templateParameters?: Record<string, string>;
36 |   stagesToSkip?: string[];
37 | }
38 | 
39 | export { Pipeline, Run };
40 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/update-wiki-page/schema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | import { defaultProject, defaultOrg } from '../../../utils/environment';
 4 | 
 5 | /**
 6 |  * Schema for validating wiki page update options
 7 |  */
 8 | export const UpdateWikiPageSchema = z.object({
 9 |   organizationId: z
10 |     .string()
11 |     .optional()
12 |     .nullable()
13 |     .describe(`The ID or name of the organization (Default: ${defaultOrg})`),
14 |   projectId: z
15 |     .string()
16 |     .optional()
17 |     .nullable()
18 |     .describe(`The ID or name of the project (Default: ${defaultProject})`),
19 |   wikiId: z.string().min(1).describe('The ID or name of the wiki'),
20 |   pagePath: z.string().min(1).describe('Path of the wiki page to update'),
21 |   content: z
22 |     .string()
23 |     .min(1)
24 |     .describe('The new content for the wiki page in markdown format'),
25 |   comment: z
26 |     .string()
27 |     .optional()
28 |     .nullable()
29 |     .describe('Optional comment for the update'),
30 | });
31 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/__test__/fixtures.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WorkItem } from '../types';
 2 | 
 3 | /**
 4 |  * Standard work item fixture for tests
 5 |  */
 6 | export const createWorkItemFixture = (
 7 |   id: number,
 8 |   title: string = 'Test Work Item',
 9 |   state: string = 'Active',
10 |   assignedTo?: string,
11 | ): WorkItem => {
12 |   return {
13 |     id,
14 |     rev: 1,
15 |     fields: {
16 |       'System.Id': id,
17 |       'System.Title': title,
18 |       'System.State': state,
19 |       ...(assignedTo ? { 'System.AssignedTo': assignedTo } : {}),
20 |     },
21 |     url: `https://dev.azure.com/test-org/test-project/_apis/wit/workItems/${id}`,
22 |   } as WorkItem;
23 | };
24 | 
25 | /**
26 |  * Create a collection of work items for list tests
27 |  */
28 | export const createWorkItemsFixture = (count: number = 3): WorkItem[] => {
29 |   return Array.from({ length: count }, (_, i) =>
30 |     createWorkItemFixture(
31 |       i + 1,
32 |       `Work Item ${i + 1}`,
33 |       i % 2 === 0 ? 'Active' : 'Resolved',
34 |     ),
35 |   );
36 | };
37 | 
```

--------------------------------------------------------------------------------
/docs/examples/azure-cli-authentication.env:
--------------------------------------------------------------------------------

```
 1 | # Example .env file for Azure CLI authentication
 2 | # Replace the values with your own
 3 | 
 4 | # Authentication method (required)
 5 | AZURE_DEVOPS_AUTH_METHOD=azure-cli
 6 | 
 7 | # Azure DevOps organization URL (required)
 8 | AZURE_DEVOPS_ORG_URL=https://dev.azure.com/your-organization
 9 | 
10 | # Default project to use when not specified (optional)
11 | AZURE_DEVOPS_DEFAULT_PROJECT=your-default-project
12 | 
13 | # API Version to use (optional, defaults to latest)
14 | # AZURE_DEVOPS_API_VERSION=6.0
15 | 
16 | # Logging Level (optional)
17 | LOG_LEVEL=info
18 | 
19 | # Note: This server uses stdio for communication with the MCP client,
20 | # not HTTP. It does not listen on a network port.
21 | 
22 | # Note: Before using Azure CLI authentication, make sure you have:
23 | # 1. Installed the Azure CLI (https://docs.microsoft.com/cli/azure/install-azure-cli)
24 | # 2. Logged in with 'az login'
25 | # 3. Verified your account has access to the Azure DevOps organization 
```

--------------------------------------------------------------------------------
/src/features/repositories/list-repositories/feature.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { AzureDevOpsError } from '../../../shared/errors';
 3 | import { ListRepositoriesOptions, GitRepository } from '../types';
 4 | 
 5 | /**
 6 |  * List repositories in a project
 7 |  *
 8 |  * @param connection The Azure DevOps WebApi connection
 9 |  * @param options Parameters for listing repositories
10 |  * @returns Array of repositories
11 |  */
12 | export async function listRepositories(
13 |   connection: WebApi,
14 |   options: ListRepositoriesOptions,
15 | ): Promise<GitRepository[]> {
16 |   try {
17 |     const gitApi = await connection.getGitApi();
18 |     const repositories = await gitApi.getRepositories(
19 |       options.projectId,
20 |       options.includeLinks,
21 |     );
22 | 
23 |     return repositories;
24 |   } catch (error) {
25 |     if (error instanceof AzureDevOpsError) {
26 |       throw error;
27 |     }
28 |     throw new Error(
29 |       `Failed to list repositories: ${error instanceof Error ? error.message : String(error)}`,
30 |     );
31 |   }
32 | }
33 | 
```

--------------------------------------------------------------------------------
/src/features/projects/list-projects/feature.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { AzureDevOpsError } from '../../../shared/errors';
 3 | import { ListProjectsOptions, TeamProject } from '../types';
 4 | 
 5 | /**
 6 |  * List all projects in the organization
 7 |  *
 8 |  * @param connection The Azure DevOps WebApi connection
 9 |  * @param options Optional parameters for listing projects
10 |  * @returns Array of projects
11 |  */
12 | export async function listProjects(
13 |   connection: WebApi,
14 |   options: ListProjectsOptions = {},
15 | ): Promise<TeamProject[]> {
16 |   try {
17 |     const coreApi = await connection.getCoreApi();
18 |     const projects = await coreApi.getProjects(
19 |       options.stateFilter,
20 |       options.top,
21 |       options.skip,
22 |       options.continuationToken,
23 |     );
24 | 
25 |     return projects;
26 |   } catch (error) {
27 |     if (error instanceof AzureDevOpsError) {
28 |       throw error;
29 |     }
30 |     throw new Error(
31 |       `Failed to list projects: ${error instanceof Error ? error.message : String(error)}`,
32 |     );
33 |   }
34 | }
35 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/__test__/test-utils.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Test utilities for work item tests
 3 |  * These utilities help reduce test execution time and improve test reliability
 4 |  */
 5 | 
 6 | /**
 7 |  * Times test execution to help identify slow tests
 8 |  * @param testName Name of the test
 9 |  * @param fn Test function to execute
10 |  */
11 | export async function timeTest(testName: string, fn: () => Promise<void>) {
12 |   const start = performance.now();
13 |   await fn();
14 |   const end = performance.now();
15 | 
16 |   const duration = end - start;
17 |   if (duration > 100) {
18 |     console.warn(`Test "${testName}" is slow (${duration.toFixed(2)}ms)`);
19 |   }
20 |   return duration;
21 | }
22 | 
23 | /**
24 |  * Setup function to prepare test environment
25 |  * Call at beginning of test to ensure consistent setup
26 |  */
27 | export function setupTestEnvironment() {
28 |   // Set any environment variables needed for tests
29 |   const originalEnv = { ...process.env };
30 | 
31 |   return {
32 |     // Clean up function to restore environment
33 |     cleanup: () => {
34 |       // Restore original environment
35 |       process.env = originalEnv;
36 |     },
37 |   };
38 | }
39 | 
```

--------------------------------------------------------------------------------
/src/utils/environment.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // Load environment variables
 2 | import dotenv from 'dotenv';
 3 | dotenv.config();
 4 | 
 5 | /**
 6 |  * Utility functions and constants related to environment variables.
 7 |  */
 8 | 
 9 | /**
10 |  * Extract organization name from Azure DevOps organization URL
11 |  */
12 | export function getOrgNameFromUrl(url?: string): string {
13 |   if (!url) return 'unknown-organization';
14 |   const devMatch = url.match(/https?:\/\/dev\.azure\.com\/([^/]+)/);
15 |   if (devMatch) {
16 |     return devMatch[1];
17 |   }
18 |   // Fallback only for Azure DevOps Server URLs
19 |   if (url.includes('azure')) {
20 |     const fallbackMatch = url.match(/https?:\/\/[^/]+\/([^/]+)/);
21 |     return fallbackMatch ? fallbackMatch[1] : 'unknown-organization';
22 |   }
23 |   return 'unknown-organization';
24 | }
25 | 
26 | /**
27 |  * Default project name from environment variables
28 |  */
29 | export const defaultProject =
30 |   process.env.AZURE_DEVOPS_DEFAULT_PROJECT || 'no default project';
31 | 
32 | /**
33 |  * Default organization name derived from the organization URL
34 |  */
35 | export const defaultOrg = getOrgNameFromUrl(process.env.AZURE_DEVOPS_ORG_URL);
36 | 
```

--------------------------------------------------------------------------------
/src/features/projects/get-project/feature.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import {
 3 |   AzureDevOpsResourceNotFoundError,
 4 |   AzureDevOpsError,
 5 | } from '../../../shared/errors';
 6 | import { TeamProject } from '../types';
 7 | 
 8 | /**
 9 |  * Get a project by ID or name
10 |  *
11 |  * @param connection The Azure DevOps WebApi connection
12 |  * @param projectId The ID or name of the project
13 |  * @returns The project details
14 |  * @throws {AzureDevOpsResourceNotFoundError} If the project is not found
15 |  */
16 | export async function getProject(
17 |   connection: WebApi,
18 |   projectId: string,
19 | ): Promise<TeamProject> {
20 |   try {
21 |     const coreApi = await connection.getCoreApi();
22 |     const project = await coreApi.getProject(projectId);
23 | 
24 |     if (!project) {
25 |       throw new AzureDevOpsResourceNotFoundError(
26 |         `Project '${projectId}' not found`,
27 |       );
28 |     }
29 | 
30 |     return project;
31 |   } catch (error) {
32 |     if (error instanceof AzureDevOpsError) {
33 |       throw error;
34 |     }
35 |     throw new Error(
36 |       `Failed to get project: ${error instanceof Error ? error.message : String(error)}`,
37 |     );
38 |   }
39 | }
40 | 
```

--------------------------------------------------------------------------------
/src/features/projects/__test__/test-helpers.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { getPersonalAccessTokenHandler } from 'azure-devops-node-api';
 3 | 
 4 | /**
 5 |  * Creates a WebApi connection for tests with real credentials
 6 |  *
 7 |  * @returns WebApi connection
 8 |  */
 9 | export async function getTestConnection(): Promise<WebApi | null> {
10 |   // If we have real credentials, use them
11 |   const orgUrl = process.env.AZURE_DEVOPS_ORG_URL;
12 |   const token = process.env.AZURE_DEVOPS_PAT;
13 | 
14 |   if (orgUrl && token) {
15 |     const authHandler = getPersonalAccessTokenHandler(token);
16 |     return new WebApi(orgUrl, authHandler);
17 |   }
18 | 
19 |   // If we don't have credentials, return null
20 |   return null;
21 | }
22 | 
23 | /**
24 |  * Determines if integration tests should be skipped
25 |  *
26 |  * @returns true if integration tests should be skipped
27 |  */
28 | export function shouldSkipIntegrationTest(): boolean {
29 |   if (!process.env.AZURE_DEVOPS_ORG_URL || !process.env.AZURE_DEVOPS_PAT) {
30 |     console.log(
31 |       'Skipping integration test: No real Azure DevOps connection available',
32 |     );
33 |     return true;
34 |   }
35 |   return false;
36 | }
37 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/__test__/test-helpers.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { getPersonalAccessTokenHandler } from 'azure-devops-node-api';
 3 | 
 4 | /**
 5 |  * Creates a WebApi connection for tests with real credentials
 6 |  *
 7 |  * @returns WebApi connection
 8 |  */
 9 | export async function getTestConnection(): Promise<WebApi | null> {
10 |   // If we have real credentials, use them
11 |   const orgUrl = process.env.AZURE_DEVOPS_ORG_URL;
12 |   const token = process.env.AZURE_DEVOPS_PAT;
13 | 
14 |   if (orgUrl && token) {
15 |     const authHandler = getPersonalAccessTokenHandler(token);
16 |     return new WebApi(orgUrl, authHandler);
17 |   }
18 | 
19 |   // If we don't have credentials, return null
20 |   return null;
21 | }
22 | 
23 | /**
24 |  * Determines if integration tests should be skipped
25 |  *
26 |  * @returns true if integration tests should be skipped
27 |  */
28 | export function shouldSkipIntegrationTest(): boolean {
29 |   if (!process.env.AZURE_DEVOPS_ORG_URL || !process.env.AZURE_DEVOPS_PAT) {
30 |     console.log(
31 |       'Skipping integration test: No real Azure DevOps connection available',
32 |     );
33 |     return true;
34 |   }
35 |   return false;
36 | }
37 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/__test__/test-helpers.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { getPersonalAccessTokenHandler } from 'azure-devops-node-api';
 3 | 
 4 | /**
 5 |  * Creates a WebApi connection for tests with real credentials
 6 |  *
 7 |  * @returns WebApi connection
 8 |  */
 9 | export async function getTestConnection(): Promise<WebApi | null> {
10 |   // If we have real credentials, use them
11 |   const orgUrl = process.env.AZURE_DEVOPS_ORG_URL;
12 |   const token = process.env.AZURE_DEVOPS_PAT;
13 | 
14 |   if (orgUrl && token) {
15 |     const authHandler = getPersonalAccessTokenHandler(token);
16 |     return new WebApi(orgUrl, authHandler);
17 |   }
18 | 
19 |   // If we don't have credentials, return null
20 |   return null;
21 | }
22 | 
23 | /**
24 |  * Determines if integration tests should be skipped
25 |  *
26 |  * @returns true if integration tests should be skipped
27 |  */
28 | export function shouldSkipIntegrationTest(): boolean {
29 |   if (!process.env.AZURE_DEVOPS_ORG_URL || !process.env.AZURE_DEVOPS_PAT) {
30 |     console.log(
31 |       'Skipping integration test: No real Azure DevOps connection available',
32 |     );
33 |     return true;
34 |   }
35 |   return false;
36 | }
37 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/create-wiki-page/schema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { defaultProject, defaultOrg } from '../../../utils/environment';
 3 | 
 4 | /**
 5 |  * Schema for creating a new wiki page in Azure DevOps
 6 |  */
 7 | export const CreateWikiPageSchema = z.object({
 8 |   organizationId: z
 9 |     .string()
10 |     .optional()
11 |     .nullable()
12 |     .describe(`The ID or name of the organization (Default: ${defaultOrg})`),
13 |   projectId: z
14 |     .string()
15 |     .optional()
16 |     .nullable()
17 |     .describe(`The ID or name of the project (Default: ${defaultProject})`),
18 |   wikiId: z.string().min(1).describe('The ID or name of the wiki'),
19 |   pagePath: z
20 |     .string()
21 |     .optional()
22 |     .nullable()
23 |     .default('/')
24 |     .describe(
25 |       'Path of the wiki page to create. If the path does not exist, it will be created. Defaults to the wiki root (/). Example: /ParentPage/NewPage',
26 |     ),
27 |   content: z
28 |     .string()
29 |     .min(1)
30 |     .describe('The content for the new wiki page in markdown format'),
31 |   comment: z
32 |     .string()
33 |     .optional()
34 |     .describe('Optional comment for the creation or update'),
35 | });
36 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   WorkItem,
 3 |   WorkItemReference,
 4 | } from 'azure-devops-node-api/interfaces/WorkItemTrackingInterfaces';
 5 | 
 6 | /**
 7 |  * Options for listing work items
 8 |  */
 9 | export interface ListWorkItemsOptions {
10 |   projectId: string;
11 |   teamId?: string;
12 |   queryId?: string;
13 |   wiql?: string;
14 |   top?: number;
15 |   skip?: number;
16 | }
17 | 
18 | /**
19 |  * Options for creating a work item
20 |  */
21 | export interface CreateWorkItemOptions {
22 |   title: string;
23 |   description?: string;
24 |   assignedTo?: string;
25 |   areaPath?: string;
26 |   iterationPath?: string;
27 |   priority?: number;
28 |   parentId?: number;
29 |   additionalFields?: Record<string, string | number | boolean | null>;
30 | }
31 | 
32 | /**
33 |  * Options for updating a work item
34 |  */
35 | export interface UpdateWorkItemOptions {
36 |   title?: string;
37 |   description?: string;
38 |   assignedTo?: string;
39 |   areaPath?: string;
40 |   iterationPath?: string;
41 |   priority?: number;
42 |   state?: string;
43 |   additionalFields?: Record<string, string | number | boolean | null>;
44 | }
45 | 
46 | // Re-export WorkItem and WorkItemReference types for convenience
47 | export type { WorkItem, WorkItemReference };
48 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/tool-definitions.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { zodToJsonSchema } from 'zod-to-json-schema';
 2 | import { ToolDefinition } from '../../shared/types/tool-definition';
 3 | import {
 4 |   ListWorkItemsSchema,
 5 |   CreateWorkItemSchema,
 6 |   UpdateWorkItemSchema,
 7 |   ManageWorkItemLinkSchema,
 8 |   GetWorkItemSchema,
 9 | } from './schemas';
10 | 
11 | /**
12 |  * List of work items tools
13 |  */
14 | export const workItemsTools: ToolDefinition[] = [
15 |   {
16 |     name: 'list_work_items',
17 |     description: 'List work items in a project',
18 |     inputSchema: zodToJsonSchema(ListWorkItemsSchema),
19 |   },
20 |   {
21 |     name: 'get_work_item',
22 |     description: 'Get details of a specific work item',
23 |     inputSchema: zodToJsonSchema(GetWorkItemSchema),
24 |   },
25 |   {
26 |     name: 'create_work_item',
27 |     description: 'Create a new work item',
28 |     inputSchema: zodToJsonSchema(CreateWorkItemSchema),
29 |   },
30 |   {
31 |     name: 'update_work_item',
32 |     description: 'Update an existing work item',
33 |     inputSchema: zodToJsonSchema(UpdateWorkItemSchema),
34 |   },
35 |   {
36 |     name: 'manage_work_item_link',
37 |     description: 'Add or remove links between work items',
38 |     inputSchema: zodToJsonSchema(ManageWorkItemLinkSchema),
39 |   },
40 | ];
41 | 
```

--------------------------------------------------------------------------------
/src/features/organizations/__test__/test-helpers.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { AzureDevOpsConfig } from '../../../shared/types';
 2 | import { AuthenticationMethod } from '../../../shared/auth';
 3 | 
 4 | /**
 5 |  * Creates test configuration for Azure DevOps tests
 6 |  *
 7 |  * @returns Azure DevOps config
 8 |  */
 9 | export function getTestConfig(): AzureDevOpsConfig | null {
10 |   // If we have real credentials, use them
11 |   const orgUrl = process.env.AZURE_DEVOPS_ORG_URL;
12 |   const pat = process.env.AZURE_DEVOPS_PAT;
13 | 
14 |   if (orgUrl && pat) {
15 |     return {
16 |       organizationUrl: orgUrl,
17 |       authMethod: AuthenticationMethod.PersonalAccessToken,
18 |       personalAccessToken: pat,
19 |       defaultProject: process.env.AZURE_DEVOPS_DEFAULT_PROJECT,
20 |     };
21 |   }
22 | 
23 |   // If we don't have credentials, return null
24 |   return null;
25 | }
26 | 
27 | /**
28 |  * Determines if integration tests should be skipped
29 |  *
30 |  * @returns true if integration tests should be skipped
31 |  */
32 | export function shouldSkipIntegrationTest(): boolean {
33 |   if (!process.env.AZURE_DEVOPS_ORG_URL || !process.env.AZURE_DEVOPS_PAT) {
34 |     console.log(
35 |       'Skipping integration test: No real Azure DevOps connection available',
36 |     );
37 |     return true;
38 |   }
39 |   return false;
40 | }
41 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/update-wiki-page/feature.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import * as azureDevOpsClient from '../../../clients/azure-devops';
 2 | import { UpdateWikiPageSchema } from './schema';
 3 | import { z } from 'zod';
 4 | import { defaultOrg, defaultProject } from '../../../utils/environment';
 5 | 
 6 | /**
 7 |  * Options for updating a wiki page
 8 |  */
 9 | export type UpdateWikiPageOptions = z.infer<typeof UpdateWikiPageSchema>;
10 | 
11 | /**
12 |  * Updates a wiki page in Azure DevOps
13 |  * @param options - The options for updating the wiki page
14 |  * @returns The updated wiki page
15 |  */
16 | export async function updateWikiPage(options: UpdateWikiPageOptions) {
17 |   const validatedOptions = UpdateWikiPageSchema.parse(options);
18 | 
19 |   const { organizationId, projectId, wikiId, pagePath, content, comment } =
20 |     validatedOptions;
21 | 
22 |   // Create the client
23 |   const client = await azureDevOpsClient.getWikiClient({
24 |     organizationId: organizationId ?? defaultOrg,
25 |   });
26 | 
27 |   // Prepare the wiki page content
28 |   const wikiPageContent = {
29 |     content,
30 |   };
31 | 
32 |   // Update the wiki page
33 |   const updatedPage = await client.updatePage(
34 |     wikiPageContent,
35 |     projectId ?? defaultProject,
36 |     wikiId,
37 |     pagePath,
38 |     {
39 |       comment: comment ?? undefined,
40 |     },
41 |   );
42 | 
43 |   return updatedPage;
44 | }
45 | 
```

--------------------------------------------------------------------------------
/docs/examples/azure-identity-authentication.env:
--------------------------------------------------------------------------------

```
 1 | # Example .env file for Azure Identity (DefaultAzureCredential) authentication
 2 | # Replace the values with your own
 3 | 
 4 | # Authentication method (required)
 5 | AZURE_DEVOPS_AUTH_METHOD=azure-identity
 6 | 
 7 | # Azure DevOps organization URL (required)
 8 | AZURE_DEVOPS_ORG_URL=https://dev.azure.com/your-organization
 9 | 
10 | # Default project to use when not specified (optional)
11 | AZURE_DEVOPS_DEFAULT_PROJECT=your-default-project
12 | 
13 | # API Version to use (optional, defaults to latest)
14 | # AZURE_DEVOPS_API_VERSION=6.0
15 | 
16 | # Azure AD tenant ID (required for service principal authentication)
17 | # AZURE_TENANT_ID=your-tenant-id
18 | 
19 | # Azure AD client ID (required for service principal authentication)
20 | # AZURE_CLIENT_ID=your-client-id
21 | 
22 | # Azure AD client secret (required for service principal authentication)
23 | # AZURE_CLIENT_SECRET=your-client-secret
24 | 
25 | # Logging Level (optional)
26 | LOG_LEVEL=info
27 | 
28 | # Note: This server uses stdio for communication with the MCP client,
29 | # not HTTP. It does not listen on a network port.
30 | 
31 | # Note: When using DefaultAzureCredential, you don't need to set AZURE_TENANT_ID, 
32 | # AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET if you're using other credential types
33 | # like Managed Identity or Azure CLI. 
```

--------------------------------------------------------------------------------
/src/features/repositories/get-repository/feature.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import {
 3 |   AzureDevOpsResourceNotFoundError,
 4 |   AzureDevOpsError,
 5 | } from '../../../shared/errors';
 6 | import { GitRepository } from '../types';
 7 | 
 8 | /**
 9 |  * Get a repository by ID or name
10 |  *
11 |  * @param connection The Azure DevOps WebApi connection
12 |  * @param projectId The ID or name of the project
13 |  * @param repositoryId The ID or name of the repository
14 |  * @returns The repository details
15 |  * @throws {AzureDevOpsResourceNotFoundError} If the repository is not found
16 |  */
17 | export async function getRepository(
18 |   connection: WebApi,
19 |   projectId: string,
20 |   repositoryId: string,
21 | ): Promise<GitRepository> {
22 |   try {
23 |     const gitApi = await connection.getGitApi();
24 |     const repository = await gitApi.getRepository(repositoryId, projectId);
25 | 
26 |     if (!repository) {
27 |       throw new AzureDevOpsResourceNotFoundError(
28 |         `Repository '${repositoryId}' not found in project '${projectId}'`,
29 |       );
30 |     }
31 | 
32 |     return repository;
33 |   } catch (error) {
34 |     if (error instanceof AzureDevOpsError) {
35 |       throw error;
36 |     }
37 |     throw new Error(
38 |       `Failed to get repository: ${error instanceof Error ? error.message : String(error)}`,
39 |     );
40 |   }
41 | }
42 | 
```

--------------------------------------------------------------------------------
/src/features/users/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Users feature module
 3 |  *
 4 |  * This module contains user-related functionality.
 5 |  */
 6 | 
 7 | export * from './types';
 8 | export * from './get-me';
 9 | 
10 | // Export tool definitions
11 | export * from './tool-definitions';
12 | 
13 | // New exports for request handling
14 | import { CallToolRequest } from '@modelcontextprotocol/sdk/types.js';
15 | import { WebApi } from 'azure-devops-node-api';
16 | import {
17 |   RequestIdentifier,
18 |   RequestHandler,
19 | } from '../../shared/types/request-handler';
20 | import { getMe } from './';
21 | 
22 | /**
23 |  * Checks if the request is for the users feature
24 |  */
25 | export const isUsersRequest: RequestIdentifier = (
26 |   request: CallToolRequest,
27 | ): boolean => {
28 |   const toolName = request.params.name;
29 |   return ['get_me'].includes(toolName);
30 | };
31 | 
32 | /**
33 |  * Handles users feature requests
34 |  */
35 | export const handleUsersRequest: RequestHandler = async (
36 |   connection: WebApi,
37 |   request: CallToolRequest,
38 | ): Promise<{ content: Array<{ type: string; text: string }> }> => {
39 |   switch (request.params.name) {
40 |     case 'get_me': {
41 |       const result = await getMe(connection);
42 |       return {
43 |         content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
44 |       };
45 |     }
46 |     default:
47 |       throw new Error(`Unknown users tool: ${request.params.name}`);
48 |   }
49 | };
50 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/get-wikis/feature.spec.int.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { getWikis } from './feature';
 3 | import {
 4 |   getTestConnection,
 5 |   shouldSkipIntegrationTest,
 6 | } from '@/shared/test/test-helpers';
 7 | 
 8 | describe('getWikis integration', () => {
 9 |   let connection: WebApi | null = null;
10 |   let projectName: string;
11 | 
12 |   beforeAll(async () => {
13 |     // Get a real connection using environment variables
14 |     connection = await getTestConnection();
15 |     projectName = process.env.AZURE_DEVOPS_DEFAULT_PROJECT || 'DefaultProject';
16 |   });
17 | 
18 |   test('should retrieve wikis from Azure DevOps', async () => {
19 |     // Skip if no connection is available
20 |     if (shouldSkipIntegrationTest()) {
21 |       return;
22 |     }
23 | 
24 |     // This connection must be available if we didn't skip
25 |     if (!connection) {
26 |       throw new Error(
27 |         'Connection should be available when test is not skipped',
28 |       );
29 |     }
30 | 
31 |     // Get the wikis
32 |     const result = await getWikis(connection, {
33 |       projectId: projectName,
34 |     });
35 | 
36 |     // Verify the result
37 |     expect(result).toBeDefined();
38 |     expect(Array.isArray(result)).toBe(true);
39 |     if (result.length > 0) {
40 |       expect(result[0].name).toBeDefined();
41 |       expect(result[0].id).toBeDefined();
42 |       expect(result[0].type).toBeDefined();
43 |     }
44 |   });
45 | });
46 | 
```

--------------------------------------------------------------------------------
/src/features/users/get-me/feature.spec.int.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { getMe } from '../get-me';
 3 | import {
 4 |   getTestConnection,
 5 |   shouldSkipIntegrationTest,
 6 | } from '@/shared/test/test-helpers';
 7 | 
 8 | describe('getMe Integration', () => {
 9 |   let connection: WebApi | null = null;
10 | 
11 |   beforeAll(async () => {
12 |     // Get a real connection using environment variables
13 |     connection = await getTestConnection();
14 |   });
15 | 
16 |   test('should get authenticated user profile information', async () => {
17 |     // Skip if no connection is available
18 |     if (shouldSkipIntegrationTest() || !connection) {
19 |       console.log('Skipping getMe integration test - no connection available');
20 |       return;
21 |     }
22 | 
23 |     // Act - make a direct API call using Axios
24 |     const result = await getMe(connection);
25 | 
26 |     // Assert on the actual response
27 |     expect(result).toBeDefined();
28 |     expect(result.id).toBeDefined();
29 |     expect(typeof result.id).toBe('string');
30 |     expect(result.displayName).toBeDefined();
31 |     expect(typeof result.displayName).toBe('string');
32 |     expect(result.displayName.length).toBeGreaterThan(0);
33 | 
34 |     // Email should be defined, a string, and not empty
35 |     expect(result.email).toBeDefined();
36 |     expect(typeof result.email).toBe('string');
37 |     expect(result.email.length).toBeGreaterThan(0);
38 |   });
39 | });
40 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/get-work-item/feature.spec.unit.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { getWorkItem } from './feature';
 2 | import { AzureDevOpsError } from '../../../shared/errors';
 3 | 
 4 | // Unit tests should only focus on isolated logic
 5 | // No real connections, HTTP requests, or dependencies
 6 | describe('getWorkItem unit', () => {
 7 |   // Unit test for error handling logic - the only part that's suitable for a unit test
 8 |   test('should propagate custom errors when thrown internally', async () => {
 9 |     // Arrange - for unit test, we mock only what's needed
10 |     const mockConnection: any = {
11 |       getWorkItemTrackingApi: jest.fn().mockImplementation(() => {
12 |         throw new AzureDevOpsError('Custom error');
13 |       }),
14 |     };
15 | 
16 |     // Act & Assert
17 |     await expect(getWorkItem(mockConnection, 123)).rejects.toThrow(
18 |       AzureDevOpsError,
19 |     );
20 |     await expect(getWorkItem(mockConnection, 123)).rejects.toThrow(
21 |       'Custom error',
22 |     );
23 |   });
24 | 
25 |   test('should wrap unexpected errors in a friendly error message', async () => {
26 |     // Arrange
27 |     const mockConnection: any = {
28 |       getWorkItemTrackingApi: jest.fn().mockImplementation(() => {
29 |         throw new Error('Unexpected error');
30 |       }),
31 |     };
32 | 
33 |     // Act & Assert
34 |     await expect(getWorkItem(mockConnection, 123)).rejects.toThrow(
35 |       'Failed to get work item: Unexpected error',
36 |     );
37 |   });
38 | });
39 | 
```

--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: CI
 2 | 
 3 | on:
 4 |   pull_request:
 5 |     branches: [main]
 6 | 
 7 | permissions:
 8 |   contents: read
 9 | 
10 | jobs:
11 |   build:
12 |     runs-on: ubuntu-latest
13 | 
14 |     steps:
15 |       - uses: actions/checkout@v3
16 |       - name: Use latest Node LTS
17 |         uses: actions/setup-node@v3
18 |         with:
19 |           node-version: 'lts/*'
20 |       - name: Install Dependencies
21 |         run: npm install
22 |       - name: Lint
23 |         run: npm run lint
24 |       - name: Build
25 |         run: npm run build
26 |       - name: Unit Tests
27 |         run: npm run test:unit
28 |       - name: Integration Tests
29 |         run: npm run test:int
30 |         env:
31 |           CI: 'true'
32 |           AZURE_DEVOPS_ORG_URL: ${{ secrets.AZURE_DEVOPS_ORG_URL }}
33 |           AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }}
34 |           AZURE_DEVOPS_DEFAULT_PROJECT: ${{ secrets.AZURE_DEVOPS_DEFAULT_PROJECT }}
35 |           AZURE_DEVOPS_DEFAULT_REPOSITORY: eShopOnWeb
36 |           AZURE_DEVOPS_AUTH_METHOD: pat
37 |       - name: E2E Tests
38 |         run: npm run test:e2e
39 |         env:
40 |           CI: 'true'
41 |           AZURE_DEVOPS_ORG_URL: ${{ secrets.AZURE_DEVOPS_ORG_URL }}
42 |           AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }}
43 |           AZURE_DEVOPS_DEFAULT_PROJECT: ${{ secrets.AZURE_DEVOPS_DEFAULT_PROJECT }}
44 |           AZURE_DEVOPS_DEFAULT_REPOSITORY: eShopOnWeb
45 |           AZURE_DEVOPS_AUTH_METHOD: pat
46 | 
```

--------------------------------------------------------------------------------
/src/features/pipelines/trigger-pipeline/schema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { defaultProject } from '../../../utils/environment';
 3 | 
 4 | /**
 5 |  * Schema for the triggerPipeline function
 6 |  */
 7 | export const TriggerPipelineSchema = z.object({
 8 |   // The project containing the pipeline
 9 |   projectId: z
10 |     .string()
11 |     .optional()
12 |     .describe(`The ID or name of the project (Default: ${defaultProject})`),
13 |   // The ID of the pipeline to trigger
14 |   pipelineId: z
15 |     .number()
16 |     .int()
17 |     .positive()
18 |     .describe('The numeric ID of the pipeline to trigger'),
19 |   // The branch to run the pipeline on
20 |   branch: z
21 |     .string()
22 |     .optional()
23 |     .describe(
24 |       'The branch to run the pipeline on (e.g., "main", "feature/my-branch"). If left empty, the default branch will be used',
25 |     ),
26 |   // Variables to pass to the pipeline run
27 |   variables: z
28 |     .record(
29 |       z.object({
30 |         value: z.string(),
31 |         isSecret: z.boolean().optional(),
32 |       }),
33 |     )
34 |     .optional()
35 |     .describe('Variables to pass to the pipeline run'),
36 |   // Parameters for template-based pipelines
37 |   templateParameters: z
38 |     .record(z.string())
39 |     .optional()
40 |     .describe('Parameters for template-based pipelines'),
41 |   // Stages to skip in the pipeline run
42 |   stagesToSkip: z
43 |     .array(z.string())
44 |     .optional()
45 |     .describe('Stages to skip in the pipeline run'),
46 | });
47 | 
```

--------------------------------------------------------------------------------
/src/features/pull-requests/tool-definitions.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { zodToJsonSchema } from 'zod-to-json-schema';
 2 | import { ToolDefinition } from '../../shared/types/tool-definition';
 3 | import {
 4 |   CreatePullRequestSchema,
 5 |   ListPullRequestsSchema,
 6 |   GetPullRequestCommentsSchema,
 7 |   AddPullRequestCommentSchema,
 8 |   UpdatePullRequestSchema,
 9 | } from './schemas';
10 | 
11 | /**
12 |  * List of pull requests tools
13 |  */
14 | export const pullRequestsTools: ToolDefinition[] = [
15 |   {
16 |     name: 'create_pull_request',
17 |     description: 'Create a new pull request',
18 |     inputSchema: zodToJsonSchema(CreatePullRequestSchema),
19 |   },
20 |   {
21 |     name: 'list_pull_requests',
22 |     description: 'List pull requests in a repository',
23 |     inputSchema: zodToJsonSchema(ListPullRequestsSchema),
24 |   },
25 |   {
26 |     name: 'get_pull_request_comments',
27 |     description: 'Get comments from a specific pull request',
28 |     inputSchema: zodToJsonSchema(GetPullRequestCommentsSchema),
29 |   },
30 |   {
31 |     name: 'add_pull_request_comment',
32 |     description:
33 |       'Add a comment to a pull request (reply to existing comments or create new threads)',
34 |     inputSchema: zodToJsonSchema(AddPullRequestCommentSchema),
35 |   },
36 |   {
37 |     name: 'update_pull_request',
38 |     description:
39 |       'Update an existing pull request with new properties, link work items, and manage reviewers',
40 |     inputSchema: zodToJsonSchema(UpdatePullRequestSchema),
41 |   },
42 | ];
43 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/tool-definitions.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { zodToJsonSchema } from 'zod-to-json-schema';
 2 | import { ToolDefinition } from '../../shared/types/tool-definition';
 3 | import {
 4 |   GetRepositorySchema,
 5 |   GetRepositoryDetailsSchema,
 6 |   ListRepositoriesSchema,
 7 |   GetFileContentSchema,
 8 |   GetAllRepositoriesTreeSchema,
 9 | } from './schemas';
10 | 
11 | /**
12 |  * List of repositories tools
13 |  */
14 | export const repositoriesTools: ToolDefinition[] = [
15 |   {
16 |     name: 'get_repository',
17 |     description: 'Get details of a specific repository',
18 |     inputSchema: zodToJsonSchema(GetRepositorySchema),
19 |   },
20 |   {
21 |     name: 'get_repository_details',
22 |     description:
23 |       'Get detailed information about a repository including statistics and refs',
24 |     inputSchema: zodToJsonSchema(GetRepositoryDetailsSchema),
25 |   },
26 |   {
27 |     name: 'list_repositories',
28 |     description: 'List repositories in a project',
29 |     inputSchema: zodToJsonSchema(ListRepositoriesSchema),
30 |   },
31 |   {
32 |     name: 'get_file_content',
33 |     description: 'Get content of a file or directory from a repository',
34 |     inputSchema: zodToJsonSchema(GetFileContentSchema),
35 |   },
36 |   {
37 |     name: 'get_all_repositories_tree',
38 |     description:
39 |       'Displays a hierarchical tree view of files and directories across multiple Azure DevOps repositories within a project, based on their default branches',
40 |     inputSchema: zodToJsonSchema(GetAllRepositoriesTreeSchema),
41 |   },
42 | ];
43 | 
```

--------------------------------------------------------------------------------
/.github/workflows/release-please.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Release Please
 2 | 
 3 | on:
 4 |   push:
 5 |     branches:
 6 |       - main
 7 | 
 8 | permissions:
 9 |   contents: write
10 |   pull-requests: write
11 |   issues: write
12 | 
13 | jobs:
14 |   release-please:
15 |     runs-on: ubuntu-latest
16 |     steps:
17 |       - uses: google-github-actions/release-please-action@v4
18 |         id: release
19 |         with:
20 |           config-file: .github/release-please-config.json
21 |           manifest-file: .github/release-please-manifest.json
22 |           
23 |       # The following steps only run if a new release is created
24 |       - name: Checkout code
25 |         if: ${{ steps.release.outputs.release_created }}
26 |         uses: actions/checkout@v3
27 |         with:
28 |           ref: ${{ steps.release.outputs.tag_name }}
29 |           
30 |       - name: Setup Node.js
31 |         if: ${{ steps.release.outputs.release_created }}
32 |         uses: actions/setup-node@v3
33 |         with:
34 |           node-version: 'lts/*'
35 |           registry-url: 'https://registry.npmjs.org/'
36 |           
37 |       - name: Install Dependencies
38 |         if: ${{ steps.release.outputs.release_created }}
39 |         run: npm ci
40 |         
41 |       - name: Build package
42 |         if: ${{ steps.release.outputs.release_created }}
43 |         run: npm run build
44 |         
45 |       - name: Publish to npm
46 |         if: ${{ steps.release.outputs.release_created }}
47 |         run: npm publish --access public
48 |         env:
49 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 
50 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/tool-definitions.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { zodToJsonSchema } from 'zod-to-json-schema';
 2 | import { ToolDefinition } from '../../shared/types/tool-definition';
 3 | import { GetWikisSchema } from './get-wikis/schema';
 4 | import { GetWikiPageSchema } from './get-wiki-page/schema';
 5 | import { CreateWikiSchema } from './create-wiki/schema';
 6 | import { UpdateWikiPageSchema } from './update-wiki-page/schema';
 7 | import { ListWikiPagesSchema } from './list-wiki-pages/schema';
 8 | import { CreateWikiPageSchema } from './create-wiki-page/schema';
 9 | 
10 | /**
11 |  * List of wikis tools
12 |  */
13 | export const wikisTools: ToolDefinition[] = [
14 |   {
15 |     name: 'get_wikis',
16 |     description: 'Get details of wikis in a project',
17 |     inputSchema: zodToJsonSchema(GetWikisSchema),
18 |   },
19 |   {
20 |     name: 'get_wiki_page',
21 |     description: 'Get the content of a wiki page',
22 |     inputSchema: zodToJsonSchema(GetWikiPageSchema),
23 |   },
24 |   {
25 |     name: 'create_wiki',
26 |     description: 'Create a new wiki in the project',
27 |     inputSchema: zodToJsonSchema(CreateWikiSchema),
28 |   },
29 |   {
30 |     name: 'update_wiki_page',
31 |     description: 'Update content of a wiki page',
32 |     inputSchema: zodToJsonSchema(UpdateWikiPageSchema),
33 |   },
34 |   {
35 |     name: 'list_wiki_pages',
36 |     description: 'List pages within an Azure DevOps wiki',
37 |     inputSchema: zodToJsonSchema(ListWikiPagesSchema),
38 |   },
39 |   {
40 |     name: 'create_wiki_page',
41 |     description:
42 |       'Create a new page in a wiki. If the page already exists at the specified path, it will be updated.',
43 |     inputSchema: zodToJsonSchema(CreateWikiPageSchema),
44 |   },
45 | ];
46 | 
```

--------------------------------------------------------------------------------
/src/features/organizations/list-organizations/feature.spec.int.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { listOrganizations } from './feature';
 2 | import {
 3 |   getTestConfig,
 4 |   shouldSkipIntegrationTest,
 5 | } from '@/shared/test/test-helpers';
 6 | 
 7 | describe('listOrganizations integration', () => {
 8 |   test('should list organizations accessible to the authenticated user', async () => {
 9 |     // Skip if no credentials are available
10 |     if (shouldSkipIntegrationTest()) {
11 |       return;
12 |     }
13 | 
14 |     // Get test configuration
15 |     const config = getTestConfig();
16 |     if (!config) {
17 |       throw new Error(
18 |         'Configuration should be available when test is not skipped',
19 |       );
20 |     }
21 | 
22 |     // Act - make an actual API call to Azure DevOps
23 |     const result = await listOrganizations(config);
24 | 
25 |     // Assert on the actual response
26 |     expect(result).toBeDefined();
27 |     expect(Array.isArray(result)).toBe(true);
28 |     expect(result.length).toBeGreaterThan(0);
29 | 
30 |     // Check structure of returned organizations
31 |     const firstOrg = result[0];
32 |     expect(firstOrg.id).toBeDefined();
33 |     expect(firstOrg.name).toBeDefined();
34 |     expect(firstOrg.url).toBeDefined();
35 | 
36 |     // The organization URL in the config should match one of the returned organizations
37 |     // Extract the organization name from the URL
38 |     const orgUrlParts = config.organizationUrl.split('/');
39 |     const configOrgName = orgUrlParts[orgUrlParts.length - 1];
40 | 
41 |     // Find matching organization
42 |     const matchingOrg = result.find(
43 |       (org) => org.name.toLowerCase() === configOrgName.toLowerCase(),
44 |     );
45 |     expect(matchingOrg).toBeDefined();
46 |   });
47 | });
48 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   GitRepository,
 3 |   GitBranchStats,
 4 |   GitRef,
 5 |   GitItem,
 6 | } from 'azure-devops-node-api/interfaces/GitInterfaces';
 7 | 
 8 | /**
 9 |  * Options for listing repositories
10 |  */
11 | export interface ListRepositoriesOptions {
12 |   projectId: string;
13 |   includeLinks?: boolean;
14 | }
15 | 
16 | /**
17 |  * Options for getting repository details
18 |  */
19 | export interface GetRepositoryDetailsOptions {
20 |   projectId: string;
21 |   repositoryId: string;
22 |   includeStatistics?: boolean;
23 |   includeRefs?: boolean;
24 |   refFilter?: string;
25 |   branchName?: string;
26 | }
27 | 
28 | /**
29 |  * Repository details response
30 |  */
31 | export interface RepositoryDetails {
32 |   repository: GitRepository;
33 |   statistics?: {
34 |     branches: GitBranchStats[];
35 |   };
36 |   refs?: {
37 |     value: GitRef[];
38 |     count: number;
39 |   };
40 | }
41 | 
42 | /**
43 |  * Options for getting all repositories tree
44 |  */
45 | export interface GetAllRepositoriesTreeOptions {
46 |   organizationId: string;
47 |   projectId: string;
48 |   repositoryPattern?: string;
49 |   depth?: number;
50 |   pattern?: string;
51 | }
52 | 
53 | /**
54 |  * Repository tree item representation for output
55 |  */
56 | export interface RepositoryTreeItem {
57 |   name: string;
58 |   path: string;
59 |   isFolder: boolean;
60 |   level: number;
61 | }
62 | 
63 | /**
64 |  * Repository tree response for a single repository
65 |  */
66 | export interface RepositoryTreeResponse {
67 |   name: string;
68 |   tree: RepositoryTreeItem[];
69 |   stats: {
70 |     directories: number;
71 |     files: number;
72 |   };
73 |   error?: string;
74 | }
75 | 
76 | /**
77 |  * Complete all repositories tree response
78 |  */
79 | export interface AllRepositoriesTreeResponse {
80 |   repositories: RepositoryTreeResponse[];
81 | }
82 | 
83 | // Re-export GitRepository type for convenience
84 | export type { GitRepository, GitBranchStats, GitRef, GitItem };
85 | 
```

--------------------------------------------------------------------------------
/src/features/pipelines/list-pipelines/feature.spec.int.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { listPipelines } from './feature';
 3 | import {
 4 |   getTestConnection,
 5 |   shouldSkipIntegrationTest,
 6 | } from '../../../shared/test/test-helpers';
 7 | 
 8 | describe('listPipelines integration', () => {
 9 |   let connection: WebApi | null = null;
10 | 
11 |   beforeAll(async () => {
12 |     // Get a real connection using environment variables
13 |     connection = await getTestConnection();
14 | 
15 |     // TODO: Implement createPipeline functionality and create test pipelines here
16 |     // Currently there is no way to create pipelines, so we can't ensure data exists like in list-work-items tests
17 |     // In the future, we should add code similar to list-work-items to create test pipelines
18 |   });
19 | 
20 |   it('should list pipelines in a project', async () => {
21 |     // Skip if no connection is available or no project specified
22 |     if (
23 |       shouldSkipIntegrationTest() ||
24 |       !connection ||
25 |       !process.env.AZURE_DEVOPS_DEFAULT_PROJECT
26 |     ) {
27 |       console.log(
28 |         'Skipping listPipelines integration test - no connection or project available',
29 |       );
30 |       return;
31 |     }
32 | 
33 |     const projectId = process.env.AZURE_DEVOPS_DEFAULT_PROJECT;
34 | 
35 |     const pipelines = await listPipelines(connection, { projectId });
36 |     expect(Array.isArray(pipelines)).toBe(true);
37 | 
38 |     // If there are pipelines, check their structure
39 |     if (pipelines.length > 0) {
40 |       const pipeline = pipelines[0];
41 |       expect(pipeline.id).toBeDefined();
42 |       expect(pipeline.name).toBeDefined();
43 |       expect(pipeline.folder).toBeDefined();
44 |       expect(pipeline.revision).toBeDefined();
45 |       expect(pipeline.url).toBeDefined();
46 |     }
47 |   });
48 | });
49 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/create-wiki/schema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | import { defaultProject, defaultOrg } from '../../../utils/environment';
 4 | 
 5 | /**
 6 |  * Wiki types for creating wiki
 7 |  */
 8 | export enum WikiType {
 9 |   /**
10 |    * The wiki is published from a git repository
11 |    */
12 |   CodeWiki = 'codeWiki',
13 | 
14 |   /**
15 |    * The wiki is provisioned for the team project
16 |    */
17 |   ProjectWiki = 'projectWiki',
18 | }
19 | 
20 | /**
21 |  * Schema for creating a wiki in an Azure DevOps project
22 |  */
23 | export const CreateWikiSchema = z
24 |   .object({
25 |     organizationId: z
26 |       .string()
27 |       .optional()
28 |       .nullable()
29 |       .describe(`The ID or name of the organization (Default: ${defaultOrg})`),
30 |     projectId: z
31 |       .string()
32 |       .optional()
33 |       .nullable()
34 |       .describe(`The ID or name of the project (Default: ${defaultProject})`),
35 |     name: z.string().describe('The name of the new wiki'),
36 |     type: z
37 |       .nativeEnum(WikiType)
38 |       .optional()
39 |       .default(WikiType.ProjectWiki)
40 |       .describe('Type of wiki to create (projectWiki or codeWiki)'),
41 |     repositoryId: z
42 |       .string()
43 |       .optional()
44 |       .nullable()
45 |       .describe(
46 |         'The ID of the repository to associate with the wiki (required for codeWiki)',
47 |       ),
48 |     mappedPath: z
49 |       .string()
50 |       .optional()
51 |       .nullable()
52 |       .default('/')
53 |       .describe(
54 |         'Folder path inside repository which is shown as Wiki (only for codeWiki)',
55 |       ),
56 |   })
57 |   .refine(
58 |     (data) => {
59 |       // If type is codeWiki, then repositoryId is required
60 |       return data.type !== WikiType.CodeWiki || !!data.repositoryId;
61 |     },
62 |     {
63 |       message: 'repositoryId is required when type is codeWiki',
64 |       path: ['repositoryId'],
65 |     },
66 |   );
67 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/get-wiki-page/feature.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import * as azureDevOpsClient from '../../../clients/azure-devops';
 2 | import { AzureDevOpsError } from '../../../shared/errors/azure-devops-errors';
 3 | 
 4 | /**
 5 |  * Options for getting a wiki page
 6 |  */
 7 | export interface GetWikiPageOptions {
 8 |   /**
 9 |    * The ID or name of the organization
10 |    * If not provided, the default organization will be used
11 |    */
12 |   organizationId: string;
13 | 
14 |   /**
15 |    * The ID or name of the project
16 |    * If not provided, the default project will be used
17 |    */
18 |   projectId: string;
19 | 
20 |   /**
21 |    * The ID or name of the wiki
22 |    */
23 |   wikiId: string;
24 | 
25 |   /**
26 |    * The path of the page within the wiki
27 |    */
28 |   pagePath: string;
29 | }
30 | 
31 | /**
32 |  * Get a wiki page from a wiki
33 |  *
34 |  * @param options Options for getting a wiki page
35 |  * @returns Wiki page content as text/plain
36 |  * @throws {AzureDevOpsResourceNotFoundError} When the wiki page is not found
37 |  * @throws {AzureDevOpsPermissionError} When the user does not have permission to access the wiki page
38 |  * @throws {AzureDevOpsError} When an error occurs while fetching the wiki page
39 |  */
40 | export async function getWikiPage(
41 |   options: GetWikiPageOptions,
42 | ): Promise<string> {
43 |   const { organizationId, projectId, wikiId, pagePath } = options;
44 | 
45 |   try {
46 |     // Create the client
47 |     const client = await azureDevOpsClient.getWikiClient({
48 |       organizationId,
49 |     });
50 | 
51 |     // Get the wiki page
52 |     return (await client.getPage(projectId, wikiId, pagePath)).content;
53 |   } catch (error) {
54 |     // If it's already an AzureDevOpsError, rethrow it
55 |     if (error instanceof AzureDevOpsError) {
56 |       throw error;
57 |     }
58 |     // Otherwise wrap it in an AzureDevOpsError
59 |     throw new AzureDevOpsError('Failed to get wiki page', { cause: error });
60 |   }
61 | }
62 | 
```

--------------------------------------------------------------------------------
/src/shared/test/test-helpers.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { getPersonalAccessTokenHandler } from 'azure-devops-node-api';
 3 | import { AzureDevOpsConfig } from '../types';
 4 | import { AuthenticationMethod } from '../auth';
 5 | 
 6 | /**
 7 |  * Creates a WebApi connection for tests with real credentials
 8 |  *
 9 |  * @returns WebApi connection
10 |  */
11 | export async function getTestConnection(): Promise<WebApi | null> {
12 |   // If we have real credentials, use them
13 |   const orgUrl = process.env.AZURE_DEVOPS_ORG_URL;
14 |   const token = process.env.AZURE_DEVOPS_PAT;
15 | 
16 |   if (orgUrl && token) {
17 |     const authHandler = getPersonalAccessTokenHandler(token);
18 |     return new WebApi(orgUrl, authHandler);
19 |   }
20 | 
21 |   // If we don't have credentials, return null
22 |   return null;
23 | }
24 | 
25 | /**
26 |  * Creates test configuration for Azure DevOps tests
27 |  *
28 |  * @returns Azure DevOps config
29 |  */
30 | export function getTestConfig(): AzureDevOpsConfig | null {
31 |   // If we have real credentials, use them
32 |   const orgUrl = process.env.AZURE_DEVOPS_ORG_URL;
33 |   const pat = process.env.AZURE_DEVOPS_PAT;
34 | 
35 |   if (orgUrl && pat) {
36 |     return {
37 |       organizationUrl: orgUrl,
38 |       authMethod: AuthenticationMethod.PersonalAccessToken,
39 |       personalAccessToken: pat,
40 |       defaultProject: process.env.AZURE_DEVOPS_DEFAULT_PROJECT,
41 |     };
42 |   }
43 | 
44 |   // If we don't have credentials, return null
45 |   return null;
46 | }
47 | 
48 | /**
49 |  * Determines if integration tests should be skipped
50 |  *
51 |  * @returns true if integration tests should be skipped
52 |  */
53 | export function shouldSkipIntegrationTest(): boolean {
54 |   if (!process.env.AZURE_DEVOPS_ORG_URL || !process.env.AZURE_DEVOPS_PAT) {
55 |     console.log(
56 |       'Skipping integration test: No real Azure DevOps connection available',
57 |     );
58 |     return true;
59 |   }
60 |   return false;
61 | }
62 | 
```

--------------------------------------------------------------------------------
/docs/testing/setup.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Testing Setup Guide
 2 | 
 3 | ## Environment Variables
 4 | 
 5 | Tests that interact with Azure DevOps APIs (integration and e2e tests) require environment variables to run properly. These variables are automatically loaded from your `.env` file during test execution.
 6 | 
 7 | Required variables:
 8 | ```
 9 | AZURE_DEVOPS_ORG_URL=https://dev.azure.com/your-organization
10 | AZURE_DEVOPS_PAT=your-personal-access-token
11 | AZURE_DEVOPS_DEFAULT_PROJECT=your-project-name
12 | ```
13 | 
14 | ## Test Structure
15 | 
16 | Tests in this project are co-located with the code they're testing:
17 | 
18 | ```
19 | src/
20 |   features/
21 |     feature-name/
22 |       feature.ts
23 |       feature.spec.unit.ts   # Unit tests
24 |       feature.spec.int.ts    # Integration tests
25 | ```
26 | 
27 | E2E tests are only located at the server level:
28 | 
29 | ```
30 | src/
31 |   server.ts
32 |   server.spec.e2e.ts   # E2E tests
33 | ```
34 | 
35 | ## Import Pattern
36 | 
37 | We use path aliases to make imports cleaner and easier to maintain. Instead of relative imports like:
38 | 
39 | ```typescript
40 | import { someFunction } from '../../../../shared/utils';
41 | ```
42 | 
43 | You can use the `@/` path alias:
44 | 
45 | ```typescript
46 | import { someFunction } from '@/shared/utils';
47 | ```
48 | 
49 | ### Test Helpers
50 | 
51 | Test helpers are located in a centralized location for all tests:
52 | 
53 | ```typescript
54 | import { getTestConnection, shouldSkipIntegrationTest } from '@/shared/test/test-helpers';
55 | ```
56 | 
57 | ## Running Tests
58 | 
59 | - Unit tests: `npm run test:unit`
60 | - Integration tests: `npm run test:int`
61 | - E2E tests: `npm run test:e2e`
62 | - All tests: `npm test`
63 | 
64 | ## VSCode Integration
65 | 
66 | The project includes VSCode settings that:
67 | 
68 | 1. Show proper test icons for `*.spec.*.ts` files
69 | 2. Enable file nesting to group test files with their implementation
70 | 3. Configure TypeScript to prefer path aliases over relative imports
71 | 
72 | These settings are stored in `.vscode/settings.json`. 
```

--------------------------------------------------------------------------------
/src/features/work-items/update-work-item/feature.spec.unit.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { updateWorkItem } from './feature';
 2 | import { AzureDevOpsError } from '../../../shared/errors';
 3 | 
 4 | // Unit tests should only focus on isolated logic
 5 | // No real connections, HTTP requests, or dependencies
 6 | describe('updateWorkItem unit', () => {
 7 |   test('should throw error when no fields are provided for update', async () => {
 8 |     // Arrange - mock connection, never used due to validation error
 9 |     const mockConnection: any = {
10 |       getWorkItemTrackingApi: jest.fn(),
11 |     };
12 | 
13 |     // Act & Assert - empty options object should throw
14 |     await expect(
15 |       updateWorkItem(
16 |         mockConnection,
17 |         123,
18 |         {}, // No fields to update
19 |       ),
20 |     ).rejects.toThrow('At least one field must be provided for update');
21 |   });
22 | 
23 |   test('should propagate custom errors when thrown internally', async () => {
24 |     // Arrange
25 |     const mockConnection: any = {
26 |       getWorkItemTrackingApi: jest.fn().mockImplementation(() => {
27 |         throw new AzureDevOpsError('Custom error');
28 |       }),
29 |     };
30 | 
31 |     // Act & Assert
32 |     await expect(
33 |       updateWorkItem(mockConnection, 123, { title: 'Updated Title' }),
34 |     ).rejects.toThrow(AzureDevOpsError);
35 | 
36 |     await expect(
37 |       updateWorkItem(mockConnection, 123, { title: 'Updated Title' }),
38 |     ).rejects.toThrow('Custom error');
39 |   });
40 | 
41 |   test('should wrap unexpected errors in a friendly error message', async () => {
42 |     // Arrange
43 |     const mockConnection: any = {
44 |       getWorkItemTrackingApi: jest.fn().mockImplementation(() => {
45 |         throw new Error('Unexpected error');
46 |       }),
47 |     };
48 | 
49 |     // Act & Assert
50 |     await expect(
51 |       updateWorkItem(mockConnection, 123, { title: 'Updated Title' }),
52 |     ).rejects.toThrow('Failed to update work item: Unexpected error');
53 |   });
54 | });
55 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/create-wiki/feature.spec.int.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { createWiki } from './feature';
 3 | import { WikiType } from './schema';
 4 | import { getTestConnection } from '@/shared/test/test-helpers';
 5 | import axios from 'axios';
 6 | 
 7 | axios.interceptors.request.use((request) => {
 8 |   console.log('Starting Request', JSON.stringify(request, null, 2));
 9 |   return request;
10 | });
11 | 
12 | describe('createWiki (Integration)', () => {
13 |   let connection: WebApi | null = null;
14 |   let projectName: string;
15 |   const testWikiName = `TestWiki_${new Date().getTime()}`;
16 | 
17 |   beforeAll(async () => {
18 |     // Get a real connection using environment variables
19 |     connection = await getTestConnection();
20 |     projectName = process.env.AZURE_DEVOPS_DEFAULT_PROJECT || 'DefaultProject';
21 |   });
22 | 
23 |   test.skip('should create a project wiki', async () => {
24 |     // PERMANENTLY SKIPPED: Azure DevOps only allows one wiki per project.
25 |     // Running this test multiple times would fail after the first wiki is created.
26 |     // This test is kept for reference but cannot be run repeatedly.
27 | 
28 |     // This connection must be available if we didn't skip
29 |     if (!connection) {
30 |       throw new Error(
31 |         'Connection should be available when test is not skipped',
32 |       );
33 |     }
34 | 
35 |     // Create the wiki
36 |     const wiki = await createWiki(connection, {
37 |       name: testWikiName,
38 |       projectId: projectName,
39 |       type: WikiType.ProjectWiki,
40 |     });
41 | 
42 |     // Verify the wiki was created
43 |     expect(wiki).toBeDefined();
44 |     expect(wiki.name).toBe(testWikiName);
45 |     expect(wiki.projectId).toBe(projectName);
46 |     expect(wiki.type).toBe(WikiType.ProjectWiki);
47 |   });
48 | 
49 |   // NOTE: We're not testing code wiki creation since that requires a repository
50 |   // that would need to be created/cleaned up and is outside the scope of this test
51 | });
52 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/list-wiki-pages/feature.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import * as azureDevOpsClient from '../../../clients/azure-devops';
 2 | import { AzureDevOpsError } from '../../../shared/errors/azure-devops-errors';
 3 | import { defaultOrg, defaultProject } from '../../../utils/environment';
 4 | import { ListWikiPagesOptions } from './schema';
 5 | 
 6 | /**
 7 |  * Summary information for a wiki page
 8 |  */
 9 | export interface WikiPageSummary {
10 |   id: number;
11 |   path: string;
12 |   url?: string;
13 |   order?: number;
14 | }
15 | 
16 | /**
17 |  * List wiki pages from a wiki
18 |  *
19 |  * @param options Options for listing wiki pages
20 |  * @returns Array of wiki page summaries
21 |  * @throws {AzureDevOpsResourceNotFoundError} When the wiki is not found
22 |  * @throws {AzureDevOpsPermissionError} When the user does not have permission to access the wiki
23 |  * @throws {AzureDevOpsError} When an error occurs while fetching the wiki pages
24 |  */
25 | export async function listWikiPages(
26 |   options: ListWikiPagesOptions,
27 | ): Promise<WikiPageSummary[]> {
28 |   const { organizationId, projectId, wikiId } = options;
29 | 
30 |   // Use defaults if not provided
31 |   const orgId = organizationId || defaultOrg;
32 |   const projId = projectId || defaultProject;
33 | 
34 |   try {
35 |     // Create the client
36 |     const client = await azureDevOpsClient.getWikiClient({
37 |       organizationId: orgId,
38 |     });
39 | 
40 |     // Get the wiki pages
41 |     const pages = await client.listWikiPages(projId, wikiId);
42 | 
43 |     // Return the pages directly since the client interface now matches our requirements
44 |     return pages.map((page) => ({
45 |       id: page.id,
46 |       path: page.path,
47 |       url: page.url,
48 |       order: page.order,
49 |     }));
50 |   } catch (error) {
51 |     // If it's already an AzureDevOpsError, rethrow it
52 |     if (error instanceof AzureDevOpsError) {
53 |       throw error;
54 |     }
55 |     // Otherwise wrap it in an AzureDevOpsError
56 |     throw new AzureDevOpsError('Failed to list wiki pages', { cause: error });
57 |   }
58 | }
59 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/update-wiki-page/feature.spec.int.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { updateWikiPage } from './feature';
 3 | import {
 4 |   getTestConnection,
 5 |   shouldSkipIntegrationTest,
 6 | } from '@/shared/test/test-helpers';
 7 | 
 8 | describe('updateWikiPage integration', () => {
 9 |   let connection: WebApi | null = null;
10 |   let projectName: string;
11 |   let organizationName: string;
12 |   let wikiId: string;
13 | 
14 |   beforeAll(async () => {
15 |     // Get a real connection using environment variables
16 |     connection = await getTestConnection();
17 |     projectName = process.env.AZURE_DEVOPS_DEFAULT_PROJECT || 'DefaultProject';
18 |     organizationName = process.env.AZURE_DEVOPS_ORGANIZATION || '';
19 |     // Note: You'll need to set this to a valid wiki ID in your environment
20 |     wikiId = `${projectName}.wiki`;
21 |   });
22 | 
23 |   test('should update a wiki page in Azure DevOps', async () => {
24 |     // Skip if no connection is available
25 |     if (shouldSkipIntegrationTest()) {
26 |       return;
27 |     }
28 | 
29 |     // This connection must be available if we didn't skip
30 |     if (!connection) {
31 |       throw new Error(
32 |         'Connection should be available when test is not skipped',
33 |       );
34 |     }
35 | 
36 |     // Skip if no wiki ID is provided
37 |     if (!wikiId) {
38 |       console.log('Skipping test: No wiki ID provided');
39 |       return;
40 |     }
41 | 
42 |     const testPagePath = '/test-page';
43 |     const testContent = '# Test Content\nThis is a test update.';
44 |     const testComment = 'Test update from integration test';
45 | 
46 |     // Update the wiki page
47 |     const result = await updateWikiPage({
48 |       organizationId: organizationName,
49 |       projectId: projectName,
50 |       wikiId: wikiId,
51 |       pagePath: testPagePath,
52 |       content: testContent,
53 |       comment: testComment,
54 |     });
55 | 
56 |     // Verify the result
57 |     expect(result).toBeDefined();
58 |     expect(result.path).toBe(testPagePath);
59 |     expect(result.content).toBe(testContent);
60 |   });
61 | });
62 | 
```

--------------------------------------------------------------------------------
/tests/setup.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Jest setup file that runs before all tests
 3 |  */
 4 | 
 5 | import dotenv from 'dotenv';
 6 | import path from 'path';
 7 | 
 8 | // Load environment variables from .env file
 9 | // Use silent mode to prevent warning when .env file is not found
10 | const result = dotenv.config({
11 |   path: path.resolve(process.cwd(), '.env'),
12 | });
13 | 
14 | // Only log if .env file was successfully loaded and DEBUG=true
15 | if (!result.error && process.env.DEBUG === 'true') {
16 |   console.log('Environment variables loaded from .env file');
17 | }
18 | 
19 | // Increase timeout for integration tests
20 | jest.setTimeout(30000); // 30 seconds
21 | 
22 | // Suppress console output during tests unless specifically desired
23 | const originalConsoleLog = console.log;
24 | const originalConsoleWarn = console.warn;
25 | const originalConsoleError = console.error;
26 | 
27 | if (process.env.DEBUG !== 'true') {
28 |   global.console.log = (...args: any[]) => {
29 |     if (
30 |       args[0]?.toString().includes('Skip') ||
31 |       args[0]?.toString().includes('Environment')
32 |     ) {
33 |       originalConsoleLog(...args);
34 |     }
35 |   };
36 | 
37 |   global.console.warn = (...args: any[]) => {
38 |     if (args[0]?.toString().includes('Warning')) {
39 |       originalConsoleWarn(...args);
40 |     }
41 |   };
42 | 
43 |   global.console.error = (...args: any[]) => {
44 |     originalConsoleError(...args);
45 |   };
46 | }
47 | 
48 | // Global setup before tests run
49 | beforeAll(() => {
50 |   console.log('Starting tests with Testing Trophy approach...');
51 | });
52 | 
53 | // Global cleanup after all tests
54 | afterAll(() => {
55 |   console.log('All tests completed.');
56 | });
57 | 
58 | // Clear all mocks before each test
59 | beforeEach(() => {
60 |   jest.clearAllMocks();
61 |   jest.spyOn(console, 'log').mockImplementation(originalConsoleLog);
62 |   jest.spyOn(console, 'warn').mockImplementation(originalConsoleWarn);
63 |   jest.spyOn(console, 'error').mockImplementation(originalConsoleError);
64 | });
65 | 
66 | // Restore all mocks after each test
67 | afterEach(() => {
68 |   jest.restoreAllMocks();
69 | });
70 | 
```

--------------------------------------------------------------------------------
/src/features/work-items/create-work-item/feature.spec.unit.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { createWorkItem } from './feature';
 2 | import { AzureDevOpsError } from '../../../shared/errors';
 3 | 
 4 | // Unit tests should only focus on isolated logic
 5 | // No real connections, HTTP requests, or dependencies
 6 | describe('createWorkItem unit', () => {
 7 |   // Test for required title validation
 8 |   test('should throw error when title is not provided', async () => {
 9 |     // Arrange - mock connection, never used due to validation error
10 |     const mockConnection: any = {
11 |       getWorkItemTrackingApi: jest.fn(),
12 |     };
13 | 
14 |     // Act & Assert
15 |     await expect(
16 |       createWorkItem(
17 |         mockConnection,
18 |         'TestProject',
19 |         'Task',
20 |         { title: '' }, // Empty title
21 |       ),
22 |     ).rejects.toThrow('Title is required');
23 |   });
24 | 
25 |   // Test for error propagation
26 |   test('should propagate custom errors when thrown internally', async () => {
27 |     // Arrange
28 |     const mockConnection: any = {
29 |       getWorkItemTrackingApi: jest.fn().mockImplementation(() => {
30 |         throw new AzureDevOpsError('Custom error');
31 |       }),
32 |     };
33 | 
34 |     // Act & Assert
35 |     await expect(
36 |       createWorkItem(mockConnection, 'TestProject', 'Task', {
37 |         title: 'Test Task',
38 |       }),
39 |     ).rejects.toThrow(AzureDevOpsError);
40 | 
41 |     await expect(
42 |       createWorkItem(mockConnection, 'TestProject', 'Task', {
43 |         title: 'Test Task',
44 |       }),
45 |     ).rejects.toThrow('Custom error');
46 |   });
47 | 
48 |   test('should wrap unexpected errors in a friendly error message', async () => {
49 |     // Arrange
50 |     const mockConnection: any = {
51 |       getWorkItemTrackingApi: jest.fn().mockImplementation(() => {
52 |         throw new Error('Unexpected error');
53 |       }),
54 |     };
55 | 
56 |     // Act & Assert
57 |     await expect(
58 |       createWorkItem(mockConnection, 'TestProject', 'Task', {
59 |         title: 'Test Task',
60 |       }),
61 |     ).rejects.toThrow('Failed to create work item: Unexpected error');
62 |   });
63 | });
64 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/get-wikis/feature.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { WikiV2 } from 'azure-devops-node-api/interfaces/WikiInterfaces';
 3 | import {
 4 |   AzureDevOpsError,
 5 |   AzureDevOpsResourceNotFoundError,
 6 | } from '../../../shared/errors';
 7 | 
 8 | /**
 9 |  * Options for getting wikis
10 |  */
11 | export interface GetWikisOptions {
12 |   /**
13 |    * The ID or name of the organization
14 |    * If not provided, the default organization will be used
15 |    */
16 |   organizationId?: string;
17 | 
18 |   /**
19 |    * The ID or name of the project
20 |    * If not provided, the wikis from all projects will be returned
21 |    */
22 |   projectId?: string;
23 | }
24 | 
25 | /**
26 |  * Get wikis in a project or organization
27 |  *
28 |  * @param connection The Azure DevOps WebApi connection
29 |  * @param options Options for getting wikis
30 |  * @returns List of wikis
31 |  */
32 | export async function getWikis(
33 |   connection: WebApi,
34 |   options: GetWikisOptions,
35 | ): Promise<WikiV2[]> {
36 |   try {
37 |     // Get the Wiki API client
38 |     const wikiApi = await connection.getWikiApi();
39 | 
40 |     // If a projectId is provided, get wikis for that specific project
41 |     // Otherwise, get wikis for the entire organization
42 |     const { projectId } = options;
43 | 
44 |     const wikis = await wikiApi.getAllWikis(projectId);
45 | 
46 |     return wikis || [];
47 |   } catch (error) {
48 |     // Handle resource not found errors specifically
49 |     if (
50 |       error instanceof Error &&
51 |       error.message &&
52 |       error.message.includes('The resource cannot be found')
53 |     ) {
54 |       throw new AzureDevOpsResourceNotFoundError(
55 |         `Resource not found: ${options.projectId ? `Project '${options.projectId}'` : 'Organization'}`,
56 |       );
57 |     }
58 | 
59 |     // If it's already an AzureDevOpsError, rethrow it
60 |     if (error instanceof AzureDevOpsError) {
61 |       throw error;
62 |     }
63 | 
64 |     // Otherwise, wrap it in a generic error
65 |     throw new AzureDevOpsError(
66 |       `Failed to get wikis: ${error instanceof Error ? error.message : String(error)}`,
67 |     );
68 |   }
69 | }
70 | 
```

--------------------------------------------------------------------------------
/src/features/pipelines/list-pipelines/feature.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import {
 3 |   AzureDevOpsError,
 4 |   AzureDevOpsAuthenticationError,
 5 |   AzureDevOpsResourceNotFoundError,
 6 | } from '../../../shared/errors';
 7 | import { ListPipelinesOptions, Pipeline } from '../types';
 8 | 
 9 | /**
10 |  * List pipelines in a project
11 |  *
12 |  * @param connection The Azure DevOps WebApi connection
13 |  * @param options Options for listing pipelines
14 |  * @returns List of pipelines
15 |  */
16 | export async function listPipelines(
17 |   connection: WebApi,
18 |   options: ListPipelinesOptions,
19 | ): Promise<Pipeline[]> {
20 |   try {
21 |     const pipelinesApi = await connection.getPipelinesApi();
22 |     const { projectId, orderBy, top, continuationToken } = options;
23 | 
24 |     // Call the pipelines API to get the list of pipelines
25 |     const pipelines = await pipelinesApi.listPipelines(
26 |       projectId,
27 |       orderBy,
28 |       top,
29 |       continuationToken,
30 |     );
31 | 
32 |     return pipelines;
33 |   } catch (error) {
34 |     // Handle specific error types
35 |     if (error instanceof AzureDevOpsError) {
36 |       throw error;
37 |     }
38 | 
39 |     // Check for specific error types and convert to appropriate Azure DevOps errors
40 |     if (error instanceof Error) {
41 |       if (
42 |         error.message.includes('Authentication') ||
43 |         error.message.includes('Unauthorized') ||
44 |         error.message.includes('401')
45 |       ) {
46 |         throw new AzureDevOpsAuthenticationError(
47 |           `Failed to authenticate: ${error.message}`,
48 |         );
49 |       }
50 | 
51 |       if (
52 |         error.message.includes('not found') ||
53 |         error.message.includes('does not exist') ||
54 |         error.message.includes('404')
55 |       ) {
56 |         throw new AzureDevOpsResourceNotFoundError(
57 |           `Project or resource not found: ${error.message}`,
58 |         );
59 |       }
60 |     }
61 | 
62 |     // Otherwise, wrap it in a generic error
63 |     throw new AzureDevOpsError(
64 |       `Failed to list pipelines: ${error instanceof Error ? error.message : String(error)}`,
65 |     );
66 |   }
67 | }
68 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/get-repository/feature.spec.unit.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { getRepository } from './feature';
 2 | import {
 3 |   AzureDevOpsError,
 4 |   AzureDevOpsResourceNotFoundError,
 5 | } from '../../../shared/errors';
 6 | 
 7 | // Unit tests should only focus on isolated logic
 8 | // No real connections, HTTP requests, or dependencies
 9 | describe('getRepository unit', () => {
10 |   test('should propagate resource not found errors', async () => {
11 |     // Arrange
12 |     const mockConnection: any = {
13 |       getGitApi: jest.fn().mockImplementation(() => ({
14 |         getRepository: jest.fn().mockResolvedValue(null), // Simulate repository not found
15 |       })),
16 |     };
17 | 
18 |     // Act & Assert
19 |     await expect(
20 |       getRepository(mockConnection, 'test-project', 'non-existent-repo'),
21 |     ).rejects.toThrow(AzureDevOpsResourceNotFoundError);
22 | 
23 |     await expect(
24 |       getRepository(mockConnection, 'test-project', 'non-existent-repo'),
25 |     ).rejects.toThrow(
26 |       "Repository 'non-existent-repo' not found in project 'test-project'",
27 |     );
28 |   });
29 | 
30 |   test('should propagate custom errors when thrown internally', async () => {
31 |     // Arrange
32 |     const mockConnection: any = {
33 |       getGitApi: jest.fn().mockImplementation(() => {
34 |         throw new AzureDevOpsError('Custom error');
35 |       }),
36 |     };
37 | 
38 |     // Act & Assert
39 |     await expect(
40 |       getRepository(mockConnection, 'test-project', 'test-repo'),
41 |     ).rejects.toThrow(AzureDevOpsError);
42 | 
43 |     await expect(
44 |       getRepository(mockConnection, 'test-project', 'test-repo'),
45 |     ).rejects.toThrow('Custom error');
46 |   });
47 | 
48 |   test('should wrap unexpected errors in a friendly error message', async () => {
49 |     // Arrange
50 |     const mockConnection: any = {
51 |       getGitApi: jest.fn().mockImplementation(() => {
52 |         throw new Error('Unexpected error');
53 |       }),
54 |     };
55 | 
56 |     // Act & Assert
57 |     await expect(
58 |       getRepository(mockConnection, 'test-project', 'test-repo'),
59 |     ).rejects.toThrow('Failed to get repository: Unexpected error');
60 |   });
61 | });
62 | 
```

--------------------------------------------------------------------------------
/docs/ci-setup.md:
--------------------------------------------------------------------------------

```markdown
 1 | # CI Environment Setup for Integration Tests
 2 | 
 3 | This document explains how to set up the CI environment to run integration tests with Azure DevOps.
 4 | 
 5 | ## GitHub Secrets Configuration
 6 | 
 7 | To run integration tests in the CI environment, you need to configure the following GitHub Secrets:
 8 | 
 9 | 1. **AZURE_DEVOPS_ORG_URL**: The URL of your Azure DevOps organization (e.g., `https://dev.azure.com/your-organization`)
10 | 2. **AZURE_DEVOPS_PAT**: A Personal Access Token with appropriate permissions
11 | 3. **AZURE_DEVOPS_DEFAULT_PROJECT** (optional): The default project to use for tests
12 | 
13 | ### Setting up GitHub Secrets
14 | 
15 | 1. Go to your GitHub repository
16 | 2. Click on "Settings" > "Secrets and variables" > "Actions"
17 | 3. Click on "New repository secret"
18 | 4. Add each of the required secrets:
19 | 
20 | #### AZURE_DEVOPS_ORG_URL
21 | 
22 | - Name: `AZURE_DEVOPS_ORG_URL`
23 | - Value: `https://dev.azure.com/your-organization`
24 | 
25 | #### AZURE_DEVOPS_PAT
26 | 
27 | - Name: `AZURE_DEVOPS_PAT`
28 | - Value: Your Personal Access Token
29 | 
30 | #### AZURE_DEVOPS_DEFAULT_PROJECT (optional)
31 | 
32 | - Name: `AZURE_DEVOPS_DEFAULT_PROJECT`
33 | - Value: Your project name
34 | 
35 | ## Personal Access Token (PAT) Requirements
36 | 
37 | The PAT used for integration tests should have the following permissions:
38 | 
39 | - **Code**: Read & Write
40 | - **Work Items**: Read & Write
41 | - **Build**: Read & Execute
42 | - **Project and Team**: Read
43 | - **Graph**: Read
44 | - **Release**: Read & Execute
45 | 
46 | ## Security Considerations
47 | 
48 | - Use a dedicated Azure DevOps organization or project for testing
49 | - Create a PAT with the minimum required permissions
50 | - Consider setting an expiration date for the PAT
51 | - Regularly rotate the PAT used in GitHub Secrets
52 | 
53 | ## Troubleshooting
54 | 
55 | If integration tests fail in CI:
56 | 
57 | 1. Check the GitHub Actions logs for detailed error messages
58 | 2. Verify that the PAT has not expired
59 | 3. Ensure the PAT has the required permissions
60 | 4. Confirm that the organization URL is correct
61 | 5. Check if the default project exists and is accessible with the provided PAT
62 | 
```

--------------------------------------------------------------------------------
/src/features/pull-requests/create-pull-request/feature.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { AzureDevOpsError } from '../../../shared/errors';
 3 | import { CreatePullRequestOptions, PullRequest } from '../types';
 4 | 
 5 | /**
 6 |  * Create a pull request
 7 |  *
 8 |  * @param connection The Azure DevOps WebApi connection
 9 |  * @param projectId The ID or name of the project
10 |  * @param repositoryId The ID or name of the repository
11 |  * @param options Options for creating the pull request
12 |  * @returns The created pull request
13 |  */
14 | export async function createPullRequest(
15 |   connection: WebApi,
16 |   projectId: string,
17 |   repositoryId: string,
18 |   options: CreatePullRequestOptions,
19 | ): Promise<PullRequest> {
20 |   try {
21 |     if (!options.title) {
22 |       throw new Error('Title is required');
23 |     }
24 | 
25 |     if (!options.sourceRefName) {
26 |       throw new Error('Source branch is required');
27 |     }
28 | 
29 |     if (!options.targetRefName) {
30 |       throw new Error('Target branch is required');
31 |     }
32 | 
33 |     const gitApi = await connection.getGitApi();
34 | 
35 |     // Create the pull request object
36 |     const pullRequest: PullRequest = {
37 |       title: options.title,
38 |       description: options.description,
39 |       sourceRefName: options.sourceRefName,
40 |       targetRefName: options.targetRefName,
41 |       isDraft: options.isDraft || false,
42 |       workItemRefs: options.workItemRefs?.map((id) => ({
43 |         id: id.toString(),
44 |       })),
45 |       reviewers: options.reviewers?.map((reviewer) => ({
46 |         id: reviewer,
47 |         isRequired: true,
48 |       })),
49 |       ...options.additionalProperties,
50 |     };
51 | 
52 |     // Create the pull request
53 |     const createdPullRequest = await gitApi.createPullRequest(
54 |       pullRequest,
55 |       repositoryId,
56 |       projectId,
57 |     );
58 | 
59 |     if (!createdPullRequest) {
60 |       throw new Error('Failed to create pull request');
61 |     }
62 | 
63 |     return createdPullRequest;
64 |   } catch (error) {
65 |     if (error instanceof AzureDevOpsError) {
66 |       throw error;
67 |     }
68 |     throw new Error(
69 |       `Failed to create pull request: ${error instanceof Error ? error.message : String(error)}`,
70 |     );
71 |   }
72 | }
73 | 
```

--------------------------------------------------------------------------------
/src/features/search/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export * from './schemas';
 2 | export * from './types';
 3 | export * from './search-code';
 4 | export * from './search-wiki';
 5 | export * from './search-work-items';
 6 | 
 7 | // Export tool definitions
 8 | export * from './tool-definitions';
 9 | 
10 | // New exports for request handling
11 | import { CallToolRequest } from '@modelcontextprotocol/sdk/types.js';
12 | import { WebApi } from 'azure-devops-node-api';
13 | import {
14 |   RequestIdentifier,
15 |   RequestHandler,
16 | } from '../../shared/types/request-handler';
17 | import {
18 |   SearchCodeSchema,
19 |   SearchWikiSchema,
20 |   SearchWorkItemsSchema,
21 |   searchCode,
22 |   searchWiki,
23 |   searchWorkItems,
24 | } from './';
25 | 
26 | /**
27 |  * Checks if the request is for the search feature
28 |  */
29 | export const isSearchRequest: RequestIdentifier = (
30 |   request: CallToolRequest,
31 | ): boolean => {
32 |   const toolName = request.params.name;
33 |   return ['search_code', 'search_wiki', 'search_work_items'].includes(toolName);
34 | };
35 | 
36 | /**
37 |  * Handles search feature requests
38 |  */
39 | export const handleSearchRequest: RequestHandler = async (
40 |   connection: WebApi,
41 |   request: CallToolRequest,
42 | ): Promise<{ content: Array<{ type: string; text: string }> }> => {
43 |   switch (request.params.name) {
44 |     case 'search_code': {
45 |       const args = SearchCodeSchema.parse(request.params.arguments);
46 |       const result = await searchCode(connection, args);
47 |       return {
48 |         content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
49 |       };
50 |     }
51 |     case 'search_wiki': {
52 |       const args = SearchWikiSchema.parse(request.params.arguments);
53 |       const result = await searchWiki(connection, args);
54 |       return {
55 |         content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
56 |       };
57 |     }
58 |     case 'search_work_items': {
59 |       const args = SearchWorkItemsSchema.parse(request.params.arguments);
60 |       const result = await searchWorkItems(connection, args);
61 |       return {
62 |         content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
63 |       };
64 |     }
65 |     default:
66 |       throw new Error(`Unknown search tool: ${request.params.name}`);
67 |   }
68 | };
69 | 
```

--------------------------------------------------------------------------------
/src/features/projects/get-project/feature.spec.int.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { getProject } from './feature';
 3 | import {
 4 |   getTestConnection,
 5 |   shouldSkipIntegrationTest,
 6 | } from '@/shared/test/test-helpers';
 7 | 
 8 | describe('getProject integration', () => {
 9 |   let connection: WebApi | null = null;
10 |   let projectName: string;
11 | 
12 |   beforeAll(async () => {
13 |     // Get a real connection using environment variables
14 |     connection = await getTestConnection();
15 |     projectName = process.env.AZURE_DEVOPS_DEFAULT_PROJECT || 'DefaultProject';
16 |   });
17 | 
18 |   test('should retrieve a real project from Azure DevOps', async () => {
19 |     // Skip if no connection is available
20 |     if (shouldSkipIntegrationTest()) {
21 |       return;
22 |     }
23 | 
24 |     // This connection must be available if we didn't skip
25 |     if (!connection) {
26 |       throw new Error(
27 |         'Connection should be available when test is not skipped',
28 |       );
29 |     }
30 | 
31 |     // Act - make an actual API call to Azure DevOps
32 |     const result = await getProject(connection, projectName);
33 | 
34 |     // Assert on the actual response
35 |     expect(result).toBeDefined();
36 |     expect(result.name).toBe(projectName);
37 |     expect(result.id).toBeDefined();
38 |     expect(result.url).toBeDefined();
39 |     expect(result.state).toBeDefined();
40 | 
41 |     // Verify basic project structure
42 |     expect(result.visibility).toBeDefined();
43 |     expect(result.lastUpdateTime).toBeDefined();
44 |   });
45 | 
46 |   test('should throw error when project is not found', async () => {
47 |     // Skip if no connection is available
48 |     if (shouldSkipIntegrationTest()) {
49 |       return;
50 |     }
51 | 
52 |     // This connection must be available if we didn't skip
53 |     if (!connection) {
54 |       throw new Error(
55 |         'Connection should be available when test is not skipped',
56 |       );
57 |     }
58 | 
59 |     // Use a non-existent project name
60 |     const nonExistentProjectName = 'non-existent-project-' + Date.now();
61 | 
62 |     // Act & Assert - should throw an error for non-existent project
63 |     await expect(
64 |       getProject(connection, nonExistentProjectName),
65 |     ).rejects.toThrow(/not found|Failed to get project/);
66 |   });
67 | });
68 | 
```

--------------------------------------------------------------------------------
/src/features/organizations/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // Re-export schemas and types
 2 | export * from './schemas';
 3 | export * from './types';
 4 | 
 5 | // Re-export features
 6 | export * from './list-organizations';
 7 | 
 8 | // Export tool definitions
 9 | export * from './tool-definitions';
10 | 
11 | import { CallToolRequest } from '@modelcontextprotocol/sdk/types.js';
12 | import { WebApi } from 'azure-devops-node-api';
13 | import {
14 |   RequestIdentifier,
15 |   RequestHandler,
16 | } from '../../shared/types/request-handler';
17 | import { listOrganizations } from './list-organizations';
18 | import { AzureDevOpsConfig } from '../../shared/types';
19 | import { AuthenticationMethod } from '../../shared/auth';
20 | 
21 | /**
22 |  * Checks if the request is for the organizations feature
23 |  */
24 | export const isOrganizationsRequest: RequestIdentifier = (
25 |   request: CallToolRequest,
26 | ): boolean => {
27 |   const toolName = request.params.name;
28 |   return ['list_organizations'].includes(toolName);
29 | };
30 | 
31 | /**
32 |  * Handles organizations feature requests
33 |  */
34 | export const handleOrganizationsRequest: RequestHandler = async (
35 |   connection: WebApi,
36 |   request: CallToolRequest,
37 | ): Promise<{ content: Array<{ type: string; text: string }> }> => {
38 |   switch (request.params.name) {
39 |     case 'list_organizations': {
40 |       // Use environment variables for authentication method and PAT
41 |       // This matches how other features handle authentication
42 |       const config: AzureDevOpsConfig = {
43 |         authMethod:
44 |           process.env.AZURE_DEVOPS_AUTH_METHOD?.toLowerCase() === 'pat'
45 |             ? AuthenticationMethod.PersonalAccessToken
46 |             : process.env.AZURE_DEVOPS_AUTH_METHOD?.toLowerCase() ===
47 |                 'azure-cli'
48 |               ? AuthenticationMethod.AzureCli
49 |               : AuthenticationMethod.AzureIdentity,
50 |         personalAccessToken: process.env.AZURE_DEVOPS_PAT,
51 |         organizationUrl: connection.serverUrl || '',
52 |       };
53 | 
54 |       const result = await listOrganizations(config);
55 |       return {
56 |         content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
57 |       };
58 |     }
59 |     default:
60 |       throw new Error(`Unknown organizations tool: ${request.params.name}`);
61 |   }
62 | };
63 | 
```

--------------------------------------------------------------------------------
/src/features/pipelines/get-pipeline/feature.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import {
 3 |   AzureDevOpsError,
 4 |   AzureDevOpsAuthenticationError,
 5 |   AzureDevOpsResourceNotFoundError,
 6 | } from '../../../shared/errors';
 7 | import { GetPipelineOptions, Pipeline } from '../types';
 8 | 
 9 | /**
10 |  * Get a specific pipeline by ID
11 |  *
12 |  * @param connection The Azure DevOps WebApi connection
13 |  * @param options Options for getting a pipeline
14 |  * @returns Pipeline details
15 |  */
16 | export async function getPipeline(
17 |   connection: WebApi,
18 |   options: GetPipelineOptions,
19 | ): Promise<Pipeline> {
20 |   try {
21 |     const pipelinesApi = await connection.getPipelinesApi();
22 |     const { projectId, pipelineId, pipelineVersion } = options;
23 | 
24 |     // Call the pipelines API to get the pipeline
25 |     const pipeline = await pipelinesApi.getPipeline(
26 |       projectId,
27 |       pipelineId,
28 |       pipelineVersion,
29 |     );
30 | 
31 |     // If pipeline not found, API returns null instead of throwing error
32 |     if (pipeline === null) {
33 |       throw new AzureDevOpsResourceNotFoundError(
34 |         `Pipeline not found with ID: ${pipelineId}`,
35 |       );
36 |     }
37 | 
38 |     return pipeline;
39 |   } catch (error) {
40 |     // Handle specific error types
41 |     if (error instanceof AzureDevOpsError) {
42 |       throw error;
43 |     }
44 | 
45 |     // Check for specific error types and convert to appropriate Azure DevOps errors
46 |     if (error instanceof Error) {
47 |       if (
48 |         error.message.includes('Authentication') ||
49 |         error.message.includes('Unauthorized') ||
50 |         error.message.includes('401')
51 |       ) {
52 |         throw new AzureDevOpsAuthenticationError(
53 |           `Failed to authenticate: ${error.message}`,
54 |         );
55 |       }
56 | 
57 |       if (
58 |         error.message.includes('not found') ||
59 |         error.message.includes('does not exist') ||
60 |         error.message.includes('404')
61 |       ) {
62 |         throw new AzureDevOpsResourceNotFoundError(
63 |           `Pipeline or project not found: ${error.message}`,
64 |         );
65 |       }
66 |     }
67 | 
68 |     // Otherwise, wrap it in a generic error
69 |     throw new AzureDevOpsError(
70 |       `Failed to get pipeline: ${error instanceof Error ? error.message : String(error)}`,
71 |     );
72 |   }
73 | }
74 | 
```

--------------------------------------------------------------------------------
/docs/tools/organizations.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Azure DevOps Organizations Tools
 2 | 
 3 | This document describes the tools available for working with Azure DevOps organizations.
 4 | 
 5 | ## list_organizations
 6 | 
 7 | Lists all Azure DevOps organizations accessible to the authenticated user.
 8 | 
 9 | ### Description
10 | 
11 | The `list_organizations` tool retrieves all Azure DevOps organizations that the authenticated user has access to. This is useful for discovering which organizations are available before performing operations on specific projects or repositories.
12 | 
13 | Unlike most other tools in this server, this tool uses Axios for direct API calls rather than the Azure DevOps Node API client, as the WebApi client doesn't support the organizations endpoint.
14 | 
15 | ### Parameters
16 | 
17 | This tool doesn't require any parameters.
18 | 
19 | ```json
20 | {
21 |   // No parameters required
22 | }
23 | ```
24 | 
25 | ### Response
26 | 
27 | The tool returns an array of organization objects, each containing:
28 | 
29 | - `id`: The unique identifier of the organization
30 | - `name`: The name of the organization
31 | - `url`: The URL of the organization
32 | 
33 | Example response:
34 | 
35 | ```json
36 | [
37 |   {
38 |     "id": "org1-id",
39 |     "name": "org1-name",
40 |     "url": "https://dev.azure.com/org1-name"
41 |   },
42 |   {
43 |     "id": "org2-id",
44 |     "name": "org2-name",
45 |     "url": "https://dev.azure.com/org2-name"
46 |   }
47 | ]
48 | ```
49 | 
50 | ### Error Handling
51 | 
52 | The tool may throw the following errors:
53 | 
54 | - `AzureDevOpsAuthenticationError`: If authentication fails or the user profile cannot be retrieved
55 | - General errors: If the accounts API call fails or other unexpected errors occur
56 | 
57 | ### Example Usage
58 | 
59 | ```typescript
60 | // Example MCP client call
61 | const result = await mcpClient.callTool('list_organizations', {});
62 | console.log(result);
63 | ```
64 | 
65 | ### Implementation Details
66 | 
67 | This tool uses a two-step process to retrieve organizations:
68 | 
69 | 1. First, it gets the user profile from `https://app.vssps.visualstudio.com/_apis/profile/profiles/me`
70 | 2. Then it extracts the `publicAlias` from the profile response
71 | 3. Finally, it uses the `publicAlias` to get organizations from `https://app.vssps.visualstudio.com/_apis/accounts?memberId={publicAlias}`
72 | 
73 | Authentication is handled using Basic Auth with the Personal Access Token.
74 | 
```

--------------------------------------------------------------------------------
/src/features/projects/schemas.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { defaultProject, defaultOrg } from '../../utils/environment';
 3 | 
 4 | /**
 5 |  * Schema for getting a project
 6 |  */
 7 | export const GetProjectSchema = z.object({
 8 |   projectId: z
 9 |     .string()
10 |     .optional()
11 |     .describe(`The ID or name of the project (Default: ${defaultProject})`),
12 |   organizationId: z
13 |     .string()
14 |     .optional()
15 |     .describe(`The ID or name of the organization (Default: ${defaultOrg})`),
16 | });
17 | 
18 | /**
19 |  * Schema for getting detailed project information
20 |  */
21 | export const GetProjectDetailsSchema = z.object({
22 |   projectId: z
23 |     .string()
24 |     .optional()
25 |     .describe(`The ID or name of the project (Default: ${defaultProject})`),
26 |   organizationId: z
27 |     .string()
28 |     .optional()
29 |     .describe(`The ID or name of the organization (Default: ${defaultOrg})`),
30 |   includeProcess: z
31 |     .boolean()
32 |     .optional()
33 |     .default(false)
34 |     .describe('Include process information in the project result'),
35 |   includeWorkItemTypes: z
36 |     .boolean()
37 |     .optional()
38 |     .default(false)
39 |     .describe('Include work item types and their structure'),
40 |   includeFields: z
41 |     .boolean()
42 |     .optional()
43 |     .default(false)
44 |     .describe('Include field information for work item types'),
45 |   includeTeams: z
46 |     .boolean()
47 |     .optional()
48 |     .default(false)
49 |     .describe('Include associated teams in the project result'),
50 |   expandTeamIdentity: z
51 |     .boolean()
52 |     .optional()
53 |     .default(false)
54 |     .describe('Expand identity information in the team objects'),
55 | });
56 | 
57 | /**
58 |  * Schema for listing projects
59 |  */
60 | export const ListProjectsSchema = z.object({
61 |   organizationId: z
62 |     .string()
63 |     .optional()
64 |     .describe(`The ID or name of the organization (Default: ${defaultOrg})`),
65 |   stateFilter: z
66 |     .number()
67 |     .optional()
68 |     .describe(
69 |       'Filter on team project state (0: all, 1: well-formed, 2: creating, 3: deleting, 4: new)',
70 |     ),
71 |   top: z.number().optional().describe('Maximum number of projects to return'),
72 |   skip: z.number().optional().describe('Number of projects to skip'),
73 |   continuationToken: z
74 |     .number()
75 |     .optional()
76 |     .describe('Gets the projects after the continuation token provided'),
77 | });
78 | 
```

--------------------------------------------------------------------------------
/src/features/repositories/list-repositories/feature.spec.unit.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { listRepositories } from './feature';
 2 | import { AzureDevOpsError } from '../../../shared/errors';
 3 | 
 4 | // Unit tests should only focus on isolated logic
 5 | describe('listRepositories unit', () => {
 6 |   test('should return empty array when no repositories are found', async () => {
 7 |     // Arrange
 8 |     const mockConnection: any = {
 9 |       getGitApi: jest.fn().mockImplementation(() => ({
10 |         getRepositories: jest.fn().mockResolvedValue([]), // No repositories found
11 |       })),
12 |     };
13 | 
14 |     // Act
15 |     const result = await listRepositories(mockConnection, {
16 |       projectId: 'test-project',
17 |     });
18 | 
19 |     // Assert
20 |     expect(result).toEqual([]);
21 |   });
22 | 
23 |   test('should propagate custom errors when thrown internally', async () => {
24 |     // Arrange
25 |     const mockConnection: any = {
26 |       getGitApi: jest.fn().mockImplementation(() => {
27 |         throw new AzureDevOpsError('Custom error');
28 |       }),
29 |     };
30 | 
31 |     // Act & Assert
32 |     await expect(
33 |       listRepositories(mockConnection, { projectId: 'test-project' }),
34 |     ).rejects.toThrow(AzureDevOpsError);
35 | 
36 |     await expect(
37 |       listRepositories(mockConnection, { projectId: 'test-project' }),
38 |     ).rejects.toThrow('Custom error');
39 |   });
40 | 
41 |   test('should wrap unexpected errors in a friendly error message', async () => {
42 |     // Arrange
43 |     const mockConnection: any = {
44 |       getGitApi: jest.fn().mockImplementation(() => {
45 |         throw new Error('Unexpected error');
46 |       }),
47 |     };
48 | 
49 |     // Act & Assert
50 |     await expect(
51 |       listRepositories(mockConnection, { projectId: 'test-project' }),
52 |     ).rejects.toThrow('Failed to list repositories: Unexpected error');
53 |   });
54 | 
55 |   test('should respect the includeLinks option', async () => {
56 |     // Arrange
57 |     const mockGetRepositories = jest.fn().mockResolvedValue([]);
58 |     const mockConnection: any = {
59 |       getGitApi: jest.fn().mockImplementation(() => ({
60 |         getRepositories: mockGetRepositories,
61 |       })),
62 |     };
63 | 
64 |     // Act
65 |     await listRepositories(mockConnection, {
66 |       projectId: 'test-project',
67 |       includeLinks: true,
68 |     });
69 | 
70 |     // Assert
71 |     expect(mockGetRepositories).toHaveBeenCalledWith('test-project', true);
72 |   });
73 | });
74 | 
```

--------------------------------------------------------------------------------
/src/utils/environment.spec.unit.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // Mock the environment module before importing
 2 | jest.mock('./environment', () => {
 3 |   const original = jest.requireActual('./environment');
 4 |   return {
 5 |     ...original,
 6 |     // We'll keep getOrgNameFromUrl as is for its own tests
 7 |     getOrgNameFromUrl: original.getOrgNameFromUrl,
 8 |   };
 9 | });
10 | 
11 | import { getOrgNameFromUrl } from './environment';
12 | 
13 | describe('environment utilities', () => {
14 |   // Store original environment variables
15 |   const originalEnv = { ...process.env };
16 | 
17 |   // Reset environment variables after each test
18 |   afterEach(() => {
19 |     process.env = { ...originalEnv };
20 |     jest.resetModules();
21 |   });
22 | 
23 |   describe('getOrgNameFromUrl', () => {
24 |     it('should extract organization name from Azure DevOps URL', () => {
25 |       const url = 'https://dev.azure.com/test-organization';
26 |       expect(getOrgNameFromUrl(url)).toBe('test-organization');
27 |     });
28 | 
29 |     it('should handle URLs with paths after the organization name', () => {
30 |       const url = 'https://dev.azure.com/test-organization/project';
31 |       expect(getOrgNameFromUrl(url)).toBe('test-organization');
32 |     });
33 | 
34 |     it('should return "unknown-organization" when URL is undefined', () => {
35 |       expect(getOrgNameFromUrl(undefined)).toBe('unknown-organization');
36 |     });
37 | 
38 |     it('should return "unknown-organization" when URL is empty', () => {
39 |       expect(getOrgNameFromUrl('')).toBe('unknown-organization');
40 |     });
41 | 
42 |     it('should return "unknown-organization" when URL does not match pattern', () => {
43 |       const url = 'https://example.com/test-organization';
44 |       expect(getOrgNameFromUrl(url)).toBe('unknown-organization');
45 |     });
46 |   });
47 | 
48 |   describe('defaultProject and defaultOrg', () => {
49 |     // Since we can't easily test the environment variable initialization directly,
50 |     // we'll test the getOrgNameFromUrl function which is used to derive defaultOrg
51 | 
52 |     it('should handle the real default case', () => {
53 |       // This test is more of a documentation than a real test
54 |       const orgNameFromUrl = getOrgNameFromUrl(
55 |         process.env.AZURE_DEVOPS_ORG_URL,
56 |       );
57 |       // We can't assert an exact value since it depends on the environment
58 |       expect(typeof orgNameFromUrl).toBe('string');
59 |     });
60 |   });
61 | });
62 | 
```

--------------------------------------------------------------------------------
/src/features/wikis/get-wiki-page/feature.spec.int.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { WebApi } from 'azure-devops-node-api';
 2 | import { getWikiPage } from './feature';
 3 | import { getWikis } from '../get-wikis/feature';
 4 | import {
 5 |   getTestConnection,
 6 |   shouldSkipIntegrationTest,
 7 | } from '@/shared/test/test-helpers';
 8 | import { getOrgNameFromUrl } from '@/utils/environment';
 9 | 
10 | process.env.AZURE_DEVOPS_DEFAULT_PROJECT =
11 |   process.env.AZURE_DEVOPS_DEFAULT_PROJECT || 'default-project';
12 | 
13 | describe('getWikiPage integration', () => {
14 |   let connection: WebApi | null = null;
15 |   let projectName: string;
16 |   let orgUrl: string;
17 | 
18 |   beforeAll(async () => {
19 |     // Mock the required environment variable for testing
20 |     process.env.AZURE_DEVOPS_ORG_URL =
21 |       process.env.AZURE_DEVOPS_ORG_URL || 'https://example.visualstudio.com';
22 |     // Get a real connection using environment variables
23 |     connection = await getTestConnection();
24 | 
25 |     // Get and validate required environment variables
26 |     const envProjectName = process.env.AZURE_DEVOPS_DEFAULT_PROJECT;
27 |     if (!envProjectName) {
28 |       throw new Error(
29 |         'AZURE_DEVOPS_DEFAULT_PROJECT environment variable is required',
30 |       );
31 |     }
32 |     projectName = envProjectName;
33 | 
34 |     const envOrgUrl = process.env.AZURE_DEVOPS_ORG_URL;
35 |     if (!envOrgUrl) {
36 |       throw new Error('AZURE_DEVOPS_ORG_URL environment variable is required');
37 |     }
38 |     orgUrl = envOrgUrl;
39 |   });
40 | 
41 |   test('should retrieve a wiki page', async () => {
42 |     // Skip if no connection is available
43 |     if (shouldSkipIntegrationTest()) {
44 |       return;
45 |     }
46 | 
47 |     // This connection must be available if we didn't skip
48 |     if (!connection) {
49 |       throw new Error(
50 |         'Connection should be available when test is not skipped',
51 |       );
52 |     }
53 | 
54 |     // First get available wikis
55 |     const wikis = await getWikis(connection, { projectId: projectName });
56 | 
57 |     // Skip if no wikis are available
58 |     if (wikis.length === 0) {
59 |       console.log('Skipping test: No wikis available in the project');
60 |       return;
61 |     }
62 | 
63 |     // Use the first available wiki
64 |     const wiki = wikis[0];
65 |     if (!wiki.name) {
66 |       throw new Error('Wiki name is undefined');
67 |     }
68 | 
69 |     // Get the wiki page
70 |     const result = await getWikiPage({
71 |       organizationId: getOrgNameFromUrl(orgUrl),
72 |       projectId: projectName,
73 |       wikiId: wiki.name,
74 |       pagePath: '/test',
75 |     });
76 | 
77 |     // Verify the result
78 |     expect(result).toBeDefined();
79 |     expect(typeof result).toBe('string');
80 |   });
81 | });
82 | 
```

--------------------------------------------------------------------------------
/src/features/projects/get-project/feature.spec.unit.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { getProject } from './feature';
 2 | import {
 3 |   AzureDevOpsError,
 4 |   AzureDevOpsResourceNotFoundError,
 5 | } from '../../../shared/errors';
 6 | import { TeamProject } from 'azure-devops-node-api/interfaces/CoreInterfaces';
 7 | import { WebApi } from 'azure-devops-node-api';
 8 | 
 9 | // Create a partial mock interface for ICoreApi
10 | interface MockCoreApi {
11 |   getProject: jest.Mock<Promise<TeamProject | null>>;
12 | }
13 | 
14 | // Create a mock connection that resembles WebApi with minimal implementation
15 | interface MockConnection {
16 |   getCoreApi: jest.Mock<Promise<MockCoreApi>>;
17 |   serverUrl?: string;
18 |   authHandler?: unknown;
19 |   rest?: unknown;
20 |   vsoClient?: unknown;
21 | }
22 | 
23 | // Unit tests should only focus on isolated logic
24 | describe('getProject unit', () => {
25 |   test('should throw resource not found error when project is null', async () => {
26 |     // Arrange
27 |     const mockCoreApi: MockCoreApi = {
28 |       getProject: jest.fn().mockResolvedValue(null), // Simulate project not found
29 |     };
30 | 
31 |     const mockConnection: MockConnection = {
32 |       getCoreApi: jest.fn().mockResolvedValue(mockCoreApi),
33 |     };
34 | 
35 |     // Act & Assert
36 |     await expect(
37 |       getProject(mockConnection as unknown as WebApi, 'non-existent-project'),
38 |     ).rejects.toThrow(AzureDevOpsResourceNotFoundError);
39 | 
40 |     await expect(
41 |       getProject(mockConnection as unknown as WebApi, 'non-existent-project'),
42 |     ).rejects.toThrow("Project 'non-existent-project' not found");
43 |   });
44 | 
45 |   test('should propagate custom errors when thrown internally', async () => {
46 |     // Arrange
47 |     const mockConnection: MockConnection = {
48 |       getCoreApi: jest.fn().mockImplementation(() => {
49 |         throw new AzureDevOpsError('Custom error');
50 |       }),
51 |     };
52 | 
53 |     // Act & Assert
54 |     await expect(
55 |       getProject(mockConnection as unknown as WebApi, 'test-project'),
56 |     ).rejects.toThrow(AzureDevOpsError);
57 | 
58 |     await expect(
59 |       getProject(mockConnection as unknown as WebApi, 'test-project'),
60 |     ).rejects.toThrow('Custom error');
61 |   });
62 | 
63 |   test('should wrap unexpected errors in a friendly error message', async () => {
64 |     // Arrange
65 |     const mockConnection: MockConnection = {
66 |       getCoreApi: jest.fn().mockImplementation(() => {
67 |         throw new Error('Unexpected error');
68 |       }),
69 |     };
70 | 
71 |     // Act & Assert
72 |     await expect(
73 |       getProject(mockConnection as unknown as WebApi, 'test-project'),
74 |     ).rejects.toThrow('Failed to get project: Unexpected error');
75 |   });
76 | });
77 | 
```
Page 1/8FirstPrevNextLast