@dockndevai/mcp-azure-devops 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ankit Verma
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.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # mcp-azure-devops
2
+
3
+ [![CI](https://github.com/dockndevai/mcp-azure-devops/actions/workflows/ci.yml/badge.svg)](https://github.com/dockndevai/mcp-azure-devops/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
5
+ [![npm](https://img.shields.io/npm/v/@dockndevai/mcp-azure-devops)](https://www.npmjs.com/package/@dockndevai/mcp-azure-devops)
6
+
7
+ A [Model Context Protocol](https://modelcontextprotocol.io) server for **Azure DevOps**. It lets an MCP-capable client (Claude Desktop, Claude Code, Cursor, Codex, …) work across **boards, repos, pipelines, and projects** — with a governance layer that keeps an AI agent inside safe boundaries.
8
+
9
+ ## What this offers
10
+
11
+ - **Projects & teams** — list projects/teams/members, inspect process templates.
12
+ - **Boards / work items** — WIQL queries, get work items, create/update work items, list iterations (sprints).
13
+ - **Repos / pull requests** — list repositories and branches, list/get PRs, open PRs.
14
+ - **Pipelines / builds** — list pipelines and recent builds, get a build, queue a run.
15
+ - **Admin / process** — list process templates, create a project, delete a project (guarded).
16
+ - **Governance built in** — access modes, project allowlists, protected projects, delete gating, typed confirmation for high-impact ops, dry-run, and JSON audit logging.
17
+
18
+ ## Governance & security model
19
+
20
+ | Concern | Flag | Default | Effect |
21
+ | --- | --- | --- | --- |
22
+ | What can the server do? | `AZDO_MODE` | `read-only` | `read-only` → reads; `read-write` → work items, PRs, pipeline runs; `admin` → create/delete project. Tools above the mode are **never registered**. |
23
+ | Which projects are in scope? | `AZDO_PROJECT_ALLOWLIST` | *(all)* | Operations on other projects are refused. |
24
+ | Which projects are read-only forever? | `AZDO_PROTECTED_PROJECTS` | *(none)* | Readable, never mutable. |
25
+ | Can it delete? | `AZDO_ALLOW_DELETE` | `false` | `delete_project` needs this **and** admin mode. |
26
+ | Typed confirmation | `AZDO_REQUIRE_CONFIRMATION` | `true` | High-impact ops require `confirm` to equal the target name — not just a boolean. |
27
+ | Preview | `AZDO_DRY_RUN` | `false` | Write/admin tools validate + log intent, then return. |
28
+ | Audit trail | `AZDO_AUDIT_LOG` | `true` | JSON line to stderr per guarded operation. |
29
+
30
+ ## Tools
31
+
32
+ **Read** (`read-only`+): `list_projects`, `get_project`, `list_teams`, `list_team_members`, `list_processes`, `query_work_items`, `get_work_item`, `list_iterations`, `list_repositories`, `list_branches`, `list_pull_requests`, `get_pull_request`, `list_pipelines`, `list_builds`, `get_build`
33
+
34
+ **Write** (`read-write`+): `create_work_item`, `update_work_item`, `create_pull_request`, `run_pipeline`
35
+
36
+ **Admin** (`admin`): `create_project`, `delete_project` (needs `AZDO_ALLOW_DELETE` + typed `confirm`)
37
+
38
+ ## Quickstart — add to your agent
39
+
40
+ Published on npm as [`@dockndevai/mcp-azure-devops`](https://www.npmjs.com/package/@dockndevai/mcp-azure-devops). Runs via `npx`; needs an org and a PAT. See [docs/CLIENTS.md](docs/CLIENTS.md) for every client and [`.env.example`](.env.example) for all variables.
41
+
42
+ **Claude Code**
43
+
44
+ ```bash
45
+ claude mcp add azure-devops -e AZDO_ORG_URL="https://dev.azure.com/your-org" -e AZDO_PAT="your-pat" -e AZDO_MODE="read-only" -- npx -y @dockndevai/mcp-azure-devops
46
+ ```
47
+
48
+ **Claude Desktop · Cursor · Windsurf**
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "azure-devops": {
54
+ "command": "npx",
55
+ "args": ["-y", "@dockndevai/mcp-azure-devops"],
56
+ "env": {
57
+ "AZDO_ORG_URL": "https://dev.azure.com/your-org",
58
+ "AZDO_PAT": "your-pat",
59
+ "AZDO_MODE": "read-only"
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## Example prompts
67
+
68
+ - *"List active pull requests in the Payments project's api repo"*
69
+ - *"Query work items assigned to me that are still Active"*
70
+ - *"Show the last 10 builds in the Web project and which failed"*
71
+ - *"Create a Bug in Payments titled 'Checkout 500 on retry'"* (needs read-write)
72
+
73
+ ## Run from source (development)
74
+
75
+ ```bash
76
+ npm install
77
+ npm run build
78
+ node dist/index.js # with the environment variables set
79
+ ```
80
+
81
+ ## Develop
82
+
83
+ ```bash
84
+ npm run dev
85
+ npm test # governance policy: modes, allowlists, delete gating, confirmation
86
+ npm run typecheck
87
+ ```
88
+
89
+ ## Publishing
90
+
91
+ Ships a [`server.json`](server.json) for the official MCP registry and an [`mcpName`](package.json) for npm ownership validation. See **[PUBLISHING.md](PUBLISHING.md)**.
92
+
93
+ ## License
94
+
95
+ MIT
@@ -0,0 +1,14 @@
1
+ import type { SecurityConfig } from "./security.js";
2
+ export interface AzdoConnection {
3
+ /** Organization URL, e.g. https://dev.azure.com/my-org */
4
+ orgUrl: string;
5
+ /** Personal Access Token (used as HTTP basic auth). */
6
+ pat: string;
7
+ apiVersion: string;
8
+ requestTimeout: number;
9
+ }
10
+ export interface AppConfig {
11
+ connection: AzdoConnection;
12
+ security: SecurityConfig;
13
+ }
14
+ export declare function loadConfig(): AppConfig;
package/dist/config.js ADDED
@@ -0,0 +1,44 @@
1
+ function bool(name, fallback) {
2
+ const v = process.env[name];
3
+ if (v === undefined || v === "")
4
+ return fallback;
5
+ return ["1", "true", "yes", "on"].includes(v.toLowerCase());
6
+ }
7
+ function list(name) {
8
+ const v = process.env[name];
9
+ if (!v)
10
+ return [];
11
+ return v.split(",").map((s) => s.trim()).filter(Boolean);
12
+ }
13
+ function parseMode() {
14
+ const raw = (process.env.AZDO_MODE ?? "read-only").toLowerCase();
15
+ if (raw === "read-only" || raw === "read-write" || raw === "admin")
16
+ return raw;
17
+ throw new Error(`Invalid AZDO_MODE '${raw}'. Expected one of: read-only, read-write, admin.`);
18
+ }
19
+ export function loadConfig() {
20
+ const org = process.env.AZDO_ORG_URL || (process.env.AZDO_ORG ? `https://dev.azure.com/${process.env.AZDO_ORG}` : "");
21
+ if (!org)
22
+ throw new Error("Missing AZDO_ORG_URL (or AZDO_ORG) — your Azure DevOps organization.");
23
+ const pat = process.env.AZDO_PAT;
24
+ if (!pat)
25
+ throw new Error("Missing AZDO_PAT — an Azure DevOps Personal Access Token.");
26
+ return {
27
+ connection: {
28
+ orgUrl: org.replace(/\/+$/, ""),
29
+ pat,
30
+ apiVersion: process.env.AZDO_API_VERSION || "7.1",
31
+ requestTimeout: Number(process.env.AZDO_TIMEOUT_MS ?? 20000),
32
+ },
33
+ security: {
34
+ mode: parseMode(),
35
+ projectAllowlist: list("AZDO_PROJECT_ALLOWLIST"),
36
+ protectedProjects: list("AZDO_PROTECTED_PROJECTS"),
37
+ allowDelete: bool("AZDO_ALLOW_DELETE", false),
38
+ requireConfirmation: bool("AZDO_REQUIRE_CONFIRMATION", true),
39
+ dryRun: bool("AZDO_DRY_RUN", false),
40
+ auditLog: bool("AZDO_AUDIT_LOG", true),
41
+ },
42
+ };
43
+ }
44
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAgBA,SAAS,IAAI,CAAC,IAAY,EAAE,QAAiB;IAC3C,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE;QAAE,OAAO,QAAQ,CAAC;IACjD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,IAAI,CAAC,IAAY;IACxB,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAClB,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;IACjE,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,YAAY,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,GAAG,CAAC;IAC/E,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,mDAAmD,CAAC,CAAC;AAChG,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,yBAAyB,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtH,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;IAClG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;IACjC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;IACvF,OAAO;QACL,UAAU,EAAE;YACV,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC/B,GAAG;YACH,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,KAAK;YACjD,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,KAAK,CAAC;SAC7D;QACD,QAAQ,EAAE;YACR,IAAI,EAAE,SAAS,EAAE;YACjB,gBAAgB,EAAE,IAAI,CAAC,wBAAwB,CAAC;YAChD,iBAAiB,EAAE,IAAI,CAAC,yBAAyB,CAAC;YAClD,WAAW,EAAE,IAAI,CAAC,mBAAmB,EAAE,KAAK,CAAC;YAC7C,mBAAmB,EAAE,IAAI,CAAC,2BAA2B,EAAE,IAAI,CAAC;YAC5D,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC;YACnC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC;SACvC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Thin fetch-based client for the Azure DevOps REST API.
3
+ * Auth is a Personal Access Token sent as HTTP Basic (username empty).
4
+ * https://learn.microsoft.com/rest/api/azure/devops
5
+ */
6
+ import type { AzdoConnection } from "../config.js";
7
+ export declare class AzdoError extends Error {
8
+ readonly status: number;
9
+ readonly body?: string | undefined;
10
+ constructor(message: string, status: number, body?: string | undefined);
11
+ }
12
+ export declare class AzdoClient {
13
+ private readonly conn;
14
+ private readonly authHeader;
15
+ constructor(conn: AzdoConnection);
16
+ /** Low-level request. `path` is appended to the org URL; api-version is added automatically. */
17
+ request<T = unknown>(method: string, path: string, opts?: {
18
+ query?: Record<string, string | number | undefined>;
19
+ body?: unknown;
20
+ contentType?: string;
21
+ }): Promise<T>;
22
+ listProjects(): Promise<unknown>;
23
+ getProject(project: string): Promise<unknown>;
24
+ listTeams(project: string): Promise<unknown>;
25
+ listTeamMembers(project: string, team: string): Promise<unknown>;
26
+ listProcesses(): Promise<unknown>;
27
+ createProject(body: unknown): Promise<unknown>;
28
+ deleteProject(projectId: string): Promise<unknown>;
29
+ queryWorkItems(project: string, wiql: string): Promise<unknown>;
30
+ getWorkItem(id: number): Promise<unknown>;
31
+ createWorkItem(project: string, type: string, patch: Array<Record<string, unknown>>): Promise<unknown>;
32
+ updateWorkItem(id: number, patch: Array<Record<string, unknown>>): Promise<unknown>;
33
+ listIterations(project: string, team: string): Promise<unknown>;
34
+ listRepositories(project: string): Promise<unknown>;
35
+ listBranches(project: string, repo: string): Promise<unknown>;
36
+ listPullRequests(project: string, repo: string, status: string): Promise<unknown>;
37
+ getPullRequest(project: string, repo: string, id: number): Promise<unknown>;
38
+ createPullRequest(project: string, repo: string, body: unknown): Promise<unknown>;
39
+ listPipelines(project: string): Promise<unknown>;
40
+ runPipeline(project: string, pipelineId: number, body: unknown): Promise<unknown>;
41
+ listBuilds(project: string, top: number): Promise<unknown>;
42
+ getBuild(project: string, buildId: number): Promise<unknown>;
43
+ }
@@ -0,0 +1,127 @@
1
+ export class AzdoError extends Error {
2
+ status;
3
+ body;
4
+ constructor(message, status, body) {
5
+ super(message);
6
+ this.status = status;
7
+ this.body = body;
8
+ this.name = "AzdoError";
9
+ }
10
+ }
11
+ export class AzdoClient {
12
+ conn;
13
+ authHeader;
14
+ constructor(conn) {
15
+ this.conn = conn;
16
+ this.authHeader = `Basic ${Buffer.from(`:${conn.pat}`).toString("base64")}`;
17
+ }
18
+ /** Low-level request. `path` is appended to the org URL; api-version is added automatically. */
19
+ async request(method, path, opts = {}) {
20
+ const url = new URL(`${this.conn.orgUrl}${path}`);
21
+ if (!url.searchParams.has("api-version"))
22
+ url.searchParams.set("api-version", this.conn.apiVersion);
23
+ for (const [k, v] of Object.entries(opts.query ?? {}))
24
+ if (v !== undefined)
25
+ url.searchParams.set(k, String(v));
26
+ const ctrl = new AbortController();
27
+ const timer = setTimeout(() => ctrl.abort(), this.conn.requestTimeout);
28
+ try {
29
+ const res = await fetch(url, {
30
+ method,
31
+ headers: {
32
+ Authorization: this.authHeader,
33
+ Accept: "application/json",
34
+ ...(opts.body !== undefined ? { "Content-Type": opts.contentType ?? "application/json" } : {}),
35
+ },
36
+ body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
37
+ signal: ctrl.signal,
38
+ });
39
+ const text = await res.text();
40
+ if (!res.ok) {
41
+ throw new AzdoError(`Azure DevOps ${method} ${path} failed with ${res.status}.`, res.status, text);
42
+ }
43
+ return (text ? JSON.parse(text) : undefined);
44
+ }
45
+ finally {
46
+ clearTimeout(timer);
47
+ }
48
+ }
49
+ // --- Projects / teams / process --------------------------------------------
50
+ listProjects() {
51
+ return this.request("GET", "/_apis/projects");
52
+ }
53
+ getProject(project) {
54
+ return this.request("GET", `/_apis/projects/${encodeURIComponent(project)}`);
55
+ }
56
+ listTeams(project) {
57
+ return this.request("GET", `/_apis/projects/${encodeURIComponent(project)}/teams`);
58
+ }
59
+ listTeamMembers(project, team) {
60
+ return this.request("GET", `/_apis/projects/${encodeURIComponent(project)}/teams/${encodeURIComponent(team)}/members`);
61
+ }
62
+ listProcesses() {
63
+ return this.request("GET", "/_apis/work/processes");
64
+ }
65
+ createProject(body) {
66
+ return this.request("POST", "/_apis/projects", { body });
67
+ }
68
+ deleteProject(projectId) {
69
+ return this.request("DELETE", `/_apis/projects/${encodeURIComponent(projectId)}`);
70
+ }
71
+ // --- Work items -------------------------------------------------------------
72
+ queryWorkItems(project, wiql) {
73
+ return this.request("POST", `/${encodeURIComponent(project)}/_apis/wit/wiql`, { body: { query: wiql } });
74
+ }
75
+ getWorkItem(id) {
76
+ return this.request("GET", `/_apis/wit/workitems/${id}`, { query: { $expand: "fields" } });
77
+ }
78
+ createWorkItem(project, type, patch) {
79
+ return this.request("POST", `/${encodeURIComponent(project)}/_apis/wit/workitems/$${encodeURIComponent(type)}`, {
80
+ body: patch,
81
+ contentType: "application/json-patch+json",
82
+ });
83
+ }
84
+ updateWorkItem(id, patch) {
85
+ return this.request("PATCH", `/_apis/wit/workitems/${id}`, {
86
+ body: patch,
87
+ contentType: "application/json-patch+json",
88
+ });
89
+ }
90
+ listIterations(project, team) {
91
+ return this.request("GET", `/${encodeURIComponent(project)}/${encodeURIComponent(team)}/_apis/work/teamsettings/iterations`);
92
+ }
93
+ // --- Repos / PRs ------------------------------------------------------------
94
+ listRepositories(project) {
95
+ return this.request("GET", `/${encodeURIComponent(project)}/_apis/git/repositories`);
96
+ }
97
+ listBranches(project, repo) {
98
+ return this.request("GET", `/${encodeURIComponent(project)}/_apis/git/repositories/${encodeURIComponent(repo)}/refs`, {
99
+ query: { filter: "heads/" },
100
+ });
101
+ }
102
+ listPullRequests(project, repo, status) {
103
+ return this.request("GET", `/${encodeURIComponent(project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests`, { query: { "searchCriteria.status": status } });
104
+ }
105
+ getPullRequest(project, repo, id) {
106
+ return this.request("GET", `/${encodeURIComponent(project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests/${id}`);
107
+ }
108
+ createPullRequest(project, repo, body) {
109
+ return this.request("POST", `/${encodeURIComponent(project)}/_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests`, { body });
110
+ }
111
+ // --- Pipelines / builds -----------------------------------------------------
112
+ listPipelines(project) {
113
+ return this.request("GET", `/${encodeURIComponent(project)}/_apis/pipelines`);
114
+ }
115
+ runPipeline(project, pipelineId, body) {
116
+ return this.request("POST", `/${encodeURIComponent(project)}/_apis/pipelines/${pipelineId}/runs`, { body });
117
+ }
118
+ listBuilds(project, top) {
119
+ return this.request("GET", `/${encodeURIComponent(project)}/_apis/build/builds`, {
120
+ query: { $top: top, queryOrder: "finishTimeDescending" },
121
+ });
122
+ }
123
+ getBuild(project, buildId) {
124
+ return this.request("GET", `/${encodeURIComponent(project)}/_apis/build/builds/${buildId}`);
125
+ }
126
+ }
127
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/devops/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { loadConfig } from "./config.js";
4
+ import { buildServer } from "./server.js";
5
+ async function main() {
6
+ let config;
7
+ try {
8
+ config = loadConfig();
9
+ }
10
+ catch (err) {
11
+ process.stderr.write(`[azure-devops-mcp] Configuration error: ${err.message}\n`);
12
+ process.exit(1);
13
+ }
14
+ const { server, enabled } = buildServer(config);
15
+ process.stderr.write(`[azure-devops-mcp] Starting in '${config.security.mode}' mode` +
16
+ `${config.security.dryRun ? " (DRY RUN)" : ""}. ` +
17
+ `${enabled.length} tools enabled: ${enabled.join(", ")}\n`);
18
+ const transport = new StdioServerTransport();
19
+ await server.connect(transport);
20
+ }
21
+ main().catch((err) => {
22
+ process.stderr.write(`[azure-devops-mcp] Fatal: ${err.stack ?? err}\n`);
23
+ process.exit(1);
24
+ });
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,KAAK,UAAU,IAAI;IACjB,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,UAAU,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2CAA4C,GAAa,CAAC,OAAO,IAAI,CAAC,CAAC;QAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mCAAmC,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ;QAC7D,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI;QACjD,GAAG,OAAO,CAAC,MAAM,mBAAmB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAC7D,CAAC;IACF,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA8B,GAAa,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;IACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Governance & security policy engine.
3
+ *
4
+ * On top of the shared model (access modes + capability gating + allowlists +
5
+ * protected resources + delete gating + dry-run + audit), this server adds two
6
+ * governance controls suited to Azure DevOps:
7
+ * - Project scoping: an allowlist of projects, plus protected projects that can
8
+ * be read but never mutated.
9
+ * - High-impact confirmation: destructive operations must echo back the exact
10
+ * target name (a typed confirmation), not just a boolean, before they run.
11
+ *
12
+ * Pure logic, no I/O — fully unit-testable.
13
+ */
14
+ export type Capability = "read" | "write" | "admin";
15
+ export type AccessMode = "read-only" | "read-write" | "admin";
16
+ export interface SecurityConfig {
17
+ mode: AccessMode;
18
+ /** If set, only these projects may be touched. Empty = all. */
19
+ projectAllowlist: string[];
20
+ /** Projects that can be read but never mutated or deleted. */
21
+ protectedProjects: string[];
22
+ /** Destructive delete_* operations require this. */
23
+ allowDelete: boolean;
24
+ /** High-impact (destructive) operations require a typed confirmation match. */
25
+ requireConfirmation: boolean;
26
+ /** Validate + log writes without sending them to Azure DevOps. */
27
+ dryRun: boolean;
28
+ auditLog: boolean;
29
+ }
30
+ export declare class PolicyError extends Error {
31
+ constructor(message: string);
32
+ }
33
+ export interface GuardContext {
34
+ tool: string;
35
+ capability: Capability;
36
+ /** Project the operation targets, if any. */
37
+ project?: string;
38
+ /** Destructive delete operation — needs allowDelete. */
39
+ destructive?: boolean;
40
+ /** For high-impact ops: the exact value the caller must type to confirm. */
41
+ confirmExpected?: string;
42
+ /** The confirmation the caller actually provided. */
43
+ confirmProvided?: string;
44
+ }
45
+ export declare class SecurityPolicy {
46
+ private readonly config;
47
+ constructor(config: SecurityConfig);
48
+ get mode(): AccessMode;
49
+ isCapabilityEnabled(capability: Capability): boolean;
50
+ isProjectAllowed(project: string): boolean;
51
+ isProjectProtected(project: string): boolean;
52
+ guard(ctx: GuardContext): {
53
+ dryRun: boolean;
54
+ };
55
+ private audit;
56
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Governance & security policy engine.
3
+ *
4
+ * On top of the shared model (access modes + capability gating + allowlists +
5
+ * protected resources + delete gating + dry-run + audit), this server adds two
6
+ * governance controls suited to Azure DevOps:
7
+ * - Project scoping: an allowlist of projects, plus protected projects that can
8
+ * be read but never mutated.
9
+ * - High-impact confirmation: destructive operations must echo back the exact
10
+ * target name (a typed confirmation), not just a boolean, before they run.
11
+ *
12
+ * Pure logic, no I/O — fully unit-testable.
13
+ */
14
+ const MODE_RANK = { "read-only": 0, "read-write": 1, admin: 2 };
15
+ const CAPABILITY_RANK = { read: 0, write: 1, admin: 2 };
16
+ export class PolicyError extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "PolicyError";
20
+ }
21
+ }
22
+ export class SecurityPolicy {
23
+ config;
24
+ constructor(config) {
25
+ this.config = config;
26
+ }
27
+ get mode() {
28
+ return this.config.mode;
29
+ }
30
+ isCapabilityEnabled(capability) {
31
+ return CAPABILITY_RANK[capability] <= MODE_RANK[this.config.mode];
32
+ }
33
+ isProjectAllowed(project) {
34
+ if (this.config.projectAllowlist.length === 0)
35
+ return true;
36
+ return this.config.projectAllowlist.includes(project);
37
+ }
38
+ isProjectProtected(project) {
39
+ return this.config.protectedProjects.includes(project);
40
+ }
41
+ guard(ctx) {
42
+ if (!this.isCapabilityEnabled(ctx.capability)) {
43
+ this.audit(ctx, "DENY", `capability '${ctx.capability}' exceeds mode '${this.config.mode}'`);
44
+ throw new PolicyError(`Operation '${ctx.tool}' requires '${ctx.capability}' access but the server runs in '${this.config.mode}' mode.`);
45
+ }
46
+ if (ctx.project !== undefined) {
47
+ if (!this.isProjectAllowed(ctx.project)) {
48
+ this.audit(ctx, "DENY", `project '${ctx.project}' not in allowlist`);
49
+ throw new PolicyError(`Project '${ctx.project}' is not in the configured allowlist (AZDO_PROJECT_ALLOWLIST).`);
50
+ }
51
+ if (ctx.capability !== "read" && this.isProjectProtected(ctx.project)) {
52
+ this.audit(ctx, "DENY", `project '${ctx.project}' is protected`);
53
+ throw new PolicyError(`Project '${ctx.project}' is protected (AZDO_PROTECTED_PROJECTS); mutations are refused.`);
54
+ }
55
+ }
56
+ if (ctx.destructive && !this.config.allowDelete) {
57
+ this.audit(ctx, "DENY", "delete not enabled");
58
+ throw new PolicyError(`Destructive operation '${ctx.tool}' is disabled. Set AZDO_ALLOW_DELETE=true to enable it.`);
59
+ }
60
+ // Governance: typed confirmation for high-impact operations.
61
+ if (ctx.destructive && this.config.requireConfirmation && ctx.confirmExpected !== undefined) {
62
+ if (ctx.confirmProvided !== ctx.confirmExpected) {
63
+ this.audit(ctx, "DENY", "confirmation mismatch");
64
+ throw new PolicyError(`Operation '${ctx.tool}' is high-impact. Re-run with confirm="${ctx.confirmExpected}" to proceed.`);
65
+ }
66
+ }
67
+ const dryRun = ctx.capability !== "read" && this.config.dryRun;
68
+ this.audit(ctx, dryRun ? "DRY_RUN" : "ALLOW");
69
+ return { dryRun };
70
+ }
71
+ audit(ctx, decision, reason) {
72
+ if (!this.config.auditLog)
73
+ return;
74
+ const line = {
75
+ ts: new Date().toISOString(),
76
+ audit: "azure-devops-mcp",
77
+ decision,
78
+ tool: ctx.tool,
79
+ capability: ctx.capability,
80
+ project: ctx.project ?? null,
81
+ destructive: ctx.destructive ?? false,
82
+ ...(reason ? { reason } : {}),
83
+ };
84
+ process.stderr.write(`${JSON.stringify(line)}\n`);
85
+ }
86
+ }
87
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { AppConfig } from "./config.js";
3
+ export declare function buildServer(config: AppConfig): {
4
+ server: McpServer;
5
+ enabled: string[];
6
+ };
package/dist/server.js ADDED
@@ -0,0 +1,44 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { AzdoClient, AzdoError } from "./devops/client.js";
3
+ import { PolicyError, SecurityPolicy } from "./security.js";
4
+ import { adminTools } from "./tools/admin.js";
5
+ import { readTools } from "./tools/read.js";
6
+ import { writeTools } from "./tools/write.js";
7
+ const ALL_TOOLS = [...readTools, ...writeTools, ...adminTools];
8
+ export function buildServer(config) {
9
+ const policy = new SecurityPolicy(config.security);
10
+ const client = new AzdoClient(config.connection);
11
+ const ctx = { client, policy };
12
+ const server = new McpServer({ name: "mcp-azure-devops", version: "0.1.0" });
13
+ const enabled = [];
14
+ for (const tool of ALL_TOOLS) {
15
+ if (!policy.isCapabilityEnabled(tool.capability))
16
+ continue;
17
+ enabled.push(tool.name);
18
+ server.registerTool(tool.name, tool.config, async (args) => {
19
+ try {
20
+ return await tool.handler(args ?? {}, ctx);
21
+ }
22
+ catch (err) {
23
+ return toErrorResult(err);
24
+ }
25
+ });
26
+ }
27
+ return { server, enabled };
28
+ }
29
+ function toErrorResult(err) {
30
+ let message;
31
+ if (err instanceof PolicyError)
32
+ message = `Policy denied: ${err.message}`;
33
+ else if (err instanceof AzdoError)
34
+ message = `Azure DevOps error (${err.status}): ${err.message}${err.body ? `\n${truncate(err.body)}` : ""}`;
35
+ else if (err instanceof Error)
36
+ message = `${err.name}: ${err.message}`;
37
+ else
38
+ message = String(err);
39
+ return { content: [{ type: "text", text: message }], isError: true };
40
+ }
41
+ function truncate(s, max = 800) {
42
+ return s.length > max ? `${s.slice(0, max)}…` : s;
43
+ }
44
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,SAAS,GAAc,CAAC,GAAG,SAAS,EAAE,GAAG,UAAU,EAAE,GAAG,UAAU,CAAC,CAAC;AAE1E,MAAM,UAAU,WAAW,CAAC,MAAiB;IAC3C,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,GAAG,GAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAE7E,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,SAAS;QAC3D,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAA6B,EAAE,EAAE;YAClF,IAAI,CAAC;gBACH,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;YAC7C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AAED,SAAS,aAAa,CAAC,GAAY;IACjC,IAAI,OAAe,CAAC;IACpB,IAAI,GAAG,YAAY,WAAW;QAAE,OAAO,GAAG,kBAAkB,GAAG,CAAC,OAAO,EAAE,CAAC;SACrE,IAAI,GAAG,YAAY,SAAS;QAAE,OAAO,GAAG,uBAAuB,GAAG,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;SACzI,IAAI,GAAG,YAAY,KAAK;QAAE,OAAO,GAAG,GAAG,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC;;QAClE,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAChF,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS,EAAE,GAAG,GAAG,GAAG;IACpC,OAAO,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { ToolDef } from "./types.js";
2
+ /**
3
+ * Admin tools. create_project needs admin mode; delete_project is high-impact:
4
+ * admin mode + AZDO_ALLOW_DELETE + a typed confirmation matching the project name.
5
+ */
6
+ export declare const adminTools: ToolDef[];
@@ -0,0 +1,69 @@
1
+ import { z } from "zod";
2
+ import { jsonResult, textResult } from "./types.js";
3
+ /**
4
+ * Admin tools. create_project needs admin mode; delete_project is high-impact:
5
+ * admin mode + AZDO_ALLOW_DELETE + a typed confirmation matching the project name.
6
+ */
7
+ export const adminTools = [
8
+ {
9
+ name: "create_project",
10
+ capability: "admin",
11
+ config: {
12
+ title: "Create project",
13
+ description: "Create a new project. Requires admin mode. Provide a process template id (see list_processes).",
14
+ inputSchema: {
15
+ name: z.string(),
16
+ description: z.string().optional(),
17
+ processTemplateId: z.string().describe("Process template id from list_processes"),
18
+ sourceControl: z.enum(["Git", "Tfvc"]).optional().describe("Default Git"),
19
+ visibility: z.enum(["private", "public"]).optional().describe("Default private"),
20
+ },
21
+ },
22
+ handler: async (a, { client, policy }) => {
23
+ const name = a.name;
24
+ const { dryRun } = policy.guard({ tool: "create_project", capability: "admin", project: name });
25
+ const body = {
26
+ name,
27
+ description: a.description,
28
+ capabilities: {
29
+ versioncontrol: { sourceControlType: a.sourceControl ?? "Git" },
30
+ processTemplate: { templateTypeId: a.processTemplateId },
31
+ },
32
+ visibility: a.visibility ?? "private",
33
+ };
34
+ if (dryRun)
35
+ return textResult(`[dry-run] Would create project '${name}'.`);
36
+ return jsonResult(await client.createProject(body));
37
+ },
38
+ },
39
+ {
40
+ name: "delete_project",
41
+ capability: "admin",
42
+ config: {
43
+ title: "Delete project",
44
+ description: "Permanently delete a project (all repos, work items, pipelines). Requires admin mode, AZDO_ALLOW_DELETE=true, " +
45
+ "and a typed confirmation equal to the project name. Irreversible.",
46
+ inputSchema: {
47
+ projectId: z.string().describe("Project id (GUID)"),
48
+ projectName: z.string().describe("Project name — used for the confirmation check"),
49
+ confirm: z.string().optional().describe("Must equal the project name to proceed"),
50
+ },
51
+ },
52
+ handler: async (a, { client, policy }) => {
53
+ const projectName = a.projectName;
54
+ const { dryRun } = policy.guard({
55
+ tool: "delete_project",
56
+ capability: "admin",
57
+ project: projectName,
58
+ destructive: true,
59
+ confirmExpected: projectName,
60
+ confirmProvided: a.confirm,
61
+ });
62
+ if (dryRun)
63
+ return textResult(`[dry-run] Would delete project '${projectName}' (${a.projectId}).`);
64
+ await client.deleteProject(a.projectId);
65
+ return jsonResult({ deleted: true, project: projectName });
66
+ },
67
+ },
68
+ ];
69
+ //# sourceMappingURL=admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.js","sourceRoot":"","sources":["../../src/tools/admin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { ToolDef } from "./types.js";
2
+ export declare const readTools: ToolDef[];
@@ -0,0 +1,212 @@
1
+ import { z } from "zod";
2
+ import { jsonResult } from "./types.js";
3
+ export const readTools = [
4
+ {
5
+ name: "list_projects",
6
+ capability: "read",
7
+ config: { title: "List projects", description: "List projects in the organization (filtered by the allowlist).", inputSchema: {} },
8
+ handler: async (_a, { client, policy }) => {
9
+ policy.guard({ tool: "list_projects", capability: "read" });
10
+ const data = (await client.listProjects());
11
+ const projects = (data.value ?? [])
12
+ .filter((p) => policy.isProjectAllowed(p.name))
13
+ .map((p) => ({ id: p.id, name: p.name, state: p.state, protected: policy.isProjectProtected(p.name) }));
14
+ return jsonResult(projects);
15
+ },
16
+ },
17
+ {
18
+ name: "get_project",
19
+ capability: "read",
20
+ config: {
21
+ title: "Get project",
22
+ description: "Fetch details of a single project.",
23
+ inputSchema: { project: z.string().describe("Project name or id") },
24
+ },
25
+ handler: async (a, { client, policy }) => {
26
+ const project = a.project;
27
+ policy.guard({ tool: "get_project", capability: "read", project });
28
+ return jsonResult(await client.getProject(project));
29
+ },
30
+ },
31
+ {
32
+ name: "list_teams",
33
+ capability: "read",
34
+ config: {
35
+ title: "List teams",
36
+ description: "List teams in a project.",
37
+ inputSchema: { project: z.string().describe("Project name or id") },
38
+ },
39
+ handler: async (a, { client, policy }) => {
40
+ const project = a.project;
41
+ policy.guard({ tool: "list_teams", capability: "read", project });
42
+ return jsonResult(await client.listTeams(project));
43
+ },
44
+ },
45
+ {
46
+ name: "list_team_members",
47
+ capability: "read",
48
+ config: {
49
+ title: "List team members",
50
+ description: "List the members of a team.",
51
+ inputSchema: { project: z.string(), team: z.string().describe("Team id or name") },
52
+ },
53
+ handler: async (a, { client, policy }) => {
54
+ const project = a.project;
55
+ policy.guard({ tool: "list_team_members", capability: "read", project });
56
+ return jsonResult(await client.listTeamMembers(project, a.team));
57
+ },
58
+ },
59
+ {
60
+ name: "list_processes",
61
+ capability: "read",
62
+ config: { title: "List processes", description: "List organization process templates (Agile, Scrum, CMMI, inherited).", inputSchema: {} },
63
+ handler: async (_a, { client, policy }) => {
64
+ policy.guard({ tool: "list_processes", capability: "read" });
65
+ return jsonResult(await client.listProcesses());
66
+ },
67
+ },
68
+ {
69
+ name: "query_work_items",
70
+ capability: "read",
71
+ config: {
72
+ title: "Query work items (WIQL)",
73
+ description: "Run a WIQL query and return matching work item ids. Example: SELECT [System.Id] FROM WorkItems WHERE [System.State] = 'Active'.",
74
+ inputSchema: { project: z.string(), wiql: z.string().describe("A WIQL query string") },
75
+ },
76
+ handler: async (a, { client, policy }) => {
77
+ const project = a.project;
78
+ policy.guard({ tool: "query_work_items", capability: "read", project });
79
+ return jsonResult(await client.queryWorkItems(project, a.wiql));
80
+ },
81
+ },
82
+ {
83
+ name: "get_work_item",
84
+ capability: "read",
85
+ config: {
86
+ title: "Get work item",
87
+ description: "Fetch a single work item with its fields.",
88
+ inputSchema: { id: z.number().int().describe("Work item id") },
89
+ },
90
+ handler: async (a, { client, policy }) => {
91
+ policy.guard({ tool: "get_work_item", capability: "read" });
92
+ return jsonResult(await client.getWorkItem(a.id));
93
+ },
94
+ },
95
+ {
96
+ name: "list_iterations",
97
+ capability: "read",
98
+ config: {
99
+ title: "List iterations (sprints)",
100
+ description: "List a team's iterations / sprints.",
101
+ inputSchema: { project: z.string(), team: z.string() },
102
+ },
103
+ handler: async (a, { client, policy }) => {
104
+ const project = a.project;
105
+ policy.guard({ tool: "list_iterations", capability: "read", project });
106
+ return jsonResult(await client.listIterations(project, a.team));
107
+ },
108
+ },
109
+ {
110
+ name: "list_repositories",
111
+ capability: "read",
112
+ config: {
113
+ title: "List repositories",
114
+ description: "List Git repositories in a project.",
115
+ inputSchema: { project: z.string() },
116
+ },
117
+ handler: async (a, { client, policy }) => {
118
+ const project = a.project;
119
+ policy.guard({ tool: "list_repositories", capability: "read", project });
120
+ return jsonResult(await client.listRepositories(project));
121
+ },
122
+ },
123
+ {
124
+ name: "list_branches",
125
+ capability: "read",
126
+ config: {
127
+ title: "List branches",
128
+ description: "List branches (heads) of a repository.",
129
+ inputSchema: { project: z.string(), repo: z.string().describe("Repository id or name") },
130
+ },
131
+ handler: async (a, { client, policy }) => {
132
+ const project = a.project;
133
+ policy.guard({ tool: "list_branches", capability: "read", project });
134
+ return jsonResult(await client.listBranches(project, a.repo));
135
+ },
136
+ },
137
+ {
138
+ name: "list_pull_requests",
139
+ capability: "read",
140
+ config: {
141
+ title: "List pull requests",
142
+ description: "List pull requests in a repository, by status.",
143
+ inputSchema: {
144
+ project: z.string(),
145
+ repo: z.string(),
146
+ status: z.enum(["active", "completed", "abandoned", "all"]).optional().describe("Default active"),
147
+ },
148
+ },
149
+ handler: async (a, { client, policy }) => {
150
+ const project = a.project;
151
+ policy.guard({ tool: "list_pull_requests", capability: "read", project });
152
+ return jsonResult(await client.listPullRequests(project, a.repo, a.status ?? "active"));
153
+ },
154
+ },
155
+ {
156
+ name: "get_pull_request",
157
+ capability: "read",
158
+ config: {
159
+ title: "Get pull request",
160
+ description: "Fetch a single pull request.",
161
+ inputSchema: { project: z.string(), repo: z.string(), id: z.number().int() },
162
+ },
163
+ handler: async (a, { client, policy }) => {
164
+ const project = a.project;
165
+ policy.guard({ tool: "get_pull_request", capability: "read", project });
166
+ return jsonResult(await client.getPullRequest(project, a.repo, a.id));
167
+ },
168
+ },
169
+ {
170
+ name: "list_pipelines",
171
+ capability: "read",
172
+ config: {
173
+ title: "List pipelines",
174
+ description: "List pipelines in a project.",
175
+ inputSchema: { project: z.string() },
176
+ },
177
+ handler: async (a, { client, policy }) => {
178
+ const project = a.project;
179
+ policy.guard({ tool: "list_pipelines", capability: "read", project });
180
+ return jsonResult(await client.listPipelines(project));
181
+ },
182
+ },
183
+ {
184
+ name: "list_builds",
185
+ capability: "read",
186
+ config: {
187
+ title: "List builds",
188
+ description: "List recent builds/runs in a project (most recent first).",
189
+ inputSchema: { project: z.string(), top: z.number().int().min(1).max(200).optional().describe("Default 20") },
190
+ },
191
+ handler: async (a, { client, policy }) => {
192
+ const project = a.project;
193
+ policy.guard({ tool: "list_builds", capability: "read", project });
194
+ return jsonResult(await client.listBuilds(project, a.top ?? 20));
195
+ },
196
+ },
197
+ {
198
+ name: "get_build",
199
+ capability: "read",
200
+ config: {
201
+ title: "Get build",
202
+ description: "Fetch a single build/run with status and result.",
203
+ inputSchema: { project: z.string(), buildId: z.number().int() },
204
+ },
205
+ handler: async (a, { client, policy }) => {
206
+ const project = a.project;
207
+ policy.guard({ tool: "get_build", capability: "read", project });
208
+ return jsonResult(await client.getBuild(project, a.buildId));
209
+ },
210
+ },
211
+ ];
212
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { ZodRawShape } from "zod";
2
+ import type { AzdoClient } from "../devops/client.js";
3
+ import type { Capability, SecurityPolicy } from "../security.js";
4
+ export interface ToolContext {
5
+ client: AzdoClient;
6
+ policy: SecurityPolicy;
7
+ }
8
+ export interface ToolResult {
9
+ content: Array<{
10
+ type: "text";
11
+ text: string;
12
+ }>;
13
+ isError?: boolean;
14
+ [key: string]: unknown;
15
+ }
16
+ export interface ToolDef<Shape extends ZodRawShape = ZodRawShape> {
17
+ name: string;
18
+ capability: Capability;
19
+ config: {
20
+ title: string;
21
+ description: string;
22
+ inputSchema: Shape;
23
+ };
24
+ handler: (args: Record<string, unknown>, ctx: ToolContext) => Promise<ToolResult>;
25
+ }
26
+ export declare function jsonResult(value: unknown): ToolResult;
27
+ export declare function textResult(text: string): ToolResult;
@@ -0,0 +1,7 @@
1
+ export function jsonResult(value) {
2
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
3
+ }
4
+ export function textResult(text) {
5
+ return { content: [{ type: "text", text }] };
6
+ }
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"AAsBA,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAC/E,CAAC;AACD,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { ToolDef } from "./types.js";
2
+ export declare const writeTools: ToolDef[];
@@ -0,0 +1,119 @@
1
+ import { z } from "zod";
2
+ import { jsonResult, textResult } from "./types.js";
3
+ export const writeTools = [
4
+ {
5
+ name: "create_work_item",
6
+ capability: "write",
7
+ config: {
8
+ title: "Create work item",
9
+ description: "Create a work item (e.g. Bug, Task, User Story) with a title and optional description.",
10
+ inputSchema: {
11
+ project: z.string(),
12
+ type: z.string().describe("Work item type, e.g. Bug, Task, User Story"),
13
+ title: z.string(),
14
+ description: z.string().optional(),
15
+ assignedTo: z.string().optional().describe("User email/UPN to assign"),
16
+ },
17
+ },
18
+ handler: async (a, { client, policy }) => {
19
+ const project = a.project;
20
+ const { dryRun } = policy.guard({ tool: "create_work_item", capability: "write", project });
21
+ const patch = [{ op: "add", path: "/fields/System.Title", value: a.title }];
22
+ if (a.description)
23
+ patch.push({ op: "add", path: "/fields/System.Description", value: a.description });
24
+ if (a.assignedTo)
25
+ patch.push({ op: "add", path: "/fields/System.AssignedTo", value: a.assignedTo });
26
+ if (dryRun)
27
+ return textResult(`[dry-run] Would create ${a.type} in '${project}': ${JSON.stringify(patch)}`);
28
+ return jsonResult(await client.createWorkItem(project, a.type, patch));
29
+ },
30
+ },
31
+ {
32
+ name: "update_work_item",
33
+ capability: "write",
34
+ config: {
35
+ title: "Update work item",
36
+ description: "Update fields of a work item (title, state, assignee, or arbitrary field paths).",
37
+ inputSchema: {
38
+ project: z.string().describe("Project (for scoping/audit)"),
39
+ id: z.number().int(),
40
+ title: z.string().optional(),
41
+ state: z.string().optional().describe("e.g. Active, Resolved, Closed"),
42
+ assignedTo: z.string().optional(),
43
+ fields: z.record(z.any()).optional().describe("Extra field map, e.g. { \"System.Tags\": \"urgent\" }"),
44
+ },
45
+ },
46
+ handler: async (a, { client, policy }) => {
47
+ const project = a.project;
48
+ const { dryRun } = policy.guard({ tool: "update_work_item", capability: "write", project });
49
+ const patch = [];
50
+ if (a.title)
51
+ patch.push({ op: "add", path: "/fields/System.Title", value: a.title });
52
+ if (a.state)
53
+ patch.push({ op: "add", path: "/fields/System.State", value: a.state });
54
+ if (a.assignedTo)
55
+ patch.push({ op: "add", path: "/fields/System.AssignedTo", value: a.assignedTo });
56
+ for (const [k, v] of Object.entries(a.fields ?? {})) {
57
+ patch.push({ op: "add", path: `/fields/${k}`, value: v });
58
+ }
59
+ if (patch.length === 0)
60
+ return { content: [{ type: "text", text: "No fields to update." }], isError: true };
61
+ if (dryRun)
62
+ return textResult(`[dry-run] Would update work item ${a.id}: ${JSON.stringify(patch)}`);
63
+ return jsonResult(await client.updateWorkItem(a.id, patch));
64
+ },
65
+ },
66
+ {
67
+ name: "create_pull_request",
68
+ capability: "write",
69
+ config: {
70
+ title: "Create pull request",
71
+ description: "Open a pull request from a source branch into a target branch.",
72
+ inputSchema: {
73
+ project: z.string(),
74
+ repo: z.string(),
75
+ sourceBranch: z.string().describe("e.g. feature/x (without refs/heads/)"),
76
+ targetBranch: z.string().describe("e.g. main"),
77
+ title: z.string(),
78
+ description: z.string().optional(),
79
+ },
80
+ },
81
+ handler: async (a, { client, policy }) => {
82
+ const project = a.project;
83
+ const { dryRun } = policy.guard({ tool: "create_pull_request", capability: "write", project });
84
+ const body = {
85
+ sourceRefName: `refs/heads/${a.sourceBranch.replace(/^refs\/heads\//, "")}`,
86
+ targetRefName: `refs/heads/${a.targetBranch.replace(/^refs\/heads\//, "")}`,
87
+ title: a.title,
88
+ description: a.description,
89
+ };
90
+ if (dryRun)
91
+ return textResult(`[dry-run] Would open PR in ${project}/${a.repo}: ${JSON.stringify(body)}`);
92
+ return jsonResult(await client.createPullRequest(project, a.repo, body));
93
+ },
94
+ },
95
+ {
96
+ name: "run_pipeline",
97
+ capability: "write",
98
+ config: {
99
+ title: "Run pipeline",
100
+ description: "Queue a run of a pipeline, optionally on a specific branch.",
101
+ inputSchema: {
102
+ project: z.string(),
103
+ pipelineId: z.number().int(),
104
+ branch: z.string().optional().describe("Branch to run, e.g. main"),
105
+ },
106
+ },
107
+ handler: async (a, { client, policy }) => {
108
+ const project = a.project;
109
+ const { dryRun } = policy.guard({ tool: "run_pipeline", capability: "write", project });
110
+ const body = a.branch
111
+ ? { resources: { repositories: { self: { refName: `refs/heads/${a.branch.replace(/^refs\/heads\//, "")}` } } } }
112
+ : {};
113
+ if (dryRun)
114
+ return textResult(`[dry-run] Would run pipeline ${a.pipelineId} in '${project}'${a.branch ? ` on ${a.branch}` : ""}.`);
115
+ return jsonResult(await client.runPipeline(project, a.pipelineId, body));
116
+ },
117
+ },
118
+ ];
119
+ //# sourceMappingURL=write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.js","sourceRoot":"","sources":["../../src/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,CAAC,MAAM,UAAU,GAAc;IACnC;QACE,IAAI,EAAE,kBAAkB;QACxB,UAAU,EAAE,OAAO;QACnB,MAAM,EAAE;YACN,KAAK,EAAE,kBAAkB;YACzB,WAAW,EAAE,wFAAwF;YACrG,WAAW,EAAE;gBACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;gBACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;gBACvE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;gBACjB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;gBAClC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;aACvE;SACF;QACD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE;YACvC,MAAM,OAAO,GAAG,CAAC,CAAC,OAAiB,CAAC;YACpC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;YAC5F,MAAM,KAAK,GAAmC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;YAC5G,IAAI,CAAC,CAAC,WAAW;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,4BAA4B,EAAE,KAAK,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;YACvG,IAAI,CAAC,CAAC,UAAU;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,2BAA2B,EAAE,KAAK,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;YACpG,IAAI,MAAM;gBAAE,OAAO,UAAU,CAAC,0BAA0B,CAAC,CAAC,IAAI,QAAQ,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC5G,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,IAAc,EAAE,KAAK,CAAC,CAAC,CAAC;QACnF,CAAC;KACF;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,UAAU,EAAE,OAAO;QACnB,MAAM,EAAE;YACN,KAAK,EAAE,kBAAkB;YACzB,WAAW,EAAE,kFAAkF;YAC/F,WAAW,EAAE;gBACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;gBAC3D,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;gBACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;gBAC5B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;gBACtE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;gBACjC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uDAAuD,CAAC;aACvG;SACF;QACD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE;YACvC,MAAM,OAAO,GAAG,CAAC,CAAC,OAAiB,CAAC;YACpC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;YAC5F,MAAM,KAAK,GAAmC,EAAE,CAAC;YACjD,IAAI,CAAC,CAAC,KAAK;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;YACrF,IAAI,CAAC,CAAC,KAAK;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;YACrF,IAAI,CAAC,CAAC,UAAU;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,2BAA2B,EAAE,KAAK,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;YACpG,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAE,CAAC,CAAC,MAAkC,IAAI,EAAE,CAAC,EAAE,CAAC;gBACjF,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YACrH,IAAI,MAAM;gBAAE,OAAO,UAAU,CAAC,oCAAoC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACpG,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,EAAY,EAAE,KAAK,CAAC,CAAC,CAAC;QACxE,CAAC;KACF;IACD;QACE,IAAI,EAAE,qBAAqB;QAC3B,UAAU,EAAE,OAAO;QACnB,MAAM,EAAE;YACN,KAAK,EAAE,qBAAqB;YAC5B,WAAW,EAAE,gEAAgE;YAC7E,WAAW,EAAE;gBACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;gBACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;gBAChB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;gBACzE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAC9C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;gBACjB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;aACnC;SACF;QACD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE;YACvC,MAAM,OAAO,GAAG,CAAC,CAAC,OAAiB,CAAC;YACpC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;YAC/F,MAAM,IAAI,GAAG;gBACX,aAAa,EAAE,cAAe,CAAC,CAAC,YAAuB,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,EAAE;gBACvF,aAAa,EAAE,cAAe,CAAC,CAAC,YAAuB,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,EAAE;gBACvF,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,WAAW,EAAE,CAAC,CAAC,WAAW;aAC3B,CAAC;YACF,IAAI,MAAM;gBAAE,OAAO,UAAU,CAAC,8BAA8B,OAAO,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC1G,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC,CAAC,IAAc,EAAE,IAAI,CAAC,CAAC,CAAC;QACrF,CAAC;KACF;IACD;QACE,IAAI,EAAE,cAAc;QACpB,UAAU,EAAE,OAAO;QACnB,MAAM,EAAE;YACN,KAAK,EAAE,cAAc;YACrB,WAAW,EAAE,6DAA6D;YAC1E,WAAW,EAAE;gBACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;gBACnB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;gBAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;aACnE;SACF;QACD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE;YACvC,MAAM,OAAO,GAAG,CAAC,CAAC,OAAiB,CAAC;YACpC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;YACxF,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM;gBACnB,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,cAAe,CAAC,CAAC,MAAiB,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE;gBAC5H,CAAC,CAAC,EAAE,CAAC;YACP,IAAI,MAAM;gBAAE,OAAO,UAAU,CAAC,gCAAgC,CAAC,CAAC,UAAU,QAAQ,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACnI,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,UAAoB,EAAE,IAAI,CAAC,CAAC,CAAC;QACrF,CAAC;KACF;CACF,CAAC"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@dockndevai/mcp-azure-devops",
3
+ "mcpName": "io.github.dockndevai/mcp-azure-devops",
4
+ "version": "0.1.0",
5
+ "description": "Model Context Protocol server for Azure DevOps — boards, repos, pipelines, and projects with governance controls (access modes, project scoping, protected projects, delete gating, confirmation, dry-run, audit).",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "repository": { "type": "git", "url": "git+https://github.com/dockndevai/mcp-azure-devops.git" },
9
+ "bugs": { "url": "https://github.com/dockndevai/mcp-azure-devops/issues" },
10
+ "homepage": "https://github.com/dockndevai/mcp-azure-devops#readme",
11
+ "bin": { "mcp-azure-devops": "dist/index.js" },
12
+ "files": ["dist"],
13
+ "engines": { "node": ">=20" },
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.json",
16
+ "dev": "tsx watch src/index.ts",
17
+ "start": "node dist/index.js",
18
+ "prepublishOnly": "npm run build",
19
+ "typecheck": "tsc -p tsconfig.json --noEmit",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "lint": "tsc -p tsconfig.json --noEmit"
23
+ },
24
+ "keywords": ["mcp", "model-context-protocol", "azure-devops", "boards", "pipelines", "devops", "governance", "llm", "ai"],
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "^1.12.0",
27
+ "zod": "^3.23.8"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.9.0",
31
+ "tsx": "^4.19.2",
32
+ "typescript": "^5.6.3",
33
+ "vitest": "^3.2.7"
34
+ }
35
+ }