{"id":51333711,"url":"https://github.com/fszalaj/knowledge-gateway","last_synced_at":"2026-07-02T01:01:55.544Z","repository":{"id":365049912,"uuid":"1270139500","full_name":"fszalaj/knowledge-gateway","owner":"fszalaj","description":"Filesystem/git-native MCP knowledge gateway: an Obsidian vault + optional code-graph (incl. Ansible) and doc-to-markdown tools, served over MCP.","archived":false,"fork":false,"pushed_at":"2026-06-27T18:06:03.000Z","size":114,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-27T18:13:52.334Z","etag":null,"topics":["ansible","code-graph","fastmcp","knowledge-gateway","knowledge-graph","markdown","mcp","obsidian"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/knowledge-gateway/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fszalaj.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-15T12:28:20.000Z","updated_at":"2026-06-27T18:05:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/fszalaj/knowledge-gateway","commit_stats":null,"previous_names":["fszalaj/obsidian-gateway"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/fszalaj/knowledge-gateway","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fszalaj%2Fknowledge-gateway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fszalaj%2Fknowledge-gateway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fszalaj%2Fknowledge-gateway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fszalaj%2Fknowledge-gateway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fszalaj","download_url":"https://codeload.github.com/fszalaj/knowledge-gateway/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fszalaj%2Fknowledge-gateway/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35028642,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ansible","code-graph","fastmcp","knowledge-gateway","knowledge-graph","markdown","mcp","obsidian"],"created_at":"2026-07-02T01:00:38.908Z","updated_at":"2026-07-02T01:01:55.512Z","avatar_url":"https://github.com/fszalaj.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# knowledge-gateway\n\n\u003c!-- mcp-name: io.github.fszalaj/knowledge-gateway --\u003e\n\nA single MCP server that gives agents (Claude Code, Codex, Cursor, Gemini, Copilot, Antigravity)\nthree capabilities over one connection:\n\n- **Vault** - read, search, and **edit** a git-backed Markdown/Obsidian vault (no Obsidian GUI), git as the source of truth.\n- **Code graph** *(optional `[graph]` / `[graph-all]`)* - build and query a code/Ansible knowledge graph of a repo (functions, calls, roles, tasks, handlers, `task -\u003e filter` edges); AST-only, local, no LLM.\n- **Convert** *(optional `[convert]`)* - turn PDF / Office / image / HTML files into Markdown.\n\nThe vault layer exists because the Obsidian *Local REST API* plugin serves only the one vault open\nin a running desktop instance, writes without a lock (silent lost updates), needs a token in every\nclient, and treats git as secondary. This gateway operates on the files directly, with git as the\nsystem of record - and adds the graph + convert tools the same way: one server, opt-in extras, no\nnew servers to wire.\n\n## Architecture\n\n```mermaid\nflowchart LR\n    subgraph clients [Agents]\n        A1[Claude Code]\n        A2[Codex]\n        A3[Gemini]\n        A4[Copilot - VS Code]\n        A5[Cursor]\n        A6[Antigravity]\n    end\n    A1 --- M(( MCP ))\n    A2 --- M\n    A3 --- M\n    A4 --- M\n    A5 --- M\n    A6 --- M\n    M --\u003e|stdio, per repo, no auth| L[Local gateway]\n    M --\u003e|HTTP + bearer + ACL| S[Shared gateway]\n    L --\u003e V[/Vault: Markdown files/]\n    S --\u003e V\n    V \u003c--\u003e|atomic write + scoped commit| G[(git)]\n```\n\nBoth modes run the **same** tool implementation over the **same** path guards; they differ in\ntransport, authentication/ACL, vault loading, and error masking.\n\n## Two ways to run\n\n| | **Local mode** (per repo) | **Shared server** (team) |\n|---|---|---|\n| Use when | a repo wants its own vault for its agents | many people/vaults behind one always-on endpoint |\n| Transport | stdio subprocess (launched by `.mcp.json`) | HTTP (put behind Tailscale/HTTPS) |\n| Secrets / tokens | **none** - nothing to generate | per-user bearer tokens (admin-generated) |\n| Trust boundary | local filesystem access you already have | tailnet + HTTPS + per-vault ACL |\n| Obsidian needed | no | no |\n\nMost repos want **Local mode**. The shared server is only for a central, always-on team gateway.\n\n## Distribution - the `stable` branch (\"update once\")\n\nThe gateway ships from one moving branch, so a release reaches every consumer and server\nwithout re-pinning anything by hand.\n\n```mermaid\nflowchart LR\n    PR[merge PR to main] --\u003e TAG[tag vX.Y.Z]\n    TAG --\u003e MV[move stable -\u003e vX.Y.Z]\n    MV --\u003e C[\"Consumers\u003cbr/\u003euvx --refresh @stable\u003cbr/\u003e(updates next session)\"]\n    MV --\u003e S[\"Servers\u003cbr/\u003edaily uv tool reinstall\u003cbr/\u003e(restart if stable moved)\"]\n```\n\n- **Consumers** pin `@stable` with `uvx --refresh` -\u003e the ref is re-fetched on every launch, so\n  a new release auto-propagates the next time an agent starts. No per-repo re-pin.\n- **Servers** (long-running) run a pinned `uv tool install @stable` plus a daily job that\n  reinstalls + restarts only when `stable` actually moves.\n- Every release is **also** an immutable `vX.Y.Z` tag - pin a tag instead of `stable` when you\n  need a frozen, auditable version.\n\n\u003e A moving *tag* does not work (uvx caches the resolved commit); a *branch* + `--refresh` does.\n\n## Quickstart - local mode (zero secrets)\n\nAdd this to the repo's `.mcp.json` at the repo root:\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"wiki\": {\n      \"command\": \"uvx\",\n      \"args\": [\"--refresh\", \"--from\", \"git+https://github.com/fszalaj/knowledge-gateway@stable\",\n               \"knowledge-gateway\", \"--local\"]\n    }\n  }\n}\n```\n\n- `--local` auto-detects the vault in the cwd, in order: the cwd itself if it has `.obsidian/`,\n  then `./wiki`, then a single `*-obsidian-vault/`, then a single child dir with `.obsidian/`\n  (ambiguous matches error). Pass `--vault ./\u003cdir\u003e` to be explicit.\n- `--refresh` re-fetches `@stable` each launch, so releases auto-apply (adds ~1-2s to start).\n- Commits are scoped to the vault's git subdir and attributed to your own\n  `git config user.name/email`. No token: the trust boundary is local filesystem access.\n\nOpen the repo in your agent, approve the `wiki` server once, done.\n\n## Tools\n\n| Tool | |\n|---|---|\n| `list_vaults` | vaults reachable here |\n| `list_notes` | Markdown paths in a vault |\n| `read_note` | raw note content |\n| `list_attachments` / `read_attachment` | list / read binary attachments (image -\u003e inline Image, else File) |\n| `list_canvases` / `read_canvas` / `write_canvas` | list / read / write Obsidian Canvas (nodes, groups, colors) |\n| `search` | ripgrep literal/regex full-text |\n| `backlinks` | notes that `[[wikilink]]` to a note |\n| `list_tags` | inline `#tags` with counts |\n| `query_notes` | find notes by frontmatter `type` / `tag` (headless Dataview-lite) |\n| `write_note` | atomic write (+ optional commit) |\n| `patch_note` | insert after a heading or at top/bottom, no full rewrite (+ commit) |\n| `patch_frontmatter` | update YAML frontmatter keys, body intact (+ commit) |\n| `delete_note` | delete a note (+ optional commit) |\n| `rename_note` | rename/move + rewrite inbound flat `[[wikilinks]]` when the name changes (+ optional commit) |\n| `git_status` / `git_commit` | pending changes / commit (subdir-scoped, attributed) |\n| `list_graphs` / `graph_query` / `graph_neighbors` / `god_nodes` / `graph_shortest_path` / `graph_stats` | query a built code graph (optional `[graph]`) |\n| `graph_build` | build a code/Ansible graph from a source tree into `.graph/\u003cname\u003e.json` (local mode only) |\n| `convert_to_markdown` | convert a file (PDF/Office/image/HTML/...) in the vault to Markdown (optional `[convert]`) |\n\nEdits are atomic (temp file + `rename`). Every path goes through `safe_note_path`, which blocks\ntraversal, symlink escape, hidden/dotfiles, non-`.md` targets, and `.git`/`.obsidian` - a caller\ncan never read or write outside the vault's notes.\n\n## Code graph and conversion (optional)\n\nTwo opt-in capabilities, gated behind extras so the core install stays dependency-free:\n\n| Extra | Adds |\n|---|---|\n| `[graph]` | Python (`ast`) + Ansible (PyYAML) code graph + the query tools |\n| `[graph-all]` | the above + a broad tree-sitter pass over ~30 languages (JS/TS/TSX, Go, Rust, Java, C#, C/C++, Ruby, PHP, bash, PowerShell, Terraform/HCL, Lua, Kotlin, Swift, Scala, R, Perl, Elixir, Dart, SQL, ...): definitions, `imports`, and within-file `calls` |\n| `[convert]` | attachment -\u003e Markdown via markitdown |\n\n**Build a graph** (AST-only - local, no network, no LLM) where the code lives:\n\n```bash\nknowledge-gateway-graph /path/to/code-repo -o /path/to/vault/.graph/myrepo.json\n# in a local-mode session the graph_build tool does the same, writing .graph/\u003cname\u003e.json\n```\n\n**Query it** over MCP with `graph_query` / `graph_neighbors` / `god_nodes` /\n`graph_shortest_path` / `graph_stats`. The graph captures functions/classes/imports/calls and -\nuniquely for Ansible - roles, tasks, handlers, `include_role`/`import_tasks`/`notify`, and\n`task -\u003e filter plugin` edges. Graph files live in the vault's `.graph/`, are vault-contained\n(resolved + checked to stay inside the vault), and are read-only to the gateway - the vault tools\nnever depend on them.\n\n## Shared server mode\n\nRun this only for a central, always-on gateway reachable over the network.\n\n**1. Map vaults** - `cp vaults.example.yaml vaults.yaml`, then set `name -\u003e path / repo_root /\nsubdir`. `repo_root` + `subdir` pathspec-scope commits to a vault that lives inside a larger repo.\n\n**2. Mint a token per user** (the admin does this):\n\n```bash\ncp tokens.example.yaml tokens.yaml\nopenssl rand -hex 32          # once PER user -\u003e the key\nchmod 0600 tokens.yaml        # refused at load if group/world-readable\n```\n\n```yaml\ntokens:\n  \"8f3c…hex…\":\n    sub: alice                # identity recorded on that user's commits\n    vaults: [teamwiki]        # the ONLY vaults this token may see/touch\n    write: true               # false = read-only\n```\n\nA token sees only the vaults in its `vaults` list; anything else returns an opaque\n`vault_forbidden`. `vaults.yaml` + `tokens.yaml` are gitignored.\n\n**3. Run** - `uv run knowledge-gateway` (127.0.0.1:8765, path `/mcp/`). For a team box, run it as\na service behind Tailscale Serve - see `deploy/` and *Operate* below.\n\n**4. Connect** - the admin shares the token over a password manager (not chat):\n\n```bash\nclaude mcp add --transport http --scope project teamwiki \\\n  https://YOUR-HOST.\u003ctailnet\u003e.ts.net/mcp/ --header \"Authorization: Bearer $GW_TOKEN\"\n```\n\n## Security model\n\n- **No secrets in the repo.** `vaults.yaml` / `tokens.yaml` are gitignored; only\n  `*.example.yaml` ship. `tokens.yaml` is refused at load if group/world-readable.\n- **Local mode has no credential surface** - a local stdio subprocess; the trust boundary is\n  filesystem access the user already has.\n- **Server mode is defense in depth, not a public endpoint** - tailnet ACL + HTTPS + per-user\n  `StaticTokenVerifier` bearer token + per-vault ACL. The bearer layer is a shared secret for\n  use **behind a trusted tailnet**; do not expose the server publicly.\n- **Path guards on all note I/O** via `safe_note_path` (traversal, symlink, hidden/dotfiles\n  incl. `.env`, non-`.md`, `.git`/`.obsidian`). Search/backlinks/tags are bounded to `*.md`.\n- **Server-mode error masking** - the HTTP server runs `mask_error_details=True`: only the\n  gateway's own expected failures surface as `ToolError`; unexpected OS/git errors are hidden.\n  Local mode keeps details visible.\n- **Commits are attributed** to the requesting user (server) or the local git identity (local),\n  and pathspec-scoped to the vault subdir.\n\n## Set it up with an AI\n\nPaste this into an agent at a repo's root to wire in local mode:\n\n```\nAdd the knowledge-gateway to this repo so agents can read/edit our vault over MCP with zero\ntokens:\n1. Create or merge `.mcp.json` at the repo root with an mcpServers.\"wiki\" entry that runs:\n   uvx --refresh --from git+https://github.com/fszalaj/knowledge-gateway@stable knowledge-gateway --local\n   (`--local` auto-detects the vault: ./wiki, a *-obsidian-vault dir, or a dir with .obsidian/.\n   If detection is ambiguous, use `--vault ./\u003cvault dir\u003e` instead of `--local`.)\n2. Verify: `uvx --refresh --from git+https://github.com/fszalaj/knowledge-gateway@stable \\\n   knowledge-gateway --help` resolves; then in the agent, call list_vaults and read one note.\nBranch + PR, no direct push, no AI attribution.\n```\n\nFor the shared server, ask your gateway admin for a token, then run the `claude mcp add …` from\n*Connect* above.\n\n## Operate (servers)\n\nA server runs the `@stable` release as a `uv tool`, with a daily job that reinstalls and\nrestarts only when `stable` moved. Reference units are in `deploy/`:\n\n```bash\nuv tool install --from git+https://github.com/fszalaj/knowledge-gateway@stable knowledge-gateway\n# the binary lives in the uv cache, so point config at the live files via env:\n#   KNOWLEDGE_GATEWAY_VAULTS=\u003cdir\u003e/vaults.yaml   KNOWLEDGE_GATEWAY_TOKENS=\u003cdir\u003e/tokens.yaml\n```\n\n- `deploy/knowledge-gateway.service` - the service (systemd `--user`).\n- `deploy/knowledge-gateway-update.{service,timer}` + `deploy/auto-update.sh` - the daily auto-update.\n\nUpdate now instead of waiting for the timer: `uv tool install --reinstall --from\ngit+https://github.com/fszalaj/knowledge-gateway@stable knowledge-gateway`, then restart the\nservice. Health: `curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8765/mcp/` -\u003e `401`.\n\n## Release (maintainers)\n\n1. PR -\u003e merge to `main` (CI: `uv lock --check`, pytest matrix).\n2. Bump `pyproject.toml` version + `CHANGELOG.md`.\n3. Tag `vX.Y.Z` and push the tag (the release workflow builds it).\n4. Move `stable`: `git branch -f stable vX.Y.Z \u0026\u0026 git push --force-with-lease origin stable`.\n\nConsumers pick it up next session; servers within a day (or restart now).\n\n## Develop\n\n```bash\nuv venv \u0026\u0026 uv pip install -e \".[dev]\"\nuv run pytest                              # ACL + path guards + edit/frontmatter + detect + masking\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffszalaj%2Fknowledge-gateway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffszalaj%2Fknowledge-gateway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffszalaj%2Fknowledge-gateway/lists"}