@azure-devops/mcp 2.9.0-nightly.20260818 → 2.9.0-nightly.20260819
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/dist/tools/pipelines.dto.js +11 -3
- package/dist/tools/pipelines.js +113 -104
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/shared/command.js +0 -34
|
@@ -56,7 +56,7 @@ export const runPipelineShape = {
|
|
|
56
56
|
/** create_pipeline inputs. */
|
|
57
57
|
export const createPipelineShape = {
|
|
58
58
|
...projectShape,
|
|
59
|
-
name: z.string().optional().describe("
|
|
59
|
+
name: z.string().optional().describe("Pipeline name. Required for: create_pipeline, rename_pipeline."),
|
|
60
60
|
folder: z.string().optional().describe("Folder path for the new pipeline. Used for: create_pipeline."),
|
|
61
61
|
yamlPath: z.string().optional().describe("Path to the YAML file in the repository. Required for: create_pipeline."),
|
|
62
62
|
repositoryType: z
|
|
@@ -67,6 +67,12 @@ export const createPipelineShape = {
|
|
|
67
67
|
repositoryId: z.string().optional().describe("ID of the repository. Used for: create_pipeline."),
|
|
68
68
|
repositoryConnectionId: z.string().optional().describe("Service connection ID for GitHub repositories. Used for: create_pipeline."),
|
|
69
69
|
};
|
|
70
|
+
/** rename_pipeline inputs. */
|
|
71
|
+
export const renamePipelineShape = {
|
|
72
|
+
...projectShape,
|
|
73
|
+
pipelineId: z.coerce.number().min(1).optional().describe("ID of the pipeline. Required for: run_pipeline, rename_pipeline."),
|
|
74
|
+
name: z.string().optional().describe("Pipeline name. Required for: create_pipeline, rename_pipeline."),
|
|
75
|
+
};
|
|
70
76
|
/** update_build_stage inputs. */
|
|
71
77
|
export const updateBuildStageShape = {
|
|
72
78
|
...projectShape,
|
|
@@ -81,15 +87,17 @@ export const updateBuildStageShape = {
|
|
|
81
87
|
/** The composed input shape for the grouped `pipelines_write` tool. */
|
|
82
88
|
export const pipelinesWriteShape = {
|
|
83
89
|
action: z
|
|
84
|
-
.enum(["run_pipeline", "create_pipeline", "update_build_stage"])
|
|
85
|
-
.describe("The action to perform. Options: run_pipeline (queue a new pipeline run), create_pipeline (create a new YAML pipeline definition), update_build_stage (cancel, retry, or run a stage on an in-flight build)."),
|
|
90
|
+
.enum(["run_pipeline", "create_pipeline", "rename_pipeline", "update_build_stage"])
|
|
91
|
+
.describe("The action to perform. Options: run_pipeline (queue a new pipeline run), create_pipeline (create a new YAML pipeline definition), rename_pipeline (rename an existing pipeline definition), update_build_stage (cancel, retry, or run a stage on an in-flight build)."),
|
|
86
92
|
...runPipelineShape,
|
|
87
93
|
...createPipelineShape,
|
|
94
|
+
...renamePipelineShape,
|
|
88
95
|
...updateBuildStageShape,
|
|
89
96
|
};
|
|
90
97
|
// Per-action schemas + inferred argument DTOs. `z.infer` keeps these types in
|
|
91
98
|
// lockstep with the schemas above.
|
|
92
99
|
export const runPipelineSchema = z.object(runPipelineShape);
|
|
93
100
|
export const createPipelineSchema = z.object(createPipelineShape);
|
|
101
|
+
export const renamePipelineSchema = z.object(renamePipelineShape);
|
|
94
102
|
export const updateBuildStageSchema = z.object(updateBuildStageShape);
|
|
95
103
|
export const pipelinesWriteSchema = z.object(pipelinesWriteShape);
|
package/dist/tools/pipelines.js
CHANGED
|
@@ -8,111 +8,102 @@ import { ConfigurationType, RepositoryType } from "azure-devops-node-api/interfa
|
|
|
8
8
|
import { mkdirSync, createWriteStream } from "fs";
|
|
9
9
|
import { createExternalContentResponse } from "../shared/content-safety.js";
|
|
10
10
|
import { join, posix, resolve, win32 } from "path";
|
|
11
|
-
import { dispatchAction, errorResult } from "../shared/command.js";
|
|
12
11
|
import { pipelinesWriteShape } from "./pipelines.dto.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
* The registry is the lookup table that couples each action to its command.
|
|
105
|
-
* Adding a new write action means registering one entry here — the dispatcher
|
|
106
|
-
* (`dispatchAction`) never changes.
|
|
107
|
-
*/
|
|
108
|
-
const pipelinesWriteCommands = {
|
|
109
|
-
run_pipeline: runPipelineCommand,
|
|
110
|
-
create_pipeline: createPipelineCommand,
|
|
111
|
-
update_build_stage: updateBuildStageCommand,
|
|
112
|
-
};
|
|
12
|
+
const errorResult = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
13
|
+
async function runPipeline(args, connectionProvider) {
|
|
14
|
+
if (!args.pipelineId)
|
|
15
|
+
return errorResult("pipelineId is required for run_pipeline");
|
|
16
|
+
if (!args.previewRun && args.yamlOverride)
|
|
17
|
+
throw new Error("Parameter 'yamlOverride' can only be specified together with parameter 'previewRun'.");
|
|
18
|
+
const connection = await connectionProvider();
|
|
19
|
+
const pipelinesApi = await connection.getPipelinesApi();
|
|
20
|
+
const runRequest = {
|
|
21
|
+
previewRun: args.previewRun,
|
|
22
|
+
resources: { ...args.resources },
|
|
23
|
+
stagesToSkip: args.stagesToSkip,
|
|
24
|
+
templateParameters: args.templateParameters,
|
|
25
|
+
variables: args.variables,
|
|
26
|
+
yamlOverride: args.yamlOverride,
|
|
27
|
+
};
|
|
28
|
+
const pipelineRun = await pipelinesApi.runPipeline(runRequest, args.project, args.pipelineId, args.pipelineVersion);
|
|
29
|
+
if (pipelineRun.id === undefined)
|
|
30
|
+
throw new Error("Failed to get build ID from pipeline run");
|
|
31
|
+
return { content: [{ type: "text", text: JSON.stringify(pipelineRun, null, 2) }] };
|
|
32
|
+
}
|
|
33
|
+
async function createPipeline(args, connectionProvider) {
|
|
34
|
+
if (!args.name)
|
|
35
|
+
return errorResult("name is required for create_pipeline");
|
|
36
|
+
if (!args.yamlPath)
|
|
37
|
+
return errorResult("yamlPath is required for create_pipeline");
|
|
38
|
+
if (!args.repositoryType)
|
|
39
|
+
return errorResult("repositoryType is required for create_pipeline");
|
|
40
|
+
if (!args.repositoryName)
|
|
41
|
+
return errorResult("repositoryName is required for create_pipeline");
|
|
42
|
+
const connection = await connectionProvider();
|
|
43
|
+
const pipelinesApi = await connection.getPipelinesApi();
|
|
44
|
+
const repositoryTypeEnumValue = safeEnumConvert(RepositoryType, args.repositoryType);
|
|
45
|
+
const repositoryPayload = { type: args.repositoryType };
|
|
46
|
+
if (repositoryTypeEnumValue === RepositoryType.AzureReposGit) {
|
|
47
|
+
repositoryPayload.id = args.repositoryId;
|
|
48
|
+
repositoryPayload.name = args.repositoryName;
|
|
49
|
+
}
|
|
50
|
+
else if (repositoryTypeEnumValue === RepositoryType.GitHub) {
|
|
51
|
+
if (!args.repositoryConnectionId)
|
|
52
|
+
throw new Error("Parameter 'repositoryConnectionId' is required for GitHub repositories.");
|
|
53
|
+
repositoryPayload.connection = { id: args.repositoryConnectionId };
|
|
54
|
+
repositoryPayload.fullname = args.repositoryName;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
throw new Error("Unsupported repository type");
|
|
58
|
+
}
|
|
59
|
+
const yamlConfigurationType = getEnumKeys(ConfigurationType).find((k) => ConfigurationType[k] === ConfigurationType.Yaml);
|
|
60
|
+
const createParams = {
|
|
61
|
+
name: args.name,
|
|
62
|
+
folder: args.folder || "\\",
|
|
63
|
+
configuration: { type: yamlConfigurationType, path: args.yamlPath, repository: repositoryPayload, variables: undefined },
|
|
64
|
+
};
|
|
65
|
+
const newPipeline = await pipelinesApi.createPipeline(createParams, args.project);
|
|
66
|
+
return { content: [{ type: "text", text: JSON.stringify(newPipeline, null, 2) }] };
|
|
67
|
+
}
|
|
68
|
+
async function renamePipeline(args, connectionProvider) {
|
|
69
|
+
if (!args.pipelineId)
|
|
70
|
+
return errorResult("pipelineId is required for rename_pipeline");
|
|
71
|
+
if (!args.name)
|
|
72
|
+
return errorResult("name is required for rename_pipeline");
|
|
73
|
+
const connection = await connectionProvider();
|
|
74
|
+
const buildApi = await connection.getBuildApi();
|
|
75
|
+
const definition = await buildApi.getDefinition(args.project, args.pipelineId);
|
|
76
|
+
const updatedDefinition = await buildApi.updateDefinition({ ...definition, name: args.name }, args.project, args.pipelineId);
|
|
77
|
+
return { content: [{ type: "text", text: JSON.stringify(updatedDefinition, null, 2) }] };
|
|
78
|
+
}
|
|
79
|
+
async function updateBuildStage(args, connectionProvider, tokenProvider, userAgentProvider) {
|
|
80
|
+
if (!args.buildId)
|
|
81
|
+
return errorResult("buildId is required for update_build_stage");
|
|
82
|
+
if (!args.stageName)
|
|
83
|
+
return errorResult("stageName is required for update_build_stage");
|
|
84
|
+
if (!args.status)
|
|
85
|
+
return errorResult("status is required for update_build_stage");
|
|
86
|
+
const connection = await connectionProvider();
|
|
87
|
+
const orgUrl = connection.serverUrl;
|
|
88
|
+
const endpoint = `${orgUrl}/${encodeURIComponent(args.project)}/_apis/build/builds/${args.buildId}/stages/${encodeURIComponent(args.stageName)}?api-version=${apiVersion}`;
|
|
89
|
+
const token = await tokenProvider();
|
|
90
|
+
const body = { forceRetryAllJobs: args.forceRetryAllJobs, state: safeEnumConvert(StageUpdateType, args.status) };
|
|
91
|
+
const response = await fetch(endpoint, {
|
|
92
|
+
method: "PATCH",
|
|
93
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}`, "User-Agent": userAgentProvider() },
|
|
94
|
+
body: JSON.stringify(body),
|
|
95
|
+
});
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const errorText = await response.text();
|
|
98
|
+
throw new Error(`Failed to update build stage: ${response.status} ${errorText}`);
|
|
99
|
+
}
|
|
100
|
+
const updatedBuild = await response.text();
|
|
101
|
+
return { content: [{ type: "text", text: JSON.stringify(updatedBuild, null, 2) }] };
|
|
102
|
+
}
|
|
113
103
|
const pipelinesWriteErrorPrefixes = {
|
|
114
104
|
run_pipeline: "Error running pipeline: ",
|
|
115
105
|
create_pipeline: "Error creating pipeline: ",
|
|
106
|
+
rename_pipeline: "Error renaming pipeline: ",
|
|
116
107
|
update_build_stage: "Error updating build stage: ",
|
|
117
108
|
};
|
|
118
109
|
const PIPELINE_TOOLS = {
|
|
@@ -385,8 +376,26 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
|
|
|
385
376
|
});
|
|
386
377
|
// ─── pipelines_write ────────────────────────────────────────────────────────
|
|
387
378
|
server.tool(PIPELINE_TOOLS.pipelines_write, "Write operations for pipelines and builds. Use the action parameter to specify the operation.", pipelinesWriteShape, async (args) => {
|
|
388
|
-
|
|
389
|
-
|
|
379
|
+
try {
|
|
380
|
+
switch (args.action) {
|
|
381
|
+
case "run_pipeline":
|
|
382
|
+
return await runPipeline(args, connectionProvider);
|
|
383
|
+
case "create_pipeline":
|
|
384
|
+
return await createPipeline(args, connectionProvider);
|
|
385
|
+
case "rename_pipeline":
|
|
386
|
+
return await renamePipeline(args, connectionProvider);
|
|
387
|
+
case "update_build_stage":
|
|
388
|
+
return await updateBuildStage(args, connectionProvider, tokenProvider, userAgentProvider);
|
|
389
|
+
default: {
|
|
390
|
+
const unsupportedAction = args.action;
|
|
391
|
+
return errorResult(`Unknown action: ${unsupportedAction}. Supported actions: ${Object.keys(pipelinesWriteErrorPrefixes).sort().join(", ")}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
397
|
+
return errorResult(`${pipelinesWriteErrorPrefixes[args.action]}${message}`);
|
|
398
|
+
}
|
|
390
399
|
});
|
|
391
400
|
}
|
|
392
|
-
export { PIPELINE_TOOLS, configurePipelineTools,
|
|
401
|
+
export { PIPELINE_TOOLS, configurePipelineTools, runPipeline, createPipeline, renamePipeline, updateBuildStage };
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = "2.9.0-nightly.
|
|
1
|
+
export const packageVersion = "2.9.0-nightly.20260819";
|
package/package.json
CHANGED
package/dist/shared/command.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Microsoft Corporation.
|
|
2
|
-
// Licensed under the MIT License.
|
|
3
|
-
/** Builds an error `CallToolResult`. */
|
|
4
|
-
export const errorResult = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
5
|
-
/**
|
|
6
|
-
* Routes a validated, action-carrying args object to the matching command.
|
|
7
|
-
*
|
|
8
|
-
* This is what removes long positional parameter lists from grouped ("action")
|
|
9
|
-
* tools: instead of destructuring every possible field, the whole typed args
|
|
10
|
-
* object is forwarded to the single command keyed by `args.action`, coupling
|
|
11
|
-
* each action to exactly one command.
|
|
12
|
-
*
|
|
13
|
-
* - Unknown actions short-circuit with an "Unknown action" error and never
|
|
14
|
-
* touch the context (so no connection is opened).
|
|
15
|
-
* - Errors thrown by a command are caught and formatted using the optional
|
|
16
|
-
* per-action `errorPrefixes` map (falling back to a generic message).
|
|
17
|
-
* - Errors returned by a command (e.g. validation `errorResult`s) pass through
|
|
18
|
-
* unchanged.
|
|
19
|
-
*/
|
|
20
|
-
export async function dispatchAction(commands, context, args, errorPrefixes) {
|
|
21
|
-
const command = commands[args.action];
|
|
22
|
-
if (!command) {
|
|
23
|
-
const supportedActions = Object.keys(commands).sort().join(", ");
|
|
24
|
-
return errorResult(`Unknown action: ${args.action}. Supported actions: ${supportedActions}`);
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
return await command.execute(context, args);
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
31
|
-
const prefix = errorPrefixes?.[args.action];
|
|
32
|
-
return errorResult(prefix ? `${prefix}${message}` : `Error: ${message}`);
|
|
33
|
-
}
|
|
34
|
-
}
|