- 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>
383 lines
11 KiB
Markdown
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) |
|