/** * MCP Outline PostgreSQL - Backlinks Tools * Note: backlinks is a VIEW, not a table - read-only * @author Descomplicar® | @link descomplicar.pt | @copyright 2026 */ import { Pool } from 'pg'; import { BaseTool, ToolResponse, PaginationArgs } from '../types/tools.js'; import { validatePagination, isValidUUID } from '../utils/security.js'; interface BacklinkListArgs extends PaginationArgs { document_id?: string; reverse_document_id?: string; } /** * backlinks.list - List document backlinks */ const listBacklinks: BaseTool = { name: 'outline_backlinks_list', description: 'List backlinks between documents. Shows which documents link to which. Backlinks is a view (read-only).', inputSchema: { type: 'object', properties: { document_id: { type: 'string', description: 'Filter by source document ID (UUID) - documents that link TO this', }, reverse_document_id: { type: 'string', description: 'Filter by target document ID (UUID) - documents that ARE LINKED FROM this', }, limit: { type: 'number', description: 'Maximum results (default: 25, max: 100)', }, offset: { type: 'number', description: 'Results to skip (default: 0)', }, }, }, handler: async (args, pgClient): Promise => { const { limit, offset } = validatePagination(args.limit, args.offset); const conditions: string[] = []; const params: any[] = []; let paramIndex = 1; if (args.document_id) { if (!isValidUUID(args.document_id)) throw new Error('Invalid document_id format'); conditions.push(`b."documentId" = $${paramIndex++}`); params.push(args.document_id); } if (args.reverse_document_id) { if (!isValidUUID(args.reverse_document_id)) throw new Error('Invalid reverse_document_id format'); conditions.push(`b."reverseDocumentId" = $${paramIndex++}`); params.push(args.reverse_document_id); } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; const result = await pgClient.query( ` SELECT b.id, b."documentId", b."reverseDocumentId", b."userId", b."createdAt", b."updatedAt", d.title as "documentTitle", rd.title as "reverseDocumentTitle", u.name as "userName" FROM backlinks b LEFT JOIN documents d ON b."documentId" = d.id LEFT JOIN documents rd ON b."reverseDocumentId" = rd.id LEFT JOIN users u ON b."userId" = u.id ${whereClause} ORDER BY b."createdAt" DESC LIMIT $${paramIndex++} OFFSET $${paramIndex} `, [...params, limit, offset] ); return { content: [{ type: 'text', text: JSON.stringify({ data: result.rows, pagination: { limit, offset, total: result.rows.length }, note: 'Backlinks is a read-only view. Links are automatically detected from document content.', }, null, 2), }], }; }, }; export const backlinksTools: BaseTool[] = [listBacklinks];