feat: add binary file attachment and import tools for Outline LTM system #1
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Feature: Binary File Attachment & Import Tools for Long-Term Memory System
Motivation
I'm building a Long-Term Memory system for Hermes Agent using Outline Wiki as the persistence layer (inspired by this Reddit post about using Obsidian for the same purpose). The system needs to:
The current MCP tools (
search_documents,get_document,list_collections,list_collection_documents,outline_api_call) handle text operations well, but the genericoutline_api_calltool sends only JSON — it cannot perform the multipart/form-data upload that Outline'sattachments.createendpoint requires.I've validated this by calling
attachments.createvia the generic tool, which returns the upload URL/metadata successfully, but the actual binary upload requires a subsequentmultipart/form-dataPOST that the JSON-only tool can't do. Workarounds via externalcurlare fragile and break the "all-Outline-operations-go-through-MCP" pattern.Required New Tools
1.
attach_file_to_document(document_id, file_path) -> strUpload a local file as an attachment to an existing Outline document.
Outline API workflow:
attachments.createwith{documentId, filename, contentType}→ returns{uploadUrl, assetId}PUTorPOSTtouploadUrlwith the binary file content asmultipart/form-dataReturn: The attachment Markdown/URL that can be inserted into the document body.
Implementation consideration: The MCP server can read the file from disk (since it has filesystem access) and do the multipart upload programmatically using
httpx(already a dependency).2.
import_file_to_outline(file_path, collection_id, parent_document_id=None) -> strImport a local file (Markdown, plain text, JSON, CSV) as a new Outline document.
Outline API workflow:
fileOperations.createwith{collectionId, parentDocumentId, format}→ returns upload metadatafileOperations.infofor completionReturn: The document URL of the newly created document.
3.
upload_image_to_document(image_path, document_id, alt_text=None) -> strA convenience wrapper that:
attachments.createReturn: Markdown image embed string ready to be appended to the document.
Architectural Notes
The existing
server.py(577 lines, FastMCP + httpx) already has anOutlineClientclass with proper async HTTP handling. The new tools can reuse:httpx.AsyncClientfor multipart uploadsosimports for filesystem accessoutline_api_callNew dependencies should be minimal —
httpxsupports multipart natively viafiles=...parameter.Security Considerations
file_pathis within allowed directoriesAcceptance Criteria
attach_file_to_documentcan attach a.png/.jpg/.pdfto any Outline documentdocuments.updateimport_file_to_outlinecan create a new document from a local.mdfileImplementation Complete
All requested features have been implemented and deployed.
What was built
1. File Upload via HTTP Gateway (closes attach/import gap)
Instead of adding filesystem-dependent MCP tools (which would break remote client compatibility), we built a streaming HTTP upload proxy at
POST /upload:documentIdandnamequery paramsBearertoken used by MCP toolsattachments.createwith Outline API to register metadataid,url, andcontentTypeThis works for any binary file (images, PDFs, CSVs, etc.) and keeps the MCP tool surface clean while enabling full upload functionality.
2. S3 + Local Storage Support
The upload endpoint auto-detects the storage backend:
formfields fromattachments.createto POST multipart data to/api/files.createThis was discovered and fixed during live testing — the original code assumed S3 only.
Tools tested and verified
outline_api_call→documents.createoutline_api_call→documents.updatePOST /upload→ file attachmentoutline_api_call→attachments.deleteTest artifact
A live test page was created in the "Scratch Pad" collection: MCP Upload Test Page containing a successfully uploaded test file.
Notes on design decisions
POST /uploadendpoint is HTTP-native, so it works from any client (Cursor, scripts, browsers) without requiring server-side file access.search_documents,get_document,list_collections,list_collection_documents,outline_api_call) are unchanged.httpx(already a dependency) for both S3 PUT and local storage multipart POST.Files changed
server.py— Added upload endpoint with dual storage backend supportDeployment
Committed to
main, CI built and pushedgitea.ext.ben.io/b3nw/outline-mcp-custom:latest, and Komodo redeployed the stack. Server is live athttps://outline-mcp.ext.ben.io.