get_recent
Retrieve recently modified notes from your Obsidian vault. Filter by folder, tags, or frontmatter to find specific content quickly.
Instructions
Get recently modified notes.
Args: limit: Number of recent notes to return (default 20). folder: Optional folder prefix to filter (e.g. "Projects/"). tags: Optional list of tag names; only notes carrying ALL listed tags match (e.g. ["meeting"]). frontmatter: Optional dict of frontmatter key/value pairs; strict type match (e.g. {"status": "active"}).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| folder | No | ||
| tags | No | ||
| frontmatter | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/mcp_server/tools.py:209-238 (handler)Core implementation of get_recent tool. Queries NoteMetadata ordered by modified_at desc, applies folder/tags/frontmatter filters, and returns formatted markdown list.
@_tracked("get_recent", ["limit", "folder", "tags", "frontmatter"]) async def get_recent_impl( limit: int = 20, folder: str | None = None, tags: list[str] | None = None, frontmatter: dict | None = None, ) -> str: """Recently modified notes.""" from sqlalchemy import select from src.models.db import NoteMetadata uid = current_user_id.get() async with async_session() as session: query = select(NoteMetadata).order_by(NoteMetadata.modified_at.desc()) query = apply_note_filters( query, folder=folder, tags=tags, frontmatter=frontmatter, user_id=uid ) query = query.limit(limit) result = await session.execute(query) notes = result.scalars().all() if not notes: return "No recent notes found" lines = [f"Last {len(notes)} modified notes:\n"] for n in notes: mod = n.modified_at.strftime("%Y-%m-%d %H:%M") if n.modified_at else "unknown" tags_str = f" [{', '.join(n.tags)}]" if n.tags else "" lines.append(f"- `{n.file_path}` — {n.title}{tags_str} (modified {mod})") return "\n".join(lines) - src/mcp_server/server.py:108-127 (registration)MCP tool registration for 'get_recent' via @mcp.tool() decorator. Defines the tool schema (limit, folder, tags, frontmatter params) and delegates to get_recent_impl.
@mcp.tool() async def get_recent( limit: int = 20, folder: str | None = None, tags: list[str] | None = None, frontmatter: dict | None = None, ) -> str: """Get recently modified notes. Args: limit: Number of recent notes to return (default 20). folder: Optional folder prefix to filter (e.g. "Projects/"). tags: Optional list of tag names; only notes carrying ALL listed tags match (e.g. ["meeting"]). frontmatter: Optional dict of frontmatter key/value pairs; strict type match (e.g. {"status": "active"}). """ return await get_recent_impl( limit=limit, folder=folder, tags=tags, frontmatter=frontmatter ) - src/mcp_server/server.py:14-14 (helper)Import of get_recent_impl from src.mcp_server.tools into the server module.
get_recent_impl, - src/mcp_server/tools.py:73-90 (helper)The _tracked decorator applied to get_recent_impl, which logs usage and timing to the usage_logs table.
def _tracked(tool_name: str, param_keys: list[str]): """Decorator that times the call and logs it to usage_logs.""" def decorator(fn): @wraps(fn) async def wrapper(*args, **kwargs): start = time.monotonic() result = await fn(*args, **kwargs) duration_ms = int((time.monotonic() - start) * 1000) params = {} for i, key in enumerate(param_keys): if i < len(args): params[key] = args[i] elif key in kwargs: params[key] = kwargs[key] await _log_usage(tool_name, _truncate_params(params), duration_ms, len(str(result))) return result return wrapper return decorator - src/services/filters.py:17-46 (helper)apply_note_filters helper used by get_recent_impl to apply folder prefix, tag containment, frontmatter containment, and user_id filters to the SQL query.
def apply_note_filters( stmt: Select, *, folder: str | None = None, tags: list[str] | None = None, frontmatter: dict | None = None, user_id: int | None = None, ) -> Select: """Append optional `folder`, `tags`, `frontmatter`, `user_id` predicates to a select over NoteMetadata. - `folder`: prefix match on `file_path`. LIKE wildcards (`%`, `_`, `\\`) are escaped. - `tags`: ARRAY containment (`notes_metadata.tags @> ARRAY[...]`). AND semantics. - `frontmatter`: JSONB containment (`notes_metadata.frontmatter @> :json`). Strict types. - `user_id`: scope to one user. `None` (single-user mode / unset) means no filter is appended, so existing NULL-user rows are returned. `int` adds `.where(NoteMetadata.user_id == user_id)`. None or empty argument means "no filter" — the predicate is not appended. """ if folder: escaped = _escape_like(folder) stmt = stmt.where(NoteMetadata.file_path.like(f"{escaped}%", escape="\\")) if tags: stmt = stmt.where(NoteMetadata.tags.contains(tags)) if frontmatter: stmt = stmt.where(NoteMetadata.frontmatter.contains(frontmatter)) if user_id is not None: stmt = stmt.where(NoteMetadata.user_id == user_id) return stmt