az-devops-cli-mcp 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to `az-devops-cli-mcp` will be documented here.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
+ Versioning follows [Semantic Versioning](https://semver.org/).
6
+
7
+ ---
8
+
9
+ ## [1.0.0] — Initial Release
10
+
11
+ ### Added
12
+ - 13 MCP tools covering Azure DevOps Boards and Repos
13
+ - Work items: create, get, update, set_iteration, query, link, verify relations, comment
14
+ - Repos/PRs: list, get, create, link to work item, list branches
15
+ - stdio MCP transport — compatible with Claude Code and Codex CLI
16
+ - TOON (Token-Oriented Object Notation) response format
17
+ - Destructive command guard (delete/remove/destroy blocked by default)
18
+ - Auto org detection from `az devops configure` or `AZURE_DEVOPS_ORG` env var
19
+ - AG2 tools library interface via `from az_devops_cli_mcp import ALL_TOOLS`
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 LytStore Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include LICENSE
3
+ include CHANGELOG.md
4
+ include pyproject.toml
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: az-devops-cli-mcp
3
+ Version: 1.0.2
4
+ Summary: Azure DevOps CLI MCP server for Claude Code and Codex CLI — boards, work items, repos, PRs
5
+ Author: LytStore Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/PraiseSinkamba/az-devops-cli-mcp
8
+ Project-URL: Repository, https://github.com/PraiseSinkamba/az-devops-cli-mcp
9
+ Project-URL: Issues, https://github.com/PraiseSinkamba/az-devops-cli-mcp/issues
10
+ Project-URL: Changelog, https://github.com/PraiseSinkamba/az-devops-cli-mcp/blob/main/CHANGELOG.md
11
+ Keywords: azure,devops,mcp,claude,codex,boards,work-items
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: mcp>=1.0.0
24
+ Requires-Dist: python-dotenv>=1.0.0
25
+ Provides-Extra: ag2
26
+ Requires-Dist: pyautogen>=0.2.0; extra == "ag2"
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest; extra == "dev"
29
+ Requires-Dist: pytest-mock; extra == "dev"
30
+ Requires-Dist: build; extra == "dev"
31
+ Requires-Dist: twine; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # az-devops-cli-mcp
35
+
36
+ Azure DevOps MCP tools for **Claude Code** and **Codex CLI** — boards, work items, repos, and PRs.
37
+
38
+ [![PyPI version](https://img.shields.io/pypi/v/az-devops-cli-mcp)](https://pypi.org/project/az-devops-cli-mcp/)
39
+ [![CI](https://github.com/PraiseSinkamba/az-devops-cli-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/PraiseSinkamba/az-devops-cli-mcp/actions)
40
+ [![Python 3.10+](https://img.shields.io/pypi/pyversions/az-devops-cli-mcp)](https://pypi.org/project/az-devops-cli-mcp/)
41
+
42
+ ---
43
+
44
+ ## Install & Register
45
+
46
+ ```bash
47
+ # 1. Install
48
+ pip install az-devops-cli-mcp
49
+
50
+ # 2. Login (once per machine)
51
+ az login
52
+ az devops configure --defaults organization=https://dev.azure.com/YOUR_ORG
53
+
54
+ # 3. Register with Claude Code
55
+ claude mcp add az-devops-cli-mcp -- python -m az_devops_cli_mcp.server
56
+
57
+ # 4. Register with Codex CLI
58
+ codex mcp add az-devops-cli-mcp -- python -m az_devops_cli_mcp.server
59
+ ```
60
+
61
+ Restart Claude Code or Codex CLI. All 13 tools are ready.
62
+
63
+ ---
64
+
65
+ ## How It Works
66
+
67
+ The server runs as a **stdio process** — spawned on demand by the client, no ports or background services needed.
68
+
69
+ ```
70
+ Claude Code / Codex CLI
71
+ | spawns on demand
72
+ v
73
+ python -m az_devops_cli_mcp.server (stdin/stdout)
74
+ |
75
+ v
76
+ az CLI (your az login session)
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Available Tools (13 total)
82
+
83
+ ### Work Items
84
+ | Tool | What it does |
85
+ |---|---|
86
+ | `create_work_item` | Create Task, Bug, User Story, Epic |
87
+ | `get_work_item` | Get item by ID |
88
+ | `update_work_item` | Update state, assignment, title |
89
+ | `set_iteration` | Assign to sprint (required for Sprint board) |
90
+ | `query_work_items` | WIQL query for bulk lookups |
91
+ | `link_work_items` | Link items (Child, Parent, Related) |
92
+ | `get_work_item_relations` | Verify hierarchy after linking |
93
+ | `add_comment` | Add discussion comment / agent log |
94
+
95
+ ### Repos / PRs
96
+ | Tool | What it does |
97
+ |---|---|
98
+ | `list_prs` | List PRs by status |
99
+ | `get_pr` | Get PR details |
100
+ | `create_pr` | Open a new PR |
101
+ | `link_pr_to_work_item` | Trace PR to work item |
102
+ | `list_branches` | List repo branches |
103
+
104
+ ---
105
+
106
+ ## AG2 Usage (optional)
107
+
108
+ ```python
109
+ from autogen import ConversableAgent
110
+ from az_devops_cli_mcp import ALL_TOOLS, WORK_ITEM_TOOLS, REPO_TOOLS
111
+
112
+ agent = ConversableAgent(
113
+ name="devops_agent",
114
+ tools=ALL_TOOLS,
115
+ system_message="You manage Azure DevOps for LytStore..."
116
+ )
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Configuration
122
+
123
+ | Method | How |
124
+ |---|---|
125
+ | `az devops configure` | Preferred — org set globally for your machine |
126
+ | `AZURE_DEVOPS_ORG` env var | Per-session override |
127
+ | `.env` file | `AZURE_DEVOPS_ORG=https://dev.azure.com/YOUR_ORG` |
128
+
129
+ ---
130
+
131
+ ## Safety
132
+
133
+ Destructive commands (`delete`, `remove`, `destroy`) are blocked at the core layer.
134
+ The agent returns an error and requires explicit user confirmation.
135
+
136
+ ---
137
+
138
+ ## Manual MCP Config (fallback)
139
+
140
+ If `claude mcp add` / `codex mcp add` aren't available, add this to your config file manually.
141
+
142
+ **Claude Code** — `~/.claude/claude_desktop_config.json`:
143
+ ```json
144
+ {
145
+ "mcpServers": {
146
+ "az-devops-cli-mcp": {
147
+ "type": "stdio",
148
+ "command": "python",
149
+ "args": ["-m", "az_devops_cli_mcp.server"]
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ **Codex CLI** — `~/.codex/config.json`: same block.
156
+
157
+ ---
158
+
159
+ ## Contributing
160
+
161
+ ```bash
162
+ git clone https://github.com/PraiseSinkamba/az-devops-cli-mcp
163
+ cd az-devops-cli-mcp
164
+ pip install -e ".[dev]"
165
+ pytest tests/
166
+ ```
@@ -0,0 +1,133 @@
1
+ # az-devops-cli-mcp
2
+
3
+ Azure DevOps MCP tools for **Claude Code** and **Codex CLI** — boards, work items, repos, and PRs.
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/az-devops-cli-mcp)](https://pypi.org/project/az-devops-cli-mcp/)
6
+ [![CI](https://github.com/PraiseSinkamba/az-devops-cli-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/PraiseSinkamba/az-devops-cli-mcp/actions)
7
+ [![Python 3.10+](https://img.shields.io/pypi/pyversions/az-devops-cli-mcp)](https://pypi.org/project/az-devops-cli-mcp/)
8
+
9
+ ---
10
+
11
+ ## Install & Register
12
+
13
+ ```bash
14
+ # 1. Install
15
+ pip install az-devops-cli-mcp
16
+
17
+ # 2. Login (once per machine)
18
+ az login
19
+ az devops configure --defaults organization=https://dev.azure.com/YOUR_ORG
20
+
21
+ # 3. Register with Claude Code
22
+ claude mcp add az-devops-cli-mcp -- python -m az_devops_cli_mcp.server
23
+
24
+ # 4. Register with Codex CLI
25
+ codex mcp add az-devops-cli-mcp -- python -m az_devops_cli_mcp.server
26
+ ```
27
+
28
+ Restart Claude Code or Codex CLI. All 13 tools are ready.
29
+
30
+ ---
31
+
32
+ ## How It Works
33
+
34
+ The server runs as a **stdio process** — spawned on demand by the client, no ports or background services needed.
35
+
36
+ ```
37
+ Claude Code / Codex CLI
38
+ | spawns on demand
39
+ v
40
+ python -m az_devops_cli_mcp.server (stdin/stdout)
41
+ |
42
+ v
43
+ az CLI (your az login session)
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Available Tools (13 total)
49
+
50
+ ### Work Items
51
+ | Tool | What it does |
52
+ |---|---|
53
+ | `create_work_item` | Create Task, Bug, User Story, Epic |
54
+ | `get_work_item` | Get item by ID |
55
+ | `update_work_item` | Update state, assignment, title |
56
+ | `set_iteration` | Assign to sprint (required for Sprint board) |
57
+ | `query_work_items` | WIQL query for bulk lookups |
58
+ | `link_work_items` | Link items (Child, Parent, Related) |
59
+ | `get_work_item_relations` | Verify hierarchy after linking |
60
+ | `add_comment` | Add discussion comment / agent log |
61
+
62
+ ### Repos / PRs
63
+ | Tool | What it does |
64
+ |---|---|
65
+ | `list_prs` | List PRs by status |
66
+ | `get_pr` | Get PR details |
67
+ | `create_pr` | Open a new PR |
68
+ | `link_pr_to_work_item` | Trace PR to work item |
69
+ | `list_branches` | List repo branches |
70
+
71
+ ---
72
+
73
+ ## AG2 Usage (optional)
74
+
75
+ ```python
76
+ from autogen import ConversableAgent
77
+ from az_devops_cli_mcp import ALL_TOOLS, WORK_ITEM_TOOLS, REPO_TOOLS
78
+
79
+ agent = ConversableAgent(
80
+ name="devops_agent",
81
+ tools=ALL_TOOLS,
82
+ system_message="You manage Azure DevOps for LytStore..."
83
+ )
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Configuration
89
+
90
+ | Method | How |
91
+ |---|---|
92
+ | `az devops configure` | Preferred — org set globally for your machine |
93
+ | `AZURE_DEVOPS_ORG` env var | Per-session override |
94
+ | `.env` file | `AZURE_DEVOPS_ORG=https://dev.azure.com/YOUR_ORG` |
95
+
96
+ ---
97
+
98
+ ## Safety
99
+
100
+ Destructive commands (`delete`, `remove`, `destroy`) are blocked at the core layer.
101
+ The agent returns an error and requires explicit user confirmation.
102
+
103
+ ---
104
+
105
+ ## Manual MCP Config (fallback)
106
+
107
+ If `claude mcp add` / `codex mcp add` aren't available, add this to your config file manually.
108
+
109
+ **Claude Code** — `~/.claude/claude_desktop_config.json`:
110
+ ```json
111
+ {
112
+ "mcpServers": {
113
+ "az-devops-cli-mcp": {
114
+ "type": "stdio",
115
+ "command": "python",
116
+ "args": ["-m", "az_devops_cli_mcp.server"]
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ **Codex CLI** — `~/.codex/config.json`: same block.
123
+
124
+ ---
125
+
126
+ ## Contributing
127
+
128
+ ```bash
129
+ git clone https://github.com/PraiseSinkamba/az-devops-cli-mcp
130
+ cd az-devops-cli-mcp
131
+ pip install -e ".[dev]"
132
+ pytest tests/
133
+ ```
@@ -0,0 +1,54 @@
1
+ """
2
+ az-devops-cli-mcp
3
+ ===============
4
+ Azure DevOps CLI MCP server — boards, work items, repos, PRs.
5
+
6
+ Usage in your agent:
7
+ from az_devops_cli_mcp import ALL_TOOLS, WORK_ITEM_TOOLS, REPO_TOOLS
8
+ """
9
+
10
+ from az_devops_cli_mcp.tools.work_items import (
11
+ create_work_item,
12
+ get_work_item,
13
+ update_work_item,
14
+ set_iteration,
15
+ query_work_items,
16
+ link_work_items,
17
+ get_work_item_relations,
18
+ add_comment,
19
+ )
20
+
21
+ from az_devops_cli_mcp.tools.repos import (
22
+ list_prs,
23
+ get_pr,
24
+ create_pr,
25
+ link_pr_to_work_item,
26
+ list_branches,
27
+ )
28
+
29
+ WORK_ITEM_TOOLS = [
30
+ create_work_item,
31
+ get_work_item,
32
+ update_work_item,
33
+ set_iteration,
34
+ query_work_items,
35
+ link_work_items,
36
+ get_work_item_relations,
37
+ add_comment,
38
+ ]
39
+
40
+ REPO_TOOLS = [
41
+ list_prs,
42
+ get_pr,
43
+ create_pr,
44
+ link_pr_to_work_item,
45
+ list_branches,
46
+ ]
47
+
48
+ ALL_TOOLS = WORK_ITEM_TOOLS + REPO_TOOLS
49
+
50
+ __all__ = [
51
+ "ALL_TOOLS",
52
+ "WORK_ITEM_TOOLS",
53
+ "REPO_TOOLS",
54
+ ]
@@ -0,0 +1,6 @@
1
+ """
2
+ Allows: python -m az_devops_cli_mcp.server
3
+ and: python -m az_devops_cli_mcp (same thing)
4
+ """
5
+ from az_devops_cli_mcp.server import run
6
+ run()
@@ -0,0 +1,50 @@
1
+ """
2
+ az_devops_cli_mcp.config
3
+ ======================
4
+ Resolves the Azure DevOps org URL in this priority order:
5
+
6
+ 1. AZURE_DEVOPS_ORG environment variable (or .env file)
7
+ 2. az devops configure --list (whatever the user already set)
8
+
9
+ This means each team member just runs:
10
+ az login
11
+ az devops configure --defaults organization=https://dev.azure.com/YOUR_ORG
12
+
13
+ ...and the tools pick it up automatically.
14
+ No hardcoded values, no shared secrets.
15
+ """
16
+
17
+ import subprocess
18
+ import os
19
+ from dotenv import load_dotenv
20
+
21
+ load_dotenv() # load .env if present (optional, never required)
22
+
23
+
24
+ def get_org() -> str:
25
+ """
26
+ Resolve the Azure DevOps org URL.
27
+ Raises RuntimeError if neither env var nor az defaults are set.
28
+ """
29
+ # 1. Env override
30
+ org = os.getenv("AZURE_DEVOPS_ORG", "").strip()
31
+ if org:
32
+ return org
33
+
34
+ # 2. Read from az devops configure --list
35
+ result = subprocess.run(
36
+ "az devops configure --list",
37
+ shell=True, capture_output=True, text=True
38
+ )
39
+ for line in result.stdout.splitlines():
40
+ if line.strip().startswith("organization"):
41
+ _, _, value = line.partition("=")
42
+ org = value.strip()
43
+ if org:
44
+ return org
45
+
46
+ raise RuntimeError(
47
+ "No Azure DevOps org configured.\n"
48
+ "Run: az devops configure --defaults organization=https://dev.azure.com/YOUR_ORG\n"
49
+ "Or set AZURE_DEVOPS_ORG in your environment."
50
+ )
@@ -0,0 +1,76 @@
1
+ """
2
+ az_devops_cli_mcp.core
3
+ ====================
4
+ Shared helpers used by all tool modules.
5
+ """
6
+
7
+ import json
8
+ import subprocess
9
+ from az_devops_cli_mcp.config import get_org
10
+
11
+ BLOCKED_COMMANDS = ["delete", "remove", "destroy"]
12
+
13
+
14
+ def az(args: str) -> dict | list:
15
+ """
16
+ Execute an az CLI command against the configured org.
17
+ Returns parsed JSON on success, {"error": "..."} on failure.
18
+ Blocks destructive commands until the user confirms.
19
+ """
20
+ if any(cmd in args.lower() for cmd in BLOCKED_COMMANDS):
21
+ return {
22
+ "error": (
23
+ f"Blocked: destructive command detected in '{args}'. "
24
+ "Confirm with user before proceeding."
25
+ )
26
+ }
27
+
28
+ try:
29
+ org = get_org()
30
+ except RuntimeError as e:
31
+ return {"error": str(e)}
32
+
33
+ result = subprocess.run(
34
+ f"az {args} --output json --org {org}",
35
+ shell=True,
36
+ capture_output=True,
37
+ text=True
38
+ )
39
+
40
+ if result.returncode != 0:
41
+ return {"error": result.stderr.strip()}
42
+
43
+ try:
44
+ return json.loads(result.stdout)
45
+ except json.JSONDecodeError:
46
+ return {"error": f"Failed to parse response: {result.stdout.strip()}"}
47
+
48
+
49
+ def toon(data: dict | list, summary: str) -> str:
50
+ """
51
+ Token-Oriented Object Notation:
52
+ Flat dict/list → key=value pipe-separated (token-efficient)
53
+ Nested objects → full JSON fallback
54
+ Error dict → [ERROR] prefix
55
+ Always prepends a summary line.
56
+ """
57
+ if isinstance(data, dict) and "error" in data:
58
+ return f"[ERROR] {data['error']}"
59
+
60
+ def is_flat(obj: dict) -> bool:
61
+ return isinstance(obj, dict) and all(
62
+ not isinstance(v, (dict, list)) for v in obj.values()
63
+ )
64
+
65
+ if isinstance(data, dict) and is_flat(data):
66
+ body = " | ".join(f"{k}={v}" for k, v in data.items())
67
+
68
+ elif isinstance(data, list) and all(is_flat(i) for i in data):
69
+ body = "(none)" if not data else "\n".join(
70
+ " | ".join(f"{k}={v}" for k, v in item.items())
71
+ for item in data
72
+ )
73
+ else:
74
+ body = json.dumps(data, indent=2)
75
+
76
+ return f"[{summary}]\n{body}"