#
tokens: 36615/50000 15/15 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .github
│   ├── copilot-instructions.md
│   └── workflows
│       ├── ci.yml
│       └── sync-spec.yml
├── .gitignore
├── .vscode
│   ├── mcp.json
│   └── tasks.json
├── build
│   ├── index.d.ts
│   ├── index.d.ts.map
│   ├── index.js
│   └── index.js.map
├── bun.lock
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── package.json
├── README.md
├── src
│   ├── index.ts
│   └── openapi.json
├── test
│   ├── ai-native-integration.test.ts
│   └── integration.test.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # dependencies (bun install)
 2 | node_modules
 3 | 
 4 | # output
 5 | out
 6 | dist
 7 | *.tgz
 8 | 
 9 | # code coverage
10 | coverage
11 | *.lcov
12 | 
13 | # logs
14 | logs
15 | _.log
16 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17 | 
18 | # dotenv environment variable files
19 | .env
20 | .env.development.local
21 | .env.test.local
22 | .env.production.local
23 | .env.local
24 | 
25 | # caches
26 | .eslintcache
27 | .cache
28 | *.tsbuildinfo
29 | 
30 | # IntelliJ based IDEs
31 | .idea
32 | 
33 | # Finder (MacOS) folder config
34 | .DS_Store
35 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Obsidian Local REST API MCP Server
  2 | 
  3 | An AI-Native MCP (Model Context Protocol) server that provides intelligent, task-oriented tools for interacting with Obsidian vaults through a local REST API.
  4 | 
  5 | ## 🧠 AI-Native Design Philosophy
  6 | 
  7 | This MCP server has been redesigned following AI-Native principles rather than simple API-to-tool mapping. Instead of exposing low-level CRUD operations, it provides high-level, task-oriented tools that LLMs can reason about more effectively.
  8 | 
  9 | ### Before vs After: The Transformation
 10 | 
 11 | | **Old Approach (CRUD-Based)** | **New Approach (AI-Native)** | **Why Better** |
 12 | |--------------------------------|-------------------------------|----------------|
 13 | | `list_files` (returns everything) | `list_directory(path, limit, offset)` | Prevents context overflow with pagination |
 14 | | `create_file` + `update_file` | `write_file(path, content, mode)` | Single tool handles create/update/append |
 15 | | `create_note` + `update_note` | `create_or_update_note(path, content, frontmatter)` | Intelligent upsert removes decision complexity |
 16 | | `search_notes(query)` | `search_vault(query, scope, path_filter)` | Precise, scopeable search with advanced filtering |
 17 | | *(no equivalent)* | `get_daily_note(date)` | High-level abstraction for common workflow |
 18 | | *(no equivalent)* | `get_recent_notes(limit)` | Task-oriented recent file access |
 19 | | *(no equivalent)* | `find_related_notes(path, on)` | Conceptual relationship discovery |
 20 | 
 21 | ## 🛠 Available Tools
 22 | 
 23 | ### Directory & File Operations
 24 | 
 25 | #### `list_directory`
 26 | **Purpose**: List directory contents with pagination to prevent context overflow
 27 | ```json
 28 | {
 29 |   "path": "Projects/",
 30 |   "recursive": false,
 31 |   "limit": 20,
 32 |   "offset": 0
 33 | }
 34 | ```
 35 | **AI Benefit**: LLM can explore vault structure incrementally without overwhelming context
 36 | 
 37 | #### `read_file`
 38 | **Purpose**: Read content of any file in the vault
 39 | ```json
 40 | {"path": "notes/meeting-notes.md"}
 41 | ```
 42 | 
 43 | #### `write_file`
 44 | **Purpose**: Write file with multiple modes - replaces separate create/update operations
 45 | ```json
 46 | {
 47 |   "path": "notes/summary.md",
 48 |   "content": "# Meeting Summary\n...",
 49 |   "mode": "append"  // "overwrite", "append", "prepend"
 50 | }
 51 | ```
 52 | **AI Benefit**: Single tool handles all write scenarios, removes ambiguity
 53 | 
 54 | #### `delete_item`
 55 | **Purpose**: Delete any file or directory
 56 | ```json
 57 | {"path": "old-notes/"}
 58 | ```
 59 | 
 60 | ### AI-Native Note Operations
 61 | 
 62 | #### `create_or_update_note`
 63 | **Purpose**: Intelligent upsert - creates if missing, updates if exists
 64 | ```json
 65 | {
 66 |   "path": "daily/2024-12-26",
 67 |   "content": "## Tasks\n- Review AI-native MCP design",
 68 |   "frontmatter": {"tags": ["daily", "tasks"]}
 69 | }
 70 | ```
 71 | **AI Benefit**: Eliminates "does this note exist?" decision tree
 72 | 
 73 | #### `get_daily_note`
 74 | **Purpose**: Smart daily note retrieval with common naming patterns
 75 | ```json
 76 | {"date": "today"}  // or "yesterday", "2024-12-26"
 77 | ```
 78 | **AI Benefit**: Abstracts file system details and naming conventions
 79 | 
 80 | #### `get_recent_notes`
 81 | **Purpose**: Get recently modified notes
 82 | ```json
 83 | {"limit": 5}
 84 | ```
 85 | **AI Benefit**: Matches natural "what did I work on recently?" queries
 86 | 
 87 | ### Advanced Search & Discovery
 88 | 
 89 | #### `search_vault`
 90 | **Purpose**: Multi-scope search with advanced filtering
 91 | ```json
 92 | {
 93 |   "query": "machine learning",
 94 |   "scope": ["content", "filename", "tags"],
 95 |   "path_filter": "research/"
 96 | }
 97 | ```
 98 | **AI Benefit**: Precise, targeted search reduces noise
 99 | 
100 | #### `find_related_notes`
101 | **Purpose**: Discover conceptual relationships between notes
102 | ```json
103 | {
104 |   "path": "ai-research.md",
105 |   "on": ["tags", "links"]
106 | }
107 | ```
108 | **AI Benefit**: Enables relationship-based workflows and serendipitous discovery
109 | 
110 | ### Legacy Tools (Backward Compatibility)
111 | 
112 | The server maintains backward compatibility with existing tools like `get_note`, `list_notes`, `get_metadata_keys`, etc.
113 | 
114 | ## Prerequisites
115 | 
116 | - Node.js 18+ or Bun runtime
117 | - [Obsidian Local REST API](https://github.com/j-shelfwood/obsidian-local-rest-api) running locally (default: http://obsidian-local-rest-api.test)
118 | 
119 | ## Installation
120 | 
121 | ### Using npx (Recommended)
122 | 
123 | ```bash
124 | npx obsidian-local-rest-api-mcp
125 | ```
126 | 
127 | ### From Source
128 | 
129 | ```bash
130 | # Clone the repository
131 | git clone https://github.com/j-shelfwood/obsidian-local-rest-api-mcp.git
132 | cd obsidian-local-rest-api-mcp
133 | 
134 | # Install dependencies with bun
135 | bun install
136 | 
137 | # Build the project
138 | bun run build
139 | ```
140 | 
141 | ## Configuration
142 | 
143 | Set environment variables for API connection:
144 | 
145 | ```bash
146 | export OBSIDIAN_API_URL="http://obsidian-local-rest-api.test"  # Default URL (or http://localhost:8000 for non-Valet setups)
147 | export OBSIDIAN_API_KEY="your-api-key"          # Optional bearer token
148 | ```
149 | 
150 | ## Usage
151 | 
152 | ### Running the Server
153 | 
154 | ```bash
155 | # Development mode with auto-reload
156 | bun run dev
157 | 
158 | # Production mode
159 | bun run start
160 | 
161 | # Or run directly
162 | node build/index.js
163 | ```
164 | 
165 | ### MCP Client Configuration
166 | 
167 | #### Claude Desktop
168 | 
169 | Add to your `claude_desktop_config.json`:
170 | 
171 | ```json
172 | {
173 |   "mcpServers": {
174 |     "obsidian-vault": {
175 |       "command": "npx",
176 |       "args": ["obsidian-local-rest-api-mcp"],
177 |       "env": {
178 |         "OBSIDIAN_API_URL": "http://obsidian-local-rest-api.test",
179 |         "OBSIDIAN_API_KEY": "your-api-key-if-needed"
180 |       }
181 |     }
182 |   }
183 | }
184 | ```
185 | 
186 | #### VS Code with MCP Extension
187 | 
188 | Use the included `.vscode/mcp.json` configuration file.
189 | 
190 | ## Development
191 | 
192 | ```bash
193 | # Watch mode for development
194 | bun run dev
195 | 
196 | # Build TypeScript
197 | bun run build
198 | 
199 | # Type checking
200 | bun run tsc --noEmit
201 | ```
202 | 
203 | ## Architecture
204 | 
205 | - **ObsidianApiClient** - HTTP client wrapper for REST API endpoints
206 | - **ObsidianMcpServer** - MCP server implementation with tool handlers
207 | - **Configuration** - Environment-based configuration with validation
208 | 
209 | ## Error Handling
210 | 
211 | The server includes comprehensive error handling:
212 | 
213 | - API connection failures
214 | - Invalid tool parameters
215 | - Network timeouts
216 | - Authentication errors
217 | 
218 | Errors are returned as MCP tool call responses with descriptive messages.
219 | 
220 | ## Debugging
221 | 
222 | Enable debug logging by setting environment variables:
223 | 
224 | ```bash
225 | export DEBUG=1
226 | export NODE_ENV=development
227 | ```
228 | 
229 | Server logs are written to stderr to avoid interfering with MCP protocol communication on stdout.
230 | 
231 | ## Troubleshooting
232 | 
233 | ### MCP Server Fails to Start
234 | 
235 | If your MCP client shows "Start Failed" or similar errors:
236 | 
237 | 1. **Test the server directly**:
238 | 
239 |    ```bash
240 |    npx obsidian-local-rest-api-mcp --version
241 |    ```
242 | 
243 |    Should output the version number.
244 | 
245 | 2. **Test MCP protocol**:
246 | 
247 |    ```bash
248 |    # Run our test script
249 |    node -e "
250 |    const { spawn } = require('child_process');
251 |    const child = spawn('npx', ['obsidian-local-rest-api-mcp'], { stdio: ['pipe', 'pipe', 'pipe'] });
252 |    child.stdout.on('data', d => console.log('OUT:', d.toString()));
253 |    child.stderr.on('data', d => console.log('ERR:', d.toString()));
254 |    setTimeout(() => {
255 |      child.stdin.write(JSON.stringify({jsonrpc:'2.0',id:1,method:'initialize',params:{protocolVersion:'2024-11-05',capabilities:{},clientInfo:{name:'test',version:'1.0.0'}}})+'\n');
256 |      setTimeout(() => child.kill(), 2000);
257 |    }, 500);
258 |    "
259 |    ```
260 | 
261 |    Should show initialization response.
262 | 
263 | 3. **Check Environment Variables**:
264 | 
265 |    - Ensure `OBSIDIAN_API_URL` points to a running Obsidian Local REST API
266 |    - Test the API directly: `curl http://obsidian-local-rest-api.test/api/files` (or your configured API URL)
267 | 
268 | 4. **Verify Obsidian Local REST API**:
269 |    - Install and run [Obsidian Local REST API](https://github.com/j-shelfwood/obsidian-local-rest-api)
270 |    - Confirm it's accessible on the configured port
271 |    - Check if authentication is required
272 | 
273 | ### Common Issues
274 | 
275 | **"Command not found"**: Make sure Node.js/npm is installed and npx is available
276 | 
277 | **"Connection refused"**: Obsidian Local REST API is not running or wrong URL
278 | 
279 | **Laravel Valet .test domains**: If using Laravel Valet, ensure your project directory name matches the .test domain (e.g., `obsidian-local-rest-api.test` for a project in `/obsidian-local-rest-api/`)
280 | 
281 | **"Unauthorized"**: Check if API key is required and properly configured
282 | 
283 | **"Timeout"**: Increase timeout in client configuration or check network connectivity
284 | 
285 | ### Cherry Studio Configuration
286 | 
287 | For Cherry Studio, use these exact settings:
288 | 
289 | - **Name**: `obsidian-vault` (or any name you prefer)
290 | - **Type**: `Standard Input/Output (stdio)`
291 | - **Command**: `npx`
292 | - **Arguments**: `obsidian-local-rest-api-mcp`
293 | - **Environment Variables**:
294 |   - `OBSIDIAN_API_URL`: Your API URL (e.g., `http://obsidian-local-rest-api.test` for Laravel Valet)
295 |   - `OBSIDIAN_API_KEY`: Optional API key if authentication is required
296 | - **Environment Variables**:
297 |   - `OBSIDIAN_API_URL`: `http://obsidian-local-rest-api.test` (or your API URL)
298 |   - `OBSIDIAN_API_KEY`: `your-api-key` (if required)
299 | 
300 | ## Contributing
301 | 
302 | 1. Fork the repository
303 | 2. Create a feature branch
304 | 3. Make changes with proper TypeScript types
305 | 4. Test with your Obsidian vault
306 | 5. Submit a pull request
307 | 
308 | ## License
309 | 
310 | MIT
311 | 
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Contributing
 2 | 
 3 | ## Development Setup
 4 | 
 5 | 1. Clone the repository
 6 | 2. Install dependencies: `bun install`
 7 | 3. Build the project: `bun run build`
 8 | 4. Run tests: `bun test`
 9 | 
10 | ## Testing
11 | 
12 | The project uses Bun's built-in test runner. Tests are located in `test/integration.test.ts` and cover:
13 | 
14 | - Binary compilation and execution
15 | - TypeScript type checking
16 | - Environment configuration
17 | - Server startup behavior
18 | - Package configuration validation
19 | 
20 | Run tests with: `bun test`
21 | 
22 | ## Publishing
23 | 
24 | The project is automatically published to npm when a GitHub release is created. To publish manually:
25 | 
26 | 1. Ensure all tests pass: `bun test`
27 | 2. Build the project: `bun run build`
28 | 3. Publish: `npm publish --access public`
29 | 
30 | ## CI/CD
31 | 
32 | GitHub Actions workflow runs on:
33 | 
34 | - Push to main/master branches
35 | - Pull requests
36 | - GitHub releases
37 | 
38 | The workflow:
39 | 
40 | - Tests on multiple Bun versions
41 | - Runs linting and tests
42 | - Builds the project
43 | - Publishes to npm on releases (requires NPM_TOKEN secret)
44 | 
45 | ## Architecture
46 | 
47 | The MCP server implements the Model Context Protocol specification and provides tools for:
48 | 
49 | - File operations (list, get, create, update, delete)
50 | - Note operations with frontmatter support
51 | - Metadata extraction and search
52 | - Content search across vault
53 | 
54 | All API calls are made to the Obsidian Local REST API with configurable base URL and authentication.
55 | 
```

--------------------------------------------------------------------------------
/.vscode/mcp.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "servers": {
 3 |     "obsidian-vault": {
 4 |       "type": "stdio",
 5 |       "command": "node",
 6 |       "args": ["build/index.js"]
 7 |     }
 8 |   }
 9 | }
10 | 
```

--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "version": "2.0.0",
 3 |   "tasks": [
 4 |     {
 5 |       "label": "Build MCP Server",
 6 |       "type": "shell",
 7 |       "command": "bun",
 8 |       "args": ["run", "build"],
 9 |       "group": "build",
10 |       "problemMatcher": ["$tsc"],
11 |       "isBackground": false
12 |     }
13 |   ]
14 | }
15 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "declaration": true,
13 |     "declarationMap": true,
14 |     "sourceMap": true
15 |   },
16 |   "include": ["src/**/*"],
17 |   "exclude": ["node_modules", "build"]
18 | }
19 | 
```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: CI/CD
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [main, master]
 6 |   pull_request:
 7 |     branches: [main, master]
 8 |   release:
 9 |     types: [published]
10 | 
11 | jobs:
12 |   test:
13 |     runs-on: ubuntu-latest
14 |     strategy:
15 |       matrix:
16 |         bun-version: ["latest"]
17 | 
18 |     steps:
19 |       - uses: actions/checkout@v4
20 | 
21 |       - name: Setup Bun
22 |         uses: oven-sh/setup-bun@v1
23 |         with:
24 |           bun-version: ${{ matrix.bun-version }}
25 | 
26 |       - name: Install dependencies
27 |         run: bun install
28 | 
29 |       - name: Run linting
30 |         run: bun run lint
31 | 
32 |       - name: Run tests
33 |         run: bun test
34 | 
35 |       - name: Build project
36 |         run: bun run build
37 | 
38 |       - name: Verify binary is executable
39 |         run: test -x build/index.js
40 | 
41 |   publish:
42 |     needs: test
43 |     runs-on: ubuntu-latest
44 |     if: github.event_name == 'release' && github.event.action == 'published'
45 | 
46 |     steps:
47 |       - uses: actions/checkout@v4
48 | 
49 |       - name: Setup Bun
50 |         uses: oven-sh/setup-bun@v1
51 |         with:
52 |           bun-version: latest
53 | 
54 |       - name: Setup Node.js for npm
55 |         uses: actions/setup-node@v4
56 |         with:
57 |           node-version: "18"
58 |           registry-url: "https://registry.npmjs.org"
59 | 
60 |       - name: Install dependencies
61 |         run: bun install
62 | 
63 |       - name: Build and test
64 |         run: bun run build && bun test
65 | 
66 |       - name: Publish to npm
67 |         run: npm publish --access public
68 |         env:
69 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
70 | 
```

