Skip to content

Tool Catalog

This is the complete reference for every MCP tool Picora exposes. AI clients automatically read this schema via tools/list; this page documents it for human readers.

Tools at a glance

ToolPurposeRequired scope
upload_imageUpload an imagemedia:write
upload_videoUpload a videomedia:write
upload_audioUpload an audio filemedia:write
upload_docUpload a Markdown documentdocs:write
list_mediaList images / videos / audiomedia:read
list_docsList Markdown documentsdocs:read
get_imageGet image metadatamedia:read
get_mediaGet video / audio metadatamedia:read
get_docGet document metadata + optional contentdocs:read
delete_mediaDelete images / videos / audiomedia:delete
delete_docDelete documentsdocs:delete
get_usageRead current quota / usage statsusage:read

All delete_* tools default to dryRun: true.


Upload tools

upload_image {#upload_image}

Upload an image file to Picora.

Input:

{
"filePath": "string (absolute path to local file)",
"title": "string?",
"tags": ["string"]?,
"isPublic": "boolean? (default: true)"
}

Output:

{
"id": "string (11-char nanoid)",
"url": "string (public CDN URL)",
"filename": "string",
"sizeBytes": "number",
"isPublic": "boolean",
"tags": ["string"],
"createdAt": "ISO 8601"
}

Errors: QUOTA_EXCEEDED:img_storage, VALIDATION_ERROR, RATE_LIMIT


upload_video {#upload_video}

Upload a video. Returns immediately with status: "processing"; transcoding happens asynchronously.

Input:

{
"filePath": "string",
"title": "string?",
"tags": ["string"]?,
"isPublic": "boolean? (default: true)"
}

Output:

{
"id": "string",
"title": "string",
"filename": "string",
"sizeBytes": "number",
"status": "processing | ready | failed",
"createdAt": "ISO 8601"
}

After upload, poll get_media until status: "ready" to get playback_url.

Errors: QUOTA_EXCEEDED:video_storage, VALIDATION_ERROR (file too large / too long), FORBIDDEN (plan doesn’t include video).


upload_audio {#upload_audio}

Upload an audio file. ID3 metadata (cover art, title, duration, bitrate) is extracted automatically.

Input:

{
"filePath": "string",
"title": "string?",
"tags": ["string"]?,
"isPublic": "boolean? (default: true)"
}

Output:

{
"id": "string",
"url": "string",
"thumbnail_url": "string (extracted ID3 cover or default)",
"title": "string",
"filename": "string",
"sizeBytes": "number",
"durationSeconds": "number",
"bitrate": "number",
"isPublic": "boolean",
"createdAt": "ISO 8601"
}

Errors: same as video.


upload_doc {#upload_doc}

Upload a Markdown document. Inline base64 images are auto-extracted and rewritten to Picora CDN URLs.

Input:

{
"filePath": "string?",
"content": "string?",
"filename": "string (e.g. 'README.md')",
"title": "string?",
"tags": ["string"]?,
"isPublic": "boolean? (default: false)",
"rewriteImages": "boolean? (default: true)"
}

Either filePath or content is required (not both).

Output:

{
"id": "string",
"title": "string",
"filename": "string",
"sizeBytes": "number",
"wordCount": "number",
"imageCount": "number (total embedded refs)",
"rewrittenCount": "number (actually rewritten)",
"failedCount": "number",
"failures": [{ "index": "number", "reason": "string" }],
"warnings": ["string"],
"isPublic": "boolean",
"tags": ["string"],
"createdAt": "ISO 8601"
}

Errors: QUOTA_EXCEEDED:doc_count, QUOTA_EXCEEDED:img_storage (when rewriting), DOC_FILE_TOO_LARGE, DOC_IMAGE_LIMIT_EXCEEDED, DOC_HASH_DUPLICATE (returns existing doc, not actually an error).


List tools

list_media {#list_media}

List images, videos, and audio (unified resource endpoint).

Input:

{
"type": "image | video | audio | null (all)",
"cursor": "string? (paginate)",
"limit": "number? (default: 20, max: 50)",
"tag": "string?"
}

Output:

{
"items": [{
"id": "string",
"type": "image | video | audio",
"title": "string",
"filename": "string",
"sizeBytes": "number",
"thumbnail_url": "string?",
"isPublic": "boolean",
"tags": ["string"],
"createdAt": "ISO 8601"
}],
"nextCursor": "string? (null if last page)"
}

list_docs {#list_docs}

List Markdown documents. Filters by title (q) and tags.

Input:

{
"cursor": "string?",
"limit": "number? (default: 20, max: 50)",
"q": "string? (matches title only)",
"tag": "string? (multi via comma)",
"isPublic": "boolean? (filter)",
"sort": "created_desc | created_asc | updated_desc | updated_asc (default: created_desc)"
}

Output:

{
"items": [{
"id": "string",
"title": "string",
"filename": "string",
"sizeBytes": "number",
"wordCount": "number",
"imageCount": "number",
"isPublic": "boolean",
"tags": ["string"],
"createdAt": "ISO 8601",
"updatedAt": "ISO 8601"
}],
"nextCursor": "string?"
}

Get tools

get_image {#get_image}

Get full metadata for one image.

Input: { "id": "string" }

Output: same as upload_image plus updatedAt.


get_media {#get_media}

Get metadata for video or audio. The playback_url field is populated when status: "ready".

Input: { "id": "string" }

Output:

{
"id": "string",
"type": "video | audio",
"title": "string",
"filename": "string",
"sizeBytes": "number",
"durationSeconds": "number?",
"playback_url": "string? (HLS playlist for video, direct URL for audio)",
"thumbnail_url": "string?",
"status": "processing | ready | failed",
"isPublic": "boolean",
"createdAt": "ISO 8601",
"updatedAt": "ISO 8601"
}

get_doc {#get_doc}

Get document metadata. Optionally include the full markdown content.

Input:

{
"id": "string",
"includeContent": "boolean? (default: false)"
}

Output: metadata fields (see list_docs) + optional content: string (utf-8 markdown).

When includeContent: true and content is huge (>5 MB), the response is truncated with truncated: true flag — fetch via the public raw endpoint instead for very large documents.


Delete tools

delete_media {#delete_media}

Delete one or many images / videos / audio. Defaults to dryRun: true.

Input:

{
"ids": ["string"],
"dryRun": "boolean (default: true)"
}

Output (dry-run):

{
"dryRun": true,
"wouldDelete": [{
"id": "string",
"type": "image | video | audio",
"title": "string",
"sizeBytes": "number"
}],
"totalCount": "number",
"totalBytes": "number"
}

Output (actual delete):

{
"dryRun": false,
"deleted": ["string (list of ids)"],
"failed": [{ "id": "string", "reason": "string" }]
}

Single batch limit: 50 ids.


delete_doc {#delete_doc}

Delete Markdown documents. Same dryRun pattern.

Input:

{
"ids": ["string"],
"dryRun": "boolean (default: true)"
}

Output: same shape as delete_media.


Usage

get_usage {#get_usage}

Read your current quota and usage stats.

Input: {} (no parameters)

Output:

{
"plan": "trial | pro | pro_plus",
"image": {
"storage": { "used": "number", "limit": "number" },
"bandwidth": { "used": "number", "limit": "number", "monthYear": "YYYY-MM" }
},
"video": {
"storage": { "used": "number", "limit": "number" },
"bandwidth": { "used": "number", "limit": "number", "monthYear": "YYYY-MM" },
"status": "ok | degraded | suspended"
},
"audio": {
"storage": { "used": "number", "limit": "number (shared with video)" }
},
"doc": {
"count": { "used": "number", "limit": "number" }
},
"uploadCount": {
"today": "number",
"todayLimit": "number",
"thisMonth": "number",
"thisMonthLimit": "number"
}
}

Error codes summary

All tools may return these structured errors. The AI client sees error.code and error.meta and can react / re-prompt.

CodeHTTPDescription
UNAUTHORIZED401API Key / OAuth token invalid or expired
FORBIDDEN403OAuth scope insufficient, or feature not in plan
QUOTA_EXCEEDED403meta.type indicates which: img_storage, media_storage, doc_count etc.
RATE_LIMIT429Tier-based rate limit exceeded; Retry-After header set
NOT_FOUND404Resource doesn’t exist
VALIDATION_ERROR422Input failed schema validation
DOC_HASH_DUPLICATE409Document content hash already exists; meta.existingId returns the original
DOC_FILE_TOO_LARGE422Markdown exceeds plan’s doc_max_file_bytes
DOC_IMAGE_LIMIT_EXCEEDED422Embedded image count exceeds plan’s doc_max_images_per_doc
STORAGE_ERROR502Object storage unavailable; retry
INTERNAL500Unexpected error; report with requestId