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>
This commit is contained in:
382
specs/001-obsidian-mcp-bundle/data-model.md
Normal file
382
specs/001-obsidian-mcp-bundle/data-model.md
Normal file
@@ -0,0 +1,382 @@
|
||||
# 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) |
|
||||
Reference in New Issue
Block a user