asn-lookup
Local IP↔AS lookups from the IPinfo Lite database, as
a CLI and a local MCP server. Downloads the free IPinfo Lite database once,
builds a compact local index, and answers lookups fully offline — so AI tools
can investigate IP addresses without hammering the ipinfo API.
- IP → AS: given an IP, return its ASN, AS name, AS domain, and
country/continent.
- AS → IP: given an ASN, return every IP prefix it announces.
- CLI + MCP: the same engine drives an interactive CLI and a stdio MCP server
(
lookup_ip, lookup_asn, update_db, db_status).
- Zero external dependencies: standard library only (
net/netip + a
self-contained on-disk index). No MMDB reader, no config libraries.
Data: IPinfo Lite (https://ipinfo.io/lite), licensed
CC BY-SA 4.0. Attribution to
IPinfo is required. This tool downloads the database with your own token
and does not redistribute it.
Install
make build # → dist/asn-lookup (signed on macOS)
Requires Go 1.25+. Cross-compile all platforms with make build-all.
Quick start
-
Get a free token at https://ipinfo.io/signup (no card required).
-
Download the database and build the local index:
export IPINFO_TOKEN=your_token_here
asn-lookup update
-
Look things up:
asn-lookup ip 8.8.8.8
# IP ASN AS NAME COUNTRY NETWORK
# 8.8.8.8 AS15169 Google LLC US 8.8.8.0/24
asn-lookup asn AS15169
# AS15169 Google LLC google.com (N prefixes)
# 8.8.4.0/24
# 8.8.8.0/24
# ...
Commands
| Command |
Description |
asn-lookup ip <IP>... |
Look up AS + country/continent per IP. Reads stdin when no args are given. |
asn-lookup asn <ASN>... |
List the prefixes announced by each ASN (AS15169 or 15169). Reads stdin when no args. |
asn-lookup update |
Download IPinfo Lite and rebuild the local index (atomic replace). |
asn-lookup doctor |
Report database presence, freshness, and configuration. |
asn-lookup mcp |
Run as a local MCP server over stdio. |
asn-lookup version |
Print the version and data attribution. |
- Default: an aligned, human-readable table.
-j / --json: JSON Lines (one JSON object per input) for pipelines.
cat ips.txt | asn-lookup ip --json | jq 'select(.found) | .asn'
Unknown, unmapped, and invalid inputs are reported per line (they never abort
the batch). For asn, --count prints only the header/counts and -n/--limit N
caps the printed prefixes.
MCP server
Register the server with an MCP client. For Claude Code:
claude mcp add asn-lookup -- /path/to/asn-lookup mcp
Or in a client config:
{
"mcpServers": {
"asn-lookup": { "command": "/path/to/asn-lookup", "args": ["mcp"] }
}
}
Tools (call get_usage first for the full manual and recovery table; the server
also advertises this via the MCP instructions field):
| Tool |
Arguments |
Purpose |
get_usage |
— |
Operating manual: tools, prefix paging, recovery table |
lookup_ip |
ip (string) or ips (array) |
IP → AS + country/continent |
lookup_asn |
asn/asns, limit, offset |
ASN → one page of prefixes, always inline |
update_db |
— |
Download + rebuild the database (needs a token) |
db_status |
— |
Generation date, record counts, staleness |
Arguments are checked strictly. A call carrying an argument a tool does not
declare fails, naming it — arguments: json: unknown field "offest" — rather
than running without it. A misspelt offset used to be dropped, which made
every page of a large AS come back as the first one. Wrong-typed arguments are
refused the same way, and nothing runs before the arguments decode. Omitting
arguments entirely still means "none".
Large ASN results are paged, not written to disk. Some ASNs map to hundreds
of thousands of prefixes (e.g. Cloudflare has ~590k in IPinfo Lite), so
lookup_asn returns a compact summary (prefix_count, v4_count, v6_count)
plus one page of prefixes inline: limit (default 50) bounds the page, offset
walks the rest, and has_more says whether any are left. The server writes no
files and takes no path argument, so it works against a client that has no
filesystem of its own. prefix_count is always the true total — nothing is
silently dropped.
The database is not downloaded automatically: the CLI prints a hint to run
update, while an MCP client can call update_db itself. A stale database
(older than 30 days) produces a warning but is never auto-refreshed.
Configuration
Settings resolve in this order (later wins): config file → environment → flags.
Token (required only for update):
IPINFO_TOKEN (or ASN_LOOKUP_TOKEN) environment variable, or
[ipinfo] token in the config file.
Config file — ~/.config/asn-lookup/config.toml
(honors XDG_CONFIG_HOME). See config.example.toml:
[ipinfo]
token = "your_token_here"
# lite_url = "https://ipinfo.io/data/ipinfo_lite.csv.gz"
[db]
# path = "~/.local/share/asn-lookup/asndb.bin"
Index location — ~/.local/share/asn-lookup/asndb.bin
(honors XDG_DATA_HOME; override with --db).
The token is never written to logs: any URL surfaced in errors has the token
redacted.
How it works
update streams the gzipped IPinfo Lite CSV, parses each network row with
net/netip, and writes a compact index: deduplicated AS/geo records plus
family-split, sorted address ranges. ip lookups binary-search the ranges;
asn lookups scan them (the reverse view is derived from the same data, so it
can never drift from the forward view). See
docs/en/architecture.md.
Development
make test # go test -race -cover ./...
make build # → dist/asn-lookup
make check # lint + test + build-all
License
Code: MIT. Database: IPinfo Lite, CC BY-SA 4.0 — attribution to
IPinfo required; not redistributed by this tool.