getbased-agent-stack 0.2.0__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.
- getbased_agent_stack-0.2.0/LICENSE +22 -0
- getbased_agent_stack-0.2.0/PKG-INFO +116 -0
- getbased_agent_stack-0.2.0/README.md +97 -0
- getbased_agent_stack-0.2.0/pyproject.toml +40 -0
- getbased_agent_stack-0.2.0/setup.cfg +4 -0
- getbased_agent_stack-0.2.0/src/getbased_agent_stack/__init__.py +8 -0
- getbased_agent_stack-0.2.0/src/getbased_agent_stack/cli.py +72 -0
- getbased_agent_stack-0.2.0/src/getbased_agent_stack.egg-info/PKG-INFO +116 -0
- getbased_agent_stack-0.2.0/src/getbased_agent_stack.egg-info/SOURCES.txt +12 -0
- getbased_agent_stack-0.2.0/src/getbased_agent_stack.egg-info/dependency_links.txt +1 -0
- getbased_agent_stack-0.2.0/src/getbased_agent_stack.egg-info/entry_points.txt +2 -0
- getbased_agent_stack-0.2.0/src/getbased_agent_stack.egg-info/requires.txt +11 -0
- getbased_agent_stack-0.2.0/src/getbased_agent_stack.egg-info/top_level.txt +1 -0
- getbased_agent_stack-0.2.0/tests/test_integration.py +223 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
|
6
|
+
of this license document, but changing it is not allowed.
|
|
7
|
+
|
|
8
|
+
Preamble
|
|
9
|
+
|
|
10
|
+
The GNU General Public License is a free, copyleft license for
|
|
11
|
+
software and other kinds of works.
|
|
12
|
+
|
|
13
|
+
The licenses for most software and other practical works are designed
|
|
14
|
+
to take away your freedom to share and change the works. By contrast,
|
|
15
|
+
the GNU General Public License is intended to guarantee your freedom to
|
|
16
|
+
share and change all versions of a program--to make sure it remains free
|
|
17
|
+
software for all its users. We, the Free Software Foundation, use the
|
|
18
|
+
GNU General Public License for most of our software; it applies also to
|
|
19
|
+
any other work released this way by its authors. You can apply it to
|
|
20
|
+
your programs, too.
|
|
21
|
+
|
|
22
|
+
For the full license text, see <https://www.gnu.org/licenses/gpl-3.0.txt>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: getbased-agent-stack
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: One-command install of the full getbased agent stack — getbased-mcp + getbased-rag + getbased-dashboard
|
|
5
|
+
License-Expression: GPL-3.0-only
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: getbased-mcp>=0.2.2
|
|
10
|
+
Requires-Dist: getbased-rag>=0.6.1
|
|
11
|
+
Requires-Dist: getbased-dashboard>=0.5.0
|
|
12
|
+
Provides-Extra: full
|
|
13
|
+
Requires-Dist: getbased-rag[full]>=0.6.1; extra == "full"
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
16
|
+
Requires-Dist: httpx>=0.27; extra == "test"
|
|
17
|
+
Requires-Dist: pytest-timeout>=2.3; extra == "test"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# getbased-agent-stack
|
|
21
|
+
|
|
22
|
+
Meta-package bundling the full [getbased](https://getbased.health) agent stack into one install: the MCP adapter, the RAG engine, the browser dashboard, a thin discovery CLI, a hardened systemd unit, and example configs for Claude Code + Hermes.
|
|
23
|
+
|
|
24
|
+
Part of the [getbased-agents monorepo](https://github.com/elkimek/getbased-agents).
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pipx install "getbased-agent-stack[full]"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Pulls:
|
|
33
|
+
|
|
34
|
+
- [`getbased-mcp`](https://github.com/elkimek/getbased-agents/tree/main/packages/mcp) — stdio MCP server that Claude Code / Hermes / OpenClaw spawn
|
|
35
|
+
- [`getbased-rag`](https://github.com/elkimek/getbased-agents/tree/main/packages/rag) — local RAG knowledge server (FastAPI + Qdrant + MiniLM/BGE)
|
|
36
|
+
- [`getbased-dashboard`](https://github.com/elkimek/getbased-agents/tree/main/packages/dashboard) — web UI for library management, MCP config generation, and agent-activity inspection
|
|
37
|
+
- The `getbased-stack` discovery CLI
|
|
38
|
+
- `[full]` extra: PDF/DOCX parsers + ONNX runtime for hardware-accelerated embeddings
|
|
39
|
+
|
|
40
|
+
Total install: ~500 MB (the ML deps dominate). Smaller installs available — `pipx install getbased-mcp` (10 MB, agent only), `pipx install "getbased-rag[full]"` (RAG only), `pipx install getbased-dashboard` (UI + MCP; pulls rag if you want the Knowledge tab working).
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 1. Start the RAG server — local Qdrant DB + MiniLM embedder
|
|
46
|
+
lens serve # blocks; serves on 127.0.0.1:8322
|
|
47
|
+
lens key # prints the bearer token
|
|
48
|
+
|
|
49
|
+
# 2. Start the dashboard in another terminal
|
|
50
|
+
getbased-dashboard serve # serves on 127.0.0.1:8323
|
|
51
|
+
|
|
52
|
+
# 3. Open http://127.0.0.1:8323 in your browser, paste the lens key
|
|
53
|
+
# Create libraries, drag-drop files to ingest (live chunks/sec pill),
|
|
54
|
+
# run the MCP Test button to verify your agent path
|
|
55
|
+
|
|
56
|
+
# 4. Wire the MCP into your AI client
|
|
57
|
+
# The dashboard's MCP tab generates paste-ready config blocks for
|
|
58
|
+
# Claude Desktop, Claude Code, Cursor, Cline, and Hermes.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Both the RAG server and the getbased PWA talk to the same `lens` instance — point the PWA at `http://127.0.0.1:8322` under **Settings → AI → Knowledge Base → External server** and the same corpus feeds the browser chat, the dashboard, and any MCP-connected agent.
|
|
62
|
+
|
|
63
|
+
## Running as a systemd service
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cp systemd/getbased-rag.service ~/.config/systemd/user/
|
|
67
|
+
systemctl --user daemon-reload
|
|
68
|
+
systemctl --user enable --now getbased-rag
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The unit is hardened (`ProtectSystem=strict`, `NoNewPrivileges`, `RestrictAddressFamilies`, etc.); run `systemd-analyze security getbased-rag` to see the score.
|
|
72
|
+
|
|
73
|
+
## Architecture
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Claude Code / Hermes / OpenClaw Browser
|
|
77
|
+
│ MCP (stdio) │ HTTP
|
|
78
|
+
▼ ▼
|
|
79
|
+
getbased-mcp getbased-dashboard (localhost:8323)
|
|
80
|
+
│ │ │ │
|
|
81
|
+
│ HTTP │ HTTP │ proxies │ spawns stdio for Test
|
|
82
|
+
▼ ▼ ▼ ▼
|
|
83
|
+
sync GW getbased-rag ◀──────────────┘ getbased-mcp
|
|
84
|
+
(localhost:8322)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The MCP holds no persistent state; it's a thin translator between MCP tool calls and two HTTP backends:
|
|
88
|
+
|
|
89
|
+
- `sync.getbased.health/api/context` — read-only lab summary pushed by your PWA session (via Agent Access token)
|
|
90
|
+
- `localhost:8322` (getbased-rag) — your local research library
|
|
91
|
+
|
|
92
|
+
The dashboard is likewise stateless — it proxies rag for Knowledge operations, imports `getbased_mcp` to introspect env/config, and spawns the MCP binary on demand to verify it works.
|
|
93
|
+
|
|
94
|
+
## Version compatibility
|
|
95
|
+
|
|
96
|
+
| Stack | mcp | rag | dashboard | Protocol |
|
|
97
|
+
|---|---|---|---|---|
|
|
98
|
+
| 0.1.x | ≥0.2.0 | ≥0.1.0 | — | v1 (multi-library) |
|
|
99
|
+
| 0.2.x | ≥0.2.2 | ≥0.6.0 | ≥0.5.0 | v1 (+ streaming ingest, per-library models) |
|
|
100
|
+
|
|
101
|
+
Bump the meta's major when sibling protocols break; bump siblings freely for normal features.
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
This package is the meta — the interesting code lives in sibling packages. See the [monorepo root README](https://github.com/elkimek/getbased-agents#development) for workspace setup.
|
|
106
|
+
|
|
107
|
+
The integration test (`tests/test_integration.py`) spins up `lens serve` in a subprocess, ingests a fixture, and exercises every MCP tool round-trip. Catches drift between the siblings the way the v1.21 catch-up drift would have been caught if the test existed then. The dashboard has its own test suite (`cd packages/dashboard && uv run pytest`) covering the proxy, modal logic, stdio probe, and activity-log handling.
|
|
108
|
+
|
|
109
|
+
## Related docs
|
|
110
|
+
|
|
111
|
+
- [packages/stack/CONTRIBUTING.md](CONTRIBUTING.md) — when to bump the meta vs a sibling
|
|
112
|
+
- [packages/stack/SECURITY.md](SECURITY.md) — threat model, scope, sibling pointers
|
|
113
|
+
|
|
114
|
+
## Licence
|
|
115
|
+
|
|
116
|
+
GPL-3.0-only, matching the siblings.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# getbased-agent-stack
|
|
2
|
+
|
|
3
|
+
Meta-package bundling the full [getbased](https://getbased.health) agent stack into one install: the MCP adapter, the RAG engine, the browser dashboard, a thin discovery CLI, a hardened systemd unit, and example configs for Claude Code + Hermes.
|
|
4
|
+
|
|
5
|
+
Part of the [getbased-agents monorepo](https://github.com/elkimek/getbased-agents).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pipx install "getbased-agent-stack[full]"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Pulls:
|
|
14
|
+
|
|
15
|
+
- [`getbased-mcp`](https://github.com/elkimek/getbased-agents/tree/main/packages/mcp) — stdio MCP server that Claude Code / Hermes / OpenClaw spawn
|
|
16
|
+
- [`getbased-rag`](https://github.com/elkimek/getbased-agents/tree/main/packages/rag) — local RAG knowledge server (FastAPI + Qdrant + MiniLM/BGE)
|
|
17
|
+
- [`getbased-dashboard`](https://github.com/elkimek/getbased-agents/tree/main/packages/dashboard) — web UI for library management, MCP config generation, and agent-activity inspection
|
|
18
|
+
- The `getbased-stack` discovery CLI
|
|
19
|
+
- `[full]` extra: PDF/DOCX parsers + ONNX runtime for hardware-accelerated embeddings
|
|
20
|
+
|
|
21
|
+
Total install: ~500 MB (the ML deps dominate). Smaller installs available — `pipx install getbased-mcp` (10 MB, agent only), `pipx install "getbased-rag[full]"` (RAG only), `pipx install getbased-dashboard` (UI + MCP; pulls rag if you want the Knowledge tab working).
|
|
22
|
+
|
|
23
|
+
## Quickstart
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 1. Start the RAG server — local Qdrant DB + MiniLM embedder
|
|
27
|
+
lens serve # blocks; serves on 127.0.0.1:8322
|
|
28
|
+
lens key # prints the bearer token
|
|
29
|
+
|
|
30
|
+
# 2. Start the dashboard in another terminal
|
|
31
|
+
getbased-dashboard serve # serves on 127.0.0.1:8323
|
|
32
|
+
|
|
33
|
+
# 3. Open http://127.0.0.1:8323 in your browser, paste the lens key
|
|
34
|
+
# Create libraries, drag-drop files to ingest (live chunks/sec pill),
|
|
35
|
+
# run the MCP Test button to verify your agent path
|
|
36
|
+
|
|
37
|
+
# 4. Wire the MCP into your AI client
|
|
38
|
+
# The dashboard's MCP tab generates paste-ready config blocks for
|
|
39
|
+
# Claude Desktop, Claude Code, Cursor, Cline, and Hermes.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Both the RAG server and the getbased PWA talk to the same `lens` instance — point the PWA at `http://127.0.0.1:8322` under **Settings → AI → Knowledge Base → External server** and the same corpus feeds the browser chat, the dashboard, and any MCP-connected agent.
|
|
43
|
+
|
|
44
|
+
## Running as a systemd service
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cp systemd/getbased-rag.service ~/.config/systemd/user/
|
|
48
|
+
systemctl --user daemon-reload
|
|
49
|
+
systemctl --user enable --now getbased-rag
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The unit is hardened (`ProtectSystem=strict`, `NoNewPrivileges`, `RestrictAddressFamilies`, etc.); run `systemd-analyze security getbased-rag` to see the score.
|
|
53
|
+
|
|
54
|
+
## Architecture
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
Claude Code / Hermes / OpenClaw Browser
|
|
58
|
+
│ MCP (stdio) │ HTTP
|
|
59
|
+
▼ ▼
|
|
60
|
+
getbased-mcp getbased-dashboard (localhost:8323)
|
|
61
|
+
│ │ │ │
|
|
62
|
+
│ HTTP │ HTTP │ proxies │ spawns stdio for Test
|
|
63
|
+
▼ ▼ ▼ ▼
|
|
64
|
+
sync GW getbased-rag ◀──────────────┘ getbased-mcp
|
|
65
|
+
(localhost:8322)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The MCP holds no persistent state; it's a thin translator between MCP tool calls and two HTTP backends:
|
|
69
|
+
|
|
70
|
+
- `sync.getbased.health/api/context` — read-only lab summary pushed by your PWA session (via Agent Access token)
|
|
71
|
+
- `localhost:8322` (getbased-rag) — your local research library
|
|
72
|
+
|
|
73
|
+
The dashboard is likewise stateless — it proxies rag for Knowledge operations, imports `getbased_mcp` to introspect env/config, and spawns the MCP binary on demand to verify it works.
|
|
74
|
+
|
|
75
|
+
## Version compatibility
|
|
76
|
+
|
|
77
|
+
| Stack | mcp | rag | dashboard | Protocol |
|
|
78
|
+
|---|---|---|---|---|
|
|
79
|
+
| 0.1.x | ≥0.2.0 | ≥0.1.0 | — | v1 (multi-library) |
|
|
80
|
+
| 0.2.x | ≥0.2.2 | ≥0.6.0 | ≥0.5.0 | v1 (+ streaming ingest, per-library models) |
|
|
81
|
+
|
|
82
|
+
Bump the meta's major when sibling protocols break; bump siblings freely for normal features.
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
This package is the meta — the interesting code lives in sibling packages. See the [monorepo root README](https://github.com/elkimek/getbased-agents#development) for workspace setup.
|
|
87
|
+
|
|
88
|
+
The integration test (`tests/test_integration.py`) spins up `lens serve` in a subprocess, ingests a fixture, and exercises every MCP tool round-trip. Catches drift between the siblings the way the v1.21 catch-up drift would have been caught if the test existed then. The dashboard has its own test suite (`cd packages/dashboard && uv run pytest`) covering the proxy, modal logic, stdio probe, and activity-log handling.
|
|
89
|
+
|
|
90
|
+
## Related docs
|
|
91
|
+
|
|
92
|
+
- [packages/stack/CONTRIBUTING.md](CONTRIBUTING.md) — when to bump the meta vs a sibling
|
|
93
|
+
- [packages/stack/SECURITY.md](SECURITY.md) — threat model, scope, sibling pointers
|
|
94
|
+
|
|
95
|
+
## Licence
|
|
96
|
+
|
|
97
|
+
GPL-3.0-only, matching the siblings.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "getbased-agent-stack"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "One-command install of the full getbased agent stack — getbased-mcp + getbased-rag + getbased-dashboard"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "GPL-3.0-only"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
# Pulls every sibling package. Bump this meta when a sibling protocol
|
|
13
|
+
# bump requires coordinated release.
|
|
14
|
+
dependencies = [
|
|
15
|
+
"getbased-mcp>=0.2.2",
|
|
16
|
+
"getbased-rag>=0.6.1",
|
|
17
|
+
"getbased-dashboard>=0.5.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
# With the full parser + ONNX stack on the rag side.
|
|
22
|
+
full = ["getbased-rag[full]>=0.6.1"]
|
|
23
|
+
# For contributors running the integration test harness.
|
|
24
|
+
test = ["pytest>=8.0", "httpx>=0.27", "pytest-timeout>=2.3"]
|
|
25
|
+
|
|
26
|
+
# In the monorepo (elkimek/getbased-agents), the workspace-root pyproject
|
|
27
|
+
# overrides these with `workspace = true` sources. From PyPI, the
|
|
28
|
+
# normal `dependencies` block above resolves remotely as expected.
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
# Convenience launcher. Delegates to the real lens CLI so users don't
|
|
32
|
+
# have to remember which package provides which binary.
|
|
33
|
+
getbased-stack = "getbased_agent_stack.cli:main"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
testpaths = ["tests"]
|
|
40
|
+
addopts = "-ra -q"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""getbased-agent-stack — meta-package binding getbased-mcp + getbased-rag.
|
|
2
|
+
|
|
3
|
+
This package contains no tools of its own; it's a convenience installer
|
|
4
|
+
plus a small CLI that proxies to the real binaries. Everything
|
|
5
|
+
interesting lives in the sibling repos.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Thin CLI wrapper that proxies to getbased-rag's `lens` and getbased-mcp's
|
|
2
|
+
runner. Kept minimal so users can still call `lens` / `getbased-mcp`
|
|
3
|
+
directly — this exists only for discoverability ("what commands do I have
|
|
4
|
+
after `pipx install getbased-agent-stack`?")."""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
HELP = """\
|
|
11
|
+
getbased-stack — thin wrapper over the two binaries installed by this package.
|
|
12
|
+
|
|
13
|
+
Real commands (use directly, they're also on your PATH):
|
|
14
|
+
lens — getbased-rag CLI (serve, ingest, stats, ...)
|
|
15
|
+
getbased-mcp — getbased-mcp stdio server (spawned by agents)
|
|
16
|
+
|
|
17
|
+
This wrapper only exists for discoverability:
|
|
18
|
+
getbased-stack serve → lens serve
|
|
19
|
+
getbased-stack info → lens info
|
|
20
|
+
getbased-stack version → print the installed package versions
|
|
21
|
+
|
|
22
|
+
Quick start:
|
|
23
|
+
1. `getbased-stack serve &` start the RAG server
|
|
24
|
+
2. `lens ingest /path/to/papers` index your docs
|
|
25
|
+
3. configure your MCP agent (Claude Code, Hermes) — see the README
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main() -> int:
|
|
30
|
+
import getbased_agent_stack
|
|
31
|
+
|
|
32
|
+
argv = sys.argv[1:]
|
|
33
|
+
if not argv or argv[0] in ("-h", "--help", "help"):
|
|
34
|
+
print(HELP)
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
cmd, rest = argv[0], argv[1:]
|
|
38
|
+
if cmd == "version":
|
|
39
|
+
try:
|
|
40
|
+
import getbased_mcp # noqa: F401
|
|
41
|
+
import lens # noqa: F401
|
|
42
|
+
import importlib.metadata as md
|
|
43
|
+
print(f"getbased-agent-stack {getbased_agent_stack.__version__}")
|
|
44
|
+
try:
|
|
45
|
+
print(f" getbased-mcp {md.version('getbased-mcp')}")
|
|
46
|
+
except md.PackageNotFoundError:
|
|
47
|
+
print(" getbased-mcp (not installed)")
|
|
48
|
+
try:
|
|
49
|
+
print(f" getbased-rag {md.version('getbased-rag')}")
|
|
50
|
+
except md.PackageNotFoundError:
|
|
51
|
+
print(" getbased-rag (not installed)")
|
|
52
|
+
return 0
|
|
53
|
+
except ImportError as e:
|
|
54
|
+
print(f"Missing dependency: {e}", file=sys.stderr)
|
|
55
|
+
return 1
|
|
56
|
+
|
|
57
|
+
# Delegate to the lens CLI for everything else.
|
|
58
|
+
try:
|
|
59
|
+
from lens.cli import app as lens_app
|
|
60
|
+
|
|
61
|
+
sys.argv = ["lens"] + argv
|
|
62
|
+
lens_app()
|
|
63
|
+
return 0
|
|
64
|
+
except SystemExit as e:
|
|
65
|
+
return int(e.code or 0)
|
|
66
|
+
except ImportError:
|
|
67
|
+
print("getbased-rag not installed — install with `pipx install getbased-agent-stack[full]`", file=sys.stderr)
|
|
68
|
+
return 1
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
sys.exit(main())
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: getbased-agent-stack
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: One-command install of the full getbased agent stack — getbased-mcp + getbased-rag + getbased-dashboard
|
|
5
|
+
License-Expression: GPL-3.0-only
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: getbased-mcp>=0.2.2
|
|
10
|
+
Requires-Dist: getbased-rag>=0.6.1
|
|
11
|
+
Requires-Dist: getbased-dashboard>=0.5.0
|
|
12
|
+
Provides-Extra: full
|
|
13
|
+
Requires-Dist: getbased-rag[full]>=0.6.1; extra == "full"
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
16
|
+
Requires-Dist: httpx>=0.27; extra == "test"
|
|
17
|
+
Requires-Dist: pytest-timeout>=2.3; extra == "test"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# getbased-agent-stack
|
|
21
|
+
|
|
22
|
+
Meta-package bundling the full [getbased](https://getbased.health) agent stack into one install: the MCP adapter, the RAG engine, the browser dashboard, a thin discovery CLI, a hardened systemd unit, and example configs for Claude Code + Hermes.
|
|
23
|
+
|
|
24
|
+
Part of the [getbased-agents monorepo](https://github.com/elkimek/getbased-agents).
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pipx install "getbased-agent-stack[full]"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Pulls:
|
|
33
|
+
|
|
34
|
+
- [`getbased-mcp`](https://github.com/elkimek/getbased-agents/tree/main/packages/mcp) — stdio MCP server that Claude Code / Hermes / OpenClaw spawn
|
|
35
|
+
- [`getbased-rag`](https://github.com/elkimek/getbased-agents/tree/main/packages/rag) — local RAG knowledge server (FastAPI + Qdrant + MiniLM/BGE)
|
|
36
|
+
- [`getbased-dashboard`](https://github.com/elkimek/getbased-agents/tree/main/packages/dashboard) — web UI for library management, MCP config generation, and agent-activity inspection
|
|
37
|
+
- The `getbased-stack` discovery CLI
|
|
38
|
+
- `[full]` extra: PDF/DOCX parsers + ONNX runtime for hardware-accelerated embeddings
|
|
39
|
+
|
|
40
|
+
Total install: ~500 MB (the ML deps dominate). Smaller installs available — `pipx install getbased-mcp` (10 MB, agent only), `pipx install "getbased-rag[full]"` (RAG only), `pipx install getbased-dashboard` (UI + MCP; pulls rag if you want the Knowledge tab working).
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 1. Start the RAG server — local Qdrant DB + MiniLM embedder
|
|
46
|
+
lens serve # blocks; serves on 127.0.0.1:8322
|
|
47
|
+
lens key # prints the bearer token
|
|
48
|
+
|
|
49
|
+
# 2. Start the dashboard in another terminal
|
|
50
|
+
getbased-dashboard serve # serves on 127.0.0.1:8323
|
|
51
|
+
|
|
52
|
+
# 3. Open http://127.0.0.1:8323 in your browser, paste the lens key
|
|
53
|
+
# Create libraries, drag-drop files to ingest (live chunks/sec pill),
|
|
54
|
+
# run the MCP Test button to verify your agent path
|
|
55
|
+
|
|
56
|
+
# 4. Wire the MCP into your AI client
|
|
57
|
+
# The dashboard's MCP tab generates paste-ready config blocks for
|
|
58
|
+
# Claude Desktop, Claude Code, Cursor, Cline, and Hermes.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Both the RAG server and the getbased PWA talk to the same `lens` instance — point the PWA at `http://127.0.0.1:8322` under **Settings → AI → Knowledge Base → External server** and the same corpus feeds the browser chat, the dashboard, and any MCP-connected agent.
|
|
62
|
+
|
|
63
|
+
## Running as a systemd service
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cp systemd/getbased-rag.service ~/.config/systemd/user/
|
|
67
|
+
systemctl --user daemon-reload
|
|
68
|
+
systemctl --user enable --now getbased-rag
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The unit is hardened (`ProtectSystem=strict`, `NoNewPrivileges`, `RestrictAddressFamilies`, etc.); run `systemd-analyze security getbased-rag` to see the score.
|
|
72
|
+
|
|
73
|
+
## Architecture
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Claude Code / Hermes / OpenClaw Browser
|
|
77
|
+
│ MCP (stdio) │ HTTP
|
|
78
|
+
▼ ▼
|
|
79
|
+
getbased-mcp getbased-dashboard (localhost:8323)
|
|
80
|
+
│ │ │ │
|
|
81
|
+
│ HTTP │ HTTP │ proxies │ spawns stdio for Test
|
|
82
|
+
▼ ▼ ▼ ▼
|
|
83
|
+
sync GW getbased-rag ◀──────────────┘ getbased-mcp
|
|
84
|
+
(localhost:8322)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The MCP holds no persistent state; it's a thin translator between MCP tool calls and two HTTP backends:
|
|
88
|
+
|
|
89
|
+
- `sync.getbased.health/api/context` — read-only lab summary pushed by your PWA session (via Agent Access token)
|
|
90
|
+
- `localhost:8322` (getbased-rag) — your local research library
|
|
91
|
+
|
|
92
|
+
The dashboard is likewise stateless — it proxies rag for Knowledge operations, imports `getbased_mcp` to introspect env/config, and spawns the MCP binary on demand to verify it works.
|
|
93
|
+
|
|
94
|
+
## Version compatibility
|
|
95
|
+
|
|
96
|
+
| Stack | mcp | rag | dashboard | Protocol |
|
|
97
|
+
|---|---|---|---|---|
|
|
98
|
+
| 0.1.x | ≥0.2.0 | ≥0.1.0 | — | v1 (multi-library) |
|
|
99
|
+
| 0.2.x | ≥0.2.2 | ≥0.6.0 | ≥0.5.0 | v1 (+ streaming ingest, per-library models) |
|
|
100
|
+
|
|
101
|
+
Bump the meta's major when sibling protocols break; bump siblings freely for normal features.
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
This package is the meta — the interesting code lives in sibling packages. See the [monorepo root README](https://github.com/elkimek/getbased-agents#development) for workspace setup.
|
|
106
|
+
|
|
107
|
+
The integration test (`tests/test_integration.py`) spins up `lens serve` in a subprocess, ingests a fixture, and exercises every MCP tool round-trip. Catches drift between the siblings the way the v1.21 catch-up drift would have been caught if the test existed then. The dashboard has its own test suite (`cd packages/dashboard && uv run pytest`) covering the proxy, modal logic, stdio probe, and activity-log handling.
|
|
108
|
+
|
|
109
|
+
## Related docs
|
|
110
|
+
|
|
111
|
+
- [packages/stack/CONTRIBUTING.md](CONTRIBUTING.md) — when to bump the meta vs a sibling
|
|
112
|
+
- [packages/stack/SECURITY.md](SECURITY.md) — threat model, scope, sibling pointers
|
|
113
|
+
|
|
114
|
+
## Licence
|
|
115
|
+
|
|
116
|
+
GPL-3.0-only, matching the siblings.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/getbased_agent_stack/__init__.py
|
|
5
|
+
src/getbased_agent_stack/cli.py
|
|
6
|
+
src/getbased_agent_stack.egg-info/PKG-INFO
|
|
7
|
+
src/getbased_agent_stack.egg-info/SOURCES.txt
|
|
8
|
+
src/getbased_agent_stack.egg-info/dependency_links.txt
|
|
9
|
+
src/getbased_agent_stack.egg-info/entry_points.txt
|
|
10
|
+
src/getbased_agent_stack.egg-info/requires.txt
|
|
11
|
+
src/getbased_agent_stack.egg-info/top_level.txt
|
|
12
|
+
tests/test_integration.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
getbased_agent_stack
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""End-to-end integration test — real RAG subprocess + real MCP tool calls.
|
|
2
|
+
|
|
3
|
+
Each sibling repo has its own hermetic unit tests (mocked HTTP or Qdrant in
|
|
4
|
+
tmp dir). This suite exists because neither of those catches protocol drift
|
|
5
|
+
between the two: the MCP tests only verify the MCP side, and the RAG tests
|
|
6
|
+
only verify the RAG side. Here we stand up an actual `lens serve` process,
|
|
7
|
+
read its generated API key, and call every MCP tool against it through the
|
|
8
|
+
normal function entry points.
|
|
9
|
+
|
|
10
|
+
Runs in CI via `pytest`. Skips cleanly if either package isn't installed
|
|
11
|
+
(keeps the test module importable even in a partial install).
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import contextlib
|
|
17
|
+
import os
|
|
18
|
+
import socket
|
|
19
|
+
import subprocess
|
|
20
|
+
import time
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Iterator
|
|
23
|
+
|
|
24
|
+
import pytest
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ── Availability guards — skip cleanly if the stack isn't installed ──
|
|
28
|
+
|
|
29
|
+
def _has(module: str) -> bool:
|
|
30
|
+
try:
|
|
31
|
+
__import__(module)
|
|
32
|
+
return True
|
|
33
|
+
except ImportError:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
pytestmark = pytest.mark.skipif(
|
|
38
|
+
not (_has("lens") and _has("getbased_mcp")),
|
|
39
|
+
reason="integration test requires both getbased-rag and getbased-mcp installed",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ── Helpers ──────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
def _free_port() -> int:
|
|
46
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
47
|
+
s.bind(("127.0.0.1", 0))
|
|
48
|
+
return s.getsockname()[1]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _wait_http_ready(url: str, timeout: float = 60.0) -> None:
|
|
52
|
+
"""Poll until the server answers, or fail the test. The first call takes
|
|
53
|
+
a few seconds because the embedder model downloads on cold start."""
|
|
54
|
+
import urllib.error
|
|
55
|
+
import urllib.request
|
|
56
|
+
|
|
57
|
+
deadline = time.monotonic() + timeout
|
|
58
|
+
last_err: Exception | None = None
|
|
59
|
+
while time.monotonic() < deadline:
|
|
60
|
+
try:
|
|
61
|
+
with urllib.request.urlopen(url, timeout=2) as resp:
|
|
62
|
+
if resp.status == 200:
|
|
63
|
+
return
|
|
64
|
+
except (urllib.error.URLError, ConnectionError, TimeoutError) as e:
|
|
65
|
+
last_err = e
|
|
66
|
+
time.sleep(0.5)
|
|
67
|
+
raise RuntimeError(f"server at {url} never came up: {last_err}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ── Fixtures ─────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
def _seed_via_cli(data_dir: Path) -> Path:
|
|
73
|
+
"""Write a fixture file and run `lens ingest` against it. Must run
|
|
74
|
+
BEFORE `lens serve` starts — Qdrant local storage is exclusive-locked
|
|
75
|
+
by whichever process touches it first, so server+ingest can't share
|
|
76
|
+
a data dir at the same time."""
|
|
77
|
+
fixture_dir = Path(__file__).parent / "fixtures"
|
|
78
|
+
fixture_dir.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
(fixture_dir / "vitamin-d.md").write_text(
|
|
80
|
+
"Vitamin D is a secosteroid hormone synthesised in skin when UVB "
|
|
81
|
+
"hits 7-dehydrocholesterol. " + "filler filler filler " * 20
|
|
82
|
+
)
|
|
83
|
+
env = {
|
|
84
|
+
**os.environ,
|
|
85
|
+
"LENS_DATA_DIR": str(data_dir),
|
|
86
|
+
"LENS_SIMILARITY_FLOOR": "0.0",
|
|
87
|
+
}
|
|
88
|
+
r = subprocess.run(
|
|
89
|
+
["lens", "ingest", str(fixture_dir)],
|
|
90
|
+
env=env,
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
timeout=180,
|
|
94
|
+
)
|
|
95
|
+
assert r.returncode == 0, f"ingest failed:\nstdout: {r.stdout}\nstderr: {r.stderr}"
|
|
96
|
+
return fixture_dir
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.fixture(scope="module")
|
|
100
|
+
def seeded_lens(tmp_path_factory) -> Iterator[dict]:
|
|
101
|
+
"""Seed a fresh tmp data dir via `lens ingest`, THEN start `lens serve`
|
|
102
|
+
against the same dir. The server picks up the already-ingested chunks
|
|
103
|
+
on first library access. Yields {url, key_file, data_dir}."""
|
|
104
|
+
data_dir = tmp_path_factory.mktemp("lens-data")
|
|
105
|
+
_seed_via_cli(data_dir)
|
|
106
|
+
|
|
107
|
+
port = _free_port()
|
|
108
|
+
env = {
|
|
109
|
+
**os.environ,
|
|
110
|
+
"LENS_DATA_DIR": str(data_dir),
|
|
111
|
+
"LENS_HOST": "127.0.0.1",
|
|
112
|
+
"LENS_PORT": str(port),
|
|
113
|
+
"LENS_SIMILARITY_FLOOR": "0.0",
|
|
114
|
+
}
|
|
115
|
+
proc = subprocess.Popen(
|
|
116
|
+
["lens", "serve"],
|
|
117
|
+
env=env,
|
|
118
|
+
stdout=subprocess.PIPE,
|
|
119
|
+
stderr=subprocess.PIPE,
|
|
120
|
+
)
|
|
121
|
+
try:
|
|
122
|
+
_wait_http_ready(f"http://127.0.0.1:{port}/health", timeout=90)
|
|
123
|
+
yield {
|
|
124
|
+
"url": f"http://127.0.0.1:{port}",
|
|
125
|
+
"key_file": data_dir / "api_key",
|
|
126
|
+
"data_dir": data_dir,
|
|
127
|
+
}
|
|
128
|
+
finally:
|
|
129
|
+
proc.terminate()
|
|
130
|
+
with contextlib.suppress(subprocess.TimeoutExpired):
|
|
131
|
+
proc.wait(timeout=5)
|
|
132
|
+
if proc.returncode is None:
|
|
133
|
+
proc.kill()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@pytest.fixture
|
|
137
|
+
def mcp_env(seeded_lens: dict, monkeypatch: pytest.MonkeyPatch):
|
|
138
|
+
"""Reload getbased_mcp with env vars pointing at the subprocess server.
|
|
139
|
+
Module-level globals pick up LENS_URL / LENS_API_KEY_FILE at import time,
|
|
140
|
+
so we need a reload after monkeypatching."""
|
|
141
|
+
import importlib
|
|
142
|
+
|
|
143
|
+
import getbased_mcp
|
|
144
|
+
|
|
145
|
+
monkeypatch.setenv("LENS_URL", seeded_lens["url"])
|
|
146
|
+
monkeypatch.setenv("LENS_API_KEY_FILE", str(seeded_lens["key_file"]))
|
|
147
|
+
# No gateway token — skip blood-work tools. We're only testing the
|
|
148
|
+
# knowledge-base end of the protocol here.
|
|
149
|
+
monkeypatch.setenv("GETBASED_TOKEN", "")
|
|
150
|
+
importlib.reload(getbased_mcp)
|
|
151
|
+
return getbased_mcp
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ── Tests ────────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
@pytest.mark.timeout(180)
|
|
157
|
+
def test_full_knowledge_tool_chain(mcp_env) -> None:
|
|
158
|
+
"""Run every knowledge-* MCP tool against the real Lens and verify the
|
|
159
|
+
round-trip produces sensible output. This is the integration test that
|
|
160
|
+
would have caught the original 'MCP is one version behind RAG' drift."""
|
|
161
|
+
|
|
162
|
+
async def run() -> None:
|
|
163
|
+
# 1. Discover the active library
|
|
164
|
+
list_out = await mcp_env.knowledge_list_libraries()
|
|
165
|
+
assert "Libraries:" in list_out
|
|
166
|
+
assert "(active)" in list_out
|
|
167
|
+
|
|
168
|
+
# 2. Inspect its stats — should reflect the ingested document
|
|
169
|
+
stats_out = await mcp_env.knowledge_stats()
|
|
170
|
+
assert "Total chunks:" in stats_out
|
|
171
|
+
assert "vitamin-d.md" in stats_out
|
|
172
|
+
|
|
173
|
+
# 3. Search for something the seeded document contains
|
|
174
|
+
search_out = await mcp_env.knowledge_search(query="vitamin D secosteroid hormone", n_results=3)
|
|
175
|
+
assert "[1]" in search_out
|
|
176
|
+
assert "vitamin-d.md" in search_out
|
|
177
|
+
|
|
178
|
+
# 4. Config tool returns a paste-ready config
|
|
179
|
+
config_out = await mcp_env.getbased_lens_config()
|
|
180
|
+
assert mcp_env.LENS_URL in config_out
|
|
181
|
+
assert "External server" in config_out
|
|
182
|
+
|
|
183
|
+
# 5. Create a second library, activate it, verify switch took effect
|
|
184
|
+
import httpx
|
|
185
|
+
|
|
186
|
+
key = mcp_env._read_lens_key()
|
|
187
|
+
async with httpx.AsyncClient(timeout=10) as client:
|
|
188
|
+
r = await client.post(
|
|
189
|
+
f"{mcp_env.LENS_URL}/libraries",
|
|
190
|
+
headers={"Authorization": f"Bearer {key}"},
|
|
191
|
+
json={"name": "Secondary"},
|
|
192
|
+
)
|
|
193
|
+
r.raise_for_status()
|
|
194
|
+
new_id = r.json()["library"]["id"]
|
|
195
|
+
|
|
196
|
+
activate_out = await mcp_env.knowledge_activate_library(library_id=new_id)
|
|
197
|
+
assert "Active library is now" in activate_out
|
|
198
|
+
assert "Secondary" in activate_out
|
|
199
|
+
|
|
200
|
+
# Stats on the new library: empty
|
|
201
|
+
new_stats = await mcp_env.knowledge_stats()
|
|
202
|
+
assert "Active library is empty" in new_stats
|
|
203
|
+
|
|
204
|
+
asyncio.run(run())
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@pytest.mark.timeout(30)
|
|
208
|
+
def test_error_surfacing_when_lens_unreachable(mcp_env, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
209
|
+
"""Point MCP at a port nothing is listening on; tools should surface a
|
|
210
|
+
friendly 'not reachable' message, not raise."""
|
|
211
|
+
import importlib
|
|
212
|
+
|
|
213
|
+
import getbased_mcp
|
|
214
|
+
|
|
215
|
+
monkeypatch.setenv("LENS_URL", "http://127.0.0.1:1") # port 1 is privileged + closed
|
|
216
|
+
importlib.reload(getbased_mcp)
|
|
217
|
+
|
|
218
|
+
async def run() -> None:
|
|
219
|
+
out = await getbased_mcp.knowledge_search(query="anything")
|
|
220
|
+
assert "Knowledge search error" in out
|
|
221
|
+
assert "not reachable" in out
|
|
222
|
+
|
|
223
|
+
asyncio.run(run())
|