Skip to content

feat(tools): add attachment download tools#83

Open
nguyenthe-hien wants to merge 8 commits into
nulab:mainfrom
nguyenthe-hien:feat/download-attachment
Open

feat(tools): add attachment download tools#83
nguyenthe-hien wants to merge 8 commits into
nulab:mainfrom
nguyenthe-hien:feat/download-attachment

Conversation

@nguyenthe-hien
Copy link
Copy Markdown

@nguyenthe-hien nguyenthe-hien commented Mar 31, 2026

Summary

  • Add 6 tools for listing and downloading attachments from issues, wiki pages, and pull requests
  • Add transfer_issue_attachment tool to copy attachments between issues
  • List tools (get_issue_attachments, get_wiki_attachments, get_pull_request_attachments): return attachment metadata (id, name, size, createdUser, etc.)
  • Download tools (get_issue_attachment, get_wiki_attachment, get_pull_request_attachment): download and return file content as base64-encoded data with MIME type detection
    • Images are returned as MCP image content type (inline rendering)
    • Other files are returned as MCP resource content type (embedded blob)
  • Transfer tool (transfer_issue_attachment): download an attachment from one issue and upload it to another

Motivation

The MCP server could reference attachment IDs when creating/updating issues, but had no way to list, download, or transfer existing attachments. This made it impossible for MCP clients to:

  • View images attached to issues
  • Download files attached to wiki pages or pull requests
  • Transfer attachments between issues or systems

This PR uses existing backlog-js methods (getIssueAttachments, getIssueAttachment, getWikisAttachments, getWikiAttachment, getPullRequestAttachments, getPullRequestAttachment, postIssueAttachment) which were available in the library but not exposed as MCP tools.

Implementation

  • List tools use standard ToolDefinition pattern (JSON output with field picking support)
  • Download tools use DynamicToolDefinition pattern to return custom CallToolResult with binary content
  • Extends Toolset type with optional dynamicTools field to support mixed tool types within the same toolset
  • registerTools() now also registers dynamicTools using direct handler (no field picking/token limit for binary content)
  • Utility: streamToBase64, getMimeType, buildAttachmentResult with improved MIME detection and base64 encoding
  • Attachment filenames are URL-decoded before MIME detection to handle encoded characters

New tools

Toolset Tool Description
issue get_issue_attachments List attachments for an issue
issue get_issue_attachment Download an attachment from an issue
issue transfer_issue_attachment Copy an attachment from one issue to another
wiki get_wiki_attachments List attachments for a wiki page
wiki get_wiki_attachment Download an attachment from a wiki page
git get_pull_request_attachments List attachments for a pull request
git get_pull_request_attachment Download an attachment from a pull request

Test plan

  • All 372 tests pass (npm test)
  • TypeScript type check passes (npx tsc --noEmit)
  • ESLint passes (npm run lint)
  • Unit tests cover: correct API calls, image vs non-image content types, error handling for missing IDs, stream-to-base64 conversion, MIME type detection, transfer attachment flow

nguyenthe-hien and others added 2 commits April 1, 2026 07:02
… requests

Add 6 new tools to list and download attachments:
- get_issue_attachments / get_issue_attachment
- get_wiki_attachments / get_wiki_attachment
- get_pull_request_attachments / get_pull_request_attachment

List tools return attachment metadata (id, name, size, etc.).
Download tools return base64-encoded file content — images are returned
as MCP image content type, other files as embedded resources.

Also extends Toolset type with optional `dynamicTools` field to support
tools that return custom CallToolResult (needed for binary file content).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds MCP tooling to list and download Backlog attachments (issues, wiki pages, pull requests), including utilities for stream-to-base64 conversion and MIME-type/content shaping, and extends tool registration to support dynamic (binary-returning) tools.

Changes:

  • Introduces attachment listing tools for issues, wiki pages, and pull requests.
  • Introduces attachment download tools that return base64 content as MCP image (inline) or resource (blob) based on MIME type.
  • Extends toolset typing and registration to support mixed static + dynamic tool definitions.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/utils/streamToBase64.ts New helper to convert Node/Web streams (or strings) to base64 for downloads.
src/utils/streamToBase64.test.ts Unit tests for stream/string base64 conversion behavior.
src/utils/getMimeType.ts New filename-extension → MIME type utility.
src/utils/getMimeType.test.ts Unit tests for MIME type detection.
src/utils/buildFileContent.ts New helper to format downloaded files into MCP image or resource content.
src/types/toolsets.ts Extends Toolset to optionally include dynamicTools.
src/tools/tools.ts Wires new attachment tools into issue, wiki, and git toolsets.
src/tools/getWikiAttachments.ts Tool to list wiki attachments.
src/tools/getWikiAttachments.test.ts Unit tests for wiki attachment listing.
src/tools/getWikiAttachment.ts Dynamic tool to download a wiki attachment and return MCP content.
src/tools/getWikiAttachment.test.ts Unit tests for wiki attachment download and filename decoding behavior.
src/tools/getPullRequestAttachments.ts Tool to list pull request attachments.
src/tools/getPullRequestAttachments.test.ts Unit tests for pull request attachment listing.
src/tools/getPullRequestAttachment.ts Dynamic tool to download a pull request attachment and return MCP content.
src/tools/getPullRequestAttachment.test.ts Unit tests for pull request attachment download and error path.
src/tools/getIssueAttachments.ts Tool to list issue attachments.
src/tools/getIssueAttachments.test.ts Unit tests for issue attachment listing.
src/tools/getIssueAttachment.ts Dynamic tool to download an issue attachment and return MCP content.
src/tools/getIssueAttachment.test.ts Unit tests for issue attachment download, decoding, and error path.
src/registerTools.ts Registers dynamicTools in enabled toolsets using direct handlers.
src/registerTools.test.ts Updates tool registration count assertions to include dynamicTools.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/utils/buildFileContent.ts
Comment thread src/registerTools.ts
Comment thread src/tools/getPullRequestAttachments.ts Outdated
Comment thread src/tools/getPullRequestAttachment.ts Outdated
@katayama8000
Copy link
Copy Markdown
Collaborator

