List Attachments
list_attachmentsList all non-markdown attachments by extension, returning sorted relative paths and per-extension counts for auditing assets or finding duplicates.
Instructions
Enumerate every non-markdown file in the vault — images, PDFs, audio/video clips, anything pasted in beyond notes/canvases/Bases. Returns a sorted list of relative paths plus a per-extension count summary. Use to audit assets, find duplicates by name, or pick targets for find_unused_attachments.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| extension | No | Restrict to one extension (e.g., 'png' or '.png'). Omit for every attachment. | |
| limit | No | Maximum number of attachment paths to return (1-10000, default: 200). Total counts are still reported. |
Implementation Reference
- src/tools/attachments.ts:103-165 (registration)Tool 'list_attachments' is registered on the McpServer via registerTool() with Zod input schema (optional 'extension' filter and 'limit' with default 200).
export function registerAttachmentTools(server: McpServer, vaultPath: string): void { server.registerTool( "list_attachments", { title: "List Attachments", description: "Enumerate every non-markdown file in the vault — images, PDFs, audio/video clips, anything pasted in beyond notes/canvases/Bases. Returns a sorted list of relative paths plus a per-extension count summary. Use to audit assets, find duplicates by name, or pick targets for find_unused_attachments.", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false, }, inputSchema: { extension: z .string() .optional() .describe("Restrict to one extension (e.g., 'png' or '.png'). Omit for every attachment."), limit: z .number() .int() .min(1) .max(10000) .optional() .default(200) .describe("Maximum number of attachment paths to return (1-10000, default: 200). Total counts are still reported."), }, }, async ({ extension, limit }) => { try { const all = await listAttachments(vaultPath); const filtered = extension ? all.filter((p) => { const ext = (extension.startsWith(".") ? extension : `.${extension}`).toLowerCase(); return p.toLowerCase().endsWith(ext); }) : all; if (filtered.length === 0) { return textResult( extension ? `No attachments with extension "${extension}".` : "No attachments in this vault.", ); } const truncated = filtered.slice(0, limit); const lines: string[] = [ `${filtered.length} attachment(s)${extension ? ` (.${extension.replace(/^\./, "")})` : ""}${filtered.length > limit ? ` (showing first ${limit})` : ""}:`, "", ]; const summary = summarizeByExtension(filtered); if (summary.size > 1) { lines.push("By extension:"); const entries = Array.from(summary.entries()).sort((a, b) => b[1] - a[1]); for (const [ext, n] of entries) lines.push(` ${ext} ${n}`); lines.push(""); } for (const p of truncated) lines.push(`- ${p}`); return textResult(lines.join("\n")); } catch (err) { log.error("list_attachments failed", { tool: "list_attachments", err: err as Error }); return errorResult(`Error listing attachments: ${sanitizeError(err)}`); } }, ); - src/tools/attachments.ts:115-129 (schema)Input schema defines 'extension' (optional string) and 'limit' (optional int, 1-10000, default 200) inputs.
inputSchema: { extension: z .string() .optional() .describe("Restrict to one extension (e.g., 'png' or '.png'). Omit for every attachment."), limit: z .number() .int() .min(1) .max(10000) .optional() .default(200) .describe("Maximum number of attachment paths to return (1-10000, default: 200). Total counts are still reported."), }, }, - src/tools/attachments.ts:130-164 (handler)Handler function: calls listAttachments() from vault lib, optionally filters by extension, truncates to limit, groups by extension for a summary, and returns text result.
async ({ extension, limit }) => { try { const all = await listAttachments(vaultPath); const filtered = extension ? all.filter((p) => { const ext = (extension.startsWith(".") ? extension : `.${extension}`).toLowerCase(); return p.toLowerCase().endsWith(ext); }) : all; if (filtered.length === 0) { return textResult( extension ? `No attachments with extension "${extension}".` : "No attachments in this vault.", ); } const truncated = filtered.slice(0, limit); const lines: string[] = [ `${filtered.length} attachment(s)${extension ? ` (.${extension.replace(/^\./, "")})` : ""}${filtered.length > limit ? ` (showing first ${limit})` : ""}:`, "", ]; const summary = summarizeByExtension(filtered); if (summary.size > 1) { lines.push("By extension:"); const entries = Array.from(summary.entries()).sort((a, b) => b[1] - a[1]); for (const [ext, n] of entries) lines.push(` ${ext} ${n}`); lines.push(""); } for (const p of truncated) lines.push(`- ${p}`); return textResult(lines.join("\n")); } catch (err) { log.error("list_attachments failed", { tool: "list_attachments", err: err as Error }); return errorResult(`Error listing attachments: ${sanitizeError(err)}`); } }, - src/lib/vault.ts:809-822 (helper)Core helper that walks the vault directory tree excluding .md, .canvas, .base files and hidden/excluded directories, returning all attachment paths sorted.
export async function listAttachments( vaultPath: string, ): Promise<string[]> { const entries = await walkVaultExcluding( await getRealVaultRoot(vaultPath), [".md", ".canvas", ".base"], ); const out: string[] = []; for (const rel of entries) { if (isExcluded(rel)) continue; out.push(rel); } return out.sort(); }