@azure-devops/mcp 2.10.0-nightly.20260927 → 2.10.0-nightly.20260928
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.js +47 -10
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/tools/pipelines.js
CHANGED
|
@@ -8,8 +8,23 @@ 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 { Readable, Transform } from "stream";
|
|
12
|
+
import { pipeline } from "stream/promises";
|
|
11
13
|
import { pipelinesWriteShape } from "./pipelines.dto.js";
|
|
12
14
|
const errorResult = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
15
|
+
const MAX_ARTIFACT_FILE_BYTES = 1024 * 1024 * 1024;
|
|
16
|
+
const MAX_ARTIFACT_BASE64_BYTES = 10 * 1024 * 1024;
|
|
17
|
+
function getArtifactResponseStream(response) {
|
|
18
|
+
if (!response.body)
|
|
19
|
+
throw new Error("Artifact download returned an empty response body.");
|
|
20
|
+
return Readable.fromWeb(response.body);
|
|
21
|
+
}
|
|
22
|
+
function validateArtifactContentLength(response, maxBytes, limitDescription) {
|
|
23
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
24
|
+
if (Number.isFinite(contentLength) && contentLength > maxBytes) {
|
|
25
|
+
throw new Error(`Artifact download exceeds the ${limitDescription} limit.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
13
28
|
async function runPipeline(args, connectionProvider) {
|
|
14
29
|
if (!args.pipelineId)
|
|
15
30
|
return errorResult("pipelineId is required for run_pipeline");
|
|
@@ -338,25 +353,47 @@ function configurePipelineTools(server, tokenProvider, connectionProvider, userA
|
|
|
338
353
|
if (!artifact) {
|
|
339
354
|
return { content: [{ type: "text", text: `Artifact ${resolvedArtifactName} not found in build ${buildId}.` }], isError: true };
|
|
340
355
|
}
|
|
341
|
-
|
|
356
|
+
if (!artifact.resource?.downloadUrl) {
|
|
357
|
+
throw new Error(`Artifact ${resolvedArtifactName} does not have a download URL.`);
|
|
358
|
+
}
|
|
359
|
+
const accessToken = await tokenProvider();
|
|
360
|
+
const response = await fetch(artifact.resource.downloadUrl, {
|
|
361
|
+
headers: { "Authorization": `Bearer ${accessToken}`, "User-Agent": userAgentProvider() },
|
|
362
|
+
});
|
|
363
|
+
if (!response.ok) {
|
|
364
|
+
throw new Error(`Artifact download failed: ${response.status} ${response.statusText}`);
|
|
365
|
+
}
|
|
342
366
|
if (destinationPath) {
|
|
367
|
+
validateArtifactContentLength(response, MAX_ARTIFACT_FILE_BYTES, "1 GB file size");
|
|
343
368
|
const fullDestinationPath = resolve(destinationPath);
|
|
344
369
|
mkdirSync(fullDestinationPath, { recursive: true });
|
|
345
370
|
const fileDestinationPath = join(fullDestinationPath, `${resolvedArtifactName}.zip`);
|
|
346
371
|
const writeStream = createWriteStream(fileDestinationPath);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
372
|
+
let downloadedBytes = 0;
|
|
373
|
+
const sizeLimitStream = new Transform({
|
|
374
|
+
transform(chunk, _encoding, callback) {
|
|
375
|
+
downloadedBytes += chunk.length;
|
|
376
|
+
if (downloadedBytes > MAX_ARTIFACT_FILE_BYTES) {
|
|
377
|
+
callback(new Error("Artifact download exceeds the 1 GB file size limit."));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
callback(null, chunk);
|
|
381
|
+
},
|
|
351
382
|
});
|
|
383
|
+
await pipeline(getArtifactResponseStream(response), sizeLimitStream, writeStream);
|
|
352
384
|
return { content: [{ type: "text", text: `Artifact ${resolvedArtifactName} downloaded to ${destinationPath}.` }] };
|
|
353
385
|
}
|
|
386
|
+
validateArtifactContentLength(response, MAX_ARTIFACT_BASE64_BYTES, "10 MB base64 response size");
|
|
354
387
|
const chunks = [];
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
388
|
+
let downloadedBytes = 0;
|
|
389
|
+
for await (const chunk of getArtifactResponseStream(response)) {
|
|
390
|
+
const bufferChunk = Buffer.from(chunk);
|
|
391
|
+
downloadedBytes += bufferChunk.length;
|
|
392
|
+
if (downloadedBytes > MAX_ARTIFACT_BASE64_BYTES) {
|
|
393
|
+
throw new Error("Artifact download exceeds the 10 MB base64 response size limit.");
|
|
394
|
+
}
|
|
395
|
+
chunks.push(bufferChunk);
|
|
396
|
+
}
|
|
360
397
|
const buffer = Buffer.concat(chunks);
|
|
361
398
|
const base64Data = buffer.toString("base64");
|
|
362
399
|
return {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = "2.10.0-nightly.
|
|
1
|
+
export const packageVersion = "2.10.0-nightly.20260928";
|
package/package.json
CHANGED