ado_upload_attachment
Upload files as attachments to Azure DevOps work items and retrieve attachment URLs to link documents directly to tasks, bugs, or features.
Instructions
Sube un archivo como adjunto a Azure DevOps y devuelve la URL del adjunto
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filePath | Yes | Ruta completa del archivo a subir | |
| fileName | No | Nombre del archivo (opcional, se usa el nombre del archivo si no se especifica) |
Implementation Reference
- src/index.ts:788-808 (handler)Handler for ado_upload_attachment tool.
async ({ filePath, fileName }) => { if (!currentPat || !currentOrg) { throw new Error("No hay conexión configurada. Usa ado_configure primero."); } if (!fs.existsSync(filePath)) { throw new Error(`El archivo no existe: ${filePath}`); } const name = fileName || path.basename(filePath); const attachment = await uploadAttachmentRest(filePath, name); return { content: [ { type: "text", text: `Archivo subido exitosamente:\n- Nombre: ${name}\n- URL: ${attachment.url}\n- ID: ${attachment.id}\n\nUsa esta URL con ado_add_attachment para vincular el adjunto a un Work Item.`, }, ], }; } - src/index.ts:781-809 (registration)Tool registration for ado_upload_attachment.
server.tool( "ado_upload_attachment", "Sube un archivo como adjunto a Azure DevOps y devuelve la URL del adjunto", { filePath: z.string().describe("Ruta completa del archivo a subir"), fileName: z.string().optional().describe("Nombre del archivo (opcional, se usa el nombre del archivo si no se especifica)"), }, async ({ filePath, fileName }) => { if (!currentPat || !currentOrg) { throw new Error("No hay conexión configurada. Usa ado_configure primero."); } if (!fs.existsSync(filePath)) { throw new Error(`El archivo no existe: ${filePath}`); } const name = fileName || path.basename(filePath); const attachment = await uploadAttachmentRest(filePath, name); return { content: [ { type: "text", text: `Archivo subido exitosamente:\n- Nombre: ${name}\n- URL: ${attachment.url}\n- ID: ${attachment.id}\n\nUsa esta URL con ado_add_attachment para vincular el adjunto a un Work Item.`, }, ], }; } ); - src/index.ts:27-66 (helper)Helper function that performs the actual REST API call to upload the attachment.
async function uploadAttachmentRest( filePath: string, fileName: string ): Promise<{ url: string; id: string }> { const fileContent = fs.readFileSync(filePath); // Construir URL del API - asegurar que no haya doble slash const baseUrl = currentOrg.endsWith("/") ? currentOrg.slice(0, -1) : currentOrg; const encodedProject = encodeURIComponent(currentProject); const encodedFileName = encodeURIComponent(fileName); const fullUrl = `${baseUrl}/${encodedProject}/_apis/wit/attachments?fileName=${encodedFileName}&api-version=7.0`; const urlObj = new URL(fullUrl); const options: https.RequestOptions = { hostname: urlObj.hostname, path: urlObj.pathname + urlObj.search, method: "POST", headers: { "Content-Type": "application/octet-stream", "Content-Length": fileContent.length, "Authorization": `Basic ${Buffer.from(`:${currentPat}`).toString("base64")}`, }, }; return new Promise((resolve, reject) => { const req = https.request(options, (res) => { let data = ""; res.on("data", (chunk) => (data += chunk)); res.on("end", () => { if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { try { const result = JSON.parse(data); resolve({ url: result.url, id: result.id }); } catch (e) { reject(new Error(`Error parsing response: ${data}`)); } } else { reject(new Error(`Error uploading attachment: ${res.statusCode} - ${data}`)); }