knowledge-gateway 0.7.4__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.
Files changed (60) hide show
  1. knowledge_gateway-0.7.4/.github/dependabot.yml +20 -0
  2. knowledge_gateway-0.7.4/.github/workflows/ci.yml +35 -0
  3. knowledge_gateway-0.7.4/.github/workflows/release.yml +37 -0
  4. knowledge_gateway-0.7.4/.gitignore +12 -0
  5. knowledge_gateway-0.7.4/CHANGELOG.md +163 -0
  6. knowledge_gateway-0.7.4/LICENSE +21 -0
  7. knowledge_gateway-0.7.4/PKG-INFO +296 -0
  8. knowledge_gateway-0.7.4/README.md +258 -0
  9. knowledge_gateway-0.7.4/conftest.py +2 -0
  10. knowledge_gateway-0.7.4/deploy/auto-update.sh +11 -0
  11. knowledge_gateway-0.7.4/deploy/knowledge-gateway-update.service +13 -0
  12. knowledge_gateway-0.7.4/deploy/knowledge-gateway-update.timer +10 -0
  13. knowledge_gateway-0.7.4/deploy/knowledge-gateway.service +54 -0
  14. knowledge_gateway-0.7.4/deploy/tailscale.md +42 -0
  15. knowledge_gateway-0.7.4/gateway/__init__.py +7 -0
  16. knowledge_gateway-0.7.4/gateway/__main__.py +3 -0
  17. knowledge_gateway-0.7.4/gateway/acl.py +62 -0
  18. knowledge_gateway-0.7.4/gateway/codegraph/__init__.py +15 -0
  19. knowledge_gateway-0.7.4/gateway/codegraph/build.py +89 -0
  20. knowledge_gateway-0.7.4/gateway/codegraph/cli.py +37 -0
  21. knowledge_gateway-0.7.4/gateway/codegraph/extract_ansible.py +183 -0
  22. knowledge_gateway-0.7.4/gateway/codegraph/extract_python.py +64 -0
  23. knowledge_gateway-0.7.4/gateway/codegraph/treesitter.py +84 -0
  24. knowledge_gateway-0.7.4/gateway/config.py +79 -0
  25. knowledge_gateway-0.7.4/gateway/convert.py +25 -0
  26. knowledge_gateway-0.7.4/gateway/detect.py +49 -0
  27. knowledge_gateway-0.7.4/gateway/edits.py +160 -0
  28. knowledge_gateway-0.7.4/gateway/gitops.py +85 -0
  29. knowledge_gateway-0.7.4/gateway/graph.py +150 -0
  30. knowledge_gateway-0.7.4/gateway/links.py +22 -0
  31. knowledge_gateway-0.7.4/gateway/locks.py +74 -0
  32. knowledge_gateway-0.7.4/gateway/search.py +94 -0
  33. knowledge_gateway-0.7.4/gateway/server.py +102 -0
  34. knowledge_gateway-0.7.4/gateway/tags.py +20 -0
  35. knowledge_gateway-0.7.4/gateway/tools.py +480 -0
  36. knowledge_gateway-0.7.4/gateway/vaults.py +147 -0
  37. knowledge_gateway-0.7.4/gateway/writes.py +31 -0
  38. knowledge_gateway-0.7.4/pyproject.toml +57 -0
  39. knowledge_gateway-0.7.4/server.json +19 -0
  40. knowledge_gateway-0.7.4/tests/conftest.py +32 -0
  41. knowledge_gateway-0.7.4/tests/test_acl.py +60 -0
  42. knowledge_gateway-0.7.4/tests/test_attachments.py +49 -0
  43. knowledge_gateway-0.7.4/tests/test_canvas.py +44 -0
  44. knowledge_gateway-0.7.4/tests/test_codegraph.py +74 -0
  45. knowledge_gateway-0.7.4/tests/test_config.py +104 -0
  46. knowledge_gateway-0.7.4/tests/test_detect.py +61 -0
  47. knowledge_gateway-0.7.4/tests/test_edits.py +126 -0
  48. knowledge_gateway-0.7.4/tests/test_gitops.py +53 -0
  49. knowledge_gateway-0.7.4/tests/test_graph.py +70 -0
  50. knowledge_gateway-0.7.4/tests/test_local.py +33 -0
  51. knowledge_gateway-0.7.4/tests/test_locks.py +84 -0
  52. knowledge_gateway-0.7.4/tests/test_masking.py +41 -0
  53. knowledge_gateway-0.7.4/tests/test_search.py +23 -0
  54. knowledge_gateway-0.7.4/tests/test_security.py +39 -0
  55. knowledge_gateway-0.7.4/tests/test_tools.py +82 -0
  56. knowledge_gateway-0.7.4/tests/test_vaults.py +112 -0
  57. knowledge_gateway-0.7.4/tests/test_writes.py +48 -0
  58. knowledge_gateway-0.7.4/tokens.example.yaml +27 -0
  59. knowledge_gateway-0.7.4/uv.lock +2234 -0
  60. knowledge_gateway-0.7.4/vaults.example.yaml +23 -0
