From 212eeb7b3cffdff0d27a7e8738fb37873d878991 Mon Sep 17 00:00:00 2001 From: Anton Dziatkovskii <194927794+tonydzi@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:08:06 -0700 Subject: [PATCH 1/3] feat: MCP Registry listing, Intel-Mac indexing fix, README first screen (v0.3.1) - embeddings extra: cap transformers<4.50 and sentence-transformers<5 on x86_64 macOS. Red: 0.3.0 from PyPI on Python 3.12 crashed sgm-index with NameError 'nn' (transformers 5 disables torch 2.2). Green: this build indexes bench/vault, 204 chunks, exit 0. - server.json for io.github.tonydzi/sqlite-graph-memory (pypi, uvx, stdio), validated against the 2025-12-11 schema; a 101-char description fails it. - publish.yml: mcp-registry job after PyPI, GitHub OIDC, version and mcp-name checks shown red on a broken copy. - new console script `sqlite-graph-memory` = sgm-mcp, for uvx. - README opens with three commands and their real output on bench/vault. - 94 tests pass. Assisted-by: Claude Code / claude-opus-5-5 Machine: MacBook-Anton Account: tonydzi Operator: Anton Dziatkovskii Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 36 +++++++++++++++++++++- .github/workflows/tests.yml | 2 +- CHANGELOG.md | 19 +++++++++++- README.md | 47 +++++++++++++++++++++++++++-- pyproject.toml | 12 +++++++- server.json | 45 +++++++++++++++++++++++++++ src/sqlite_graph_memory/__init__.py | 2 +- 7 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 server.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5d30179..4fb0f8c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -44,7 +44,7 @@ jobs: run: | python -m venv /tmp/clean /tmp/clean/bin/pip install dist/*.whl - for cmd in sgm-index sgm-ask sgm-mcp sgm-turnstate-hook sgm-turnstate-show; do + for cmd in sgm-index sgm-ask sgm-mcp sgm-turnstate-hook sgm-turnstate-show sqlite-graph-memory; do test -x "/tmp/clean/bin/$cmd" || { echo "::error::$cmd missing from wheel"; exit 1; } done - uses: actions/upload-artifact@v4 @@ -66,3 +66,37 @@ jobs: - uses: pypa/gh-action-pypi-publish@release/v1 with: skip-existing: true # the package may already be uploaded before the GitHub release object exists + + # List the server in the official MCP Registry once PyPI has the release. + # Auth is GitHub OIDC (no stored secret): the registry grants the + # io.github.tonydzi/* namespace to workflows running in tonydzi's repos. + # Ownership is proven by the `mcp-name:` line in README.md, which PyPI shows + # as the package description, so this job must run AFTER the PyPI upload. + mcp-registry: + needs: publish + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@v4 + - name: server.json must describe the version being released + run: | + pkg=$(python3 -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])") + srv=$(python3 -c "import json;d=json.load(open('server.json'));print(d['version'], d['packages'][0]['version'])") + [ "$srv" = "$pkg $pkg" ] || { echo "::error::server.json says '$srv', pyproject says $pkg"; exit 1; } + grep -q "mcp-name: $(python3 -c "import json;print(json.load(open('server.json'))['name'])")" README.md \ + || { echo "::error::README.md lacks the mcp-name line the registry verifies"; exit 1; } + - name: Wait until PyPI serves this version + run: | + v=$(python3 -c "import json;print(json.load(open('server.json'))['version'])") + for i in $(seq 1 30); do + curl -sf "https://pypi.org/pypi/sqlite-graph-memory/$v/json" >/dev/null && exit 0 + sleep 20 + done + echo "::error::PyPI never served $v"; exit 1 + - name: Install mcp-publisher + run: | + curl -sL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher + - run: ./mcp-publisher login github-oidc + - run: ./mcp-publisher publish diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f9db3eb..e9c4720 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,7 +63,7 @@ jobs: /tmp/clean/bin/pip install dist/*.whl - name: every advertised command must start run: | - for cmd in sgm-index sgm-ask sgm-turnstate-hook sgm-turnstate-show; do + for cmd in sgm-index sgm-ask sgm-mcp sgm-turnstate-hook sgm-turnstate-show sqlite-graph-memory; do test -x "/tmp/clean/bin/$cmd" || { echo "::error::$cmd was not installed"; exit 1; } done /tmp/clean/bin/sgm-turnstate-show --help > /dev/null diff --git a/CHANGELOG.md b/CHANGELOG.md index b2c095a..a74d701 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ What shipped, in plain words. Entries below v0.1.3 were written on 2026-09-05 fr release notes that already existed — the file itself did not exist until then, so those three are recorded after the fact rather than backdated to look contemporaneous. -## Unreleased +## v0.3.1 — unreleased **Credit where it is due.** The MCP server that ships as `sgm-mcp` was written by Vardhman Gupta ([@Kaap10](https://github.com/Kaap10)) in PR #8: the server, its tests and its docs. His @@ -12,6 +12,23 @@ follow-up fixes are the shared answer-file race (commit 3ce7061), the stdout fal and the swallowed read errors (PR #14). He is now listed in the `authors` field of `pyproject.toml`, which is the attribution he asked for. +**Indexing works on Intel Macs again.** A fresh `pip install 'sqlite-graph-memory[embeddings]'` +on an x86_64 Mac with Python 3.12 crashed `sgm-index` with `NameError: name 'nn' is not +defined`. Torch stops at 2.2 there, transformers 5.x needs 2.6 and quietly disables torch, +and sentence-transformers 6.x requires transformers 5.x. The embeddings extra now caps +transformers below 4.50 and sentence-transformers below 5 on that one platform. Measured +both ways on the same machine: 0.3.0 from PyPI crashes, this build indexes the 202-note +bench vault cleanly (204 chunks). + +**Listed in the official MCP Registry.** `server.json` describes the server as +`io.github.tonydzi/sqlite-graph-memory`, the README carries the `mcp-name` line the registry +checks, and the publish workflow lists the release after PyPI has it. The package also +installs the MCP server under its own name, `sqlite-graph-memory`, so registry clients can +start it with `uvx`. + +**The README opens with three commands and their real output** on the bench vault, including +the honest caveat that vector-only ranks ahead of graph mode on that vault overall. + ## bench-v0.1 — 2026-10-09 **A benchmark other people can run.** Until now every retrieval number in this repo came from diff --git a/README.md b/README.md index ebe7bb2..4fd4033 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,54 @@ # sqlite-graph-memory + + **Your agent forgets everything between sessions, and plain vector search gives it back -flat, unrelated chunks. This gives it associative recall over your own markdown notes — -one SQLite file, no graph database, no ETL pass, no server.** +flat, unrelated chunks. This gives it associative recall over your own markdown notes: +vector search finds the entry points, the `[[wikilinks]]` you already wrote are the graph. +One SQLite file, no graph database, no ETL pass, no server.** + +## Three commands ```bash -pip install sqlite-graph-memory +pip install 'sqlite-graph-memory[embeddings]' +sgm-index ./notes +sgm-ask --ab "In which programming language is the tool behind Project Ouzel written?" +``` + +Real output, top 5 of 12, on the 202-note synthetic vault that ships in +[`bench/vault`](bench/vault) (copy it to `./notes` to get the same thing): + +``` +--- DIRECT memory (vector) --- + 1. [ 1.66] project-ouzel + 2. [ -1.33] project-avocet + 3. [ -1.79] tool-cobbleset + 4. [ -2.44] meeting-2025-10-21-lighthouse-sync + 5. [ -2.70] person-severin-ibarrondo + +--- ASSOCIATIVE memory (vector+graph) --- + 1. [ 1.66] project-ouzel + 2. [ 1.14] tool-lanternfish <== +graph + 3. [ -1.33] project-avocet + 4. [ -1.79] tool-cobbleset + 5. [ -2.44] meeting-2025-10-21-lighthouse-sync ``` +The answer lives in `tool-lanternfish` ("Lanternfish is written in Elixir"). The question +never names Lanternfish, so the vector pass does not find it in the top 12 at all. +`project-ouzel` links to it, so one graph hop puts it second. + +That is the idea in one query, and it is not a blanket win. On this same vault vector-only +ranks ahead of graph mode overall; the numbers are in [`bench/`](bench/README.md). `--ab` +exists so you can measure which one wins on your own notes. + +**From any MCP client** (Claude Desktop, Cursor, Zed, Windsurf): `sgm-mcp` after the install +above, or `uvx --with 'sqlite-graph-memory[embeddings]' sqlite-graph-memory`. Set +`BRAIN_INDEX_DIR` to the `index` folder `sgm-index` wrote. Config: +[`examples/mcp-config.json`](examples/mcp-config.json). + +## What it is + Graph RAG on SQLite for AI agents — a working pilot, not a framework. It is the extracted memory layer of a personal "second brain" agent setup: vector search finds the entry points, the `[[wikilinks]]` you already wrote by hand are the graph, and a cross-encoder diff --git a/pyproject.toml b/pyproject.toml index b013207..1e7451a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "sqlite-graph-memory" -version = "0.3.0" +version = "0.3.1" description = "Associative memory for LLM agents: Graph RAG over a folder of markdown notes, in one SQLite file, with no server to run." readme = "README.md" requires-python = ">=3.9" @@ -47,9 +47,16 @@ dependencies = ["numpy>=1.24"] # Intel Macs top out at torch 2.2.x, which is built against NumPy 1.x and dies on # NumPy 2 with "RuntimeError: Numpy is not available" (measured 2026-10-15 on an # x86_64 Mac, Python 3.9: sgm-index crashed; with numpy<2 it indexed cleanly). +# The same ceiling bites one layer up: transformers 5.x needs torch >= 2.6, so on +# torch 2.2 it disables torch and sgm-index dies with "NameError: name 'nn' is not +# defined" inside transformers/integrations/accelerate.py (measured 2026-10-15 on +# an x86_64 Mac, Python 3.12, fresh `pip install` from PyPI). sentence-transformers +# 6.x in turn requires transformers 5.x, so both are capped there. embeddings = [ "sentence-transformers>=2.6", "numpy<2; sys_platform == 'darwin' and platform_machine == 'x86_64'", + "transformers<4.50; sys_platform == 'darwin' and platform_machine == 'x86_64'", + "sentence-transformers<5; sys_platform == 'darwin' and platform_machine == 'x86_64'", ] [project.urls] @@ -64,6 +71,9 @@ sgm-ask = "sqlite_graph_memory.brain_ask:main" sgm-turnstate-hook = "sqlite_graph_memory.turnstate_hook:main" sgm-turnstate-show = "sqlite_graph_memory.turnstate_show:main" sgm-mcp = "sqlite_graph_memory.mcp_server:main" +# Same server under the package's own name, so `uvx sqlite-graph-memory` (what MCP +# registry clients run for a PyPI package) starts it without a --from flag. +sqlite-graph-memory = "sqlite_graph_memory.mcp_server:main" [tool.hatch.build.targets.wheel] packages = ["src/sqlite_graph_memory"] diff --git a/server.json b/server.json new file mode 100644 index 0000000..bdbaee9 --- /dev/null +++ b/server.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.tonydzi/sqlite-graph-memory", + "title": "sqlite-graph-memory", + "description": "Recall over your markdown notes: vector search plus your [[wikilinks]] as a graph, in SQLite.", + "repository": { + "url": "https://github.com/tonydzi/sqlite-graph-memory", + "source": "github" + }, + "version": "0.3.1", + "packages": [ + { + "registryType": "pypi", + "registryBaseUrl": "https://pypi.org", + "identifier": "sqlite-graph-memory", + "version": "0.3.1", + "runtimeHint": "uvx", + "runtimeArguments": [ + { + "type": "named", + "name": "--with", + "value": "sqlite-graph-memory[embeddings]==0.3.1", + "description": "Recall needs the embedding and reranker models; the base package leaves them out to stay small." + } + ], + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "name": "BRAIN_INDEX_DIR", + "description": "Directory holding the index. Build it once with `sgm-index /path/to/notes`.", + "format": "filepath", + "isRequired": true + }, + { + "name": "TURNSTATE_DB", + "description": "SQLite file for recall telemetry.", + "format": "filepath", + "default": "./turnstate.db" + } + ] + } + ] +} diff --git a/src/sqlite_graph_memory/__init__.py b/src/sqlite_graph_memory/__init__.py index 640ceef..ae46173 100644 --- a/src/sqlite_graph_memory/__init__.py +++ b/src/sqlite_graph_memory/__init__.py @@ -21,6 +21,6 @@ on an ``ImportError`` at startup. """ -__version__ = "0.3.0" +__version__ = "0.3.1" __all__ = ["__version__"] From 3cc229b9d8d38d11e64d22cc23e705fe73819016 Mon Sep 17 00:00:00 2001 From: Anton Dziatkovskii <194927794+tonydzi@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:13:39 -0700 Subject: [PATCH 2/3] docs: say where ./notes comes from, the bench vault is not in the wheel A breaker panel caught it: the first screen told pip users to copy bench/vault, which only exists in a clone. The one-line clone+copy command was run and yields the 202 notes. Assisted-by: Claude Code / claude-opus-5-5 Machine: MacBook-Anton Account: tonydzi Operator: Anton Dziatkovskii Co-Authored-By: Claude Opus 5.5 --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4fd4033..7bcc55f 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,14 @@ sgm-index ./notes sgm-ask --ab "In which programming language is the tool behind Project Ouzel written?" ``` -Real output, top 5 of 12, on the 202-note synthetic vault that ships in -[`bench/vault`](bench/vault) (copy it to `./notes` to get the same thing): +`./notes` is any folder of markdown notes. To get exactly the output below, use the +202-note synthetic vault from this repo (it is not inside the pip package): + +```bash +git clone --depth 1 https://github.com/tonydzi/sqlite-graph-memory && cp -R sqlite-graph-memory/bench/vault ./notes +``` + +Real output, top 5 of 12: ``` --- DIRECT memory (vector) --- From 2be4e24481abf4c9a6fadcf2d71f1daa5174965b Mon Sep 17 00:00:00 2001 From: Anton Dziatkovskii <194927794+tonydzi@users.noreply.github.com> Date: Sun, 18 Oct 2026 09:09:07 -0700 Subject: [PATCH 3/3] fix: MCP handshake reports the package version, CI pins runtimeArguments too SERVER_INFO still said 0.3.0 after the 0.3.1 bump (Kaap10 review, Bugbot). It now reads __version__, with a test. publish.yml also compares the pinned uvx --with value in server.json, so a 0.3.2 bump cannot leave it behind. Assisted-by: Claude Code/claude-sonnet-5-5 Machine: MacBook-Anton Account: tonydzi Operator: robot:contrib-watch-daily Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/publish.yml | 4 ++-- src/sqlite_graph_memory/mcp_server.py | 4 +++- tests/test_mcp_server.py | 5 +++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4fb0f8c..7965ddd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -83,8 +83,8 @@ jobs: - name: server.json must describe the version being released run: | pkg=$(python3 -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])") - srv=$(python3 -c "import json;d=json.load(open('server.json'));print(d['version'], d['packages'][0]['version'])") - [ "$srv" = "$pkg $pkg" ] || { echo "::error::server.json says '$srv', pyproject says $pkg"; exit 1; } + srv=$(python3 -c "import json;d=json.load(open('server.json'));p=d['packages'][0];print(d['version'], p['version'], p['runtimeArguments'][0]['value'])") + [ "$srv" = "$pkg $pkg sqlite-graph-memory[embeddings]==$pkg" ] || { echo "::error::server.json says '$srv', pyproject says $pkg"; exit 1; } grep -q "mcp-name: $(python3 -c "import json;print(json.load(open('server.json'))['name'])")" README.md \ || { echo "::error::README.md lacks the mcp-name line the registry verifies"; exit 1; } - name: Wait until PyPI serves this version diff --git a/src/sqlite_graph_memory/mcp_server.py b/src/sqlite_graph_memory/mcp_server.py index 1902dfb..cd4d180 100644 --- a/src/sqlite_graph_memory/mcp_server.py +++ b/src/sqlite_graph_memory/mcp_server.py @@ -22,6 +22,8 @@ import sys, os, json, subprocess, tempfile from pathlib import Path +from sqlite_graph_memory import __version__ + # Ensure UTF-8 for stdio communication on all platforms (including Windows console) try: sys.stdin.reconfigure(encoding='utf-8') @@ -43,7 +45,7 @@ def _brain_ask_cmd(): SERVER_INFO = { "name": "sqlite-graph-memory", - "version": "0.3.0" + "version": __version__ } TOOLS = [ diff --git a/tests/test_mcp_server.py b/tests/test_mcp_server.py index 2dd44da..ca5f1bf 100644 --- a/tests/test_mcp_server.py +++ b/tests/test_mcp_server.py @@ -12,6 +12,11 @@ +def test_server_info_version_tracks_package(): + from sqlite_graph_memory import __version__ + assert SERVER_INFO["version"] == __version__ + + def test_mcp_initialize(): req = { "jsonrpc": "2.0",