rekal 0.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.
Files changed (39) hide show
  1. rekal-0.0.3/.github/workflows/ci.yml +36 -0
  2. rekal-0.0.3/.github/workflows/release.yml +50 -0
  3. rekal-0.0.3/.gitignore +53 -0
  4. rekal-0.0.3/AGENTS.md +170 -0
  5. rekal-0.0.3/CLAUDE.md +1 -0
  6. rekal-0.0.3/LICENSE +21 -0
  7. rekal-0.0.3/PKG-INFO +51 -0
  8. rekal-0.0.3/README.md +31 -0
  9. rekal-0.0.3/pyproject.toml +96 -0
  10. rekal-0.0.3/rekal/__init__.py +5 -0
  11. rekal-0.0.3/rekal/__main__.py +77 -0
  12. rekal-0.0.3/rekal/_version.py +24 -0
  13. rekal-0.0.3/rekal/adapters/__init__.py +0 -0
  14. rekal-0.0.3/rekal/adapters/mcp_adapter.py +43 -0
  15. rekal-0.0.3/rekal/adapters/sqlite_adapter.py +745 -0
  16. rekal-0.0.3/rekal/adapters/tools/__init__.py +3 -0
  17. rekal-0.0.3/rekal/adapters/tools/conversations.py +69 -0
  18. rekal-0.0.3/rekal/adapters/tools/core.py +90 -0
  19. rekal-0.0.3/rekal/adapters/tools/introspection.py +76 -0
  20. rekal-0.0.3/rekal/adapters/tools/smart_write.py +73 -0
  21. rekal-0.0.3/rekal/embeddings.py +45 -0
  22. rekal-0.0.3/rekal/models.py +82 -0
  23. rekal-0.0.3/rekal/scoring.py +45 -0
  24. rekal-0.0.3/tests/__init__.py +0 -0
  25. rekal-0.0.3/tests/conftest.py +43 -0
  26. rekal-0.0.3/tests/test_cli.py +81 -0
  27. rekal-0.0.3/tests/test_conversation_tools.py +51 -0
  28. rekal-0.0.3/tests/test_conversations.py +119 -0
  29. rekal-0.0.3/tests/test_core_tools.py +56 -0
  30. rekal-0.0.3/tests/test_embeddings.py +51 -0
  31. rekal-0.0.3/tests/test_introspection.py +126 -0
  32. rekal-0.0.3/tests/test_introspection_tools.py +68 -0
  33. rekal-0.0.3/tests/test_mcp_adapter.py +25 -0
  34. rekal-0.0.3/tests/test_scoring.py +80 -0
  35. rekal-0.0.3/tests/test_search.py +72 -0
  36. rekal-0.0.3/tests/test_smart_write.py +78 -0
  37. rekal-0.0.3/tests/test_smart_write_tools.py +43 -0
  38. rekal-0.0.3/tests/test_sqlite_adapter.py +86 -0
  39. rekal-0.0.3/uv.lock +1194 -0
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_call:
9
+
10
+ jobs:
11
+ lint:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: astral-sh/setup-uv@v6
16
+ - run: uv sync --frozen
17
+ - run: uv run ruff check rekal/ tests/
18
+ - run: uv run ruff format --check rekal/ tests/
19
+
20
+ typecheck:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - uses: astral-sh/setup-uv@v6
25
+ - run: uv sync --frozen
26
+ - run: uv run ty check rekal/ tests/
27
+
28
+ test:
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ with:
33
+ fetch-depth: 0
34
+ - uses: astral-sh/setup-uv@v6
35
+ - run: uv sync --frozen
36
+ - run: uv run pytest --cov=rekal --cov-report=term-missing --cov-fail-under=100 tests/
@@ -0,0 +1,50 @@
1
+ name: Release to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ ci:
14
+ uses: ./.github/workflows/ci.yml
15
+ permissions:
16
+ contents: read
17
+
18
+ build:
19
+ needs: ci
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ with:
24
+ fetch-depth: 0
25
+ - uses: astral-sh/setup-uv@v6
26
+ - run: uv build
27
+ - uses: actions/upload-artifact@v4
28
+ with:
29
+ name: dist
30
+ path: dist/
31
+
32
+ publish:
33
+ needs: build
34
+ runs-on: ubuntu-latest
35
+ environment: pypi
36
+ steps:
37
+ - uses: actions/download-artifact@v4
38
+ with:
39
+ name: dist
40
+ path: dist/
41
+ - uses: pypa/gh-action-pypi-publish@release/v1
42
+
43
+ github-release:
44
+ needs: publish
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+ - run: gh release create "${{ github.ref_name }}" --generate-notes
49
+ env:
50
+ GH_TOKEN: ${{ github.token }}
rekal-0.0.3/.gitignore ADDED
@@ -0,0 +1,53 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.egg-info/
6
+ *.egg
7
+ dist/
8
+ build/
9
+ sdist/
10
+ wheels/
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+
16
+ # uv — lockfile is tracked for reproducible CI
17
+ # uv.lock
18
+
19
+ # Testing / coverage
20
+ .pytest_cache/
21
+ .coverage
22
+ htmlcov/
23
+ coverage.xml
24
+
25
+ # Type checkers
26
+ .ty_cache/
27
+ .mypy_cache/
28
+
29
+ # Editors
30
+ .vscode/
31
+ .idea/
32
+ *.swp
33
+ *.swo
34
+ *~
35
+
36
+ # OS
37
+ .DS_Store
38
+ Thumbs.db
39
+
40
+ # rekal database files
41
+ *.db
42
+ *.sqlite
43
+ *.sqlite3
44
+
45
+ # Generated version file
46
+ rekal/_version.py
47
+
48
+ # Claude Code
49
+ .claude/
50
+
51
+ # Environment
52
+ .env
53
+ .env.*
rekal-0.0.3/AGENTS.md ADDED
@@ -0,0 +1,170 @@
1
+ # rekal — Agent Instructions
2
+
3
+ ## What this project is
4
+
5
+ rekal is a Model Context Protocol (MCP) server that gives LLMs persistent long-term memory.
6
+ It uses hybrid search (FTS5 + vector + recency) in a single SQLite file.
7
+ Python 3.14+, installed via `pip install rekal`, runs as a stdio MCP server.
8
+
9
+ ## Architecture
10
+
11
+ ```
12
+ MCP Client
13
+ │ stdio (JSON-RPC)
14
+
15
+ mcp_adapter.py ← FastMCP server, lifespan (creates/closes DB)
16
+
17
+ ├── tools/core.py ─┐
18
+ ├── tools/introspection.py│─ thin @mcp.tool() wrappers, delegate to SqliteDatabase
19
+ ├── tools/smart_write.py │
20
+ └── tools/conversations.py┘
21
+
22
+ sqlite_adapter.py ← SqliteDatabase @dataclass, ALL SQL lives here
23
+
24
+ ├── SQLite (memories, conversations, tags, conflicts)
25
+ ├── FTS5 (full-text index, auto-synced via triggers)
26
+ └── sqlite-vec (vector index)
27
+ ```
28
+
29
+ ### Key rules
30
+
31
+ - `SqliteDatabase` is a `@dataclass` holding the `aiosqlite.Connection` and ALL query methods. Every SQL statement lives here. No SQL in tool files.
32
+ - Tool modules in `adapters/tools/` are thin wrappers. They register MCP tools via `@mcp.tool()` and call methods on `SqliteDatabase`.
33
+ - `mcp_adapter.py` creates the FastMCP server, manages lifespan, and imports tool modules so they register.
34
+ - Adding a new tool = add a method to `SqliteDatabase` + add a thin tool wrapper in the appropriate `tools/*.py` file.
35
+
36
+ ## Python style — modern, strict, no shortcuts
37
+
38
+ ### Never use `Any`
39
+
40
+ Zero tolerance. Every value has a real type. If you reach for `Any`, find the actual type.
41
+ Use `TYPE_CHECKING` imports, union types, `Protocol`, or type aliases instead.
42
+
43
+ ```python
44
+ # Correct
45
+ SqlParam = str | int | float | bytes | None
46
+ params: list[SqlParam] = []
47
+
48
+ # Wrong
49
+ params: list[Any] = []
50
+ ```
51
+
52
+ ### No underscore prefixes on attributes or methods
53
+
54
+ Public by default. No `self._db`, no `_helper()`, no `_CONSTANT`.
55
+ Use plain names: `self.db`, `helper()`, `SCHEMA`.
56
+
57
+ ```python
58
+ # Correct
59
+ @dataclass
60
+ class SqliteDatabase:
61
+ db: aiosqlite.Connection
62
+ embed: EmbeddingFunc
63
+
64
+ # Wrong
65
+ class SqliteDatabase:
66
+ def __init__(self, db, embed):
67
+ self._db = db
68
+ self._embed = embed
69
+ ```
70
+
71
+ ### No mutable globals
72
+
73
+ No module-level mutable state. Constants like `SCHEMA` (a frozen SQL string) and type aliases like `SqlParam` are fine.
74
+ For values that might look like globals, use functions or dataclass fields instead.
75
+
76
+ ```python
77
+ # Correct
78
+ def default_db_path() -> str:
79
+ return str(Path.home() / ".rekal" / "memory.db")
80
+
81
+ # Wrong
82
+ _DEFAULT_DB_PATH = str(Path.home() / ".rekal" / "memory.db")
83
+ ```
84
+
85
+ ### Use dataclasses everywhere possible
86
+
87
+ Prefer `@dataclass` over hand-written `__init__`. Pydantic `BaseModel` only where serialization is needed (models.py).
88
+
89
+ ### Use `Annotated` with `Field(description=...)` on all MCP tool parameters
90
+
91
+ Every tool parameter (except `ctx`) must have a description for the MCP schema.
92
+
93
+ ```python
94
+ @mcp.tool()
95
+ async def memory_store(
96
+ ctx: Context,
97
+ content: Annotated[str, Field(description="The text content to store")],
98
+ limit: Annotated[int, Field(description="Maximum number of results")] = 10,
99
+ ) -> str:
100
+ ```
101
+
102
+ ### Type annotations on everything
103
+
104
+ Every function parameter, return type, and non-obvious variable. Use `from __future__ import annotations` in every file.
105
+
106
+ ### Use union syntax, not Optional
107
+
108
+ ```python
109
+ # Correct
110
+ project: str | None = None
111
+
112
+ # Wrong
113
+ project: Optional[str] = None
114
+ ```
115
+
116
+ ## Testing
117
+
118
+ ### 100% coverage, no exceptions
119
+
120
+ Every line tested. If truly untestable (e.g., defensive except for impossible conditions), add `# pragma: no cover` with explanation.
121
+
122
+ ### No mocking, no monkey patching
123
+
124
+ Use real implementations:
125
+ - Real in-memory SQLite via `:memory:` (sub-millisecond, tests real SQL)
126
+ - Deterministic hash-based embeddings from `conftest.py` (fast, deterministic, correct shape)
127
+ - Real MCP tool functions called with constructed context objects
128
+
129
+ ### Test structure
130
+
131
+ ```
132
+ tests/
133
+ ├── conftest.py # db fixture (in-memory), deterministic_embed
134
+ ├── test_sqlite_adapter.py # Direct DB operations
135
+ ├── test_search.py # Hybrid search scoring
136
+ ├── test_introspection.py # memory_similar, topics, timeline, etc.
137
+ ├── test_smart_write.py # supersede, build_context
138
+ ├── test_conversations.py # Conversation DAG operations
139
+ ├── test_scoring.py # Score normalization math
140
+ ├── test_embeddings.py # Embedding utilities
141
+ ├── test_core_tools.py # MCP tool wrappers for core
142
+ ├── test_introspection_tools.py # MCP tool wrappers for introspection
143
+ ├── test_smart_write_tools.py # MCP tool wrappers for smart_write
144
+ ├── test_conversation_tools.py # MCP tool wrappers for conversations
145
+ ├── test_mcp_adapter.py # Lifespan test
146
+ └── test_cli.py # CLI commands
147
+ ```
148
+
149
+ ## CI checks — all must pass
150
+
151
+ ```bash
152
+ ruff check rekal/ tests/
153
+ ruff format --check rekal/ tests/
154
+ ty check rekal/ tests/
155
+ pytest --cov=rekal --cov-report=term-missing --cov-fail-under=100 tests/
156
+ ```
157
+
158
+ ## Tooling config
159
+
160
+ - **ruff** — lint + format, line-length 99, target py314
161
+ - **ty** — type checker, python-version 3.14
162
+ - **pytest** — asyncio_mode auto, 100% coverage required
163
+
164
+ ## Dependencies
165
+
166
+ - `mcp[cli]` — FastMCP framework
167
+ - `aiosqlite` — async SQLite
168
+ - `sqlite-vec` — vector search extension
169
+ - `fastembed` — ONNX embeddings (lazy-loaded)
170
+ - `pydantic` — models and validation
rekal-0.0.3/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ Read AGENTS.md before doing anything.
rekal-0.0.3/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jan Bjorge
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.
rekal-0.0.3/PKG-INFO ADDED
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: rekal
3
+ Version: 0.0.3
4
+ Summary: Long-term memory MCP server for LLMs — hybrid search in a single SQLite file
5
+ Author: Jan Bjorge
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: llm,mcp,memory,sqlite,vector-search
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
13
+ Requires-Python: >=3.14
14
+ Requires-Dist: aiosqlite>=0.20.0
15
+ Requires-Dist: fastembed>=0.4.0
16
+ Requires-Dist: mcp[cli]>=1.0.0
17
+ Requires-Dist: pydantic>=2.0.0
18
+ Requires-Dist: sqlite-vec>=0.1.6
19
+ Description-Content-Type: text/markdown
20
+
21
+ # rekal
22
+
23
+ Long-term memory MCP server for LLMs. Hybrid search (FTS5 + vector + recency) in a single SQLite file.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install rekal
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ Add to your MCP client config:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "rekal": {
39
+ "command": "rekal"
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## CLI
46
+
47
+ ```bash
48
+ rekal serve # Run as MCP server (default)
49
+ rekal health # Show database health report
50
+ rekal export # Export all memories as JSON
51
+ ```
rekal-0.0.3/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # rekal
2
+
3
+ Long-term memory MCP server for LLMs. Hybrid search (FTS5 + vector + recency) in a single SQLite file.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install rekal
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Add to your MCP client config:
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "rekal": {
19
+ "command": "rekal"
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ ## CLI
26
+
27
+ ```bash
28
+ rekal serve # Run as MCP server (default)
29
+ rekal health # Show database health report
30
+ rekal export # Export all memories as JSON
31
+ ```
@@ -0,0 +1,96 @@
1
+ [build-system]
2
+ requires = ["hatchling", "hatch-vcs"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "rekal"
7
+ dynamic = ["version"]
8
+ description = "Long-term memory MCP server for LLMs — hybrid search in a single SQLite file"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.14"
12
+ authors = [{ name = "Jan Bjorge" }]
13
+ keywords = ["mcp", "memory", "llm", "sqlite", "vector-search"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3.14",
18
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
19
+ ]
20
+ dependencies = [
21
+ "mcp[cli]>=1.0.0",
22
+ "aiosqlite>=0.20.0",
23
+ "sqlite-vec>=0.1.6",
24
+ "fastembed>=0.4.0",
25
+ "pydantic>=2.0.0",
26
+ ]
27
+
28
+ [project.scripts]
29
+ rekal = "rekal.__main__:main"
30
+
31
+ [tool.hatch.version]
32
+ source = "vcs"
33
+
34
+ [tool.hatch.build.hooks.vcs]
35
+ version-file = "rekal/_version.py"
36
+
37
+ [tool.ruff]
38
+ target-version = "py314"
39
+ line-length = 99
40
+ extend-exclude = ["rekal/_version.py"]
41
+
42
+ [tool.ruff.lint]
43
+ select = [
44
+ "E",
45
+ "W",
46
+ "F",
47
+ "I",
48
+ "N",
49
+ "UP",
50
+ "B",
51
+ "A",
52
+ "SIM",
53
+ "TCH",
54
+ "RUF",
55
+ "PTH",
56
+ "RET",
57
+ "ARG",
58
+ "ERA",
59
+ "TID",
60
+ "PL",
61
+ ]
62
+ ignore = [
63
+ "PLR0913",
64
+ ]
65
+
66
+ [tool.ruff.lint.per-file-ignores]
67
+ "tests/**" = ["PLR2004", "TCH001", "TCH002", "TCH003", "PLC0415", "ARG001"]
68
+ "rekal/adapters/tools/**" = ["TCH001", "TCH002"]
69
+ "rekal/__main__.py" = ["PLC0415"]
70
+ "rekal/embeddings.py" = ["PLC0415"]
71
+
72
+ [tool.ruff.lint.isort]
73
+ known-first-party = ["rekal"]
74
+
75
+ [tool.ty.environment]
76
+ python-version = "3.14"
77
+
78
+ [tool.pytest.ini_options]
79
+ asyncio_mode = "auto"
80
+
81
+ [tool.coverage.run]
82
+ source = ["rekal"]
83
+
84
+ [tool.coverage.report]
85
+ fail_under = 100
86
+ show_missing = true
87
+ omit = ["rekal/_version.py"]
88
+
89
+ [dependency-groups]
90
+ dev = [
91
+ "pytest>=9.0.3",
92
+ "pytest-asyncio>=1.3.0",
93
+ "pytest-cov>=7.1.0",
94
+ "ruff>=0.15.10",
95
+ "ty>=0.0.29",
96
+ ]
@@ -0,0 +1,5 @@
1
+ """rekal — long-term memory MCP server for LLMs."""
2
+
3
+ from rekal._version import __version__
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,77 @@
1
+ """CLI entry point: rekal, rekal health, rekal export."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import asyncio
7
+ import json
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from rekal.adapters.mcp_adapter import default_db_path
13
+ from rekal.adapters.sqlite_adapter import SqliteDatabase
14
+ from rekal.embeddings import FastEmbedder
15
+
16
+
17
+ def get_db_path(args: argparse.Namespace) -> str:
18
+ return args.db or os.environ.get("REKAL_DB_PATH", default_db_path())
19
+
20
+
21
+ async def run_serve() -> None: # pragma: no cover — interactive stdio server
22
+ from rekal.adapters.mcp_adapter import mcp
23
+
24
+ await mcp.run_stdio_async()
25
+
26
+
27
+ async def run_health(db_path: str) -> None:
28
+ if not Path(db_path).exists():
29
+ print(f"Database not found: {db_path}")
30
+ sys.exit(1)
31
+
32
+ embed = FastEmbedder()
33
+ db = await SqliteDatabase.create(db_path, embed)
34
+ try:
35
+ report = await db.memory_health()
36
+ print(json.dumps(report.model_dump(), indent=2))
37
+ finally:
38
+ await db.close()
39
+
40
+
41
+ async def run_export(db_path: str) -> None:
42
+ if not Path(db_path).exists():
43
+ print(f"Database not found: {db_path}")
44
+ sys.exit(1)
45
+
46
+ embed = FastEmbedder()
47
+ db = await SqliteDatabase.create(db_path, embed)
48
+ try:
49
+ memories = await db.memory_timeline(limit=100_000)
50
+ data = [m.model_dump() for m in memories]
51
+ print(json.dumps(data, indent=2))
52
+ finally:
53
+ await db.close()
54
+
55
+
56
+ def main() -> None:
57
+ parser = argparse.ArgumentParser(prog="rekal", description="rekal memory MCP server")
58
+ parser.add_argument("--db", help="Path to SQLite database file")
59
+ sub = parser.add_subparsers(dest="command")
60
+
61
+ sub.add_parser("serve", help="Run as MCP server (default)")
62
+ sub.add_parser("health", help="Show database health report")
63
+ sub.add_parser("export", help="Export all memories as JSON")
64
+
65
+ args = parser.parse_args()
66
+ command = args.command or "serve"
67
+
68
+ if command == "serve": # pragma: no cover — interactive stdio server
69
+ asyncio.run(run_serve())
70
+ elif command == "health":
71
+ asyncio.run(run_health(get_db_path(args)))
72
+ elif command == "export":
73
+ asyncio.run(run_export(get_db_path(args)))
74
+
75
+
76
+ if __name__ == "__main__": # pragma: no cover
77
+ main()
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.0.3'
22
+ __version_tuple__ = version_tuple = (0, 0, 3)
23
+
24
+ __commit_id__ = commit_id = None
File without changes
@@ -0,0 +1,43 @@
1
+ """FastMCP server, lifespan, and DB initialization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from contextlib import asynccontextmanager
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING
10
+
11
+ from mcp.server.fastmcp import FastMCP
12
+
13
+ from rekal.adapters.sqlite_adapter import SqliteDatabase
14
+ from rekal.embeddings import FastEmbedder
15
+
16
+ if TYPE_CHECKING:
17
+ from collections.abc import AsyncIterator
18
+
19
+
20
+ def default_db_path() -> str:
21
+ return str(Path.home() / ".rekal" / "memory.db")
22
+
23
+
24
+ @dataclass
25
+ class AppContext:
26
+ db: SqliteDatabase
27
+
28
+
29
+ @asynccontextmanager
30
+ async def lifespan(_server: FastMCP) -> AsyncIterator[AppContext]:
31
+ db_path = os.environ.get("REKAL_DB_PATH", default_db_path())
32
+ Path(db_path).parent.mkdir(parents=True, exist_ok=True)
33
+ embed = FastEmbedder()
34
+ db = await SqliteDatabase.create(db_path, embed, dimensions=embed.dimensions)
35
+ try:
36
+ yield AppContext(db=db)
37
+ finally:
38
+ await db.close()
39
+
40
+
41
+ mcp = FastMCP("rekal", lifespan=lifespan)
42
+
43
+ import rekal.adapters.tools # noqa: E402, F401