vault-bridge 1.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vault_bridge-1.0.3/PKG-INFO +122 -0
- vault_bridge-1.0.3/README.md +104 -0
- vault_bridge-1.0.3/pyproject.toml +70 -0
- vault_bridge-1.0.3/setup.cfg +4 -0
- vault_bridge-1.0.3/src/obsidian_mcp/__init__.py +8 -0
- vault_bridge-1.0.3/src/obsidian_mcp/configure.py +169 -0
- vault_bridge-1.0.3/src/obsidian_mcp/core.py +363 -0
- vault_bridge-1.0.3/src/obsidian_mcp/indexer.py +566 -0
- vault_bridge-1.0.3/src/obsidian_mcp/logging.py +53 -0
- vault_bridge-1.0.3/src/obsidian_mcp/models.py +303 -0
- vault_bridge-1.0.3/src/obsidian_mcp/server.py +156 -0
- vault_bridge-1.0.3/src/vault_bridge.egg-info/PKG-INFO +122 -0
- vault_bridge-1.0.3/src/vault_bridge.egg-info/SOURCES.txt +18 -0
- vault_bridge-1.0.3/src/vault_bridge.egg-info/dependency_links.txt +1 -0
- vault_bridge-1.0.3/src/vault_bridge.egg-info/entry_points.txt +2 -0
- vault_bridge-1.0.3/src/vault_bridge.egg-info/requires.txt +12 -0
- vault_bridge-1.0.3/src/vault_bridge.egg-info/top_level.txt +1 -0
- vault_bridge-1.0.3/tests/test_core.py +344 -0
- vault_bridge-1.0.3/tests/test_logging.py +221 -0
- vault_bridge-1.0.3/tests/test_server.py +223 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vault-bridge
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Bridge your AI assistant to local knowledge vaults — read, write, and search notes without cloud dependencies
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: fastmcp-slim[server]<4.0,>=3.4
|
|
8
|
+
Requires-Dist: pyyaml>=6.0
|
|
9
|
+
Requires-Dist: structlog>=24.1
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.3
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
15
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
16
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
17
|
+
Requires-Dist: types-PyYAML; extra == "dev"
|
|
18
|
+
|
|
19
|
+
# Vault Bridge
|
|
20
|
+
|
|
21
|
+
Bridge your AI assistant to local knowledge vaults — read, write, search, and manage notes without cloud dependencies.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- **read_note** — Read a note with optional frontmatter parsing
|
|
26
|
+
- **write_note** — Write notes with YAML frontmatter support
|
|
27
|
+
- **list_notes** — List notes with glob filtering and recursion
|
|
28
|
+
- **search_notes** — Regex search across vault content
|
|
29
|
+
- **search_frontmatter** — Query notes by frontmatter key/value
|
|
30
|
+
- **get_daily_note** — Get or create daily notes with templates
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install vault-bridge
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
vault-bridge
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or configure in your MCP client:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"vault": {
|
|
50
|
+
"command": "vault-bridge",
|
|
51
|
+
"env": {
|
|
52
|
+
"VAULT_BRIDGE_VAULT_PATH": "C:/path/to/vault"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
| Environment Variable | Default | Description |
|
|
62
|
+
|---------------------|---------|-------------|
|
|
63
|
+
| `VAULT_BRIDGE_VAULT_PATH` | Current directory | Path to vault root |
|
|
64
|
+
| `VAULT_BRIDGE_MAX_FILE_SIZE` | 10MB | Max file size for operations |
|
|
65
|
+
| `VAULT_BRIDGE_DEFAULT_ENCODING` | utf-8 | Text encoding |
|
|
66
|
+
| `VAULT_BRIDGE_INDEX_FRONTMATTER` | true | Parse YAML frontmatter |
|
|
67
|
+
| `VAULT_BRIDGE_FOLLOW_SYMLINKS` | false | Follow symlinks |
|
|
68
|
+
|
|
69
|
+
## Tool Reference
|
|
70
|
+
|
|
71
|
+
### read_note
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"path": "Projects/roadmap.md",
|
|
75
|
+
"include_frontmatter": true
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### write_note
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"path": "Projects/new-idea.md",
|
|
83
|
+
"content": "# New Idea\n\nDetails here...",
|
|
84
|
+
"frontmatter": { "tags": ["idea", "draft"], "status": "wip" }
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### list_notes
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"path": "Projects",
|
|
92
|
+
"glob_pattern": "**/*.md",
|
|
93
|
+
"recursive": true
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### search_notes
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"pattern": "MCP",
|
|
101
|
+
"path": ".",
|
|
102
|
+
"max_results": 50
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### search_frontmatter
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"key": "status",
|
|
110
|
+
"value": "done",
|
|
111
|
+
"operator": "eq"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### get_daily_note
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"date": "2026-08-21",
|
|
119
|
+
"folder": "Daily Notes",
|
|
120
|
+
"create_if_missing": true
|
|
121
|
+
}
|
|
122
|
+
```
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Vault Bridge
|
|
2
|
+
|
|
3
|
+
Bridge your AI assistant to local knowledge vaults — read, write, search, and manage notes without cloud dependencies.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **read_note** — Read a note with optional frontmatter parsing
|
|
8
|
+
- **write_note** — Write notes with YAML frontmatter support
|
|
9
|
+
- **list_notes** — List notes with glob filtering and recursion
|
|
10
|
+
- **search_notes** — Regex search across vault content
|
|
11
|
+
- **search_frontmatter** — Query notes by frontmatter key/value
|
|
12
|
+
- **get_daily_note** — Get or create daily notes with templates
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install vault-bridge
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
vault-bridge
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or configure in your MCP client:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"vault": {
|
|
32
|
+
"command": "vault-bridge",
|
|
33
|
+
"env": {
|
|
34
|
+
"VAULT_BRIDGE_VAULT_PATH": "C:/path/to/vault"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
| Environment Variable | Default | Description |
|
|
44
|
+
|---------------------|---------|-------------|
|
|
45
|
+
| `VAULT_BRIDGE_VAULT_PATH` | Current directory | Path to vault root |
|
|
46
|
+
| `VAULT_BRIDGE_MAX_FILE_SIZE` | 10MB | Max file size for operations |
|
|
47
|
+
| `VAULT_BRIDGE_DEFAULT_ENCODING` | utf-8 | Text encoding |
|
|
48
|
+
| `VAULT_BRIDGE_INDEX_FRONTMATTER` | true | Parse YAML frontmatter |
|
|
49
|
+
| `VAULT_BRIDGE_FOLLOW_SYMLINKS` | false | Follow symlinks |
|
|
50
|
+
|
|
51
|
+
## Tool Reference
|
|
52
|
+
|
|
53
|
+
### read_note
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"path": "Projects/roadmap.md",
|
|
57
|
+
"include_frontmatter": true
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### write_note
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"path": "Projects/new-idea.md",
|
|
65
|
+
"content": "# New Idea\n\nDetails here...",
|
|
66
|
+
"frontmatter": { "tags": ["idea", "draft"], "status": "wip" }
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### list_notes
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"path": "Projects",
|
|
74
|
+
"glob_pattern": "**/*.md",
|
|
75
|
+
"recursive": true
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### search_notes
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"pattern": "MCP",
|
|
83
|
+
"path": ".",
|
|
84
|
+
"max_results": 50
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### search_frontmatter
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"key": "status",
|
|
92
|
+
"value": "done",
|
|
93
|
+
"operator": "eq"
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### get_daily_note
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"date": "2026-08-21",
|
|
101
|
+
"folder": "Daily Notes",
|
|
102
|
+
"create_if_missing": true
|
|
103
|
+
}
|
|
104
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "vault-bridge"
|
|
3
|
+
version = "1.0.3"
|
|
4
|
+
description = "Bridge your AI assistant to local knowledge vaults — read, write, and search notes without cloud dependencies"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"fastmcp-slim[server]>=3.4,<4.0",
|
|
9
|
+
"pyyaml>=6.0",
|
|
10
|
+
"structlog>=24.1",
|
|
11
|
+
"pydantic-settings>=2.3",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
vault-bridge = "obsidian_mcp.server:main"
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=8.0",
|
|
20
|
+
"pytest-asyncio>=0.23",
|
|
21
|
+
"pytest-cov>=5.0",
|
|
22
|
+
"ruff>=0.4",
|
|
23
|
+
"mypy>=1.10",
|
|
24
|
+
"types-PyYAML",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.ruff]
|
|
28
|
+
target-version = "py311"
|
|
29
|
+
line-length = 100
|
|
30
|
+
select = ["E", "F", "I", "UP", "W", "C4", "PT", "PTH", "SIM", "T10", "ARG", "RUF"]
|
|
31
|
+
ignore = [
|
|
32
|
+
"S101",
|
|
33
|
+
"PTH123",
|
|
34
|
+
"ARG001",
|
|
35
|
+
"ARG002",
|
|
36
|
+
"TRY003",
|
|
37
|
+
"PLC0415",
|
|
38
|
+
"PLR2004",
|
|
39
|
+
"ARG002",
|
|
40
|
+
"SIM102",
|
|
41
|
+
"INP001",
|
|
42
|
+
"F401",
|
|
43
|
+
"W292",
|
|
44
|
+
"S603",
|
|
45
|
+
"PLW1510",
|
|
46
|
+
"PLW0603",
|
|
47
|
+
"W293",
|
|
48
|
+
"W291",
|
|
49
|
+
"E501",
|
|
50
|
+
"UP015",
|
|
51
|
+
"RUF022",
|
|
52
|
+
"I001",
|
|
53
|
+
"BLE001",
|
|
54
|
+
]
|
|
55
|
+
fixable = ["ALL"]
|
|
56
|
+
unfixable = []
|
|
57
|
+
|
|
58
|
+
[tool.ruff.format]
|
|
59
|
+
quote-style = "double"
|
|
60
|
+
indent-style = "space"
|
|
61
|
+
|
|
62
|
+
[tool.mypy]
|
|
63
|
+
python_version = "3.11"
|
|
64
|
+
warn_return_any = true
|
|
65
|
+
warn_unused_configs = true
|
|
66
|
+
disallow_untyped_defs = true
|
|
67
|
+
strict = true
|
|
68
|
+
|
|
69
|
+
[tool.hatch.build.targets.wheel]
|
|
70
|
+
packages = ["src/obsidian_mcp"]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Auto-configure CLI for vault-bridge — one-command setup like natestrong."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, TypedDict
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from .logging import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ClientConfig(TypedDict):
|
|
16
|
+
config_path: Path
|
|
17
|
+
server_key: str
|
|
18
|
+
template: dict[str, Any]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
CLIENT_CONFIGS: dict[str, ClientConfig] = {
|
|
22
|
+
"claude_desktop": {
|
|
23
|
+
"config_path": Path.home() / "AppData" / "Roaming" / "Claude" / "claude_desktop_config.json",
|
|
24
|
+
"server_key": "vault-bridge",
|
|
25
|
+
"template": {
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"vault-bridge": {
|
|
28
|
+
"command": "uvx",
|
|
29
|
+
"args": ["vault-bridge", "{vault_path}"],
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
"cursor": {
|
|
35
|
+
"config_path": Path.home() / ".cursor" / "mcp.json",
|
|
36
|
+
"server_key": "vault-bridge",
|
|
37
|
+
"template": {
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"vault-bridge": {
|
|
40
|
+
"command": "uvx",
|
|
41
|
+
"args": ["vault-bridge", "{vault_path}"],
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
"windsurf": {
|
|
47
|
+
"config_path": Path.home() / ".codeium" / "windsurf" / "mcp_config.json",
|
|
48
|
+
"server_key": "vault-bridge",
|
|
49
|
+
"template": {
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"vault-bridge": {
|
|
52
|
+
"command": "uvx",
|
|
53
|
+
"args": ["vault-bridge", "{vault_path}"],
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def load_existing_config(config_path: Path) -> dict[str, Any]:
|
|
62
|
+
"""Load existing JSON config or return empty."""
|
|
63
|
+
if config_path.exists():
|
|
64
|
+
try:
|
|
65
|
+
with open(config_path, encoding="utf-8") as f:
|
|
66
|
+
data: dict[str, Any] = json.load(f)
|
|
67
|
+
return data
|
|
68
|
+
except json.JSONDecodeError:
|
|
69
|
+
return {}
|
|
70
|
+
return {}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def save_config(config_path: Path, config: dict[str, Any]) -> None:
|
|
74
|
+
"""Save JSON config with pretty formatting."""
|
|
75
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
77
|
+
json.dump(config, f, indent=2)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def update_config_for_client(config: dict[str, Any], server_key: str, vault_path: str) -> dict[str, Any]:
|
|
81
|
+
"""Update config with vault-bridge server entry."""
|
|
82
|
+
if "mcpServers" not in config:
|
|
83
|
+
config["mcpServers"] = {}
|
|
84
|
+
|
|
85
|
+
config["mcpServers"][server_key] = {
|
|
86
|
+
"command": "uvx",
|
|
87
|
+
"args": ["vault-bridge", vault_path],
|
|
88
|
+
}
|
|
89
|
+
return config
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@click.command()
|
|
93
|
+
@click.option(
|
|
94
|
+
"--vault-path",
|
|
95
|
+
"-v",
|
|
96
|
+
required=True,
|
|
97
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
|
|
98
|
+
help="Path to the Obsidian vault root directory.",
|
|
99
|
+
)
|
|
100
|
+
@click.option(
|
|
101
|
+
"--client",
|
|
102
|
+
"-c",
|
|
103
|
+
type=click.Choice(list(CLIENT_CONFIGS.keys())),
|
|
104
|
+
default="claude_desktop",
|
|
105
|
+
help="Target client to configure.",
|
|
106
|
+
)
|
|
107
|
+
@click.option(
|
|
108
|
+
"--force",
|
|
109
|
+
"-f",
|
|
110
|
+
is_flag=True,
|
|
111
|
+
help="Overwrite existing vault-bridge configuration.",
|
|
112
|
+
)
|
|
113
|
+
@click.option(
|
|
114
|
+
"--dry-run",
|
|
115
|
+
is_flag=True,
|
|
116
|
+
help="Show what would be changed without writing.",
|
|
117
|
+
)
|
|
118
|
+
def main(vault_path: Path, client: str, force: bool, dry_run: bool) -> int:
|
|
119
|
+
"""Auto-configure vault-bridge for your AI client.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
vault-bridge-configure -v "/path/to/vault" -c claude_desktop
|
|
123
|
+
"""
|
|
124
|
+
vault_path = vault_path.resolve()
|
|
125
|
+
|
|
126
|
+
# Validate vault
|
|
127
|
+
if not (vault_path / ".obsidian").exists() and not any(vault_path.glob("*.md")):
|
|
128
|
+
logger.warning("configure.not_vault", path=str(vault_path))
|
|
129
|
+
click.echo(f"Warning: {vault_path} doesn't appear to be an Obsidian vault (.obsidian folder missing, no .md files)", err=True)
|
|
130
|
+
if not force:
|
|
131
|
+
return 1
|
|
132
|
+
|
|
133
|
+
client_config = CLIENT_CONFIGS[client]
|
|
134
|
+
config_path = client_config["config_path"]
|
|
135
|
+
server_key = client_config["server_key"]
|
|
136
|
+
|
|
137
|
+
logger.info("configure.start", vault_path=str(vault_path), client=client)
|
|
138
|
+
|
|
139
|
+
# Load existing config
|
|
140
|
+
existing: dict[str, Any] = load_existing_config(config_path)
|
|
141
|
+
|
|
142
|
+
# Check for existing entry
|
|
143
|
+
if server_key in existing.get("mcpServers", {}) and not force:
|
|
144
|
+
logger.warning("configure.exists", config_path=str(config_path))
|
|
145
|
+
click.echo(f"vault-bridge already configured in {config_path}. Use --force to overwrite.", err=True)
|
|
146
|
+
return 1
|
|
147
|
+
|
|
148
|
+
# Update config
|
|
149
|
+
updated: dict[str, Any] = update_config_for_client(existing, server_key, str(vault_path))
|
|
150
|
+
|
|
151
|
+
if dry_run:
|
|
152
|
+
click.echo(f"Would update {config_path}:")
|
|
153
|
+
click.echo(json.dumps(updated, indent=2))
|
|
154
|
+
return 0
|
|
155
|
+
|
|
156
|
+
# Save
|
|
157
|
+
save_config(config_path, updated)
|
|
158
|
+
|
|
159
|
+
logger.info("configure.success", config_path=str(config_path))
|
|
160
|
+
click.echo(f"✓ Configured vault-bridge for {client}")
|
|
161
|
+
click.echo(f" Config: {config_path}")
|
|
162
|
+
click.echo(f" Vault: {vault_path}")
|
|
163
|
+
click.echo(f" Restart {client} to apply changes.")
|
|
164
|
+
|
|
165
|
+
return 0
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if __name__ == "__main__":
|
|
169
|
+
sys.exit(main())
|