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:
2026-03-22 11:21:38 -05:00
parent e9e0112240
commit 622b28e42c
35 changed files with 5139 additions and 35 deletions

View File

@@ -0,0 +1,692 @@
# Tool Schemas
**Purpose**: Define input/output schemas for all MCP tools exposed by the Obsidian bundle
## Tool Naming Convention
All tools use prefix `obsidian_` followed by the operation:
- `obsidian_create_note`
- `obsidian_search`
- `obsidian_list_tasks`
This prevents naming conflicts with other MCP servers.
## Priority 1 Tools: File Operations (15 tools)
### obsidian_create_note
**Description**: Create a new note in the vault
**Input Schema**:
```json
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Note name (without .md extension) or exact path",
"minLength": 1,
"maxLength": 255
},
"path": {
"type": "string",
"description": "Optional: Directory path within vault"
},
"content": {
"type": "string",
"description": "Initial note content (markdown)"
},
"template": {
"type": "string",
"description": "Template name to use"
},
"overwrite": {
"type": "boolean",
"description": "Overwrite if file exists (default: false)"
},
"open": {
"type": "boolean",
"description": "Open file after creating (default: false)"
}
},
"required": ["name"]
}
```
**Output Example**:
```json
{
"success": true,
"data": {
"path": "Projects/Meeting Notes.md",
"created": true,
"opened": false
}
}
```
---
### obsidian_read_note
**Description**: Read the contents of a note
**Input Schema**:
```json
{
"type": "object",
"properties": {
"file": {
"type": "string",
"description": "Note name (wikilink-style resolution)"
},
"path": {
"type": "string",
"description": "Exact path to note"
}
},
"oneOf": [{"required": ["file"]}, {"required": ["path"]}]
}
```
**Output Example**:
```json
{
"success": true,
"data": {
"path": "Projects/Meeting Notes.md",
"content": "# Meeting Notes\n\n- Agenda item 1\n- Agenda item 2",
"size": 52,
"modified": "2026-03-22T15:30:00Z"
}
}
```
---
### obsidian_append_to_note
**Description**: Append content to a note
**Input Schema**:
```json
{
"type": "object",
"properties": {
"file": {"type": "string"},
"path": {"type": "string"},
"content": {
"type": "string",
"description": "Content to append (required)"
},
"inline": {
"type": "boolean",
"description": "Append without newline (default: false)"
}
},
"required": ["content"],
"oneOf": [{"required": ["file"]}, {"required": ["path"]}]
}
```
---
### obsidian_delete_note
**Description**: Delete a note (moves to trash by default)
**Input Schema**:
```json
{
"type": "object",
"properties": {
"file": {"type": "string"},
"path": {"type": "string"},
"permanent": {
"type": "boolean",
"description": "Skip trash, delete permanently (default: false)"
}
},
"oneOf": [{"required": ["file"]}, {"required": ["path"]}]
}
```
---
### obsidian_move_note
**Description**: Move or rename a note
**Input Schema**:
```json
{
"type": "object",
"properties": {
"file": {"type": "string"},
"path": {"type": "string"},
"to": {
"type": "string",
"description": "Destination folder or full path (required)"
}
},
"required": ["to"],
"oneOf": [{"required": ["file"]}, {"required": ["path"]}]
}
```
---
## Priority 2 Tools: Search & Discovery (20 tools)
### obsidian_search
**Description**: Search vault for text
**Input Schema**:
```json
{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query (required)"
},
"folder": {
"type": "string",
"description": "Limit search to specific folder"
},
"limit": {
"type": "number",
"description": "Maximum number of results"
},
"caseSensitive": {
"type": "boolean",
"description": "Case-sensitive search (default: false)"
}
},
"required": ["query"]
}
```
**Output Example**:
```json
{
"success": true,
"data": {
"query": "machine learning",
"matchCount": 5,
"files": [
{"path": "Research/AI Notes.md", "matches": 3},
{"path": "Projects/ML Project.md", "matches": 2}
]
}
}
```
---
### obsidian_get_backlinks
**Description**: List backlinks to a note
**Input Schema**:
```json
{
"type": "object",
"properties": {
"file": {"type": "string"},
"path": {"type": "string"},
"counts": {
"type": "boolean",
"description": "Include link counts (default: false)"
}
},
"oneOf": [{"required": ["file"]}, {"required": ["path"]}]
}
```
**Output Example**:
```json
{
"success": true,
"data": {
"target": "Projects/ML Project.md",
"backlinks": [
{"source": "Research/AI Notes.md", "count": 2},
{"source": "Weekly Review.md", "count": 1}
],
"totalBacklinks": 2
}
}
```
---
### obsidian_list_tags
**Description**: List tags in the vault or specific file
**Input Schema**:
```json
{
"type": "object",
"properties": {
"file": {"type": "string", "description": "Limit to specific file"},
"path": {"type": "string", "description": "Limit to specific path"},
"counts": {
"type": "boolean",
"description": "Include occurrence counts (default: false)"
},
"sortBy": {
"type": "string",
"enum": ["name", "count"],
"description": "Sort order (default: name)"
}
}
}
```
**Output Example**:
```json
{
"success": true,
"data": {
"tags": [
{"name": "project", "count": 15},
{"name": "important", "count": 8},
{"name": "todo", "count": 5}
],
"totalTags": 3
}
}
```
---
## Priority 3 Tools: Tasks & Properties (15 tools)
### obsidian_list_tasks
**Description**: List tasks in the vault or specific file
**Input Schema**:
```json
{
"type": "object",
"properties": {
"file": {"type": "string"},
"path": {"type": "string"},
"status": {
"type": "string",
"enum": ["todo", "done", "all"],
"description": "Filter by status (default: all)"
},
"verbose": {
"type": "boolean",
"description": "Group by file with line numbers (default: false)"
}
}
}
```
**Output Example**:
```json
{
"success": true,
"data": {
"tasks": [
{
"ref": "Projects/TODO.md:5",
"file": "Projects/TODO.md",
"line": 5,
"text": "Finish documentation",
"status": "",
"done": false
},
{
"ref": "Weekly Review.md:12",
"file": "Weekly Review.md",
"line": 12,
"text": "Review PRs",
"status": "x",
"done": true
}
],
"totalTasks": 2
}
}
```
---
### obsidian_toggle_task
**Description**: Toggle a task's completion status
**Input Schema**:
```json
{
"type": "object",
"properties": {
"ref": {
"type": "string",
"description": "Task reference (path:line)"
},
"file": {"type": "string"},
"path": {"type": "string"},
"line": {
"type": "number",
"description": "Line number (1-indexed)"
}
},
"oneOf": [
{"required": ["ref"]},
{"required": ["file", "line"]},
{"required": ["path", "line"]}
]
}
```
---
### obsidian_set_property
**Description**: Set a property value on a note
**Input Schema**:
```json
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Property name (required)"
},
"value": {
"description": "Property value (required, type: string|number|boolean|array)"
},
"type": {
"type": "string",
"enum": ["text", "number", "checkbox", "date", "datetime", "list"],
"description": "Property type (optional, inferred from value)"
},
"file": {"type": "string"},
"path": {"type": "string"}
},
"required": ["name", "value"],
"oneOf": [{"required": ["file"]}, {"required": ["path"]}]
}
```
---
## Priority 4 Tools: Vault Navigation (15 tools)
### obsidian_list_files
**Description**: List files in the vault
**Input Schema**:
```json
{
"type": "object",
"properties": {
"folder": {
"type": "string",
"description": "Filter by folder"
},
"extension": {
"type": "string",
"description": "Filter by extension (e.g., 'md')"
}
}
}
```
---
### obsidian_get_vault_info
**Description**: Get vault statistics
**Input Schema**:
```json
{
"type": "object",
"properties": {
"info": {
"type": "string",
"enum": ["name", "path", "files", "folders", "size", "all"],
"description": "Specific info to return (default: all)"
}
}
}
```
**Output Example**:
```json
{
"success": true,
"data": {
"name": "My Vault",
"path": "/Users/user/Documents/Obsidian/My Vault",
"fileCount": 1234,
"folderCount": 56,
"totalSize": 5242880
}
}
```
---
## Priority 5 Tools: Advanced Features (30 tools)
### obsidian_daily_append
**Description**: Append content to today's daily note
**Input Schema**:
```json
{
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "Content to append (required)"
},
"inline": {
"type": "boolean",
"description": "Append without newline (default: false)"
},
"open": {
"type": "boolean",
"description": "Open daily note after appending (default: false)"
}
},
"required": ["content"]
}
```
---
### obsidian_list_templates
**Description**: List available templates
**Input Schema**:
```json
{
"type": "object",
"properties": {}
}
```
---
### obsidian_list_plugins
**Description**: List installed plugins
**Input Schema**:
```json
{
"type": "object",
"properties": {
"filter": {
"type": "string",
"enum": ["core", "community", "all"],
"description": "Filter by plugin type (default: all)"
},
"enabledOnly": {
"type": "boolean",
"description": "Show only enabled plugins (default: false)"
},
"includeVersions": {
"type": "boolean",
"description": "Include version numbers (default: false)"
}
}
}
```
---
## Complete Tool List (95 tools)
### File Operations (15)
1. obsidian_create_note
2. obsidian_read_note
3. obsidian_append_to_note
4. obsidian_prepend_to_note
5. obsidian_delete_note
6. obsidian_move_note
7. obsidian_rename_note
8. obsidian_open_note
9. obsidian_get_file_info
10. obsidian_list_recents
11. obsidian_get_outline
12. obsidian_get_wordcount
13. obsidian_random_note
14. obsidian_open_file
15. obsidian_list_orphans
### Search & Discovery (20)
16. obsidian_search
17. obsidian_search_with_context
18. obsidian_get_backlinks
19. obsidian_get_outgoing_links
20. obsidian_list_unresolved_links
21. obsidian_list_tags
22. obsidian_get_tag_info
23. obsidian_list_aliases
24. obsidian_list_properties
25. obsidian_get_property_count
26. obsidian_list_deadends
27. obsidian_list_files
28. obsidian_list_folders
29. obsidian_get_folder_info
30. obsidian_get_vault_info
31. obsidian_list_vaults
32. obsidian_open_search
33. obsidian_get_version
34. obsidian_reload_vault
35. obsidian_list_workspace_tabs
### Tasks & Properties (15)
36. obsidian_list_tasks
37. obsidian_toggle_task
38. obsidian_mark_task_done
39. obsidian_mark_task_todo
40. obsidian_update_task_status
41. obsidian_get_property
42. obsidian_set_property
43. obsidian_remove_property
44. obsidian_list_note_properties
45. obsidian_list_vault_properties
46. obsidian_daily_tasks
47. obsidian_active_file_tasks
48. obsidian_active_file_properties
49. obsidian_active_file_tags
50. obsidian_active_file_aliases
### Daily Notes (6)
51. obsidian_open_daily_note
52. obsidian_daily_append
53. obsidian_daily_prepend
54. obsidian_daily_read
55. obsidian_daily_path
56. obsidian_random_read
### Templates & Bookmarks (8)
57. obsidian_list_templates
58. obsidian_read_template
59. obsidian_insert_template
60. obsidian_create_bookmark
61. obsidian_list_bookmarks
62. obsidian_bookmark_file
63. obsidian_bookmark_search
64. obsidian_bookmark_url
### Plugins & Themes (12)
65. obsidian_list_plugins
66. obsidian_list_enabled_plugins
67. obsidian_get_plugin_info
68. obsidian_enable_plugin
69. obsidian_disable_plugin
70. obsidian_list_themes
71. obsidian_get_active_theme
72. obsidian_set_theme
73. obsidian_list_css_snippets
74. obsidian_enable_snippet
75. obsidian_disable_snippet
76. obsidian_restricted_mode_status
### File History & Sync (12)
77. obsidian_list_file_versions
78. obsidian_read_version
79. obsidian_restore_version
80. obsidian_list_files_with_history
81. obsidian_open_history
82. obsidian_list_sync_versions
83. obsidian_read_sync_version
84. obsidian_restore_sync_version
85. obsidian_get_sync_status
86. obsidian_pause_sync
87. obsidian_resume_sync
88. obsidian_list_sync_deleted
### Bases & Commands (7)
89. obsidian_list_bases
90. obsidian_list_base_views
91. obsidian_query_base
92. obsidian_create_base_item
93. obsidian_list_commands
94. obsidian_execute_command
95. obsidian_get_hotkey
## Error Response Format
All tools return errors in consistent structure:
```json
{
"success": false,
"error": {
"code": "OBSIDIAN_NOT_RUNNING",
"message": "Obsidian application is not running. Please start Obsidian and try again.",
"details": {
"attempted": "create_note",
"file": "Meeting Notes.md"
}
}
}
```
## Validation Rules
All input schemas enforce:
- String min/max lengths
- Required fields
- Enum constraints for predefined values
- Type coercion where safe
- Sanitization of special characters before CLI execution