--------------------------------------------------------------------------------
/.github/workflows/sync-spec.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Sync API Spec
 2 | 
 3 | on:
 4 |   repository_dispatch:
 5 |     types: [api_spec_updated]
 6 |   schedule:
 7 |     - cron: '*/5 * * * *'
 8 |   workflow_dispatch:
 9 | 
10 | jobs:
11 |   sync:
12 |     runs-on: ubuntu-latest
13 |     permissions:
14 |       contents: write
15 |     steps:
16 |       - name: Checkout
17 |         uses: actions/checkout@v4
18 |         with:
19 |           persist-credentials: true
20 | 
21 |       - name: Set up Node (Bun)
22 |         uses: oven-sh/setup-bun@v1
23 |         with:
24 |           bun-version: '1.2.17'
25 | 
26 |       - name: Fetch OpenAPI spec from source repo
27 |         env:
28 |           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29 |         run: |
30 |           repo=${{ github.event.client_payload.spec_repo || 'j-shelfwood/obsidian-local-rest-api' }}
31 |           path=${{ github.event.client_payload.spec_path || 'openapi.json' }}
32 |           curl -sSL \
33 |             -H "Accept: application/vnd.github.raw+json" \
34 |             https://api.github.com/repos/${repo}/contents/${path} > openapi.json
35 |           jq . openapi.json > /dev/null
36 | 
37 |       - name: Update working tree with new spec
38 |         run: |
39 |           mkdir -p src
40 |           mv openapi.json src/openapi.json
41 | 
42 |       - name: Install deps
43 |         run: bun install --frozen-lockfile || bun install
44 | 
45 |       - name: Build
46 |         run: bun run build
47 | 
48 |       - name: Test
49 |         run: bun test
50 | 
51 |       - name: Commit and push to main
52 |         env:
53 |           GIT_AUTHOR_NAME: github-actions[bot]
54 |           GIT_AUTHOR_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
55 |           GIT_COMMITTER_NAME: github-actions[bot]
56 |           GIT_COMMITTER_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
57 |         run: |
58 |           if git diff --quiet --exit-code src/openapi.json; then
59 |             echo "No changes to spec; skipping commit."
60 |             exit 0
61 |           fi
62 |           git add src/openapi.json
63 |           git commit -m "chore: sync API spec (scheduled or dispatch)"
64 |           git push origin HEAD:main
65 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "obsidian-local-rest-api-mcp",
 3 |   "version": "1.1.0",
 4 |   "type": "module",
 5 |   "main": "build/index.js",
 6 |   "bin": {
 7 |     "obsidian-local-rest-api-mcp": "build/index.js"
 8 |   },
 9 |   "scripts": {
10 |     "build": "bun run tsc && chmod +x build/index.js",
11 |     "dev": "bun run --watch src/index.ts",
12 |     "start": "node build/index.js",
13 |     "test": "bun test",
14 |     "test:unit": "echo 'Basic validation passed - integration tests require running Laravel API'",
15 |     "test:integration": "bun run test:integration.ts",
16 |     "lint": "tsc --noEmit",
17 |     "prepublishOnly": "bun run build && bun run test && bun run lint",
18 |     "publish:npm": "npm publish --access public",
19 |     "version:patch": "npm version patch && npm publish --access public",
20 |     "version:minor": "npm version minor && npm publish --access public",
21 |     "version:major": "npm version major && npm publish --access public"
22 |   },
23 |   "keywords": [
24 |     "mcp",
25 |     "obsidian",
26 |     "model-context-protocol",
27 |     "vault",
28 |     "notes",
29 |     "rest-api",
30 |     "llm",
31 |     "ai",
32 |     "ai-native",
33 |     "task-oriented",
34 |     "intelligent-tools",
35 |     "knowledge-management"
36 |   ],
37 |   "author": "shelfwood",
38 |   "license": "MIT",
39 |   "description": "AI-Native MCP server for Obsidian vaults with task-oriented, intelligent tools designed for LLM workflows",
40 |   "repository": {
41 |     "type": "git",
42 |     "url": "git+https://github.com/j-shelfwood/obsidian-local-rest-api-mcp.git"
43 |   },
44 |   "bugs": {
45 |     "url": "https://github.com/j-shelfwood/obsidian-local-rest-api-mcp/issues"
46 |   },
47 |   "homepage": "https://github.com/j-shelfwood/obsidian-local-rest-api-mcp#readme",
48 |   "files": [
49 |     "build/**/*",
50 |     "README.md",
51 |     "package.json"
52 |   ],
53 |   "engines": {
54 |     "node": ">=18.0.0"
55 |   },
56 |   "devDependencies": {
57 |     "typescript": "^5.8.3",
58 |     "@types/bun": "latest"
59 |   },
60 |   "private": false,
61 |   "dependencies": {
62 |     "@modelcontextprotocol/sdk": "^1.13.1",
63 |     "zod": "^3.25.67"
64 |   },
65 |   "types": "build/index.d.ts"
66 | }
67 | 
```

--------------------------------------------------------------------------------
/.github/copilot-instructions.md:
--------------------------------------------------------------------------------

```markdown
 1 | <!-- Use this file to provide workspace-specific custom instructions to Copilot. For more details, visit https://code.visualstudio.com/docs/copilot/copilot-customization#_use-a-githubcopilotinstructionsmd-file -->
 2 | 
 3 | # Obsidian Local REST API MCP Server
 4 | 
 5 | You can find more info and examples at https://modelcontextprotocol.io/llms-full.txt
 6 | 
 7 | ## Project Context
 8 | 
 9 | This is an MCP (Model Context Protocol) server that provides LLM tool calls to interact with an Obsidian vault through a local REST API. The server acts as a bridge between MCP clients (like Claude Desktop, VS Code, etc.) and the Obsidian Local REST API.
