Files
Peter.Morton 622b28e42c feat: implement Obsidian MCP Bundle MVP (Phase 1-3)
- 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>
2026-03-22 11:21:38 -05:00

383 lines
11 KiB
Markdown

# 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 configuration
- `path` (string, readonly): Filesystem path to vault root
- `fileCount` (number, readonly): Total number of files
- `folderCount` (number, readonly): Total number of folders
- `size` (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 path
- `path` (string, readonly): Full path relative to vault root
- `content` (string): File contents (markdown text)
- `size` (number, readonly): File size in bytes
- `created` (datetime, readonly): Creation timestamp
- `modified` (datetime, readonly): Last modification timestamp
- `properties` (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 value
- `type` (enum, readonly): text|number|checkbox|date|datetime|list
- `file` (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 task
- `line` (number, required): Line number in file (1-indexed)
- `text` (string, readonly): Task description
- `status` (string, required): Empty (""), "x" (done), or custom character
- `done` (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 path
- `target` (string, required): Target note name or path
- `type` (enum, readonly): wikilink|markdown|unresolved
- `count` (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 # prefix
- `count` (number, optional): Number of occurrences in vault
- `files` (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 name
- `file` (string, required): Note path this alias refers to
**Validation Rules**:
- Alias must be defined in note's frontmatter `aliases` property
**Relationships**:
- BELONGS_TO Note
**CLI Mapping**: `obsidian aliases`
---
### Template
**Description**: Reusable note template
**Attributes**:
- `name` (string, required): Template name/path
- `content` (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 note
- `path` (string, readonly): Path to daily note file
- `content` (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 identifier
- `name` (string, readonly): Display name
- `version` (string, readonly): Plugin version
- `enabled` (boolean, readonly): Activation status
- `type` (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 name
- `version` (string, readonly): Theme version
- `active` (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 name
- `type` (enum, readonly): file|folder|search|url
- `target` (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**:
```typescript
{
success: true,
data: { /* entity or array of entities */ }
}
```
**Error Response**:
```typescript
{
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:
```typescript
// 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) |