search_media
Find similar media in your indexed library by providing a URL or base64 content. Choose search depth from exact matches to comprehensive visual similarity analysis.
Instructions
Search for similar or matching media across the indexed library. Provide a media_url or base64 media to find matches. Tiers: exact (hash match), quick (perceptual hash), perceptual (visual similarity), compositional (scene structure), full (all tiers). Returns results immediately.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| media_url | No | Public URL of the media to search for | |
| media | No | Base64-encoded media content to search for | |
| type | No | Search tier — controls depth vs speed tradeoff. Default: perceptual | |
| tags | No | Restrict search to media with these tags | |
| limit | No | Maximum results to return (1-100, default: 20) |
Implementation Reference
- src/tools/search-media.ts:38-79 (handler)The async handler function that executes the search_media tool. It builds the request body from params (media_url, media, type, tags, limit), constructs the API path with query parameters, makes a POST request to /api/v1/search, and returns the formatted JSON result or error message.
async (params) => { try { const body: Record<string, unknown> = {}; if (params.media_url) body.media_url = params.media_url; if (params.media) body.media = params.media; if (params.type) body.type = params.type; if (params.tags) body.scope = { tags: params.tags }; const queryParams: Record<string, string | undefined> = {}; if (params.limit) queryParams.limit = String(params.limit); const path = "/api/v1/search" + (Object.keys(queryParams).length ? "?" + new URLSearchParams( Object.fromEntries( Object.entries(queryParams).filter( (e): e is [string, string] => e[1] !== undefined, ), ), ).toString() : ""); const result = await api.post(path, body); return { content: [ { type: "text" as const, text: JSON.stringify(result, null, 2) }, ], }; } catch (err) { return { content: [ { type: "text" as const, text: `Error: ${err instanceof Error ? err.message : String(err)}`, }, ], isError: true as const, }; } }, - src/tools/search-media.ts:13-37 (schema)Zod schema defining the input validation for search_media tool parameters: media_url (optional URL), media (optional base64), type (enum with 5 search tiers), tags (optional string array), and limit (optional int 1-100).
media_url: z .string() .url() .optional() .describe("Public URL of the media to search for"), media: z .string() .optional() .describe("Base64-encoded media content to search for"), type: z .enum(["exact", "quick", "perceptual", "compositional", "full"]) .optional() .describe("Search tier — controls depth vs speed tradeoff. Default: perceptual"), tags: z .array(z.string()) .optional() .describe("Restrict search to media with these tags"), limit: z .number() .int() .min(1) .max(100) .optional() .describe("Maximum results to return (1-100, default: 20)"), }, - src/tools/search-media.ts:5-81 (registration)The register function that registers the search_media tool with the MCP server, including the tool name, description, input schema, and handler callback.
export function register(server: McpServer, api: ApiClient): void { server.tool( "search_media", "Search for similar or matching media across the indexed library. " + "Provide a media_url or base64 media to find matches. " + "Tiers: exact (hash match), quick (perceptual hash), perceptual (visual similarity), " + "compositional (scene structure), full (all tiers). Returns results immediately.", { media_url: z .string() .url() .optional() .describe("Public URL of the media to search for"), media: z .string() .optional() .describe("Base64-encoded media content to search for"), type: z .enum(["exact", "quick", "perceptual", "compositional", "full"]) .optional() .describe("Search tier — controls depth vs speed tradeoff. Default: perceptual"), tags: z .array(z.string()) .optional() .describe("Restrict search to media with these tags"), limit: z .number() .int() .min(1) .max(100) .optional() .describe("Maximum results to return (1-100, default: 20)"), }, async (params) => { try { const body: Record<string, unknown> = {}; if (params.media_url) body.media_url = params.media_url; if (params.media) body.media = params.media; if (params.type) body.type = params.type; if (params.tags) body.scope = { tags: params.tags }; const queryParams: Record<string, string | undefined> = {}; if (params.limit) queryParams.limit = String(params.limit); const path = "/api/v1/search" + (Object.keys(queryParams).length ? "?" + new URLSearchParams( Object.fromEntries( Object.entries(queryParams).filter( (e): e is [string, string] => e[1] !== undefined, ), ), ).toString() : ""); const result = await api.post(path, body); return { content: [ { type: "text" as const, text: JSON.stringify(result, null, 2) }, ], }; } catch (err) { return { content: [ { type: "text" as const, text: `Error: ${err instanceof Error ? err.message : String(err)}`, }, ], isError: true as const, }; } }, ); } - src/index.ts:10-10 (registration)Import statement for the search_media register function from tools/search-media.js.
import { register as searchMedia } from "./tools/search-media.js"; - src/index.ts:50-50 (registration)Registration call that invokes searchMedia(server, api) to register the search_media tool with the MCP server.
searchMedia(server, api);