Steledger
Durable identity and memory for AI agents, anchored on the Emercoin blockchain.
Should I use this
Quality & Safety
Findings (8)
- HIGH
- LOWin read_record
- LOWin list_records
- LOWin whoami
- LOWin register_identity
- LOWin store_memory
- LOWin store_memory_batch
- LOWin transfer_records
Based on automated analysis of tool definitions and protocol compliance.
Context Cost
This is the approximate number of tokens consumed each time the server's tools are loaded into a model's context. Higher counts reduce the attention available for other tasks.
Install
One-Click Install
Add this to your `claude_desktop_config.json` file:
{
"mcpServers": {
"gateway": {
"url": "https://api.steledger.com/mcp"
}
}
}Remote endpoints
https://api.steledger.com/mcpstreamable-httpWhat it can do
Tool inventory
Tools (8)
š¢node_status
Check that the chain node behind this service is healthy and fully synced: its version, block height, header height, peer connections and sync state. It is an Emercoin node. Read-only, no sign-in required, no parameters. `synced` is not a comparison of the two heights ā it is true once the node's verification progress passes 0.9999, so it can still be false while `blocks` and `headers` already match. Trust that field, not the arithmetic. While it is false, a `read_record` may reflect an older state of the chain; writes still work, they simply confirm later. Also the way to make sense of expiry: `expires_in` on a record is denominated in blocks, and this tool reports the current height, so the two together are the only authoritative answer to when something lapses. A term is bought in days and charged at a flat 175 blocks each; the chain has been producing about 171 a day lately (8.4 min/block over the 103 days to 2026-09-22), so a term is close to its nominal length right now ā but that rate drifts, which is why you should read the blocks rather than convert to days.
Input Schema
{
"type": "object",
"properties": {},
"title": "node_statusArguments"
}Output Schema
{
"type": "object",
"properties": {
"version": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Version"
},
"blocks": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Blocks"
},
"headers": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Headers"
},
"verificationprogress": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Verificationprogress"
},
"connections": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Connections"
},
"synced": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Synced"
}
},
"title": "NodeStatus"
}š¢read_record(name)
Read one on-chain record by its full name ā an agent's identity (`ai:gh:<github_id>`) or a memory (`ai:gh:<github_id>:mem:<hash>`) written by `register_identity` / `store_memory`. Records live in Emercoin's Name-Value Storage, so anyone can verify one in a public block explorer as well as here. Returns the confirmed on-chain record, or a `pending` one still in the mempool ā the `status` field ('confirmed' | 'pending') distinguishes them. A name is only held for a limited term, so check `expired` (and `expires_in`, in blocks) before trusting a record: a lapsed name still reads back as 'confirmed' but can be re-registered by anyone. Read-only, no sign-in required; use `whoami` to find your own github_id. A name that has never been written is an error, not an empty record ā handle the failure, do not test the fields for null. `name` is the full NVS name and is capped at 512 bytes by the chain.
Input Schema
{
"type": "object",
"properties": {
"name": {
"description": "Full NVS record name to read. Identity records are 'ai:gh:<github_id>' (e.g. 'ai:gh:3772563'); memory records are 'ai:gh:<github_id>:mem:<sha256-hex>'. Any existing NVS name works.",
"title": "Name",
"type": "string"
}
},
"required": [
"name"
],
"title": "read_recordArguments"
}Output Schema
{
"type": "object",
"properties": {
"status": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Status"
},
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Name"
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Value"
},
"txid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Txid"
},
"time": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Time"
},
"address": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Address"
},
"address_is_mine": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Address Is Mine"
},
"operation": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Operation"
},
"days_added": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Days Added"
},
"expired": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Expired"
},
"expires_in": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Expires In"
},
"expires_at": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Expires At"
},
"pending_update": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Pending Update"
},
"pending": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"title": "Pending"
}
},
"title": "NvsRecord"
}š¢list_records(github_id, limit, offset)
List every record under one GitHub id ā its identity record `ai:gh:<id>` and all its memories `ai:gh:<id>:mem:<hash>` ā newest first, with each memory's content hash and metadata. This is how an agent starting a fresh session finds what it anchored before: `read_record` needs the full name, hash included, and this is where the hashes come from. Read-only, no sign-in needed to list any id; omit `github_id` to list your own when signed in. What comes back is the chain's view: confirmed records only (a write still in the mempool shows up after its block), expired ones included and flagged `expired` ā their names can be taken by someone else, so do not treat them as yours. Only fingerprints and metadata live here; the content itself stays wherever you stored it. Results are cached for about a minute, so a record confirmed seconds ago may take that long to appear.
Input Schema
{
"type": "object",
"properties": {
"github_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Numeric GitHub id whose records to list. Omit it to list your own (needs a signed-in session; `whoami` shows the id).",
"title": "Github Id"
},
"limit": {
"default": 50,
"description": "Records per page, 1ā200.",
"maximum": 200,
"minimum": 1,
"title": "Limit",
"type": "integer"
},
"offset": {
"default": 0,
"description": "Where the page starts; use `next_offset` from the previous page.",
"minimum": 0,
"title": "Offset",
"type": "integer"
}
},
"title": "list_recordsArguments"
}Output Schema
{
"type": "object",
"properties": {
"github_id": {
"title": "Github Id",
"type": "integer"
},
"total": {
"title": "Total",
"type": "integer"
},
"offset": {
"title": "Offset",
"type": "integer"
},
"next_offset": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Next Offset"
},
"records": {
"items": {
"$ref": "#/$defs/RecordEntry"
},
"title": "Records",
"type": "array"
}
},
"required": [
"github_id",
"total",
"offset",
"next_offset",
"records"
],
"$defs": {
"RecordEntry": {
"description": "One record under a GitHub id. `kind` is identity, memory or other;\n`content_hash` is set for memories, `address` for the identity record.",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"kind": {
"title": "Kind",
"type": "string"
},
"content_hash": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Content Hash"
},
"address": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Address"
},
"metadata": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Metadata"
},
"registered_at": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Registered At"
},
"expires_in": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Expires In"
},
"expired": {
"title": "Expired",
"type": "boolean"
}
},
"title": "RecordEntry",
"type": "object"
}
},
"title": "RecordList"
}š¢whoami
Report the current session's identity. Read-only, no sign-in required: an anonymous session gets `{authenticated: false}` with a hint (not an error), a signed-in one gets `{authenticated: true}` plus the GitHub-rooted id, login and tariff. Call it to confirm who you are before `register_identity` / `store_memory`; an anonymous caller must sign in (GitHub OAuth) first. `github_id` is the one field you usually need: every record name is built from it ā `ai:gh:<github_id>` and `ai:gh:<github_id>:mem:<hash>` ā so this is how you learn which names are yours to write and to read back. `tariff` is `free` for every account today; it governs the write limits, currently 10 writes per minute and 100 per trailing 24 hours per account, and writing needs a GitHub account at least 30 days old. `quota` says how many writes are left right now (and, for a young account, the date writes open); every write returns the same figures, so plan batches with them. Note what this tool does not do: it reports the session only, reading the token your client already holds without calling GitHub, and it proves nothing about control of an Emercoin address ā that is what signing a challenge at login is for.
Input Schema
{
"type": "object",
"properties": {},
"title": "whoamiArguments"
}Output Schema
{
"type": "object",
"properties": {
"authenticated": {
"default": null,
"title": "Authenticated",
"type": "boolean"
},
"github_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Github Id"
},
"github_login": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Github Login"
},
"tariff": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Tariff"
},
"quota": {
"anyOf": [
{
"$ref": "#/$defs/Quota"
},
{
"type": "null"
}
],
"default": null
},
"hint": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Hint"
}
},
"$defs": {
"Quota": {
"description": "Writes this account has left on its tier. `writes_open_on` (a UTC date)\nappears only while the GitHub account is too new to write.",
"properties": {
"writes_left_this_minute": {
"title": "Writes Left This Minute",
"type": "integer"
},
"writes_left_today": {
"title": "Writes Left Today",
"type": "integer"
},
"writes_open_on": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Writes Open On"
}
},
"title": "Quota",
"type": "object"
}
},
"title": "WhoAmI"
}š”register_identity(address, metadata)
Create or rotate your on-chain identity record `ai:gh:<github_id>`, binding an Emercoin address to your GitHub identity. Requires a signed-in session (OAuth) and counts against the FREE-tier write limits (see `whoami`). Run `whoami` first to confirm you are signed in; anchor memories under this identity afterwards with `store_memory`. Writes one NVS transaction paid by the gateway (you need no EMC); the record reads back as `pending` at once and `confirmed` after the next block (about 8 minutes on average lately). Idempotent ā calling again rebinds the address, and `metadata` is replaced rather than merged. Limits worth knowing before you call: `metadata` is stored verbatim in the record value alongside your github id, login and address, and the whole value must stay under 20 KiB ā the chain rejects more. Every 128 bytes of name plus value adds about 0.0001 EMC to the fee the gateway pays for you. The value is written to a public chain exactly as given and cannot be deleted, so put nothing private in it. `address` is not parsed or checked here ā any string is accepted, because control is proven later by signing a challenge at login, so a typo surfaces then rather than now. Returns the record name and the transaction id.
Input Schema
{
"type": "object",
"properties": {
"address": {
"description": "Emercoin address to bind to your GitHub identity, e.g. 'EVfAn...'. It is the anchor for later signature login ā you must control its key (control is proven when you sign a challenge at login, not here).",
"title": "Address",
"type": "string"
},
"metadata": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional JSON object stored verbatim in the identity record, e.g. {\"agent\": \"my-bot\", \"url\": \"https://...\"}. Omit if unused.",
"title": "Metadata"
}
},
"required": [
"address"
],
"title": "register_identityArguments"
}Output Schema
{
"type": "object",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"txid": {
"title": "Txid",
"type": "string"
},
"quota": {
"$ref": "#/$defs/Quota"
}
},
"required": [
"name",
"txid",
"quota"
],
"$defs": {
"Quota": {
"description": "Writes this account has left on its tier. `writes_open_on` (a UTC date)\nappears only while the GitHub account is too new to write.",
"properties": {
"writes_left_this_minute": {
"title": "Writes Left This Minute",
"type": "integer"
},
"writes_left_today": {
"title": "Writes Left Today",
"type": "integer"
},
"writes_open_on": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Writes Open On"
}
},
"title": "Quota",
"type": "object"
}
},
"title": "WriteResult"
}š”store_memory(content_hash, metadata)
Anchor a fingerprint of a memory or artifact on-chain as the NVS record `ai:gh:<github_id>:mem:<content_hash>`. Only the hash and your metadata are stored ā never the content, which you keep wherever you like (a file, a database, IPFS). What you get is a tamper-evident, timestamped proof that content with this hash existed, which anyone can verify later; `list_records` finds your earlier ones again. Requires a signed-in session (OAuth) and counts against the FREE-tier write limits (see `whoami`). Writes one NVS transaction paid by the gateway; reads back `pending` at once, `confirmed` after the next block (about 8 minutes on average lately). Not idempotent ā each distinct hash is a new record. Register your identity first ā nothing enforces it, the write succeeds either way, but a memory under an unregistered id anchors to nobody and proves correspondingly little. Writing a hash you already anchored renews that record: its term is extended (terms add up), and its metadata is replaced by what you pass now ā so pass the old metadata again if you want to keep it. That is the only renewal there is; a record left alone lapses after its term (see `expires_in`). Limits worth knowing before you call: `content_hash` becomes part of the record *name*, `ai:gh:<github_id>:mem:<hash>`, so it must look like a digest: 32ā128 characters of letters, digits, '_' or '-' (hex of any common algorithm, or an IPFS CID); anything else is refused with `invalid_hash` before any quota is spent. Beyond that shape it is never verified: nothing checks that it is the hash of anything, so a wrong digest anchors happily and proves nothing. `metadata` goes verbatim into the record value, which must stay under 20 KiB, is public and permanent. Returns the record name and the transaction id.
Input Schema
{
"type": "object",
"properties": {
"content_hash": {
"description": "Hash of the artifact/memory, e.g. a SHA-256 hex digest. It becomes the record's ':mem:<hash>' suffix; the content itself stays off-chain (e.g. IPFS) ā only this fingerprint is anchored.",
"title": "Content Hash",
"type": "string"
},
"metadata": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional JSON object stored with the record (note, source, tags, ā¦). Omit if unused.",
"title": "Metadata"
}
},
"required": [
"content_hash"
],
"title": "store_memoryArguments"
}Output Schema
{
"type": "object",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"txid": {
"title": "Txid",
"type": "string"
},
"quota": {
"$ref": "#/$defs/Quota"
}
},
"required": [
"name",
"txid",
"quota"
],
"$defs": {
"Quota": {
"description": "Writes this account has left on its tier. `writes_open_on` (a UTC date)\nappears only while the GitHub account is too new to write.",
"properties": {
"writes_left_this_minute": {
"title": "Writes Left This Minute",
"type": "integer"
},
"writes_left_today": {
"title": "Writes Left Today",
"type": "integer"
},
"writes_open_on": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Writes Open On"
}
},
"title": "Quota",
"type": "object"
}
},
"title": "WriteResult"
}š”store_memory_batch(records)
Anchor many fingerprints in ONE on-chain transaction ā the way to record a session's worth of artifacts without spending a write per minute on each. Each item becomes `ai:gh:<github_id>:mem:<content_hash>`, exactly as with `store_memory`: only hashes and metadata, never content. All or nothing: if any item is refused (a malformed hash, say), nothing is written. Requires a signed-in session. A batch of N counts as N writes against the FREE-tier limits (10 per minute, 100 per 24 hours), so at most 10 items fit in one call on a fresh minute; the result says how many writes are left. One transaction id comes back for the whole batch; each name reads back `pending` at once and `confirmed` after the next block.
Input Schema
{
"type": "object",
"properties": {
"records": {
"description": "1ā100 items, each {\"content_hash\": \"<digest>\", \"metadata\": {...}}; metadata is optional. Same rules as store_memory for each item.",
"items": {
"$ref": "#/$defs/MemoryItem"
},
"maxItems": 100,
"minItems": 1,
"title": "Records",
"type": "array"
}
},
"required": [
"records"
],
"$defs": {
"MemoryItem": {
"properties": {
"content_hash": {
"title": "Content Hash",
"type": "string"
},
"metadata": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Metadata"
}
},
"title": "MemoryItem",
"type": "object"
}
},
"title": "store_memory_batchArguments"
}Output Schema
{
"type": "object",
"properties": {
"txid": {
"title": "Txid",
"type": "string"
},
"count": {
"title": "Count",
"type": "integer"
},
"names": {
"items": {
"type": "string"
},
"title": "Names",
"type": "array"
},
"quota": {
"$ref": "#/$defs/Quota"
}
},
"required": [
"txid",
"count",
"names",
"quota"
],
"$defs": {
"Quota": {
"description": "Writes this account has left on its tier. `writes_open_on` (a UTC date)\nappears only while the GitHub account is too new to write.",
"properties": {
"writes_left_this_minute": {
"title": "Writes Left This Minute",
"type": "integer"
},
"writes_left_today": {
"title": "Writes Left Today",
"type": "integer"
},
"writes_open_on": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Writes Open On"
}
},
"title": "Quota",
"type": "object"
}
},
"title": "BatchWriteResult"
}š“transfer_records(to_address, irreversible, names, everything)
Hand your records over from the gateway's wallet to an address you choose, in one transaction. IRREVERSIBLE: once the block confirms, the gateway can no longer change, renew or return them ā nor can anyone else but the holder of `to_address`. Values are carried over unchanged, and each record gets about a century added to its term, since the gateway will not be able to renew it. Why you might: with an address whose key you hold, the records are really yours ā you can prove control by signing, and payments sent to your identity name reach you rather than the gateway. Changing a record afterwards needs your own Emercoin node and its fees. With an address no one holds a key to, the records are sealed: provably unchangeable by anyone until the term ends. If you only want proof that something existed at a given time, do not transfer ā a record held by the gateway is already dated. Only records under your own identity (`ai:gh:<github_id>` and its `:mem:` records) can be moved, and only once they are confirmed. Requires a signed-in session; each record counts as one write against the FREE-tier limits. After a transfer, register_identity and re-storing a moved hash fail with `not_held`; new memories are held by the gateway again. Returns the transaction id, the names moved, and a plain statement of what changes.
Input Schema
{
"type": "object",
"properties": {
"to_address": {
"description": "Emercoin address that will hold the records from now on. Any valid address is accepted: one whose key you hold, or one no one holds a key to, which seals the records.",
"title": "To Address",
"type": "string"
},
"irreversible": {
"description": "Must be true. Confirms you understand the gateway can never change, renew or return these records afterwards.",
"title": "Irreversible",
"type": "boolean"
},
"names": {
"anyOf": [
{
"items": {
"type": "string"
},
"maxItems": 100,
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Records to transfer, e.g. [\"ai:gh:123\", \"ai:gh:123:mem:<hash>\"] ā only your own. Omit and set `everything` instead to move them all.",
"title": "Names"
},
"everything": {
"default": false,
"description": "Transfer your identity and every live memory at once (up to 100). Use instead of `names`.",
"title": "Everything",
"type": "boolean"
}
},
"required": [
"to_address",
"irreversible"
],
"title": "transfer_recordsArguments"
}Output Schema
{
"type": "object",
"properties": {
"txid": {
"title": "Txid",
"type": "string"
},
"count": {
"title": "Count",
"type": "integer"
},
"names": {
"items": {
"type": "string"
},
"title": "Names",
"type": "array"
},
"to_address": {
"title": "To Address",
"type": "string"
},
"after": {
"title": "After",
"type": "string"
},
"quota": {
"$ref": "#/$defs/Quota"
}
},
"required": [
"txid",
"count",
"names",
"to_address",
"after",
"quota"
],
"$defs": {
"Quota": {
"description": "Writes this account has left on its tier. `writes_open_on` (a UTC date)\nappears only while the GitHub account is too new to write.",
"properties": {
"writes_left_this_minute": {
"title": "Writes Left This Minute",
"type": "integer"
},
"writes_left_today": {
"title": "Writes Left Today",
"type": "integer"
},
"writes_open_on": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Writes Open On"
}
},
"title": "Quota",
"type": "object"
}
},
"title": "TransferResult"
}Community
Evidence