@@ -0,0 +1,20 @@
1
+ version: 2
2
+ # Keep the SHA-pinned GitHub Actions and the Python/uv dependency tree current.
3
+ # Dependabot opens small PRs; CI (and branch protection) gate them.
4
+ updates:
5
+ - package-ecosystem: github-actions
6
+ directory: /
7
+ schedule:
8
+ interval: weekly
9
+ commit-message:
10
+ prefix: ci
11
+ groups:
12
+ actions:
13
+ patterns: ["*"]
14
+
15
+ - package-ecosystem: uv
16
+ directory: /
17
+ schedule:
18
+ interval: weekly
19
+ commit-message:
20
+ prefix: deps
@@ -0,0 +1,35 @@
1
+ name: ci
2
+
3
+ # Test on every PR and on pushes to main (so the published default branch and
4
+ # every release tag are known-green). No secrets, no network beyond pip.
5
+ on:
6
+ pull_request:
7
+ push:
8
+ branches: [main]
9
+ tags: ['v*']
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: ${{ github.workflow }}-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ jobs:
19
+ test:
20
+ runs-on: ubuntu-24.04
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ python-version: ["3.11", "3.12", "3.13"]
25
+ steps:
26
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
27
+ - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
28
+ # The committed lockfile must stay consistent with pyproject.
29
+ - run: uv lock --check
30
+ # search/backlinks/list_tags shell out to ripgrep; install it so those
31
+ # tools and their tests actually run in CI (not silently skipped).
32
+ - run: sudo apt-get update && sudo apt-get install -y ripgrep
33
+ - run: uv venv --python ${{ matrix.python-version }}
34
+ - run: uv pip install -e ".[dev]"
35
+ - run: uv run pytest -q --cov=gateway --cov-report=term-missing
@@ -0,0 +1,37 @@
1
+ name: release
2
+
3
+ # On a vX.Y.Z tag: build, publish a GitHub Release, then publish to PyPI via Trusted
4
+ # Publishing (OIDC, no token). CI (ci.yml) also runs on tags, so a release is only cut
5
+ # from a known-green tree. The GitHub Release runs before PyPI so a not-yet-configured
6
+ # Trusted Publisher cannot block it.
7
+ # One-time setup on pypi.org: add a pending publisher for project `knowledge-gateway` ->
8
+ # owner fszalaj, repo knowledge-gateway, workflow release.yml, environment pypi.
9
+ # (Renamed from `obsidian-gateway`; the old project's publisher no longer matches the dist name.)
10
+ on:
11
+ push:
12
+ tags: ['v*']
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ concurrency:
18
+ group: release-${{ github.ref }}
19
+ cancel-in-progress: false
20
+
21
+ jobs:
22
+ release:
23
+ runs-on: ubuntu-24.04
24
+ permissions:
25
+ contents: write
26
+ id-token: write
27
+ environment: pypi
28
+ steps:
29
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
30
+ - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
31
+ - run: uv build
32
+ - name: Publish GitHub Release
33
+ env:
34
+ GH_TOKEN: ${{ github.token }}
35
+ run: gh release create "${{ github.ref_name }}" dist/* --title "${{ github.ref_name }}" --generate-notes
36
+ - name: Publish to PyPI (Trusted Publishing)
37
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
@@ -0,0 +1,12 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .coverage
6
+ htmlcov/
7
+ dist/
8
+ build/
9
+
10
+ # Live config - NEVER commit (paths + bearer tokens live only on the host)
11
+ vaults.yaml
12
+ tokens.yaml
@@ -0,0 +1,163 @@
1
+ # Changelog
2
+
3
+ All notable changes to knowledge-gateway. Consumers track the moving **`stable`** branch
4
+ (`uvx --refresh --from git+...@stable`); each release moves `stable` and auto-propagates on
5
+ next launch (no per-repo re-pin). Every release is also an immutable `vX.Y.Z` tag for
6
+ pinning/audit.
7
+
8
+ ## v0.7.4 - 2026-06-27
9
+
10
+ ### Changed
11
+ - GitHub repo renamed to **`knowledge-gateway`**; all URLs now point to
12
+ `github.com/fszalaj/knowledge-gateway` (the old `obsidian-gateway` URL redirects, so existing
13
+ `@stable` configs keep working). First release cut under the `knowledge-gateway` PyPI Trusted
14
+ Publisher.
15
+
16
+ ## v0.7.3 - 2026-06-27
17
+
18
+ ### Fixed
19
+ - Swept the last `obsidian-gateway` references in `gateway/` code: the tempfile-fallback lock-dir
20
+ name and the fcntl-unavailable warning in `gateway/locks.py`, and the `knowledge-gateway-graph`
21
+ CLI docstring. `git grep obsidian-gateway -- gateway/**` is now empty (the name survives only in
22
+ the repo URL, the "formerly" note, and CHANGELOG history).
23
+
24
+ ## v0.7.2 - 2026-06-27
25
+
26
+ ### Fixed
27
+ - Three stray `obsidian-gateway` references missed in the rename (caught by review): the
28
+ `importlib.metadata.version("obsidian-gateway")` lookup in `gateway/__init__.py` (was silently
29
+ falling back to the wrong version), the `knowledge-gateway-graph` CLI `prog` name, and the git
30
+ lock-dir name in `gateway/locks.py`.
31
+
32
+ ## v0.7.1 - 2026-06-27
33
+
34
+ ### Changed
35
+ - **Dropped the obsidian-gateway back-compat aliases** (pre-1.0 dev): removed the `obsidian-gateway`
36
+ console scripts and renamed the `OBSIDIAN_GATEWAY_*` env vars to `KNOWLEDGE_GATEWAY_*`. The only
37
+ names now are `knowledge-gateway` (the git repo URL stays `.../obsidian-gateway` until the repo is
38
+ renamed). Swept the README + deploy units; renamed the `deploy/*.service`/`.timer` unit files.
39
+
40
+ ## v0.7.0 - 2026-06-27
41
+
42
+ ### Changed
43
+ - **Renamed `obsidian-gateway` -> `knowledge-gateway`** - it is no longer just a vault wrapper but a
44
+ knowledge gateway (vault + code-graph + convert). The distribution, CLI, MCP display name, and
45
+ `server.json` are now `knowledge-gateway`; the import package stays `gateway`, and the MCP server
46
+ is still keyed `wiki` in client configs.
47
+ - **Back-compat:** the old `obsidian-gateway` / `obsidian-gateway-graph` console scripts remain as
48
+ aliases, and `OBSIDIAN_GATEWAY_VAULT`/`_LOCAL` env vars are still read - existing `uvx --from
49
+ git+...@stable obsidian-gateway` configs keep working during the transition.
50
+ - README repositioned (vault + code-graph + convert), `server.py` instructions list the graph/convert tools.
51
+
52
+ ### PyPI
53
+ - Trusted Publishing must be reconfigured for the new project name: add a pending publisher for
54
+ `knowledge-gateway` (owner fszalaj, repo knowledge-gateway, workflow release.yml, environment pypi).
55
+
56
+ ## v0.6.0 - 2026-06-27
57
+
58
+ ### Added
59
+ - **Code graph (optional `[graph]` / `[graph-all]`)**: a `gateway/codegraph/` package builds a
60
+ NetworkX graph of a source tree - Python (`ast`), Ansible (PyYAML walker: roles/tasks/handlers/
61
+ `include_role`/`import_tasks`/`notify` + `task -> filter plugin` edges), and an optional broad
62
+ tree-sitter pass (JS/TS/Go/Rust/Terraform/bash/PowerShell/...). New read-only MCP tools
63
+ `list_graphs`, `graph_query`, `graph_neighbors`, `god_nodes`, `graph_shortest_path`,
64
+ `graph_stats`; a local-only `graph_build`; and a `obsidian-gateway-graph` CLI.
65
+ - **Document conversion (optional `[convert]`)**: `convert_to_markdown` turns a vault file
66
+ (PDF/Office/image/HTML/...) into Markdown via markitdown.
67
+
68
+ ### Security
69
+ - Graph files live in the vault's `.graph/` and are vault-contained (resolved + `is_relative_to`
70
+ the vault, so a symlinked `.graph` cannot escape). Malformed graphs map to `graph_invalid:`.
71
+ Optional deps (networkx, tree-sitter-language-pack, markitdown) are imported lazily, so the core
72
+ gateway requires none of them.
73
+
74
+ ## v0.5.1 - 2026-06-16
75
+
76
+ ### Changed
77
+ - **Server instructions**: point agents at `_templates/<type>.md` before creating a page
78
+ (the folder is already reachable via `list_notes`/`read_note` - no new tools).
79
+
80
+ ### Distribution
81
+ - **PyPI Trusted Publishing**: `release.yml` publishes to PyPI on a `vX.Y.Z` tag via OIDC
82
+ (no token). First PyPI release - consumers can `uvx obsidian-gateway` (alongside `@stable`).
83
+ - **MCP Registry**: `server.json` manifest + a `mcp-name` marker in the README, for listing
84
+ in the official MCP Registry.
85
+
86
+ ## v0.5.0 - 2026-06-15
87
+
88
+ ### Features (MCP-FEAT)
89
+ - **Attachments**: `list_attachments` + `read_attachment` - read binary vault files (images
90
+ return as an inline `Image`; PDF/audio/video as a `File`), path-guarded, 25 MiB cap.
91
+ - **Obsidian Canvas**: `list_canvases` + `read_canvas` + `write_canvas` - read/write `.canvas`
92
+ JSON (nodes including `group` nodes, edges, `color` fields), so agents can work with groups
93
+ and colors.
94
+
95
+ ## v0.4.2 - 2026-06-15
96
+
97
+ ### Concurrency (CONC-1)
98
+ - **Per-repo write lock** (`fcntl.flock`): serializes read-modify-write tools
99
+ (`patch_note` / `patch_frontmatter`) and concurrent commits across threads and processes on
100
+ one host, fixing lost-update and mixed-commit races on the shared server. Taken once at the
101
+ tool boundary (the inner commit never re-locks); degrades to a no-op (one-time warning) where
102
+ fcntl/flock is unavailable, rather than failing the write.
103
+ - **Path-scoped commits**: each mutating op commits only its own files (`commit(paths=...)`),
104
+ so a `commit=True` op cannot sweep and mis-attribute a concurrent op's pending change.
105
+ `GIT_LITERAL_PATHSPECS=1` on every git call.
106
+
107
+ ## v0.4.1 - 2026-06-15
108
+
109
+ ### Docs
110
+ - README rewritten (enterprise style; architecture + `stable`-distribution mermaid diagrams; documents the `stable` "update once" model; AI-setup prompt fixed to `@stable` + `--local`).
111
+
112
+ ### Changed
113
+ - The FastMCP server ships an `instructions` prompt describing the tools + Obsidian/git conventions to connecting agents.
114
+ - Reference deploy artifacts for the `@stable` model: `deploy/obsidian-gateway.service` (uv-tool binary) + `deploy/obsidian-gateway-update.{service,timer}` + `deploy/auto-update.sh`.
115
+
116
+ ### Dependencies
117
+ - `ruamel.yaml` allowed up to `<0.20` (lock 0.19.1; Dependabot).
118
+
119
+ ## v0.4.0 - 2026-06-15
120
+
121
+ ### Security
122
+ - Server-mode **error masking**: the HTTP server runs `mask_error_details=True`; the
123
+ gateway's expected client-facing failures surface as `ToolError`, while unexpected OS/git
124
+ errors are hidden from the client.
125
+
126
+ ### Features
127
+ - **`--local` vault auto-detect**: `--local` / `OBSIDIAN_GATEWAY_LOCAL` auto-detects the
128
+ cwd's vault (cwd-is-vault, `./wiki`, a real `*-obsidian-vault`, a child with `.obsidian/`),
129
+ so one global codex/antigravity MCP config works in any repo. Explicit `--vault` still
130
+ wins; a bare invocation still runs the HTTP server.
131
+
132
+ ### Distribution
133
+ - Introduced the moving **`stable`** branch for "update once" rollout (see the header).
134
+
135
+ ## v0.3.0 - 2026-06-15
136
+
137
+ Security, supply-chain, Obsidian-correctness and test coverage. Re-baselines the project
138
+ after the old `v0.2.0` tag was removed; pin this release's commit SHA (or a future PyPI
139
+ `==0.3.0`).
140
+
141
+ ### Security & supply chain
142
+ - Runtime deps bounded to the current major (`fastmcp>=3,<4`, `pyyaml>=6,<7`, `ruamel.yaml>=0.18,<0.19`); `uv.lock` committed and CI-verified (`uv lock --check`).
143
+ - `tokens.yaml` is refused at load time if it is group/world-readable.
144
+ - `atomic_write` preserves an existing note's file mode (a new note is 0644, not mkstemp's 0600).
145
+ - systemd unit hardened with seccomp/capability/rlimit sandboxing (safe for a `--user` unit).
146
+ - CI: Node 24 SHA-pinned actions, Python 3.11-3.13 matrix, ripgrep installed, coverage; Dependabot for github-actions + uv.
147
+
148
+ ### Features & correctness
149
+ - `backlinks` and `rename_note` match the flat note name **case-insensitively** (Obsidian resolves links that way) and accept a trailing `.md` and `^block`.
150
+ - `read_note` rejects a note over 10 MiB; `query_notes` handles a scalar frontmatter `tags:`.
151
+ - `__version__` is sourced from package metadata.
152
+
153
+ ### Tests
154
+ - Coverage 54% -> 82%; added end-to-end tool tests plus rename / gitops / search / security suites.
155
+
156
+ ### Planned
157
+ - PyPI Trusted Publishing, so consumers can `uvx obsidian-gateway==<version>` without a git fetch.
158
+ - Server-mode concurrency hardening (per-note locks, path-scoped commits) and error masking.
159
+
160
+ ## v0.2.0 - removed
161
+
162
+ Initial release: local stdio + HTTP server, 14 git/Obsidian-aware tools, per-vault ACL,
163
+ path guards. This tag was deleted; use v0.3.0 or later.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Filip Szalaj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,296 @@
1
+ Metadata-Version: 2.4
2
+ Name: knowledge-gateway
3
+ Version: 0.7.4
4
+ Summary: Filesystem/git-native FastMCP knowledge gateway: serve an Obsidian vault over MCP, plus optional code-graph and doc-to-markdown tools (formerly obsidian-gateway)
5
+ Project-URL: Homepage, https://github.com/fszalaj/knowledge-gateway
6
+ Project-URL: Repository, https://github.com/fszalaj/knowledge-gateway
7
+ Project-URL: Issues, https://github.com/fszalaj/knowledge-gateway/issues
8
+ Author: Filip Szalaj
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: ansible,code-graph,fastmcp,git,knowledge-base,knowledge-graph,markdown,mcp,obsidian,tree-sitter,vault
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: fastmcp<4,>=3
19
+ Requires-Dist: pyyaml<7,>=6
20
+ Requires-Dist: ruamel-yaml<0.20,>=0.18
21
+ Provides-Extra: all
22
+ Requires-Dist: markitdown>=0.1; extra == 'all'
23
+ Requires-Dist: networkx<4,>=3.4; extra == 'all'
24
+ Requires-Dist: tree-sitter-language-pack>=0.7; extra == 'all'
25
+ Provides-Extra: convert
26
+ Requires-Dist: markitdown>=0.1; extra == 'convert'
27
+ Provides-Extra: dev
28
+ Requires-Dist: networkx<4,>=3.4; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
30
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0; extra == 'dev'
32
+ Provides-Extra: graph
33
+ Requires-Dist: networkx<4,>=3.4; extra == 'graph'
34
+ Provides-Extra: graph-all
35
+ Requires-Dist: networkx<4,>=3.4; extra == 'graph-all'
36
+ Requires-Dist: tree-sitter-language-pack>=0.7; extra == 'graph-all'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # knowledge-gateway
40
+
41
+ <!-- mcp-name: io.github.fszalaj/knowledge-gateway -->
42
+
43
+ > Formerly **obsidian-gateway**. Renamed because it is no longer just a vault wrapper - it is a
44
+ > filesystem- and git-native **knowledge gateway** for AI agents. The package, CLI, and MCP
45
+ > display name are now `knowledge-gateway`; the MCP server is still keyed `wiki` in client configs.
46
+
47
+ A single MCP server that gives agents (Claude Code, Codex, Cursor, Gemini, Copilot, Antigravity)
48
+ three capabilities over one connection:
49
+
50
+ - **Vault** - read, search, and **edit** a git-backed Markdown/Obsidian vault (no Obsidian GUI), git as the source of truth.
51
+ - **Code graph** *(optional `[graph]` / `[graph-all]`)* - build and query a code/Ansible knowledge graph of a repo (functions, calls, roles, tasks, handlers, `task -> filter` edges); AST-only, local, no LLM.
52
+ - **Convert** *(optional `[convert]`)* - turn PDF / Office / image / HTML files into Markdown.
53
+
54
+ The vault layer exists because the Obsidian *Local REST API* plugin serves only the one vault open
55
+ in a running desktop instance, writes without a lock (silent lost updates), needs a token in every
56
+ client, and treats git as secondary. This gateway operates on the files directly, with git as the
57
+ system of record - and adds the graph + convert tools the same way: one server, opt-in extras, no
58
+ new servers to wire.
59
+
60
+ ## Architecture
61
+
62
+ ```mermaid
63
+ flowchart LR
64
+ subgraph clients [Agents]
65
+ A1[Claude Code]
66
+ A2[Codex]
67
+ A3[Antigravity / Cursor]
68
+ end
69
+ A1 --- M(( MCP ))
70
+ A2 --- M
71
+ A3 --- M
72
+ M -->|stdio, per repo, no auth| L[Local gateway]
73
+ M -->|HTTP + bearer + ACL| S[Shared gateway]
74
+ L --> V[/Vault: Markdown files/]
75
+ S --> V
76
+ V <-->|atomic write + scoped commit| G[(git)]
77
+ ```
78
+
79
+ Both modes run the **same** tool implementation over the **same** path guards; they differ in
80
+ transport, authentication/ACL, vault loading, and error masking.
81
+
82
+ ## Two ways to run
83
+
84
+ | | **Local mode** (per repo) | **Shared server** (team) |
85
+ |---|---|---|
86
+ | Use when | a repo wants its own vault for its agents | many people/vaults behind one always-on endpoint |
87
+ | Transport | stdio subprocess (launched by `.mcp.json`) | HTTP (put behind Tailscale/HTTPS) |
88
+ | Secrets / tokens | **none** - nothing to generate | per-user bearer tokens (admin-generated) |
89
+ | Trust boundary | local filesystem access you already have | tailnet + HTTPS + per-vault ACL |
90
+ | Obsidian needed | no | no |
91
+
92
+ Most repos want **Local mode**. The shared server is only for a central, always-on team gateway.
93
+
94
+ ## Distribution - the `stable` branch ("update once")
95
+
96
+ The gateway ships from one moving branch, so a release reaches every consumer and server
97
+ without re-pinning anything by hand.
98
+
99
+ ```mermaid
100
+ flowchart LR
101
+ PR[merge PR to main] --> TAG[tag vX.Y.Z]
102
+ TAG --> MV[move stable -> vX.Y.Z]
103
+ MV --> C["Consumers<br/>uvx --refresh @stable<br/>(updates next session)"]
104
+ MV --> S["Servers<br/>daily uv tool reinstall<br/>(restart if stable moved)"]
105
+ ```
106
+
107
+ - **Consumers** pin `@stable` with `uvx --refresh` -> the ref is re-fetched on every launch, so
108
+ a new release auto-propagates the next time an agent starts. No per-repo re-pin.
109
+ - **Servers** (long-running) run a pinned `uv tool install @stable` plus a daily job that
110
+ reinstalls + restarts only when `stable` actually moves.
111
+ - Every release is **also** an immutable `vX.Y.Z` tag - pin a tag instead of `stable` when you
112
+ need a frozen, auditable version.
113
+
114
+ > A moving *tag* does not work (uvx caches the resolved commit); a *branch* + `--refresh` does.
115
+
116
+ ## Quickstart - local mode (zero secrets)
117
+
118
+ Add this to the repo's `.mcp.json` at the repo root:
119
+
120
+ ```jsonc
121
+ {
122
+ "mcpServers": {
123
+ "wiki": {
124
+ "command": "uvx",
125
+ "args": ["--refresh", "--from", "git+https://github.com/fszalaj/knowledge-gateway@stable",
126
+ "knowledge-gateway", "--local"]
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ - `--local` auto-detects the vault in the cwd, in order: the cwd itself if it has `.obsidian/`,
133
+ then `./wiki`, then a single `*-obsidian-vault/`, then a single child dir with `.obsidian/`
134
+ (ambiguous matches error). Pass `--vault ./<dir>` to be explicit.
135
+ - `--refresh` re-fetches `@stable` each launch, so releases auto-apply (adds ~1-2s to start).
136
+ - Commits are scoped to the vault's git subdir and attributed to your own
137
+ `git config user.name/email`. No token: the trust boundary is local filesystem access.
138
+
139
+ Open the repo in your agent, approve the `wiki` server once, done.
140
+
141
+ ## Tools
142
+
143
+ | Tool | |
144
+ |---|---|
145
+ | `list_vaults` | vaults reachable here |
146
+ | `list_notes` | Markdown paths in a vault |
147
+ | `read_note` | raw note content |
148
+ | `list_attachments` / `read_attachment` | list / read binary attachments (image -> inline Image, else File) |
149
+ | `list_canvases` / `read_canvas` / `write_canvas` | list / read / write Obsidian Canvas (nodes, groups, colors) |
150
+ | `search` | ripgrep literal/regex full-text |
151
+ | `backlinks` | notes that `[[wikilink]]` to a note |
152
+ | `list_tags` | inline `#tags` with counts |
153
+ | `query_notes` | find notes by frontmatter `type` / `tag` (headless Dataview-lite) |
154
+ | `write_note` | atomic write (+ optional commit) |
155
+ | `patch_note` | insert after a heading or at top/bottom, no full rewrite (+ commit) |
156
+ | `patch_frontmatter` | update YAML frontmatter keys, body intact (+ commit) |
157
+ | `delete_note` | delete a note (+ optional commit) |
158
+ | `rename_note` | rename/move + rewrite inbound flat `[[wikilinks]]` when the name changes (+ optional commit) |
159
+ | `git_status` / `git_commit` | pending changes / commit (subdir-scoped, attributed) |
160
+ | `list_graphs` / `graph_query` / `graph_neighbors` / `god_nodes` / `graph_shortest_path` / `graph_stats` | query a built code graph (optional `[graph]`) |
161
+ | `graph_build` | build a code/Ansible graph from a source tree into `.graph/<name>.json` (local mode only) |
162
+ | `convert_to_markdown` | convert a file (PDF/Office/image/HTML/...) in the vault to Markdown (optional `[convert]`) |
163
+
164
+ Edits are atomic (temp file + `rename`). Every path goes through `safe_note_path`, which blocks
165
+ traversal, symlink escape, hidden/dotfiles, non-`.md` targets, and `.git`/`.obsidian` - a caller
166
+ can never read or write outside the vault's notes.
167
+
168
+ ## Code graph and conversion (optional)
169
+
170
+ Two opt-in capabilities, gated behind extras so the core install stays dependency-free:
171
+
172
+ | Extra | Adds |
173
+ |---|---|
174
+ | `[graph]` | Python (`ast`) + Ansible (PyYAML) code graph + the query tools |
175
+ | `[graph-all]` | the above plus a broad tree-sitter pass (JS/TS/Go/Rust/Terraform/bash/PowerShell/...) |
176
+ | `[convert]` | attachment -> Markdown via markitdown |
177
+
178
+ **Build a graph** (AST-only - local, no network, no LLM) where the code lives:
179
+
180
+ ```bash
181
+ knowledge-gateway-graph /path/to/code-repo -o /path/to/vault/.graph/myrepo.json
182
+ # in a local-mode session the graph_build tool does the same, writing .graph/<name>.json
183
+ ```
184
+
185
+ **Query it** over MCP with `graph_query` / `graph_neighbors` / `god_nodes` /
186
+ `graph_shortest_path` / `graph_stats`. The graph captures functions/classes/imports/calls and -
187
+ uniquely for Ansible - roles, tasks, handlers, `include_role`/`import_tasks`/`notify`, and
188
+ `task -> filter plugin` edges. Graph files live in the vault's `.graph/`, are vault-contained
189
+ (resolved + checked to stay inside the vault), and are read-only to the gateway - the vault tools
190
+ never depend on them.
191
+
192
+ ## Shared server mode
193
+
194
+ Run this only for a central, always-on gateway reachable over the network.
195
+
196
+ **1. Map vaults** - `cp vaults.example.yaml vaults.yaml`, then set `name -> path / repo_root /
197
+ subdir`. `repo_root` + `subdir` pathspec-scope commits to a vault that lives inside a larger repo.
198
+
199
+ **2. Mint a token per user** (the admin does this):
200
+
201
+ ```bash
202
+ cp tokens.example.yaml tokens.yaml
203
+ openssl rand -hex 32 # once PER user -> the key
204
+ chmod 0600 tokens.yaml # refused at load if group/world-readable
205
+ ```
206
+
207
+ ```yaml
208
+ tokens:
209
+ "8f3c…hex…":
210
+ sub: alice # identity recorded on that user's commits
211
+ vaults: [teamwiki] # the ONLY vaults this token may see/touch
212
+ write: true # false = read-only
213
+ ```
214
+
215
+ A token sees only the vaults in its `vaults` list; anything else returns an opaque
216
+ `vault_forbidden`. `vaults.yaml` + `tokens.yaml` are gitignored.
217
+
218
+ **3. Run** - `uv run knowledge-gateway` (127.0.0.1:8765, path `/mcp/`). For a team box, run it as
219
+ a service behind Tailscale Serve - see `deploy/` and *Operate* below.
220
+
221
+ **4. Connect** - the admin shares the token over a password manager (not chat):
222
+
223
+ ```bash
224
+ claude mcp add --transport http --scope project teamwiki \
225
+ https://YOUR-HOST.<tailnet>.ts.net/mcp/ --header "Authorization: Bearer $GW_TOKEN"
226
+ ```
227
+
228
+ ## Security model
229
+
230
+ - **No secrets in the repo.** `vaults.yaml` / `tokens.yaml` are gitignored; only
231
+ `*.example.yaml` ship. `tokens.yaml` is refused at load if group/world-readable.
232
+ - **Local mode has no credential surface** - a local stdio subprocess; the trust boundary is
233
+ filesystem access the user already has.
234
+ - **Server mode is defense in depth, not a public endpoint** - tailnet ACL + HTTPS + per-user
235
+ `StaticTokenVerifier` bearer token + per-vault ACL. The bearer layer is a shared secret for
236
+ use **behind a trusted tailnet**; do not expose the server publicly.
237
+ - **Path guards on all note I/O** via `safe_note_path` (traversal, symlink, hidden/dotfiles
238
+ incl. `.env`, non-`.md`, `.git`/`.obsidian`). Search/backlinks/tags are bounded to `*.md`.
239
+ - **Server-mode error masking** - the HTTP server runs `mask_error_details=True`: only the
240
+ gateway's own expected failures surface as `ToolError`; unexpected OS/git errors are hidden.
241
+ Local mode keeps details visible.
242
+ - **Commits are attributed** to the requesting user (server) or the local git identity (local),
243
+ and pathspec-scoped to the vault subdir.
244
+
245
+ ## Set it up with an AI
246
+
247
+ Paste this into an agent at a repo's root to wire in local mode:
248
+
249
+ ```
250
+ Add the knowledge-gateway to this repo so agents can read/edit our vault over MCP with zero
251
+ tokens:
252
+ 1. Create or merge `.mcp.json` at the repo root with an mcpServers."wiki" entry that runs:
253
+ uvx --refresh --from git+https://github.com/fszalaj/knowledge-gateway@stable knowledge-gateway --local
254
+ (`--local` auto-detects the vault: ./wiki, a *-obsidian-vault dir, or a dir with .obsidian/.
255
+ If detection is ambiguous, use `--vault ./<vault dir>` instead of `--local`.)
256
+ 2. Verify: `uvx --refresh --from git+https://github.com/fszalaj/knowledge-gateway@stable \
257
+ knowledge-gateway --help` resolves; then in the agent, call list_vaults and read one note.
258
+ Branch + PR, no direct push, no AI attribution.
259
+ ```
260
+
261
+ For the shared server, ask your gateway admin for a token, then run the `claude mcp add …` from
262
+ *Connect* above.
263
+
264
+ ## Operate (servers)
265
+
266
+ A server runs the `@stable` release as a `uv tool`, with a daily job that reinstalls and
267
+ restarts only when `stable` moved. Reference units are in `deploy/`:
268
+
269
+ ```bash
270
+ uv tool install --from git+https://github.com/fszalaj/knowledge-gateway@stable knowledge-gateway
271
+ # the binary lives in the uv cache, so point config at the live files via env:
272
+ # KNOWLEDGE_GATEWAY_VAULTS=<dir>/vaults.yaml KNOWLEDGE_GATEWAY_TOKENS=<dir>/tokens.yaml
273
+ ```
274
+
275
+ - `deploy/knowledge-gateway.service` - the service (systemd `--user`).
276
+ - `deploy/knowledge-gateway-update.{service,timer}` + `deploy/auto-update.sh` - the daily auto-update.
277
+
278
+ Update now instead of waiting for the timer: `uv tool install --reinstall --from
279
+ git+https://github.com/fszalaj/knowledge-gateway@stable knowledge-gateway`, then restart the
280
+ service. Health: `curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8765/mcp/` -> `401`.
281
+
282
+ ## Release (maintainers)
283
+
284
+ 1. PR -> merge to `main` (CI: `uv lock --check`, pytest matrix).
285
+ 2. Bump `pyproject.toml` version + `CHANGELOG.md`.
286
+ 3. Tag `vX.Y.Z` and push the tag (the release workflow builds it).
287
+ 4. Move `stable`: `git branch -f stable vX.Y.Z && git push --force-with-lease origin stable`.
288
+
289
+ Consumers pick it up next session; servers within a day (or restart now).
290
+
291
+ ## Develop
292
+
293
+ ```bash
294
+ uv venv && uv pip install -e ".[dev]"
295
+ uv run pytest # ACL + path guards + edit/frontmatter + detect + masking
296
+ ```