stellar-agentgate
v0.2.0
Published
Policy-signer-gated MCP bridge for Soroban contracts, packaged as a stellar-cli plugin
Downloads
343
Maintainers
Readme
Built for the "CLI Plugins for Agents" bounty's Soroban-to-MCP track.
See it work
(click for the full version with music — docs/videos/full-demo.mp4)
The clip above is real: an agent-held signer calling example-vault through
the policy-gated wallet on testnet, animated from the actual outcomes in
scripts/demo.ts -- two calls allowed, two rejected, 100%
enforced on-chain.
Why
Most "agent calls a contract" demos hand the agent an unconstrained key. This
project's thesis: the safety boundary belongs on-chain, as a
passkey-kit smart-wallet Policy signer, not in application code the agent
could route around. An agent's Ed25519 key is registered on the wallet with
SignerLimits that make it powerless alone -- every call to a target
contract additionally requires agent-policy to co-sign, and that policy
enforces a per-wallet, on-chain-configured allow-list ({contract, method,
capped-argument}) plus a cumulative rolling-window spend cap. An MCP server
exposes exactly the methods the policy currently allows -- generated from the
contract's own on-chain spec, not hardcoded -- so an agent's blast radius is
bounded by the policy even if its key leaks.
Architecture
Full diagrams (architecture, signing flow, policy decision) live in
docs/ -- see the Documentation section below.
The allow/deny outcome comes down to one on-chain check:
Layout
contracts/
agent-policy/ -- generalized Policy signer (allow-list + spend cap)
example-vault/ -- canonical demo contract (deposit/withdraw/admin-only/touch)
src/
scval-encoders.ts -- shared, unit-tested Soroban ScVal encoders (Signer/SignerLimits/AllowedCall/...)
soroban-auth.ts -- signs a passkey-kit wallet's custom __check_auth entries
soroban-tx.ts -- simulate -> sign -> re-simulate -> submit engine
spec-to-tools.ts -- reads a policy's allow-list + contract specs, emits MCP tools
mcp-server.ts -- MCP server wiring the above together
init.ts -- `stellar agentgate init`: self-service onboarding against the canonical policy
cli.ts -- `stellar-agentgate` plugin entrypoint
testing/
deploy-fresh-env.ts -- deploys a pristine demo environment; used by reset-demo.ts AND the e2e suite
scripts/
setup-wallet.ts, restrict-agent-signer.ts, reconfigure-policy.ts -- one-time demo bootstrap
reset-demo.ts -- one-command fresh redeploy (via deploy-fresh-env.ts), writes .env
demo.ts -- scripted allow/reject proof against testnet
video/
src/ -- Remotion compositions/components for the videos and stills above
scripts/ -- WAV synth, render-all pipeline (mp4 -> gif -> png)
test/
unit/ -- fast, no network (bun test)
e2e/ -- real testnet transactions, run on demand (bun run test:e2e)Documentation
Full docs (architecture, security model, MCP tool generation, testing) are
live at acachete.mintlify.site,
built from docs/ as a Mintlify site, with diagrams
generated via Graphviz (scripts/diagrams/) -- Python scripts producing
vertical, professionally-styled SVGs, no manual diagramming.
bun run docs:dev # local preview at http://localhost:3000
bun run docs:validate # strict build validation + broken-link check
bun run diagrams # regenerate docs/images/diagrams/*.svg from scripts/diagrams/*.pyWhy a custom auth-signing module
stellar-cli/stellar-sdk can only auto-sign auth entries for plain Stellar
accounts. A passkey-kit smart wallet is a custom account -- its
__check_auth expects a bespoke Signatures(Map<SignerKey, Signature>)
argument that no generic tool can construct. src/soroban-auth.ts builds and
signs that structure directly against the XDR the wallet's own Rust source
defines. Two further, non-obvious protocol details this project had to work
out empirically (see inline comments):
- Signing a custom-account auth entry with a real signature costs more CPU/resources than the unsigned entry simulation priced -- a second simulation pass after signing is required to get an accurate fee.
- An agent's Ed25519 signer must have restrictive
SignerLimits(a required Policy co-signer for the target contract), notSignerLimits(None)-- otherwise its own signature alone satisfies__check_authand the policy is never consulted at all.
Running it
Requires stellar-cli, Rust + wasm32v1-none target, and Bun 1.3+.
# contracts
stellar contract build
cargo test --workspace
# TS layer
bun install
bun run typecheck
bun run test # unit tests: fast, no network, no testnet cost
# demo wallet bootstrap (testnet) -- deploys fresh contracts + wallet, writes .env
bun run reset-demo
bun run demo # scripted allow/reject proof against what reset-demo just deployed
# e2e tests: deploy their OWN fresh environment per file, real testnet
# transactions, take a couple of minutes and are never part of `bun test`
bun run build # test/e2e/mcp-protocol spawns the compiled dist/cli.js
bun run test:e2e
# as an MCP server (what an agent actually talks to) -- published on npm,
# no clone required:
npm install -g stellar-agentgate # or: bun link / bun install -g . from source
# wire up YOUR OWN wallet against the canonical agent-policy (see
# docs/deployments-canonical.md) -- no policy contract of your own to deploy:
stellar agentgate init --owner owner # requires `stellar keys generate --network testnet --fund owner` first
stellar agentgate policy-explain # human-readable: what can this agent do?
stellar agentgate mcp # MCP server over stdio
# regenerating the videos/images above (separate Bun project under video/)
bun run video:music # regenerate the original background track
bun run video:render # regenerate all mp4/gif/png assets in docs/.env (gitignored) holds RPC_URL, NETWORK_PASSPHRASE,
OWNER_SECRET/AGENT_SIGNER_SECRET, and the deployed contract IDs --
bun run reset-demo writes it for you; see .env.example for the shape if
setting it up by hand. Since .env never gets committed, the same run also
(re)writes docs/deployments-testnet.md --
that's the checked-in record of which contract IDs are currently live on
testnet.
Roadmap (not built, documented only)
Fleet mode: one MCP server, one policy contract per managed wallet, routing agent calls across many wallets/contracts instead of the single canonical wallet this submission demonstrates. Same policy-signer mechanism, scaled out -- deliberately out of scope for this submission's timeline.



