Introduction
Readable is a registry of numbers and usernames for wallets and AI agents. A number is digits after a prefix, such as +0 4242; a username is a word under an extension, written word.extension. This page calls both names. A name points at a wallet, its holder owns it as an NFT on Base Sepolia, and any app can look it up without asking anyone.
A wallet address is 42 characters that nobody can say aloud or check by eye. A name is short enough to read out, type on a phone and recognise when it is wrong. A number carries no SIM, no country and no identity: it reaches a wallet and nothing else, so it is safe to publish.
This is a rehearsal on Base Sepolia, a test network. Names registered here are for testing and carry no value.
How it works
The whole registry is one contract. Registering a name mints a token to your wallet, and everything about the name is read from that contract.
- One registry on one chain. Every name lives on Base Sepolia, so a name has exactly one holder.
- A name is an NFT. It sits in your wallet like any other token, and you can transfer or sell it.
- It is held for a paid term. You pay for one to 10 years and renew before the term ends.
- It points at a wallet. At first that is the wallet holding it. The holder can point it at another.
- It carries records. A display name, an avatar, a website, where an AI agent answers, and addresses on chains that aren’t EVM.
- Only its holder changes it, and an editor it names. The registry has no function that takes a name from its holder, for the registry’s owner either: see what the owner can do.
Numbers
A number is written +0 42421230: the prefix +0, then the digits, unbroken. No country’s phone code starts with 0, so a Readable number can never be mistaken for a phone number.
- Length
- 2 to 12 digits
- Check digit
- The last digit, in numbers of 8 digits and longer
- Stored as
42421230- Shortest
- +0 42
- Spaces and dashes don’t count.
+0 4242 1230,+0-4242-1230and42421230are all +0 42421230. The registry stores the digits alone. - No number starts with 0. A zero in front would add nothing to the check digit and make a twin of the number after it, so
04242is read as +0 4242, its prefix written without the plus. - Short numbers are the rare ones. Only 100 numbers have 2 digits, and they are priced to match. Long numbers cost very little.
- Long numbers check themselves. From 8 digits, the last digit is worked out from the others, so one mistyped digit fails instead of reaching a stranger. See the check digit.
Usernames and extensions
A username is a word under an extension, written word.extension. The registry’s owner adds each extension onchain, and this site offers whichever ones the registry holds.
- Letters of one script. Latin, Greek, Cyrillic, Armenian, Hebrew, Arabic and Georgian letters, and Chinese, Japanese and Korean characters, with digits and hyphens. A hyphen can’t start or end a name.
- No look-alikes. A Greek, Cyrillic or Armenian name needs a letter that can’t pass for a Latin one, so nobody can register a copy of a Latin name drawn in other letters.
- Lowercase.
Aliceandaliceare the same name. - No emoji. A name with an emoji in it is refused under every extension.
These rules live in a contract of their own, which the registry’s owner can replace to take more scripts. New rules apply to new registrations: a name already held keeps its label, and is renewed by the length it was registered with.
An extension belongs to this registry alone. If it is spelled like the ending of another naming system, the two are unrelated, and the same name can belong to different people in each.
Get a name
- Search. Type the number or username you want on the home page. Its card says whether it is available, taken or reserved, and if it can’t be a name, why.
- Choose the years. Connect a wallet and pick one to 10 years. The card shows the total.
- Pay in ETH. The name is minted to your wallet in the same transaction, and points at your wallet from that moment.
It is one transaction from your wallet, which sends the exact price with it. The network fee on Base Sepolia is a small part of a cent of ETH on top.
No ETH yet? Where Coinbase’s onramp serves the chain, the payment dialog offers to buy it by card, delivered to the wallet you are paying from.
Prices
A name is priced by the year and by its length: the shorter it is, the more it costs. The registry holds the price list, and its owner can change it. A change applies to registrations and renewals from then on, never to years already paid for.
Manage a name
My names lists the names your wallet holds. The wallet holding a name, or the editor it names, signs each change, and while the site’s relayer is running it pays no fee: the wallet signs a message instead of a transaction and the relayer sends it. A smart wallet’s change goes to the site’s paymaster instead, when there is one. Renewing is a payment, so it is a transaction of its own.
- Point to a wallet
- Where the name delivers. Hold the name in a wallet you keep safe and point it at the one you use.
- Make primary
- The one name apps show in place of your address. A wallet has one primary name at a time.
- Records
- What the name says about you or your agent, saved together in one change.
- Editor
- Another wallet, such as the agent the name stands for, that can point it, fill in its records and set its other chains. It can’t move the name, and it goes when the name changes hands.
- Renew
- Adds years to the term.
- Remind me to renew
- A calendar file with a reminder a month and a week before the name expires. The site keeps no address and sends nothing.
Watching a name. Someone else’s name can be watched from its card. My names lists the names you watch, in this browser only, with the day each returns to the public unless its holder renews it, and a calendar file for that day.
Pay a name
Anyone can send ETH to a name from its page or its search card: +0 4242 instead of a 0x… address. The wallet the name points to is looked up again the moment you sign, so a name that changed hands since the page loaded pays its new holder, and a name that reaches nobody is refused.
Renewal and expiry
A name is yours until its term ends. Renewing adds years to the end of the term, so renewing early loses nothing. Anyone can pay for a renewal, not only the holder.
- During the term
- The name resolves, and its holder can point it, fill it in and transfer it.
- For 30 days after
- The grace period. The name reaches nobody and can’t be changed or transferred, but it is still held: it can be renewed, and nobody else can register it.
- After that
- Anyone can register it. It starts clean, pointing at its new holder, with no records.
A name stops delivering the moment its term ends, not at the end of the grace period. Renew before the date on the name’s card.
Transfers
A name moves like any NFT: send it from your wallet, or sell it on a marketplace. The registry resets it as it moves, so a name that changes hands never keeps delivering to whoever held it before.
- It points at its new holder. Wherever the previous holder had pointed it is forgotten.
- Its records are cleared. The previous holder’s avatar, website and agent payment address are gone.
- Its editor is removed. Whoever the previous holder let edit the name no longer can.
- It stops being the sender’s primary name. The new holder makes it theirs if they want it to be.
- The term stays. The new holder gets the years that were left. A name whose term has ended can’t be transferred.
The registry
Everything below reads one contract. It needs no key, no account and no permission: any wallet, app or agent can call it.
- Network
- Base Sepolia (chain id 84532)
- Token
- Readable (READ), ERC-721
- Paid in
- ETH, the price sent with the transaction
What to read
resolve(tokenId)- The wallet a name delivers to. The zero address when it reaches nobody.
lookup(namespaceId, label)- Everything about one name in a single read: whether it is valid, available, who holds it, until when, and its price.
tokenIdOf(namespaceId, label)- The token id of a name.
primaryNameOf(account)- The name a wallet shows in place of its address.
namesOf(account)- The token ids a wallet holds.
recordOf(tokenId)- A name’s label, where it points, and when its term began and ends. Where it points outlives the term: send with resolve.
text(tokenId, key)- One stored text of a name.
addr(tokenId, coinType)- Where a name receives on another chain, as ENS keeps it. An EVM chain answers with what resolve answers; any other is empty when none is set.
editorOf(tokenId)- The wallet its holder lets change the name, or the zero address.
generationOf(tokenId)- How many times the name has had a new holder, by registration or transfer. Key what belongs to one holder by the token id and this.
getNamespaces()- The number ranges and extensions, with their rules.
getPrices(namespaceId)- A namespace’s yearly prices, from the length in its priceFrom up.
SDK and API
The quickest way to resolve a name is readable-sdk, a small library on viem, or the API this site serves, built on it. Both read the registry and need no key. A number can be written any way people write it: +0 42421230, +0 4242 1230, 42421230 and +0-42421230 are the same.
npm install readable-sdk viemimport { createReadable } from 'readable-sdk'
import { baseSepolia } from 'viem/chains'
const readable = createReadable({ chain: baseSepolia, registry: '0x7c636853ECFA14e61d3A9Ec0c9501d66D17797D6' })
// '0x…', or null when it reaches nobody
await readable.resolve('+0 42421230')
// Its Bitcoin address, 'bc1q…', or null
await readable.getAddress('+0 42421230', 'btc')
// Its records: { name, job, social: { x }, … }
await readable.getRecords('+0 42421230')
// The primary name a wallet chose, or null
await readable.getName('0x…')
// All of the above in one answer
await readable.getProfile('+0 42421230')What it reads
resolve(name)- The wallet the name reaches: what most EVM chains use.
getAddress(name, coin)- Its address on another chain: btc, sol, eth, or a coin type.
getRecords(name)- Its records document.
getName(address)- The primary name a wallet chose.
getProfile(name)- The name, its holder, wallet, addresses, records and expiry, in one read.
Each answers null for a name that reaches nobody: never registered, expired, or one that can’t exist. The reads of one call go out together, as a single multicall.
From any other language, ask the API. It answers JSON to any origin.
curl https://www.readable.name/api/resolve/+0%2042421230
{
"name": "+0 42421230",
"tokenId": "…",
"owner": "0x…",
"address": "0x…",
"expiresAt": 1822493531,
"addresses": { "btc": "bc1q…", "sol": null },
"records": { "name": "Alice", "social": { "x": "alice" } }
}
curl https://www.readable.name/api/reverse/0x…
{ "address": "0x…", "name": "+0 42421230" }/api/resolve/<name>- What getProfile answers. 404 when the name reaches nobody.
/api/reverse/<address>- The wallet’s primary name, or null.
/api/verify/<name>- Which accounts in the name’s records are proven to be its holder’s.
/api/agents- Every live name whose records reach an AI agent.
/api/badge/<name>- The name’s badge, an SVG image.
Resolve when you send. The API’s answers are kept for 30 seconds, and a name can change hands in that time. Before money moves, use the SDK, which reads the registry at each call.
For AI agents
A name can reach an AI agent: its records name the endpoint where the agent answers, its ERC-8004 id and where it is paid. Agents lists every name that does.
Agents that speak MCP can use names without any code. Add this site’s MCP server, which needs no key:
{
"mcpServers": {
"readable": {
"url": "https://www.readable.name/api/mcp"
}
}
}Its tools
resolve_name- The wallet, addresses, holder, expiry and records of a name.
lookup_address- The primary name a wallet chose.
check_name- Whether a name is free, what it costs, or who holds it.
dial_agent- The endpoint, ERC-8004 id and payment address of the agent behind a name.
list_agents- Every agent with a name.
payment_details- The chain, token and recipient to pay a name now.
In code, getAgent(name) of the SDK answers the same as dial_agent.
Names for the agents your app runs. registerFor(namespaceId, label, to, years) buys a name straight into another wallet, the agent’s or its user’s, at the price register charges. Its holder can then make the agent its editor with setEditor(tokenId, agent), so the agent keeps its own endpoint and payment address up to date and can never sell the name. Keep a rating or a reputation by the token id and generationOf(tokenId), so it never passes to whoever holds the name next.
In wallets and apps
Where a name works outside this site. Readable is new, and few apps read its names yet: building a registry is the easy part, and a name is only as useful as the apps that read it. Those marked Next are built and wait for their launch, those marked Planned are not built yet, and the roadmap says what comes after. Any app can read names today through the SDK and API, without asking anyone.
- Tunnel
- New chat takes +0 4242 where it takes an address. The contact keeps the number and the wallet it reached, and warns if they part.
- Hup
- A profile shows the number its wallet chose as its primary name.
- Mini apps
- The site runs inside Hup with the wallet the app already has.
- ENS · Next
- Through a gateway on this site, a wallet that reads ENS resolves 4242.readable.eth to the wallet +0 4242 points to, and its text records to the name’s records.
- MetaMask · Next
- The Readable Snap resolves +0 4242 typed into MetaMask’s send field, and shows the name of a wallet that chose one.
- PayPost · Planned
- Send an invoice to +0 4242, and pay one.
Resolve a name
Resolving is three steps: find the namespace, reduce what was typed to the label the registry stores, and ask where it points. This is +0 42421230 with viem.
import { createPublicClient, http, parseAbi, zeroAddress } from 'viem'
import { baseSepolia } from 'viem/chains'
const registry = '0x7c636853ECFA14e61d3A9Ec0c9501d66D17797D6'
const abi = parseAbi([
'struct Namespace { uint256 id; uint8 kind; string name; uint8 minLength; uint8 maxLength; uint8 checkDigitFrom; uint8 priceFrom; bool paused; address manager; uint8 managerShare; string metadata; uint64 createdAt; }',
'function getNamespaces() view returns (Namespace[])',
'function tokenIdOf(uint256 namespaceId, string label) view returns (uint256)',
'function resolve(uint256 tokenId) view returns (address)',
'function primaryNameOf(address account) view returns (uint256 tokenId, string name)',
'function text(uint256 tokenId, string key) view returns (string)',
'function addr(uint256 tokenId, uint256 coinType) view returns (bytes)',
])
const client = createPublicClient({ chain: baseSepolia, transport: http() })
const read = (functionName, args) => client.readContract({ address: registry, abi, functionName, args })
// 1. Find the namespace. Numbers are kind 1, named by the digits of their prefix
const namespaces = await read('getNamespaces')
const numbers = namespaces.find((namespace) => namespace.kind === 1 && namespace.name === '0')
// 2. Reduce what was typed to the stored label: the prefix and every separator go
const label = '+0 42421230'.replace(/^\+0/, '').replace(/\D/g, '')
// 3. Resolve. The zero address means nobody: never registered, or expired
const tokenId = await read('tokenIdOf', [numbers.id, label])
const wallet = await read('resolve', [tokenId])
if (wallet === zeroAddress) throw new Error('This number reaches nobody')For a username, the label is the word before the dot, in lowercase and in Unicode’s composed form: word.normalize('NFC').toLowerCase(). The namespace is the one of kind 0 named by the extension.
Deliver with resolve, not with lookup. lookup answers what a search card shows, and keeps naming the holder through the grace period. resolve stops the moment the term ends.
From a contract
A contract on Base Sepolia resolves a number in one line. This one pays ETH to whoever a number reaches, and refuses when it reaches nobody.
interface IReadable {
function tokenIdOf(uint256 namespaceId, string calldata label) external view returns (uint256);
function resolve(uint256 tokenId) external view returns (address);
}
contract PayANumber {
IReadable constant READABLE = IReadable(0x7c636853ECFA14e61d3A9Ec0c9501d66D17797D6);
// The id of the number namespace, from getNamespaces()
uint256 constant NUMBERS = 1;
error NobodyThere(string digits);
error NotDelivered();
/// @param digits The number without its prefix or spaces
function pay(string calldata digits) external payable {
address to = READABLE.resolve(READABLE.tokenIdOf(NUMBERS, digits));
if (to == address(0)) revert NobodyThere(digits);
(bool delivered, ) = to.call{value: msg.value}("");
if (!delivered) revert NotDelivered();
}
}tokenIdOf reverts for a namespace that doesn’t exist and for a label with a character its namespace never allows. A number that is well formed and was never registered resolves to the zero address.
From a wallet to its name
To show a name where you would show an address, read the wallet’s primary name. The registry answers only with a name the wallet holds now and whose term has not ended.
const [tokenId, name] = await read('primaryNameOf', [account])
// No token id and an empty name: the account has chosen none, or the one it chose has expired
if (tokenId === 0n) showAddress(account)
else showName(name)- The name arrives ready to show. The registry writes a number as this site does,
+0 42421230, and a username asword.extension. - Holding is not pointing. A primary name says who the wallet is. If you need the name to deliver to that same wallet, compare
resolve(tokenId)with it. namesOfincludes names whose term has ended. They stay in the wallet until someone else registers them. CheckexpiresAtinrecordOf.
Records
A name’s records are one JSON document, stored under the text key records. One document means a holder saves everything in a single transaction and an app reads it in a single call.
{
"v": 1,
"avatar": "ipfs://…",
"name": "Alice",
"job": "Designer",
"about": "Making things readable",
"email": "alice@example.com",
"url": "https://alice.example",
"location": "Lisbon",
"birthday": "1990-05-14",
"social": {
"x": "alice",
"telegram": "alice_lisbon",
"github": "alice",
"hup": "alice",
"up": "0x…"
},
"agent": {
"endpoint": "https://alice.example/mcp",
"erc8004": "412",
"payment": "0x…"
}
}avatar- Photo. A link to an image, or one uploaded here and pinned on IPFS.
name- Display name
job- Job title
about- About
emailurl- Website
location- Location. A city or a country, never a street address.
birthday- Birthday
social.x- X handle, without the @. Read at https://x.com/<handle>.
social.telegram- Telegram handle, without the @. Read at https://t.me/<handle>.
social.github- GitHub handle, without the @. Read at https://github.com/<handle>.
social.hup- Hup handle, without the @. Read at https://www.hup.social/@<handle>.
social.up- A LUKSO Universal Profile, by its address. The contact card shows the name and picture it publishes.
agent.endpoint- Agent endpoint. Where the agent answers.
agent.erc8004- Agent id (ERC-8004)
agent.payment- Agent payment address
const stored = await read('text', [tokenId, 'records'])
// Written by the holder, so anything can be in it
let records = {}
try {
records = JSON.parse(stored || '{}')
} catch {}
const endpoint = records.agent?.endpoint- Every field is optional, and a name with no records answers with an empty text.
- Your app can keep its own keys. This site leaves the keys it doesn’t know as they are when a holder saves.
- Records are what the holder says. Nothing checks them. Treat a link or a payment address as a claim by whoever holds the name.
- Where a name points is not a record. It is what
resolveanswers, and the holder sets it withsetTarget. - Addresses on other chains are not records either. They have a call of their own: see other chains.
Other chains
A name can also carry an address on chains that aren’t EVM, kept as ENS keeps them (ENSIP-9). addr(tokenId, coinType) answers one, in the chain’s own binary form, and empty bytes when the holder set none. Base Sepolia and most other EVM chains use what resolve answers, and addr gives the same for any EVM chain’s coin type.
Coin types the site offers
- Bitcoin
0- Solana
501
A coin type is a chain’s number in SLIP-44, the list wallets use to tell chains apart in their key paths (m/44'/0'/… for Bitcoin). The registry takes any coin type but an EVM chain’s, so another chain is a change to an app, not to the contract.
A coin type is not a chain id. A chain id tells EVM networks apart so a transaction signed for one can’t run on another; a coin type names a chain’s address format. Ethereum’s chain id is 1 and its coin type 60; Bitcoin and Solana have no chain id. ENS gives an EVM chain the coin type 0x80000000 | chainId (ENSIP-11), so Base Sepolia, chain 84532, is 2147568180. Readable stores nothing under those: asked for one of them, or for 60, addr answers with the address resolve gives, and setAddrs refuses them.
Turn the bytes into text with @ensdomains/address-encoder, whose coder for each chain knows its coin type.
import { btc, sol } from '@ensdomains/address-encoder/coins'
import { hexToBytes } from 'viem'
// Each coder knows its chain's coin type and turns the stored bytes back into text
const addressOn = async (coin) => {
const bytes = await read('addr', [tokenId, BigInt(coin.coinType)])
// 0x: the holder set none on that chain
return bytes === '0x' ? null : coin.encode(hexToBytes(bytes))
}
const bitcoin = await addressOn(btc) // 'bc1q…'
const solana = await addressOn(sol)The holder sets them with setAddrs: a token id and a list of coin types with their bytes. The registry stores the bytes as they come and never reads them, so check an address before sending it.
import { btc, sol } from '@ensdomains/address-encoder/coins'
import { bytesToHex, parseAbi } from 'viem'
const abi = parseAbi([
'struct CoinAddress { uint256 coinType; bytes value; }',
'function setAddrs(uint256 tokenId, CoinAddress[] entries)',
])
// Several chains in one transaction. Empty bytes remove one;
// a chain left out keeps its address
await wallet.writeContract({
address: registry,
abi,
functionName: 'setAddrs',
args: [
tokenId,
[
{ coinType: BigInt(btc.coinType), value: bytesToHex(btc.decode('bc1q…')) },
{ coinType: BigInt(sol.coinType), value: '0x' },
],
],
})- They belong to one holding, as records do. A transfer or a new registration starts with none, and an expired name answers with none.
- They are what the holder says. Nothing proves the holder controls the address.
Token ids and NFT data
A name’s token id is the hash of its namespace id and its label, so an app can work it out without a call.
import { encodePacked, keccak256 } from 'viem'
// keccak256(abi.encodePacked(namespaceId, label)) in Solidity
const tokenId = BigInt(keccak256(encodePacked(['uint256', 'string'], [1n, '42421230'])))What a wallet or a marketplace shows for the token comes from the link the registry gives as tokenURI. The registry’s owner sets where that link leads, and this site serves it: the name as its title, and a picture of the name.
/nft/<id>- The token’s title, description, picture and traits, as JSON.
/nft/<id>/image- The picture, as SVG.
The check digit
Numbers of 8 digits and longer end in a check digit, worked out from the digits before it by the Luhn formula that payment cards use. You choose all the digits but the last.
Of the ten numbers that differ only in their last digit, one is valid. A single mistyped digit, and most swaps of two neighbours, give a number that can’t exist, so the mistake is caught before anything is sent.
// Luhn: from the right, double every second digit, and the sum must end in 0
const luhnValid = (digits) => {
let sum = 0
for (let i = 0; i < digits.length; i++) {
let digit = Number(digits[digits.length - 1 - i])
if (i % 2 === 1) {
digit *= 2
if (digit > 9) digit -= 9
}
sum += digit
}
return sum % 10 === 0
}
// The one digit that completes the digits before it
const checkDigitFor = (body) => [...'0123456789'].find((digit) => luhnValid(body + digit))
const valid = (digits) => digits.length < 8 || luhnValid(digits)The registry runs the same check: lookup answers a wrong check digit with the validity InvalidCheckDigit (6), and register refuses it. Check in your app first, and the reader learns about the typo without a call.
Events
An indexer follows these.
NameRegistered- A name was registered, by payment or as a grant from the owner.
NameRenewed- A term was extended.
TargetChanged- A name points at another wallet. A transfer emits it too.
PrimaryNameChanged- A wallet chose its primary name, or lost it. Token id 0 means none.
TextChanged- A stored text was written.
AddressChanged- An address on another chain was set or removed. An empty value means none.
EditorChanged- A holder named an editor, or removed it. The zero address means none.
Transfer- The ERC-721 event: a name changed hands.
NamespaceAdded, PricesUpdated- What the registry sells, and for how much.
Treat a transfer and a new registration as a reset. Both clear the name’s stored texts, addresses and editor without a TextChanged, AddressChanged or EditorChanged for each. An index that keeps the old values would show the previous holder’s records.
In your app
A name can change hands or run out. Five habits keep that from hurting anyone.
- Resolve when you use it. Ask the registry at the moment of sending, not once at sign-up.
- Remember who it was. When someone saves a contact, keep the number together with the address it reached, and warn them if the two no longer match.
- Nobody means stop. The zero address is never somewhere to send.
- Check the digits first. Validate a number’s length and check digit before asking the chain.
- Write it as one number. The prefix, a space, then the digits with no spaces between them: +0 42421230.
What the owner can do
The registry has an owner: one wallet that decides what is sold. What it can do ends where a registered name begins.
The owner can
- Add extensions
- And number ranges, each with its rules.
- Replace the name rules
- Which characters a username can hold, for registrations from then on. A name already held keeps its label.
- Change prices
- For registrations and renewals from then on. A renewal costs the price of its day, so a price its holder won’t pay ends the name with its term.
- Reserve names
- Keep names nobody holds out of public registration.
- Grant a name
- Register an available name for someone without payment.
- Pause
- Stop new registrations in one namespace, or in all. Renewals never stop, and names already held keep working.
- Change settings
- The grace period, where payments go, the forwarder, and where the NFT’s title and picture come from.
The owner can’t
- Take a name
- The registry has no function that moves or ends a name someone holds, and no forwarder can move one.
- Change a name
- Where it points and its records are changed by its holder and the editor it names, its primary status by its holder alone, with the one exception below.
- Shorten a term
- Years paid for are kept.
The forwarder. A holder’s or an editor’s change to a name without ETH (where it points, its primary status, its records, its other chains) reaches the registry through one forwarder contract, which the registry trusts to say who signed it. It is believed for those changes alone: purchases, transfers, approvals, the choice of an editor and every owner function answer to the wallet that calls them, so no forwarder can move a name, name an editor or act as the owner. The owner can replace the forwarder, and one it wrote could change any live name’s target, records and addresses. That is the setting to watch: every change is announced by TrustedForwarderUpdated.
Roadmap
NumbersNumbers from 2 digits, with search, registration, renewal, records and transfers.
UsernamesWords under extensions, written word.extension, which the registry’s owner adds onchain.
Add a contact by numberIn Tunnel, the encrypted chat: type a number where you would paste an address, and be warned when the number changes hands.
Bill and pay a numberIn PayPost: send an invoice to a number, and pay one.
Dial an agentA number that reaches an AI agent, with its endpoint and payment address in the number’s records, found through the MCP server and the agents directory.