search_pages
Search Logseq pages by content or title, with optional filtering by tags and folder types to find specific information in your knowledge graph.
Instructions
페이지 내용/제목 검색. 태그 및 폴더 필터 지원
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | 검색어 | |
| tags | No | 태그 필터 (선택) | |
| folder | No | 폴더 필터 (선택) |
Implementation Reference
- src/index.ts:298-304 (handler)MCP tool handler for 'search_pages': parses arguments with schema and delegates to GraphService.searchPages method.case 'search_pages': { const { query, tags, folder } = SearchPagesSchema.parse(args); const results = await graph.searchPages(query, { tags, folder }); return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }], }; }
- src/index.ts:85-89 (schema)Zod schema defining input validation for search_pages tool: query (required), optional tags and folder filters.const SearchPagesSchema = z.object({ query: z.string().max(MAX_QUERY_LENGTH).describe('검색어'), tags: z.array(z.string().max(MAX_TAG_LENGTH)).max(MAX_TAGS_COUNT).optional().describe('태그 필터 (선택)'), folder: z.enum(['pages', 'journals']).optional().describe('폴더 필터 (선택)'), });
- src/index.ts:181-193 (registration)Registration of 'search_pages' tool in the TOOLS array, including name, description, and input schema for MCP server.{ name: 'search_pages', description: '페이지 내용/제목 검색. 태그 및 폴더 필터 지원', inputSchema: { type: 'object' as const, properties: { query: { type: 'string', description: '검색어' }, tags: { type: 'array', items: { type: 'string' }, description: '태그 필터 (선택)' }, folder: { type: 'string', enum: ['pages', 'journals'], description: '폴더 필터 (선택)' }, }, required: ['query'], }, },
- src/graph.ts:271-319 (helper)Core implementation of page search in GraphService: lists pages, applies filters, scans content line-by-line for query matches, returns results with context.async searchPages(query: string, options?: { tags?: string[]; folder?: string }): Promise<SearchResult[]> { // 보안: 검색어 길이 제한 (DoS 방지) if (query.length > 1000) { throw new Error('Search query too long (max 1000 characters)'); } const results: SearchResult[] = []; const pages = await this.listPages(options?.folder); const queryLower = query.toLowerCase(); for (const page of pages) { // Filter by tags if specified if (options?.tags && options.tags.length > 0) { const hasTag = options.tags.some(tag => page.tags.includes(tag)); if (!hasTag) continue; } // Search in content const filePath = join(this.graphPath, page.path); // 보안: TOCTOU 방지 - 심링크/하드링크 체크 try { await this.checkRegularFile(filePath); } catch { continue; // 심링크/하드링크/특수파일은 건너뜀 } const content = await readFile(filePath, 'utf-8'); const lines = content.split('\n'); const matches: SearchMatch[] = []; for (let i = 0; i < lines.length; i++) { if (lines[i].toLowerCase().includes(queryLower)) { matches.push({ line: i + 1, content: lines[i], context: lines.slice(Math.max(0, i - 1), i + 2).join('\n'), }); } } // Also match page name if (page.name.toLowerCase().includes(queryLower) || matches.length > 0) { results.push({ page, matches }); } } return results; }