@mixio-pro/kalaasetu-mcp
Version:
A powerful Model Context Protocol server providing AI tools for content generation and analysis
191 lines (163 loc) • 6.94 kB
text/typescript
import { z } from "zod";
export const perplexityImages = {
name: "perplexityImages",
description: "Searches for images using the Perplexity API. Returns a formatted text response that includes a summary and a numbered list of image URLs with citations mapped to the text.",
parameters: z.object({
query: z.string().describe("The search query for images."),
image_domain_filter: z.array(z.string()).optional().describe("A list of domains to include or exclude. To exclude, prefix with '-'. E.g., ['wikimedia.org', '-gettyimages.com']."),
image_format_filter: z.array(z.string()).optional().describe("A list of allowed image formats. E.g., ['jpg', 'png', 'gif']."),
}),
execute: async (args: { query: string; image_domain_filter?: string[]; image_format_filter?: string[] }) => {
const apiKey = process.env.PERPLEXITY_API_KEY;
if (!apiKey) {
throw new Error("PERPLEXITY_API_KEY environment variable is not set.");
}
const url = "https://api.perplexity.ai/chat/completions";
const headers = {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"accept": "application/json"
};
const payload: any = {
model: "sonar",
messages: [
{ role: "user", content: `Show me images of ${args.query}` }
],
return_images: true
};
if (args.image_domain_filter) {
payload.image_domain_filter = args.image_domain_filter;
}
if (args.image_format_filter) {
payload.image_format_filter = args.image_format_filter;
}
const res = await fetch(url, {
method: "POST",
headers: headers,
body: JSON.stringify(payload),
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Perplexity API request failed: ${res.status} ${text}`);
}
const data: any = await res.json();
let content = data.choices?.[0]?.message?.content;
const images = data.images;
const citations = data.citations;
if (!images || images.length === 0) {
return `No direct image URLs found in the API response. The text content was: ${content}`;
}
// Create a map of origin_url -> new 1-based index
const originUrlToImageIndex: { [key: string]: number } = {};
images.forEach((img: any, index: number) => {
if (img.origin_url) {
originUrlToImageIndex[img.origin_url] = index + 1;
}
});
// Create a map of old citation index -> new image index
const oldToNewCitationMap: { [key: number]: number } = {};
if (citations && Array.isArray(citations)) {
citations.forEach((citationUrl: string, index: number) => {
if (originUrlToImageIndex[citationUrl]) {
oldToNewCitationMap[index + 1] = originUrlToImageIndex[citationUrl];
}
});
}
// Replace citations in the content
if (content && typeof content === 'string') {
content = content.replace(/\[(\d+)\]/g, (match: string, oldIndexStr: string) => {
const oldIndex = parseInt(oldIndexStr, 10);
const newIndex = oldToNewCitationMap[oldIndex];
if (newIndex) {
return `[${newIndex}]`;
}
return ''; // Remove citation if it doesn't correspond to an image
}).replace(/(\s\s+)/g, ' ').trim(); // Clean up extra spaces
}
// Build the final formatted output
let output = content + "\n\n--- Images ---\n";
images.forEach((img: any, index: number) => {
output += `${index + 1}. ${img.image_url}\n (Source: ${img.origin_url})\n`;
});
return output;
},
};
export const perplexityVideos = {
name: "perplexityVideos",
description: "Searches for videos using the Perplexity API. Returns a formatted text response that includes a summary and a numbered list of video URLs with citations mapped to the text.",
parameters: z.object({
query: z.string().describe("The search query for videos."),
search_domain_filter: z.array(z.string()).optional().describe("A list of domains to limit the search to (e.g., ['youtube.com']). Use a '-' prefix to exclude a domain."),
}),
execute: async (args: { query: string; search_domain_filter?: string[] }) => {
const apiKey = process.env.PERPLEXITY_API_KEY;
if (!apiKey) {
throw new Error("PERPLEXITY_API_KEY environment variable is not set.");
}
const url = "https://api.perplexity.ai/chat/completions";
const headers = {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"accept": "application/json"
};
const payload: any = {
model: "sonar-pro",
messages: [
{ role: "user", content: `Show me videos of ${args.query}` }
],
media_response: { overrides: { return_videos: true } }
};
if (args.search_domain_filter) {
payload.search_domain_filter = args.search_domain_filter;
}
const res = await fetch(url, {
method: "POST",
headers: headers,
body: JSON.stringify(payload),
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Perplexity API request failed: ${res.status} ${text}`);
}
const data: any = await res.json();
let content = data.choices?.[0]?.message?.content;
const videos = data.videos;
const citations = data.citations;
if (!videos || videos.length === 0) {
return `No direct video URLs found in the API response. Full API Response: ${JSON.stringify(data, null, 2)}`;
}
// Create a map of video url -> new 1-based index
const urlToVideoIndex: { [key: string]: number } = {};
videos.forEach((video: any, index: number) => {
if (video.url) {
urlToVideoIndex[video.url] = index + 1;
}
});
// Create a map of old citation index -> new video index
const oldToNewCitationMap: { [key: number]: number } = {};
if (citations && Array.isArray(citations)) {
citations.forEach((citationUrl: string, index: number) => {
if (urlToVideoIndex[citationUrl]) {
oldToNewCitationMap[index + 1] = urlToVideoIndex[citationUrl];
}
});
}
// Replace citations in the content
if (content && typeof content === 'string') {
content = content.replace(/\[(\d+)\]/g, (match: string, oldIndexStr: string) => {
const oldIndex = parseInt(oldIndexStr, 10);
const newIndex = oldToNewCitationMap[oldIndex];
if (newIndex) {
return `[${newIndex}]`;
}
return ''; // Remove citation if it doesn't correspond to a video
}).replace(/(\s\s+)/g, ' ').trim(); // Clean up extra spaces
}
// Build the final formatted output
let output = content + "\n\n--- Videos ---\n";
videos.forEach((video: any, index: number) => {
output += `${index + 1}. ${video.url}\n`;
});
return output;
},
};