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
|
### attachments.redirect
|
||||||
Get attachment URL. Params: id (required)
|
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
|
## Auth
|
||||||
|
|||||||
@@ -684,16 +684,25 @@ async def upload_endpoint(request: Request) -> JSONResponse:
|
|||||||
|
|
||||||
logger.info("Upload to storage completed successfully")
|
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(
|
return JSONResponse(
|
||||||
{
|
{
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"data": {
|
"data": {
|
||||||
"id": attachment_data.get("id"),
|
"id": attachment_data.get("id"),
|
||||||
"name": attachment_data.get("name"),
|
"name": att_name,
|
||||||
"size": attachment_data.get("size"),
|
"size": att_size,
|
||||||
"contentType": attachment_data.get("contentType"),
|
"contentType": attachment_data.get("contentType"),
|
||||||
"url": attachment_data.get("url"),
|
"url": att_url,
|
||||||
|
"embedMarkdown": embed_markdown,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user