Skip to main content
Glama
arslanmusta

azure-devops-attachment-mcp-server

by arslanmusta

azure-devops-attachment-mcp-server

CI npm

An MCP server (stdio) that lets AI assistants manage work item attachments on on-premise Azure DevOps Server (2019 / 2020 / 2022, formerly TFS) through the REST API, authenticated with a Personal Access Token.

It exposes four tools:

Tool

What it does

list_attachments

Lists the attachments of a work item (name, size, comment, date, id, URL).

add_attachment

Uploads a local file and attaches it to a work item.

delete_attachment

Removes an attachment from a work item by id or unique file name.

download_attachment

Saves an attachment to a local directory and returns the path.

No extra dependencies beyond the MCP SDK and zod: it uses Node's built-in fetch, so npx starts fast.

Requirements

  • Node.js 20 or newer.

  • A Personal Access Token (PAT) for the collection with the Work Items (Read & Write) scope.

  • Network access from the machine running the MCP server to the Azure DevOps Server.

Related MCP server: Azure DevOps MCP Server

Quick start

Add the server to your MCP client configuration. The generic JSON form (Claude Desktop, Cursor, Windsurf, VS Code mcp.json and most other clients accept it):

{
  "mcpServers": {
    "azure-devops-attachments": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "azure-devops-attachment-mcp-server"],
      "env": {
        "AZURE_DEVOPS_URL": "https://tfs.company.local/tfs/DefaultCollection",
        "AZURE_DEVOPS_PAT": "<your personal access token>",
        "AZURE_DEVOPS_PROJECT": "MyProject"
      }
    }
  }
}

Claude Code:

claude mcp add azure-devops-attachments \
  -e AZURE_DEVOPS_URL=https://tfs.company.local/tfs/DefaultCollection \
  -e AZURE_DEVOPS_PAT=<your personal access token> \
  -e AZURE_DEVOPS_PROJECT=MyProject \
  -- npx -y azure-devops-attachment-mcp-server

Then ask your assistant things like:

  • "List the attachments on work item 4711."

  • "Attach ./reports/perf.xlsx to bug 4711 with the comment 'perf run 2026-09'."

  • "Download the attachment spec.pdf from work item 4711 into ~/Downloads."

  • "Remove the attachment old-log.txt from work item 4711."

Configuration

All configuration is passed through environment variables.

Variable

Required

Default

Description

AZURE_DEVOPS_URL

yes

Collection URL, e.g. https://tfs.company.local/tfs/DefaultCollection. Include the /tfs/<Collection> path used by your server.

AZURE_DEVOPS_PAT

yes

Personal Access Token. Sent only as a Basic auth header to AZURE_DEVOPS_URL; never logged.

AZURE_DEVOPS_PROJECT

no

Default team project for the attachment endpoints. The work item's own project takes precedence when known.

AZURE_DEVOPS_API_VERSION

no

7.0

REST api-version. 7.0 = Azure DevOps Server 2022, 6.0 = 2020, 5.0 = 2019.

AZURE_DEVOPS_DOWNLOAD_DIR

no

process cwd

Directory where download_attachment saves files when no outputDir is given.

AZURE_DEVOPS_ALLOW_INSECURE_TLS

no

false

Set to true to accept self-signed certificates. Insecure; prefer NODE_EXTRA_CA_CERTS=/path/to/ca.pem.

The server exits with code 1 and a message naming the missing variable when the configuration is incomplete.

Tools

Every tool returns a short human-readable summary plus structuredContent matching the documented shape. Errors come back as tool errors (isError: true) with an actionable message, for example which environment variable to check after an HTTP 401.

list_attachments

Input

Type

Notes

workItemId

integer

Required.

Returns { workItemId, workItemRev, count, attachments[] } where each attachment has { id, name, size, comment, createdDate, url, relationIndex }. size is null when the server does not report it (older servers or migrated data).

add_attachment

Input

Type

Notes

workItemId

integer

Required.

filePath

string

Required. Absolute or cwd-relative path of a local file (max 130 MB).

fileName

string

Optional. Name stored on the server; defaults to the file's base name.

comment

string

Optional. Comment shown next to the attachment.

project

string

Optional. Team project for the upload; defaults to the work item's project, then AZURE_DEVOPS_PROJECT.

Returns { workItemId, workItemRev, attachment: { id, name, size, url, comment } }.

