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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,2 @@
1
+ [console_scripts]
2
+ getbased-stack = getbased_agent_stack.cli:main
@@ -0,0 +1,11 @@
1
+ getbased-mcp>=0.2.2
2
+ getbased-rag>=0.6.1
3
+ getbased-dashboard>=0.5.0
4
+
5
+ [full]
6
+ getbased-rag[full]>=0.6.1
7
+
8
+ [test]
9
+ pytest>=8.0
10
+ httpx>=0.27
11
+ pytest-timeout>=2.3
@@ -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())