feat: Add embedMarkdown to upload response and document attachment format
Build and Push Outline MCP Docker Image / build (push) Successful in 7s
Build and Push Outline MCP Docker Image / build (push) Successful in 7s
The upload response now includes an embedMarkdown field with the correct Outline attachment syntax ([name size](/api/attachments.redirect?id=...)) so callers can insert it directly into documents for native card rendering. IMPLEMENTATION.md updated with storage backend details, auth header, and embedding format documentation.
This commit is contained in:
@@ -314,6 +314,61 @@ Delete attachment. Params: id (required)
|
||||
### attachments.redirect
|
||||
Get attachment URL. Params: id (required)
|
||||
|
||||
### Custom HTTP Gateway Upload
|
||||
Instead of manually calling `attachments.create` and executing the subsequent storage upload, you can use the MCP server's integrated HTTP upload proxy.
|
||||
|
||||
This endpoint receives binary content over HTTP, registers the attachment with Outline, and uploads it to the configured storage backend (S3 or local file storage).
|
||||
|
||||
- **Endpoint**: `POST /upload`
|
||||
- **Query Parameters**:
|
||||
- `documentId` (required): The UUID of the destination Outline document.
|
||||
- `name` (required): The filename of the attachment (e.g., `image.png`, `document.pdf`).
|
||||
- **Headers**:
|
||||
- `Authorization` (required): `Bearer <OUTLINE_API_TOKEN>`
|
||||
- `Content-Length` (required): The exact size of the file in bytes.
|
||||
- `Content-Type` (optional): The MIME type of the file (e.g., `application/pdf`, `image/png`). If omitted, it will be auto-detected by filename extension.
|
||||
- **Request Body**: The raw file binary content.
|
||||
- **Storage backends**: Auto-detected from the Outline API response.
|
||||
- **S3**: PUT to pre-signed URL.
|
||||
- **Local file storage**: Multipart POST to `/api/files.create` with form fields.
|
||||
- **Response**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"id": "attachment-uuid",
|
||||
"name": "filename.ext",
|
||||
"size": 12345,
|
||||
"contentType": "mime/type",
|
||||
"url": "/api/attachments.redirect?id=attachment-uuid",
|
||||
"embedMarkdown": "[filename.ext 12345](/api/attachments.redirect?id=attachment-uuid)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Embedding Attachments in Documents
|
||||
|
||||
Outline uses a special markdown syntax to render attachment cards (with file icon, name, and download size). The format is:
|
||||
|
||||
```
|
||||
[filename.ext filesize](/api/attachments.redirect?id=attachment-uuid)
|
||||
```
|
||||
|
||||
Where:
|
||||
- `filename.ext` is the attachment filename
|
||||
- `filesize` is the file size in bytes (space-separated, not a label)
|
||||
- The URL is the **relative** `/api/attachments.redirect?id=...` path
|
||||
|
||||
The `embedMarkdown` field in the upload response provides this string ready to insert into a document body via `documents.update`.
|
||||
|
||||
**Example**: Upload a file and embed it in a document:
|
||||
```
|
||||
1. POST /upload?documentId=doc-uuid&name=report.pdf → get embedMarkdown
|
||||
2. outline_api_call("documents.update", {"id": "doc-uuid", "text": "...\n\n" + embedMarkdown, "append": true})
|
||||
```
|
||||
|
||||
**Important**: Do NOT use absolute URLs (e.g., `https://docs.example.com/api/attachments.redirect?id=...`) — Outline will not render the attachment card for absolute URLs. Always use the relative path.
|
||||
|
||||
---
|
||||
|
||||
## Auth
|
||||
|
||||
@@ -684,16 +684,25 @@ async def upload_endpoint(request: Request) -> JSONResponse:
|
||||
|
||||
logger.info("Upload to storage completed successfully")
|
||||
|
||||
# 4. Return success and attachment metadata
|
||||
# 4. Return success with attachment metadata and embedding markdown
|
||||
att_name = attachment_data.get("name", filename)
|
||||
att_size = attachment_data.get("size", size)
|
||||
att_url = attachment_data.get("url", "")
|
||||
|
||||
# Outline renders attachment cards when the link text is "filename size"
|
||||
# and the href is the relative /api/attachments.redirect path.
|
||||
embed_markdown = f"[{att_name} {att_size}]({att_url})"
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"ok": True,
|
||||
"data": {
|
||||
"id": attachment_data.get("id"),
|
||||
"name": attachment_data.get("name"),
|
||||
"size": attachment_data.get("size"),
|
||||
"name": att_name,
|
||||
"size": att_size,
|
||||
"contentType": attachment_data.get("contentType"),
|
||||
"url": attachment_data.get("url"),
|
||||
"url": att_url,
|
||||
"embedMarkdown": embed_markdown,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user