Upload Media File
Immutable Catalog + Curated Guide
This lossless descriptor comes from the active Portal catalog and is enhanced with reviewed examples.
- Contract tier:
agent_ready - Risk:
write - Catalog version:
2026-07-12.2 - Descriptor hash:
a7af7933fbcb442934e6ca0f6d6e84a6cb3253a225c4b0795674a669aecdbfa7
Upload one owned file to GHL Media Storage. Prefer dataUrl for generated files; direct uploads are limited to 25 MiB after decoding.
Request Body
{
"jsonrpc": "2.0",
"id": "ghl-upload-media-file-example",
"method": "tools/call",
"params": {
"name": "ghl.upload_media_file",
"arguments": {
"dataUrl": "data:image/jpeg;base64,<BASE64_JPEG_BYTES>",
"name": "approved-blog-hero.jpg",
"folderId": "folder_blog_images"
}
}
}
Arguments Schema
{
"type": "object",
"required": [],
"properties": {
"file": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"uri": {
"type": "string",
"description": "Compatibility alias for url; local filesystem URIs are rejected."
},
"url": {
"type": "string",
"description": "Public HTTP or HTTPS source URL for direct multipart upload."
},
"name": {
"type": "string",
"description": "Preferred filename for the uploaded media object."
},
"base64": {
"type": "string",
"description": "Raw base64 content containing decoded bytes of at most 25 MiB."
},
"dataUrl": {
"type": "string",
"description": "Base64 data URL containing decoded content of at most 25 MiB."
},
"fileName": {
"type": "string",
"description": "Compatibility alias for name."
},
"mimeType": {
"type": "string",
"description": "Compatibility alias for contentType."
},
"contentType": {
"type": "string",
"description": "MIME type for direct binary content, such as image/jpeg."
}
},
"additionalProperties": true
}
],
"description": "Backward-compatible file content field. For remote agents this may be base64, a data URL, or an object with a public HTTP/S url/uri. Direct decoded content is limited to 25 MiB. It must not be a local path."
},
"name": {
"type": "string",
"description": "Custom name for the uploaded file. Takes precedence over fileName."
},
"altId": {
"type": "string",
"description": "Location or agency ID. Defaults to the current GHL location."
},
"width": {
"type": "number",
"description": "Optional image width in pixels. For base64/data URL PNG/JPEG/SVG uploads this is auto-detected when possible so GHL can build media grid thumbnails."
},
"base64": {
"type": "string",
"description": "Raw base64 file content for generated image/file uploads. Decoded content is limited to 25 MiB. Provide contentType and fileName when possible."
},
"height": {
"type": "number",
"description": "Optional image height in pixels. For base64/data URL PNG/JPEG/SVG uploads this is auto-detected when possible so GHL can build media grid thumbnails."
},
"hosted": {
"type": "boolean",
"description": "When true or omitted with fileUrl, GHL ingests fileUrl as an already-hosted URL. Set hosted=false with fileUrl to fetch and multipart-upload the bytes through this MCP server."
},
"altType": {
"enum": [
"location",
"agency"
],
"type": "string",
"default": "location",
"description": "GHL media context type."
},
"dataUrl": {
"type": "string",
"description": "Base64 data URL, such as data:image/png;base64,..., for generated image uploads. Decoded content is limited to 25 MiB."
},
"fileUrl": {
"type": "string",
"description": "HTTP or HTTPS URL. By default this is sent to GHL as hosted=true for already-hosted media. Set hosted=false to make this MCP server fetch the URL and multipart-upload the bytes directly to GHL without base64 inflation."
},
"fileName": {
"type": "string",
"description": "Alias for name."
},
"folderId": {
"type": "string",
"description": "Alias for parentId. Use list_media_folders to discover IDs by folder name."
},
"parentId": {
"type": "string",
"description": "GHL media parent folder ID to upload into. Takes precedence over folderId."
},
"tenantId": {
"type": "string",
"description": "Tenant ID from list_tenants."
},
"sourceUrl": {
"type": "string",
"description": "Public HTTP or HTTPS source URL for direct binary upload. The MCP server fetches this URL, rejects local/private-network targets, enforces a 25 MiB non-video file limit, and sends the file to GHL as multipart/form-data."
},
"locationId": {
"type": "string",
"description": "GHL location ID. In BYOK runtime this must match the request header location."
},
"contentType": {
"type": "string",
"description": "MIME type for base64/data URL/direct uploads, such as image/png."
},
"blurHashCode": {
"type": "string",
"description": "Optional GHL-compatible blur hash metadata for image previews. Usually omitted unless already known."
},
"strictTenant": {
"type": "boolean",
"default": true,
"description": "Require tenantId to match locationId routing (default: true)."
}
}
}
Output Schema
{
"type": "object",
"required": [
"ok",
"data",
"error",
"meta"
],
"properties": {
"ok": {
"type": "boolean",
"description": "Whether the GHL MCP operation completed successfully."
},
"data": {
"anyOf": [
{
"type": "object",
"additionalProperties": true
},
{
"type": "array",
"items": {}
},
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
}
],
"description": "Tool-specific GHL or local navigation result when ok is true."
},
"meta": {
"type": "object",
"description": "Safe execution, routing, and timing metadata.",
"additionalProperties": true
},
"error": {
"anyOf": [
{
"type": "object",
"additionalProperties": true
},
{
"type": "null"
}
],
"description": "Safe error details when ok is false; credential values are never returned."
}
},
"description": "Normalized GHL MCP response envelope.",
"additionalProperties": false
}
Code Examples
- cURL
- Node.js
- Python
curl -X POST 'https://portal.madpanda3d.com/api/mcp' \
-H 'Authorization: Bearer mad_live_***' \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": "ghl-upload-media-file-example",
"method": "tools/call",
"params": {
"name": "ghl.upload_media_file",
"arguments": {
"dataUrl": "data:image/jpeg;base64,<BASE64_JPEG_BYTES>",
"name": "approved-blog-hero.jpg",
"folderId": "folder_blog_images"
}
}
}'
const response = await fetch('https://portal.madpanda3d.com/api/mcp', {
method: 'POST',
headers: {
Authorization: 'Bearer mad_live_***',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"jsonrpc": "2.0",
"id": "ghl-upload-media-file-example",
"method": "tools/call",
"params": {
"name": "ghl.upload_media_file",
"arguments": {
"dataUrl": "data:image/jpeg;base64,<BASE64_JPEG_BYTES>",
"name": "approved-blog-hero.jpg",
"folderId": "folder_blog_images"
}
}
}),
});
const data = await response.json();
console.log(data);
import requests
url = 'https://portal.madpanda3d.com/api/mcp'
headers = {
'Authorization': 'Bearer mad_live_***',
'Content-Type': 'application/json',
}
payload = {
"jsonrpc": "2.0",
"id": "ghl-upload-media-file-example",
"method": "tools/call",
"params": {
"name": "ghl.upload_media_file",
"arguments": {
"dataUrl": "data:image/jpeg;base64,<BASE64_JPEG_BYTES>",
"name": "approved-blog-hero.jpg",
"folderId": "folder_blog_images"
}
}
}
response = requests.post(url, headers=headers, json=payload, timeout=60)
print(response.json())
Example Responses
- 200 Success
- 422 Invalid Upload Input
- 413 Decoded File Too Large
{
"success": true,
"status": "uploaded",
"source": "dataUrl",
"message": "Media uploaded successfully with ID: media_abc123",
"fileId": "media_abc123",
"id": "media_abc123",
"url": "https://storage.example.com/approved-blog-hero.jpg",
"hostedUrl": "https://storage.example.com/approved-blog-hero.jpg",
"name": "approved-blog-hero.jpg",
"parentId": "folder_blog_images",
"folderId": "folder_blog_images",
"contentType": "image/jpeg",
"mimeType": "image/jpeg",
"width": 1200,
"height": 675
}
{
"success": false,
"message": "Provide exactly one upload input: dataUrl, base64, fileUrl, sourceUrl, or file."
}
{
"success": false,
"message": "Decoded direct uploads must not exceed 25 MiB."
}