sealkeeper
Look up an AI agent or service on SealKeeper, verify its SEAL on your own machine and decide whether to call it.
A SEAL is SealKeeper's signed credential for an agent or a service. This library checks it against SealKeeper's published keys, so what you act on is what SealKeeper signed.
Install
pip install sealkeeper
Python 3.10 or later. It depends on httpx and cryptography only.
Look up
from sealkeeper import SealKeeper
with SealKeeper() as sk:
found = sk.lookup("alice/helper")
if found.verified:
print(found.kind, found.level, found.trust)
else:
print(found.code)
A target is any of these.
- A handle, such as
alice/helper. - A page URL,
https://sealkeeper.run/agents/alice/helperorhttps://sealkeeper.run/agents/<agent id>. - An agent id, 43 characters of base64url, or its
did:key. - A card URL, or an MCP server's URL. Pass it as it is listed, since the host is the only part read case blind.
lookup returns a Lookup. seal is the verified SEAL, and kind, level and trust read from it, so they are None unless it verified. Without one, code says why.
code |
Meaning |
|---|---|
broken |
The SEAL did not verify. broken holds the reason |
withheld |
SealKeeper withholds the SEAL. reason and since say why and from when |
no_seal |
No SEAL while the agent is dormant. dormant_days says how long |
checking |
SealKeeper is still checking it |
delisted |
SealKeeper no longer lists it |
not_found |
The target names no agent or service |
unknown |
No SEAL, and the answer says nothing more |
A code this version does not know comes through as sent, with the whole answer in raw. handle is the handle SealKeeper answered, for display only.
Verify
from sealkeeper import SealBroken, SealKeeper
with SealKeeper() as sk:
try:
seal = sk.verify(seal_text, expected_agent_id)
except SealBroken as broken:
print(broken.reason)
expected_agent_id is the agent you are about to trust, as an agent id or a did:key. A valid SEAL for another agent is wrong_agent.
verify runs the checks of the SEAL Standard in order. The header, the key its kid names, the signature, the issuer, the version, the shape, exp, iat and sub. The reasons are in SEAL_BROKEN_REASONS.
malformed,unknown_kid,bad_signature,wrong_issuer,unsupported_version,expired,not_yet_valid,wrong_agent
It returns a VerifiedSeal with agent_id, kind (agent or service), level (none, bronze, silver or gold), trust (the Trust Score, None before version 4), issued_at, expires_at and payload.
The keys come from https://sealkeeper.run/.well-known/seal.json. A copy is kept for five minutes and fetched again for a kid it does not know. When no copy younger than five minutes can be had, verify raises UnavailableError and never trusts an older one.
The shape check covers the fields this library reads and returns, sub, iat, exp, level, trust, subject.kind and listing.card_url. It is not a full conformance reader of the standard, so read the rest of payload with that in mind.
With a key document of your own, verify_with_keys(seal, read_keys(document), now, expected_agent_id) does the same offline.
verify_seal(seal, agent=None, at=None) asks SealKeeper to check a SEAL instead, and answers as SealKeeper does, with its record of holds.
Gate
from sealkeeper import Gate
gate = Gate()
decision = gate.check("https://mcp.example.com/mcp", "service")
if decision.passed:
call_it()
else:
print(decision.code, decision.reason)
check(target, kind) answers Pass or Block, each with passed and code. A pass on a SEAL is sealed, with the VerifiedSeal in seal. kind is "agent" or "service". acheck is the same, awaited. Neither raises for a decision, so a check never ends your agent's run.
A pass needs a current, valid SEAL of that kind at bronze or above. For a card or MCP server URL, the SEAL must also name that URL.
Gate(allow_unsealed=True)also passes a target SealKeeper does not know, one it is still checking and one with no SEAL while dormant.Pass.codethen says which, andPass.sealisNone. Everything else still blocks.cache_ttlkeeps each lookup for that many seconds, 300 by default, 0 to 86,400. A failed lookup is never kept, and a kept SEAL is checked against itsexpon every use.
Each decision writes one INFO line to the sealkeeper logger, with the code, the kind and the agent's handle, or the host of a URL. Never the SEAL or a full URL.
Block codes
BLOCK_CODES holds every one.
| Code | Meaning |
|---|---|
unavailable |
SealKeeper could not be reached, timed out or asked to slow down |
bad_target |
SealKeeper refused the target. reason is its line |
error |
SealKeeper answered something this library cannot read |
not_found |
The target names no agent or service |
checking |
SealKeeper is still checking it |
no_seal |
No SEAL while it is dormant |
withheld |
SealKeeper withholds its SEAL. reason names why |
delisted |
SealKeeper no longer lists it |
unknown |
No SEAL, and nothing more said |
wrong_url |
The SEAL is for another URL than the target |
wrong_kind |
The SEAL is for the other kind |
below_bronze |
The SEAL's level is none |
malformed and the other seven reasons of SEAL_BROKEN_REASONS |
The SEAL did not verify |
Errors
lookup, get_trust, verify_seal and verify raise these, all SealKeeperError.
BadTargetError, SealKeeper refused the target, with its line.UnavailableError, SealKeeper could not be reached in 5 seconds or kept answering 429.SealBroken, fromverify, withreason.SealKeeperErroritself, for an answer this library cannot read.
Options
Both SealKeeper and AsyncSealKeeper take these, all optional.
http_client, anhttpx.Clientorhttpx.AsyncClientto send through.clock, a function answering Unix seconds, for the SEAL checks.user_agent_suffix, added to theUser-Agent, such as"my-wrapper/1.0.0".sleep, what waits before the one retry of a 429.
Gate takes client, async_client and clock the same way.
License
MIT
Metadata
Release files for sealkeeper 0.1.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 | Actions |
|---|---|---|---|---|
| sealkeeper-0.1.0.tar.gz | 38.7 kB | With provenance | Details for sealkeeper-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| sealkeeper-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for sealkeeper-0.1.0-py3-none-any.whl |
Total release size: 59.7 kB
Release files / sealkeeper-0.1.0.tar.gz
| Download URL | sealkeeper-0.1.0.tar.gz |
|---|---|
| Size | 38.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
76bd454e501c349ffbf9b9ddc7a770514bc425e3abc024b1e0ac539a067ad462
|
|
BLAKE2b-256 checksum How to use checksums |
2b1393ea211cd3c0e42345e81c75815f15b8afb047876a190bb98fb275d8f6a0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
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 Nov 2, 2026.
Transparency logRelease files / sealkeeper-0.1.0-py3-none-any.whl
| Download URL | sealkeeper-0.1.0-py3-none-any.whl |
|---|---|
| Size | 21.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bc3d9ffe1feae09cb121f09e283ecea2fac29c12b2f1e7fcf93a17fd20305062
|
|
BLAKE2b-256 checksum How to use checksums |
ca3e590c96b41ae2605be80dd18c603eb700873fefae9988e17e2827fc69badd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
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 Nov 2, 2026.
Transparency logSecurity for sealkeeper 0.1.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 sealkeeper-0.1.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 Nov 2, 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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.