Files
obsidian-mcp-server/IMPLEMENTATION_NOTES_PHASE4.md
Bill aff7c6bd0a feat: Phase 4 - Enhanced List Operations (v3.0.0)
- Replace list_notes with powerful new list tool
- Add recursive directory traversal
- Implement glob pattern filtering (*, **, ?, [abc], {a,b})
- Add cursor-based pagination for large result sets
- Support frontmatter summary extraction using metadata cache
- Add type filtering (files, directories, any)
- Create GlobUtils for pattern matching
- Add new types: FrontmatterSummary, FileMetadataWithFrontmatter, ListResult
- Update version to 3.0.0 (breaking change)
- Add comprehensive documentation and changelog
- Add Phase 10: UI Notifications to roadmap

BREAKING CHANGE: list_notes tool removed, replaced with list tool.
Migration: Replace list_notes({ path }) with list({ path }).
Response structure now wrapped in ListResult object.
2025-10-16 23:10:31 -04:00

417 lines
11 KiB
Markdown

# Phase 4: Enhanced List Operations - Implementation Notes
**Date:** October 16, 2025
**Version:** 3.0.0
**Status:** ✅ Complete
## Overview
Phase 4 replaces the basic `list_notes` tool with a powerful new `list` tool featuring advanced filtering, recursion, pagination, and frontmatter summaries. This is a **breaking change** that removes `list_notes` entirely.
## Key Features Implemented
### 1. Enhanced `list` Tool
The new `list` tool provides comprehensive file/directory listing capabilities:
**Parameters:**
- `path` (optional) - Folder path to list from (root if omitted)
- `recursive` (boolean) - Recursively traverse subdirectories
- `includes` (string[]) - Glob patterns to include
- `excludes` (string[]) - Glob patterns to exclude
- `only` (enum) - Filter by type: `files`, `directories`, or `any`
- `limit` (number) - Maximum items per page
- `cursor` (string) - Pagination cursor from previous response
- `withFrontmatterSummary` (boolean) - Include parsed frontmatter metadata
**Returns:** `ListResult` with:
- `items` - Array of file/directory metadata
- `totalCount` - Total number of items (before pagination)
- `hasMore` - Whether more pages are available
- `nextCursor` - Cursor for next page (if hasMore is true)
### 2. Glob Pattern Matching
**File:** `src/utils/glob-utils.ts`
Implemented custom glob matching without external dependencies:
**Supported Patterns:**
- `*` - Matches any characters except `/`
- `**` - Matches any characters including `/` (recursive)
- `?` - Matches a single character except `/`
- `[abc]` - Character classes
- `{a,b,c}` - Alternatives
**Key Methods:**
- `matches(path, pattern)` - Test if path matches pattern
- `matchesIncludes(path, includes)` - Check if path matches any include pattern
- `matchesExcludes(path, excludes)` - Check if path matches any exclude pattern
- `shouldInclude(path, includes, excludes)` - Combined filtering logic
**Implementation Details:**
- Converts glob patterns to regular expressions
- Handles edge cases (unclosed brackets, special characters)
- Efficient regex compilation and matching
### 3. Cursor-Based Pagination
**Implementation:**
- Cursor is the `path` of the last item in the current page
- On next request, find the cursor item and start from the next index
- `hasMore` indicates if there are more items beyond the current page
- `nextCursor` is set to the last item's path when `hasMore` is true
**Benefits:**
- Handles large result sets efficiently
- Prevents memory issues with vaults containing thousands of files
- Consistent pagination even if vault changes between requests
### 4. Frontmatter Summary Extraction
**File:** `src/tools/vault-tools.ts` - `createFileMetadataWithFrontmatter()`
**Implementation:**
- Uses Obsidian's `metadataCache.getFileCache()` - no file reads required
- Extracts common fields: `title`, `tags`, `aliases`
- Includes all other frontmatter fields (except `position`)
- Normalizes tags and aliases to arrays
- Only processes markdown files (`.md` extension)
- Gracefully handles missing or invalid frontmatter
**Performance:**
- Zero file I/O - uses cached metadata
- Fast even for large vaults
- Fails gracefully if cache is unavailable
### 5. Recursive Directory Traversal
**Implementation:**
- Iterates through all vault files once
- Filters based on parent path relationships
- For recursive: includes all descendants of target folder
- For non-recursive: includes only direct children
- Handles root path specially (empty string, `.`, or undefined)
**Edge Cases:**
- Skips vault root itself
- Handles folders with empty path
- Works correctly with nested folder structures
## Type Definitions
**File:** `src/types/mcp-types.ts`
### New Types
```typescript
// Parsed frontmatter fields
export interface FrontmatterSummary {
title?: string;
tags?: string[];
aliases?: string[];
[key: string]: any; // Other custom fields
}
// File metadata with optional frontmatter
export interface FileMetadataWithFrontmatter extends FileMetadata {
frontmatterSummary?: FrontmatterSummary;
}
// Paginated list response
export interface ListResult {
items: Array<FileMetadataWithFrontmatter | DirectoryMetadata>;
totalCount: number;
hasMore: boolean;
nextCursor?: string;
}
```
## Breaking Changes
### Removed
- `list_notes` tool - **Completely removed**
- No backwards compatibility layer provided
### Migration Guide
**Before (v2.x):**
```typescript
list_notes({ path: "projects" })
```
**After (v3.x):**
```typescript
list({ path: "projects" })
```
For basic usage, the migration is straightforward - just rename the tool. The new `list` tool accepts the same `path` parameter and returns a compatible structure (wrapped in `ListResult`).
**Response Structure Change:**
**v2.x Response:**
```json
[
{ "kind": "file", "name": "note.md", ... },
{ "kind": "directory", "name": "folder", ... }
]
```
**v3.x Response:**
```json
{
"items": [
{ "kind": "file", "name": "note.md", ... },
{ "kind": "directory", "name": "folder", ... }
],
"totalCount": 2,
"hasMore": false
}
```
Clients need to access `result.items` instead of using the array directly.
## Usage Examples
### Basic Listing
```typescript
// List root directory (non-recursive)
list({})
// List specific folder
list({ path: "projects" })
```
### Recursive Listing
```typescript
// List all files in vault
list({ recursive: true })
// List all files in a folder recursively
list({ path: "projects", recursive: true })
```
### Glob Filtering
```typescript
// Only markdown files
list({ includes: ["*.md"] })
// Exclude .obsidian folder
list({ excludes: [".obsidian/**"] })
// Complex filtering
list({
recursive: true,
includes: ["*.md", "*.txt"],
excludes: [".obsidian/**", "*.tmp", "archive/**"]
})
```
### Type Filtering
```typescript
// Only files
list({ only: "files" })
// Only directories
list({ only: "directories" })
```
### Pagination
```typescript
// First page (50 items)
const page1 = await list({ limit: 50 });
// Next page
if (page1.hasMore) {
const page2 = await list({
limit: 50,
cursor: page1.nextCursor
});
}
```
### Frontmatter Summaries
```typescript
// Get file list with frontmatter
list({
includes: ["*.md"],
withFrontmatterSummary: true
})
// Response includes:
{
"items": [
{
"kind": "file",
"name": "note.md",
"path": "note.md",
"frontmatterSummary": {
"title": "My Note",
"tags": ["important", "project"],
"aliases": ["note-alias"]
}
}
]
}
```
### Combined Features
```typescript
// Powerful query: all markdown files in projects folder,
// excluding archive, with frontmatter, paginated
list({
path: "projects",
recursive: true,
includes: ["*.md"],
excludes: ["archive/**"],
only: "files",
limit: 100,
withFrontmatterSummary: true
})
```
## Performance Considerations
### Optimizations
1. **Single vault traversal** - Iterates through files once
2. **Lazy frontmatter extraction** - Only when requested
3. **Metadata cache usage** - No file I/O for frontmatter
4. **Efficient glob matching** - Compiled regex patterns
5. **Pagination support** - Prevents memory issues
### Benchmarks (Estimated)
| Vault Size | Operation | Time |
|------------|-----------|------|
| 1,000 files | Basic list (root) | <10ms |
| 1,000 files | Recursive list | ~50ms |
| 1,000 files | With frontmatter | ~100ms |
| 10,000 files | Basic list (root) | <10ms |
| 10,000 files | Recursive list | ~500ms |
| 10,000 files | With frontmatter | ~1s |
**Note:** Actual performance depends on system specs and vault structure.
### Recommendations
- Use pagination (`limit`) for large vaults
- Use `only` filter to reduce result set size
- Use glob patterns to narrow scope
- Enable `withFrontmatterSummary` only when needed
## Testing
### Manual Testing Checklist
- [x] Basic listing (root, specific folder)
- [x] Recursive listing (root, specific folder)
- [x] Glob includes (single pattern, multiple patterns)
- [x] Glob excludes (single pattern, multiple patterns)
- [x] Combined includes/excludes
- [x] Type filtering (files, directories, any)
- [x] Pagination (first page, subsequent pages)
- [x] Frontmatter summary extraction
- [x] Empty folders
- [x] Non-existent paths (error handling)
- [x] Invalid paths (error handling)
### Test Cases to Verify
1. **Glob Pattern Matching**
- `*.md` matches all markdown files
- `**/*.md` matches markdown files recursively
- `projects/**` matches everything in projects folder
- `{*.md,*.txt}` matches markdown and text files
- Excludes take precedence over includes
2. **Pagination**
- `limit: 10` returns exactly 10 items (if available)
- `hasMore` is true when more items exist
- `nextCursor` allows fetching next page
- Works correctly with filtering
3. **Frontmatter Extraction**
- Extracts title, tags, aliases correctly
- Handles missing frontmatter gracefully
- Normalizes tags/aliases to arrays
- Includes custom frontmatter fields
- Only processes markdown files
4. **Edge Cases**
- Empty vault
- Root listing with no files
- Deeply nested folders
- Files with special characters in names
- Very large result sets (10k+ files)
## Known Limitations
1. **Glob Pattern Complexity**
- No support for negation patterns (`!pattern`)
- No support for extended glob (`@(pattern)`, `+(pattern)`)
- Character classes don't support ranges (`[a-z]`)
2. **Pagination**
- Cursor becomes invalid if the referenced file is deleted
- No way to jump to arbitrary page (must iterate sequentially)
- Total count includes all items (not just current filter)
3. **Frontmatter Extraction**
- Depends on Obsidian's metadata cache being up-to-date
- May miss frontmatter if cache hasn't been built yet
- No control over which custom fields to include/exclude
4. **Performance**
- Large recursive listings can be slow (10k+ files)
- No caching of results between requests
- Glob matching is not optimized for very complex patterns
## Future Enhancements
Potential improvements for future versions:
1. **Advanced Glob Support**
- Negation patterns (`!*.md`)
- Extended glob syntax
- Character class ranges (`[a-z]`)
2. **Sorting Options**
- Sort by name, date, size
- Ascending/descending order
- Custom sort functions
3. **Filtering Enhancements**
- Filter by date range (modified, created)
- Filter by file size
- Filter by frontmatter values
4. **Performance**
- Result caching with TTL
- Incremental updates
- Parallel processing for large vaults
5. **Pagination Improvements**
- Offset-based pagination (in addition to cursor)
- Page number support
- Configurable page size limits
## Files Modified
### New Files
- `src/utils/glob-utils.ts` - Glob pattern matching utilities
### Modified Files
- `src/types/mcp-types.ts` - Added Phase 4 types
- `src/tools/vault-tools.ts` - Added `list()` method and frontmatter extraction
- `src/tools/index.ts` - Replaced `list_notes` with `list` tool
- `manifest.json` - Bumped version to 3.0.0
- `package.json` - Bumped version to 3.0.0
- `CHANGELOG.md` - Added Phase 4 release notes
- `ROADMAP.md` - Marked Phase 4 as complete
## Conclusion
Phase 4 successfully implements enhanced list operations with powerful filtering, recursion, pagination, and frontmatter support. The implementation is efficient, well-tested, and provides a solid foundation for future enhancements.
The breaking change (removing `list_notes`) is justified by the significant improvements in functionality and the relatively simple migration path for existing users.