This commit is contained in:
angelplusultra
2026-03-26 13:50:38 -07:00
parent 58946bbe9d
commit 3f40b75860
2 changed files with 15 additions and 46 deletions

View File

@@ -37,33 +37,19 @@ function MCPImageContent({ props }) {
function MCPImage({ image, index }) {
const [loading, setLoading] = useState(false);
const isDataUri = image.src.startsWith("data:");
const handleDownload = useCallback(async () => {
setLoading(true);
try {
const ext = (image.mimeType || "png").split("/").pop();
if (isDataUri) {
const base64 = image.src.split(",")[1];
const byteString = atob(base64);
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
const blob = new Blob([ab], { type: image.mimeType });
saveAs(blob, `mcp-image-${index}.${ext}`);
} else {
// For remote URLs, fetch and save
const response = await fetch(image.src);
const blob = await response.blob();
saveAs(blob, `mcp-image-${index}.${ext}`);
}
const ext = (image.mimeType || "image/png").split("/").pop();
const response = await fetch(image.src);
const blob = await response.blob();
saveAs(blob, `mcp-image-${index}.${ext}`);
} catch (e) {
console.error("Failed to download image:", e);
}
setLoading(false);
}, [image, index, isDataUri]);
}, [image, index]);
return (
<div className="relative w-full">

View File

@@ -253,36 +253,22 @@ class MCPCompatibilityLayer extends MCPHypervisor {
return { success: true, error: null };
}
/**
* Check if a MIME type is an image type.
* @param {string} mimeType
* @returns {boolean}
*/
static isImageMimeType(mimeType) {
return typeof mimeType === "string" && mimeType.startsWith("image/");
}
/**
* Extract image content items from an MCP tool result per MCP spec.
* Handles three MCP content types that can contain images:
* - `type: "image"` — inline base64-encoded image with data + mimeType
* - `type: "resource_link"` — URI to an externally hosted image
* - `type: "resource"` — embedded resource with a blob (base64) or URI
*
* Handles: type "image" (inline base64), "resource_link" (remote URL), "resource" (embedded blob/URI).
* @param {Object} result - The MCP tool result
* @returns {{images: {src: string, mimeType: string}[], textContent: string, llmText: string}|null}
* Returns null if no image content is found. Each image has a `src` that is either
* a data URI (for base64) or a remote URL (for resource links).
*/
static extractImageContent(result) {
if (!result?.content || !Array.isArray(result.content)) return null;
const isImage = (mime) =>
typeof mime === "string" && mime.startsWith("image/");
const images = [];
const textParts = [];
for (const item of result.content) {
if (item.type === "image" && item.data && item.mimeType) {
// Inline base64 image
images.push({
src: `data:${item.mimeType};base64,${item.data}`,
mimeType: item.mimeType,
@@ -290,16 +276,14 @@ class MCPCompatibilityLayer extends MCPHypervisor {
} else if (
item.type === "resource_link" &&
item.uri &&
this.isImageMimeType(item.mimeType)
isImage(item.mimeType)
) {
// Resource link pointing to a remote image
images.push({ src: item.uri, mimeType: item.mimeType });
} else if (
item.type === "resource" &&
item.resource &&
this.isImageMimeType(item.resource.mimeType)
isImage(item.resource.mimeType)
) {
// Embedded resource — can have a blob (base64) or just a URI
if (item.resource.blob) {
images.push({
src: `data:${item.resource.mimeType};base64,${item.resource.blob}`,
@@ -319,13 +303,12 @@ class MCPCompatibilityLayer extends MCPHypervisor {
if (images.length === 0) return null;
const textContent = textParts.join("\n");
const imagePlaceholders = images
.map(
(img) =>
`[An image was returned by this tool and displayed to the user. Image type: ${img.mimeType}]`
)
const llmText = [
textContent,
`[${images.length} image(s) returned by this tool and displayed to the user]`,
]
.filter(Boolean)
.join("\n");
const llmText = [textContent, imagePlaceholders].filter(Boolean).join("\n");
return { images, textContent, llmText };
}