Skip to content

feat(attachments): add upload/delete/list_attachments tools#12

Open
jacky1967 wants to merge 1 commit into
skridlevsky:mainfrom
jacky1967:phase2-attachments
Open

feat(attachments): add upload/delete/list_attachments tools#12
jacky1967 wants to merge 1 commit into
skridlevsky:mainfrom
jacky1967:phase2-attachments

Conversation

@jacky1967
Copy link
Copy Markdown
Contributor

Summary

Adds 3 new MCP tools to the Obsidian backend for binary file management (PDFs, images, audio attachments). Designed for consumers that prefer a single MCP-driven write path over a direct filesystem fallback for non-Markdown content.

Motivation

Current MCP tools cover Markdown pages but not binary attachments. Consumers wanting to operate strictly through MCP have to fall back to a separate filesystem layer for things like image uploads or PDF storage, which forces them to maintain two distinct write paths and complicates path-traversal hardening.

What's added

3 tools on *vault.Client (Obsidian-only by design — Logseq has no filesystem concept, so not added to backend.Backend):

  • upload_attachment(path, contentBase64) — atomic temp+rename, auto-creates parent dirs, rejects .md (caller should use create_page).
  • delete_attachment(path) — rejects .md, errors if missing or if target is a directory.
  • list_attachments(folder, recursive?) — non-.md filter, sorted by path, returns size + RFC3339 mtime.

Registered in server.go alongside the existing Obsidian reload tool via type-assertion (skipped automatically in --read-only mode).

Path safety

Reuses the existing safePath() helper so attachments inherit the same boundary checks as pages — no new attack surface.

Tests

ok  	github.com/skridlevsky/graphthulhu/tools	0.407s
ok  	github.com/skridlevsky/graphthulhu/vault	3.323s
  • 14 tests at the vault layer (atomicity, parent-dir creation, .md rejection on upload/delete, missing path, directory-as-target, listing, recursion, sort order).
  • 10 tests at the tools layer (MCP arg validation, base64 decode errors, surface coverage).
  • End-to-end smoke over stdio JSON-RPC: initializetools/list shows the 3 new tools (35 total) → upload/list/delete/list-empty chain → .md rejection path. (Smoke run locally during development, not committed.)

No side-effect

  • Logseq backend untouched (no Backend interface change).
  • Existing tools unchanged.
  • --read-only mode still skips all writers, including the 3 new ones.
  • No new dependency added to go.mod.

Files

 server.go                 |  15 +++
 tools/attachments.go      |  70 ++++++++++++++
 tools/attachments_test.go | 160 +++++++++++++++++++++++++++++++
 types/tools.go            |  16 ++++
 vault/attachments.go      | 181 +++++++++++++++++++++++++++++++++++
 vault/attachments_test.go | 238 ++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 680 insertions(+)

Compatibility note vs PR #11

This branch is independent of #11 (Dockerfile). They touch disjoint paths and can be merged in any order.

Adds 3 MCP tools for binary file management on the Obsidian backend, for
consumers that prefer an MCP-driven write path over direct filesystem
fallback for attachments (PDFs, images, audio):

- upload_attachment(path, contentBase64) — atomic temp+rename, parent
  dirs auto-created, .md rejected (use create_page).
- delete_attachment(path) — .md rejected (use delete_page), error if
  missing or directory.
- list_attachments(folder, recursive?) — non-.md filter, sorted by
  path, returns size + RFC3339 mtime.

Tools sit on *vault.Client directly (Obsidian-only by design); not
added to the backend.Backend interface — Logseq has no filesystem
concept. Registered in server.go alongside the existing Obsidian
"reload" tool via type-assertion (skipped in --read-only mode).

Path traversal protection reuses safePath() so attachments inherit
the same boundary checks as pages.

Tests: 14 vault layer + 10 tools layer + end-to-end smoke (stdio
JSON-RPC: initialize, tools/list shows 35 tools incl. 3 new, upload
→list→delete→list-empty chain, .md rejection).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant