Skip to main content

Generated, not hardcoded

Point stellar-agentgate at any passkey-kit wallet and agent-policy instance, and it exposes exactly the methods that wallet’s policy currently allows — no more, no less. This comes from two on-chain reads at server start, implemented in src/spec-to-tools.ts:
1

Read the policy's allow-list

A read-only simulate of agent-policy.get_config(wallet) returns the configured Vec<AllowedCall> — each entry naming a {contract, fn_name, amount_arg_index}.
2

Fetch each target contract's own spec

server.getContractWasmByContractId() + contract.Spec.fromWasm() gives the REAL parameter names and types for each allowed method — sourced from the contract’s own compiled spec, never guessed.
3

Build one MCP tool per allowed method

A JSON Schema is generated from the real parameter types, and the tool’s description states its spend cap (or confirms it’s uncapped) in plain language.
If the wallet owner changes the allow-list on-chain — adds a method, tightens a cap — the next MCP server start reflects that immediately. There is no separate configuration file to keep in sync.

Address auto-fill

Every AllowedCall this project’s demo contract exposes takes the wallet’s own address as its primary parameter (deposit(from, amount), withdraw(to, amount)) — the overwhelmingly common shape for a policy-gated call. Rather than requiring an agent to know and pass a 56-character C.../G... address just to say “deposit 100,” any Address-typed parameter the caller omits defaults to the wallet’s own address:
src/mcp-server.ts
An agent can still pass an explicit address to override this — the default only fills gaps, it never overwrites a value the caller provided.

Spend-cap hints on the amount argument itself

Beyond the tool-level description ((spend-capped by argument index N) or (uncapped)), the JSON Schema property for that specific argument also carries the note — so an agent inspecting just that one field, not the whole tool description, still knows it’s the number that counts against the wallet’s rolling-window allowance:
Built by describeAmountArg in src/spec-to-tools.ts, keyed off the same amount_arg_index policy__ itself reads on-chain — the hint and the enforcement can never drift out of sync with each other.

Rejections are ordinary tool errors

When the policy rejects a call — cap exceeded, method not allowed — the MCP server does not crash or retry. It returns a normal MCP tool result with isError: true and the on-chain error surfaced in plain text:
An agent (or the human watching it) sees a clean, expected failure — not an unhandled exception.

Try it directly

The policy-explain subcommand runs the same discovery logic and prints it in plain text, useful for a human auditing what an agent can currently do before trusting it: