helio-client 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.
- helio_client-0.1.0/.gitignore +85 -0
- helio_client-0.1.0/AGENTS.md +134 -0
- helio_client-0.1.0/PKG-INFO +118 -0
- helio_client-0.1.0/README.md +102 -0
- helio_client-0.1.0/pyproject.toml +39 -0
- helio_client-0.1.0/src/helio/__init__.py +22 -0
- helio_client-0.1.0/src/helio/_version.py +24 -0
- helio_client-0.1.0/src/helio/client.py +271 -0
- helio_client-0.1.0/src/helio/context.py +135 -0
- helio_client-0.1.0/src/helio/py.typed +0 -0
- helio_client-0.1.0/src/helio/types.py +52 -0
- helio_client-0.1.0/tests/__init__.py +0 -0
- helio_client-0.1.0/tests/conftest.py +13 -0
- helio_client-0.1.0/tests/test_client.py +416 -0
- helio_client-0.1.0/tests/test_context.py +193 -0
- helio_client-0.1.0/tests/test_context_threading.py +137 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
node_modules
|
|
5
|
+
**/node_modules/
|
|
6
|
+
.pnpm-store/
|
|
7
|
+
/.pnp
|
|
8
|
+
.pnp.js
|
|
9
|
+
|
|
10
|
+
# testing
|
|
11
|
+
/coverage
|
|
12
|
+
coverage
|
|
13
|
+
|
|
14
|
+
# next.js
|
|
15
|
+
/.next/
|
|
16
|
+
/out/
|
|
17
|
+
|
|
18
|
+
# production
|
|
19
|
+
/build
|
|
20
|
+
dist/
|
|
21
|
+
dist
|
|
22
|
+
|
|
23
|
+
# misc
|
|
24
|
+
.DS_Store
|
|
25
|
+
*.pem
|
|
26
|
+
|
|
27
|
+
# debug
|
|
28
|
+
npm-debug.log*
|
|
29
|
+
yarn-debug.log*
|
|
30
|
+
yarn-error.log*
|
|
31
|
+
.pnpm-debug.log*
|
|
32
|
+
|
|
33
|
+
# local env files
|
|
34
|
+
.env*.local
|
|
35
|
+
.env
|
|
36
|
+
|
|
37
|
+
# vercel
|
|
38
|
+
.vercel
|
|
39
|
+
|
|
40
|
+
# typescript
|
|
41
|
+
*.tsbuildinfo
|
|
42
|
+
next-env.d.ts
|
|
43
|
+
|
|
44
|
+
# misc local untracked files
|
|
45
|
+
.local/
|
|
46
|
+
docs/.local/
|
|
47
|
+
# local Claude Code state, except the shared project config
|
|
48
|
+
.claude/*
|
|
49
|
+
!/.claude/settings.json
|
|
50
|
+
|
|
51
|
+
# generated benchmark report (machine-specific)
|
|
52
|
+
docs/benchmark-results.md
|
|
53
|
+
|
|
54
|
+
# runtime-generated audit databases
|
|
55
|
+
helio-audit.db
|
|
56
|
+
*.db
|
|
57
|
+
*.db-wal
|
|
58
|
+
*.db-shm
|
|
59
|
+
|
|
60
|
+
# python
|
|
61
|
+
packages/python-sdk/src/helio/_version.py
|
|
62
|
+
__pycache__/
|
|
63
|
+
*.py[cod]
|
|
64
|
+
*$py.class
|
|
65
|
+
*.egg
|
|
66
|
+
*.egg-info/
|
|
67
|
+
dist/
|
|
68
|
+
build/
|
|
69
|
+
.eggs/
|
|
70
|
+
.venv/
|
|
71
|
+
venv/
|
|
72
|
+
env/
|
|
73
|
+
.Python
|
|
74
|
+
pip-log.txt
|
|
75
|
+
pip-delete-this-directory.txt
|
|
76
|
+
.pytest_cache/
|
|
77
|
+
.mypy_cache/
|
|
78
|
+
.ruff_cache/
|
|
79
|
+
.hatch/
|
|
80
|
+
*.pyo
|
|
81
|
+
*.pyd
|
|
82
|
+
.python-version
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
.gstack/
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# helio (Python SDK)
|
|
2
|
+
|
|
3
|
+
Thin Python client for the Helio MCP governance proxy. Communicates with the proxy's sideband HTTP API to report evidence and context. The SDK NEVER makes governance decisions — that is always the proxy's job.
|
|
4
|
+
|
|
5
|
+
**Hard constraint: total source must stay under 500 lines** for hand-written SDK modules (`__init__.py`, `types.py`, `client.py`, `context.py`). Governance logic belongs in the proxy, not here.
|
|
6
|
+
|
|
7
|
+
## Package Layout
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/helio/
|
|
11
|
+
├── __init__.py → Public API surface (re-exports HelioClient, HelioContext, HelioError, types, __version__)
|
|
12
|
+
├── types.py → Frozen dataclasses: EvidenceEntry, CompletedTool, SessionState, EvidenceStateReport
|
|
13
|
+
├── client.py → HelioClient: low-level sync httpx client mapping to sideband API + HelioError
|
|
14
|
+
├── context.py → HelioContext: high-level wrapper with local requirement tracking (thread-safe)
|
|
15
|
+
├── _version.py → Auto-generated by hatch-vcs (NOT hand-written; excluded from the 500-line budget)
|
|
16
|
+
└── py.typed → PEP 561 marker
|
|
17
|
+
|
|
18
|
+
tests/
|
|
19
|
+
├── __init__.py → Marks the tests package
|
|
20
|
+
├── conftest.py → Shared fixtures
|
|
21
|
+
├── test_client.py → HelioClient unit tests (respx for HTTP mocking)
|
|
22
|
+
├── test_context.py → HelioContext unit tests (respx for HTTP mocking)
|
|
23
|
+
└── test_context_threading.py → Concurrency tests for require_evidence (the _lock invariant)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Key Classes
|
|
27
|
+
|
|
28
|
+
**`HelioClient`** — Low-level HTTP client wrapping the proxy sideband API. Constructor: `HelioClient(proxy_url="http://127.0.0.1:3200", session_id=None, *, timeout=5.0)`.
|
|
29
|
+
- `mark_evidence(tool_name, evidence_key, data, ttl=300)` → `POST /evidence`
|
|
30
|
+
- `set_context(key, value)` → `POST /context`
|
|
31
|
+
- `get_session_state()` → `GET /session/:session_id/state` → returns `SessionState`
|
|
32
|
+
- `close()` + context manager support (`with HelioClient(...) as client:`)
|
|
33
|
+
- `session_id` / `proxy_url` read-only properties
|
|
34
|
+
- Auto-generates session ID (`uuid.uuid4().hex`) when not provided
|
|
35
|
+
- **Reads `HELIO_SDK_TOKEN` from the environment at construction** and, if set, attaches `Authorization: Bearer <token>` to every request. The token is bound for the client's lifetime — rotate by restarting the process (matches the proxy's per-boot token model). An empty/unset value means no `Authorization` header (open-mode local dev).
|
|
36
|
+
- Transport/status failures are normalized into `HelioError` (`ConnectError` → "Cannot connect…", `TimeoutException` → "…timed out", `HTTPStatusError` → "…failed: HTTP <code>" with `status_code`); `POST /evidence` errors additionally surface the proxy's structured `code`/`key`/`allowed_keys` details.
|
|
37
|
+
|
|
38
|
+
**`HelioError`** — Base exception for all SDK errors; carries an optional `status_code`.
|
|
39
|
+
|
|
40
|
+
**`HelioContext`** — Primary public API wrapping `HelioClient`. Same constructor signature as `HelioClient`.
|
|
41
|
+
- `mark_evidence(tool_name, evidence_key, data, ttl=300)` — mark tool output as evidence (delegates to the client)
|
|
42
|
+
- `require_evidence(keys)` — declare local requirements (accepts a `str` or `list[str]`; informational only, never raises)
|
|
43
|
+
- `set(key, value)` — set session context
|
|
44
|
+
- `get_evidence_state()` → `EvidenceStateReport` with satisfied/missing comparison
|
|
45
|
+
- `client` property (access the underlying `HelioClient`), `session_id` property, `close()` + context manager support
|
|
46
|
+
- **Thread-safe:** a `threading.Lock` guards `_required_keys`; `get_evidence_state()` snapshots required keys under the lock, then does the network call outside it.
|
|
47
|
+
|
|
48
|
+
**`EvidenceStateReport`** — Frozen dataclass mirroring `SessionState`'s fields (`session_id`, `evidence`, `context`, `completed_tools`) plus `satisfied` and `missing` lists computed by comparing proxy evidence against locally declared requirements.
|
|
49
|
+
|
|
50
|
+
## Dependencies & Build
|
|
51
|
+
|
|
52
|
+
- `httpx>=0.27` — sync HTTP client (the only runtime dependency)
|
|
53
|
+
- `pytest==9.0.2` + `respx==0.22.0` — testing (dev extra: `helio[dev]`)
|
|
54
|
+
- Build backend: `hatchling` + `hatch-vcs` — the version is derived dynamically from git tags (`raw-options.root = "../.."`) and written to `src/helio/_version.py` at build time. There is no static version in `pyproject.toml`.
|
|
55
|
+
- `requires-python = ">=3.10"`
|
|
56
|
+
|
|
57
|
+
## Commands
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# From repo root:
|
|
61
|
+
pip install -e ./packages/python-sdk # Install SDK in editable mode
|
|
62
|
+
pip install -e './packages/python-sdk[dev]' # With test dependencies
|
|
63
|
+
|
|
64
|
+
# Run tests (either works):
|
|
65
|
+
cd packages/python-sdk && pytest
|
|
66
|
+
pnpm test:python-sdk # from repo root — runs `python3 -m pytest packages/python-sdk/tests`
|
|
67
|
+
|
|
68
|
+
# Line count check for hand-written modules (must stay under 500 total):
|
|
69
|
+
wc -l src/helio/{__init__,types,client,context}.py
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Security Standards
|
|
73
|
+
|
|
74
|
+
The SDK communicates with the proxy over localhost HTTP. Even though this is a local-only channel, the SDK handles evidence data that influences governance decisions. Security matters.
|
|
75
|
+
|
|
76
|
+
### Data Integrity
|
|
77
|
+
|
|
78
|
+
- **Evidence data is passed through as-is to the proxy.** The SDK does not validate, transform, or cache evidence content. The proxy is the single source of truth for evidence validation and enforcement.
|
|
79
|
+
- **Session IDs are generated securely.** When not provided, the SDK generates a UUID hex string using Python's `uuid.uuid4()`. Never generate session IDs from predictable or user-controllable sources.
|
|
80
|
+
- **The SDK never stores evidence or governance state on disk.** All state is in-memory and tied to the client/context instance lifetime.
|
|
81
|
+
|
|
82
|
+
### Network Safety
|
|
83
|
+
|
|
84
|
+
- **The SDK connects only to localhost by default** (`http://127.0.0.1:3200`). The `proxy_url` is operator-configurable but should never point to an untrusted host.
|
|
85
|
+
- **The SDK authenticates with the sideband Bearer token.** It reads `HELIO_SDK_TOKEN` from the environment at construction; the proxy prints this token to stderr on `helio start` when `sdk.enabled` is true. Operators are responsible for passing it to the SDK process. The token is read once and bound for the client's lifetime — rotation means restarting the process.
|
|
86
|
+
- **HTTP errors from the proxy are raised as `HelioError`.** The SDK does not silently swallow connection failures, timeouts, or error responses — callers must handle them.
|
|
87
|
+
- **No retries with backoff.** The SDK is a thin client. Retry logic belongs in the calling application, not here. Adding retry logic would obscure failures and increase complexity beyond the 500-line budget.
|
|
88
|
+
|
|
89
|
+
### Input Handling
|
|
90
|
+
|
|
91
|
+
- **All public method parameters are type-annotated.** Type checkers catch misuse at development time.
|
|
92
|
+
- **Frozen dataclasses for all types.** Data returned by the SDK is immutable. Callers cannot accidentally modify evidence state or session data.
|
|
93
|
+
- **JSON serialization uses httpx's built-in encoder.** Never manually construct JSON strings.
|
|
94
|
+
|
|
95
|
+
### Scope Discipline
|
|
96
|
+
|
|
97
|
+
- **The SDK must never make governance decisions.** No caching of policy results, no local evaluation of rules, no "smart" behavior that second-guesses the proxy. The SDK reports data; the proxy decides.
|
|
98
|
+
- **The SDK must never grow beyond 500 lines.** If a feature requires more code, it belongs in the proxy, not the SDK. This constraint exists to minimize the attack surface and keep the SDK auditable.
|
|
99
|
+
|
|
100
|
+
## Testing Standards
|
|
101
|
+
|
|
102
|
+
- **pytest** with **respx** for mocking httpx requests — no real HTTP calls in unit tests
|
|
103
|
+
- Fixtures in `conftest.py` provide pre-configured mock routes
|
|
104
|
+
- Tests verify both the HTTP call shape (URL, body, method) and the parsed return types
|
|
105
|
+
- E2E test lives in the proxy package: `packages/proxy/src/__tests__/e2e-python-sdk-sideband.test.ts` — Vitest spawns a Python subprocess with `PYTHONPATH` pointing at SDK source
|
|
106
|
+
- **Test error paths:** connection refused, HTTP 4xx/5xx, malformed JSON responses
|
|
107
|
+
- **Test session ID generation:** verify UUIDs are valid and unique across instances
|
|
108
|
+
|
|
109
|
+
## Code Standards
|
|
110
|
+
|
|
111
|
+
- Python 3.10+ (`from __future__ import annotations` for forward refs)
|
|
112
|
+
- Frozen dataclasses for all types (`@dataclass(frozen=True)`)
|
|
113
|
+
- Type hints on all public APIs
|
|
114
|
+
- Docstrings on all public classes and methods (Google style)
|
|
115
|
+
- No default exports — explicit `__all__` in `__init__.py`
|
|
116
|
+
- **snake_case everywhere** — in Python types *and* in the JSON sent to / parsed from the proxy. The sideband API speaks snake_case on the wire (`session_id`, `tool_name`, `evidence_key`, `evidence_data`, `ttl_seconds`, `completed_tools`, …), so response objects splat straight into their dataclass constructors with no key-rename layer. Do not reintroduce a camelCase translation step.
|
|
117
|
+
|
|
118
|
+
## Sideband API Endpoints (proxy side)
|
|
119
|
+
|
|
120
|
+
The SDK talks to these endpoints on the proxy's sideband server (default port 3200 on `127.0.0.1`; the bind host is configurable via `sdk.host`). When the proxy has a sideband token configured, every request except `GET /healthz` must carry `Authorization: Bearer <HELIO_SDK_TOKEN>`; the sideband also rejects any request with an `Origin` header and blocks `OPTIONS` preflights.
|
|
121
|
+
|
|
122
|
+
| Method | Path | Purpose |
|
|
123
|
+
|--------|------|---------|
|
|
124
|
+
| `GET` | `/healthz` | Liveness probe (always unauthenticated) |
|
|
125
|
+
| `POST` | `/evidence` | Report evidence from tool output |
|
|
126
|
+
| `POST` | `/context` | Set arbitrary session context |
|
|
127
|
+
| `GET` | `/session/:session_id/state` | Fetch combined evidence + context + completed tools |
|
|
128
|
+
|
|
129
|
+
## Design Constraints
|
|
130
|
+
|
|
131
|
+
- SDK is a **thin client only** — no caching, no policy evaluation, no decision-making
|
|
132
|
+
- All governance enforcement happens in the proxy; the SDK just reports data
|
|
133
|
+
- Session ID is the correlation key between SDK calls and proxy-side evidence/dependency tracking
|
|
134
|
+
- The `require_evidence()` method is purely local bookkeeping for developer convenience; the proxy enforces evidence requirements via policy rules
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: helio-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Thin Python SDK for the Helio MCP governance proxy
|
|
5
|
+
Project-URL: Homepage, https://github.com/gethelio/helio
|
|
6
|
+
Project-URL: Repository, https://github.com/gethelio/helio
|
|
7
|
+
Project-URL: Documentation, https://github.com/gethelio/helio/tree/main/docs
|
|
8
|
+
Project-URL: Issues, https://github.com/gethelio/helio/issues
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: httpx>=0.27
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest==9.0.2; extra == 'dev'
|
|
14
|
+
Requires-Dist: respx==0.22.0; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Helio Python SDK
|
|
18
|
+
|
|
19
|
+
Thin Python client for the [Helio MCP governance proxy](https://github.com/gethelio/helio). Communicates with the proxy's sideband HTTP API to report evidence and context. The SDK never makes governance decisions — that is always the proxy's job.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install helio-client
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
> **`helio-client` is only the PyPI distribution name** — the string you `pip install`. This package is the **Helio Python SDK**; the distribution name differs solely because the bare `helio` name on PyPI is held by an unrelated abandoned project. It changes nothing about how you use the SDK — the import path stays `helio` (`from helio import HelioContext`), and these docs refer to it as the Helio Python SDK throughout.
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
The proxy prints a per-boot SDK token to stderr on startup (`SDK token (pass as HELIO_SDK_TOKEN env var to your SDK clients): ...`). Export it in the environment where your SDK client runs — the client reads `HELIO_SDK_TOKEN` automatically and attaches it as `Authorization: Bearer <token>` on every sideband call. Without it, requests to the proxy sideband are rejected with `401`.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export HELIO_SDK_TOKEN=<token-from-proxy-startup-logs>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from helio import HelioContext
|
|
39
|
+
|
|
40
|
+
with HelioContext(proxy_url="http://127.0.0.1:3200") as ctx:
|
|
41
|
+
# Declare what evidence this session needs
|
|
42
|
+
ctx.require_evidence("orders.lookup")
|
|
43
|
+
|
|
44
|
+
# Mark tool output as evidence under the required key
|
|
45
|
+
ctx.mark_evidence("get_order", "orders.lookup", {"orderId": 42})
|
|
46
|
+
|
|
47
|
+
# Set arbitrary session context
|
|
48
|
+
ctx.set("agent_id", "support-bot")
|
|
49
|
+
|
|
50
|
+
# Check what evidence the proxy has
|
|
51
|
+
report = ctx.get_evidence_state()
|
|
52
|
+
print(report.satisfied) # ['orders.lookup']
|
|
53
|
+
print(report.missing) # []
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
### HelioContext
|
|
59
|
+
|
|
60
|
+
High-level wrapper with local requirement tracking.
|
|
61
|
+
|
|
62
|
+
| Method | Description |
|
|
63
|
+
|--------|-------------|
|
|
64
|
+
| `mark_evidence(tool_name, evidence_key, data, ttl=300)` | Report evidence from a tool output |
|
|
65
|
+
| `require_evidence(keys)` | Declare local requirements (informational) |
|
|
66
|
+
| `set(key, value)` | Set arbitrary session context |
|
|
67
|
+
| `get_evidence_state()` | Get evidence state with satisfied/missing comparison |
|
|
68
|
+
|
|
69
|
+
### HelioClient
|
|
70
|
+
|
|
71
|
+
Low-level HTTP client mapping to the sideband API.
|
|
72
|
+
|
|
73
|
+
| Method | Endpoint | Description |
|
|
74
|
+
|--------|----------|-------------|
|
|
75
|
+
| `mark_evidence(tool_name, evidence_key, data, ttl=300)` | `POST /evidence` | Report evidence |
|
|
76
|
+
| `set_context(key, value)` | `POST /context` | Set session context |
|
|
77
|
+
| `get_session_state()` | `GET /session/:session_id/state` | Fetch combined state |
|
|
78
|
+
|
|
79
|
+
### HelioError
|
|
80
|
+
|
|
81
|
+
All SDK methods raise `HelioError` on failure instead of raw HTTP exceptions. The error includes an actionable message and an optional `status_code` attribute.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from helio import HelioContext, HelioError
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
with HelioContext() as ctx:
|
|
88
|
+
ctx.mark_evidence("tool", "key", "data")
|
|
89
|
+
except HelioError as e:
|
|
90
|
+
print(e) # "POST /evidence failed: HTTP 400"
|
|
91
|
+
print(e.status_code) # 400
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Caught error types:
|
|
95
|
+
- **Connection errors** — proxy unreachable (`"Cannot connect to proxy at ..."`)
|
|
96
|
+
- **Timeout errors** — proxy did not respond in time (`"Proxy request timed out: ..."`)
|
|
97
|
+
- **HTTP errors** — proxy returned 4xx/5xx (`"POST /evidence failed: HTTP 400"`)
|
|
98
|
+
- **Serialization errors** — request payload is not JSON-serializable for POST calls (for example, `{"v": object()}`), normalized to `HelioError` with endpoint context (`"Failed to serialize POST /evidence payload as JSON: ..."`).
|
|
99
|
+
- **Evidence allowlist errors** — `POST /evidence` may return `code=evidence_key_not_in_policy_allowlist` when `evidence_key` does not match any policy `evidence.requires` key. The SDK includes the rejected key and configured-key preview in `HelioError` for quick diagnosis.
|
|
100
|
+
- **Malformed sideband responses** — invalid JSON or missing fields from `GET /session/:session_id/state` are normalized to `HelioError` (`"... returned malformed response payload"`).
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
| Parameter | Default | Description |
|
|
105
|
+
|-----------|---------|-------------|
|
|
106
|
+
| `proxy_url` | `http://127.0.0.1:3200` | Sideband API base URL |
|
|
107
|
+
| `session_id` | Auto-generated UUID | Correlation key for evidence/context |
|
|
108
|
+
| `timeout` | `5.0` | HTTP request timeout in seconds |
|
|
109
|
+
|
|
110
|
+
## Constraints
|
|
111
|
+
|
|
112
|
+
- **Under 500 lines** — governance logic belongs in the proxy, not the SDK
|
|
113
|
+
- **Thin client only** — no caching, no policy evaluation, no decision-making
|
|
114
|
+
- **Python 3.10+** required
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
Apache 2.0
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Helio Python SDK
|
|
2
|
+
|
|
3
|
+
Thin Python client for the [Helio MCP governance proxy](https://github.com/gethelio/helio). Communicates with the proxy's sideband HTTP API to report evidence and context. The SDK never makes governance decisions — that is always the proxy's job.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install helio-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
> **`helio-client` is only the PyPI distribution name** — the string you `pip install`. This package is the **Helio Python SDK**; the distribution name differs solely because the bare `helio` name on PyPI is held by an unrelated abandoned project. It changes nothing about how you use the SDK — the import path stays `helio` (`from helio import HelioContext`), and these docs refer to it as the Helio Python SDK throughout.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
The proxy prints a per-boot SDK token to stderr on startup (`SDK token (pass as HELIO_SDK_TOKEN env var to your SDK clients): ...`). Export it in the environment where your SDK client runs — the client reads `HELIO_SDK_TOKEN` automatically and attaches it as `Authorization: Bearer <token>` on every sideband call. Without it, requests to the proxy sideband are rejected with `401`.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
export HELIO_SDK_TOKEN=<token-from-proxy-startup-logs>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from helio import HelioContext
|
|
23
|
+
|
|
24
|
+
with HelioContext(proxy_url="http://127.0.0.1:3200") as ctx:
|
|
25
|
+
# Declare what evidence this session needs
|
|
26
|
+
ctx.require_evidence("orders.lookup")
|
|
27
|
+
|
|
28
|
+
# Mark tool output as evidence under the required key
|
|
29
|
+
ctx.mark_evidence("get_order", "orders.lookup", {"orderId": 42})
|
|
30
|
+
|
|
31
|
+
# Set arbitrary session context
|
|
32
|
+
ctx.set("agent_id", "support-bot")
|
|
33
|
+
|
|
34
|
+
# Check what evidence the proxy has
|
|
35
|
+
report = ctx.get_evidence_state()
|
|
36
|
+
print(report.satisfied) # ['orders.lookup']
|
|
37
|
+
print(report.missing) # []
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
### HelioContext
|
|
43
|
+
|
|
44
|
+
High-level wrapper with local requirement tracking.
|
|
45
|
+
|
|
46
|
+
| Method | Description |
|
|
47
|
+
|--------|-------------|
|
|
48
|
+
| `mark_evidence(tool_name, evidence_key, data, ttl=300)` | Report evidence from a tool output |
|
|
49
|
+
| `require_evidence(keys)` | Declare local requirements (informational) |
|
|
50
|
+
| `set(key, value)` | Set arbitrary session context |
|
|
51
|
+
| `get_evidence_state()` | Get evidence state with satisfied/missing comparison |
|
|
52
|
+
|
|
53
|
+
### HelioClient
|
|
54
|
+
|
|
55
|
+
Low-level HTTP client mapping to the sideband API.
|
|
56
|
+
|
|
57
|
+
| Method | Endpoint | Description |
|
|
58
|
+
|--------|----------|-------------|
|
|
59
|
+
| `mark_evidence(tool_name, evidence_key, data, ttl=300)` | `POST /evidence` | Report evidence |
|
|
60
|
+
| `set_context(key, value)` | `POST /context` | Set session context |
|
|
61
|
+
| `get_session_state()` | `GET /session/:session_id/state` | Fetch combined state |
|
|
62
|
+
|
|
63
|
+
### HelioError
|
|
64
|
+
|
|
65
|
+
All SDK methods raise `HelioError` on failure instead of raw HTTP exceptions. The error includes an actionable message and an optional `status_code` attribute.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from helio import HelioContext, HelioError
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
with HelioContext() as ctx:
|
|
72
|
+
ctx.mark_evidence("tool", "key", "data")
|
|
73
|
+
except HelioError as e:
|
|
74
|
+
print(e) # "POST /evidence failed: HTTP 400"
|
|
75
|
+
print(e.status_code) # 400
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Caught error types:
|
|
79
|
+
- **Connection errors** — proxy unreachable (`"Cannot connect to proxy at ..."`)
|
|
80
|
+
- **Timeout errors** — proxy did not respond in time (`"Proxy request timed out: ..."`)
|
|
81
|
+
- **HTTP errors** — proxy returned 4xx/5xx (`"POST /evidence failed: HTTP 400"`)
|
|
82
|
+
- **Serialization errors** — request payload is not JSON-serializable for POST calls (for example, `{"v": object()}`), normalized to `HelioError` with endpoint context (`"Failed to serialize POST /evidence payload as JSON: ..."`).
|
|
83
|
+
- **Evidence allowlist errors** — `POST /evidence` may return `code=evidence_key_not_in_policy_allowlist` when `evidence_key` does not match any policy `evidence.requires` key. The SDK includes the rejected key and configured-key preview in `HelioError` for quick diagnosis.
|
|
84
|
+
- **Malformed sideband responses** — invalid JSON or missing fields from `GET /session/:session_id/state` are normalized to `HelioError` (`"... returned malformed response payload"`).
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
| Parameter | Default | Description |
|
|
89
|
+
|-----------|---------|-------------|
|
|
90
|
+
| `proxy_url` | `http://127.0.0.1:3200` | Sideband API base URL |
|
|
91
|
+
| `session_id` | Auto-generated UUID | Correlation key for evidence/context |
|
|
92
|
+
| `timeout` | `5.0` | HTTP request timeout in seconds |
|
|
93
|
+
|
|
94
|
+
## Constraints
|
|
95
|
+
|
|
96
|
+
- **Under 500 lines** — governance logic belongs in the proxy, not the SDK
|
|
97
|
+
- **Thin client only** — no caching, no policy evaluation, no decision-making
|
|
98
|
+
- **Python 3.10+** required
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
Apache 2.0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "helio-client"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Thin Python SDK for the Helio MCP governance proxy"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"httpx>=0.27",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
dev = [
|
|
18
|
+
"pytest==9.0.2",
|
|
19
|
+
"respx==0.22.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/gethelio/helio"
|
|
24
|
+
Repository = "https://github.com/gethelio/helio"
|
|
25
|
+
Documentation = "https://github.com/gethelio/helio/tree/main/docs"
|
|
26
|
+
Issues = "https://github.com/gethelio/helio/issues"
|
|
27
|
+
|
|
28
|
+
[tool.pytest.ini_options]
|
|
29
|
+
testpaths = ["tests"]
|
|
30
|
+
|
|
31
|
+
[tool.hatch.version]
|
|
32
|
+
source = "vcs"
|
|
33
|
+
raw-options = { root = "../.." }
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.hooks.vcs]
|
|
36
|
+
version-file = "src/helio/_version.py"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.wheel]
|
|
39
|
+
packages = ["src/helio"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Helio Python SDK — thin client for the MCP governance proxy."""
|
|
2
|
+
|
|
3
|
+
from ._version import __version__
|
|
4
|
+
from .client import HelioClient, HelioError
|
|
5
|
+
from .context import HelioContext
|
|
6
|
+
from .types import (
|
|
7
|
+
CompletedTool,
|
|
8
|
+
EvidenceEntry,
|
|
9
|
+
EvidenceStateReport,
|
|
10
|
+
SessionState,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"__version__",
|
|
15
|
+
"HelioClient",
|
|
16
|
+
"HelioContext",
|
|
17
|
+
"HelioError",
|
|
18
|
+
"CompletedTool",
|
|
19
|
+
"EvidenceEntry",
|
|
20
|
+
"EvidenceStateReport",
|
|
21
|
+
"SessionState",
|
|
22
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|