contextstore-sdk 0.1.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,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: contextstore-sdk
3
+ Version: 0.1.0
4
+ Summary: Client SDK for the ContextStore company brain — compiled memory and agent team, accessed via a typed Python API.
5
+ Author: ContextStore
6
+ License: MIT
7
+ Project-URL: Homepage, https://contextstore.oritm.tech
8
+ Keywords: memory,mcp,second-brain,agents,knowledge-base
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Software Development :: Libraries
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+
15
+ # ContextStore Python SDK
16
+
17
+ Talk to your ContextStore **company brain** — compiled memory + agent team — from
18
+ Python. The SDK is a typed client over your backend's MCP JSON-RPC layer.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install contextstore-sdk
24
+ ```
25
+
26
+ ## Quickstart
27
+
28
+ ```python
29
+ import os
30
+ from contextstore import ContextStore
31
+
32
+ client = ContextStore(api_key=os.environ["CONTEXTSTORE_KEY"])
33
+
34
+ # Remember something
35
+ client.memory.add(
36
+ content="Board approved usage-based pricing on Sep 12",
37
+ metadata={"project": "pricing", "owner": "maya"},
38
+ )
39
+
40
+ # Ask the company brain — with sources
41
+ res = client.memory.query("What did we decide about pricing?")
42
+ print(res.answer)
43
+ print(res.sources)
44
+ ```
45
+
46
+ ## Local / self-hosted
47
+
48
+ Point at your own backend (default is the hosted one):
49
+
50
+ ```python
51
+ client = ContextStore(
52
+ api_key=os.environ["CONTEXTSTORE_KEY"],
53
+ base_url="https://contextstore.onrender.com",
54
+ )
55
+ ```
56
+
57
+ For local development you can pass a JWT instead of an api_key:
58
+
59
+ ```python
60
+ client = ContextStore(token="<jwt>")
61
+ ```
62
+
63
+ ## Namespaces / methods
64
+
65
+ | Python | Backend MCP tool |
66
+ |---|---|
67
+ | `client.memory.add(...)` | `memory_store` |
68
+ | `client.memory.query(...)` | `memory_recall` |
69
+ | `client.memory.log_turn(...)` | `log_conversation_turn` |
70
+ | `client.context.snapshot()` | `get_context_snapshot` |
71
+ | `client.context.checkin(...)` | `smart_checkin` |
72
+ | `client.company.learn(...)` | `company_learn` |
73
+ | `client.company.map()` / `.set_map(...)` | `company_map` |
74
+ | `client.company.review(run=True)` | `company_review` |
75
+ | `client.company.approve(...)` / `.reject(...)` | `review_propagation` |
76
+ | `client.company.pending(...)` | `list_propagation_queue` |
77
+ | `client.permissions.grant/revoke/list(...)` | permission admin tools |
78
+ | `client.permissions.check_authority(...)` | `check_action_authority` |
79
+
80
+ ## Errors
81
+
82
+ - `AuthenticationError` — bad/expired/scoped-out API key
83
+ - `ApiError` — the MCP tool returned an error
84
+ - `ContextStoreError` — network / transport issues
85
+
86
+ No external dependencies — uses only the Python standard library.
@@ -0,0 +1,72 @@
1
+ # ContextStore Python SDK
2
+
3
+ Talk to your ContextStore **company brain** — compiled memory + agent team — from
4
+ Python. The SDK is a typed client over your backend's MCP JSON-RPC layer.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pip install contextstore-sdk
10
+ ```
11
+
12
+ ## Quickstart
13
+
14
+ ```python
15
+ import os
16
+ from contextstore import ContextStore
17
+
18
+ client = ContextStore(api_key=os.environ["CONTEXTSTORE_KEY"])
19
+
20
+ # Remember something
21
+ client.memory.add(
22
+ content="Board approved usage-based pricing on Sep 12",
23
+ metadata={"project": "pricing", "owner": "maya"},
24
+ )
25
+
26
+ # Ask the company brain — with sources
27
+ res = client.memory.query("What did we decide about pricing?")
28
+ print(res.answer)
29
+ print(res.sources)
30
+ ```
31
+
32
+ ## Local / self-hosted
33
+
34
+ Point at your own backend (default is the hosted one):
35
+
36
+ ```python
37
+ client = ContextStore(
38
+ api_key=os.environ["CONTEXTSTORE_KEY"],
39
+ base_url="https://contextstore.onrender.com",
40
+ )
41
+ ```
42
+
43
+ For local development you can pass a JWT instead of an api_key:
44
+
45
+ ```python
46
+ client = ContextStore(token="<jwt>")
47
+ ```
48
+
49
+ ## Namespaces / methods
50
+
51
+ | Python | Backend MCP tool |
52
+ |---|---|
53
+ | `client.memory.add(...)` | `memory_store` |
54
+ | `client.memory.query(...)` | `memory_recall` |
55
+ | `client.memory.log_turn(...)` | `log_conversation_turn` |
56
+ | `client.context.snapshot()` | `get_context_snapshot` |
57
+ | `client.context.checkin(...)` | `smart_checkin` |
58
+ | `client.company.learn(...)` | `company_learn` |
59
+ | `client.company.map()` / `.set_map(...)` | `company_map` |
60
+ | `client.company.review(run=True)` | `company_review` |
61
+ | `client.company.approve(...)` / `.reject(...)` | `review_propagation` |
62
+ | `client.company.pending(...)` | `list_propagation_queue` |
63
+ | `client.permissions.grant/revoke/list(...)` | permission admin tools |
64
+ | `client.permissions.check_authority(...)` | `check_action_authority` |
65
+
66
+ ## Errors
67
+
68
+ - `AuthenticationError` — bad/expired/scoped-out API key
69
+ - `ApiError` — the MCP tool returned an error
70
+ - `ContextStoreError` — network / transport issues
71
+
72
+ No external dependencies — uses only the Python standard library.
@@ -0,0 +1,26 @@
1
+ """ContextStore — Python SDK for the ContextStore company brain.
2
+
3
+ Talk to your compiled memory and agent team over a clean, typed API.
4
+ See the ContextStore class for usage.
5
+ """
6
+
7
+ from .client import ContextStore
8
+ from ._transport import (
9
+ ApiError,
10
+ AuthenticationError,
11
+ ContextStoreError,
12
+ )
13
+ from ._models import ActionResult, QueryResult, SnapshotResult, Source
14
+
15
+ __all__ = [
16
+ "ContextStore",
17
+ "ActionResult",
18
+ "QueryResult",
19
+ "SnapshotResult",
20
+ "Source",
21
+ "ContextStoreError",
22
+ "AuthenticationError",
23
+ "ApiError",
24
+ ]
25
+
26
+ __version__ = "0.1.0"
@@ -0,0 +1,117 @@
1
+ """ContextStore SDK — typed result models for MCP tool responses."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass, field
7
+ from typing import Any, Dict, List, Optional
8
+
9
+
10
+ def _load_text(result: Dict[str, Any]) -> Dict[str, Any]:
11
+ """Extract the JSON payload the server returns inside content[].text."""
12
+ for block in result.get("content", []):
13
+ if isinstance(block, dict) and block.get("type") == "text":
14
+ text = str(block.get("text", ""))
15
+ try:
16
+ parsed = json.loads(text)
17
+ if isinstance(parsed, dict):
18
+ return parsed
19
+ except (ValueError, TypeError):
20
+ return {"text": text}
21
+ return {}
22
+
23
+
24
+ @dataclass
25
+ class Source:
26
+ """A single source returned by the memory recall."""
27
+
28
+ text: str
29
+ source: Optional[str] = None
30
+ memory_type: Optional[str] = None
31
+ confidence: Optional[float] = None
32
+ extra: Dict[str, Any] = field(default_factory=dict)
33
+
34
+
35
+ @dataclass
36
+ class QueryResult:
37
+ """Typed result of `client.memory.query(...)`."""
38
+
39
+ answer: str = ""
40
+ sources: List[Source] = field(default_factory=list)
41
+ raw: Dict[str, Any] = field(default_factory=dict)
42
+
43
+ @classmethod
44
+ def from_result(cls, result: Dict[str, Any]) -> "QueryResult":
45
+ data = _load_text(result)
46
+ answer = data.get("answer") or data.get("summary") or _raw_text(result)
47
+ if isinstance(answer, list):
48
+ answer = "\n".join(str(a) for a in answer)
49
+ sources_raw = data.get("sources") or data.get("results") or []
50
+ sources: List[Source] = []
51
+ if isinstance(sources_raw, list):
52
+ for s in sources_raw:
53
+ if isinstance(s, dict):
54
+ sources.append(
55
+ Source(
56
+ text=str(s.get("content") or s.get("text") or s.get("summary") or s),
57
+ source=s.get("source") or s.get("memory_type") or None,
58
+ memory_type=s.get("memory_type") or s.get("type") or None,
59
+ confidence=s.get("confidence") or s.get("score") or None,
60
+ extra=s,
61
+ )
62
+ )
63
+ else:
64
+ sources.append(Source(text=str(s)))
65
+ elif isinstance(sources_raw, dict):
66
+ for key, val in sources_raw.items():
67
+ sources.append(
68
+ Source(
69
+ text=str(val.get("content") or val if isinstance(val, dict) else val),
70
+ source=key,
71
+ extra=val if isinstance(val, dict) else {},
72
+ )
73
+ )
74
+ return cls(answer=str(answer), sources=sources, raw=data)
75
+
76
+
77
+ @dataclass
78
+ class ActionResult:
79
+ """Generic result of a tool that returns a JSON-ish payload."""
80
+
81
+ data: Dict[str, Any] = field(default_factory=dict)
82
+ text: str = ""
83
+
84
+ @classmethod
85
+ def from_result(cls, result: Dict[str, Any]) -> "ActionResult":
86
+ return cls(data=_load_text(result), text=_raw_text(result))
87
+
88
+
89
+ def _raw_text(result: Dict[str, Any]) -> str:
90
+ parts = []
91
+ for block in result.get("content", []):
92
+ if isinstance(block, dict) and block.get("type") == "text":
93
+ parts.append(str(block.get("text", "")))
94
+ return "\n".join(p for p in parts if p)
95
+
96
+
97
+ @dataclass
98
+ class SnapshotResult:
99
+ """Typed result of `client.snapshot()`."""
100
+
101
+ data: Dict[str, Any] = field(default_factory=dict)
102
+ matched_playbooks: List[str] = field(default_factory=list)
103
+ has_profile: bool = False
104
+ text: str = ""
105
+
106
+ @classmethod
107
+ def from_result(cls, result: Dict[str, Any]) -> "SnapshotResult":
108
+ data = _load_text(result)
109
+ playbooks = data.get("matched_playbooks") or []
110
+ if isinstance(playbooks, str):
111
+ playbooks = [playbooks]
112
+ return cls(
113
+ data=data,
114
+ matched_playbooks=list(playbooks),
115
+ has_profile=bool(data.get("has_profile") or data.get("user_profile")),
116
+ text=_raw_text(result),
117
+ )
@@ -0,0 +1,150 @@
1
+ """ContextStore SDK — MCP JSON-RPC transport over Streamable HTTP."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time
7
+ import urllib.error
8
+ import urllib.request
9
+ from typing import Any, Dict, Optional
10
+
11
+ DEFAULT_BASE_URL = "https://contextstore.onrender.com"
12
+ DEFAULT_MCP_PATH = "/mcp"
13
+ DEFAULT_TIMEOUT = 60.0
14
+
15
+
16
+ class ContextStoreError(Exception):
17
+ """Base error for ContextStore SDK."""
18
+
19
+
20
+ class AuthenticationError(ContextStoreError):
21
+ """Raised when the API key / token is invalid, expired, or lacks scope."""
22
+
23
+
24
+ class ApiError(ContextStoreError):
25
+ """Raised when the MCP call returns an error or an isError result."""
26
+
27
+
28
+ class Transport:
29
+ """Minimal MCP Streamable HTTP (JSON-RPC) client with no external deps."""
30
+
31
+ def __init__(
32
+ self,
33
+ *,
34
+ api_key: Optional[str] = None,
35
+ token: Optional[str] = None,
36
+ base_url: str = DEFAULT_BASE_URL,
37
+ timeout: float = DEFAULT_TIMEOUT,
38
+ ) -> None:
39
+ if not api_key and not token:
40
+ raise ValueError("Either api_key or token (JWT) must be provided")
41
+ self._api_key = api_key
42
+ self._token = token
43
+ self._base_url = base_url.rstrip("/")
44
+ self._timeout = timeout
45
+ self._headers_cache: Optional[Dict[str, str]] = None
46
+
47
+ def _headers(self) -> Dict[str, str]:
48
+ headers = {
49
+ "Content-Type": "application/json",
50
+ "Accept": "application/json, text/event-stream",
51
+ }
52
+ if self._api_key:
53
+ headers["Authorization"] = f"Bearer {self._api_key}"
54
+ elif self._token:
55
+ headers["Authorization"] = f"Bearer {self._token}"
56
+ return headers
57
+
58
+ def call_tool(self, name: str, arguments: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
59
+ """Send a JSON-RPC tools/call and return the result payload (dict)."""
60
+ request_id = int(time.time() * 1000) % (2 ** 31)
61
+ payload = {
62
+ "jsonrpc": "2.0",
63
+ "id": request_id,
64
+ "method": "tools/call",
65
+ "params": {"name": name, "arguments": arguments or {}},
66
+ }
67
+ url = f"{self._base_url}{DEFAULT_MCP_PATH}"
68
+ data = json.dumps(payload).encode("utf-8")
69
+ req = urllib.request.Request(url, data=data, headers=self._headers(), method="POST")
70
+ try:
71
+ with urllib.request.urlopen(req, timeout=self._timeout) as resp:
72
+ raw = resp.read().decode("utf-8")
73
+ except urllib.error.HTTPError as e:
74
+ body = ""
75
+ try:
76
+ body = e.read().decode("utf-8")
77
+ except Exception:
78
+ pass
79
+ if e.code in (401, 403):
80
+ raise AuthenticationError(
81
+ f"Authentication failed ({e.code}): {body or e.reason}"
82
+ )
83
+ raise ApiError(f"HTTP {e.code}: {body or e.reason}")
84
+ except urllib.error.URLError as e:
85
+ raise ContextStoreError(f"Network error: {e.reason}")
86
+
87
+ # The response may be either a single JSON object or an SSE stream.
88
+ obj = self._parse_response(raw)
89
+ if "error" in obj:
90
+ err = obj["error"]
91
+ raise ApiError(str(err.get("message", err)))
92
+ result = obj.get("result", {})
93
+ if result.get("isError"):
94
+ text = self._text_of(result)
95
+ raise ApiError(text or "MCP tool returned an error")
96
+ return result
97
+
98
+ def list_tools(self) -> list:
99
+ payload = {
100
+ "jsonrpc": "2.0",
101
+ "id": int(time.time() * 1000) % (2 ** 31),
102
+ "method": "tools/list",
103
+ "params": {},
104
+ }
105
+ url = f"{self._base_url}{DEFAULT_MCP_PATH}"
106
+ req = urllib.request.Request(
107
+ url,
108
+ data=json.dumps(payload).encode("utf-8"),
109
+ headers=self._headers(),
110
+ method="POST",
111
+ )
112
+ try:
113
+ with urllib.request.urlopen(req, timeout=self._timeout) as resp:
114
+ raw = resp.read().decode("utf-8")
115
+ except urllib.error.HTTPError as e:
116
+ if e.code in (401, 403):
117
+ raise AuthenticationError(f"Authentication failed ({e.code})")
118
+ raise ApiError(f"HTTP {e.code}")
119
+ except urllib.error.URLError as e:
120
+ raise ContextStoreError(f"Network error: {e.reason}")
121
+ obj = self._parse_response(raw)
122
+ if "error" in obj:
123
+ raise ApiError(str(obj["error"]))
124
+ return obj.get("result", {}).get("tools", [])
125
+
126
+ @staticmethod
127
+ def _parse_response(raw: str) -> Dict[str, Any]:
128
+ raw = raw.strip()
129
+ if not raw:
130
+ raise ApiError("Empty response from server")
131
+ if raw.startswith("event:"):
132
+ # SSE stream: take the last data: line.
133
+ for line in raw.splitlines():
134
+ line = line.strip()
135
+ if line.startswith("data:") and line[5:].strip():
136
+ raw = line[5:].strip()
137
+ if not raw.startswith("{"):
138
+ raise ApiError(f"Unexpected response format: {raw[:200]}")
139
+ try:
140
+ return json.loads(raw)
141
+ except json.JSONDecodeError as e:
142
+ raise ApiError(f"Invalid JSON response: {e}")
143
+
144
+ @staticmethod
145
+ def _text_of(result: Dict[str, Any]) -> str:
146
+ texts = []
147
+ for block in result.get("content", []):
148
+ if isinstance(block, dict) and block.get("type") == "text":
149
+ texts.append(str(block.get("text", "")))
150
+ return "\n".join(t for t in texts if t)
@@ -0,0 +1,54 @@
1
+ """ContextStore SDK — the public client.
2
+
3
+ Usage:
4
+ import os
5
+ from contextstore import ContextStore
6
+
7
+ client = ContextStore(api_key=os.environ["CONTEXTSTORE_KEY"])
8
+
9
+ client.memory.add("Board approved usage-based pricing on Sep 12",
10
+ metadata={"project": "pricing", "owner": "maya"})
11
+ res = client.memory.query("What did we decide about pricing?")
12
+ print(res.answer, res.sources)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Optional
18
+
19
+ from ._transport import Transport
20
+ from .company import CompanyBrainNamespace
21
+ from .context import ContextNamespace
22
+ from .memory import MemoryNamespace
23
+ from .permissions import PermissionsNamespace
24
+
25
+ __all__ = ["ContextStore"]
26
+
27
+
28
+ class ContextStore:
29
+ """Root client. Points at the ContextStore backend and exposes namespaced,
30
+ typed access to the MCP tool catalog over JSON-RPC."""
31
+
32
+ def __init__(
33
+ self,
34
+ *,
35
+ api_key: Optional[str] = None,
36
+ token: Optional[str] = None,
37
+ base_url: str = "https://contextstore.onrender.com",
38
+ timeout: float = 60.0,
39
+ ) -> None:
40
+ transport = Transport(
41
+ api_key=api_key,
42
+ token=token,
43
+ base_url=base_url,
44
+ timeout=timeout,
45
+ )
46
+ self._transport = transport
47
+ self.memory = MemoryNamespace(transport)
48
+ self.company = CompanyBrainNamespace(transport)
49
+ self.context = ContextNamespace(transport)
50
+ self.permissions = PermissionsNamespace(transport)
51
+
52
+ def ping(self) -> bool:
53
+ """Quick connectivity/auth check: list the available MCP tools."""
54
+ return self._transport.list_tools() is not None
@@ -0,0 +1,75 @@
1
+ """ContextStore SDK — Company Brain namespace (learn, map, review, approve)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from ._models import ActionResult
8
+ from ._transport import Transport
9
+
10
+
11
+ class CompanyBrainNamespace:
12
+ """Access to the Company Brain: corrections, the live map, and the review
13
+ ritual that gates what propagates company-wide."""
14
+
15
+ def __init__(self, transport: Transport) -> None:
16
+ self._transport = transport
17
+
18
+ def learn(
19
+ self,
20
+ correction: str,
21
+ *,
22
+ correction_type: str = "fact",
23
+ reviewer: Optional[str] = None,
24
+ reason: Optional[str] = None,
25
+ playbook_name: Optional[str] = None,
26
+ when_to_use: Optional[str] = None,
27
+ ) -> ActionResult:
28
+ """Record a correction/lesson. It is stored as pending_review and must pass
29
+ human sign-off (via review()) before it spreads company-wide."""
30
+ arguments: Dict[str, Any] = {"correction": correction, "correction_type": correction_type}
31
+ if reviewer:
32
+ arguments["reviewer"] = reviewer
33
+ if reason:
34
+ arguments["reason"] = reason
35
+ if playbook_name:
36
+ arguments["playbook_name"] = playbook_name
37
+ if when_to_use:
38
+ arguments["when_to_use"] = when_to_use
39
+ result = self._transport.call_tool("company_learn", arguments)
40
+ return ActionResult.from_result(result)
41
+
42
+ def map(self, *, playbooks: bool = True) -> ActionResult:
43
+ """Read the current company map (front door: priorities, source order, nav)."""
44
+ result = self._transport.call_tool("company_map", {"playbooks": playbooks})
45
+ return ActionResult.from_result(result)
46
+
47
+ def set_map(self, map_text: str) -> ActionResult:
48
+ """Upsert a new company map. Map is stored as a tagged, recallable memory."""
49
+ result = self._transport.call_tool("company_map", {"set_map": True, "map_text": map_text})
50
+ return ActionResult.from_result(result)
51
+
52
+ def review(self, run: bool = True) -> ActionResult:
53
+ """List pending learnings awaiting human sign-off. run=True lists them."""
54
+ result = self._transport.call_tool("company_review", {"run": run})
55
+ return ActionResult.from_result(result)
56
+
57
+ def approve(self, queue_id: str) -> ActionResult:
58
+ """Approve a pending propagation request so it spreads company-wide."""
59
+ result = self._transport.call_tool("review_propagation", {"queue_id": queue_id, "action": "approve"})
60
+ return ActionResult.from_result(result)
61
+
62
+ def reject(self, queue_id: str) -> ActionResult:
63
+ """Reject a pending propagation request (stays restricted)."""
64
+ result = self._transport.call_tool("review_propagation", {"queue_id": queue_id, "action": "reject"})
65
+ return ActionResult.from_result(result)
66
+
67
+ def pending(self, org_id: Optional[str] = None) -> List[Dict[str, Any]]:
68
+ """List the raw pending propagation queue entries."""
69
+ arguments: Dict[str, Any] = {}
70
+ if org_id:
71
+ arguments["org_id"] = org_id
72
+ result = self._transport.call_tool("list_propagation_queue", arguments)
73
+ data = ActionResult.from_result(result).data
74
+ items = data.get("pending_requests") or []
75
+ return items if isinstance(items, list) else []
@@ -0,0 +1,28 @@
1
+ """ContextStore SDK — context namespace (snapshot, checkin)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+
7
+ from ._models import ActionResult, SnapshotResult
8
+ from ._transport import Transport
9
+
10
+
11
+ class ContextNamespace:
12
+ """Startup + session context (get_context_snapshot / smart_checkin)."""
13
+
14
+ def __init__(self, transport: Transport) -> None:
15
+ self._transport = transport
16
+
17
+ def snapshot(self) -> SnapshotResult:
18
+ """Load user profile, project conventions, and recent history. Call first."""
19
+ result = self._transport.call_tool("get_context_snapshot", {})
20
+ return SnapshotResult.from_result(result)
21
+
22
+ def checkin(self, user_intent: Optional[str] = None) -> ActionResult:
23
+ """Optional richer startup loader: active_thread, asks, suggestions."""
24
+ arguments: Dict[str, Any] = {}
25
+ if user_intent:
26
+ arguments["user_intent"] = user_intent
27
+ result = self._transport.call_tool("smart_checkin", arguments)
28
+ return ActionResult.from_result(result)
@@ -0,0 +1,86 @@
1
+ """ContextStore SDK — memory namespace (store, recall, log, list)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from ._models import ActionResult, QueryResult
8
+ from ._transport import Transport
9
+
10
+
11
+ class MemoryNamespace:
12
+ """Typesafe access to the brain's memory layer (memory_store / memory_recall)."""
13
+
14
+ def __init__(self, transport: Transport) -> None:
15
+ self._transport = transport
16
+
17
+ def add(
18
+ self,
19
+ content: str,
20
+ *,
21
+ memory_type: str = "general",
22
+ tags: Optional[List[str]] = None,
23
+ metadata: Optional[Dict[str, Any]] = None,
24
+ project: Optional[str] = None,
25
+ source: Optional[str] = "sdk",
26
+ team_id: Optional[str] = None,
27
+ confidence_score: Optional[float] = None,
28
+ ) -> ActionResult:
29
+ """Remember a durable fact. Maps to the server's memory_store tool.
30
+
31
+ `metadata` is a convenience map merged into `tags`/`source`; keys other
32
+ than the tool's known params are passed through as tags so nothing is lost.
33
+ """
34
+ arguments: Dict[str, Any] = {
35
+ "content": content,
36
+ "memory_type": memory_type,
37
+ "tags": tags or [],
38
+ "source": source or "sdk",
39
+ }
40
+ if project:
41
+ arguments["project"] = project
42
+ if team_id:
43
+ arguments["team_id"] = team_id
44
+ if confidence_score is not None:
45
+ arguments["confidence_score"] = confidence_score
46
+ if metadata:
47
+ for key, value in metadata.items():
48
+ if key in arguments:
49
+ arguments[key] = value
50
+ elif isinstance(value, str):
51
+ arguments.setdefault("tags", []).append(f"{key}:{value}")
52
+ result = self._transport.call_tool("memory_store", arguments)
53
+ return ActionResult.from_result(result)
54
+
55
+ def query(
56
+ self,
57
+ query: str,
58
+ *,
59
+ top_k: int = 5,
60
+ memory_type: Optional[str] = None,
61
+ project: Optional[str] = None,
62
+ user_intent: Optional[str] = None,
63
+ include_scratchpad: bool = False,
64
+ ) -> QueryResult:
65
+ """Ask the company brain a question; returns `answer` + `sources`."""
66
+ arguments: Dict[str, Any] = {"query": query, "top_k": top_k}
67
+ if memory_type:
68
+ arguments["memory_type"] = memory_type
69
+ if project:
70
+ arguments["project"] = project
71
+ if user_intent:
72
+ arguments["user_intent"] = user_intent
73
+ if include_scratchpad:
74
+ arguments["include_scratchpad"] = True
75
+ result = self._transport.call_tool("memory_recall", arguments)
76
+ return QueryResult.from_result(result)
77
+
78
+ def log_turn(self, role: str, content: str, *, thread_id: Optional[str] = None, thread_label: Optional[str] = None) -> ActionResult:
79
+ """Log a conversation turn (role='user' or 'assistant')."""
80
+ arguments: Dict[str, Any] = {"role": role, "content": content}
81
+ if thread_id:
82
+ arguments["thread_id"] = thread_id
83
+ if thread_label:
84
+ arguments["thread_label"] = thread_label
85
+ result = self._transport.call_tool("log_conversation_turn", arguments)
86
+ return ActionResult.from_result(result)
@@ -0,0 +1,47 @@
1
+ """ContextStore SDK — permissions & action-authority namespace (admin tools)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+
7
+ from ._models import ActionResult
8
+ from ._transport import Transport
9
+
10
+
11
+ class PermissionsNamespace:
12
+ """Admin controls: grant/revoke tool permissions and check action authority.
13
+ These require an API key with the 'admin' scope."""
14
+
15
+ def __init__(self, transport: Transport) -> None:
16
+ self._transport = transport
17
+
18
+ def grant(self, *, agent_id: str, tool_name: str, allowed: bool = True) -> ActionResult:
19
+ result = self._transport.call_tool(
20
+ "grant_permission",
21
+ {"agent_id": agent_id, "tool_name": tool_name, "allowed": allowed},
22
+ )
23
+ return ActionResult.from_result(result)
24
+
25
+ def revoke(self, *, agent_id: str, tool_name: str) -> ActionResult:
26
+ result = self._transport.call_tool(
27
+ "revoke_permission",
28
+ {"agent_id": agent_id, "tool_name": tool_name},
29
+ )
30
+ return ActionResult.from_result(result)
31
+
32
+ def list(self, agent_id: Optional[str] = None) -> ActionResult:
33
+ arguments: Dict[str, Any] = {}
34
+ if agent_id:
35
+ arguments["agent_id"] = agent_id
36
+ result = self._transport.call_tool("list_permissions", arguments)
37
+ return ActionResult.from_result(result)
38
+
39
+ def check_authority(self, memory_id: str, requested_action: str) -> ActionResult:
40
+ """Verify whether a memory's stored action_authority allows an action
41
+ (e.g. 'act:auto_email', 'act:deploy', 'act:refund'). Queues for human
42
+ sign-off if unauthorized."""
43
+ result = self._transport.call_tool(
44
+ "check_action_authority",
45
+ {"memory_id": memory_id, "requested_action": requested_action},
46
+ )
47
+ return ActionResult.from_result(result)
@@ -0,0 +1,2 @@
1
+ # Marker file for PEP 561: this package ships inline type hints.
2
+
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: contextstore-sdk
3
+ Version: 0.1.0
4
+ Summary: Client SDK for the ContextStore company brain — compiled memory and agent team, accessed via a typed Python API.
5
+ Author: ContextStore
6
+ License: MIT
7
+ Project-URL: Homepage, https://contextstore.oritm.tech
8
+ Keywords: memory,mcp,second-brain,agents,knowledge-base
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Software Development :: Libraries
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+
15
+ # ContextStore Python SDK
16
+
17
+ Talk to your ContextStore **company brain** — compiled memory + agent team — from
18
+ Python. The SDK is a typed client over your backend's MCP JSON-RPC layer.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install contextstore-sdk
24
+ ```
25
+
26
+ ## Quickstart
27
+
28
+ ```python
29
+ import os
30
+ from contextstore import ContextStore
31
+
32
+ client = ContextStore(api_key=os.environ["CONTEXTSTORE_KEY"])
33
+
34
+ # Remember something
35
+ client.memory.add(
36
+ content="Board approved usage-based pricing on Sep 12",
37
+ metadata={"project": "pricing", "owner": "maya"},
38
+ )
39
+
40
+ # Ask the company brain — with sources
41
+ res = client.memory.query("What did we decide about pricing?")
42
+ print(res.answer)
43
+ print(res.sources)
44
+ ```
45
+
46
+ ## Local / self-hosted
47
+
48
+ Point at your own backend (default is the hosted one):
49
+
50
+ ```python
51
+ client = ContextStore(
52
+ api_key=os.environ["CONTEXTSTORE_KEY"],
53
+ base_url="https://contextstore.onrender.com",
54
+ )
55
+ ```
56
+
57
+ For local development you can pass a JWT instead of an api_key:
58
+
59
+ ```python
60
+ client = ContextStore(token="<jwt>")
61
+ ```
62
+
63
+ ## Namespaces / methods
64
+
65
+ | Python | Backend MCP tool |
66
+ |---|---|
67
+ | `client.memory.add(...)` | `memory_store` |
68
+ | `client.memory.query(...)` | `memory_recall` |
69
+ | `client.memory.log_turn(...)` | `log_conversation_turn` |
70
+ | `client.context.snapshot()` | `get_context_snapshot` |
71
+ | `client.context.checkin(...)` | `smart_checkin` |
72
+ | `client.company.learn(...)` | `company_learn` |
73
+ | `client.company.map()` / `.set_map(...)` | `company_map` |
74
+ | `client.company.review(run=True)` | `company_review` |
75
+ | `client.company.approve(...)` / `.reject(...)` | `review_propagation` |
76
+ | `client.company.pending(...)` | `list_propagation_queue` |
77
+ | `client.permissions.grant/revoke/list(...)` | permission admin tools |
78
+ | `client.permissions.check_authority(...)` | `check_action_authority` |
79
+
80
+ ## Errors
81
+
82
+ - `AuthenticationError` — bad/expired/scoped-out API key
83
+ - `ApiError` — the MCP tool returned an error
84
+ - `ContextStoreError` — network / transport issues
85
+
86
+ No external dependencies — uses only the Python standard library.
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ contextstore/__init__.py
4
+ contextstore/_models.py
5
+ contextstore/_transport.py
6
+ contextstore/client.py
7
+ contextstore/company.py
8
+ contextstore/context.py
9
+ contextstore/memory.py
10
+ contextstore/permissions.py
11
+ contextstore/py.typed
12
+ contextstore_sdk.egg-info/PKG-INFO
13
+ contextstore_sdk.egg-info/SOURCES.txt
14
+ contextstore_sdk.egg-info/dependency_links.txt
15
+ contextstore_sdk.egg-info/top_level.txt
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "contextstore-sdk"
7
+ version = "0.1.0"
8
+ description = "Client SDK for the ContextStore company brain — compiled memory and agent team, accessed via a typed Python API."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "ContextStore" }]
13
+ keywords = ["memory", "mcp", "second-brain", "agents", "knowledge-base"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Operating System :: OS Independent",
17
+ "Topic :: Software Development :: Libraries",
18
+ ]
19
+
20
+ [project.urls]
21
+ Homepage = "https://contextstore.oritm.tech"
22
+
23
+ [tool.setuptools]
24
+ packages = ["contextstore"]
25
+
26
+ [tool.setuptools.package-data]
27
+ contextstore = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+