Custody
Custody sits between an AI agent and the tools it uses on oncology data, so the agent can work under controls a privacy or compliance officer can actually review: data-aware policy, approval gates for anything sensitive, a tamper-evident audit log, and isolated execution for every command.
Agent (Claude Code / Codex / OpenHands / Goose / any MCP client)
| MCP (stdio or streamable HTTP)
v
Custody Gateway
1. Identify principal + session
2. Classify request (tool, datasets, data class, egress, model target)
3. Policy engine -> allow | deny | require_approval
4. Approval broker -> wait for human decision (timeout = deny)
5. Executor -> sandboxed container
6. Output guard -> redact/summarize results by data class
7. Audit log -> hash-chained entry at every step
|
v
Custody's own tools: catalog.list, sql.query, shell.run, files.write,
files.export, approvals.status, fhir.search
Status: v0.1.0 tagged and public (285 tests, 96% coverage on
src/custody, ruff and mypy --strict clean); main carries
several items since merged in from ROADMAP.md (signed policy bundles,
an OPA policy backend, a Kubernetes deployment, a FHIR connector, an
S3 Object Lock audit sink, an egress allowlist proxy, OIDC-verified
approver identity) -- see CHANGELOG.md's [Unreleased] section for
exactly what, and for what shipped in v0.1.0 itself. One scope note:
the architecture above describes a gateway that can also proxy a
downstream MCP server's tools, that proxying is not implemented,
every tool call this release handles goes through one of the
built-ins listed above (see custody/mcp_server.py's module
docstring).
Quickstart (under 5 minutes to the demo, Docker required)
From a clone of this repository (not yet published to PyPI):
git clone <this repo>
cd custody
uv sync --extra dev
docker build -f docker/sandbox.Dockerfile -t custody-sandbox:duckdb .
uv run custody demo
custody demo generates a synthetic oncology dataset with a fixed
seed, writes a sample policy/catalog/principals to disk, and walks
through: an allowed catalog listing, a de-identified cohort query
(not small-cell suppressed, that only applies at limited_data_set
and above), the same cohort breakdown re-run on identified data (held
for approval, then approved, this time suppressed), a two-person
export denied by one of the two required approvers, a blocked network
call from the sandbox, and custody audit verify catching a
deliberately tampered log entry. See docs/demo.md for a narrated
walkthrough of the output.
Once initialized (custody init in an empty directory, or after
custody demo), run custody serve --principal <your-agent-id> to
start the real MCP gateway (stdio), or custody ui for the local
approvals web UI.
Using it with Claude Code
Two integration modes:
- MCP gateway (primary). Custody runs as an MCP server
(
custody serve) exposing its built-in tools. Seeexamples/claude-code-mcp-config.jsonfor the config snippet. PreToolUsehook adapter (secondary). Routes Claude Code's own native tool calls (Bash, Write, Edit, WebFetch) through the same policy engine and audit log, asclaude_code.bash/claude_code.write/claude_code.edit/claude_code.web_fetchin your policy. Arequire_approvaldecision here doesn't block the hook (aPreToolUsehook has roughly 30 seconds, Custody's default approval timeout is 15 minutes); it records the pending approval and returns Claude Code's own"ask"decision instead.
See docs/integrations/claude-code.md for both, with exact config.
What Custody is not
- Not a HIPAA certification. Installing Custody does not make anyone compliant. The accurate phrase is that Custody provides controls that can support HIPAA-aligned programs.
- Not an audit or a SOC 2 report.
custody evidence exportproduces a mechanical summary of the audit log and policy, mapped to SOC 2 Common Criteria language, for a reviewer's convenience. It is not an attestation and does not substitute for an independent auditor. See docs/evidence-pack.md. - Not a replacement for a business associate agreement, a risk assessment, or your institution's review process.
- Tamper-evident, not tamper-proof. The audit log detects changes; it does not make them physically impossible. For long-term retention, ship checkpoints to write-once storage (see docs/audit-log.md).
- Not a perfect sandbox. Docker isolation is one layer in a defense-in-depth design, not the whole design. See docs/threat-model.md for exactly what v0.1 defends against and what it does not.
- Not a full SSO login flow.
principals.yamlis still the one source of truth for which roles a principal holds.custody approvals approve/deny --as-tokencan verify an approver's identity against a real OIDC provider's signed token instead of trusting a bare--asstring (see docs/sso-oidc.md), but there is no browser login flow, no session management, and the approvals web UI is unaffected (still its own startup-token gate). - Not sandboxed with gVisor or Firecracker. Both require a Linux host with KVM; neither is buildable or verifiable on a macOS development machine, so neither is implemented here, see docs/threat-model.md T2 and ROADMAP.md.
Components
| Component | Module | Docs |
|---|---|---|
| Data catalog | custody/catalog.py |
this README |
| Policy engine | custody/policy/ |
docs/policy-reference.md |
| Signed policy bundles | custody/policy/bundle.py |
docs/policy-reference.md, custody policy sign/verify --help |
| OPA policy backend (optional, same policy.yaml) | custody/policy/opa_backend.py |
docs/opa-policy-backend.md |
| Kubernetes deployment | k8s/, docker/custody.Dockerfile |
docs/kubernetes.md |
| Approval broker | custody/approvals/ |
this README, docs/threat-model.md |
| SSO/OIDC approver identity (optional, token verification only) | custody/approvals/oidc.py |
docs/sso-oidc.md |
| Approval notifier (CLI, UI, webhook) | custody/approvals/notifier.py |
docs/policy-reference.md, custody serve --help |
| Audit log | custody/audit/ |
docs/audit-log.md |
S3 Object Lock audit sink (optional, pip install custody-mcp[s3]) |
custody/audit/s3_sink.py |
docs/s3-object-lock-sink.md |
| Evidence-pack export | custody/evidence/ |
docs/evidence-pack.md |
| Sandbox executor | custody/sandbox/ |
docs/threat-model.md |
| Egress allowlist proxy | custody/sandbox/egress_proxy.py |
docs/egress-proxy.md, docs/threat-model.md T9 |
| Output guard | custody/guard.py |
this README |
| Model routing | custody/routing.py |
docs/threat-model.md |
| Gateway | custody/gateway.py |
this README |
| Built-in tools | custody/tools/ |
this README |
| FHIR connector (read-only, FHIR only not OMOP) | custody/tools/fhir.py |
docs/fhir-connector.md |
| MCP server | custody/mcp_server.py |
docs/integrations/claude-code.md |
| CLI | custody/cli/ |
custody --help |
| Local approvals UI | custody/approvals_ui.py |
this README |
| Claude Code hook | custody/hooks/claude_code.py |
docs/integrations/claude-code.md |
| PyPI release workflow (Trusted Publishing) | .github/workflows/publish.yml |
docs/releasing.md |
Synthetic data only
Every example, fixture, and test in this repository uses generated oncology records. No real patient data belongs anywhere near this project, in an issue, a PR, or a commit. See CONTRIBUTING.md.
License
Apache 2.0. See LICENSE and NOTICE.
Metadata
Release files for custody-mcp 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| custody_mcp-0.2.0.tar.gz | 255.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| custody_mcp-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 350.0 kB
Release files / custody_mcp-0.2.0.tar.gz
| Download URL | custody_mcp-0.2.0.tar.gz |
|---|---|
| Size | 255.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5e562366c9825739ec262e914095532707098feea89de363fdf75313885b9383
|
|
BLAKE2b-256 checksum How to use checksums |
51a4f1b721d2df3abb5c8a7636a7052481d197be0ad9e2cd0d86375ae6c2646c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logRelease files / custody_mcp-0.2.0-py3-none-any.whl
| Download URL | custody_mcp-0.2.0-py3-none-any.whl |
|---|---|
| Size | 94.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
29fba5c40629986d8bb5fd1a6e44aaf5f7889a2365f35fa99bc9710bfa90580c
|
|
BLAKE2b-256 checksum How to use checksums |
28441d795f9535420d96524b92a3477a1811f4dd848686b458ebd6a0644e910e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logSecurity for custody-mcp 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for custody_mcp-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.