adding tool to receive photo of listing

This commit is contained in:
orangecoding
2026-04-09 11:51:42 +02:00
parent f3ad529107
commit 05f74f99ef

View File

@@ -220,6 +220,122 @@ export function createMcpServer() {
},
);
// ── get_photo_for_listing ─────────────────────────────────────────────────────
server.tool(
'get_photo_for_listing',
'Fetch and return the photo of a listing by its ID as an image for vision analysis.',
{
listingId: z.string().describe('The listing ID whose photo to fetch'),
},
async ({ listingId }, extra) => {
const { user, error } = authenticateToolCall(extra, 'get_photo_for_listing');
if (error) return normalizeError(error, 'get_photo_for_listing');
const listing = getListingById(listingId, user.id, user.isAdmin);
if (!listing) {
return normalizeError('Listing not found or access denied.', 'get_photo_for_listing');
}
const imageUrl = listing.image_url;
if (!imageUrl) {
return normalizeError('No image available for this listing.', 'get_photo_for_listing');
}
const SUPPORTED_MIME_TYPES = new Set(['image/jpeg', 'image/png', 'image/gif', 'image/webp']);
let response;
try {
response = await fetch(imageUrl, {
signal: AbortSignal.timeout(10_000),
headers: {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
Accept: 'image/jpeg,image/png,image/webp,image/gif,image/*,*/*',
},
});
} catch (fetchErr) {
return normalizeError(`Failed to fetch image: ${fetchErr.message}`, 'get_photo_for_listing');
}
if (!response.ok) {
return normalizeError(
`Image fetch returned HTTP ${response.status}. Image URL: ${imageUrl}`,
'get_photo_for_listing',
);
}
const contentType = response.headers.get('content-type') ?? '';
const headerMimeType = contentType.split(';')[0].trim().toLowerCase();
let buffer;
try {
buffer = await response.arrayBuffer();
} catch (readErr) {
return normalizeError(`Failed to read image body: ${readErr.message}`, 'get_photo_for_listing');
}
const bytes = new Uint8Array(buffer);
if (bytes.length < 12) {
return normalizeError(
`Downloaded file is too small to determine image type. Image URL: ${imageUrl}`,
'get_photo_for_listing',
);
}
let resolvedMime;
if (SUPPORTED_MIME_TYPES.has(headerMimeType)) {
resolvedMime = headerMimeType;
} else {
if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
resolvedMime = 'image/jpeg';
} else if (
bytes[0] === 0x89 &&
bytes[1] === 0x50 &&
bytes[2] === 0x4e &&
bytes[3] === 0x47 &&
bytes[4] === 0x0d &&
bytes[5] === 0x0a &&
bytes[6] === 0x1a &&
bytes[7] === 0x0a
) {
resolvedMime = 'image/png';
} else if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x38) {
resolvedMime = 'image/gif';
} else if (
bytes[0] === 0x52 &&
bytes[1] === 0x49 &&
bytes[2] === 0x46 &&
bytes[3] === 0x46 &&
bytes[8] === 0x57 &&
bytes[9] === 0x45 &&
bytes[10] === 0x42 &&
bytes[11] === 0x50
) {
resolvedMime = 'image/webp';
} else {
return normalizeError(
`Image format not supported by vision models (header: ${headerMimeType || 'unknown'}). Image URL: ${imageUrl}`,
'get_photo_for_listing',
);
}
}
const base64 = Buffer.from(buffer).toString('base64');
return {
content: [
{
type: 'image',
data: base64,
mimeType: resolvedMime,
},
],
};
},
);
// ── get_current_date_ime ─────────────────────────────────────────────────────
server.tool('get_current_date_time', 'Returns the current date and time.', {}, () => {
return {