10 | 
11 | ## Architecture
12 | 
13 | - **MCP Server**: Implements the Model Context Protocol server specification
14 | - **API Client**: Wraps the Obsidian Local REST API endpoints
15 | - **Tools**: Exposes vault operations as MCP tools for LLM consumption
16 | 
17 | ## Key Files
18 | 
19 | - `src/index.ts` - Main MCP server implementation
20 | - `package.json` - Project configuration with bun support
21 | - `tsconfig.json` - TypeScript configuration optimized for Node16 modules
22 | 
23 | ## Available Tools
24 | 
25 | The server exposes these tools to MCP clients:
26 | 
27 | ### File Operations
28 | - `list_files` - List all files in vault
29 | - `get_file` - Get file content
30 | - `create_file` - Create new file/directory
31 | - `update_file` - Update file content
32 | - `delete_file` - Delete file
33 | 
34 | ### Note Operations  
35 | - `list_notes` - List notes with metadata
36 | - `get_note` - Get note with frontmatter
37 | - `create_note` - Create note with optional frontmatter
38 | - `update_note` - Update note content/frontmatter
39 | - `delete_note` - Delete note
40 | - `search_notes` - Search notes by content
41 | 
42 | ### Metadata Operations
43 | - `get_metadata_keys` - List all frontmatter keys
44 | - `get_metadata_values` - Get unique values for a key
45 | 
46 | ## Configuration
47 | 
48 | Set environment variables:
49 | - `OBSIDIAN_API_URL` - Base URL of the REST API (default: http://localhost:8000)
50 | - `OBSIDIAN_API_KEY` - Optional bearer token for authentication
51 | 
52 | ## Development Guidelines
53 | 
54 | - Use TypeScript strict mode
55 | - Follow MCP protocol specifications
56 | - Handle errors gracefully with meaningful messages
57 | - Validate inputs using zod schemas
58 | - Use proper async/await patterns
59 | - Log errors to stderr (stdout reserved for MCP protocol)
60 | 
```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Changelog
 2 | 
 3 | ## [1.1.0] - 2024-12-26
 4 | 
 5 | ### 🧠 Major: AI-Native Redesign
 6 | 
 7 | This release represents a complete philosophical shift from CRUD-based API mappings to truly AI-native, task-oriented tools designed for LLM workflows.
 8 | 
 9 | #### ✨ New AI-Native Tools
10 | 
11 | - **`list_directory`** - Paginated directory listing prevents context overflow
12 | - **`write_file`** - Unified create/update/append operations with mode parameter
13 | - **`create_or_update_note`** - Intelligent upsert eliminates existence checks
14 | - **`search_vault`** - Multi-scope search with advanced filtering (content, filename, tags)
15 | - **`get_daily_note`** - Smart daily note resolution with common naming patterns
16 | - **`get_recent_notes`** - Task-oriented recent file access
17 | - **`find_related_notes`** - Conceptual relationship discovery via tags and links
18 | 
19 | #### 🔧 Enhanced Backend API
20 | 
21 | - New `/api/vault/*` endpoints for high-level operations
22 | - Enhanced `/api/files/write` with multi-mode support
23 | - Enhanced `/api/notes/upsert` for intelligent create/update
24 | - Advanced search and relationship discovery endpoints
25 | 
26 | #### 📊 Performance Improvements
27 | 
28 | - **Context Efficiency**: Pagination prevents LLM context overflow
29 | - **Decision Reduction**: Upsert operations eliminate existence checks
30 | - **Cognitive Alignment**: Tools match natural language concepts
31 | 
32 | #### 🔄 Backward Compatibility
33 | 
34 | - All existing tools (`get_note`, `list_notes`, etc.) remain functional
35 | - Legacy endpoints preserved for existing integrations
36 | - Gradual migration path available
37 | 
38 | #### 🏗 Architecture
39 | 
40 | - New `VaultController` for high-level vault operations
41 | - Enhanced `FileController` and `NoteController`
42 | - Improved `LocalVaultService` with additional file system methods
43 | 
44 | ### Breaking Changes
45 | 
46 | None - this release maintains full backward compatibility while adding the new AI-native capabilities.
47 | 
48 | ### Migration Guide
49 | 
50 | Existing integrations continue to work unchanged. To take advantage of AI-native features:
51 | 
52 | 1. Use `list_directory` instead of `list_files` for large vaults
53 | 2. Replace `create_file`/`update_file` pairs with single `write_file` calls
54 | 3. Use `create_or_update_note` for reliable note operations
55 | 4. Leverage `search_vault` for precise, scoped searches
56 | 
57 | ---
58 | 
59 | ## [1.0.3] - Previous Release
60 | 
61 | Legacy CRUD-based implementation with basic file and note operations.
62 | 
```

--------------------------------------------------------------------------------
/test/integration.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env bun
  2 | 
  3 | import { test, expect, describe, beforeAll, afterAll } from "bun:test";
  4 | import { spawn, ChildProcess } from "child_process";
  5 | 
  6 | describe("MCP Server Basic Tests", () => {
  7 |   test("Server binary exists and is executable", () => {
  8 |     const fs = require("fs");
  9 |     const path = require("path");
 10 | 
 11 |     const binaryPath = path.join(process.cwd(), "build", "index.js");
 12 |     expect(fs.existsSync(binaryPath)).toBe(true);
 13 | 
 14 |     const stats = fs.statSync(binaryPath);
 15 |     expect(stats.isFile()).toBe(true);
 16 |   });
 17 | 
 18 |   test("TypeScript compilation succeeds", async () => {
 19 |     const buildProcess = spawn("bun", ["run", "tsc", "--noEmit"], {
 20 |       stdio: "pipe",
 21 |       cwd: process.cwd()
 22 |     });
 23 | 
 24 |     return new Promise((resolve, reject) => {
 25 |       let stderr = "";
 26 | 
 27 |       buildProcess.stderr?.on("data", (data) => {
 28 |         stderr += data.toString();
 29 |       });
 30 | 
 31 |       buildProcess.on("close", (code) => {
 32 |         if (code === 0) {
 33 |           resolve(undefined);
 34 |         } else {
 35 |           reject(new Error(`TypeScript compilation failed: ${stderr}`));
 36 |         }
 37 |       });
 38 |     });
 39 |   });
 40 | 
 41 |   test("Environment configuration parsing", () => {
 42 |     const originalUrl = process.env.OBSIDIAN_API_URL;
 43 |     const originalKey = process.env.OBSIDIAN_API_KEY;
 44 | 
 45 |     // Test default values
 46 |     delete process.env.OBSIDIAN_API_URL;
 47 |     delete process.env.OBSIDIAN_API_KEY;
 48 | 
 49 |     // Test custom values
 50 |     process.env.OBSIDIAN_API_URL = "http://test.local:9000";
 51 |     process.env.OBSIDIAN_API_KEY = "test-key";
 52 | 
 53 |     expect(process.env.OBSIDIAN_API_URL).toBe("http://test.local:9000");
 54 |     expect(process.env.OBSIDIAN_API_KEY).toBe("test-key");
 55 | 
 56 |     // Restore original values
 57 |     if (originalUrl) process.env.OBSIDIAN_API_URL = originalUrl;
 58 |     else delete process.env.OBSIDIAN_API_URL;
 59 |     if (originalKey) process.env.OBSIDIAN_API_KEY = originalKey;
 60 |     else delete process.env.OBSIDIAN_API_KEY;
 61 |   });
 62 | 
 63 |   test("Server startup timeout handling", async () => {
 64 |     const serverProcess = spawn("node", ["build/index.js"], {
 65 |       stdio: ["pipe", "pipe", "pipe"],
 66 |       env: {
 67 |         ...process.env,
 68 |         OBSIDIAN_API_URL: "http://localhost:8000",
 69 |       },
 70 |     });
 71 | 
 72 |     return new Promise((resolve) => {
 73 |       let hasOutput = false;
 74 | 
 75 |       // Check stderr for startup message
 76 |       serverProcess.stderr?.on("data", (data) => {
 77 |         const output = data.toString();
 78 |         if (output.includes("running on stdio")) {
 79 |           hasOutput = true;
 80 |         }
 81 |       });
 82 | 
 83 |       // Give server time to start and emit startup message
 84 |       global.setTimeout(() => {
 85 |         serverProcess.kill();
 86 |         // Server starting is success (it should run indefinitely)
 87 |         expect(hasOutput).toBe(true);
 88 |         resolve(undefined);
 89 |       }, 2000);
 90 | 
 91 |       serverProcess.on("error", () => {
 92 |         // Process errors are expected in test environment
 93 |         resolve(undefined);
 94 |       });
 95 |     });
 96 |   }, 5000);
 97 | });
 98 | 
 99 | describe("Package Configuration", () => {
100 |   test("Package.json has required fields for publishing", () => {
101 |     const packageJson = require("../package.json");
102 | 
103 |     expect(packageJson.name).toBe("obsidian-local-rest-api-mcp");
104 |     expect(packageJson.version).toBeDefined();
105 |     expect(packageJson.description).toBeDefined();
106 |     expect(packageJson.main).toBe("build/index.js");
107 |     expect(packageJson.types).toBe("build/index.d.ts");
108 |     expect(packageJson.license).toBe("MIT");
109 |     expect(packageJson.repository).toBeDefined();
110 |     expect(packageJson.keywords).toBeArray();
111 |     expect(packageJson.files).toContain("build/**/*");
112 |     expect(packageJson.private).toBe(false);
113 |   });
114 | 
115 |   test("Binary configuration is correct", () => {
116 |     const packageJson = require("../package.json");
117 | 
118 |     expect(packageJson.bin).toBeDefined();
119 |     expect(packageJson.bin["obsidian-local-rest-api-mcp"]).toBe("build/index.js");
120 |   });
121 | 
122 |   test("Dependencies are properly specified", () => {
123 |     const packageJson = require("../package.json");
124 | 
125 |     expect(packageJson.dependencies).toBeDefined();
126 |     expect(packageJson.dependencies["@modelcontextprotocol/sdk"]).toBeDefined();
127 |     expect(packageJson.dependencies["zod"]).toBeDefined();
128 | 
129 |     expect(packageJson.devDependencies).toBeDefined();
130 |     expect(packageJson.devDependencies["typescript"]).toBeDefined();
131 |   });
132 | });
133 | 
```

--------------------------------------------------------------------------------
/test/ai-native-integration.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Integration test script for the AI-Native MCP tools
  5 |  *
  6 |  * This script tests the key functionality of the redesigned MCP server
  7 |  * to ensure the AI-native tools work as expected.
  8 |  */
  9 | 
 10 | // Skip in CI environments or when explicitly requested
 11 | if (process.env.CI === 'true' || process.env.SKIP_INTEGRATION_TESTS === 'true') {
 12 |     console.log('⏭️  Skipping AI-Native MCP integration tests in CI environment.');
 13 |     process.exit(0);
 14 | }
 15 | 
 16 | import { readFileSync } from 'fs';
 17 | import { fileURLToPath } from 'url';
 18 | import { dirname, join } from 'path';
 19 | 
 20 | const __filename = fileURLToPath(import.meta.url);
 21 | const __dirname = dirname(__filename);
 22 | 
 23 | // Configuration
 24 | interface Config {
 25 |     baseUrl: string;
 26 |     apiKey?: string;
 27 | }
 28 | 
 29 | const config: Config = {
 30 |     baseUrl: process.env.OBSIDIAN_API_URL || "http://obsidian-local-rest-api.test",
 31 |     apiKey: process.env.OBSIDIAN_API_KEY,
 32 | };
 33 | 
 34 | // Simple API client for testing
 35 | class TestApiClient {
 36 |     private baseUrl: string;
 37 |     private headers: Record<string, string>;
 38 | 
 39 |     constructor(config: Config) {
 40 |         this.baseUrl = `${config.baseUrl}/api`;
 41 |         this.headers = {
 42 |             "Content-Type": "application/json",
 43 |             "Accept": "application/json",
 44 |         };
 45 | 
 46 |         if (config.apiKey) {
 47 |             this.headers.Authorization = `Bearer ${config.apiKey}`;
 48 |         }
 49 |     }
 50 | 
 51 |     private async request(path: string, options: RequestInit = {}): Promise<any> {
 52 |         const url = `${this.baseUrl}${path}`;
 53 |         try {
 54 |             const response = await fetch(url, {
 55 |                 ...options,
 56 |                 headers: {
 57 |                     ...this.headers,
 58 |                     ...options.headers,
 59 |                 },
 60 |             });
 61 | 
 62 |             if (!response.ok) {
 63 |                 throw new Error(`API request failed: ${response.status} ${response.statusText}`);
 64 |             }
 65 | 
 66 |             return response.json();
 67 |         } catch (error) {
 68 |             console.error(`Request to ${url} failed:`, error);
 69 |             throw error;
 70 |         }
 71 |     }
 72 | 
 73 |     async testListDirectory() {
 74 |         console.log("🧪 Testing list_directory...");
 75 |         const result = await this.request("/vault/directory?path=.&limit=5");
 76 |         console.log(`✅ Found ${result.total_items} items in vault root`);
 77 |         return result;
 78 |     }
 79 | 
 80 |     async testSearchVault() {
 81 |         console.log("🧪 Testing search_vault...");
 82 |         const result = await this.request("/vault/search?query=test&scope=content,filename");
 83 |         console.log(`✅ Search found ${result.total_results} results`);
 84 |         return result;
 85 |     }
 86 | 
 87 |     async testCreateOrUpdateNote() {
 88 |         console.log("🧪 Testing create_or_update_note...");
 89 |         const testNote = {
 90 |             path: "test-ai-native-note",
 91 |             content: "This is a test note created by the AI-native MCP tools.",
 92 |             front_matter: {
 93 |                 tags: ["test", "ai-native", "mcp"],
 94 |                 created: new Date().toISOString(),
 95 |             }
 96 |         };
 97 | 
 98 |         const result = await this.request("/notes/upsert", {
 99 |             method: "POST",
100 |             body: JSON.stringify(testNote),
101 |         });
102 |         console.log(`✅ Note upserted successfully: ${result.path}`);
103 |         return result;
104 |     }
105 | 
106 |     async testWriteFile() {
107 |         console.log("🧪 Testing write_file (append mode)...");
108 |         const result = await this.request("/files/write", {
109 |             method: "POST",
110 |             body: JSON.stringify({
111 |                 path: "test-ai-native-note.md",
112 |                 content: "\n\n## Additional Content\nThis was appended using the write_file tool.",
113 |                 mode: "append"
114 |             }),
115 |         });
116 |         console.log(`✅ File appended successfully`);
117 |         return result;
118 |     }
119 | 
120 |     async testGetRecentNotes() {
121 |         console.log("🧪 Testing get_recent_notes...");
122 |         const result = await this.request("/vault/notes/recent?limit=3");
123 |         console.log(`✅ Found ${result.length} recent notes`);
124 |         return result;
125 |     }
126 | 
127 |     async testGetDailyNote() {
128 |         console.log("🧪 Testing get_daily_note...");
129 |         try {
130 |             const result = await this.request("/vault/notes/daily?date=today");
131 |             console.log(`✅ Found daily note: ${result.path}`);
132 |             return result;
133 |         } catch (error) {
134 |             console.log(`ℹ️  No daily note found (this is expected if you don't have daily notes)`);
135 |             return null;
136 |         }
137 |     }
138 | 
139 |     async testFindRelatedNotes() {
140 |         console.log("🧪 Testing find_related_notes...");
141 |         try {
142 |             const result = await this.request("/vault/notes/related/test-ai-native-note.md?on=tags");
143 |             console.log(`✅ Found ${result.total_found} related notes`);
144 |             return result;
145 |         } catch (error) {
146 |             console.log(`ℹ️  No related notes found or test note doesn't exist yet`);
147 |             return null;
148 |         }
149 |     }
150 | }
151 | 
152 | // Run tests
153 | async function runTests() {
154 |     console.log("🚀 Starting AI-Native MCP Integration Tests\n");
155 | 
156 |     const client = new TestApiClient(config);
157 | 
158 |     try {
159 |         await client.testListDirectory();
160 |         await client.testCreateOrUpdateNote();
161 |         await client.testWriteFile();
162 |         await client.testSearchVault();
163 |         await client.testGetRecentNotes();
164 |         await client.testGetDailyNote();
165 |         await client.testFindRelatedNotes();
166 |         console.log("\n🎉 All tests completed successfully!");
167 |         console.log("\n📋 Summary of AI-Native Improvements:");
168 |         console.log("• list_directory: Paginated directory listing prevents context overflow");
169 |         console.log("• write_file: Unified create/update/append operations");
170 |         console.log("• create_or_update_note: Intelligent upsert removes LLM decision complexity");
171 |         console.log("• search_vault: Advanced search with scope filtering");
172 |         console.log("• get_recent_notes: Task-oriented recent file access");
173 |         console.log("• get_daily_note: Smart daily note resolution");
174 |         console.log("• find_related_notes: Conceptual note relationships");
175 | 
176 |     } catch (error) {
177 |         console.error("❌ Test failed:", error);
178 |         process.exit(1);
179 |     }
180 | }
181 | 
182 | if (import.meta.url === `file://${process.argv[1]}`) {
183 |     runTests().catch(console.error);
184 | }
185 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { readFileSync } from 'fs';
  4 | import { fileURLToPath } from 'url';
  5 | import { dirname, join } from 'path';
  6 | 
  7 | // Handle CLI arguments
  8 | const args = process.argv.slice(2);
  9 | if (args.includes('--version') || args.includes('-v')) {
 10 |   const __filename = fileURLToPath(import.meta.url);
 11 |   const __dirname = dirname(__filename);
 12 |   const packagePath = join(__dirname, '..', 'package.json');
 13 |   const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
 14 |   console.log(packageJson.version);
 15 |   process.exit(0);
 16 | }
 17 | 
 18 | if (args.includes('--help') || args.includes('-h')) {
 19 |   console.log(`
 20 | Obsidian Local REST API MCP Server
 21 | 
 22 | Usage: obsidian-local-rest-api-mcp [options]
 23 | 
 24 | Options:
 25 |   -v, --version    Show version number
 26 |   -h, --help       Show help
 27 | 
 28 | Environment Variables:
 29 |   OBSIDIAN_API_URL    Base URL for Obsidian REST API (default: http://obsidian-local-rest-api.test)
 30 |   OBSIDIAN_API_KEY    Optional bearer token for authentication
 31 | 
 32 | This is an MCP server that communicates via stdio. It should be configured
 33 | in your MCP client (like Claude Desktop) rather than run directly.
 34 | 
 35 | Example Claude Desktop configuration:
 36 | {
 37 |   "mcpServers": {
 38 |     "obsidian-vault": {
 39 |       "command": "npx",
 40 |       "args": ["obsidian-local-rest-api-mcp"],
 41 |       "env": {
 42 |         "OBSIDIAN_API_URL": "http://localhost:8000"
 43 |       }
 44 |     }
 45 |   }
 46 | }
 47 | `);
 48 |   process.exit(0);
 49 | }
 50 | 
 51 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 52 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
 53 | import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
 54 | import { z } from "zod";
 55 | 
 56 | // Configuration schema
 57 | const ConfigSchema = z.object({
 58 |   baseUrl: z.string().url().default("http://obsidian-local-rest-api.test"),
 59 |   apiKey: z.string().optional(),
 60 | });
 61 | 
 62 | type Config = z.infer<typeof ConfigSchema>;
 63 | 
 64 | // API client for Obsidian REST API
 65 | class ObsidianApiClient {
 66 |   private baseUrl: string;
 67 |   private headers: Record<string, string>;
 68 | 
 69 |   constructor(config: Config) {
 70 |     this.baseUrl = `${config.baseUrl}/api`;
 71 |     this.headers = {
 72 |       "Content-Type": "application/json",
 73 |       "Accept": "application/json",
 74 |     };
 75 | 
 76 |     if (config.apiKey) {
 77 |       this.headers.Authorization = `Bearer ${config.apiKey}`;
 78 |     }
 79 |   }
 80 | 
 81 |   private async request(path: string, options: RequestInit = {}): Promise<any> {
 82 |     const url = `${this.baseUrl}${path}`;
 83 |     const response = await fetch(url, {
 84 |       ...options,
 85 |       headers: {
 86 |         ...this.headers,
 87 |         ...options.headers,
 88 |       },
 89 |     });
 90 | 
 91 |     if (!response.ok) {
 92 |       throw new Error(`API request failed: ${response.status} ${response.statusText}`);
 93 |     }
 94 | 
 95 |     return response.json();
 96 |   }
 97 | 
 98 |   // Enhanced API client methods for AI-native operations
 99 | 
100 |   // Directory operations
101 |   async listDirectory(path: string = ".", recursive: boolean = false, limit: number = 50, offset: number = 0) {
102 |     const params = new URLSearchParams({
103 |       path,
104 |       recursive: recursive.toString(),
105 |       limit: limit.toString(),
106 |       offset: offset.toString(),
107 |     });
108 |     return this.request(`/vault/directory?${params}`);
109 |   }
110 | 
111 |   // File operations
112 |   async readFile(path: string) {
113 |     return this.request(`/files/${encodeURIComponent(path)}`);
114 |   }
115 | 
116 |   async writeFile(path: string, content: string, mode: string = "overwrite") {
117 |     return this.request("/files/write", {
118 |       method: "POST",
119 |       body: JSON.stringify({ path, content, mode }),
120 |     });
121 |   }
122 | 
123 |   async deleteItem(path: string) {
124 |     return this.request(`/files/${encodeURIComponent(path)}`, {
125 |       method: "DELETE",
126 |     });
127 |   }
128 | 
129 |   // AI-native note operations
130 |   async createOrUpdateNote(path: string, content: string, frontmatter: Record<string, any> = {}) {
131 |     return this.request("/notes/upsert", {
132 |       method: "POST",
133 |       body: JSON.stringify({
134 |         path,
135 |         content,
136 |         front_matter: frontmatter
137 |       }),
138 |     });
139 |   }
140 | 
141 |   async getDailyNote(date: string = "today") {
142 |     const params = new URLSearchParams({ date });
143 |     return this.request(`/vault/notes/daily?${params}`);
144 |   }
145 | 
146 |   async getRecentNotes(limit: number = 5) {
147 |     const params = new URLSearchParams({ limit: limit.toString() });
148 |     return this.request(`/vault/notes/recent?${params}`);
149 |   }
150 | 
151 |   // Enhanced search
152 |   async searchVault(query: string, scope: string[] = ["content", "filename", "tags"], pathFilter?: string) {
153 |     const params = new URLSearchParams({
154 |       query,
155 |       scope: scope.join(","),
156 |     });
157 |     if (pathFilter) {
158 |       params.append("path_filter", pathFilter);
159 |     }
160 |     return this.request(`/vault/search?${params}`);
161 |   }
162 | 
163 |   async findRelatedNotes(path: string, on: string[] = ["tags", "links"]) {
164 |     const params = new URLSearchParams({
165 |       on: on.join(","),
166 |     });
167 |     return this.request(`/vault/notes/related/${encodeURIComponent(path)}?${params}`);
168 |   }
169 | 
170 |   // Legacy methods for backward compatibility
171 |   async listFiles() {
172 |     return this.request("/files");
173 |   }
174 | 
175 |   async getFile(path: string) {
176 |     return this.request(`/files/${encodeURIComponent(path)}`);
177 |   }
178 | 
179 |   async createFile(path: string, content: string, type: "file" | "directory" = "file") {
180 |     return this.request("/files", {
181 |       method: "POST",
182 |       body: JSON.stringify({ path, content, type }),
183 |     });
184 |   }
185 | 
186 |   async updateFile(path: string, content: string) {
187 |     return this.request(`/files/${encodeURIComponent(path)}`, {
188 |       method: "PUT",
189 |       body: JSON.stringify({ content }),
190 |     });
191 |   }
192 | 
193 |   async deleteFile(path: string) {
194 |     return this.request(`/files/${encodeURIComponent(path)}`, {
195 |       method: "DELETE",
196 |     });
197 |   }
198 | 
199 |   // Notes endpoints
200 |   async listNotes() {
201 |     return this.request("/notes");
202 |   }
203 | 
204 |   async getNote(path: string) {
205 |     return this.request(`/notes/${encodeURIComponent(path)}`);
206 |   }
207 | 
208 |   async createNote(path: string, content: string, frontmatter?: Record<string, any>) {
209 |     const body: any = { path, content };
210 |     if (frontmatter) {
211 |       body.frontmatter = frontmatter;
212 |     }
213 |     return this.request("/notes", {
214 |       method: "POST",
215 |       body: JSON.stringify(body),
216 |     });
217 |   }
218 | 
219 |   async updateNote(path: string, content?: string, frontmatter?: Record<string, any>) {
220 |     const body: any = {};
221 |     if (content !== undefined) body.content = content;
222 |     if (frontmatter !== undefined) body.frontmatter = frontmatter;
223 | 
224 |     return this.request(`/notes/${encodeURIComponent(path)}`, {
225 |       method: "PATCH",
226 |       body: JSON.stringify(body),
227 |     });
228 |   }
229 | 
230 |   async deleteNote(path: string) {
231 |     return this.request(`/notes/${encodeURIComponent(path)}`, {
232 |       method: "DELETE",
233 |     });
234 |   }
235 | 
236 |   // Search notes
237 |   async searchNotes(query: string) {
238 |     return this.request(`/notes?search=${encodeURIComponent(query)}`);
239 |   }
240 | 
241 |   // Metadata endpoints
242 |   async getMetadataKeys() {
243 |     return this.request("/metadata/keys");
244 |   }
245 | 
246 |   async getMetadataValues(key: string) {
247 |     return this.request(`/metadata/values/${encodeURIComponent(key)}`);
248 |   }
249 | }
250 | 
251 | // MCP Server implementation
252 | class ObsidianMcpServer {
253 |   private server: Server;
254 |   private client: ObsidianApiClient;
255 | 
256 |   constructor(config: Config) {
257 |     this.client = new ObsidianApiClient(config);
258 |     this.server = new Server({
259 |       name: "obsidian-vault-mcp",
260 |       version: "1.0.0",
261 |     }, {
262 |       capabilities: {
263 |         tools: {},
264 |       },
265 |     });
266 | 
267 |     this.setupTools();
268 |   }
269 | 
270 |   private setupTools() {
271 |     // AI-Native Tools Definition
272 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
273 |       tools: [
274 |         // Directory Operations
275 |         {
276 |           name: "list_directory",
277 |           description: "List directory contents with pagination to prevent context overflow. Shows immediate contents by default.",
278 |           inputSchema: {
279 |             type: "object",
280 |             properties: {
281 |               path: { type: "string", description: "Directory path to list", default: "." },
282 |               recursive: { type: "boolean", description: "Include subdirectories recursively", default: false },
283 |               limit: { type: "number", description: "Maximum items to return", default: 50 },
284 |               offset: { type: "number", description: "Pagination offset", default: 0 },
285 |             },
286 |           },
287 |         },
288 | 
289 |         // File Operations
290 |         {
291 |           name: "read_file",
292 |           description: "Read content of a specific file from the vault",
293 |           inputSchema: {
294 |             type: "object",
295 |             properties: {
296 |               path: { type: "string", description: "Path to the file" },
297 |             },
298 |             required: ["path"],
299 |           },
300 |         },
301 |         {
302 |           name: "write_file",
303 |           description: "Write file content with different modes: overwrite (default), append, or prepend. Handles both create and update operations.",
304 |           inputSchema: {
305 |             type: "object",
306 |             properties: {
307 |               path: { type: "string", description: "Path to the file" },
308 |               content: { type: "string", description: "Content to write" },
309 |               mode: { type: "string", enum: ["overwrite", "append", "prepend"], description: "Write mode", default: "overwrite" },
310 |             },
311 |             required: ["path", "content"],
312 |           },
313 |         },
314 |         {
315 |           name: "delete_item",
316 |           description: "Delete a file or directory from the vault",
317 |           inputSchema: {
318 |             type: "object",
319 |             properties: {
320 |               path: { type: "string", description: "Path to the item to delete" },
321 |             },
322 |             required: ["path"],
323 |           },
324 |         },
325 | 
326 |         // AI-Native Note Operations
327 |         {
328 |           name: "create_or_update_note",
329 |           description: "Create or update a note with content and frontmatter. Performs upsert operation - creates if doesn't exist, updates if it does.",
330 |           inputSchema: {
331 |             type: "object",
332 |             properties: {
333 |               path: { type: "string", description: "Path for the note (without .md extension)" },
334 |               content: { type: "string", description: "Note content" },
335 |               frontmatter: { type: "object", description: "Frontmatter metadata", default: {} },
336 |             },
337 |             required: ["path", "content"],
338 |           },
339 |         },
340 |         {
341 |           name: "get_daily_note",
342 |           description: "Get daily note for a specific date. Handles common daily note naming conventions and file locations.",
343 |           inputSchema: {
344 |             type: "object",
345 |             properties: {
346 |               date: { type: "string", description: "Date (today, yesterday, tomorrow, or YYYY-MM-DD)", default: "today" },
347 |             },
348 |           },
349 |         },
350 |         {
351 |           name: "get_recent_notes",
352 |           description: "Get recently modified notes, ordered by modification time",
353 |           inputSchema: {
354 |             type: "object",
355 |             properties: {
356 |               limit: { type: "number", description: "Number of recent notes to return", default: 5 },
357 |             },
358 |           },
359 |         },
360 | 
361 |         // Enhanced Search and Discovery
362 |         {
363 |           name: "search_vault",
364 |           description: "Search vault content across files, filenames, and metadata with advanced filtering",
365 |           inputSchema: {
366 |             type: "object",
367 |             properties: {
368 |               query: { type: "string", description: "Search query" },
369 |               scope: {
370 |                 type: "array",
371 |                 items: { type: "string", enum: ["content", "filename", "tags"] },
372 |                 description: "Search scope - where to look for the query",
373 |                 default: ["content", "filename", "tags"]
374 |               },
375 |               path_filter: { type: "string", description: "Limit search to specific path prefix" },
376 |             },
377 |             required: ["query"],
378 |           },
379 |         },
380 |         {
381 |           name: "find_related_notes",
382 |           description: "Find notes related to a given note based on shared tags, links, or backlinks",
383 |           inputSchema: {
384 |             type: "object",
385 |             properties: {
386 |               path: { type: "string", description: "Path to the source note" },
387 |               on: {
388 |                 type: "array",
389 |                 items: { type: "string", enum: ["tags", "links"] },
390 |                 description: "Relationship criteria to use for finding related notes",
391 |                 default: ["tags", "links"]
392 |               },
393 |             },
394 |             required: ["path"],
395 |           },
396 |         },
397 | 
398 |         // Legacy Tools (for backward compatibility)
399 |         {
400 |           name: "get_note",
401 |           description: "Get a specific note with its content and metadata (legacy)",
402 |           inputSchema: {
403 |             type: "object",
404 |             properties: {
405 |               path: { type: "string", description: "Path to the note" },
406 |             },
407 |             required: ["path"],
408 |           },
409 |         },
410 |         {
411 |           name: "list_notes",
412 |           description: "List all notes in the vault with optional search filter (legacy with search support)",
413 |           inputSchema: {
414 |             type: "object",
415 |             properties: {
416 |               search: { type: "string", description: "Optional search query to filter notes" },
417 |             },
418 |           },
419 |         },
420 |         {
421 |           name: "get_metadata_keys",
422 |           description: "Get all available frontmatter keys from notes",
423 |           inputSchema: {
424 |             type: "object",
425 |             properties: {},
426 |           },
427 |         },
428 |         {
429 |           name: "get_metadata_values",
430 |           description: "Get all unique values for a specific frontmatter key",
431 |           inputSchema: {
432 |             type: "object",
433 |             properties: {
434 |               key: { type: "string", description: "Frontmatter key" },
435 |             },
436 |             required: ["key"],
437 |           },
438 |         },
439 |       ],
440 |     }));
441 | 
442 |     // Tool call handler
443 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
444 |       const { name, arguments: args } = request.params;
445 | 
446 |       try {
447 |         let result: any;
448 | 
449 |         switch (name) {
450 |           // AI-Native Tools
451 |           case "list_directory":
452 |             result = await this.client.listDirectory(
453 |               args?.path as string,
454 |               args?.recursive as boolean,
455 |               args?.limit as number,
456 |               args?.offset as number
457 |             );
458 |             break;
459 | 
460 |           case "read_file":
461 |             result = await this.client.readFile(args?.path as string);
462 |             break;
463 | 
464 |           case "write_file":
465 |             result = await this.client.writeFile(
466 |               args?.path as string,
467 |               args?.content as string,
468 |               args?.mode as string
469 |             );
470 |             break;
471 | 
472 |           case "delete_item":
473 |             result = await this.client.deleteItem(args?.path as string);
474 |             break;
475 | 
476 |           case "create_or_update_note":
477 |             result = await this.client.createOrUpdateNote(
478 |               args?.path as string,
479 |               args?.content as string,
480 |               args?.frontmatter as Record<string, any>
481 |             );
482 |             break;
483 | 
484 |           case "get_daily_note":
485 |             result = await this.client.getDailyNote(args?.date as string);
486 |             break;
487 | 
488 |           case "get_recent_notes":
489 |             result = await this.client.getRecentNotes(args?.limit as number);
490 |             break;
491 | 
492 |           case "search_vault":
493 |             result = await this.client.searchVault(
494 |               args?.query as string,
495 |               args?.scope as string[],
496 |               args?.path_filter as string
497 |             );
498 |             break;
499 | 
500 |           case "find_related_notes":
501 |             result = await this.client.findRelatedNotes(
502 |               args?.path as string,
503 |               args?.on as string[]
504 |             );
505 |             break;
506 | 
507 |           // Legacy Tools (backward compatibility)
508 |           case "list_notes":
509 |             const searchQuery = args?.search as string;
510 |             if (searchQuery) {
511 |               // Use the enhanced search functionality
512 |               result = await this.client.searchVault(searchQuery, ["content", "filename", "tags"]);
513 |             } else {
514 |               result = await this.client.listNotes();
515 |             }
516 |             break;
517 | 
518 |           case "get_note":
519 |             result = await this.client.getNote(args?.path as string);
520 |             break;
521 | 
522 |           case "get_metadata_keys":
523 |             result = await this.client.getMetadataKeys();
524 |             break;
525 | 
526 |           case "get_metadata_values":
527 |             result = await this.client.getMetadataValues(args?.key as string);
528 |             break;
529 | 
530 |           default:
531 |             throw new Error(`Unknown tool: ${name}`);
532 |         }
533 | 
534 |         return {
535 |           content: [
536 |             {
537 |               type: "text",
538 |               text: JSON.stringify(result, null, 2),
539 |             },
540 |           ],
541 |         };
542 |       } catch (error) {
543 |         return {
544 |           content: [
545 |             {
546 |               type: "text",
547 |               text: `Error: ${error instanceof Error ? error.message : String(error)}`,
548 |             },
549 |           ],
550 |           isError: true,
551 |         };
552 |       }
553 |     });
554 |   }
555 | 
556 |   async start() {
557 |     const transport = new StdioServerTransport();
558 |     await this.server.connect(transport);
559 |     console.error("Obsidian MCP Server running on stdio");
560 | 
561 |     // Keep the process alive
562 |     const keepAlive = () => {
563 |       setTimeout(keepAlive, 1000);
564 |     };
565 |     keepAlive();
566 |   }
567 | }
568 | 
569 | // Main function
570 | async function main() {
571 |   // Read configuration from environment variables
572 |   const config = ConfigSchema.parse({
573 |     baseUrl: process.env.OBSIDIAN_API_URL || "http://obsidian-local-rest-api.test",
574 |     apiKey: process.env.OBSIDIAN_API_KEY,
575 |   });
576 | 
577 |   const mcpServer = new ObsidianMcpServer(config);
578 |   await mcpServer.start();
579 | }
580 | 
581 | // Error handling
582 | process.on('SIGINT', () => {
583 |   process.exit(0);
584 | });
585 | 
586 | process.on('SIGTERM', () => {
587 |   process.exit(0);
588 | });
589 | 
590 | // Run the server if this file is executed directly
591 | main().catch((error) => {
592 |   console.error("Fatal error:", error);
593 |   process.exit(1);
594 | });
595 | 
```

--------------------------------------------------------------------------------
/src/openapi.json:
--------------------------------------------------------------------------------

```json
   1 | {
   2 |   "openapi": "3.0.0",
   3 |   "info": {
   4 |     "title": "Obsidian Vault REST API",
   5 |     "version": "1.0.0"
   6 |   },
   7 |   "servers": [
   8 |     {
   9 |       "url": "https://{host}/api",
  10 |       "variables": {
  11 |         "host": {
  12 |           "default": "localhost",
  13 |           "description": "Base hostname (change to 127.0.0.1, my-vm.local, etc.)"
  14 |         }
  15 |       }
  16 |     }
  17 |   ],
  18 |   "security": [
  19 |     {
  20 |       "bearerAuth": []
  21 |     }
  22 |   ],
  23 |   "components": {
  24 |     "securitySchemes": {
  25 |       "bearerAuth": {
  26 |         "type": "http",
  27 |         "scheme": "bearer",
  28 |         "bearerFormat": "JWT"
  29 |       }
  30 |     },
  31 |     "schemas": {
  32 |       "SuccessResponse": {
  33 |         "description": "Successful operation",
  34 |         "type": "object",
  35 |         "properties": {
  36 |           "message": {
  37 |             "type": "string"
  38 |           },
  39 |           "path": {
  40 |             "type": "string"
  41 |           }
  42 |         }
  43 |       },
  44 |       "ErrorResponse": {
  45 |         "description": "Error response",
  46 |         "type": "object",
  47 |         "properties": {
  48 |           "error": {
  49 |             "type": "string"
  50 |           }
  51 |         }
  52 |       },
  53 |       "Note": {
  54 |         "description": "A vault note",
  55 |         "type": "object",
  56 |         "properties": {
  57 |           "path": {
  58 |             "type": "string"
  59 |           },
  60 |           "front_matter": {
  61 |             "type": "object",
  62 |             "additionalProperties": true
  63 |           },
  64 |           "content": {
  65 |             "type": "string"
  66 |           }
  67 |         }
  68 |       },
  69 |       "NoteInput": {
  70 |         "description": "Input for creating or replacing a note",
  71 |         "type": "object",
  72 |         "required": [
  73 |           "path"
  74 |         ],
  75 |         "properties": {
  76 |           "path": {
  77 |             "type": "string"
  78 |           },
  79 |           "front_matter": {
  80 |             "type": "object",
  81 |             "additionalProperties": true
  82 |           },
  83 |           "content": {
  84 |             "type": "string"
  85 |           }
  86 |         }
  87 |       },
  88 |       "NoteUpdate": {
  89 |         "description": "Partial update for a note",
  90 |         "type": "object",
  91 |         "required": [
  92 |           "path"
  93 |         ],
  94 |         "properties": {
  95 |           "path": {
  96 |             "type": "string"
  97 |           },
  98 |           "front_matter": {
  99 |             "type": "object",
 100 |             "additionalProperties": true
 101 |           },
 102 |           "content": {
 103 |             "type": "string"
 104 |           }
 105 |         }
 106 |       },
 107 |       "BulkDeleteInput": {
 108 |         "description": "Input for deleting multiple notes",
 109 |         "type": "object",
 110 |         "required": [
 111 |           "paths"
 112 |         ],
 113 |         "properties": {
 114 |           "paths": {
 115 |             "type": "array",
 116 |             "items": {
 117 |               "type": "string"
 118 |             }
 119 |           }
 120 |         }
 121 |       },
 122 |       "BulkDeleteResult": {
 123 |         "description": "Result of bulk delete",
 124 |         "type": "object",
 125 |         "properties": {
 126 |           "deleted": {
 127 |             "type": "array",
 128 |             "items": {
 129 |               "type": "string"
 130 |             }
 131 |           },
 132 |           "notFound": {
 133 |             "type": "array",
 134 |             "items": {
 135 |               "type": "string"
 136 |             }
 137 |           }
 138 |         }
 139 |       },
 140 |       "BulkUpdateInput": {
 141 |         "description": "Input for updating multiple notes",
 142 |         "type": "object",
 143 |         "required": [
 144 |           "items"
 145 |         ],
 146 |         "properties": {
 147 |           "items": {
 148 |             "type": "array",
 149 |             "items": {
 150 |               "$ref": "#/components/schemas/NoteUpdate"
 151 |             }
 152 |           }
 153 |         }
 154 |       },
 155 |       "BulkUpdateResult": {
 156 |         "description": "Result of bulk update",
 157 |         "type": "object",
 158 |         "properties": {
 159 |           "results": {
 160 |             "type": "array",
 161 |             "items": {
 162 |               "type": "object",
 163 |               "properties": {
 164 |                 "path": {
 165 |                   "type": "string"
 166 |                 },
 167 |                 "status": {
 168 |                   "type": "string"
 169 |                 },
 170 |                 "note": {
 171 |                   "$ref": "#/components/schemas/Note"
 172 |                 }
 173 |               }
 174 |             }
 175 |           }
 176 |         }
 177 |       }
 178 |     }
 179 |   },
 180 |   "paths": {
 181 |     "/files": {
 182 |       "get": {
 183 |         "tags": [
 184 |           "Files"
 185 |         ],
 186 |         "operationId": "listFiles",
 187 |         "summary": "List all files in the vault",
 188 |         "responses": {
 189 |           "200": {
 190 |             "description": "List of file objects, each with path, last_modified, created_at, size, and mime_type",
 191 |             "content": {
 192 |               "application/json": {
 193 |                 "schema": {
 194 |                   "type": "array",
 195 |                   "items": {
 196 |                     "type": "object",
 197 |                     "properties": {
 198 |                       "path": {
 199 |                         "type": "string"
 200 |                       },
 201 |                       "last_modified": {
 202 |                         "type": "string",
 203 |                         "format": "date-time"
 204 |                       },
 205 |                       "created_at": {
 206 |                         "type": "string",
 207 |                         "format": "date-time"
 208 |                       },
 209 |                       "size": {
 210 |                         "type": "integer"
 211 |                       },
 212 |                       "mime_type": {
 213 |                         "type": "string",
 214 |                         "nullable": true
 215 |                       }
 216 |                     }
 217 |                   }
 218 |                 }
 219 |               }
 220 |             }
 221 |           }
 222 |         }
 223 |       },
 224 |       "post": {
 225 |         "tags": [
 226 |           "Files"
 227 |         ],
 228 |         "operationId": "createFile",
 229 |         "summary": "Create a new file in the vault (legacy)",
 230 |         "requestBody": {
 231 |           "required": true,
 232 |           "content": {
 233 |             "application/json": {
 234 |               "schema": {
 235 |                 "type": "object",
 236 |                 "required": [
 237 |                   "path"
 238 |                 ],
 239 |                 "properties": {
 240 |                   "path": {
 241 |                     "type": "string"
 242 |                   },
 243 |                   "type": {
 244 |                     "type": "string",
 245 |                     "enum": [
 246 |                       "file",
 247 |                       "directory"
 248 |                     ]
 249 |                   },
 250 |                   "content": {
 251 |                     "type": "string"
 252 |                   }
 253 |                 }
 254 |               }
 255 |             }
 256 |           }
 257 |         },
 258 |         "responses": {
 259 |           "201": {
 260 |             "description": "File created successfully",
 261 |             "content": {
 262 |               "application/json": {
 263 |                 "schema": {
 264 |                   "$ref": "#/components/schemas/SuccessResponse"
 265 |                 }
 266 |               }
 267 |             }
 268 |           },
 269 |           "409": {
 270 |             "description": "File already exists",
 271 |             "content": {
 272 |               "application/json": {
 273 |                 "schema": {
 274 |                   "$ref": "#/components/schemas/ErrorResponse"
 275 |                 }
 276 |               }
 277 |             }
 278 |           }
 279 |         }
 280 |       }
 281 |     },
 282 |     "/files/write": {
 283 |       "post": {
 284 |         "tags": [
 285 |           "Files"
 286 |         ],
 287 |         "operationId": "writeFileSmart",
 288 |         "summary": "Write a file with mode (overwrite, append, prepend)",
 289 |         "requestBody": {
 290 |           "required": true,
 291 |           "content": {
 292 |             "application/json": {
 293 |               "schema": {
 294 |                 "type": "object",
 295 |                 "required": [
 296 |                   "path",
 297 |                   "content"
 298 |                 ],
 299 |                 "properties": {
 300 |                   "path": {
 301 |                     "type": "string"
 302 |                   },
 303 |                   "content": {
 304 |                     "type": "string"
 305 |                   },
 306 |                   "mode": {
 307 |                     "type": "string",
 308 |                     "enum": [
 309 |                       "overwrite",
 310 |                       "append",
 311 |                       "prepend"
 312 |                     ],
 313 |                     "default": "overwrite"
 314 |                   }
 315 |                 }
 316 |               }
 317 |             }
 318 |           }
 319 |         },
 320 |         "responses": {
 321 |           "200": {
 322 |             "description": "Write completed",
 323 |             "content": {
 324 |               "application/json": {
 325 |                 "schema": {
 326 |                   "type": "object",
 327 |                   "properties": {
 328 |                     "message": {
 329 |                       "type": "string"
 330 |                     },
 331 |                     "path": {
 332 |                       "type": "string"
 333 |                     },
 334 |                     "mode": {
 335 |                       "type": "string"
 336 |                     },
 337 |                     "size": {
 338 |                       "type": "integer"
 339 |                     }
 340 |                   }
 341 |                 }
 342 |               }
 343 |             }
 344 |           }
 345 |         }
 346 |       }
 347 |     },
 348 |     "/files/{path}": {
 349 |       "get": {
 350 |         "tags": [
 351 |           "Files"
 352 |         ],
 353 |         "operationId": "getFile",
 354 |         "summary": "Get raw content of a file",
 355 |         "parameters": [
 356 |           {
 357 |             "in": "path",
 358 |             "name": "path",
 359 |             "schema": {
 360 |               "type": "string"
 361 |             },
 362 |             "style": "simple",
 363 |             "explode": false,
 364 |             "allowReserved": true,
 365 |             "required": true,
 366 |             "description": "URL-encoded vault-relative path to file"
 367 |           }
 368 |         ],
 369 |         "responses": {
 370 |           "200": {
 371 |             "description": "Raw file content",
 372 |             "content": {
 373 |               "text/plain": {
 374 |                 "schema": {
 375 |                   "type": "string"
 376 |                 }
 377 |               },
 378 |               "application/octet-stream": {
 379 |                 "schema": {
 380 |                   "type": "string",
 381 |                   "format": "binary"
 382 |                 }
 383 |               }
 384 |             }
 385 |           },
 386 |           "404": {
 387 |             "description": "File not found",
 388 |             "content": {
 389 |               "application/json": {
 390 |                 "schema": {
 391 |                   "$ref": "#/components/schemas/ErrorResponse"
 392 |                 }
 393 |               }
 394 |             }
 395 |           }
 396 |         }
 397 |       },
 398 |       "put": {
 399 |         "tags": [
 400 |           "Files"
 401 |         ],
 402 |         "operationId": "updateFile",
 403 |         "summary": "Update file content (legacy)",
 404 |         "parameters": [
 405 |           {
 406 |             "in": "path",
 407 |             "name": "path",
 408 |             "schema": {
 409 |               "type": "string"
 410 |             },
 411 |             "style": "simple",
 412 |             "explode": false,
 413 |             "allowReserved": true,
 414 |             "required": true
 415 |           }
 416 |         ],
 417 |         "requestBody": {
 418 |           "required": true,
 419 |           "content": {
 420 |             "application/json": {
 421 |               "schema": {
 422 |                 "type": "object",
 423 |                 "required": [
 424 |                   "content"
 425 |                 ],
 426 |                 "properties": {
 427 |                   "content": {
 428 |                     "type": "string"
 429 |                   }
 430 |                 }
 431 |               }
 432 |             }
 433 |           }
 434 |         },
 435 |         "responses": {
 436 |           "200": {
 437 |             "description": "File updated successfully",
 438 |             "content": {
 439 |               "application/json": {
 440 |                 "schema": {
 441 |                   "$ref": "#/components/schemas/SuccessResponse"
 442 |                 }
 443 |               }
 444 |             }
 445 |           },
 446 |           "404": {
 447 |             "description": "File not found",
 448 |             "content": {
 449 |               "application/json": {
 450 |                 "schema": {
 451 |                   "$ref": "#/components/schemas/ErrorResponse"
 452 |                 }
 453 |               }
 454 |             }
 455 |           }
 456 |         }
 457 |       },
 458 |       "delete": {
 459 |         "tags": [
 460 |           "Files"
 461 |         ],
 462 |         "operationId": "deleteFile",
 463 |         "summary": "Delete a file or directory",
 464 |         "parameters": [
 465 |           {
 466 |             "in": "path",
 467 |             "name": "path",
 468 |             "schema": {
 469 |               "type": "string"
 470 |             },
 471 |             "style": "simple",
 472 |             "explode": false,
 473 |             "allowReserved": true,
 474 |             "required": true
 475 |           }
 476 |         ],
 477 |         "responses": {
 478 |           "200": {
 479 |             "description": "File deleted successfully",
 480 |             "content": {
 481 |               "application/json": {
 482 |                 "schema": {
 483 |                   "$ref": "#/components/schemas/SuccessResponse"
 484 |                 }
 485 |               }
 486 |             }
 487 |           },
 488 |           "404": {
 489 |             "description": "File not found",
 490 |             "content": {
 491 |               "application/json": {
 492 |                 "schema": {
 493 |                   "$ref": "#/components/schemas/ErrorResponse"
 494 |                 }
 495 |               }
 496 |             }
 497 |           }
 498 |         }
 499 |       }
 500 |     },
 501 |     "/notes": {
 502 |       "get": {
 503 |         "tags": [
 504 |           "Notes"
 505 |         ],
 506 |         "operationId": "listNotes",
 507 |         "summary": "List all markdown notes with parsed front matter",
 508 |         "responses": {
 509 |           "200": {
 510 |             "description": "List of notes (Laravel Resource)",
 511 |             "content": {
 512 |               "application/json": {
 513 |                 "schema": {
 514 |                   "type": "array",
 515 |                   "items": {
 516 |                     "$ref": "#/components/schemas/Note"
 517 |                   }
 518 |                 }
 519 |               }
 520 |             }
 521 |           }
 522 |         }
 523 |       },
 524 |       "post": {
 525 |         "tags": [
 526 |           "Notes"
 527 |         ],
 528 |         "operationId": "createNote",
 529 |         "summary": "Create a new note (legacy)",
 530 |         "requestBody": {
 531 |           "required": true,
 532 |           "content": {
 533 |             "application/json": {
 534 |               "schema": {
 535 |                 "$ref": "#/components/schemas/NoteInput"
 536 |               }
 537 |             }
 538 |           }
 539 |         },
 540 |         "responses": {
 541 |           "201": {
 542 |             "description": "Note created",
 543 |             "content": {
 544 |               "application/json": {
 545 |                 "schema": {
 546 |                   "$ref": "#/components/schemas/Note"
 547 |                 }
 548 |               }
 549 |             }
 550 |           }
 551 |         }
 552 |       }
 553 |     },
 554 |     "/notes/upsert": {
 555 |       "post": {
 556 |         "tags": [
 557 |           "Notes"
 558 |         ],
 559 |         "operationId": "upsertNote",
 560 |         "summary": "Create or update a note intelligently",
 561 |         "requestBody": {
 562 |           "required": true,
 563 |           "content": {
 564 |             "application/json": {
 565 |               "schema": {
 566 |                 "type": "object",
 567 |                 "required": [
 568 |                   "path"
 569 |                 ],
 570 |                 "properties": {
 571 |                   "path": {
 572 |                     "type": "string"
 573 |                   },
 574 |                   "front_matter": {
 575 |                     "type": "object",
 576 |                     "additionalProperties": true
 577 |                   },
 578 |                   "content": {
 579 |                     "type": "string"
 580 |                   }
 581 |                 }
 582 |               }
 583 |             }
 584 |           }
 585 |         },
 586 |         "responses": {
 587 |           "200": {
 588 |             "description": "Note upserted",
 589 |             "content": {
 590 |               "application/json": {
 591 |                 "schema": {
 592 |                   "$ref": "#/components/schemas/Note"
 593 |                 }
 594 |               }
 595 |             }
 596 |           }
 597 |         }
 598 |       }
 599 |     },
 600 |     "/notes/{path}": {
 601 |       "get": {
 602 |         "tags": [
 603 |           "Notes"
 604 |         ],
 605 |         "operationId": "getNote",
 606 |         "summary": "Retrieve a single note",
 607 |         "parameters": [
 608 |           {
 609 |             "in": "path",
 610 |             "name": "path",
 611 |             "schema": {
 612 |               "type": "string"
 613 |             },
 614 |             "style": "simple",
 615 |             "explode": false,
 616 |             "allowReserved": true,
 617 |             "required": true
 618 |           }
 619 |         ],
 620 |         "responses": {
 621 |           "200": {
 622 |             "description": "Note object",
 623 |             "content": {
 624 |               "application/json": {
 625 |                 "schema": {
 626 |                   "$ref": "#/components/schemas/Note"
 627 |                 }
 628 |               }
 629 |             }
 630 |           },
 631 |           "404": {
 632 |             "description": "Note not found",
 633 |             "content": {
 634 |               "application/json": {
 635 |                 "schema": {
 636 |                   "$ref": "#/components/schemas/ErrorResponse"
 637 |                 }
 638 |               }
 639 |             }
 640 |           }
 641 |         }
 642 |       },
 643 |       "put": {
 644 |         "tags": [
 645 |           "Notes"
 646 |         ],
 647 |         "operationId": "replaceNote",
 648 |         "summary": "Replace a note (legacy)",
 649 |         "parameters": [
 650 |           {
 651 |             "in": "path",
 652 |             "name": "path",
 653 |             "schema": {
 654 |               "type": "string"
 655 |             },
 656 |             "required": true
 657 |           }
 658 |         ],
 659 |         "requestBody": {
 660 |           "required": true,
 661 |           "content": {
 662 |             "application/json": {
 663 |               "schema": {
 664 |                 "$ref": "#/components/schemas/NoteInput"
 665 |               }
 666 |             }
 667 |           }
 668 |         },
 669 |         "responses": {
 670 |           "200": {
 671 |             "description": "Note replaced",
 672 |             "content": {
 673 |               "application/json": {
 674 |                 "schema": {
 675 |                   "$ref": "#/components/schemas/Note"
 676 |                 }
 677 |               }
 678 |             }
 679 |           },
 680 |           "404": {
 681 |             "description": "Note not found",
 682 |             "content": {
 683 |               "application/json": {
 684 |                 "schema": {
 685 |                   "$ref": "#/components/schemas/ErrorResponse"
 686 |                 }
 687 |               }
 688 |             }
 689 |           }
 690 |         }
 691 |       },
 692 |       "patch": {
 693 |         "tags": [
 694 |           "Notes"
 695 |         ],
 696 |         "operationId": "updateNote",
 697 |         "summary": "Update parts of a note (legacy)",
 698 |         "parameters": [
 699 |           {
 700 |             "in": "path",
 701 |             "name": "path",
 702 |             "schema": {
 703 |               "type": "string"
 704 |             },
 705 |             "required": true
 706 |           }
 707 |         ],
 708 |         "requestBody": {
 709 |           "content": {
 710 |             "application/json": {
 711 |               "schema": {
 712 |                 "type": "object",
 713 |                 "properties": {
 714 |                   "front_matter": {
 715 |                     "type": "object"
 716 |                   },
 717 |                   "content": {
 718 |                     "type": "string"
 719 |                   }
 720 |                 }
 721 |               }
 722 |             }
 723 |           }
 724 |         },
 725 |         "responses": {
 726 |           "200": {
 727 |             "description": "Note updated",
 728 |             "content": {
 729 |               "application/json": {
 730 |                 "schema": {
 731 |                   "$ref": "#/components/schemas/Note"
 732 |                 }
 733 |               }
 734 |             }
 735 |           },
 736 |           "404": {
 737 |             "description": "Note not found",
 738 |             "content": {
 739 |               "application/json": {
 740 |                 "schema": {
 741 |                   "$ref": "#/components/schemas/ErrorResponse"
 742 |                 }
 743 |               }
 744 |             }
 745 |           }
 746 |         }
 747 |       },
 748 |       "delete": {
 749 |         "tags": [
 750 |           "Notes"
 751 |         ],
 752 |         "operationId": "deleteNote",
 753 |         "summary": "Delete a note",
 754 |         "parameters": [
 755 |           {
 756 |             "in": "path",
 757 |             "name": "path",
 758 |             "schema": {
 759 |               "type": "string"
 760 |             },
 761 |             "required": true
 762 |           }
 763 |         ],
 764 |         "responses": {
 765 |           "200": {
 766 |             "description": "Note deleted",
 767 |             "content": {
 768 |               "application/json": {
 769 |                 "schema": {
 770 |                   "$ref": "#/components/schemas/SuccessResponse"
 771 |                 }
 772 |               }
 773 |             }
 774 |           },
 775 |           "404": {
 776 |             "description": "Note not found",
 777 |             "content": {
 778 |               "application/json": {
 779 |                 "schema": {
 780 |                   "$ref": "#/components/schemas/ErrorResponse"
 781 |                 }
 782 |               }
 783 |             }
 784 |           }
 785 |         }
 786 |       }
 787 |     },
 788 |     "/bulk/notes/delete": {
 789 |       "delete": {
 790 |         "tags": [
 791 |           "Notes"
 792 |         ],
 793 |         "operationId": "bulkDeleteNotes",
 794 |         "summary": "Delete multiple notes",
 795 |         "requestBody": {
 796 |           "required": true,
 797 |           "content": {
 798 |             "application/json": {
 799 |               "schema": {
 800 |                 "$ref": "#/components/schemas/BulkDeleteInput"
 801 |               }
 802 |             }
 803 |           }
 804 |         },
 805 |         "responses": {
 806 |           "200": {
 807 |             "description": "Bulk delete result",
 808 |             "content": {
 809 |               "application/json": {
 810 |                 "schema": {
 811 |                   "$ref": "#/components/schemas/BulkDeleteResult"
 812 |                 }
 813 |               }
 814 |             }
 815 |           }
 816 |         }
 817 |       }
 818 |     },
 819 |     "/bulk/notes/update": {
 820 |       "patch": {
 821 |         "tags": [
 822 |           "Notes"
 823 |         ],
 824 |         "operationId": "bulkUpdateNotes",
 825 |         "summary": "Update multiple notes",
 826 |         "requestBody": {
 827 |           "required": true,
 828 |           "content": {
 829 |             "application/json": {
 830 |               "schema": {
 831 |                 "$ref": "#/components/schemas/BulkUpdateInput"
 832 |               }
 833 |             }
 834 |           }
 835 |         },
 836 |         "responses": {
 837 |           "200": {
 838 |             "description": "Bulk update result",
 839 |             "content": {
 840 |               "application/json": {
 841 |                 "schema": {
 842 |                   "$ref": "#/components/schemas/BulkUpdateResult"
 843 |                 }
 844 |               }
 845 |             }
 846 |           }
 847 |         }
 848 |       }
 849 |     },
 850 |     "/metadata/keys": {
 851 |       "get": {
 852 |         "tags": [
 853 |           "Metadata"
 854 |         ],
 855 |         "operationId": "listMetadataKeys",
 856 |         "summary": "List all front matter keys across notes",
 857 |         "responses": {
 858 |           "200": {
 859 |             "description": "Unique keys",
 860 |             "content": {
 861 |               "application/json": {
 862 |                 "schema": {
 863 |                   "type": "object",
 864 |                   "required": [
 865 |                     "data"
 866 |                   ],
 867 |                   "properties": {
 868 |                     "data": {
 869 |                       "type": "array",
 870 |                       "items": {
 871 |                         "type": "string"
 872 |                       }
 873 |                     }
 874 |                   }
 875 |                 }
 876 |               }
 877 |             }
 878 |           }
 879 |         }
 880 |       },
 881 |       "/metadata/values/{key}": {
 882 |         "get": {
 883 |           "tags": [
 884 |             "Metadata"
 885 |           ],
 886 |           "operationId": "listMetadataValues",
 887 |           "summary": "List unique values for a front matter key",
 888 |           "parameters": [
 889 |             {
 890 |               "in": "path",
 891 |               "name": "key",
 892 |               "schema": {
 893 |                 "type": "string"
 894 |               },
 895 |               "required": true
 896 |             }
 897 |           ],
 898 |           "responses": {
 899 |             "200": {
 900 |               "description": "Unique values",
 901 |               "content": {
 902 |                 "application/json": {
 903 |                   "schema": {
 904 |                     "type": "object",
 905 |                     "required": [
 906 |                       "data"
 907 |                     ],
 908 |                     "properties": {
 909 |                       "data": {
 910 |                         "type": "array",
 911 |                         "items": {
 912 |                           "oneOf": [
 913 |                             {
 914 |                               "type": "string"
 915 |                             },
 916 |                             {
 917 |                               "type": "number"
 918 |                             }
 919 |                           ]
 920 |                         }
 921 |                       }
 922 |                     }
 923 |                   }
 924 |                 }
 925 |               }
 926 |             }
 927 |           }
 928 |         },
 929 |         "/vault/directory": {
 930 |           "get": {
 931 |             "tags": [
 932 |               "Vault"
 933 |             ],
 934 |             "operationId": "listDirectory",
 935 |             "summary": "List directory contents with pagination",
 936 |             "parameters": [
 937 |               {
 938 |                 "in": "query",
 939 |                 "name": "path",
 940 |                 "schema": {
 941 |                   "type": "string",
 942 |                   "default": "."
 943 |                 }
 944 |               },
 945 |               {
 946 |                 "in": "query",
 947 |                 "name": "recursive",
 948 |                 "schema": {
 949 |                   "type": "boolean",
 950 |                   "default": false
 951 |                 }
 952 |               },
 953 |               {
 954 |                 "in": "query",
 955 |                 "name": "limit",
 956 |                 "schema": {
 957 |                   "type": "integer",
 958 |                   "default": 50
 959 |                 }
 960 |               },
 961 |               {
 962 |                 "in": "query",
 963 |                 "name": "offset",
 964 |                 "schema": {
 965 |                   "type": "integer",
 966 |                   "default": 0
 967 |                 }
 968 |               }
 969 |             ],
 970 |             "responses": {
 971 |               "200": {
 972 |                 "description": "Directory listing",
 973 |                 "content": {
 974 |                   "application/json": {
 975 |                     "schema": {
 976 |                       "type": "object",
 977 |                       "additionalProperties": true
 978 |                     }
 979 |                   }
 980 |                 }
 981 |               }
 982 |             }
 983 |           }
 984 |         },
 985 |         "/vault/search": {
 986 |           "get": {
 987 |             "tags": [
 988 |               "Vault"
 989 |             ],
 990 |             "operationId": "searchVault",
 991 |             "summary": "Search vault content across content, filenames, and tags",
 992 |             "parameters": [
 993 |               {
 994 |                 "in": "query",
 995 |                 "name": "query",
 996 |                 "schema": {
 997 |                   "type": "string"
 998 |                 },
 999 |                 "required": true
1000 |               },
1001 |               {
1002 |                 "in": "query",
1003 |                 "name": "scope",
1004 |                 "schema": {
1005 |                   "type": "string",
1006 |                   "description": "Comma-separated: content,filename,tags"
1007 |                 }
1008 |               },
1009 |               {
1010 |                 "in": "query",
1011 |                 "name": "path_filter",
1012 |                 "schema": {
1013 |                   "type": "string"
1014 |                 }
1015 |               }
1016 |             ],
1017 |             "responses": {
1018 |               "200": {
1019 |                 "description": "Search results",
1020 |                 "content": {
1021 |                   "application/json": {
1022 |                     "schema": {
1023 |                       "type": "object",
1024 |                       "additionalProperties": true
1025 |                     }
1026 |                   }
1027 |                 }
1028 |               },
1029 |               "400": {
1030 |                 "description": "Missing query"
1031 |               }
1032 |             }
1033 |           }
1034 |         },
1035 |         "/vault/notes/recent": {
1036 |           "get": {
1037 |             "tags": [
1038 |               "Vault"
1039 |             ],
1040 |             "operationId": "getRecentNotes",
1041 |             "summary": "Get recently modified notes",
1042 |             "parameters": [
1043 |               {
1044 |                 "in": "query",
1045 |                 "name": "limit",
1046 |                 "schema": {
1047 |                   "type": "integer",
1048 |                   "default": 5
1049 |                 }
1050 |               }
1051 |             ],
1052 |             "responses": {
1053 |               "200": {
1054 |                 "description": "Recent notes",
1055 |                 "content": {
1056 |                   "application/json": {
1057 |                     "schema": {
1058 |                       "type": "array",
1059 |                       "items": {
1060 |                         "$ref": "#/components/schemas/Note"
1061 |                       }
1062 |                     }
1063 |                   }
1064 |                 }
1065 |               }
1066 |             }
1067 |           }
1068 |         },
1069 |         "/vault/notes/daily": {
1070 |           "get": {
1071 |             "tags": [
1072 |               "Vault"
1073 |             ],
1074 |             "operationId": "getDailyNote",
1075 |             "summary": "Get daily note by date or shortcuts",
1076 |             "parameters": [
1077 |               {
1078 |                 "in": "query",
1079 |                 "name": "date",
1080 |                 "schema": {
1081 |                   "type": "string",
1082 |                   "description": "today|yesterday|tomorrow|YYYY-MM-DD"
1083 |                 }
1084 |               }
1085 |             ],
1086 |             "responses": {
1087 |               "200": {
1088 |                 "description": "Daily note",
1089 |                 "content": {
1090 |                   "application/json": {
1091 |                     "schema": {
1092 |                       "$ref": "#/components/schemas/Note"
1093 |                     }
1094 |                   }
1095 |                 }
1096 |               },
1097 |               "404": {
1098 |                 "description": "Not found"
1099 |               },
1100 |               "400": {
1101 |                 "description": "Invalid date"
1102 |               }
1103 |             }
1104 |           }
1105 |         },
1106 |         "/vault/notes/related/{path}": {
1107 |           "get": {
1108 |             "tags": [
1109 |               "Vault"
1110 |             ],
1111 |             "operationId": "getRelatedNotes",
1112 |             "summary": "Find related notes based on tags and links",
1113 |             "parameters": [
1114 |               {
1115 |                 "in": "path",
1116 |                 "name": "path",
1117 |                 "schema": {
1118 |                   "type": "string"
1119 |                 },
1120 |                 "required": true
1121 |               },
1122 |               {
1123 |                 "in": "query",
1124 |                 "name": "on",
1125 |                 "schema": {
1126 |                   "type": "string",
1127 |                   "description": "Comma-separated: tags,links"
1128 |                 }
1129 |               },
1130 |               {
1131 |                 "in": "query",
1132 |                 "name": "limit",
1133 |                 "schema": {
1134 |                   "type": "integer",
1135 |                   "default": 10
1136 |                 }
1137 |               }
1138 |             ],
1139 |             "responses": {
1140 |               "200": {
1141 |                 "description": "Related notes",
1142 |                 "content": {
1143 |                   "application/json": {
1144 |                     "schema": {
1145 |                       "type": "object",
1146 |                       "additionalProperties": true
1147 |                     }
1148 |                   }
1149 |                 }
1150 |               },
1151 |               "404": {
1152 |                 "description": "Source note not found"
1153 |               }
1154 |             }
1155 |           }
1156 |         },
1157 |         "/vault/overview": {
1158 |           "get": {
1159 |             "tags": [
1160 |               "Vault"
1161 |             ],
1162 |             "operationId": "getVaultOverview",
1163 |             "summary": "Vault overview including README and first two folder levels",
1164 |             "responses": {
1165 |               "200": {
1166 |                 "description": "Overview",
1167 |                 "content": {
1168 |                   "application/json": {
1169 |                     "schema": {
1170 |                       "type": "object",
1171 |                       "additionalProperties": true
1172 |                     }
1173 |                   }
1174 |                 }
1175 |               }
1176 |             }
1177 |           }
1178 |         },
1179 |         "/agent/grep": {
1180 |           "post": {
1181 |             "tags": [
1182 |               "Agent"
1183 |             ],
1184 |             "operationId": "grepVault",
1185 |             "summary": "Search vault content with regex and patterns",
1186 |             "requestBody": {
1187 |               "required": true,
1188 |               "content": {
1189 |                 "application/json": {
1190 |                   "schema": {
1191 |                     "type": "object",
1192 |                     "required": [
1193 |                       "pattern"
1194 |                     ],
1195 |                     "properties": {
1196 |                       "pattern": {
1197 |                         "type": "string"
1198 |                       },
1199 |                       "is_regex": {
1200 |                         "type": "boolean"
1201 |                       },
1202 |                       "case_sensitive": {
1203 |                         "type": "boolean"
1204 |                       },
1205 |                       "include_frontmatter": {
1206 |                         "type": "boolean"
1207 |                       },
1208 |                       "file_pattern": {
1209 |                         "type": "string"
1210 |                       },
1211 |                       "max_results": {
1212 |                         "type": "integer"
1213 |                       },
1214 |                       "context_lines": {
1215 |                         "type": "integer"
1216 |                       }
1217 |                     }
1218 |                   }
1219 |                 }
1220 |               }
1221 |             },
1222 |             "responses": {
1223 |               "200": {
1224 |                 "description": "Search summary",
1225 |                 "content": {
1226 |                   "application/json": {
1227 |                     "schema": {
1228 |                       "type": "object",
1229 |                       "additionalProperties": true
1230 |                     }
1231 |                   }
1232 |                 }
1233 |               }
1234 |             }
1235 |           }
1236 |         },
1237 |         "/agent/query-frontmatter": {
1238 |           "post": {
1239 |             "tags": [
1240 |               "Agent"
1241 |             ],
1242 |             "operationId": "queryFrontmatter",
1243 |             "summary": "Query frontmatter fields across notes",
1244 |             "requestBody": {
1245 |               "required": false,
1246 |               "content": {
1247 |                 "application/json": {
1248 |                   "schema": {
1249 |                     "type": "object",
1250 |                     "properties": {
1251 |                       "fields": {
1252 |                         "type": "array",
1253 |                         "items": {
1254 |                           "type": "string"
1255 |                         }
1256 |                       },
1257 |                       "where": {
1258 |                         "type": "object",
1259 |                         "additionalProperties": true
1260 |                       },
1261 |                       "sort_by": {
1262 |                         "type": "string"
1263 |                       },
1264 |                       "sort_direction": {
1265 |                         "type": "string",
1266 |                         "enum": [
1267 |                           "asc",
1268 |                           "desc"
1269 |                         ]
1270 |                       },
1271 |                       "limit": {
1272 |                         "type": "integer"
1273 |                       },
1274 |                       "distinct": {
1275 |                         "type": "boolean"
1276 |                       }
1277 |                     }
1278 |                   }
1279 |                 }
1280 |               }
1281 |             },
1282 |             "responses": {
1283 |               "200": {
1284 |                 "description": "Query results",
1285 |                 "content": {
1286 |                   "application/json": {
1287 |                     "schema": {
1288 |                       "type": "object",
1289 |                       "additionalProperties": true
1290 |                     }
1291 |                   }
1292 |                 }
1293 |               }
1294 |             }
1295 |           }
1296 |         },
1297 |         "/agent/backlinks/{note_path}": {
1298 |           "get": {
1299 |             "tags": [
1300 |               "Agent"
1301 |             ],
1302 |             "operationId": "getBacklinks",
1303 |             "summary": "Find backlinks and mentions for a note",
1304 |             "parameters": [
1305 |               {
1306 |                 "in": "path",
1307 |                 "name": "note_path",
1308 |                 "schema": {
1309 |                   "type": "string"
1310 |                 },
1311 |                 "required": true
1312 |               },
1313 |               {
1314 |                 "in": "query",
1315 |                 "name": "include_mentions",
1316 |                 "schema": {
1317 |                   "type": "boolean",
1318 |                   "default": true
1319 |                 }
1320 |               },
1321 |               {
1322 |                 "in": "query",
1323 |                 "name": "include_tags",
1324 |                 "schema": {
1325 |                   "type": "boolean",
1326 |                   "default": false
1327 |                 }
1328 |               }
1329 |             ],
1330 |             "responses": {
1331 |               "200": {
1332 |                 "description": "Backlink summary",
1333 |                 "content": {
1334 |                   "application/json": {
1335 |                     "schema": {
1336 |                       "type": "object",
1337 |                       "additionalProperties": true
1338 |                     }
1339 |                   }
1340 |                 }
1341 |               }
1342 |             }
1343 |           }
1344 |         },
1345 |         "/agent/tags": {
1346 |           "get": {
1347 |             "tags": [
1348 |               "Agent"
1349 |             ],
1350 |             "operationId": "getTags",
1351 |             "summary": "List tags across notes",
1352 |             "parameters": [
1353 |               {
1354 |                 "in": "query",
1355 |                 "name": "min_count",
1356 |                 "schema": {
1357 |                   "type": "integer",
1358 |                   "default": 1
1359 |                 }
1360 |               },
1361 |               {
1362 |                 "in": "query",
1363 |                 "name": "include_nested",
1364 |                 "schema": {
1365 |                   "type": "boolean",
1366 |                   "default": true
1367 |                 }
1368 |               },
1369 |               {
1370 |                 "in": "query",
1371 |                 "name": "format",
1372 |                 "schema": {
1373 |                   "type": "string",
1374 |                   "enum": [
1375 |                     "flat",
1376 |                     "hierarchical"
1377 |                   ],
1378 |                   "default": "flat"
1379 |                 }
1380 |               }
1381 |             ],
1382 |             "responses": {
1383 |               "200": {
1384 |                 "description": "Tags",
1385 |                 "content": {
1386 |                   "application/json": {
1387 |                     "schema": {
1388 |                       "type": "object",
1389 |                       "additionalProperties": true
1390 |                     }
1391 |                   }
1392 |                 }
1393 |               }
1394 |             }
1395 |           }
1396 |         },
1397 |         "/agent/stats": {
1398 |           "get": {
1399 |             "tags": [
1400 |               "Agent"
1401 |             ],
1402 |             "operationId": "getVaultStats",
1403 |             "summary": "Get vault statistics and health metrics",
1404 |             "responses": {
1405 |               "200": {
1406 |                 "description": "Stats",
1407 |                 "content": {
1408 |                   "application/json": {
1409 |                     "schema": {
1410 |                       "type": "object",
1411 |                       "additionalProperties": true
1412 |                     }
1413 |                   }
1414 |                 }
1415 |               }
1416 |             }
1417 |           }
1418 |         }
1419 |       },
1420 |       "components": {
1421 |         "securitySchemes": {
1422 |           "bearerAuth": {
1423 |             "type": "http",
1424 |             "scheme": "bearer",
1425 |             "bearerFormat": "JWT"
1426 |           }
1427 |         },
1428 |         "schemas": {
1429 |           "SuccessResponse": {
1430 |             "description": "Successful operation",
1431 |             "type": "object",
1432 |             "properties": {
1433 |               "message": {
1434 |                 "type": "string"
1435 |               },
1436 |               "path": {
1437 |                 "type": "string"
1438 |               }
1439 |             }
1440 |           },
1441 |           "ErrorResponse": {
1442 |             "description": "Error response",
1443 |             "type": "object",
1444 |             "properties": {
1445 |               "error": {
1446 |                 "type": "string"
1447 |               }
1448 |             }
1449 |           },
1450 |           "Note": {
1451 |             "description": "A vault note",
1452 |             "type": "object",
1453 |             "properties": {
1454 |               "path": {
1455 |                 "type": "string"
1456 |               },
1457 |               "front_matter": {
1458 |                 "type": "object",
1459 |                 "additionalProperties": true
1460 |               },
1461 |               "content": {
1462 |                 "type": "string"
1463 |               }
1464 |             }
1465 |           },
1466 |           "NoteInput": {
1467 |             "description": "Input for creating or replacing a note",
1468 |             "type": "object",
1469 |             "required": [
1470 |               "path"
1471 |             ],
1472 |             "properties": {
1473 |               "path": {
1474 |                 "type": "string"
1475 |               },
1476 |               "front_matter": {
1477 |                 "type": "object",
1478 |                 "additionalProperties": true
1479 |               },
1480 |               "content": {
1481 |                 "type": "string"
1482 |               }
1483 |             }
1484 |           },
1485 |           "NoteUpdate": {
1486 |             "description": "Partial update for a note",
1487 |             "type": "object",
1488 |             "required": [
1489 |               "path"
1490 |             ],
1491 |             "properties": {
1492 |               "path": {
1493 |                 "type": "string"
1494 |               },
1495 |               "front_matter": {
1496 |                 "type": "object",
1497 |                 "additionalProperties": true
1498 |               },
1499 |               "content": {
1500 |                 "type": "string"
1501 |               }
1502 |             }
1503 |           },
1504 |           "BulkDeleteInput": {
1505 |             "description": "Input for deleting multiple notes",
1506 |             "type": "object",
1507 |             "required": [
1508 |               "paths"
1509 |             ],
1510 |             "properties": {
1511 |               "paths": {
1512 |                 "type": "array",
1513 |                 "items": {
1514 |                   "type": "string"
1515 |                 }
1516 |               }
1517 |             }
1518 |           },
1519 |           "BulkDeleteResult": {
1520 |             "description": "Result of bulk delete",
1521 |             "type": "object",
1522 |             "properties": {
1523 |               "deleted": {
1524 |                 "type": "array",
1525 |                 "items": {
1526 |                   "type": "string"
1527 |                 }
1528 |               },
1529 |               "notFound": {
1530 |                 "type": "array",
1531 |                 "items": {
1532 |                   "type": "string"
1533 |                 }
1534 |               }
1535 |             }
1536 |           },
1537 |           "BulkUpdateInput": {
1538 |             "description": "Input for updating multiple notes",
1539 |             "type": "object",
1540 |             "required": [
1541 |               "items"
1542 |             ],
1543 |             "properties": {
1544 |               "items": {
1545 |                 "type": "array",
1546 |                 "items": {
1547 |                   "$ref": "#/components/schemas/NoteUpdate"
1548 |                 }
1549 |               }
1550 |             }
1551 |           },
1552 |           "BulkUpdateResult": {
1553 |             "description": "Result of bulk update",
1554 |             "type": "object",
1555 |             "properties": {
1556 |               "results": {
1557 |                 "type": "array",
1558 |                 "items": {
1559 |                   "type": "object",
1560 |                   "properties": {
1561 |                     "path": {
1562 |                       "type": "string"
1563 |                     },
1564 |                     "status": {
1565 |                       "type": "string"
1566 |                     },
1567 |                     "note": {
1568 |                       "$ref": "#/components/schemas/Note"
1569 |                     }
1570 |                   }
1571 |                 }
1572 |               }
1573 |             }
1574 |           }
1575 |         }
1576 |       }
1577 |     },
1578 |     "/metadata/values/{key}": {
1579 |       "get": {
1580 |         "tags": [
1581 |           "Metadata"
1582 |         ],
1583 |         "operationId": "listMetadataValues",
1584 |         "summary": "List unique values for a front matter key",
1585 |         "parameters": [
1586 |           {
1587 |             "in": "path",
1588 |             "name": "key",
1589 |             "schema": {
1590 |               "type": "string"
1591 |             },
1592 |             "required": true
1593 |           }
1594 |         ],
1595 |         "responses": {
1596 |           "200": {
1597 |             "description": "Unique values",
1598 |             "content": {
1599 |               "application/json": {
1600 |                 "schema": {
1601 |                   "type": "object",
1602 |                   "required": [
1603 |                     "data"
1604 |                   ],
1605 |                   "properties": {
1606 |                     "data": {
1607 |                       "type": "array",
1608 |                       "items": {
1609 |                         "oneOf": [
1610 |                           {
1611 |                             "type": "string"
1612 |                           },
1613 |                           {
1614 |                             "type": "number"
1615 |                           }
1616 |                         ]
1617 |                       }
1618 |                     }
1619 |                   }
1620 |                 }
1621 |               }
1622 |             }
1623 |           }
1624 |         }
1625 |       },
1626 |       "/vault/directory": {
1627 |         "get": {
1628 |           "tags": [
1629 |             "Vault"
1630 |           ],
1631 |           "operationId": "listDirectory",
1632 |           "summary": "List directory contents with pagination",
1633 |           "parameters": [
1634 |             {
1635 |               "in": "query",
1636 |               "name": "path",
1637 |               "schema": {
1638 |                 "type": "string",
1639 |                 "default": "."
1640 |               }
1641 |             },
1642 |             {
1643 |               "in": "query",
1644 |               "name": "recursive",
1645 |               "schema": {
1646 |                 "type": "boolean",
1647 |                 "default": false
1648 |               }
1649 |             },
1650 |             {
1651 |               "in": "query",
1652 |               "name": "limit",
1653 |               "schema": {
1654 |                 "type": "integer",
1655 |                 "default": 50
1656 |               }
1657 |             },
1658 |             {
1659 |               "in": "query",
1660 |               "name": "offset",
1661 |               "schema": {
1662 |                 "type": "integer",
1663 |                 "default": 0
1664 |               }
1665 |             }
1666 |           ],
1667 |           "responses": {
1668 |             "200": {
1669 |               "description": "Directory listing",
1670 |               "content": {
1671 |                 "application/json": {
1672 |                   "schema": {
1673 |                     "type": "object",
1674 |                     "additionalProperties": true
1675 |                   }
1676 |                 }
1677 |               }
1678 |             }
1679 |           }
1680 |         }
1681 |       },
1682 |       "/vault/search": {
1683 |         "get": {
1684 |           "tags": [
1685 |             "Vault"
1686 |           ],
1687 |           "operationId": "searchVault",
1688 |           "summary": "Search vault content across content, filenames, and tags",
1689 |           "parameters": [
1690 |             {
1691 |               "in": "query",
1692 |               "name": "query",
1693 |               "schema": {
1694 |                 "type": "string"
1695 |               },
1696 |               "required": true
1697 |             },
1698 |             {
1699 |               "in": "query",
1700 |               "name": "scope",
1701 |               "schema": {
1702 |                 "type": "string",
1703 |                 "description": "Comma-separated: content,filename,tags"
1704 |               }
1705 |             },
1706 |             {
1707 |               "in": "query",
1708 |               "name": "path_filter",
1709 |               "schema": {
1710 |                 "type": "string"
1711 |               }
1712 |             }
1713 |           ],
1714 |           "responses": {
1715 |             "200": {
1716 |               "description": "Search results",
1717 |               "content": {
1718 |                 "application/json": {
1719 |                   "schema": {
1720 |                     "type": "object",
1721 |                     "additionalProperties": true
1722 |                   }
1723 |                 }
1724 |               }
1725 |             },
1726 |             "400": {
1727 |               "description": "Missing query"
1728 |             }
1729 |           }
1730 |         }
1731 |       },
1732 |       "/vault/notes/recent": {
1733 |         "get": {
1734 |           "tags": [
1735 |             "Vault"
1736 |           ],
1737 |           "operationId": "getRecentNotes",
1738 |           "summary": "Get recently modified notes",
1739 |           "parameters": [
1740 |             {
1741 |               "in": "query",
1742 |               "name": "limit",
1743 |               "schema": {
1744 |                 "type": "integer",
1745 |                 "default": 5
1746 |               }
1747 |             }
1748 |           ],
1749 |           "responses": {
1750 |             "200": {
1751 |               "description": "Recent notes",
1752 |               "content": {
1753 |                 "application/json": {
1754 |                   "schema": {
1755 |                     "type": "array",
1756 |                     "items": {
1757 |                       "$ref": "#/components/schemas/Note"
1758 |                     }
1759 |                   }
1760 |                 }
1761 |               }
1762 |             }
1763 |           }
1764 |         }
1765 |       },
1766 |       "/vault/notes/daily": {
1767 |         "get": {
1768 |           "tags": [
1769 |             "Vault"
1770 |           ],
1771 |           "operationId": "getDailyNote",
1772 |           "summary": "Get daily note by date or shortcuts",
1773 |           "parameters": [
1774 |             {
1775 |               "in": "query",
1776 |               "name": "date",
1777 |               "schema": {
1778 |                 "type": "string",
1779 |                 "description": "today|yesterday|tomorrow|YYYY-MM-DD"
1780 |               }
1781 |             }
1782 |           ],
1783 |           "responses": {
1784 |             "200": {
1785 |               "description": "Daily note",
1786 |               "content": {
1787 |                 "application/json": {
1788 |                   "schema": {
1789 |                     "$ref": "#/components/schemas/Note"
1790 |                   }
1791 |                 }
1792 |               }
1793 |             },
1794 |             "404": {
1795 |               "description": "Not found"
1796 |             },
1797 |             "400": {
1798 |               "description": "Invalid date"
1799 |             }
1800 |           }
1801 |         }
1802 |       },
1803 |       "/vault/notes/related/{path}": {
1804 |         "get": {
1805 |           "tags": [
1806 |             "Vault"
1807 |           ],
1808 |           "operationId": "getRelatedNotes",
1809 |           "summary": "Find related notes based on tags and links",
1810 |           "parameters": [
1811 |             {
1812 |               "in": "path",
1813 |               "name": "path",
1814 |               "schema": {
1815 |                 "type": "string"
1816 |               },
1817 |               "required": true
1818 |             },
1819 |             {
1820 |               "in": "query",
1821 |               "name": "on",
1822 |               "schema": {
1823 |                 "type": "string",
1824 |                 "description": "Comma-separated: tags,links"
1825 |               }
1826 |             },
1827 |             {
1828 |               "in": "query",
1829 |               "name": "limit",
1830 |               "schema": {
1831 |                 "type": "integer",
1832 |                 "default": 10
1833 |               }
1834 |             }
1835 |           ],
1836 |           "responses": {
1837 |             "200": {
1838 |               "description": "Related notes",
1839 |               "content": {
1840 |                 "application/json": {
1841 |                   "schema": {
1842 |                     "type": "object",
1843 |                     "additionalProperties": true
1844 |                   }
1845 |                 }
1846 |               }
1847 |             },
1848 |             "404": {
1849 |               "description": "Source note not found"
1850 |             }
1851 |           }
1852 |         }
1853 |       },
1854 |       "/vault/overview": {
1855 |         "get": {
1856 |           "tags": [
1857 |             "Vault"
1858 |           ],
1859 |           "operationId": "getVaultOverview",
1860 |           "summary": "Vault overview including README and first two folder levels",
1861 |           "responses": {
1862 |             "200": {
1863 |               "description": "Overview",
1864 |               "content": {
1865 |                 "application/json": {
1866 |                   "schema": {
1867 |                     "type": "object",
1868 |                     "additionalProperties": true
1869 |                   }
1870 |                 }
1871 |               }
1872 |             }
1873 |           }
1874 |         }
1875 |       },
1876 |       "/agent/grep": {
1877 |         "post": {
1878 |           "tags": [
1879 |             "Agent"
1880 |           ],
1881 |           "operationId": "grepVault",
1882 |           "summary": "Search vault content with regex and patterns",
1883 |           "requestBody": {
1884 |             "required": true,
1885 |             "content": {
1886 |               "application/json": {
1887 |                 "schema": {
1888 |                   "type": "object",
1889 |                   "required": [
1890 |                     "pattern"
1891 |                   ],
1892 |                   "properties": {
1893 |                     "pattern": {
1894 |                       "type": "string"
1895 |                     },
1896 |                     "is_regex": {
1897 |                       "type": "boolean"
1898 |                     },
1899 |                     "case_sensitive": {
1900 |                       "type": "boolean"
1901 |                     },
1902 |                     "include_frontmatter": {
1903 |                       "type": "boolean"
1904 |                     },
1905 |                     "file_pattern": {
1906 |                       "type": "string"
1907 |                     },
1908 |                     "max_results": {
1909 |                       "type": "integer"
1910 |                     },
1911 |                     "context_lines": {
1912 |                       "type": "integer"
1913 |                     }
1914 |                   }
1915 |                 }
1916 |               }
1917 |             }
1918 |           },
1919 |           "responses": {
1920 |             "200": {
1921 |               "description": "Search summary",
1922 |               "content": {
1923 |                 "application/json": {
1924 |                   "schema": {
1925 |                     "type": "object",
1926 |                     "additionalProperties": true
1927 |                   }
1928 |                 }
1929 |               }
1930 |             }
1931 |           }
1932 |         }
1933 |       },
1934 |       "/agent/query-frontmatter": {
1935 |         "post": {
1936 |           "tags": [
1937 |             "Agent"
1938 |           ],
1939 |           "operationId": "queryFrontmatter",
1940 |           "summary": "Query frontmatter fields across notes",
1941 |           "requestBody": {
1942 |             "required": false,
1943 |             "content": {
1944 |               "application/json": {
1945 |                 "schema": {
1946 |                   "type": "object",
1947 |                   "properties": {
1948 |                     "fields": {
1949 |                       "type": "array",
1950 |                       "items": {
1951 |                         "type": "string"
1952 |                       }
1953 |                     },
1954 |                     "where": {
1955 |                       "type": "object",
1956 |                       "additionalProperties": true
1957 |                     },
1958 |                     "sort_by": {
1959 |                       "type": "string"
1960 |                     },
1961 |                     "sort_direction": {
1962 |                       "type": "string",
1963 |                       "enum": [
1964 |                         "asc",
1965 |                         "desc"
1966 |                       ]
1967 |                     },
1968 |                     "limit": {
1969 |                       "type": "integer"
1970 |                     },
1971 |                     "distinct": {
1972 |                       "type": "boolean"
1973 |                     }
1974 |                   }
1975 |                 }
1976 |               }
1977 |             }
1978 |           },
1979 |           "responses": {
1980 |             "200": {
1981 |               "description": "Query results",
1982 |               "content": {
1983 |                 "application/json": {
1984 |                   "schema": {
1985 |                     "type": "object",
1986 |                     "additionalProperties": true
1987 |                   }
1988 |                 }
1989 |               }
1990 |             }
1991 |           }
1992 |         }
1993 |       },
1994 |       "/agent/backlinks/{note_path}": {
1995 |         "get": {
1996 |           "tags": [
1997 |             "Agent"
1998 |           ],
1999 |           "operationId": "getBacklinks",
2000 |           "summary": "Find backlinks and mentions for a note",
2001 |           "parameters": [
2002 |             {
2003 |               "in": "path",
2004 |               "name": "note_path",
2005 |               "schema": {
2006 |                 "type": "string"
2007 |               },
2008 |               "required": true
2009 |             },
2010 |             {
2011 |               "in": "query",
2012 |               "name": "include_mentions",
2013 |               "schema": {
2014 |                 "type": "boolean",
2015 |                 "default": true
2016 |               }
2017 |             },
2018 |             {
2019 |               "in": "query",
2020 |               "name": "include_tags",
2021 |               "schema": {
2022 |                 "type": "boolean",
2023 |                 "default": false
2024 |               }
2025 |             }
2026 |           ],
2027 |           "responses": {
2028 |             "200": {
2029 |               "description": "Backlink summary",
2030 |               "content": {
2031 |                 "application/json": {
2032 |                   "schema": {
2033 |                     "type": "object",
2034 |                     "additionalProperties": true
2035 |                   }
2036 |                 }
2037 |               }
2038 |             }
2039 |           }
2040 |         }
2041 |       },
2042 |       "/agent/tags": {
2043 |         "get": {
2044 |           "tags": [
2045 |             "Agent"
2046 |           ],
2047 |           "operationId": "getTags",
2048 |           "summary": "List tags across notes",
2049 |           "parameters": [
2050 |             {
2051 |               "in": "query",
2052 |               "name": "min_count",
2053 |               "schema": {
2054 |                 "type": "integer",
2055 |                 "default": 1
2056 |               }
2057 |             },
2058 |             {
2059 |               "in": "query",
2060 |               "name": "include_nested",
2061 |               "schema": {
2062 |                 "type": "boolean",
2063 |                 "default": true
2064 |               }
2065 |             },
2066 |             {
2067 |               "in": "query",
2068 |               "name": "format",
2069 |               "schema": {
2070 |                 "type": "string",
2071 |                 "enum": [
2072 |                   "flat",
2073 |                   "hierarchical"
2074 |                 ],
2075 |                 "default": "flat"
2076 |               }
2077 |             }
2078 |           ],
2079 |           "responses": {
2080 |             "200": {
2081 |               "description": "Tags",
2082 |               "content": {
2083 |                 "application/json": {
2084 |                   "schema": {
2085 |                     "type": "object",
2086 |                     "additionalProperties": true
2087 |                   }
2088 |                 }
2089 |               }
2090 |             }
2091 |           }
2092 |         }
2093 |       },
2094 |       "/agent/stats": {
2095 |         "get": {
2096 |           "tags": [
2097 |             "Agent"
2098 |           ],
2099 |           "operationId": "getVaultStats",
2100 |           "summary": "Get vault statistics and health metrics",
2101 |           "responses": {
2102 |             "200": {
2103 |               "description": "Stats",
2104 |               "content": {
2105 |                 "application/json": {
2106 |                   "schema": {
2107 |                     "type": "object",
2108 |                     "additionalProperties": true
2109 |                   }
2110 |                 }
2111 |               }
2112 |             }
2113 |           }
2114 |         }
2115 |       }
2116 |     },
2117 |     "/vault/directory": {
2118 |       "get": {
2119 |         "tags": [
2120 |           "Vault"
2121 |         ],
2122 |         "operationId": "listDirectory",
2123 |         "summary": "List directory contents with pagination",
2124 |         "parameters": [
2125 |           {
2126 |             "in": "query",
2127 |             "name": "path",
2128 |             "schema": {
2129 |               "type": "string",
2130 |               "default": "."
2131 |             }
2132 |           },
2133 |           {
2134 |             "in": "query",
2135 |             "name": "recursive",
2136 |             "schema": {
2137 |               "type": "boolean",
2138 |               "default": false
2139 |             }
2140 |           },
2141 |           {
2142 |             "in": "query",
2143 |             "name": "limit",
2144 |             "schema": {
2145 |               "type": "integer",
2146 |               "default": 50
2147 |             }
2148 |           },
2149 |           {
2150 |             "in": "query",
2151 |             "name": "offset",
2152 |             "schema": {
2153 |               "type": "integer",
2154 |               "default": 0
2155 |             }
2156 |           }
2157 |         ],
2158 |         "responses": {
2159 |           "200": {
2160 |             "description": "Directory listing",
2161 |             "content": {
2162 |               "application/json": {
2163 |                 "schema": {
2164 |                   "type": "object",
2165 |                   "additionalProperties": true
2166 |                 }
2167 |               }
2168 |             }
2169 |           }
2170 |         }
2171 |       }
2172 |     },
2173 |     "/vault/search": {
2174 |       "get": {
2175 |         "tags": [
2176 |           "Vault"
2177 |         ],
2178 |         "operationId": "searchVault",
2179 |         "summary": "Search vault content across content, filenames, and tags",
2180 |         "parameters": [
2181 |           {
2182 |             "in": "query",
2183 |             "name": "query",
2184 |             "schema": {
2185 |               "type": "string"
2186 |             },
2187 |             "required": true
2188 |           },
2189 |           {
2190 |             "in": "query",
2191 |             "name": "scope",
2192 |             "schema": {
2193 |               "type": "string",
2194 |               "description": "Comma-separated: content,filename,tags"
2195 |             }
2196 |           },
2197 |           {
2198 |             "in": "query",
2199 |             "name": "path_filter",
2200 |             "schema": {
2201 |               "type": "string"
2202 |             }
2203 |           }
2204 |         ],
2205 |         "responses": {
2206 |           "200": {
2207 |             "description": "Search results",
2208 |             "content": {
2209 |               "application/json": {
2210 |                 "schema": {
2211 |                   "type": "object",
2212 |                   "additionalProperties": true
2213 |                 }
2214 |               }
2215 |             }
2216 |           },
2217 |           "400": {
2218 |             "description": "Missing query"
2219 |           }
2220 |         }
2221 |       }
2222 |     },
2223 |     "/vault/notes/recent": {
2224 |       "get": {
2225 |         "tags": [
2226 |           "Vault"
2227 |         ],
2228 |         "operationId": "getRecentNotes",
2229 |         "summary": "Get recently modified notes",
2230 |         "parameters": [
2231 |           {
2232 |             "in": "query",
2233 |             "name": "limit",
2234 |             "schema": {
2235 |               "type": "integer",
2236 |               "default": 5
2237 |             }
2238 |           }
2239 |         ],
2240 |         "responses": {
2241 |           "200": {
2242 |             "description": "Recent notes",
2243 |             "content": {
2244 |               "application/json": {
2245 |                 "schema": {
2246 |                   "type": "array",
2247 |                   "items": {
2248 |                     "$ref": "#/components/schemas/Note"
2249 |                   }
2250 |                 }
2251 |               }
2252 |             }
2253 |           }
2254 |         }
2255 |       }
2256 |     },
2257 |     "/vault/notes/daily": {
2258 |       "get": {
2259 |         "tags": [
2260 |           "Vault"
2261 |         ],
2262 |         "operationId": "getDailyNote",
2263 |         "summary": "Get daily note by date or shortcuts",
2264 |         "parameters": [
2265 |           {
2266 |             "in": "query",
2267 |             "name": "date",
2268 |             "schema": {
2269 |               "type": "string",
2270 |               "description": "today|yesterday|tomorrow|YYYY-MM-DD"
2271 |             }
2272 |           }
2273 |         ],
2274 |         "responses": {
2275 |           "200": {
2276 |             "description": "Daily note",
2277 |             "content": {
2278 |               "application/json": {
2279 |                 "schema": {
2280 |                   "$ref": "#/components/schemas/Note"
2281 |                 }
2282 |               }
2283 |             }
2284 |           },
2285 |           "404": {
2286 |             "description": "Not found"
2287 |           },
2288 |           "400": {
2289 |             "description": "Invalid date"
2290 |           }
2291 |         }
2292 |       }
2293 |     },
2294 |     "/vault/notes/related/{path}": {
2295 |       "get": {
2296 |         "tags": [
2297 |           "Vault"
2298 |         ],
2299 |         "operationId": "getRelatedNotes",
2300 |         "summary": "Find related notes based on tags and links",
2301 |         "parameters": [
2302 |           {
2303 |             "in": "path",
2304 |             "name": "path",
2305 |             "schema": {
2306 |               "type": "string"
2307 |             },
2308 |             "required": true
2309 |           },
2310 |           {
2311 |             "in": "query",
2312 |             "name": "on",
2313 |             "schema": {
2314 |               "type": "string",
2315 |               "description": "Comma-separated: tags,links"
2316 |             }
2317 |           },
2318 |           {
2319 |             "in": "query",
2320 |             "name": "limit",
2321 |             "schema": {
2322 |               "type": "integer",
2323 |               "default": 10
2324 |             }
2325 |           }
2326 |         ],
2327 |         "responses": {
2328 |           "200": {
2329 |             "description": "Related notes",
2330 |             "content": {
2331 |               "application/json": {
2332 |                 "schema": {
2333 |                   "type": "object",
2334 |                   "additionalProperties": true
2335 |                 }
2336 |               }
2337 |             }
2338 |           },
2339 |           "404": {
2340 |             "description": "Source note not found"
2341 |           }
2342 |         }
2343 |       }
2344 |     },
2345 |     "/vault/overview": {
2346 |       "get": {
2347 |         "tags": [
2348 |           "Vault"
2349 |         ],
2350 |         "operationId": "getVaultOverview",
2351 |         "summary": "Vault overview including README and first two folder levels",
2352 |         "responses": {
2353 |           "200": {
2354 |             "description": "Overview",
2355 |             "content": {
2356 |               "application/json": {
2357 |                 "schema": {
2358 |                   "type": "object",
2359 |                   "additionalProperties": true
2360 |                 }
2361 |               }
2362 |             }
2363 |           }
2364 |         }
2365 |       }
2366 |     },
2367 |     "/agent/grep": {
2368 |       "post": {
2369 |         "tags": [
2370 |           "Agent"
2371 |         ],
2372 |         "operationId": "grepVault",
2373 |         "summary": "Search vault content with regex and patterns",
2374 |         "requestBody": {
2375 |           "required": true,
2376 |           "content": {
2377 |             "application/json": {
2378 |               "schema": {
2379 |                 "type": "object",
2380 |                 "required": [
2381 |                   "pattern"
2382 |                 ],
2383 |                 "properties": {
2384 |                   "pattern": {
2385 |                     "type": "string"
2386 |                   },
2387 |                   "is_regex": {
2388 |                     "type": "boolean"
2389 |                   },
2390 |                   "case_sensitive": {
2391 |                     "type": "boolean"
2392 |                   },
2393 |                   "include_frontmatter": {
2394 |                     "type": "boolean"
2395 |                   },
2396 |                   "file_pattern": {
2397 |                     "type": "string"
2398 |                   },
2399 |                   "max_results": {
2400 |                     "type": "integer"
2401 |                   },
2402 |                   "context_lines": {
2403 |                     "type": "integer"
2404 |                   }
2405 |                 }
2406 |               }
2407 |             }
2408 |           }
2409 |         },
2410 |         "responses": {
2411 |           "200": {
2412 |             "description": "Search summary",
2413 |             "content": {
2414 |               "application/json": {
2415 |                 "schema": {
2416 |                   "type": "object",
2417 |                   "additionalProperties": true
2418 |                 }
2419 |               }
2420 |             }
2421 |           }
2422 |         }
2423 |       }
2424 |     },
2425 |     "/agent/query-frontmatter": {
2426 |       "post": {
2427 |         "tags": [
2428 |           "Agent"
2429 |         ],
2430 |         "operationId": "queryFrontmatter",
2431 |         "summary": "Query frontmatter fields across notes",
2432 |         "requestBody": {
2433 |           "required": false,
2434 |           "content": {
2435 |             "application/json": {
2436 |               "schema": {
2437 |                 "type": "object",
2438 |                 "properties": {
2439 |                   "fields": {
2440 |                     "type": "array",
2441 |                     "items": {
2442 |                       "type": "string"
2443 |                     }
2444 |                   },
2445 |                   "where": {
2446 |                     "type": "object",
2447 |                     "additionalProperties": true
2448 |                   },
2449 |                   "sort_by": {
2450 |                     "type": "string"
2451 |                   },
2452 |                   "sort_direction": {
2453 |                     "type": "string",
2454 |                     "enum": [
2455 |                       "asc",
2456 |                       "desc"
2457 |                     ]
2458 |                   },
2459 |                   "limit": {
2460 |                     "type": "integer"
2461 |                   },
2462 |                   "distinct": {
2463 |                     "type": "boolean"
2464 |                   }
2465 |                 }
2466 |               }
2467 |             }
2468 |           }
2469 |         },
2470 |         "responses": {
2471 |           "200": {
2472 |             "description": "Query results",
2473 |             "content": {
2474 |               "application/json": {
2475 |                 "schema": {
2476 |                   "type": "object",
2477 |                   "additionalProperties": true
2478 |                 }
2479 |               }
2480 |             }
2481 |           }
2482 |         }
2483 |       }
2484 |     },
2485 |     "/agent/backlinks/{note_path}": {
2486 |       "get": {
2487 |         "tags": [
2488 |           "Agent"
2489 |         ],
2490 |         "operationId": "getBacklinks",
2491 |         "summary": "Find backlinks and mentions for a note",
2492 |         "parameters": [
2493 |           {
2494 |             "in": "path",
2495 |             "name": "note_path",
2496 |             "schema": {
2497 |               "type": "string"
2498 |             },
2499 |             "required": true
2500 |           },
2501 |           {
2502 |             "in": "query",
2503 |             "name": "include_mentions",
2504 |             "schema": {
2505 |               "type": "boolean",
2506 |               "default": true
2507 |             }
2508 |           },
2509 |           {
2510 |             "in": "query",
2511 |             "name": "include_tags",
2512 |             "schema": {
2513 |               "type": "boolean",
2514 |               "default": false
2515 |             }
2516 |           }
2517 |         ],
2518 |         "responses": {
2519 |           "200": {
2520 |             "description": "Backlink summary",
2521 |             "content": {
2522 |               "application/json": {
2523 |                 "schema": {
2524 |                   "type": "object",
2525 |                   "additionalProperties": true
2526 |                 }
2527 |               }
2528 |             }
2529 |           }
2530 |         }
2531 |       }
2532 |     },
2533 |     "/agent/tags": {
2534 |       "get": {
2535 |         "tags": [
2536 |           "Agent"
2537 |         ],
2538 |         "operationId": "getTags",
2539 |         "summary": "List tags across notes",
2540 |         "parameters": [
2541 |           {
2542 |             "in": "query",
2543 |             "name": "min_count",
2544 |             "schema": {
2545 |               "type": "integer",
2546 |               "default": 1
2547 |             }
2548 |           },
2549 |           {
2550 |             "in": "query",
2551 |             "name": "include_nested",
2552 |             "schema": {
2553 |               "type": "boolean",
2554 |               "default": true
2555 |             }
2556 |           },
2557 |           {
2558 |             "in": "query",
2559 |             "name": "format",
2560 |             "schema": {
2561 |               "type": "string",
2562 |               "enum": [
2563 |                 "flat",
2564 |                 "hierarchical"
2565 |               ],
2566 |               "default": "flat"
2567 |             }
2568 |           }
2569 |         ],
2570 |         "responses": {
2571 |           "200": {
2572 |             "description": "Tags",
2573 |             "content": {
2574 |               "application/json": {
2575 |                 "schema": {
2576 |                   "type": "object",
2577 |                   "additionalProperties": true
2578 |                 }
2579 |               }
2580 |             }
2581 |           }
2582 |         }
2583 |       }
2584 |     },
2585 |     "/agent/stats": {
2586 |       "get": {
2587 |         "tags": [
2588 |           "Agent"
2589 |         ],
2590 |         "operationId": "getVaultStats",
2591 |         "summary": "Get vault statistics and health metrics",
2592 |         "responses": {
2593 |           "200": {
2594 |             "description": "Stats",
2595 |             "content": {
2596 |               "application/json": {
2597 |                 "schema": {
2598 |                   "type": "object",
2599 |                   "additionalProperties": true
2600 |                 }
2601 |               }
2602 |             }
2603 |           }
2604 |         }
2605 |       }
2606 |     }
2607 |   }
2608 | }
2609 | 
```