azdo-onprem-mcp 1.0.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/.env.example ADDED
@@ -0,0 +1,11 @@
1
+ # Copy to .env and fill in (do not commit .env).
2
+ # MCP clients often set these in mcp.json instead of a file.
3
+
4
+ AZURE_BASE_URL=https://devops.example.com/Collection/MyProject
5
+ AZURE_PAT=
6
+
7
+ # Optional
8
+ # AZURE_COLLECTION_URL=https://devops.example.com/Collection
9
+ # AZURE_HTTP_TIMEOUT_MS=120000
10
+ # AZURE_DEVOPS_API_VERSION=7.1
11
+ # MCP_DEBUG=0
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # azdo-onprem-mcp
2
+
3
+ Minimal [Model Context Protocol](https://modelcontextprotocol.io) server for **self-hosted Azure DevOps Server** (on-premises). It uses the REST APIs with a PAT and **Basic** auth (`curl -u ":$PAT"`), same as Azure DevOps Services patterns but with your own base URL.
4
+
5
+ **Not** for `dev.azure.com` only—any DevOps Server reachable over HTTPS works if the APIs respond.
6
+
7
+ ## Requirements
8
+
9
+ - **Node.js** 18+
10
+ - A **Personal Access Token** with at least **Work Items (Read)** (and any other scopes you rely on)
11
+ - **VPN/network** access to your server if required
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install -g azdo-onprem-mcp
17
+ ```
18
+
19
+ Or run without a global install:
20
+
21
+ ```bash
22
+ npx azdo-onprem-mcp
23
+ ```
24
+
25
+ The package exposes the CLI binary **`azdo-onprem-mcp`** (see `package.json` → `bin`).
26
+
27
+ ## Environment variables
28
+
29
+ | Variable | Required | Description |
30
+ |----------|----------|-------------|
31
+ | `AZURE_BASE_URL` | Yes | **Team project** root URL, same as in the browser for that project, e.g. `https://devops.example.com/Collection/MyProject` (no trailing slash required). |
32
+ | `AZURE_PAT` | Yes | Personal Access Token. Sent as Basic auth with an empty username (`Authorization: Basic base64(":PAT")`). |
33
+ | `AZURE_COLLECTION_URL` | No | Overrides collection root for **`listProjects`** only. If unset, the collection URL is derived by dropping the last path segment of `AZURE_BASE_URL` (e.g. `.../Collection/Project` → `.../Collection`). |
34
+ | `AZURE_HTTP_TIMEOUT_MS` | No | HTTP timeout in ms (default `120000`). |
35
+ | `AZURE_DEVOPS_API_VERSION` | No | REST `api-version` query parameter (default `7.1`). Try `7.0` or `6.0` if your server is older. |
36
+ | `MCP_DEBUG` | No | Set to `1` to log request URLs and timing on stderr (no secrets). |
37
+
38
+ Do **not** commit secrets. Prefer Cursor/IDE env injection or your OS secret store.
39
+
40
+ ## Cursor (or any MCP client)
41
+
42
+ Example `mcp.json` entry:
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "azdo-onprem": {
48
+ "command": "node",
49
+ "args": ["/absolute/path/to/azure-org-mcp/dist/server.js"],
50
+ "env": {
51
+ "AZURE_BASE_URL": "https://devops.example.com/Collection/MyProject",
52
+ "AZURE_PAT": "<your-pat>"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ After installing from npm globally, you can point `command` at the binary if it is on your `PATH`, or keep using `node` with the path to `dist/server.js` from a cloned repo (run `npm run build` first).
60
+
61
+ Reload MCP / restart the editor after changing env.
62
+
63
+ ## Tools
64
+
65
+ | Tool | Description |
66
+ |------|-------------|
67
+ | `getWorkItem` | `GET` …`/_apis/wit/workitems/{id}?` `$expand=all` — returns the **full** REST body as **MCP `structuredContent`** and as JSON text. Empty field values are often omitted by Azure DevOps; unset fields are not listed. If the chat UI shows a short snippet, use the structured payload (or your client’s tool-result JSON view). |
68
+ | `searchWorkItems` | WIQL search on `System.Title` (contains), then batch-fetch details — returns `{ id, title, state }[]`. |
69
+ | `listProjects` | `GET` …`/_apis/projects` at the **collection** URL (see `AZURE_COLLECTION_URL` / derivation above). |
70
+
71
+ ## Verify with curl (optional)
72
+
73
+ Mac/Linux: use **single quotes** around the URL so `$expand` is not interpreted by the shell.
74
+
75
+ ```bash
76
+ export AZURE_PAT='your-pat'
77
+ curl -sS -u ":$AZURE_PAT" \
78
+ 'https://your-host/Collection/MyProject/_apis/wit/workitems/12345?$expand=all&api-version=7.1'
79
+ ```
80
+
81
+ ## Development
82
+
83
+ From a clone of this repository:
84
+
85
+ ```bash
86
+ npm install
87
+ npm run build
88
+ npm start
89
+ ```
90
+
91
+ Source lives in `src/` (TypeScript); `npm run build` emits to `dist/`. With `AZURE_BASE_URL` and `AZURE_PAT` set, `npm start` runs the MCP server on stdio (stop with Ctrl+C). Point your MCP client at this command or use `npx azdo-onprem-mcp` after publishing.
92
+
93
+ ## Publish to npm
94
+
95
+ From the repo root (with `npm login` if needed):
96
+
97
+ ```bash
98
+ npm run build
99
+ npm publish
100
+ ```
101
+
102
+ `prepack` runs `npm run build` automatically, so `dist/` is always fresh in the tarball. For a **scoped** package name (e.g. `@your-scope/azdo-onprem-mcp`), use `npm publish --access public` the first time.
103
+
104
+ ## License
105
+
106
+ MIT (see `package.json` → `"license"`).
@@ -0,0 +1,16 @@
1
+ import { type AxiosInstance } from "axios";
2
+ /**
3
+ * Team projects API is rooted at the collection, not the project.
4
+ * Set AZURE_COLLECTION_URL to override (e.g. https://host/CollectionName).
5
+ * Otherwise the last path segment of AZURE_BASE_URL is dropped when it looks
6
+ * like .../Collection/Project.
7
+ */
8
+ export declare function resolveCollectionBaseUrl(): string;
9
+ /** REST `api-version` query param for all DevOps calls (default 7.1). */
10
+ export declare function devOpsApiVersion(): string;
11
+ /**
12
+ * Extract a readable message from an Axios error response.
13
+ */
14
+ export declare function formatAxiosError(err: unknown): string;
15
+ export declare function createAzureDevOpsClient(): AxiosInstance;
16
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAc,EAAmB,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAsBnE;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAkBjD;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,IAAI,MAAM,CAGzC;AA+CD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAyCrD;AAWD,wBAAgB,uBAAuB,IAAI,aAAa,CA6DvD"}
package/dist/client.js ADDED
@@ -0,0 +1,177 @@
1
+ import axios from "axios";
2
+ function requireEnv(name) {
3
+ const v = process.env[name];
4
+ if (!v || String(v).trim() === "") {
5
+ throw new Error(`Missing required environment variable: ${name}`);
6
+ }
7
+ return String(v).trim();
8
+ }
9
+ /** PAT copy/paste often wraps quotes; trim and strip matching quotes only. */
10
+ function normalizePat(pat) {
11
+ let p = String(pat).trim();
12
+ if ((p.startsWith('"') && p.endsWith('"')) ||
13
+ (p.startsWith("'") && p.endsWith("'"))) {
14
+ p = p.slice(1, -1).trim();
15
+ }
16
+ return p;
17
+ }
18
+ /**
19
+ * Team projects API is rooted at the collection, not the project.
20
+ * Set AZURE_COLLECTION_URL to override (e.g. https://host/CollectionName).
21
+ * Otherwise the last path segment of AZURE_BASE_URL is dropped when it looks
22
+ * like .../Collection/Project.
23
+ */
24
+ export function resolveCollectionBaseUrl() {
25
+ const explicit = process.env.AZURE_COLLECTION_URL?.trim();
26
+ if (explicit) {
27
+ return explicit.replace(/\/+$/, "");
28
+ }
29
+ const projectBase = requireEnv("AZURE_BASE_URL").replace(/\/+$/, "");
30
+ let u;
31
+ try {
32
+ u = new URL(projectBase);
33
+ }
34
+ catch {
35
+ return projectBase;
36
+ }
37
+ const parts = u.pathname.replace(/\/+$/, "").split("/").filter(Boolean);
38
+ if (parts.length >= 2) {
39
+ parts.pop();
40
+ u.pathname = `/${parts.join("/")}`;
41
+ }
42
+ return u.toString().replace(/\/+$/, "");
43
+ }
44
+ /** REST `api-version` query param for all DevOps calls (default 7.1). */
45
+ export function devOpsApiVersion() {
46
+ const v = process.env.AZURE_DEVOPS_API_VERSION?.trim();
47
+ return v || "7.1";
48
+ }
49
+ /**
50
+ * Build a Basic auth header for Azure DevOps PAT (empty username).
51
+ */
52
+ function patAuthorizationHeader(pat) {
53
+ const token = Buffer.from(`:${pat}`, "utf8").toString("base64");
54
+ return `Basic ${token}`;
55
+ }
56
+ function responseDetail(data) {
57
+ if (data == null) {
58
+ return "";
59
+ }
60
+ if (typeof data === "string") {
61
+ return data.trim();
62
+ }
63
+ if (typeof data === "object" && data !== null) {
64
+ const o = data;
65
+ if (typeof o.message === "string") {
66
+ return o.message;
67
+ }
68
+ if (typeof o.errorMessage === "string") {
69
+ return o.errorMessage;
70
+ }
71
+ try {
72
+ const s = JSON.stringify(data);
73
+ return s === "{}" ? "" : s;
74
+ }
75
+ catch {
76
+ return "";
77
+ }
78
+ }
79
+ return "";
80
+ }
81
+ function errCodeSuffix(err) {
82
+ if (err &&
83
+ typeof err === "object" &&
84
+ "code" in err &&
85
+ typeof err.code === "string") {
86
+ return ` [${err.code}]`;
87
+ }
88
+ return "";
89
+ }
90
+ /**
91
+ * Extract a readable message from an Axios error response.
92
+ */
93
+ export function formatAxiosError(err) {
94
+ if (!axios.isAxiosError(err)) {
95
+ const msg = err instanceof Error ? err.message || String(err) : String(err);
96
+ const code = errCodeSuffix(err);
97
+ if ((err && typeof err === "object" && err.code === "ECONNABORTED") ||
98
+ /timeout/i.test(msg)) {
99
+ return `Request timed out${code}. Increase AZURE_HTTP_TIMEOUT_MS (default 120000) or check VPN/network.`;
100
+ }
101
+ return `${msg}${code}`;
102
+ }
103
+ if (!err.response) {
104
+ const code = err.code ? ` [${err.code}]` : "";
105
+ if (err.code === "ECONNABORTED" || /timeout/i.test(String(err.message))) {
106
+ return `Request timed out${code}. Increase AZURE_HTTP_TIMEOUT_MS (default 120000) or check VPN/network.`;
107
+ }
108
+ return `${err.message || String(err)}${code}`;
109
+ }
110
+ const { status, statusText, data } = err.response;
111
+ const base = `${status} ${statusText || ""}`.trim();
112
+ const wwwAuth = err.response.headers?.["www-authenticate"];
113
+ const detail = responseDetail(data);
114
+ if (status === 401) {
115
+ const hint = "PAT rejected or anonymous. Fix: use a valid PAT with Work Items (read), set AZURE_BASE_URL to the team project root (same as in the browser), ensure AZURE_PAT is exported in this shell / Cursor MCP env (no extra quotes). Test: curl -sS -u \":$AZURE_PAT\" \"$AZURE_BASE_URL/_apis/wit/workitems?ids=1&api-version=7.1\"";
116
+ const authHint = wwwAuth ? ` www-authenticate: ${wwwAuth}.` : "";
117
+ return detail
118
+ ? `${base}: ${detail}.${authHint} ${hint}`
119
+ : `${base}.${authHint} ${hint}`;
120
+ }
121
+ if (detail) {
122
+ return `${base}: ${detail}`;
123
+ }
124
+ return base;
125
+ }
126
+ function httpTimeoutMs() {
127
+ const raw = process.env.AZURE_HTTP_TIMEOUT_MS;
128
+ if (raw == null || String(raw).trim() === "") {
129
+ return 120_000;
130
+ }
131
+ const n = Number.parseInt(String(raw), 10);
132
+ return Number.isFinite(n) && n > 0 ? n : 120_000;
133
+ }
134
+ export function createAzureDevOpsClient() {
135
+ const baseURL = requireEnv("AZURE_BASE_URL").replace(/\/+$/, "");
136
+ const pat = normalizePat(requireEnv("AZURE_PAT"));
137
+ const timeout = httpTimeoutMs();
138
+ const client = axios.create({
139
+ baseURL,
140
+ timeout,
141
+ headers: {
142
+ Authorization: patAuthorizationHeader(pat),
143
+ Accept: "application/json",
144
+ "Content-Type": "application/json",
145
+ },
146
+ validateStatus: (s) => s >= 200 && s < 300,
147
+ responseType: "json",
148
+ });
149
+ if (process.env.MCP_DEBUG === "1") {
150
+ client.interceptors.request.use((config) => {
151
+ const uri = client.getUri(config);
152
+ const h = config.headers;
153
+ const auth = (typeof h.get === "function" ? h.get("Authorization") : null) ||
154
+ h.Authorization ||
155
+ h.common?.Authorization;
156
+ console.error(`[MCP_DEBUG] ${config.method?.toUpperCase()} ${uri}`);
157
+ console.error(`[MCP_DEBUG] Authorization header: ${auth ? "present" : "MISSING (would get TF400813 / anonymous)"}`);
158
+ console.error(`[MCP_DEBUG] timeout ${timeout}ms (set AZURE_HTTP_TIMEOUT_MS to change)`);
159
+ config.mcpRequestStart = Date.now();
160
+ return config;
161
+ });
162
+ client.interceptors.response.use((res) => {
163
+ const started = res.config?.mcpRequestStart;
164
+ const ms = started != null ? Date.now() - started : null;
165
+ console.error(`[MCP_DEBUG] ${res.status} in ${ms != null ? `${ms}ms` : "?ms"}`);
166
+ return res;
167
+ }, (error) => {
168
+ const started = error.config?.mcpRequestStart;
169
+ const ms = started != null ? Date.now() - started : null;
170
+ console.error(`[MCP_DEBUG] failed after ${ms != null ? `${ms}ms` : "?ms"}: ${error.message}`);
171
+ return Promise.reject(error);
172
+ });
173
+ }
174
+ client.interceptors.response.use((res) => res, (err) => Promise.reject(new Error(formatAxiosError(err))));
175
+ return client;
176
+ }
177
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { AxiosInstance } from "axios";
2
+ export interface TeamProjectSummary {
3
+ id: string;
4
+ name: string;
5
+ state: string;
6
+ visibility: string;
7
+ }
8
+ /**
9
+ * List team projects in the collection (GET Core /projects).
10
+ * Uses collection-scoped base URL; see resolveCollectionBaseUrl in client.ts.
11
+ */
12
+ export declare function listProjects(client: AxiosInstance): Promise<TeamProjectSummary[]>;
13
+ //# sourceMappingURL=projects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../src/projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAG3C,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAaD;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAY/B"}
@@ -0,0 +1,19 @@
1
+ import { devOpsApiVersion, resolveCollectionBaseUrl } from "./client.js";
2
+ /**
3
+ * List team projects in the collection (GET Core /projects).
4
+ * Uses collection-scoped base URL; see resolveCollectionBaseUrl in client.ts.
5
+ */
6
+ export async function listProjects(client) {
7
+ const collectionBase = resolveCollectionBaseUrl();
8
+ const url = `${collectionBase}/_apis/projects`;
9
+ const { data } = await client.get(url, {
10
+ params: { "api-version": devOpsApiVersion() },
11
+ });
12
+ return (data.value ?? []).map((p) => ({
13
+ id: String(p.id ?? ""),
14
+ name: String(p.name ?? ""),
15
+ state: String(p.state ?? ""),
16
+ visibility: String(p.visibility ?? ""),
17
+ }));
18
+ }
19
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../src/projects.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAoBzE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAqB;IAErB,MAAM,cAAc,GAAG,wBAAwB,EAAE,CAAC;IAClD,MAAM,GAAG,GAAG,GAAG,cAAc,iBAAiB,CAAC;IAC/C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,GAAG,CAAsB,GAAG,EAAE;QAC1D,MAAM,EAAE,EAAE,aAAa,EAAE,gBAAgB,EAAE,EAAE;KAC9C,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACpC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC1B,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC5B,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC;KACvC,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
package/dist/server.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { createAzureDevOpsClient } from "./client.js";
5
+ import { registerTools } from "./tools.js";
6
+ async function main() {
7
+ const client = createAzureDevOpsClient();
8
+ const server = new McpServer({
9
+ name: "azure-devops-server",
10
+ version: "1.0.0",
11
+ });
12
+ registerTools(server, client);
13
+ const transport = new StdioServerTransport();
14
+ await server.connect(transport);
15
+ }
16
+ main().catch((err) => {
17
+ const msg = err && typeof err === "object" && "message" in err
18
+ ? String(err.message)
19
+ : String(err);
20
+ console.error(msg);
21
+ process.exit(1);
22
+ });
23
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,uBAAuB,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE9B,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,GAAY,EAAE,EAAE;IAC5B,MAAM,GAAG,GACP,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,SAAS,IAAI,GAAG;QAChD,CAAC,CAAC,MAAM,CAAE,GAA4B,CAAC,OAAO,CAAC;QAC/C,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { AxiosInstance } from "axios";
3
+ export declare function registerTools(server: McpServer, client: AxiosInstance): void;
4
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAiD3C,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CA8B5E"}
package/dist/tools.js ADDED
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ import { listProjects } from "./projects.js";
3
+ import { getWorkItem, searchWorkItems } from "./workItems.js";
4
+ function jsonResult(payload) {
5
+ return {
6
+ content: [
7
+ {
8
+ type: "text",
9
+ text: JSON.stringify(payload, null, 2),
10
+ },
11
+ ],
12
+ };
13
+ }
14
+ /**
15
+ * Full work item for MCP: `structuredContent` is the complete REST object.
16
+ * Preamble reminds clients that data lives under `fields`, `relations`, etc. — not just id/title/state.
17
+ */
18
+ function workItemResult(item) {
19
+ const full = JSON.parse(JSON.stringify(item));
20
+ const fields = full.fields;
21
+ const fieldCount = fields && typeof fields === "object" && !Array.isArray(fields)
22
+ ? Object.keys(fields).length
23
+ : 0;
24
+ const relCount = Array.isArray(full.relations) ? full.relations.length : 0;
25
+ const topKeys = Object.keys(full).join(", ");
26
+ const preamble = "This is the full Azure DevOps GET work item response ($expand=all). " +
27
+ `Top-level keys: [${topKeys}]. ` +
28
+ `The "fields" object has ${fieldCount} entries (only non-empty fields are returned by the API). ` +
29
+ "Description/tags/HTML live under keys like System.Description, System.Tags. " +
30
+ `"relations" has ${relCount} link rows. ` +
31
+ "Use the JSON below in full — do not reduce to id/title/state/assignedTo only.\n\n";
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text",
36
+ text: preamble + JSON.stringify(full, null, 2),
37
+ },
38
+ ],
39
+ structuredContent: full,
40
+ };
41
+ }
42
+ export function registerTools(server, client) {
43
+ server.tool("listProjects", "List team projects in the Azure DevOps collection. Uses AZURE_COLLECTION_URL if set, otherwise derives collection URL from AZURE_BASE_URL.", {}, async () => {
44
+ const projects = await listProjects(client);
45
+ return jsonResult(projects);
46
+ });
47
+ server.tool("getWorkItem", "Returns the complete Azure DevOps work item JSON from GET /_apis/wit/workitems/{id}?$expand=all. Includes the full `fields` map (System.Title, System.Description, System.Tags, custom fields, etc. — only fields with values are present), `relations`, `_links`, `rev`. This is NOT limited to id/title/state/assignedTo; read the `fields` object for description, tags, and links.", { id: z.number().int().positive() }, async ({ id }) => {
48
+ const item = await getWorkItem(client, id);
49
+ return workItemResult(item);
50
+ });
51
+ server.tool("searchWorkItems", 'Search work items whose title contains the given text (WIQL CONTAINS on System.Title). Returns { id, title, state }[].', { query: z.string() }, async ({ query }) => {
52
+ const items = await searchWorkItems(client, query);
53
+ return jsonResult(items);
54
+ });
55
+ }
56
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAE9D,SAAS,UAAU,CAAC,OAAgB;IAClC,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;aACvC;SACF;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,IAAmB;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAkB,CAAC;IAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,MAAM,UAAU,GACd,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAC5D,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAiC,CAAC,CAAC,MAAM;QACvD,CAAC,CAAC,CAAC,CAAC;IACR,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,QAAQ,GACZ,sEAAsE;QACtE,oBAAoB,OAAO,KAAK;QAChC,2BAA2B,UAAU,4DAA4D;QACjG,8EAA8E;QAC9E,mBAAmB,QAAQ,cAAc;QACzC,mFAAmF,CAAC;IAEtF,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;aAC/C;SACF;QACD,iBAAiB,EAAE,IAAI;KACxB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAiB,EAAE,MAAqB;IACpE,MAAM,CAAC,IAAI,CACT,cAAc,EACd,4IAA4I,EAC5I,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;QAC5C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,aAAa,EACb,wXAAwX,EACxX,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,EACnC,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;QACf,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC3C,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,wHAAwH,EACxH,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,EACrB,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QAClB,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACnD,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { AxiosInstance } from "axios";
2
+ /** Azure DevOps work item REST body from GET .../workitems/{id}?$expand=all */
3
+ export type AzureWorkItem = Record<string, unknown>;
4
+ /** Escape single quotes for WIQL string literals */
5
+ export declare function escapeWiqlString(s: string): string;
6
+ /**
7
+ * Full work item JSON from GET .../workitems/{id}?$expand=all (id, rev, fields, relations, _links, ...).
8
+ */
9
+ export declare function getWorkItem(client: AxiosInstance, id: number): Promise<AzureWorkItem>;
10
+ export interface WorkItemSearchRow {
11
+ id: number;
12
+ title: unknown;
13
+ state: unknown;
14
+ }
15
+ export declare function searchWorkItems(client: AxiosInstance, query: string): Promise<WorkItemSearchRow[]>;
16
+ //# sourceMappingURL=workItems.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workItems.d.ts","sourceRoot":"","sources":["../src/workItems.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAG3C,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,oDAAoD;AACpD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAElD;AAOD;;GAEG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,aAAa,EACrB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,aAAa,CAAC,CAUxB;AAoBD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB;AAgCD,wBAAsB,eAAe,CACnC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAgC9B"}
@@ -0,0 +1,72 @@
1
+ import { devOpsApiVersion } from "./client.js";
2
+ /** Escape single quotes for WIQL string literals */
3
+ export function escapeWiqlString(s) {
4
+ return s.replace(/'/g, "''");
5
+ }
6
+ /** Team project root, e.g. https://host/Collection/Project — must match AZURE_BASE_URL. */
7
+ function projectBaseUrl(client) {
8
+ return String(client.defaults.baseURL ?? "").replace(/\/+$/, "");
9
+ }
10
+ /**
11
+ * Full work item JSON from GET .../workitems/{id}?$expand=all (id, rev, fields, relations, _links, ...).
12
+ */
13
+ export async function getWorkItem(client, id) {
14
+ const base = projectBaseUrl(client);
15
+ const url = `${base}/_apis/wit/workitems/${id}`;
16
+ const { data } = await client.get(url, {
17
+ params: {
18
+ $expand: "all",
19
+ "api-version": devOpsApiVersion(),
20
+ },
21
+ });
22
+ return data;
23
+ }
24
+ async function fetchWorkItemsBatch(client, ids) {
25
+ const map = new Map();
26
+ if (ids.length === 0) {
27
+ return map;
28
+ }
29
+ const base = projectBaseUrl(client);
30
+ const url = `${base}/_apis/wit/workitems`;
31
+ const { data } = await client.get(url, {
32
+ params: {
33
+ ids: ids.join(","),
34
+ fields: "System.Title,System.State",
35
+ "api-version": devOpsApiVersion(),
36
+ },
37
+ });
38
+ const items = data.value ?? [];
39
+ for (const item of items) {
40
+ const f = item.fields ?? {};
41
+ map.set(item.id, {
42
+ title: f["System.Title"],
43
+ state: f["System.State"],
44
+ });
45
+ }
46
+ return map;
47
+ }
48
+ export async function searchWorkItems(client, query) {
49
+ const trimmed = query.trim();
50
+ if (trimmed === "") {
51
+ return [];
52
+ }
53
+ const safe = escapeWiqlString(trimmed);
54
+ const wiql = `SELECT [System.Id], [System.Title], [System.State] FROM WorkItems WHERE [System.Title] CONTAINS '${safe}'`;
55
+ const base = projectBaseUrl(client);
56
+ const url = `${base}/_apis/wit/wiql`;
57
+ const { data } = await client.post(url, { query: wiql }, { params: { "api-version": devOpsApiVersion() } });
58
+ const rawList = data.workItems ?? data.queryResult?.workItems ?? [];
59
+ const ids = rawList
60
+ .map((w) => w.id)
61
+ .filter((id) => typeof id === "number");
62
+ const details = await fetchWorkItemsBatch(client, ids);
63
+ const result = [];
64
+ for (const id of ids) {
65
+ const row = details.get(id);
66
+ if (row) {
67
+ result.push({ id, title: row.title, state: row.state });
68
+ }
69
+ }
70
+ return result;
71
+ }
72
+ //# sourceMappingURL=workItems.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workItems.js","sourceRoot":"","sources":["../src/workItems.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "azdo-onprem-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for self-hosted Azure DevOps Server",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ajithnow/azdo-onprem-mcp.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/ajithnow/azdo-onprem-mcp/issues"
12
+ },
13
+ "homepage": "https://github.com/ajithnow/azdo-onprem-mcp#readme",
14
+ "keywords": [
15
+ "mcp",
16
+ "model-context-protocol",
17
+ "azure-devops",
18
+ "azure-devops-server",
19
+ "work-items"
20
+ ],
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ ".env.example"
25
+ ],
26
+ "type": "module",
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "bin": {
31
+ "azdo-onprem-mcp": "dist/server.js"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "start": "node dist/server.js",
36
+ "prepack": "npm run build"
37
+ },
38
+ "dependencies": {
39
+ "@modelcontextprotocol/sdk": "^1.28.0",
40
+ "axios": "^1.7.9",
41
+ "zod": "^3.25.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.10.0",
45
+ "typescript": "^5.7.3"
46
+ }
47
+ }