Eniyan SDK (Python)
Govern your AI agents wherever they run. This SDK wraps the Eniyan API —
agent identity verification, live scope decisions, JIT credential windows,
short-lived OAuth tokens, and self-reported run telemetry — so any agent
loop becomes Eniyan-governed in a few lines. Eniyan never hosts or observes
your loop; your agent reports metadata-only telemetry (names, scopes, token
counts, outcomes — never prompts, arguments, results, or model output).
The package also ships four MCP servers — eniyan-mcp (governance
operations), eniyan-fs (local files), eniyan-web (websites, and, where
the org opts in, transaction execution at web checkouts), and eniyan-mail
(email) — plus eniyan-a2a, an inbound agent-to-agent gate that runs as a
reverse proxy in front of your agent, and eniyan-scan, a local discovery
tool for personal accounts.
pip install eniyan
Quickstart
import os
from eniyan import EniyanClient, governed_run, EniyanScopeRefused
client = EniyanClient(
base_url="https://api.eniyantrust.com",
api_key=os.environ["ENIYAN_API_KEY"],
credential_token=os.environ["ENIYAN_CREDENTIAL_TOKEN"],
agent_id=os.environ["ENIYAN_AGENT_ID"],
)
with governed_run(client, harness="my-loop", jit=True, scopes=["crm:read"]) as run:
fetch = run.tool("fetch_accounts", scope="crm:read")(fetch_accounts)
accounts = fetch() # refused under block mode BEFORE it runs
run.model_call("claude-sonnet-5", input_tokens=1200, output_tokens=300)
On exit — success or crash — the run is finished, buffered steps are flushed, and the JIT window is completed and attested.
What each piece maps to
| SDK call | API |
|---|---|
client.verify() / client.check_scope() |
POST /v1/credentials/verify |
client.mint_token() / client.introspect() |
POST /v1/oauth/token / /introspect |
client.mint_consumer_token() / mint_trading_token() |
POST /v1/oauth/token (delegation_id / mandate_id) |
client.report_delegation_usage() / report_trading_usage() |
POST /v1/consumer/delegations/{ref}/usage / POST /v1/trading/mandates/{ref}/usage |
client.validate_grant() |
POST /v1/agents/credentials/validate |
client.get_web_config() / report_web_injection() |
POST /v1/agents/credentials/web-access-config / /web-injection-events |
client.create_purchase_request() / get_purchase_status() / consume_purchase_request() / report_purchase_outcome() |
POST /v1/agents/credentials/purchase-requests[...] |
client.create_task() / complete_task() / attest_task() |
JIT task lifecycle |
client.start_run() / append_steps() / finish_run() |
POST /v1/runs |
Advisory-mode scope violations do not raise — the call proceeds and the
violation is flagged server-side (GovernanceDecision.advisory_flagged).
Block mode raises EniyanScopeRefused before the tool executes.
MCP server
pip install "eniyan[mcp]" and run eniyan-mcp to expose the same
governance operations as MCP tools for Claude Code or any MCP-capable
harness. See the gated docs for configuration.
Development
pip install -e ".[dev]"
pytest
tests/integration_local.py runs the full flow against a local Eniyan
stack (docker compose up from the repo root).
eniyan-fs — govern your agents' access to local files
A second MCP server in the same package: agents reach files ONLY inside the roots you configure, every access is policy-checked and audited by Eniyan (metadata only — root aliases + path hashes, never contents, never full paths), and revoking the agent's credential is the kill switch.
{
"mcpServers": {
"eniyan-fs": {
"command": "eniyan-fs",
"env": {
"ENIYAN_API_KEY": "...",
"ENIYAN_CREDENTIAL_TOKEN": "...",
"ENIYAN_AGENT_ID": "...",
"ENIYAN_FS_ROOTS": "projects=/abs/path:notes=/abs/other"
}
}
}
}
Writes and deletes fail closed when Eniyan is unreachable; deletion is
double-gated (org scope fs:delete AND ENIYAN_FS_ALLOW_DELETE=1).
eniyan-web — govern your agents' web access
A third MCP server in the same package: agents reach ONLY the websites
allowlisted on the agent's Eniyan dashboard page, with per-site read /
write / edit / download abilities (mapped to request effect: GET, POST,
PUT/PATCH/DELETE) plus a Pay / Transact ability that lets the browser
engine execute purchases and transactions the org has authorized — see
"Web transaction execution (0.5.0)" below (shopping/transaction tools
register on the browser engine; the local card vault is set up with
eniyan-web --setup-card). Every access is policy-checked and audited (metadata
only — domains and URL hashes, never page content, never full URLs),
page content is screened locally for prompt injections and always
delivered wrapped as untrusted data, and revoking the credential is the
kill switch.
{
"mcpServers": {
"eniyan-web": {
"command": "eniyan-web",
"env": {
"ENIYAN_API_KEY": "...",
"ENIYAN_CREDENTIAL_TOKEN": "...",
"ENIYAN_AGENT_ID": "..."
}
}
}
}
No sites live in the config — the allowlist is managed in the dashboard
and syncs to the gate within a minute. Writes/edits/downloads fail
closed when Eniyan is unreachable; downloads are double-gated (the
dashboard switch AND ENIYAN_WEB_ALLOW_DOWNLOADS=1 +
ENIYAN_WEB_DOWNLOAD_DIR). The real-browser engine (Playwright) is the
enterprise default and needs pip install "eniyan[browser]" plus the
dashboard engine set to browser.
Web transaction execution (0.5.0) — delegations & mandates at web checkouts
When the organization opts in (dashboard toggles), the browser gate executes the SAME transaction authority agents already exercise via API:
- Consumer delegations —
authorize_transactionwith the delegation ref mints a 5-minute transaction-constrained token bound to the page the gate is actually on (the observed host is the counterparty). A step-up pauses locally: relay the consumer's challenge link through your platform, then pollcheck_transaction. Caps, budgets, counterparty allow-lists, and approvals are identical on either surface. - Trading mandates — full parity: web orders ride mandate math only; a refusal is final.
- Personal purchases — an approved purchase now authorizes the SUBMIT itself.
Fill-less first-class (0.5.0 behavior change): the authorization
opens the submit window with no card fill — merchant-saved cards, wallet
buttons (Apple/Google Pay on the site), and payment methods on file all
ride it, and Eniyan touches no payment instrument of any kind. The
locally-vaulted card remains only for stores where the card must be
typed (fill_payment); the vault is cross-platform (macOS Keychain
built in, Windows/Linux via pip install "eniyan[card]"). The minted
access token never appears in tool results or model context. In 0.4.0
the submit window opened only after a card fill — 0.5.0 opens it at
authorization.
eniyan-scan — see which agents are set up on your computer, and what they can reach (personal accounts)
Not an MCP server and not a gate: a command-line tool for macOS, Linux and Windows that lists the AI agents and MCP servers configured on your own computer, and what each of them can reach, and reports them to your Eniyan dashboard, where you approve each one or mark it not allowed. It is visibility only — Eniyan records what it finds and what you decide, and it does not stop anything. It is available to personal accounts only.
What it reads (read-only; it never changes a configuration file):
- the MCP configuration of the AI coding tools, desktop assistants and editors installed for your user, including their extensions and plugins;
- the projects your AI coding tools record in their own settings files, at
most 200: Claude Code's first (the projects listed in
~/.claude.jsonand the folders under~/.claude/projects), then Codex's, Gemini CLI's trusted folders, and the folders VS Code and Cursor have opened. Claude Code's are also read first. In a project only another tool recorded, a tool's settings file counts only if that tool is set up on your computer. In each it reads the MCP configuration and the dependency manifests (for agent frameworks and SDKs), and notes other agent configuration files by presence without opening them. A project only Windsurf or Zed knows about is not walked; - your own background jobs, to find always-on agent runtimes: LaunchAgents
on macOS; systemd user units, autostart entries and your crontab on Linux;
your Task Scheduler tasks, Startup folder and
Runkey on Windows; - what your agents can reach: each AI coding tool's saved permission settings (what it may do without asking), the configuration files where developer command-line tools keep their sign-ins (GitHub, GitLab, git, SSH, deploy and cloud tools, package registries, secrets managers, AI CLIs), the NAMES exported in your shell startup files, and the CI workflow files of the same projects.
It never walks your whole home folder, only those projects, and it reads no
process list and no logs. It never runs anything to learn a credential:
no gh auth status, no cloud CLI sign-in check, no keychain lookup of
another tool's secret, no git credential helper. Reading configuration files is the only method. It never
opens a private key (a key file counts by its existence), and "a token is
saved here" is a yes or no, never the token, its length or a hash of it.
macOS privacy-protected folders. macOS treats the background jobs as
their own program, so the access you gave Terminal does not carry over.
Without Full Disk Access they cannot read projects in ~/Documents,
~/Desktop, ~/Downloads, iCloud Drive or another volume; those projects
are skipped, and eniyan-scan --status says how many were.
What it sends (metadata only): for each finding, its kind, its name,
which tool's configuration it came from, its transport (stdio, HTTP, SSE or
WebSocket), the name of the program that launches it (never its arguments),
its package name when that is a well-known one, which well-known
dependencies matched, a remote server's hostname when it looks public (never
an IP address or a name under a well-known local suffix such as .local,
.internal, .lan, .home or .corp), whether the tool has it switched
off, and a device-salted hash in place of each project folder, profile, job
label or CI job. Settings, sign-ins and MCP servers that apply to your whole user
account carry a hash of your computer itself, so the dashboard shows each
computer on its own. What an agent can reach is sent only as identifiers from a
fixed list Eniyan serves to the scanner — which tool, which service (GitHub,
AWS, npm, ...), which kind of permission ("runs shell commands without
asking"), how a sign-in is stored — plus the public hostname a sign-in is
for. Each report also carries the device token, what started the run, the
scanner and corpus versions, and when it ran. Never file contents, paths,
usernames, account or profile names, command arguments, permission rule
text, environment variables or secret names (names or values), tokens,
workflow, job, branch or repository names, SSH host aliases, IP addresses,
headers, or full URLs; a server name that looks like a path is sent as an
opaque unnamed-<hash> instead. The salt stays on your computer:
eniyan-scan --where <hash>, run on the computer the dashboard names with
the hash it shows, prints locally what it stands for: a folder, a profile,
a job, or "this computer (user level)".
An approval covers what it saw. The dashboard approves exactly what the finding showed you, and if the finding changed while the page was open, the approval is refused and nothing is recorded. After that, if a later report shows an approved agent can now do more (a new service, a wider permission, or an MCP server whose tools stopped asking first), the finding comes back to you as "Asks again" instead of staying approved. A narrower report never re-opens anything. The dashboard's reach view shows each computer as it last reported: something a computer stops reporting leaves its reach at the next report, and stays in the findings list until you forget it.
pip install --upgrade "eniyan[scan]>=0.9" # eniyan-scan ships in 0.9
eniyan-scan --install # paste the device token from the dashboard when asked
eniyan-scan --dry-run # print the JSON a run sends; sends nothing, needs no token
eniyan-scan --status # installed jobs, last run, skipped projects, any unsent report
eniyan-scan --uninstall # remove everything --install created
The scan extra installs keyring (the token store on Linux and Windows),
pyyaml (for the AI tools that keep settings in YAML) and, before Python
3.11, tomli. Plain pip install eniyan works too; it skips the YAML
settings and counts them as unread.
--dry-run is the exact report only once --install has run. Before that
no signature list is cached (fetching one takes the device token), so it
shows the harness configs only: no package names, no background job
runtimes, no agent frameworks or instruction files from your projects, no
agent reach, and project refs from a throwaway salt. After install it
matches what a run sends, except that on macOS, run from Terminal, it can
read the privacy-protected folders the background jobs cannot.
--install takes a device token you create in the dashboard (Settings →
Agent Directory → Add this computer; it is shown once). It reads the token
from a prompt, or from stdin with --token-stdin, never from the command
line, checks it with Eniyan, and stores it in your system's credential
store — never in an argument, a log, an environment variable or a job
definition. It then sets up two per-user background jobs, with no admin
rights: one that runs when an AI tool's configuration changes, and a daily
one that catches anything the first misses. It also records, locally, the
config-folder overrides set in the terminal it runs in (such as
CLAUDE_CONFIG_DIR, CODEX_HOME, GH_CONFIG_DIR, KUBECONFIG and the
XDG_* folders; paths only, from a fixed list), because the background
jobs never see that terminal's environment. Every run reads those folders;
--status lists the recorded names, and running --install again records
them afresh after you change one. Before it exits it sends the
first report itself, so the dashboard shows the computer straight away, even
after a reinstall with a new token. It prints exactly what it installed.
--uninstall stops the jobs and deletes them, the stored token and
~/.eniyan/scan/; it is safe to run when nothing is installed.
What --install sets up, per system:
| Token | Change-triggered job | Daily job | |
|---|---|---|---|
| macOS | Keychain | LaunchAgent on WatchPaths |
LaunchAgent on StartCalendarInterval |
| Linux | Secret Service, through keyring |
systemd user .path unit |
systemd user .timer (Persistent=true) |
| Windows | Credential Manager, through keyring |
Task Scheduler task every 15 minutes (Windows has no per-user file trigger; an unchanged run exits at once) | Task Scheduler daily task that runs late if the computer was off |
On Linux without a systemd user manager (some containers), it adds one
hourly line to your crontab instead, inside a marked block that
--uninstall removes exactly; that run does a daily scan at most once per
20 hours, and there is no job that runs when a configuration changes. A
crontab line needs a cron daemon to run it: on WSL without systemd, which
starts none, --install stops when none is running and says how to turn
one on; elsewhere it installs and tells you if it saw no daemon, and
--status says so too. If Linux has no usable keyring, or the jobs would
run from cron (which cannot reach your desktop keyring), --install stops
and says so; --token-file stores the token in
~/.eniyan/scan/device-token (owner-only) instead, and says that it is a
plaintext file. To run eniyan-scan --run from a scheduler of your own
instead of --install, set ENIYAN_SCAN_DEVICE_TOKEN in that job's
environment; it is read only when no token is stored.
Nothing keeps running without --install: pip install starts no
background job, and --run and --dry-run scan once and exit. The device
token can add findings and refresh what they say (switched off or not, the
matched dependencies, what they can reach), but it cannot approve, remove or
read your decisions, and it cannot change what a finding runs: the same
name running a different program or host arrives as a new finding for you
to decide. An account holds at most 2000 findings; a report that would go
past that is refused whole and nothing from it is stored, so forget the
findings you no longer need in the dashboard. If you revoke the device in
the dashboard, the next run exits with status 2 and stops reporting until
you reinstall with a new token.
Release files for eniyan 0.9.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 | |
|---|---|---|---|
| eniyan-0.9.0.tar.gz | 324.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| eniyan-0.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 681.7 kB
Release files / eniyan-0.9.0.tar.gz
| Download URL | eniyan-0.9.0.tar.gz |
|---|---|
| Size | 324.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7c71c47731491bc6f5660019a6fb72e8979bc066161a6e7dccc98e5f6639abac
|
|
BLAKE2b-256 checksum How to use checksums |
e8c464c81c29a0a0d922b964d5c6f5bf471ed7bd7f33315b3b223a21d36ad079
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Release files / eniyan-0.9.0-py3-none-any.whl
| Download URL | eniyan-0.9.0-py3-none-any.whl |
|---|---|
| Size | 357.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
09707a0c7cc88ff66042be35d1539e20e048e1fd6daa286d9509de2844300e55
|
|
BLAKE2b-256 checksum How to use checksums |
c92cc29cb11ec1f028fecd6a0619f6e68cba680cf072c34777ddca0932c26407
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|