Spaces:
Running
Running
bigwolfe
commited on
Commit
Β·
6cdb404
1
Parent(s):
58b4b49
start up script, mcp working, input sanitization over mcp
Browse files- .gitignore +4 -0
- ai-notes/mcp-tools-test-report.md +206 -0
- backend/src/services/indexer.py +43 -1
- backend/tests/unit/test_indexer_search.py +55 -0
- frontend/src/components/ui/label.tsx +1 -0
- frontend/src/pages/Settings.tsx +7 -1
- specs/001-obsidian-docs-viewer/data-model.md +2 -0
- start-dev.sh +71 -0
- status-dev.sh +48 -0
- stop-dev.sh +50 -0
.gitignore
CHANGED
|
@@ -43,3 +43,7 @@ build/
|
|
| 43 |
# Runtime data
|
| 44 |
data/
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
# Runtime data
|
| 44 |
data/
|
| 45 |
|
| 46 |
+
# Development script artifacts
|
| 47 |
+
.backend.pid
|
| 48 |
+
.frontend.pid
|
| 49 |
+
|
ai-notes/mcp-tools-test-report.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MCP Tools Test Report
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
Comprehensive testing of all MCP obsidian-docs tools exposed by the server.
|
| 5 |
+
|
| 6 |
+
## Test Results
|
| 7 |
+
|
| 8 |
+
### β
`list_notes` - **PASSING**
|
| 9 |
+
**Functionality**: Lists all notes in the vault
|
| 10 |
+
**Test Results**:
|
| 11 |
+
- Successfully listed 10 notes from the vault
|
| 12 |
+
- Returns array with: `path`, `title`, `last_modified` timestamp
|
| 13 |
+
- Works with optional `folder` parameter (tested with `null` and empty string)
|
| 14 |
+
- **Status**: β
Working correctly
|
| 15 |
+
|
| 16 |
+
**Sample Output**:
|
| 17 |
+
```json
|
| 18 |
+
[
|
| 19 |
+
{"path":"API Documentation.md","title":"API Documentation 123","last_modified":"2025-11-17T01:17:52.721411+00:00"},
|
| 20 |
+
{"path":"test-from-cursor-over-mcp.md","title":"test from cursor over mcp","last_modified":"2025-11-17T02:16:14.156806+00:00"}
|
| 21 |
+
]
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
### β
`read_note` - **PASSING**
|
| 27 |
+
**Functionality**: Reads a note with metadata and body
|
| 28 |
+
**Test Results**:
|
| 29 |
+
- Successfully read existing notes
|
| 30 |
+
- Returns: `path`, `title`, `metadata` (created, updated, title), and `body`
|
| 31 |
+
- Properly handles deleted notes (returns "Note not found" error)
|
| 32 |
+
- **Status**: β
Working correctly
|
| 33 |
+
|
| 34 |
+
**Sample Output**:
|
| 35 |
+
```json
|
| 36 |
+
{
|
| 37 |
+
"path":"test-from-cursor-over-mcp.md",
|
| 38 |
+
"title":"test from cursor over mcp",
|
| 39 |
+
"metadata":{
|
| 40 |
+
"created":"2025-11-17T02:13:39+00:00",
|
| 41 |
+
"title":"test from cursor over mcp",
|
| 42 |
+
"updated":"2025-11-17T02:16:14+00:00"
|
| 43 |
+
},
|
| 44 |
+
"body":"# test from cursor over mcp\n\nThis is a test note created from Cursor using the MCP server.\n\n**Updated**: This note has been updated to test the write_note functionality.\n\nNow referencing [[API Documentation]] to test backlinks."
|
| 45 |
+
}
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
**Error Handling**:
|
| 49 |
+
- β
Returns proper error for non-existent notes: "Note not found: test-delete-me.md"
|
| 50 |
+
|
| 51 |
+
---
|
| 52 |
+
|
| 53 |
+
### β
`write_note` - **PASSING**
|
| 54 |
+
**Functionality**: Creates or updates notes with optional metadata
|
| 55 |
+
**Test Results**:
|
| 56 |
+
- Successfully created new notes
|
| 57 |
+
- Successfully updated existing notes (preserves creation timestamp, updates modified timestamp)
|
| 58 |
+
- Supports optional `title` parameter
|
| 59 |
+
- Supports optional `metadata` parameter (not fully tested with tags)
|
| 60 |
+
- Automatically updates frontmatter timestamps
|
| 61 |
+
- **Status**: β
Working correctly
|
| 62 |
+
|
| 63 |
+
**Test Cases**:
|
| 64 |
+
1. β
Created new note: `test-from-cursor-over-mcp.md`
|
| 65 |
+
2. β
Updated existing note: Modified `test-from-cursor-over-mcp.md` and confirmed timestamp updated
|
| 66 |
+
3. β
Created note for deletion testing: `test-delete-me.md`
|
| 67 |
+
|
| 68 |
+
**Sample Output**:
|
| 69 |
+
```json
|
| 70 |
+
{"status":"ok","path":"test-from-cursor-over-mcp.md"}
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
### β
`delete_note` - **PASSING**
|
| 76 |
+
**Functionality**: Deletes a note from the vault
|
| 77 |
+
**Test Results**:
|
| 78 |
+
- Successfully deleted test note `test-delete-me.md`
|
| 79 |
+
- Returns status confirmation: `{"status":"ok"}`
|
| 80 |
+
- Deleted note removed from vault (verified by attempting to read it)
|
| 81 |
+
- **Status**: β
Working correctly
|
| 82 |
+
|
| 83 |
+
**Test Case**:
|
| 84 |
+
1. β
Created `test-delete-me.md`
|
| 85 |
+
2. β
Verified it exists by reading it
|
| 86 |
+
3. β
Deleted it successfully
|
| 87 |
+
4. β
Confirmed deletion by attempting to read (returned "Note not found" error)
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
+
|
| 91 |
+
### β
`search_notes` - **MOSTLY PASSING**
|
| 92 |
+
**Functionality**: Full-text search with snippets and recency-aware scoring
|
| 93 |
+
**Test Results**:
|
| 94 |
+
- Successfully searches notes with proper queries
|
| 95 |
+
- Returns results with highlighted snippets using `<mark>` tags
|
| 96 |
+
- Supports `limit` parameter (tested with 5 and 10)
|
| 97 |
+
- Returns: `path`, `title`, `snippet` (with markdown highlights)
|
| 98 |
+
- **Status**: β οΈ Working but has SQL syntax issues with special characters
|
| 99 |
+
|
| 100 |
+
**Test Cases**:
|
| 101 |
+
1. β
Search "test cursor" - Found matching note with highlights
|
| 102 |
+
2. β
Search "API" - Found multiple notes with proper highlighting
|
| 103 |
+
3. β Search with special characters (apostrophe) - **Error**: `fts5: syntax error near "'"`
|
| 104 |
+
|
| 105 |
+
**Sample Output**:
|
| 106 |
+
```json
|
| 107 |
+
[
|
| 108 |
+
{
|
| 109 |
+
"path":"test-from-cursor-over-mcp.md",
|
| 110 |
+
"title":"test from cursor over mcp",
|
| 111 |
+
"snippet":"# <mark>test</mark> from <mark>cursor</mark> over mcp\n\nThis is a <mark>test</mark> note created from <mark>Cursor</mark> using the MCP server."
|
| 112 |
+
}
|
| 113 |
+
]
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
**Known Issue**:
|
| 117 |
+
- β SQL syntax error when search query contains special characters like apostrophes
|
| 118 |
+
- **Recommendation**: Implement input sanitization/escaping for search queries
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
### β
`get_backlinks` - **PASSING**
|
| 123 |
+
**Functionality**: Lists notes that reference the target note via wikilinks
|
| 124 |
+
**Test Results**:
|
| 125 |
+
- Successfully retrieved backlinks for existing notes
|
| 126 |
+
- Returns array of notes that reference the target
|
| 127 |
+
- Includes: `path` and `title` for each backlink
|
| 128 |
+
- **Status**: β
Working correctly
|
| 129 |
+
|
| 130 |
+
**Test Cases**:
|
| 131 |
+
1. β
Retrieved backlinks for "API Documentation.md" - Found 6 notes referencing it
|
| 132 |
+
2. β
Confirmed backlinks include the test note we created that references it
|
| 133 |
+
|
| 134 |
+
**Sample Output**:
|
| 135 |
+
```json
|
| 136 |
+
[
|
| 137 |
+
{"path":"test-from-cursor-over-mcp.md","title":"test from cursor over mcp"},
|
| 138 |
+
{"path":"Architecture Overview.md","title":"Architecture Overview"},
|
| 139 |
+
{"path":"FAQ.md","title":"FAQ"}
|
| 140 |
+
]
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
---
|
| 144 |
+
|
| 145 |
+
### β
`get_tags` - **PASSING**
|
| 146 |
+
**Functionality**: Lists all tags and their associated note counts
|
| 147 |
+
**Test Results**:
|
| 148 |
+
- Successfully retrieved all tags from the vault
|
| 149 |
+
- Returns array with: `tag` name and `count` of notes using that tag
|
| 150 |
+
- **Status**: β
Working correctly
|
| 151 |
+
|
| 152 |
+
**Sample Output**:
|
| 153 |
+
```json
|
| 154 |
+
[
|
| 155 |
+
{"tag":"guide","count":2},
|
| 156 |
+
{"tag":"agents","count":1},
|
| 157 |
+
{"tag":"ai","count":1},
|
| 158 |
+
{"tag":"architecture","count":1},
|
| 159 |
+
{"tag":"mcp","count":1}
|
| 160 |
+
]
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
---
|
| 164 |
+
|
| 165 |
+
## Summary
|
| 166 |
+
|
| 167 |
+
### Tools Tested: 7
|
| 168 |
+
### Passing: 6
|
| 169 |
+
### Passing with Issues: 1
|
| 170 |
+
|
| 171 |
+
| Tool | Status | Notes |
|
| 172 |
+
|------|--------|-------|
|
| 173 |
+
| `list_notes` | β
PASS | Works perfectly |
|
| 174 |
+
| `read_note` | β
PASS | Works perfectly, proper error handling |
|
| 175 |
+
| `write_note` | β
PASS | Works perfectly, updates timestamps correctly |
|
| 176 |
+
| `delete_note` | β
PASS | Works perfectly, proper cleanup |
|
| 177 |
+
| `search_notes` | β οΈ PASS (with issues) | SQL syntax error with special characters |
|
| 178 |
+
| `get_backlinks` | β
PASS | Works perfectly |
|
| 179 |
+
| `get_tags` | β
PASS | Works perfectly |
|
| 180 |
+
|
| 181 |
+
## Issues Found
|
| 182 |
+
|
| 183 |
+
### 1. Search Query SQL Syntax Error
|
| 184 |
+
**Severity**: Medium
|
| 185 |
+
**Description**: The `search_notes` tool fails when search queries contain special characters like apostrophes
|
| 186 |
+
**Error Message**: `fts5: syntax error near "'"`
|
| 187 |
+
**Recommendation**: Implement proper SQL escaping/sanitization for FTS5 queries in the backend
|
| 188 |
+
|
| 189 |
+
## Recommendations
|
| 190 |
+
|
| 191 |
+
1. **Fix SQL injection vulnerability** in search functionality
|
| 192 |
+
2. **Add input validation** for all tool parameters
|
| 193 |
+
3. **Consider adding**:
|
| 194 |
+
- Error codes for better error handling
|
| 195 |
+
- Rate limiting information
|
| 196 |
+
- Validation for path parameters (no '..' or '\\' as documented)
|
| 197 |
+
|
| 198 |
+
## Test Notes Created/Modified
|
| 199 |
+
|
| 200 |
+
- β
`test-from-cursor-over-mcp.md` - Created and updated
|
| 201 |
+
- β
`test-delete-me.md` - Created and deleted (for deletion testing)
|
| 202 |
+
|
| 203 |
+
## Conclusion
|
| 204 |
+
|
| 205 |
+
Overall, the MCP server is working well with 6 out of 7 tools functioning perfectly. The only issue is with the search functionality when handling special characters, which should be addressed for production use.
|
| 206 |
+
|
backend/src/services/indexer.py
CHANGED
|
@@ -12,6 +12,7 @@ from .database import DatabaseService
|
|
| 12 |
from .vault import VaultNote
|
| 13 |
|
| 14 |
WIKILINK_PATTERN = re.compile(r"\[\[([^\]]+)\]\]")
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
def _utcnow_iso() -> str:
|
|
@@ -35,6 +36,45 @@ def normalize_tag(tag: str | None) -> str:
|
|
| 35 |
return tag.strip().lower()
|
| 36 |
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
class IndexerService:
|
| 39 |
"""Manage SQLite-backed metadata, tags, search index, and link graph."""
|
| 40 |
|
|
@@ -241,6 +281,8 @@ class IndexerService:
|
|
| 241 |
if not query or not query.strip():
|
| 242 |
raise ValueError("Search query cannot be empty")
|
| 243 |
|
|
|
|
|
|
|
| 244 |
conn = self.db_service.connect()
|
| 245 |
try:
|
| 246 |
rows = conn.execute(
|
|
@@ -257,7 +299,7 @@ class IndexerService:
|
|
| 257 |
ORDER BY score DESC
|
| 258 |
LIMIT ?
|
| 259 |
""",
|
| 260 |
-
(user_id,
|
| 261 |
).fetchall()
|
| 262 |
finally:
|
| 263 |
conn.close()
|
|
|
|
| 12 |
from .vault import VaultNote
|
| 13 |
|
| 14 |
WIKILINK_PATTERN = re.compile(r"\[\[([^\]]+)\]\]")
|
| 15 |
+
WHITESPACE_RE = re.compile(r"\s+")
|
| 16 |
|
| 17 |
|
| 18 |
def _utcnow_iso() -> str:
|
|
|
|
| 36 |
return tag.strip().lower()
|
| 37 |
|
| 38 |
|
| 39 |
+
def _prepare_match_query(query: str) -> str:
|
| 40 |
+
"""
|
| 41 |
+
Sanitize user-supplied query text for FTS5 MATCH usage.
|
| 42 |
+
|
| 43 |
+
- Splits on whitespace to keep simple keyword semantics.
|
| 44 |
+
- Wraps each token in double quotes to neutralize punctuation (e.g., apostrophes).
|
| 45 |
+
- Escapes embedded double quotes by doubling them.
|
| 46 |
+
- Preserves trailing '*' characters for prefix searches.
|
| 47 |
+
"""
|
| 48 |
+
tokens = [token for token in WHITESPACE_RE.split(query or "") if token.strip()]
|
| 49 |
+
sanitized_terms: List[str] = []
|
| 50 |
+
|
| 51 |
+
for token in tokens:
|
| 52 |
+
cleaned = token.strip()
|
| 53 |
+
suffix = ""
|
| 54 |
+
while cleaned.endswith("*"):
|
| 55 |
+
suffix += "*"
|
| 56 |
+
cleaned = cleaned[:-1]
|
| 57 |
+
|
| 58 |
+
# Remove wrapping quotes if present; inner quotes are preserved/escaped below.
|
| 59 |
+
if cleaned.startswith('"') and cleaned.endswith('"') and len(cleaned) >= 2:
|
| 60 |
+
cleaned = cleaned[1:-1]
|
| 61 |
+
if cleaned.startswith("'") and cleaned.endswith("'") and len(cleaned) >= 2:
|
| 62 |
+
cleaned = cleaned[1:-1]
|
| 63 |
+
|
| 64 |
+
cleaned = cleaned.strip()
|
| 65 |
+
if not cleaned:
|
| 66 |
+
continue
|
| 67 |
+
|
| 68 |
+
escaped = cleaned.replace('"', '""')
|
| 69 |
+
term = f'"{escaped}"{suffix}'
|
| 70 |
+
sanitized_terms.append(term)
|
| 71 |
+
|
| 72 |
+
if not sanitized_terms:
|
| 73 |
+
raise ValueError("Search query must contain alphanumeric characters")
|
| 74 |
+
|
| 75 |
+
return " ".join(sanitized_terms)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
class IndexerService:
|
| 79 |
"""Manage SQLite-backed metadata, tags, search index, and link graph."""
|
| 80 |
|
|
|
|
| 281 |
if not query or not query.strip():
|
| 282 |
raise ValueError("Search query cannot be empty")
|
| 283 |
|
| 284 |
+
sanitized_query = _prepare_match_query(query)
|
| 285 |
+
|
| 286 |
conn = self.db_service.connect()
|
| 287 |
try:
|
| 288 |
rows = conn.execute(
|
|
|
|
| 299 |
ORDER BY score DESC
|
| 300 |
LIMIT ?
|
| 301 |
""",
|
| 302 |
+
(user_id, sanitized_query, limit),
|
| 303 |
).fetchall()
|
| 304 |
finally:
|
| 305 |
conn.close()
|
backend/tests/unit/test_indexer_search.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
from backend.src.services.database import DatabaseService
|
| 6 |
+
from backend.src.services.indexer import IndexerService
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@pytest.fixture()
|
| 10 |
+
def indexer(tmp_path: Path) -> IndexerService:
|
| 11 |
+
db_path = tmp_path / "index.db"
|
| 12 |
+
db_service = DatabaseService(db_path)
|
| 13 |
+
db_service.initialize()
|
| 14 |
+
return IndexerService(db_service=db_service)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def _note(path: str, title: str, body: str) -> dict:
|
| 18 |
+
return {
|
| 19 |
+
"path": path,
|
| 20 |
+
"metadata": {"title": title},
|
| 21 |
+
"body": body,
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_search_notes_handles_apostrophes(indexer: IndexerService) -> None:
|
| 26 |
+
indexer.index_note(
|
| 27 |
+
"local-dev",
|
| 28 |
+
_note(
|
| 29 |
+
"notes/obrien.md",
|
| 30 |
+
"O'Brien Authentication",
|
| 31 |
+
"Details about O'Brien's authentication flow.",
|
| 32 |
+
),
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
results = indexer.search_notes("local-dev", "O'Brien")
|
| 36 |
+
|
| 37 |
+
assert results
|
| 38 |
+
assert results[0]["path"] == "notes/obrien.md"
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def test_search_notes_preserves_prefix_queries(indexer: IndexerService) -> None:
|
| 42 |
+
indexer.index_note(
|
| 43 |
+
"local-dev",
|
| 44 |
+
_note(
|
| 45 |
+
"notes/auth.md",
|
| 46 |
+
"Authorization Overview",
|
| 47 |
+
"Prefix search should match auth prefix tokens.",
|
| 48 |
+
),
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
results = indexer.search_notes("local-dev", "auth*")
|
| 52 |
+
|
| 53 |
+
assert results
|
| 54 |
+
assert results[0]["path"] == "notes/auth.md"
|
| 55 |
+
|
frontend/src/components/ui/label.tsx
CHANGED
|
@@ -23,3 +23,4 @@ Label.displayName = LabelPrimitive.Root.displayName
|
|
| 23 |
|
| 24 |
export { Label }
|
| 25 |
|
|
|
|
|
|
| 23 |
|
| 24 |
export { Label }
|
| 25 |
|
| 26 |
+
|
frontend/src/pages/Settings.tsx
CHANGED
|
@@ -203,13 +203,19 @@ export function Settings() {
|
|
| 203 |
"obsidian-docs": {
|
| 204 |
"command": "python",
|
| 205 |
"args": ["-m", "backend.src.mcp.server"],
|
|
|
|
| 206 |
"env": {
|
| 207 |
-
"BEARER_TOKEN": "${apiToken || 'YOUR_TOKEN_HERE'}"
|
|
|
|
|
|
|
| 208 |
}
|
| 209 |
}
|
| 210 |
}
|
| 211 |
}`}
|
| 212 |
</pre>
|
|
|
|
|
|
|
|
|
|
| 213 |
</div>
|
| 214 |
</CardContent>
|
| 215 |
</Card>
|
|
|
|
| 203 |
"obsidian-docs": {
|
| 204 |
"command": "python",
|
| 205 |
"args": ["-m", "backend.src.mcp.server"],
|
| 206 |
+
"cwd": "/path/to/Document-MCP",
|
| 207 |
"env": {
|
| 208 |
+
"BEARER_TOKEN": "${apiToken || 'YOUR_TOKEN_HERE'}",
|
| 209 |
+
"FASTMCP_SHOW_CLI_BANNER": "false",
|
| 210 |
+
"PYTHONPATH": "/path/to/Document-MCP"
|
| 211 |
}
|
| 212 |
}
|
| 213 |
}
|
| 214 |
}`}
|
| 215 |
</pre>
|
| 216 |
+
<p className="text-xs text-muted-foreground mt-2">
|
| 217 |
+
π‘ Replace <code className="bg-muted px-1 rounded">/path/to/Document-MCP</code> with your actual project path
|
| 218 |
+
</p>
|
| 219 |
</div>
|
| 220 |
</CardContent>
|
| 221 |
</Card>
|
specs/001-obsidian-docs-viewer/data-model.md
CHANGED
|
@@ -413,6 +413,8 @@ ORDER BY rank DESC
|
|
| 413 |
LIMIT 50;
|
| 414 |
```
|
| 415 |
|
|
|
|
|
|
|
| 416 |
---
|
| 417 |
|
| 418 |
#### note_tags
|
|
|
|
| 413 |
LIMIT 50;
|
| 414 |
```
|
| 415 |
|
| 416 |
+
**Safety**: Incoming queries are tokenized and each token is wrapped in double quotes before being passed to `MATCH`, escaping embedded quotes and preserving trailing `*` for prefix searches. This prevents syntax errors from characters such as apostrophes while keeping simple keyword semantics.
|
| 417 |
+
|
| 418 |
---
|
| 419 |
|
| 420 |
#### note_tags
|
start-dev.sh
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Development startup script for Document Viewer
|
| 3 |
+
|
| 4 |
+
# Colors for output
|
| 5 |
+
GREEN='\033[0;32m'
|
| 6 |
+
BLUE='\033[0;34m'
|
| 7 |
+
RED='\033[0;31m'
|
| 8 |
+
NC='\033[0m' # No Color
|
| 9 |
+
|
| 10 |
+
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 11 |
+
BACKEND_DIR="$PROJECT_ROOT/backend"
|
| 12 |
+
FRONTEND_DIR="$PROJECT_ROOT/frontend"
|
| 13 |
+
|
| 14 |
+
# PID files
|
| 15 |
+
BACKEND_PID_FILE="$PROJECT_ROOT/.backend.pid"
|
| 16 |
+
FRONTEND_PID_FILE="$PROJECT_ROOT/.frontend.pid"
|
| 17 |
+
|
| 18 |
+
echo -e "${BLUE}Starting Document Viewer Development Environment${NC}"
|
| 19 |
+
echo "=================================================="
|
| 20 |
+
|
| 21 |
+
# Check if backend venv exists
|
| 22 |
+
if [ ! -d "$BACKEND_DIR/.venv" ]; then
|
| 23 |
+
echo -e "${RED}Error: Backend virtual environment not found${NC}"
|
| 24 |
+
echo "Run: cd backend && uv venv && uv pip install -e ."
|
| 25 |
+
exit 1
|
| 26 |
+
fi
|
| 27 |
+
|
| 28 |
+
# Check if frontend node_modules exists
|
| 29 |
+
if [ ! -d "$FRONTEND_DIR/node_modules" ]; then
|
| 30 |
+
echo -e "${RED}Error: Frontend dependencies not installed${NC}"
|
| 31 |
+
echo "Run: cd frontend && npm install"
|
| 32 |
+
exit 1
|
| 33 |
+
fi
|
| 34 |
+
|
| 35 |
+
# Start Backend
|
| 36 |
+
echo -e "${GREEN}Starting backend server...${NC}"
|
| 37 |
+
cd "$BACKEND_DIR"
|
| 38 |
+
JWT_SECRET_KEY="local-dev-secret-key-123" \
|
| 39 |
+
VAULT_BASE_PATH="$PROJECT_ROOT/data/vaults" \
|
| 40 |
+
.venv/bin/uvicorn src.api.main:app --host 0.0.0.0 --port 8000 --reload > "$PROJECT_ROOT/backend.log" 2>&1 &
|
| 41 |
+
BACKEND_PID=$!
|
| 42 |
+
echo $BACKEND_PID > "$BACKEND_PID_FILE"
|
| 43 |
+
echo -e "${GREEN}β Backend started (PID: $BACKEND_PID)${NC}"
|
| 44 |
+
echo " Logs: $PROJECT_ROOT/backend.log"
|
| 45 |
+
echo " URL: http://localhost:8000"
|
| 46 |
+
|
| 47 |
+
# Wait a moment for backend to start
|
| 48 |
+
sleep 2
|
| 49 |
+
|
| 50 |
+
# Start Frontend
|
| 51 |
+
echo -e "${GREEN}Starting frontend dev server...${NC}"
|
| 52 |
+
cd "$FRONTEND_DIR"
|
| 53 |
+
npm run dev > "$PROJECT_ROOT/frontend.log" 2>&1 &
|
| 54 |
+
FRONTEND_PID=$!
|
| 55 |
+
echo $FRONTEND_PID > "$FRONTEND_PID_FILE"
|
| 56 |
+
echo -e "${GREEN}β Frontend started (PID: $FRONTEND_PID)${NC}"
|
| 57 |
+
echo " Logs: $PROJECT_ROOT/frontend.log"
|
| 58 |
+
echo " URL: http://localhost:5173"
|
| 59 |
+
|
| 60 |
+
echo ""
|
| 61 |
+
echo -e "${BLUE}=================================================="
|
| 62 |
+
echo "Development servers are running!"
|
| 63 |
+
echo "=================================================="
|
| 64 |
+
echo -e "${NC}"
|
| 65 |
+
echo "Frontend: http://localhost:5173"
|
| 66 |
+
echo "Backend: http://localhost:8000"
|
| 67 |
+
echo ""
|
| 68 |
+
echo "To stop servers, run: ./stop-dev.sh"
|
| 69 |
+
echo "To view logs, run: tail -f backend.log frontend.log"
|
| 70 |
+
echo ""
|
| 71 |
+
|
status-dev.sh
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Check status of development servers for Document Viewer
|
| 3 |
+
|
| 4 |
+
# Colors for output
|
| 5 |
+
GREEN='\033[0;32m'
|
| 6 |
+
RED='\033[0;31m'
|
| 7 |
+
BLUE='\033[0;34m'
|
| 8 |
+
NC='\033[0m' # No Color
|
| 9 |
+
|
| 10 |
+
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 11 |
+
BACKEND_PID_FILE="$PROJECT_ROOT/.backend.pid"
|
| 12 |
+
FRONTEND_PID_FILE="$PROJECT_ROOT/.frontend.pid"
|
| 13 |
+
|
| 14 |
+
echo -e "${BLUE}Document Viewer Development Status${NC}"
|
| 15 |
+
echo "=================================================="
|
| 16 |
+
|
| 17 |
+
# Check Backend
|
| 18 |
+
if [ -f "$BACKEND_PID_FILE" ]; then
|
| 19 |
+
BACKEND_PID=$(cat "$BACKEND_PID_FILE")
|
| 20 |
+
if ps -p $BACKEND_PID > /dev/null 2>&1; then
|
| 21 |
+
echo -e "${GREEN}β Backend: RUNNING${NC} (PID: $BACKEND_PID)"
|
| 22 |
+
echo " URL: http://localhost:8000"
|
| 23 |
+
else
|
| 24 |
+
echo -e "${RED}β Backend: NOT RUNNING${NC} (stale PID: $BACKEND_PID)"
|
| 25 |
+
fi
|
| 26 |
+
else
|
| 27 |
+
echo -e "${RED}β Backend: NOT RUNNING${NC}"
|
| 28 |
+
fi
|
| 29 |
+
|
| 30 |
+
# Check Frontend
|
| 31 |
+
if [ -f "$FRONTEND_PID_FILE" ]; then
|
| 32 |
+
FRONTEND_PID=$(cat "$FRONTEND_PID_FILE")
|
| 33 |
+
if ps -p $FRONTEND_PID > /dev/null 2>&1; then
|
| 34 |
+
echo -e "${GREEN}β Frontend: RUNNING${NC} (PID: $FRONTEND_PID)"
|
| 35 |
+
echo " URL: http://localhost:5173"
|
| 36 |
+
else
|
| 37 |
+
echo -e "${RED}β Frontend: NOT RUNNING${NC} (stale PID: $FRONTEND_PID)"
|
| 38 |
+
fi
|
| 39 |
+
else
|
| 40 |
+
echo -e "${RED}β Frontend: NOT RUNNING${NC}"
|
| 41 |
+
fi
|
| 42 |
+
|
| 43 |
+
echo ""
|
| 44 |
+
echo "Logs:"
|
| 45 |
+
echo " Backend: tail -f $PROJECT_ROOT/backend.log"
|
| 46 |
+
echo " Frontend: tail -f $PROJECT_ROOT/frontend.log"
|
| 47 |
+
echo ""
|
| 48 |
+
|
stop-dev.sh
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Stop development servers for Document Viewer
|
| 3 |
+
|
| 4 |
+
# Colors for output
|
| 5 |
+
RED='\033[0;31m'
|
| 6 |
+
GREEN='\033[0;32m'
|
| 7 |
+
NC='\033[0m' # No Color
|
| 8 |
+
|
| 9 |
+
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 10 |
+
BACKEND_PID_FILE="$PROJECT_ROOT/.backend.pid"
|
| 11 |
+
FRONTEND_PID_FILE="$PROJECT_ROOT/.frontend.pid"
|
| 12 |
+
|
| 13 |
+
echo -e "${RED}Stopping Document Viewer Development Environment${NC}"
|
| 14 |
+
echo "=================================================="
|
| 15 |
+
|
| 16 |
+
# Stop Backend
|
| 17 |
+
if [ -f "$BACKEND_PID_FILE" ]; then
|
| 18 |
+
BACKEND_PID=$(cat "$BACKEND_PID_FILE")
|
| 19 |
+
if ps -p $BACKEND_PID > /dev/null 2>&1; then
|
| 20 |
+
echo "Stopping backend (PID: $BACKEND_PID)..."
|
| 21 |
+
kill $BACKEND_PID
|
| 22 |
+
rm "$BACKEND_PID_FILE"
|
| 23 |
+
echo -e "${GREEN}β Backend stopped${NC}"
|
| 24 |
+
else
|
| 25 |
+
echo "Backend process not found (PID: $BACKEND_PID)"
|
| 26 |
+
rm "$BACKEND_PID_FILE"
|
| 27 |
+
fi
|
| 28 |
+
else
|
| 29 |
+
echo "No backend PID file found"
|
| 30 |
+
fi
|
| 31 |
+
|
| 32 |
+
# Stop Frontend
|
| 33 |
+
if [ -f "$FRONTEND_PID_FILE" ]; then
|
| 34 |
+
FRONTEND_PID=$(cat "$FRONTEND_PID_FILE")
|
| 35 |
+
if ps -p $FRONTEND_PID > /dev/null 2>&1; then
|
| 36 |
+
echo "Stopping frontend (PID: $FRONTEND_PID)..."
|
| 37 |
+
kill $FRONTEND_PID
|
| 38 |
+
rm "$FRONTEND_PID_FILE"
|
| 39 |
+
echo -e "${GREEN}β Frontend stopped${NC}"
|
| 40 |
+
else
|
| 41 |
+
echo "Frontend process not found (PID: $FRONTEND_PID)"
|
| 42 |
+
rm "$FRONTEND_PID_FILE"
|
| 43 |
+
fi
|
| 44 |
+
else
|
| 45 |
+
echo "No frontend PID file found"
|
| 46 |
+
fi
|
| 47 |
+
|
| 48 |
+
echo ""
|
| 49 |
+
echo -e "${GREEN}Development servers stopped${NC}"
|
| 50 |
+
|