Initial commit
This commit is contained in:
294
mcp-server/tools/media/upload.js
Normal file
294
mcp-server/tools/media/upload.js
Normal file
@@ -0,0 +1,294 @@
|
||||
import { z } from "zod";
|
||||
import { withAuth, getSessionCredentials } from "../../auth/index.js";
|
||||
import { handleToolError, validateRequired, handleApiResponse } from "../helpers/errorHandler.js";
|
||||
import { withAuthParams } from "../helpers/authSchema.js";
|
||||
import { AcaiHttpClient } from "../helpers/acaiHttpClient.js";
|
||||
|
||||
/**
|
||||
* Helper: POST to mcp_respond.php via viewer_functions.php
|
||||
*/
|
||||
async function mcpPost(target, actionWs, payload, token, tokenHash) {
|
||||
return AcaiHttpClient.postViewerAction(
|
||||
target,
|
||||
actionWs,
|
||||
payload,
|
||||
token,
|
||||
tokenHash,
|
||||
{},
|
||||
60000
|
||||
);
|
||||
}
|
||||
|
||||
export function registerUploadRecordImageTool(server) {
|
||||
server.tool(
|
||||
"upload_record_image",
|
||||
"Upload an image to a specific record field in Acai CMS. Downloads the image from a URL and uploads it. Table names are WITHOUT the 'cms_' prefix. The recordId is the 'num' primary key, never 'id'. If the URL came from generate_image, prefer uploadUrl (or fullUrl) over dockerUrl in Forge environments.",
|
||||
withAuthParams({
|
||||
tableName: z.string().describe("Table name without 'cms_' prefix (e.g., 'productos')"),
|
||||
recordId: z.string().describe("Record 'num' (primary key)"),
|
||||
fieldName: z.string().describe("Field name (e.g., 'galeria_imagenes')"),
|
||||
imageUrl: z.string().describe("URL of the image to upload"),
|
||||
alt: z.string().optional().describe("Alt text for the image (optional)"),
|
||||
}),
|
||||
{ readOnlyHint: false, destructiveHint: false },
|
||||
withAuth(async ({ tableName, recordId, fieldName, imageUrl, alt = "" }, extra) => {
|
||||
try {
|
||||
const validationError = validateRequired(
|
||||
{ tableName, recordId, fieldName, imageUrl },
|
||||
['tableName', 'recordId', 'fieldName', 'imageUrl'],
|
||||
'upload_record_image'
|
||||
);
|
||||
if (validationError) return validationError;
|
||||
|
||||
const credentials = await getSessionCredentials(extra.sessionId);
|
||||
|
||||
// Upload via mcp_respond.php uploadRecordImage (sends imageUrl, PHP downloads it)
|
||||
const response = await mcpPost(
|
||||
credentials,
|
||||
"uploadRecordImage",
|
||||
{ tableName, recordId, fieldName, imageUrl, alt },
|
||||
credentials.token,
|
||||
credentials.tokenHash
|
||||
);
|
||||
|
||||
const apiError = handleApiResponse(response.data, 'upload_record_image');
|
||||
if (apiError) return apiError;
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
message: "Image uploaded successfully",
|
||||
tableName,
|
||||
recordId,
|
||||
fieldName,
|
||||
...response.data
|
||||
}, null, 2)
|
||||
}],
|
||||
};
|
||||
} catch (error) {
|
||||
return handleToolError(error, 'upload_record_image', { tableName, recordId, fieldName });
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"list_record_uploads",
|
||||
"List all uploaded files in a specific upload field of a record. Table names are WITHOUT the 'cms_' prefix. The recordId is the 'num' primary key, never 'id'.",
|
||||
withAuthParams({
|
||||
tableName: z.string().describe("Table name without 'cms_' prefix (e.g., 'noticias')"),
|
||||
recordId: z.string().describe("Record 'num' (primary key)"),
|
||||
fieldName: z.string().describe("Upload field name (e.g., 'imagen_destacada')"),
|
||||
}),
|
||||
{ readOnlyHint: true, destructiveHint: false },
|
||||
withAuth(async ({ tableName, recordId, fieldName }, extra) => {
|
||||
try {
|
||||
const validationError = validateRequired(
|
||||
{ tableName, recordId, fieldName },
|
||||
['tableName', 'recordId', 'fieldName'],
|
||||
'list_record_uploads'
|
||||
);
|
||||
if (validationError) return validationError;
|
||||
|
||||
const credentials = await getSessionCredentials(extra.sessionId);
|
||||
|
||||
const response = await mcpPost(
|
||||
credentials,
|
||||
"listRecordUploads",
|
||||
{ tableName, recordId, fieldName },
|
||||
credentials.token,
|
||||
credentials.tokenHash
|
||||
);
|
||||
|
||||
const apiError = handleApiResponse(response.data, 'list_record_uploads');
|
||||
if (apiError) return apiError;
|
||||
|
||||
const uploads = (response.data.data || []).map(upload => ({
|
||||
uploadId: upload.num,
|
||||
filePath: upload.filePath,
|
||||
urlPath: upload.urlPath,
|
||||
fileName: (upload.filePath || "").split('/').pop(),
|
||||
altText: upload.info1 || upload.alt || "",
|
||||
width: upload.width,
|
||||
height: upload.height,
|
||||
filesize: upload.filesize,
|
||||
createdTime: upload.createdTime,
|
||||
order: upload.order
|
||||
}));
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
tableName,
|
||||
recordId,
|
||||
fieldName,
|
||||
uploadsCount: uploads.length,
|
||||
uploads,
|
||||
note: "Use uploadId (num field) to replace or delete a specific file"
|
||||
}, null, 2)
|
||||
}],
|
||||
};
|
||||
} catch (error) {
|
||||
return handleToolError(error, 'list_record_uploads', { tableName, recordId, fieldName });
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"replace_record_image",
|
||||
"Replace an existing image in an upload field. Downloads a new image from URL and replaces the specified upload. Use list_record_uploads to get the uploadId first. Table names are WITHOUT the 'cms_' prefix.",
|
||||
withAuthParams({
|
||||
tableName: z.string().describe("Table name without 'cms_' prefix"),
|
||||
recordId: z.string().describe("Record 'num' (primary key)"),
|
||||
fieldName: z.string().describe("Upload field name"),
|
||||
uploadId: z.string().describe("Upload ID to replace (get from list_record_uploads)"),
|
||||
imageUrl: z.string().describe("URL of the new image to upload"),
|
||||
alt: z.string().optional().describe("Alt text for the image (optional)"),
|
||||
}),
|
||||
{ readOnlyHint: false, destructiveHint: false },
|
||||
withAuth(async ({ tableName, recordId, fieldName, uploadId, imageUrl, alt = "" }, extra) => {
|
||||
try {
|
||||
const validationError = validateRequired(
|
||||
{ tableName, recordId, fieldName, uploadId, imageUrl },
|
||||
['tableName', 'recordId', 'fieldName', 'uploadId', 'imageUrl'],
|
||||
'replace_record_image'
|
||||
);
|
||||
if (validationError) return validationError;
|
||||
|
||||
const credentials = await getSessionCredentials(extra.sessionId);
|
||||
|
||||
// Step 1: Delete old upload
|
||||
await mcpPost(
|
||||
credentials,
|
||||
"deleteRecordUpload",
|
||||
{ uploadId },
|
||||
credentials.token,
|
||||
credentials.tokenHash
|
||||
);
|
||||
|
||||
// Step 2: Upload new image
|
||||
const response = await mcpPost(
|
||||
credentials,
|
||||
"uploadRecordImage",
|
||||
{ tableName, recordId, fieldName, imageUrl, alt },
|
||||
credentials.token,
|
||||
credentials.tokenHash
|
||||
);
|
||||
|
||||
const apiError = handleApiResponse(response.data, 'replace_record_image');
|
||||
if (apiError) return apiError;
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
message: "Image replaced successfully",
|
||||
tableName,
|
||||
recordId,
|
||||
fieldName,
|
||||
replacedUploadId: uploadId,
|
||||
...response.data
|
||||
}, null, 2)
|
||||
}],
|
||||
};
|
||||
} catch (error) {
|
||||
return handleToolError(error, 'replace_record_image', { tableName, recordId, fieldName, uploadId });
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"delete_record_upload",
|
||||
"Delete an uploaded file from a record's upload field. Use list_record_uploads to get the uploadId first. Table names are WITHOUT the 'cms_' prefix.",
|
||||
withAuthParams({
|
||||
uploadId: z.string().describe("Upload ID to delete (get from list_record_uploads)"),
|
||||
}),
|
||||
{ readOnlyHint: false, destructiveHint: true },
|
||||
withAuth(async ({ uploadId }, extra) => {
|
||||
try {
|
||||
const validationError = validateRequired(
|
||||
{ uploadId },
|
||||
['uploadId'],
|
||||
'delete_record_upload'
|
||||
);
|
||||
if (validationError) return validationError;
|
||||
|
||||
const credentials = await getSessionCredentials(extra.sessionId);
|
||||
|
||||
const response = await mcpPost(
|
||||
credentials,
|
||||
"deleteRecordUpload",
|
||||
{ uploadId },
|
||||
credentials.token,
|
||||
credentials.tokenHash
|
||||
);
|
||||
|
||||
const apiError = handleApiResponse(response.data, 'delete_record_upload');
|
||||
if (apiError) return apiError;
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
message: "Upload deleted successfully",
|
||||
uploadId,
|
||||
...response.data
|
||||
}, null, 2)
|
||||
}],
|
||||
};
|
||||
} catch (error) {
|
||||
return handleToolError(error, 'delete_record_upload', { uploadId });
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"reorder_record_uploads",
|
||||
"Reorder uploaded files in a record's upload field. Pass an array of upload IDs (num) in the desired order. Use list_record_uploads to get the current upload IDs first.",
|
||||
withAuthParams({
|
||||
uploadIds: z.array(z.union([z.string(), z.number()])).describe("Array of upload IDs (num field) in the desired display order"),
|
||||
}),
|
||||
{ readOnlyHint: false, destructiveHint: false },
|
||||
withAuth(async ({ uploadIds }, extra) => {
|
||||
try {
|
||||
const validationError = validateRequired(
|
||||
{ uploadIds },
|
||||
['uploadIds'],
|
||||
'reorder_record_uploads'
|
||||
);
|
||||
if (validationError) return validationError;
|
||||
|
||||
const credentials = await getSessionCredentials(extra.sessionId);
|
||||
|
||||
const response = await mcpPost(
|
||||
credentials,
|
||||
"reorderRecordUploads",
|
||||
{ uploadIds },
|
||||
credentials.token,
|
||||
credentials.tokenHash
|
||||
);
|
||||
|
||||
const apiError = handleApiResponse(response.data, 'reorder_record_uploads');
|
||||
if (apiError) return apiError;
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
message: "Uploads reordered successfully",
|
||||
...response.data
|
||||
}, null, 2)
|
||||
}],
|
||||
};
|
||||
} catch (error) {
|
||||
return handleToolError(error, 'reorder_record_uploads', { uploadIds });
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user