Lucent
MCP server that enables AI signing agents to pre-flight transactions by checking ERC-7730 descriptor safety, explaining function semantics, and scanning contracts for danger primitives.
README
Lucent
Tooling to author, verify, and attest ERC-7730 Clear Signing descriptors for the public registry that compatible wallets read from.
A descriptor is a JSON file that tells a wallet how to render a contract call in plain language, so users see what they are signing instead of raw hex. Lucent covers the full path: find contracts that lack a descriptor, write and harden one, check it beyond schema validity, prove it against real transactions, and produce an ERC-8176 attestation.
For signing agents: the MCP server
An AI agent about to sign a transaction faces exactly the question Lucent's
checks answer — is this call safe, and does the screen a human would see actually
describe what it does? scripts/mcp_server.py exposes that as an MCP server
(stdlib JSON-RPC over stdio) so an agent can pre-flight a signature:
check_descriptor— one combined gate over an ERC-7730 descriptor: the audit grade (screen shows the right fields), the comprehension grade (a plain-language consequence sentence + risk tier per function), and the danger scan (structural primitives a clear screen can't make safe). Returns averdict.gateofsafe_to_present/review/blockto branch on — a CRITICAL danger primitive blocks regardless of how benign the sentence reads.explain_signature— the actor→action→object sentence + risk tier + reason for one function, to render a human confirmation before a signature.scan_contract— danger-scan a deployed contract by address (fetches the verified ABI from Sourcify), so an agent can assess a contract before any transaction is built.
make mcp # or: .venv/bin/python scripts/mcp_server.py
Register it as a stdio MCP server pointing at scripts/mcp_server.py from the
repo root (see mcp.json). Same transport shape as the sibling Groundcheck and
Seiche servers.
Install
make setup # creates .venv and installs requirements (Python 3.12+)
Most stages that read on-chain data need a free Etherscan API key:
export ETHERSCAN_API_KEY=...
Pipeline
| Stage | Script | Purpose |
|---|---|---|
| Discover | discover.py |
Classify candidates: verified, signable, and uncovered |
| Fetch ABI | fetch_abi.py |
Verified ABI from Sourcify (a registry requirement) |
| Resolve proxy | resolve_proxy.py |
Cache an implementation ABI under a proxy address |
| Generate | erc7730 generate |
Bootstrap a draft descriptor |
| Lint | erc7730 lint |
Schema, selectors, device limits, ABI consistency |
| Audit | audit.py |
Grade the descriptor on screen trustworthiness |
| Comprehend | comprehend.py |
Grade the descriptor on human comprehension risk |
| Danger | danger.py |
Flag structural danger primitives a clear screen can't make safe |
| Verify | semverify.py |
Check the screen against real on-chain movements |
| Prove | preview.py, fetch_tx.py |
Render the screen and build real test vectors |
| Package | to_submission.py |
Registry-form output under dist/, gated on audit grade |
| Attest | attest.py |
ERC-8176 attestation over the descriptor hash |
| Watch | watch.py |
Monitor merged descriptors for drift |
| Review | review.py |
All checks composed into one publishable report |
A common.py module holds the shared Sourcify and Etherscan clients and ABI
utilities.
Audit
erc7730 lint checks that a descriptor is well-formed. audit.py checks whether
the on-device screen would mislead a user, which lint does not:
- CRITICAL: a payable function that never shows
@.value, or atokenAmountwith no known token. - HIGH: a signable function with no intent or no visible field, or an address shown as raw hex.
- MEDIUM/LOW: labels or intents past the device character limits, missing interpolated summaries.
It reports a letter grade. to_submission.py refuses to package below grade B.
A raw generated draft of the ENS controller scores F; the hardened descriptors
score A.
Comprehension risk
Lint checks that a descriptor is well-formed; audit.py checks that the screen
shows the right fields. Neither asks the question that
"What I Sign Is Not What I See" shows is the
real failure: users mis-understand a technically-correct screen. Its studies
found people fixate on the amount and recipient and miss scope, delegation, and
unlimited allowances — and that a bare field list, even a complete one, leaves
comprehension at chance on the dangerous cases. Its Signature Semantic Decoder
cut false approvals on unlimited-allowance and phishing transactions by 73% and
46% by rendering an actor→action→object sentence and a risk tier with a reason.
comprehend.py brings that to the descriptor. For each signable function it
emits:
- a consequence sentence — who acts on what, plus conditions, built from the ABI and the descriptor's own labels so it renders what the wallet will show: "You let {Operator} transfer ANY of your tokens in this contract, at any time, until you revoke it."
- a risk tier with the clause that earned it — the paper's users rejected
bare labels and demanded the why. Patterns scored are the ones the study found
people miss: operator grants (
setApprovalForAll), ERC-20 allowances (flagged distinctly from ERC-721 token-id approvals, sinceapprove(address,uint256)reads identically but means different things), permits (off-chain, invisible in history), admin/upgrade authority, and raw-hex recipients (the address-poisoning surface).
An unrecognised function with no on-screen intent is reported as an unexplained
screen (a caution), never silently cleared — an unexplained screen invites blind
approval, which is the failure the paper measures. Run it with make comprehend DESC=…; NameWrapper's setApprovalForAll and the controller's
transferOwnership both surface as CRITICAL comprehension risks that lint and
audit pass.
Danger surface
Audit asks whether the screen shows the right fields; comprehend asks whether the
human understands them. danger.py asks the third question: can this function,
by construction, do something a clear screen still can't make safe? A descriptor
can render a perfectly honest sentence for execute(address target, bytes data) —
"Call {target} with {data}" — and that call can still drain the wallet, because
the primitive itself is unbounded.
Runtime systems catch this by instrumenting transaction-trace properties
(arXiv:2408.14621: arbitrary
CALL/DELEGATECALL/SELFDESTRUCT in the trace). danger.py lifts the same
property set to static ABI analysis, so the danger is named before anyone
signs:
- CRITICAL — arbitrary external call (a call-family name, or a target-address
- calldata-blob signature),
delegatecall(foreign code in this contract's context), self-destruct, and upgrade-and-execute.
- calldata-blob signature),
- HIGH — unbounded delegation (
setApprovalForAll), authority transfer (ownership / admin / role). - MEDIUM — value sweep to a caller-supplied address.
Precision is the whole game: a danger scan that cries wolf on safeTransferFrom
is worse than none. The detector distinguishes calldata from data-as-content by
parameter name (target+data, not any address-plus-bytes), excludes to
(a recipient, not a callee), and whitelists the standard ERC receiver hooks — so
the shipped ENS bundle raises zero false arbitrary-call flags while a real
execute(target,data) drainer is still caught. --strict exits non-zero on any
CRITICAL.
Semantic verification
Lint proves a descriptor is well-formed, not that its summary is honest. A descriptor can pass every schema check and still render a benign screen for a call that sends assets elsewhere.
For each test vector, semverify.py fetches the mined receipt (the record of
what actually moved), extracts the asset movements and approvals (ETH, ERC-20,
ERC-1155, ApprovalForAll), and checks the screen against them: every real
recipient and operator is shown, ETH spent is shown, and the field labelled as
the recipient matches the address that received the asset.
Worked example, a safeTransferFrom descriptor with the To and From labels
swapped:
| Check | Result |
|---|---|
erc7730 lint |
pass (schema-valid, both fields shown) |
audit.py |
grade A (structurally correct) |
semverify.py |
divergence (labels the sender as recipient) |
The receipt is exact for mined transactions. The recipient check is heuristic on field labels; it catches recipient hiding and label spoofing, not every possible mismatch.
Unmined calls — fork replay. A brand-new descriptor for a call that has never
been mined (a fresh contract, a rarely-used function) has no receipt to check
against. forkreplay.py closes that gap: given a call spec
{signer, function, args, value} it forks mainnet at HEAD into a local anvil,
impersonates the signer, executes the call against real on-chain state, and reads
back the standard eth receipt. That (tx, receipt) pair is handed to
semverify.verify_one unchanged — so a label swap or hidden recipient on an
unmined call is caught by the identical, tested code path, not a second
implementation. Run it with make semverify DESC=… SIMULATE=1 on a test file
whose vectors carry a call object instead of a txHash. It needs anvil +
cast (foundryup) and an RPC URL (ETH_RPC_URL); without them the call vector
is skipped with a reason, never silently passed.
Reviewing a registry PR
The clear-signing governance model asks security reviewers to check descriptor
PRs "for correctness, clarity, and adversarial edge cases, then publish your
findings clearly." review.py is that role as one command: it composes lint,
the screen audit, the comprehension grade, the danger scan, and (when test
vectors and an ETHERSCAN_API_KEY are available) semantic verification into a
single markdown report ready to post on the pull request:
make review DESC=path/to/calldata-Contract.json OUT=review.md
The overall verdict is the same gate the MCP server exposes to signing agents
(safe_to_present / review / block), so a human review and an agent
pre-flight can never disagree. Checks that cannot run are reported as explicit
skips with the reason, never silently passed. To review a descriptor from a
registry PR, fetch its ABI first (make fetch CHAIN=<id> ADDR=<address>) so
the audit runs against the verified on-chain ABI.
Post-quantum co-signing
A descriptor hash is keccak256, which is quantum-safe. The ECDSA signature over
it is not, and attestations are long-lived. attest.py --pq adds a post-quantum
signature over the same hash so the attestation stays verifiable if the
signature scheme is broken. The hash is unchanged; only the signature scheme is
added.
| Scheme | Standard | Signature size |
|---|---|---|
ml_dsa_65 (default) |
FIPS 204 | ~3.3 KB |
ml_dsa_44 / ml_dsa_87 |
FIPS 204 | ~2.4 / ~4.6 KB |
falcon_512 |
FIPS 206 draft | ~0.65 KB (float and side-channel risk) |
sphincs_sha2_128s_simple |
FIPS 205 | ~7.9 KB (hash-based) |
The signature binds the exact descriptor hash. Keys are read from LUCENT_PQ_*
env vars or a gitignored .attester-keys/ directory, written owner-only. No
cryptographically-relevant quantum computer exists yet and there is no standard
for post-quantum attestations, so this is forward positioning, not a current
requirement.
Current state
Three ENS descriptors, each grade A and lint clean against the on-chain ABI,
packaged under dist/registry-pr/ens/:
| Descriptor | Functions | Test vectors |
|---|---|---|
ETHRegistrarController (0x2535…303b) |
7 | 8 |
NameWrapper (0xD441…6401) |
26 | 6 |
| BulkRenewal | 1 | 3 |
Test vectors are real historical transactions, built with
fetch_tx.py <chain> <address> <descriptor>.
A registry PR should be submitted by or on behalf of the contract's owner. The remaining step for the ENS descriptors is that authorization, not code.
Attester registration
attest.py --profile writes an auditor profile
(auditors/eip155-1-<address>/profile.json) for a registry PR. Signing an EAS
offchain attestation needs the ERC-8176 schema UID (published on clearsigning.org)
and an attester key. Without them, attest.py writes an unsigned evidence
bundle so the pipeline can run end to end first.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。