katayama8000 commented Apr 2, 2026

@nguyenthe-hien
Thank you for the PR!

But Can you run npm run lint locally and fix errors?
Also, please make sure it works locally.

@nguyenthe-hien

This comment was marked as outdated.

@katayama8000
Copy link
Copy Markdown
Collaborator

@nguyenthe-hien
Thanks. I will review the PR soon.

I wanna ask you that when you wanna get attachments via mcp server?

@nguyenthe-hien

This comment was marked as outdated.

…ling

- Add transferIssueAttachment tool to copy attachments between issues
- Extend getIssueAttachment, getWikiAttachment, getPullRequestAttachment with inline base64 support
- Refactor buildAttachmentResult utility with improved MIME detection and test coverage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@katayama8000
Copy link
Copy Markdown
Collaborator

@nguyenthe-hien
Can you run npm run lint locally and fix errors again?
then I will review it and merge it.

@alex-pythonista
Copy link
Copy Markdown
Contributor

I reproduced the lint failure locally and prepared a small fix branch here:

https://github.com/alex-pythonista/backlog-mcp-server/tree/fix/pr-83-lint

Commit:

eeed9b7 fix lint errors in attachment transfer

What it changes:

  • Imports Buffer from node:buffer
  • Imports URL from node:url
  • Removes the unused beforeEach import from transferIssueAttachment.test.ts

Local verification passed:

  • npm run lint
  • npm run format
  • npm run typecheck
  • npm run typecheck:all
  • npm test - 83 files, 372 tests passed
  • npm run build

Since I do not have permission to push to this PR's source branch directly, the author can cherry-pick eeed9b7 or apply the branch diff.

@nguyenthe-hien
Copy link
Copy Markdown
Author

@alex-pythonista I’ve applied your code and it works well. Thanks!

@alex-pythonista
Copy link
Copy Markdown
Contributor

@alex-pythonista I’ve applied your code and it works well. Thanks!

Thank you. Nice work btw. I needed this PR for my workflow.

@katayama8000
Copy link
Copy Markdown
Collaborator

@nguyenthe-hien @alex-pythonista
I'd appreciate it if you could break it down into smaller, reviewable units.
The current diff is a bit too large to review effectively.

@alex-pythonista
Copy link
Copy Markdown
Contributor

@katayama8000 @nguyenthe-hien — proposing a way to slice this up.

The diff has natural seams along infrastructure → list tools → download tools → transfer tool, since each layer depends on the previous. Suggested split into 4 sequential PRs:

PR 1 — Infrastructure: dynamic tools + utilities (~600 LOC)
Foundational plumbing only, no user-visible tools yet. Reviewer can focus on the new DynamicToolDefinition pattern.

  • src/types/toolsets.ts (add dynamicTools field)
  • src/registerTools.ts + test (register dynamic tools, wrap with backlogErrorHandler)
  • src/tools/dynamicTools/toolsets.ts + test
  • src/utils/streamToBase64.ts + test, streamToBuffer.ts
  • src/utils/getMimeType.ts + test
  • src/utils/buildFileContent.ts, buildAttachmentResult.ts + test
  • src/utils/optimizeImageForInline.ts
  • package.json / package-lock.json (new deps)

PR 2 — List tools (~240 LOC, depends on PR 1)
Standard ToolDefinition pattern returning JSON metadata — small and mechanical.

  • getIssueAttachments, getWikiAttachments, getPullRequestAttachments (+ tests)
  • tools.ts registration

PR 3 — Download tools (~1,100 LOC, depends on PR 1)
Exercises the DynamicToolDefinition path for binary content.

  • getIssueAttachment, getWikiAttachment, getPullRequestAttachment (+ tests)
  • tools.ts registration

PR 4 — Transfer tool + README (~700 LOC, depends on PR 3)
Composes download + upload; naturally last.

  • transferIssueAttachment (+ test)
  • tools.ts registration
  • README.md updates

@nguyenthe-hien if you'd prefer, I'm happy to open PRs 1–4 from my fork using the existing commits on this branch — just let me know.

@nguyenthe-hien
Copy link
Copy Markdown
Author

@katayama8000 @alex-pythonista

Per @katayama8000's request, I've split this PR into 4 smaller, sequential PRs:

# PR Layer Files New
1 #114 Infrastructure (dynamic tools + utilities) 16 16
2 #115 List tools (issue/wiki/PR attachments) 23 7
3 #116 Download tools (binary content) 29 6
4 #117 Transfer tool + README 32 3

How they relate

All 4 branches base on main, so each PR's diff against main includes the layers below it until they're merged. Each PR description clearly marks which layer is new and which layers are inherited from earlier PRs.

Recommended review/merge order: #114#115#116#117.

Once a PR is merged, the next PR's diff shrinks automatically.

Status

This PR (#83) will be closed once #117 is merged. Please review the new PRs instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants