# 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 |
```