- Complete project setup with TypeScript, Jest, MCPB manifest - Implement foundational infrastructure (CLI executor, logger, error handler) - Add 9 file operation tools for User Story 1 - Full MCP protocol compliance with stdio transport - Input validation and sanitization for security - Comprehensive error handling with actionable messages - Constitutional compliance: all 6 principles satisfied MVP includes: - obsidian_create_note, read, append, prepend, delete, move, rename, open, file_info - Zod validation schemas for all parameters - 30s timeout configuration with per-command overrides - Stderr-only logging with sanitized output - Graceful shutdown handling Build: ✅ 0 errors, 0 vulnerabilities Tasks: 48/167 complete (MVP milestone) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
11 KiB
Data Model: Obsidian MCP Bundle
Phase: 1 (Design & Contracts)
Date: 2026-03-22
Purpose: Define data structures and their relationships
Overview
This MCP bundle operates on Obsidian's data model (vault, notes, properties, tasks, links, tags). The bundle does NOT maintain its own data store - it provides a typed interface layer over Obsidian CLI operations.
Core Entities
Vault
Description: An Obsidian knowledge base (collection of markdown files and folders)
Attributes:
name(string, required): Vault identifier for user configurationpath(string, readonly): Filesystem path to vault rootfileCount(number, readonly): Total number of filesfolderCount(number, readonly): Total number of folderssize(number, readonly): Total size in bytes
Validation Rules:
- Name must match an existing Obsidian vault
- Path must be accessible with read/write permissions
State Transitions: N/A (vault state managed by Obsidian)
CLI Mapping: obsidian vault, obsidian vaults
Note
Description: A markdown file within the vault
Attributes:
name(string, required): File name without extension OR exact pathpath(string, readonly): Full path relative to vault rootcontent(string): File contents (markdown text)size(number, readonly): File size in bytescreated(datetime, readonly): Creation timestampmodified(datetime, readonly): Last modification timestampproperties(Property[], readonly): Frontmatter metadata
Validation Rules:
- Name must be valid filename (no path separators in name-only mode)
- Path must be within vault (no directory traversal)
- Name ambiguity: If multiple files match name, require exact path
- Content must be valid UTF-8 text
Relationships:
- HAS_MANY Properties (frontmatter)
- HAS_MANY Tasks (checkbox items)
- HAS_MANY Links (outgoing)
- REFERENCED_BY Links (backlinks)
- HAS_MANY Tags
State Transitions:
- Created → Exists
- Exists → Modified (content/properties changed)
- Exists → Deleted (moved to trash or permanent delete)
- Deleted → Restored (from history)
CLI Mapping: obsidian create, obsidian read, obsidian file, obsidian delete
Property
Description: Frontmatter metadata key-value pair
Attributes:
name(string, required): Property key (alphanumeric, underscores)value(string|number|boolean|date|array, required): Property valuetype(enum, readonly): text|number|checkbox|date|datetime|listfile(string, required): Note path containing this property
Validation Rules:
- Name must be valid YAML key
- Value must match type (enforced by Obsidian)
- List values must be array of strings
- Date/datetime must be ISO 8601 format
Relationships:
- BELONGS_TO Note
CLI Mapping: obsidian property:set, obsidian property:read, obsidian property:remove, obsidian properties
Task
Description: Markdown checkbox item with status tracking
Attributes:
ref(string, required): Task reference in format "path:line"file(string, required): Note path containing taskline(number, required): Line number in file (1-indexed)text(string, readonly): Task descriptionstatus(string, required): Empty (""), "x" (done), or custom characterdone(boolean, computed): True if status is "x"
Validation Rules:
- Line must exist in file
- Line must contain checkbox syntax:
- [ ]or- [x]or- [char] - Status character must be single character or empty
Relationships:
- BELONGS_TO Note
- MAY_REFERENCE other Notes (via wikilinks in task text)
State Transitions:
- todo (status="") → done (status="x")
- done (status="x") → todo (status="")
- Any status → custom status (status="char")
CLI Mapping: obsidian tasks, obsidian task
Link
Description: Connection between notes (wikilink or markdown link)
Attributes:
source(string, required): Source note pathtarget(string, required): Target note name or pathtype(enum, readonly): wikilink|markdown|unresolvedcount(number, optional): Number of occurrences (for aggregation)resolved(boolean, readonly): False if target doesn't exist
Validation Rules:
- Source must be existing note
- Target format depends on type (wikilink uses
[[name]], markdown uses[text](path))
Relationships:
- ORIGINATES_FROM Note (source)
- POINTS_TO Note (target, if resolved)
CLI Mapping: obsidian links, obsidian backlinks, obsidian unresolved
Tag
Description: Categorization marker (e.g., #project, #important)
Attributes:
name(string, required): Tag without # prefixcount(number, optional): Number of occurrences in vaultfiles(string[], optional): Files containing this tag
Validation Rules:
- Name must start with letter, can contain letters/numbers/hyphens/underscores
- Case-sensitive
- Nested tags use slash (e.g., "project/active")
Relationships:
- APPEARS_IN many Notes
CLI Mapping: obsidian tags, obsidian tag
Alias
Description: Alternative name for a note (defined in frontmatter)
Attributes:
alias(string, required): Alternative namefile(string, required): Note path this alias refers to
Validation Rules:
- Alias must be defined in note's frontmatter
aliasesproperty
Relationships:
- BELONGS_TO Note
CLI Mapping: obsidian aliases
Template
Description: Reusable note template
Attributes:
name(string, required): Template name/pathcontent(string, readonly): Template content (may include variables)resolved(string, optional): Template with variables resolved
Validation Rules:
- Name must match existing template file
- Variables use {{variable}} syntax
CLI Mapping: obsidian templates, obsidian template:read, obsidian template:insert
DailyNote
Description: Special note type for daily journaling
Attributes:
date(date, required): Date for the daily notepath(string, readonly): Path to daily note filecontent(string): Daily note content
Validation Rules:
- Date must be valid ISO date (YYYY-MM-DD)
- Path follows Obsidian's daily note configuration
Relationships:
- IS_A Note (special type)
State Transitions:
- Not created → Created (on first daily:append or daily:prepend)
CLI Mapping: obsidian daily, obsidian daily:read, obsidian daily:append, obsidian daily:prepend, obsidian daily:path
Plugin
Description: Obsidian plugin (core or community)
Attributes:
id(string, required): Plugin identifiername(string, readonly): Display nameversion(string, readonly): Plugin versionenabled(boolean, readonly): Activation statustype(enum, readonly): core|community
Validation Rules:
- ID must match installed plugin
CLI Mapping: obsidian plugins, obsidian plugins:enabled, obsidian plugin
Theme
Description: Obsidian visual theme
Attributes:
name(string, required): Theme nameversion(string, readonly): Theme versionactive(boolean, readonly): Whether currently active
Validation Rules:
- Name must match installed theme or be empty string for default
CLI Mapping: obsidian themes, obsidian theme, obsidian theme:set
Bookmark
Description: Saved reference to file, folder, search, or URL
Attributes:
title(string, required): Bookmark display nametype(enum, readonly): file|folder|search|urltarget(string, required): File path, folder path, search query, or URL
Validation Rules:
- File/folder targets must exist
- URLs must be valid HTTP/HTTPS
- Search queries must be non-empty
CLI Mapping: obsidian bookmarks, obsidian bookmark
Tool Request/Response Schemas
Each MCP tool accepts parameters matching entity attributes and returns structured results.
Common Patterns
Success Response:
{
success: true,
data: { /* entity or array of entities */ }
}
Error Response:
{
success: false,
error: {
code: string, // e.g., "NOTE_NOT_FOUND"
message: string, // Human-readable error
details?: unknown // Additional context
}
}
Parameter Validation
All tool parameters are validated using Zod schemas before CLI execution:
// Example: Create note parameters
{
name: string (1-255 chars),
path?: string (valid path),
content?: string,
template?: string,
overwrite?: boolean,
open?: boolean,
newtab?: boolean
}
CLI Output Parsing
Obsidian CLI returns data in various formats. The bundle normalizes to JSON:
Format Mapping
- JSON output → Direct parse
- TSV output → Parse to array of objects
- CSV output → Parse to array of objects
- Text output → Wrap in
{ raw: string }object - Empty output → Return
{ success: true }
Special Cases
- Ambiguous file names: CLI returns multiple paths → Error with path list
- File not found: CLI exits with error code → Map to NOT_FOUND error
- Obsidian not running: CLI connection fails → Map to OBSIDIAN_NOT_RUNNING error
Data Flow
AI Assistant
↓
MCP Tool Call (JSON-RPC)
↓
Input Validation (Zod)
↓
Parameter Sanitization
↓
CLI Command Construction
↓
Obsidian CLI Execution (spawn with timeout)
↓
Output Parsing (JSON/TSV/CSV/Text)
↓
Error Mapping (if needed)
↓
Structured Response (JSON)
↓
MCP Tool Result (JSON-RPC)
↓
AI Assistant
Concurrency & State
- No shared state: Each tool call is independent
- Concurrent file modifications: Rely on Obsidian's built-in conflict detection (per clarification)
- Vault state: Managed entirely by Obsidian (bundle is stateless)
- CLI commands: Sequential execution per tool call (no parallel CLI commands from single call)
Error Handling
Errors are categorized and mapped to user-friendly messages:
| Error Category | Code | Message Pattern |
|---|---|---|
| Obsidian not running | OBSIDIAN_NOT_RUNNING | "Obsidian application is not running. Please start Obsidian and try again." |
| Vault not found | VAULT_NOT_FOUND | "Vault '{name}' not found. Check your vault name in MCP settings." |
| File not found | FILE_NOT_FOUND | "Note '{name}' not found. Use exact path or check spelling." |
| Ambiguous name | AMBIGUOUS_NAME | "Multiple notes named '{name}' found: {paths}. Please specify exact path." |
| Permission denied | PERMISSION_DENIED | "Cannot access {path}. Check file permissions." |
| Timeout | TIMEOUT | "Operation took too long (>30s). Try with smaller scope." |
| Validation error | VALIDATION_ERROR | "{field} {constraint}" (e.g., "name must be 1-255 characters") |
| CLI error | CLI_ERROR | "{original CLI error message}" (for unexpected errors) |