hebbianvault-mcp 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,27 @@
1
+ # Node
2
+ node_modules/
3
+ dist/
4
+ *.js.map
5
+ *.d.ts.map
6
+
7
+ # Python
8
+ __pycache__/
9
+ *.py[cod]
10
+ *.egg-info/
11
+ .eggs/
12
+ dist/
13
+ build/
14
+ *.dist-info/
15
+ .venv/
16
+ .uv/
17
+
18
+ # Test / coverage artifacts
19
+ .coverage
20
+ htmlcov/
21
+ .pytest_cache/
22
+ .mypy_cache/
23
+ .ruff_cache/
24
+
25
+ # IDE
26
+ .vscode/
27
+ .idea/
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: hebbianvault-mcp
3
+ Version: 0.2.0
4
+ Summary: Customer-installable MCP server for the Hebbian tenant brain (Python). Same 8 tools as the Node.js sibling @hebbianvault/mcp.
5
+ Author-email: Hebbian <hello@hebbian.ai>
6
+ License: Apache-2.0
7
+ Keywords: ai-tools,hebbian,knowledge-graph,mcp,model-context-protocol
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: httpx>=0.27.0
10
+ Requires-Dist: mcp>=1.0.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ # hebbianvault-mcp (Python)
14
+
15
+ Python sibling of `@hebbianvault/mcp`. Connect a Python MCP host to your Hebbian workspace — same 8 tools, same configuration. A thin client; all intelligence and access control live in the Hebbian service.
16
+
17
+ ## Quick start
18
+
19
+ ```bash
20
+ pip install hebbianvault-mcp
21
+ # or with uv (preferred)
22
+ uv add hebbianvault-mcp
23
+ ```
24
+
25
+ Generate a token from your Hebbian integrations page (AI Tools tab → Generate token).
26
+
27
+ ## Configuration
28
+
29
+ Set `HEBBIAN_API_TOKEN` (or `HEBBIAN_TOKEN`) env var:
30
+
31
+ ```bash
32
+ HEBBIAN_API_TOKEN=your_token_here hebbian-mcp
33
+ ```
34
+
35
+ Or write `~/.config/hebbian/mcp-tenant.json`:
36
+
37
+ ```json
38
+ {
39
+ "token": "your_token_here"
40
+ }
41
+ ```
42
+
43
+ | Variable | Purpose |
44
+ |---|---|
45
+ | `HEBBIAN_API_TOKEN` | Your token (required). `HEBBIAN_TOKEN` is also accepted. |
46
+ | `HEBBIAN_API_URL` | Override the API base URL (Enterprise self-host). Defaults to the Hebbian SaaS API. |
47
+ | `HEBBIAN_TENANT` | Optional workspace slug — only needed if your account belongs to more than one workspace. |
48
+
49
+ ## Generic agent config
50
+
51
+ ```python
52
+ import os
53
+ os.environ["HEBBIAN_API_TOKEN"] = "your_token_here"
54
+ from hebbianvault_mcp import create_server
55
+ server = create_server()
56
+ # server.run() to start serving over stdio
57
+ ```
58
+
59
+ ## Token scope
60
+
61
+ Your token decides what the adapter can see and do — a personal-workspace token gives access to your own knowledge; a company-workspace token gives access to the shared company workspace (where your role allows). Scope is decided by the Hebbian service.
62
+
63
+ ## Tools
64
+
65
+ | Tool | What it does |
66
+ |---|---|
67
+ | `hebbian_read_node` | Read a single node by UUID |
68
+ | `hebbian_search` | Find nodes in your workspace matching a query |
69
+ | `hebbian_ask` | Ask a question and get an answer backed by source quotes |
70
+ | `hebbian_capture` | Write a note into your workspace |
71
+ | `hebbian_traverse` | Explore nodes connected to a starting node |
72
+ | `hebbian_provenance` | See where a node's knowledge came from |
73
+ | `hebbian_salience` | See a node's recent activity over time |
74
+ | `hebbian_recent_activity` | Catch up on recent changes in your workspace |
75
+
76
+ Results only ever include what your token is allowed to see.
77
+
78
+ ## Development
79
+
80
+ ```bash
81
+ uv venv && uv pip install -e ".[dev]"
82
+ pytest
83
+ ruff check src/ tests/
84
+ ```
85
+
86
+ ## Node.js version
87
+
88
+ The canonical package is `@hebbianvault/mcp` on npm. This Python sibling has identical tools and configuration. Use whichever matches your MCP host environment.
89
+
90
+ ## License
91
+
92
+ Apache-2.0. See the root [LICENSE](../LICENSE) file.
@@ -0,0 +1,80 @@
1
+ # hebbianvault-mcp (Python)
2
+
3
+ Python sibling of `@hebbianvault/mcp`. Connect a Python MCP host to your Hebbian workspace — same 8 tools, same configuration. A thin client; all intelligence and access control live in the Hebbian service.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ pip install hebbianvault-mcp
9
+ # or with uv (preferred)
10
+ uv add hebbianvault-mcp
11
+ ```
12
+
13
+ Generate a token from your Hebbian integrations page (AI Tools tab → Generate token).
14
+
15
+ ## Configuration
16
+
17
+ Set `HEBBIAN_API_TOKEN` (or `HEBBIAN_TOKEN`) env var:
18
+
19
+ ```bash
20
+ HEBBIAN_API_TOKEN=your_token_here hebbian-mcp
21
+ ```
22
+
23
+ Or write `~/.config/hebbian/mcp-tenant.json`:
24
+
25
+ ```json
26
+ {
27
+ "token": "your_token_here"
28
+ }
29
+ ```
30
+
31
+ | Variable | Purpose |
32
+ |---|---|
33
+ | `HEBBIAN_API_TOKEN` | Your token (required). `HEBBIAN_TOKEN` is also accepted. |
34
+ | `HEBBIAN_API_URL` | Override the API base URL (Enterprise self-host). Defaults to the Hebbian SaaS API. |
35
+ | `HEBBIAN_TENANT` | Optional workspace slug — only needed if your account belongs to more than one workspace. |
36
+
37
+ ## Generic agent config
38
+
39
+ ```python
40
+ import os
41
+ os.environ["HEBBIAN_API_TOKEN"] = "your_token_here"
42
+ from hebbianvault_mcp import create_server
43
+ server = create_server()
44
+ # server.run() to start serving over stdio
45
+ ```
46
+
47
+ ## Token scope
48
+
49
+ Your token decides what the adapter can see and do — a personal-workspace token gives access to your own knowledge; a company-workspace token gives access to the shared company workspace (where your role allows). Scope is decided by the Hebbian service.
50
+
51
+ ## Tools
52
+
53
+ | Tool | What it does |
54
+ |---|---|
55
+ | `hebbian_read_node` | Read a single node by UUID |
56
+ | `hebbian_search` | Find nodes in your workspace matching a query |
57
+ | `hebbian_ask` | Ask a question and get an answer backed by source quotes |
58
+ | `hebbian_capture` | Write a note into your workspace |
59
+ | `hebbian_traverse` | Explore nodes connected to a starting node |
60
+ | `hebbian_provenance` | See where a node's knowledge came from |
61
+ | `hebbian_salience` | See a node's recent activity over time |
62
+ | `hebbian_recent_activity` | Catch up on recent changes in your workspace |
63
+
64
+ Results only ever include what your token is allowed to see.
65
+
66
+ ## Development
67
+
68
+ ```bash
69
+ uv venv && uv pip install -e ".[dev]"
70
+ pytest
71
+ ruff check src/ tests/
72
+ ```
73
+
74
+ ## Node.js version
75
+
76
+ The canonical package is `@hebbianvault/mcp` on npm. This Python sibling has identical tools and configuration. Use whichever matches your MCP host environment.
77
+
78
+ ## License
79
+
80
+ Apache-2.0. See the root [LICENSE](../LICENSE) file.
@@ -0,0 +1,12 @@
1
+ """
2
+ conftest.py — ensure src/ is on sys.path for pytest.
3
+
4
+ Belt-and-braces for uv editable installs where the .pth file may not be
5
+ processed (Python 3.11+ + uv venv combinations). See coding-factory LEARNINGS
6
+ 2026-05-15.
7
+ """
8
+
9
+ import os
10
+ import sys
11
+
12
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "hebbianvault-mcp"
7
+ version = "0.2.0"
8
+ description = "Customer-installable MCP server for the Hebbian tenant brain (Python). Same 8 tools as the Node.js sibling @hebbianvault/mcp."
9
+ readme = "README.md"
10
+ license = { text = "Apache-2.0" }
11
+ requires-python = ">=3.11"
12
+ keywords = ["mcp", "hebbian", "knowledge-graph", "ai-tools", "model-context-protocol"]
13
+ authors = [
14
+ { name = "Hebbian", email = "hello@hebbian.ai" }
15
+ ]
16
+
17
+ dependencies = [
18
+ "mcp>=1.0.0",
19
+ "httpx>=0.27.0",
20
+ ]
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "pytest>=8.0",
25
+ "pytest-asyncio>=0.23",
26
+ "respx>=0.21",
27
+ "ruff>=0.5",
28
+ "pyright>=1.1",
29
+ ]
30
+
31
+ [project.scripts]
32
+ hebbian-mcp = "hebbianvault_mcp.server:main"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["src/hebbianvault_mcp"]
36
+
37
+ [tool.ruff]
38
+ target-version = "py311"
39
+ line-length = 100
40
+
41
+ [tool.ruff.lint]
42
+ select = ["E", "F", "I", "N", "W", "ANN", "S", "B", "UP"]
43
+ ignore = ["ANN101", "ANN102", "S101"]
44
+
45
+ [tool.ruff.lint.per-file-ignores]
46
+ "tests/**/*.py" = ["E501", "S105", "ANN401"]
47
+ "src/hebbianvault_mcp/client.py" = ["ANN401"]
48
+
49
+ [tool.pytest.ini_options]
50
+ asyncio_mode = "auto"
51
+ testpaths = ["tests"]
52
+
53
+ [tool.pyright]
54
+ pythonVersion = "3.11"
55
+ typeCheckingMode = "basic"
56
+ include = ["src", "tests"]
@@ -0,0 +1,19 @@
1
+ """
2
+ hebbianvault_mcp — Customer-installable MCP server for the Hebbian tenant brain.
3
+
4
+ One package, scope-by-token (Employee or Company). Install in any MCP-compatible
5
+ Python environment. Configure with a token issued from your Hebbian integrations page.
6
+
7
+ Usage:
8
+ $ hebbian-mcp
9
+
10
+ Or programmatically:
11
+ from hebbianvault_mcp import create_server
12
+ server = create_server()
13
+ server.run()
14
+ """
15
+
16
+ from .server import create_server, main
17
+
18
+ __all__ = ["create_server", "main"]
19
+ __version__ = "0.1.0"
@@ -0,0 +1,118 @@
1
+ """
2
+ hebbianvault_mcp.client — HTTPS client for the Hebbian API.
3
+
4
+ - Adds Authorization: Bearer header to every request.
5
+ - Surfaces API errors as HebbianApiError with status_code + error_code + message.
6
+ - No business logic — thin transport wrapper over httpx.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import logging
13
+ from typing import Any
14
+
15
+ import httpx
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ USER_AGENT = "hebbianvault-mcp/0.2.0 (Python)"
20
+
21
+
22
+ class HebbianApiError(Exception):
23
+ """Structured API error returned by the Hebbian API."""
24
+
25
+ def __init__(
26
+ self,
27
+ status_code: int,
28
+ error_code: str,
29
+ message: str,
30
+ detail: Any = None,
31
+ ) -> None:
32
+ super().__init__(message)
33
+ self.status_code = status_code
34
+ self.error_code = error_code
35
+ self.detail = detail
36
+
37
+ def to_tool_error(self) -> str:
38
+ """Human-readable string for MCP tool error responses."""
39
+ if self.status_code == 401:
40
+ return (
41
+ f"Authentication failed ({self.error_code}): {self}. "
42
+ "Your token may be expired or revoked. "
43
+ "Generate a new token from the AI Tools tab in your Hebbian integrations page."
44
+ )
45
+ if self.status_code == 403:
46
+ return (
47
+ f"Permission denied ({self.error_code}): {self}. "
48
+ "Check that your token scope (employee/company) matches the operation."
49
+ )
50
+ if self.status_code == 404:
51
+ return f"Not found ({self.error_code}): {self}"
52
+ if self.status_code == 429:
53
+ return f"Rate limit exceeded ({self.error_code}): {self}. Slow down and retry."
54
+ return f"API error {self.status_code} ({self.error_code}): {self}"
55
+
56
+
57
+ class HebbianClient:
58
+ """Thin async HTTP client for the Hebbian API."""
59
+
60
+ def __init__(self, api_url: str, token: str, tenant: str | None = None) -> None:
61
+ # Normalise: strip trailing slash
62
+ self._api_url = api_url.rstrip("/")
63
+ self._headers = {
64
+ "Authorization": f"Bearer {token}",
65
+ "Accept": "application/json",
66
+ "User-Agent": USER_AGENT,
67
+ }
68
+ # Only sent when the account belongs to more than one workspace; the API
69
+ # resolves the single-membership case from the token alone.
70
+ if tenant and tenant.strip():
71
+ self._headers["X-Hebbian-Tenant"] = tenant.strip()
72
+
73
+ async def get(
74
+ self,
75
+ path: str,
76
+ params: dict[str, Any] | None = None,
77
+ ) -> Any:
78
+ """Execute a GET request and return parsed JSON."""
79
+ url = f"{self._api_url}{path}"
80
+ async with httpx.AsyncClient() as http:
81
+ response = await http.get(url, headers=self._headers, params=params)
82
+ return await self._handle_response(response)
83
+
84
+ async def post(self, path: str, body: dict[str, Any]) -> Any:
85
+ """Execute a POST request and return parsed JSON."""
86
+ url = f"{self._api_url}{path}"
87
+ async with httpx.AsyncClient() as http:
88
+ response = await http.post(
89
+ url,
90
+ headers={**self._headers, "Content-Type": "application/json"},
91
+ content=json.dumps(body).encode(),
92
+ )
93
+ return await self._handle_response(response)
94
+
95
+ @staticmethod
96
+ async def _handle_response(response: httpx.Response) -> Any:
97
+ """Parse and validate a response. Raises HebbianApiError on non-2xx."""
98
+ if response.is_success:
99
+ return response.json()
100
+
101
+ # Try to parse structured error body
102
+ error_code = f"HTTP_{response.status_code}"
103
+ message = response.reason_phrase or f"Status {response.status_code}"
104
+ detail = None
105
+ try:
106
+ body = response.json()
107
+ error_code = body.get("code") or body.get("error") or error_code
108
+ message = body.get("message") or body.get("error") or message
109
+ detail = body.get("detail")
110
+ except Exception: # noqa: BLE001
111
+ message = response.text or message
112
+
113
+ raise HebbianApiError(
114
+ status_code=response.status_code,
115
+ error_code=error_code,
116
+ message=message,
117
+ detail=detail,
118
+ )
@@ -0,0 +1,106 @@
1
+ """
2
+ hebbianvault_mcp.config — Configuration loading.
3
+
4
+ Auth: bearer token from HEBBIAN_API_TOKEN env var (preferred) or
5
+ HEBBIAN_TOKEN (alternative) or config-file JSON path.
6
+ URL: HEBBIAN_API_URL env var (default: the Hebbian SaaS API).
7
+ Tenant: HEBBIAN_TENANT (optional) — only needed when your account belongs to
8
+ more than one Hebbian workspace.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import logging
15
+ import os
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Hebbian SaaS API. Enterprise/self-host customers override with HEBBIAN_API_URL.
22
+ DEFAULT_API_URL = "https://api.hebbianvault.com"
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class HebbianConfig:
27
+ """Resolved configuration for the MCP server."""
28
+
29
+ api_url: str
30
+ """Base URL of the Hebbian API. Configurable for Enterprise self-host."""
31
+
32
+ token: str
33
+ """Bearer token for authentication. Never log this value."""
34
+
35
+ tenant: str | None = None
36
+ """Optional workspace slug; sent as X-Hebbian-Tenant when set."""
37
+
38
+
39
+ def load_config() -> HebbianConfig:
40
+ """
41
+ Load configuration from environment variables and optional config file.
42
+
43
+ Priority (highest first):
44
+ 1. HEBBIAN_API_TOKEN / HEBBIAN_TOKEN env vars
45
+ 2. HEBBIAN_CONFIG_PATH — path to a JSON config file
46
+ 3. ~/.config/hebbian/mcp-tenant.json
47
+
48
+ Raises:
49
+ RuntimeError: if no token can be resolved.
50
+ """
51
+ api_url = os.environ.get("HEBBIAN_API_URL", DEFAULT_API_URL)
52
+ env_tenant = (os.environ.get("HEBBIAN_TENANT") or "").strip() or None
53
+
54
+ # ── Token from env ─────────────────────────────────────────────────────────
55
+ env_token = os.environ.get("HEBBIAN_API_TOKEN") or os.environ.get("HEBBIAN_TOKEN")
56
+ if env_token:
57
+ return HebbianConfig(api_url=api_url, token=env_token, tenant=env_tenant)
58
+
59
+ # ── Token from config file ─────────────────────────────────────────────────
60
+ config_path = _resolve_config_path()
61
+ if config_path:
62
+ file_cfg = _read_config_file(config_path)
63
+ if file_cfg.get("token"):
64
+ return HebbianConfig(
65
+ api_url=file_cfg.get("api_url", api_url),
66
+ token=file_cfg["token"],
67
+ tenant=env_tenant or file_cfg.get("tenant"),
68
+ )
69
+
70
+ raise RuntimeError(
71
+ "Hebbian MCP: No API token found. "
72
+ "Set HEBBIAN_API_TOKEN env var or HEBBIAN_TOKEN env var, "
73
+ "or write your token to ~/.config/hebbian/mcp-tenant.json as "
74
+ '{"token": "..."}. '
75
+ "Generate a token from the AI Tools tab in your Hebbian integrations page."
76
+ )
77
+
78
+
79
+ def _resolve_config_path() -> Path | None:
80
+ """Resolve config file path — explicit env var or XDG default."""
81
+ explicit = os.environ.get("HEBBIAN_CONFIG_PATH")
82
+ if explicit:
83
+ p = Path(explicit).expanduser().resolve()
84
+ if p.exists():
85
+ return p
86
+ logger.warning(
87
+ "HEBBIAN_CONFIG_PATH set to %r but file not found.", explicit
88
+ )
89
+ return None
90
+
91
+ home = Path.home()
92
+ default = home / ".config" / "hebbian" / "mcp-tenant.json"
93
+ return default if default.exists() else None
94
+
95
+
96
+ def _read_config_file(path: Path) -> dict[str, str]:
97
+ """Read and parse a JSON config file; tolerates missing fields."""
98
+ try:
99
+ raw = path.read_text(encoding="utf-8")
100
+ parsed = json.loads(raw)
101
+ if not isinstance(parsed, dict):
102
+ raise ValueError("Config file must be a JSON object")
103
+ return {k: str(v) for k, v in parsed.items() if isinstance(v, str)}
104
+ except Exception as exc: # noqa: BLE001
105
+ logger.warning("Could not read config file at %r: %s", str(path), exc)
106
+ return {}
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ hebbianvault_mcp.server — MCP server entry point.
4
+
5
+ Boots the Hebbian tenant MCP server over stdio transport.
6
+
7
+ Usage:
8
+ python -m hebbianvault_mcp.server
9
+ hebbian-mcp # via pyproject.toml [project.scripts]
10
+
11
+ Auth:
12
+ HEBBIAN_API_TOKEN env var (or HEBBIAN_TOKEN)
13
+ or ~/.config/hebbian/mcp-tenant.json with {"token": "hbn_..."}
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import logging
20
+ import sys
21
+ from typing import Any
22
+
23
+ from mcp.server import Server
24
+ from mcp.server.stdio import stdio_server
25
+ from mcp.types import TextContent, Tool
26
+
27
+ from .client import HebbianClient
28
+ from .config import load_config
29
+ from .tools import TOOL_HANDLERS, TOOL_SCHEMAS
30
+
31
+ logging.basicConfig(
32
+ level=logging.WARNING,
33
+ stream=sys.stderr,
34
+ format="%(asctime)s [%(name)s] %(levelname)s %(message)s",
35
+ )
36
+ logger = logging.getLogger("hebbianvault_mcp")
37
+
38
+ SERVER_NAME = "hebbianvault-mcp"
39
+ SERVER_VERSION = "0.1.0"
40
+
41
+
42
+ def create_server() -> Server:
43
+ """
44
+ Create and configure the Hebbian MCP server.
45
+
46
+ Call server.run() or use as an async context to start serving.
47
+ """
48
+ config = load_config()
49
+ client = HebbianClient(api_url=config.api_url, token=config.token, tenant=config.tenant)
50
+
51
+ app = Server(SERVER_NAME)
52
+
53
+ @app.list_tools()
54
+ async def list_tools() -> list[Tool]: # type: ignore[override]
55
+ return [
56
+ Tool(
57
+ name=schema["name"],
58
+ description=schema["description"],
59
+ inputSchema=schema["inputSchema"],
60
+ )
61
+ for schema in TOOL_SCHEMAS
62
+ ]
63
+
64
+ @app.call_tool()
65
+ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]: # type: ignore[override]
66
+ handler = TOOL_HANDLERS.get(name)
67
+ if handler is None:
68
+ return [
69
+ TextContent(
70
+ type="text",
71
+ text=f"Unknown tool: {name}. Available tools: {', '.join(TOOL_HANDLERS)}",
72
+ )
73
+ ]
74
+ try:
75
+ result = await handler(client, arguments or {})
76
+ return [TextContent(type="text", text=result)]
77
+ except Exception as exc: # noqa: BLE001
78
+ logger.error("Tool %r raised: %s", name, exc)
79
+ return [TextContent(type="text", text=f"Error: {exc}")]
80
+
81
+ logger.info(
82
+ "%s@%s started. API: %s",
83
+ SERVER_NAME,
84
+ SERVER_VERSION,
85
+ config.api_url,
86
+ )
87
+ return app
88
+
89
+
90
+ def main() -> None:
91
+ """Entry point for `hebbian-mcp` CLI command."""
92
+ server = create_server()
93
+
94
+ async def _run() -> None:
95
+ async with stdio_server() as (read_stream, write_stream):
96
+ await server.run(
97
+ read_stream,
98
+ write_stream,
99
+ server.create_initialization_options(),
100
+ )
101
+
102
+ try:
103
+ asyncio.run(_run())
104
+ except KeyboardInterrupt:
105
+ pass
106
+
107
+
108
+ if __name__ == "__main__":
109
+ main()