delete_attachment

Input

Type

Notes

workItemId

integer

Required.

attachmentId

GUID

One of attachmentId / name is required.

name

string

Exact file name; rejected with the candidate ids when several attachments share it.

Returns { workItemId, workItemRev, removed: { id, name, relationIndex }, note }. Only the link between the work item and the file is removed; Azure DevOps has no REST endpoint that deletes the stored file itself (this is also what the web UI does).

download_attachment

Input

Type

Notes

workItemId

integer

Required.

attachmentId

GUID

One of attachmentId / name is required.

name

string

Exact file name; must be unique on the work item.

fileName

string

Optional. Local file name; defaults to the server-side name (sanitized).

outputDir

string

Optional. Defaults to AZURE_DEVOPS_DOWNLOAD_DIR, then the server's cwd.

project

string

Optional. Team project for the download URL.

Returns { id, name, path, size, workItemId } with the absolute path of the saved file. Existing files are never overwritten: a (1), (2), ... suffix is added instead.

How it maps to the REST API

Tool

Calls

list

GET {collection}/_apis/wit/workitems/{id}?$expand=relations and filters rel == "AttachedFile".

add

POST {collection}/{project}/_apis/wit/attachments?fileName=... (octet-stream), then PATCH .../workitems/{id} adding an AttachedFile relation, guarded by test /rev.

delete

PATCH .../workitems/{id} removing /relations/{index}, guarded by test /rev.

download

GET {collection}/{project}/_apis/wit/attachments/{guid}?fileName=...&download=true, streamed to disk.

Details that matter on-premise:

  • Work items are addressed at collection level, so a work item in any project of the collection works.

  • Download URLs are rebuilt from AZURE_DEVOPS_URL instead of trusting the host inside the relation URL, which on some servers points at an internal name.

  • X-TFS-FedAuthRedirect: Suppress is sent so a rejected PAT yields a clear 401 instead of a sign-in page.

  • A failed test /rev (concurrent edit) triggers exactly one refetch-and-retry.

Limitations

  • Simple uploads only: files above 130 MB (chunked upload) are rejected with a clear message.

  • PAT authentication only; NTLM / Windows integrated authentication is not supported.

  • Deleting removes the attachment link; the blob stays on the server (no API exists to purge it).

  • stdio transport only.

Security notes

  • The PAT is used solely as a Basic auth header for requests to AZURE_DEVOPS_URL and never appears in logs, tool output or error messages.

  • Everything the server logs goes to stderr; stdout is reserved for the MCP protocol.

  • Downloaded file names are sanitized (path separators, .., control characters) and files are written only inside the requested output directory.

  • AZURE_DEVOPS_ALLOW_INSECURE_TLS=true disables certificate verification for the whole process. Use it only on trusted networks; adding your CA via NODE_EXTRA_CA_CERTS is the safer option.

Troubleshooting

Symptom

Likely cause

Authentication failed (HTTP 401)

PAT invalid, expired, created in another collection, or missing the Work Items (Read & Write) scope.

returned a sign-in page instead of data

Same as above, or AZURE_DEVOPS_URL is not the collection URL (missing /tfs/DefaultCollection).

Could not reach ...: SELF_SIGNED_CERT_IN_CHAIN

Self-signed certificate: set NODE_EXTRA_CA_CERTS or, less safely, AZURE_DEVOPS_ALLOW_INSECURE_TLS=true.

Could not reach ...: ENOTFOUND / ECONNREFUSED

Wrong host in AZURE_DEVOPS_URL, VPN not connected, or the server is down.

TF401232: Work item ... does not exist

Wrong id, or the PAT owner cannot read that work item.

2 attachments named "..."

Pass attachmentId (from list_attachments) instead of name.

Run npx @modelcontextprotocol/inspector npx -y azure-devops-attachment-mcp-server with the environment variables set to try the tools interactively.

Development

npm install
npm run typecheck   # tsc --noEmit over src and tests
npm test            # vitest (builds dist/ first so the bin tests use the real entry point)
npm run build       # emits dist/
npm run inspect     # MCP Inspector against dist/index.js

Tests run against a fake fetch; no Azure DevOps Server is needed. See docs/design.md for the design notes.

Releasing

  1. Bump version in package.json and commit.

  2. Tag it: git tag v<version> && git push --tags.

  3. The Publish to npm workflow builds, tests and publishes with provenance. It needs an NPM_TOKEN repository secret (an npm automation token).

License

MIT

Available Tools

4 tools
add_attachmentAdd an attachment to a work itemA

Upload a local file and attach it to a work item (adds an AttachedFile relation). Simple upload limit: 130 MB. Returns the attachment id and the work item's new revision.

ParametersJSON Schema
NameRequiredDescriptionDefault
commentNoOptional comment shown next to the attachment.
projectNoTeam project name or ID used for the attachment endpoints. Defaults to the work item's own project, then to AZURE_DEVOPS_PROJECT.
fileNameNoName to store on the server; defaults to the file's base name.
filePathYesAbsolute or cwd-relative path of the local file to upload (simple upload limit: 130 MB).
workItemIdYesWork item ID (unique within the collection).

Output Schema

ParametersJSON Schema
NameRequiredDescription
attachmentYes
workItemIdYes
workItemRevYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already indicate a mutating, non-idempotent operation, and the description adds useful behavioral context: it creates an AttachedFile relation, has a 130 MB simple-upload limit, and produces a new work item revision. It does not address auth prerequisites or duplicate-file-name behavior, but it goes beyond what the annotations convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tightly written sentences front-load the core action and then add the relation, upload limit, and return values. No filler or redundant wording.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a straightforward upload tool with full schema coverage and an output schema, the description is nearly complete: it gives the mechanism, the size limit, and the key return values. It could be more complete with explicit guidance on when to use this tool versus its siblings and any authentication prerequisites, but those are not critical for invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all five parameters. The description's upload-limit and return-value details are contextual rather than parameter-specific, so it does not add much semantic meaning about individual parameters; baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Upload a local file and attach it to a work item') and identifies the resulting relation ('adds an AttachedFile relation'). This specific verb+object+target makes it easy to distinguish from sibling tools such as list_attachments, delete_attachment, and download_attachment.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The context of adding a new attachment is unambiguous, but the description does not explicitly enumerate the sibling alternatives or say when not to use this tool. It provides a clear use case and an operational limit, so the absence of an explicit exclusion is a minor gap.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_attachmentDelete a work item attachmentA
Destructive

Remove an attachment from a work item by attachment id or by a file name that is unique on the work item. Only the link is removed; Azure DevOps has no API to delete the stored file.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoExact server-side file name. Works only when the name is unique on the work item; otherwise pass attachmentId.
workItemIdYesWork item ID (unique within the collection).
attachmentIdNoAttachment GUID as returned by list_attachments (the last segment of the attachment URL).

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
removedYes
workItemIdYes
workItemRevYes

TDQS

A4.1/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds a critical behavioral nuance beyond the annotations: 'Only the link is removed; Azure DevOps has no API to delete the stored file.' This clarifies the exact scope of destruction and a platform limitation, which is valuable context the annotations alone do not provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences: the first states the primary purpose and identification options, the second adds the critical limitation. No wasted words and the most important information is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with only three parametershare, full schema coverage, destructive annotations, and an output schema, the description is complete. It covers what is deleted, how to select the target, and the file-storage caveat.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all three parameters well. The description reinforces the choice between name and attachmentId, but adds little beyond what the schema states.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a precise verb ('Remove') and resource ('attachment from a work item'), making the tool's function unmistakable. The destructive intent is clearly differentiated from the sibling tools list_attachments, add_attachment, and download_attachment.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains how to identify an attachment (by ID or unique name) but does not explicitly state when this tool should be preferred over alternatives or provide exclusion conditions. There is no guidance about when to use list_attachments to find an attachment ID before deleting.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

download_attachmentDownload a work item attachmentA

Download a work item attachment to a local directory by attachment id or unique file name. Existing files are never overwritten (a " (n)" suffix is added). Returns the absolute path of the saved file.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoExact server-side file name. Works only when the name is unique on the work item; otherwise pass attachmentId.
projectNoTeam project name or ID used for the attachment endpoints. Defaults to the work item's own project, then to AZURE_DEVOPS_PROJECT.
fileNameNoLocal file name to save as; defaults to the server-side name. Existing files are never overwritten; a " (n)" suffix is added.
outputDirNoDirectory to save into; defaults to AZURE_DEVOPS_DOWNLOAD_DIR, then to the server's working directory.
workItemIdYesWork item ID (unique within the collection).
attachmentIdNoAttachment GUID as returned by list_attachments (the last segment of the attachment URL).

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYes
nameYes
pathYes
sizeYes
workItemIdYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations, the description discloses that existing local files are never overwritten, a ' (n)' suffix is added, and the absolute path of the saved file is returned. This is exactly the non-obvious behavior an agent needs to know, and it is consistent with the openWorldHint and idempotentHint annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences and under 30 words, yet conveys purpose, selection method, file-collision behavior, and return value. Every sentence earns its place and the main action is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given six parameters and full schema coverage, the description is largely complete: it covers the download action, the local destination, conflict behavior, and the return value. It does not explicitly state at the tool level that one of attachmentId or name is effectively required, though the schema descriptions imply it. Minor omission, but overall sufficient for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents every parameter. The description's mention of 'attachment id or unique file name' only restates what the schema's attachmentId and name fields already convey. It adds no extra semantic detail beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb and resource ('Download a work item attachment'), the destination ('to a local directory'), and the two selection keys ('by attachment id or unique file name'). This clearly distinguishes it from the sibling list/add/delete attachment tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives practical selection guidance: use attachmentId or a unique file name. The schema adds the important caveat that name-based lookup only works when the name is unique, otherwise attachmentId should be used. It does not explicitly say 'use list_attachments first' or state when not to use this tool, but the intended usage is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_attachmentsList work item attachmentsA
Read-onlyIdempotent

List the file attachments of an Azure DevOps work item: name, size, comment, created date, attachment id (GUID) and URL.

ParametersJSON Schema
NameRequiredDescriptionDefault
workItemIdYesWork item ID (unique within the collection).

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYes
workItemIdYes
attachmentsYes
workItemRevYes

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, fully covering the safety profile. The description adds the returned fields (name, size, comment, created date, attachment id, URL), which is beyond the schema's parameter-only coverage, but it does not mention pagination, sorting, or the behavior when the work item has no attachments. This is acceptable given the annotations, but the description could add a note on the absence of results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, concise sentence that immediately states the purpose and lists the key output fields, front-loading the essential information. It contains no filler or redundancy, and every detail is useful for the agent. It is appropriately sized for the tool's simplicity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's low complexity (one parameter), the rich input schema, and the presence of an output schema, the description is largely complete: it states the purpose and the exact fields returned. The only missing element is a note about the lack of filtering or pagination, but the annotations cover safety, and the output schema handles return values. This is nearly complete for a list operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema provides 100% coverage for the single parameter 'workItemId' with a clear description, so the baseline is 3. The description adds no additional detail on the parameter, but that is not necessary given the schema's completeness. The description focuses on the output, which is already captured in the output schema, so it adds minimal value here.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists file attachments of an Azure DevOps work item and enumerates the returned fields, which is specific and informative. Although it does not explicitly name sibling tools, the verb 'list' and the resource 'attachments' distinguish it from add, delete, and download operations, and the scope is unambiguous. The only minor gap is not naming the sibling for contrast, but the purpose is clear.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for retrieving attachment metadata from a work item, which is clear for a simple list operation. It does not provide explicit when-to-use guidance or contrast with alternatives like download_attachment, but the operation is straightforward and the schema's required workItemId defines the key context. Given the low complexity, a 3 is adequate; explicit exclusions would push it higher.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observedadd_attachment
    • First observeddelete_attachment
    • First observeddownload_attachment
    • First observedlist_attachments

TDQS

A4.2/5.0

Scored across 4 tools

Disambiguation5/5

Each tool performs a clearly distinct action on attachments: list, upload, delete, and download. There is no overlap in purpose, and the descriptions reinforce the boundaries.

Naming Consistency5/5

All tool names follow a consistent verb_noun snake_case pattern: list_attachments, add_attachment, delete_attachment, download_attachment. The only minor variation is singular versus plural attachment, but it reads naturally and does not cause confusion.

Tool Count5/5

Four tools is exactly the right scope for an attachment management server: list, add, delete, and download. There is no redundancy or unnecessary bloat.

Completeness5/5

The tool set covers the full actionable lifecycle for work item attachments: enumerate, add, remove the link, and retrieve the file. Update is not applicable since attachments are immutable, so there are no meaningful gaps.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers