{"id":47331345,"url":"https://github.com/duksh/peerglass","last_synced_at":"2026-03-17T21:00:46.040Z","repository":{"id":339619718,"uuid":"1162697165","full_name":"duksh/peerglass","owner":"duksh","description":"Deterministic MCP server for internet resource intelligence: 5-RIR RDAP, RPKI validation, BGP visibility, abuse contacts, peering data, and IPv4/IPv6 stats.","archived":false,"fork":false,"pushed_at":"2026-02-20T18:19:15.000Z","size":153,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-20T20:23:15.463Z","etag":null,"topics":["afrinic","apnic","arin","bgp","iana","icann","ietf","ipv4","ipv6","lacnic","peeringdb","rdap","ripe-ncc","rir","rpki"],"latest_commit_sha":null,"homepage":"https://duksh.github.io/peerglass/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/duksh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-20T15:28:52.000Z","updated_at":"2026-02-20T17:37:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/duksh/peerglass","commit_stats":null,"previous_names":["duksh/peerglass"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/duksh/peerglass","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duksh%2Fpeerglass","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duksh%2Fpeerglass/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duksh%2Fpeerglass/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duksh%2Fpeerglass/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/duksh","download_url":"https://codeload.github.com/duksh/peerglass/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duksh%2Fpeerglass/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30631419,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-17T17:32:55.572Z","status":"ssl_error","status_checked_at":"2026-03-17T17:32:38.732Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["afrinic","apnic","arin","bgp","iana","icann","ietf","ipv4","ipv6","lacnic","peeringdb","rdap","ripe-ncc","rir","rpki"],"created_at":"2026-03-17T21:00:25.009Z","updated_at":"2026-03-17T21:00:46.024Z","avatar_url":"https://github.com/duksh.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🔍 PeerGlass — Internet Resource Intelligence\n\n![PeerGlass Branding](gitlab-repo-img-pg-01.jpg)\n\n\u003e **Protocol note:** PeerGlass uses **RDAP** (Registration Data Access Protocol —\n\u003e RFC 7480–7484). RDAP (RFC 7480–7484) is the IANA-mandated JSON successor\n\u003e to legacy plain-text WHOIS.\n\u003e Where you see *\"historical-whois\"* in source code or API responses, that is\n\u003e RIPE Stat's own name for their endpoint — it is not our protocol choice.\n\nQuery all **5 global Regional Internet Registries** simultaneously using RDAP,\nvalidate routes via **RPKI**, inspect **BGP** routing visibility, trace full\n**historical allocation timelines**, discover **IXP peering data** via PeeringDB,\nand monitor **network health** — all from natural language in Claude or via REST API.\n\n---\n\n## What Are the 5 RIRs?\n\nThink of the internet's IP address space like a global land registry.\nIANA (the root) delegates large blocks to 5 regional bodies:\n\n| RIR | Region | Countries |\n|-----|--------|-----------|\n| 🌍 **AFRINIC** | Africa | 54 |\n| 🌏 **APNIC** | Asia-Pacific | 56 economies |\n| 🌎 **ARIN** | North America | USA, Canada, Caribbean |\n| 🌎 **LACNIC** | Latin America \u0026 Caribbean | 33 |\n| 🌍 **RIPE NCC** | Europe, Middle East, Central Asia | 75+ |\n\n---\n\n## Quick Start\n\n### Install\n\n```bash\ngit clone https://github.com/duksh/peerglass\ncd peerglass\npip install -e .\n```\n\n### Configure MCP Clients (Claude and others)\n\nPeerGlass runs as a standard MCP stdio server (`mcp.run()` in `server.py`),\nso any MCP-compatible AI client can use it — not just Claude Desktop.\n\nFor Claude Desktop, edit\n`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)\nor `%APPDATA%\\Claude\\claude_desktop_config.json` (Windows):\n\n```json\n{\n  \"mcpServers\": {\n    \"peerglass\": {\n      \"command\": \"peerglass\",\n      \"args\": []\n    }\n  }\n}\n```\n\nIf `peerglass` is not in your PATH, use:\n\n```json\n{\n  \"mcpServers\": {\n    \"peerglass\": {\n      \"command\": \"python\",\n      \"args\": [\"/full/path/to/peerglass/server.py\"]\n    }\n  }\n}\n```\n\nRestart Claude Desktop. All **42 tools** become immediately available.\n\n### Start the REST API\n\n```bash\nuvicorn api:app --host 0.0.0.0 --port 8000 --reload\n```\n\nInteractive docs at: http://localhost:8000/docs\n\n### Start the Web UI\n\n```bash\ncd ui\nnpm install\nnpm run dev        # Dev server at http://localhost:5173\n```\n\nFor production build:\n\n```bash\ncd ui\nnpm run build      # Output in ui/dist/\n```\n\nSet `VITE_API_BASE_URL` to point at your PeerGlass API deployment:\n\n```bash\nVITE_API_BASE_URL=https://api.peerglass.io npm run build\n```\n\n---\n\n## Example Queries (Natural Language in Claude)\n\n**Phase 1 — Registry lookups:**\n```\n\"Who owns the IP address 185.220.101.1?\"\n\"What is the abuse contact for 8.8.8.8?\"\n\"Who is AS13335 registered to?\"\n\"Are all 5 RIR RDAP servers online right now?\"\n```\n\n**Phase 2 — Routing security:**\n```\n\"Is the route 1.1.1.0/24 via AS13335 RPKI valid?\"\n\"Is 8.8.8.0/24 currently visible in the global BGP table?\"\n\"What prefixes is AS15169 (Google) announcing right now?\"\n\"Find all internet resources registered to Cloudflare globally.\"\n```\n\n**Phase 3 — Historical intelligence:**\n```\n\"Show me the full registration history of 8.8.8.0/24.\"\n\"Has the prefix 192.0.2.0/24 ever been transferred between organizations?\"\n\"Give me the global IPv4 exhaustion stats for all 5 RIRs.\"\n\"Show me the prefix hierarchy for 1.1.1.0/24 — parent blocks and sub-assignments.\"\n```\n\n**Phase 4 — Peering, IXPs, health \u0026 monitoring:**\n```\n\"Who does AS13335 (Cloudflare) peer with at internet exchanges?\"\n\"List all IXPs where Google has a presence.\"\n\"Is the network for 1.1.1.0/24 currently healthy — any ROA issues or BGP anomalies?\"\n\"Monitor AS13335 for changes since last baseline.\"\n```\n\n**Phase 5 — DNS intelligence:**\n```\n\"What DNS records does cloudflare.com have?\"\n\"Is the DNSSEC chain valid for google.com?\"\n\"Check if 1.2.3.4 is on any spam blocklist.\"\n\"Audit the email security posture of example.com.\"\n\"Has my DNS change for api.example.com propagated globally yet?\"\n```\n\n**Phase 6 — TLS, certificates \u0026 threat intel:**\n```\n\"When does the TLS certificate for cloudflare.com expire?\"\n\"Show me all certificates ever issued for *.example.com.\"\n\"Is 198.51.100.1 flagged as malicious — what ports and CVEs does Shodan see?\"\n\"What hostnames has the IP 1.2.3.4 served historically?\"\n```\n\n---\n\n## All 42 MCP Tools\n\n### Phase 1 — Registry Queries\n\n| Tool | Description | Cache TTL |\n|------|-------------|-----------|\n| `rir_query_ip` | Query all 5 RIRs for an IP address (parallel) | 1 hour |\n| `rir_query_asn` | Query all 5 RIRs for an ASN (parallel) | 1 hour |\n| `rir_get_abuse_contact` | Find abuse contact for any IP globally | 1 hour |\n| `rir_server_status` | Health check all 5 RDAP servers | live |\n| `rir_cache_stats` | View query cache state and TTLs | live |\n\n### Phase 2 — Routing Intelligence\n\n| Tool | Description | Cache TTL |\n|------|-------------|-----------|\n| `rir_check_rpki` | Validate RPKI/ROA status for prefix + ASN | 15 min |\n| `rir_check_bgp_status` | Check BGP visibility for a prefix or ASN | 5 min |\n| `rir_get_announced_prefixes` | List all BGP-announced prefixes for an ASN | 5 min |\n| `rir_audit_org` | Audit all IP/ASN resources for an organization | 6 hours |\n\n### Phase 3 — Historical Intelligence\n\n| Tool | Description | Cache TTL |\n|------|-------------|-----------|\n| `rir_prefix_history` | Full ownership timeline for any prefix or ASN | 12 hours |\n| `rir_detect_transfers` | Detect cross-org / cross-RIR resource transfers | 12 hours |\n| `rir_ipv4_stats` | Global IPv4/IPv6/ASN dashboard + optional delegated IPv4 block listing (`include_blocks`, filters, pagination) | 24 hours |\n| `rir_prefix_overview` | Prefix hierarchy: parent, children, BGP status | 1 hour |\n\n### Phase 4 — Peering, IXPs, Health \u0026 Monitoring\n\n| Tool | Description | Cache TTL |\n|------|-------------|-----------|\n| `rir_peering_info` | PeeringDB peering data + BGP neighbours for an ASN | 1 hour |\n| `rir_ixp_lookup` | Search Internet Exchange Points globally | 6 hours |\n| `rir_network_health` | RPKI + BGP + RDAP health composite check | 5 min |\n| `rir_change_monitor` | Detect changes since last baseline (delta report) | live |\n\n### Phase 5 — DNS Intelligence\n\n| Tool | Description | Cache TTL |\n|------|-------------|-----------|\n| `peerglass_dns_resolve` | DNS resolution with RDAP IP correlation (A/AAAA/PTR/MX/TXT/NS…) | 5 min |\n| `peerglass_dns_enumerate` | Full DNS record enumeration — all types in one call + SPF/DMARC extraction | 5 min |\n| `peerglass_dns_dnssec` | DNSSEC chain-of-trust validation (SECURE / INSECURE / BOGUS / INDETERMINATE) | 5 min |\n| `peerglass_dns_dnsbl` | DNS blocklist check against 30 RBLs in parallel (Spamhaus, Barracuda, SORBS…) | 15 min |\n| `peerglass_dns_email_security` | Email security audit: SPF, DMARC, DKIM, MX, BIMI + risk score | 15 min |\n| `peerglass_dns_propagation` | DNS propagation check across 10 global resolvers simultaneously | live |\n\n### Phase 6 — TLS, Certificates \u0026 Threat Intelligence\n\n| Tool | Description | Cache TTL |\n|------|-------------|-----------|\n| `peerglass_tls_inspect` | TLS certificate inspection: subject, SANs, expiry, cipher suite, HSTS | 1 hour |\n| `peerglass_ct_logs` | Certificate Transparency log search via crt.sh — discover all certs ever issued | 6 hours |\n| `peerglass_threat_intel` | Threat intelligence: Shodan InternetDB (open ports, CVEs) + GreyNoise (classification, risk score) | 15 min |\n| `peerglass_passive_dns` | Passive DNS history via RIPE Stat — what IPs/hostnames were associated over time | 12 hours |\n\n### Phase 7 — BGP Depth\n\n| Tool | Description | Cache TTL |\n|------|-------------|-----------|\n| `rir_check_irr` | IRR route object validation via IRRExplorer — checks RIPE/RADB/ARIN/APNIC/LACNIC consistency | 1 hour |\n| `rir_detect_route_leak` | BGP route leak detection — identifies valley-free violations and multi-origin anomalies | 5 min |\n| `rir_looking_glass` | BGP looking glass via RIPE RIS — real AS paths from global vantage points | 5 min |\n| `rir_route_stability` | Route flap and stability analysis — state changes, uptime %, stability score | 15 min |\n\n### Phase 8 — Humanitarian \u0026 Crisis Intelligence\n\n| Tool | Description | Cache TTL |\n|------|-------------|-----------|\n| `peerglass_shutdown_detect` | Country BGP shutdown detection — compares current prefix counts vs baseline | 5 min |\n| `peerglass_monitor_register` | Register webhook alerts for shutdown/change events | live |\n| `peerglass_shutdown_timeline` | Historical BGP shutdown timeline with SHA-256 integrity hash (for UN reports) | 1 hour |\n| `peerglass_dns_censorship` | DNS censorship fingerprinting — detects NXDOMAIN injection, IP poisoning, DPI blocking | 10 min |\n| `peerglass_satellite_connectivity` | Satellite internet tracking — Starlink, Viasat, OneWeb BGP presence | 15 min |\n| `peerglass_country_chokepoints` | Country internet chokepoint mapping — critical transit providers, resilience score | 6 hours |\n| `peerglass_ooni_report` | OONI censorship measurements — blocked domains, Tor accessibility, circumvention tools | 30 min |\n| `peerglass_country_health` | Composite country internet health dashboard — 0–100 score from BGP + DNS + OONI + satellite | 5 min |\n\n### Phase 9 — Advanced Platform\n\n| Tool | Description | Cache TTL |\n|------|-------------|-----------|\n| `rir_as_relationships` | AS relationship classification (provider/customer/peer) via CAIDA AS-Rank API | 7 days |\n| `peerglass_geo_lookup` | GeoIP enrichment via MaxMind GeoLite2-City (requires `PEERGLASS_GEOIP_DB` env var) | 24 hours |\n| `peerglass_atlas_trace` | RIPE Atlas distributed traceroute from global vantage points (requires `PEERGLASS_RIPE_ATLAS_KEY`) | 5 min |\n\n---\n\n## REST API — 41 Endpoints\n\nPeerGlass exposes every tool as a REST endpoint, allowing integration with\ndashboards, scripts, and CI/CD pipelines — no Claude required.\n\n**Registry \u0026 Routing**\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/v1/ip/{ip}` | RDAP lookup for an IP address |\n| GET | `/v1/asn/{asn}` | RDAP lookup for an ASN |\n| GET | `/v1/abuse/{ip}` | Abuse contact for any IP |\n| GET | `/v1/rpki?prefix=...\u0026asn=...` | RPKI validation |\n| GET | `/v1/bgp/{resource}` | BGP visibility status |\n| GET | `/v1/announced/{asn}` | Announced prefixes for an ASN |\n| GET | `/v1/org?name=...` | Audit all resources for an org name |\n| GET | `/v1/history/{resource}` | Prefix/ASN ownership history |\n| GET | `/v1/transfers/{resource}` | Transfer detection |\n| GET | `/v1/stats/ipv4` | Global IPv4/IPv6/ASN stats |\n| GET | `/v1/overview/{prefix}` | Prefix hierarchy overview |\n| GET | `/v1/peering/{asn}` | Peering info from PeeringDB |\n| GET | `/v1/ixp` | IXP search and listing |\n| GET | `/v1/health/{resource}` | Composite network health check |\n| GET | `/v1/monitor/{resource}` | Change monitoring (delta) |\n\n**DNS Intelligence**\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/v1/dns/resolve/{target}` | DNS resolution + RDAP IP correlation |\n| GET | `/v1/dns/enumerate/{domain}` | Full DNS record enumeration (all types) |\n| GET | `/v1/dns/dnssec/{domain}` | DNSSEC chain-of-trust validation |\n| GET | `/v1/dns/dnsbl/{ip}` | DNS blocklist check (30 RBLs in parallel) |\n| GET | `/v1/dns/email/{domain}` | Email security audit (SPF/DMARC/DKIM/BIMI) |\n| GET | `/v1/dns/propagation/{domain}` | DNS propagation across 10 global resolvers |\n\n**TLS, Certificates \u0026 Threat Intel**\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/v1/tls/{hostname}` | TLS certificate inspection |\n| GET | `/v1/ct/{domain}` | Certificate Transparency log search |\n| GET | `/v1/threat/{ip}` | Threat intelligence (Shodan + GreyNoise) |\n| GET | `/v1/pdns/{resource}` | Passive DNS history (RIPE Stat) |\n\n**BGP Depth**\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/v1/irr?prefix=...\u0026asn=...` | IRR route object validation |\n| GET | `/v1/route-leak/{prefix}` | BGP route leak detection |\n| GET | `/v1/looking-glass/{prefix}` | BGP looking glass (RIPE RIS) |\n| GET | `/v1/stability/{prefix}` | Route flap stability analysis |\n\n**Humanitarian \u0026 Crisis**\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/v1/shutdown/{country_code}` | Country BGP shutdown detection |\n| POST | `/v1/shutdown/monitor` | Register shutdown webhook alert |\n| GET | `/v1/shutdown/timeline/{resource}` | Historical shutdown timeline |\n| GET | `/v1/censorship/{domain}` | DNS censorship fingerprinting |\n| GET | `/v1/satellite/{country_code}` | Satellite connectivity status |\n| GET | `/v1/chokepoints/{country_code}` | Country internet chokepoints |\n| GET | `/v1/ooni/{country_code}` | OONI censorship measurements |\n| GET | `/v1/health/country/{country_code}` | Composite country internet health |\n\n**Advanced Platform**\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/v1/as-relationships/{asn}` | AS relationship classification (CAIDA) |\n| GET | `/v1/geo/{ip}` | GeoIP enrichment (MaxMind GeoLite2) |\n| GET | `/v1/atlas/{target}` | RIPE Atlas distributed traceroute |\n| POST | `/v1/bulk` | Bulk query up to 50 resources in one call |\n\n**Quick example:**\n\n```bash\n# Look up who owns 1.1.1.1\ncurl http://localhost:8000/v1/ip/1.1.1.1\n\n# Validate RPKI for Cloudflare's prefix\ncurl \"http://localhost:8000/v1/rpki?prefix=1.1.1.0/24\u0026asn=AS13335\"\n\n# Get BGP peers for Google\ncurl http://localhost:8000/v1/peering/AS15169\n\n# List AFRINIC delegated IPv4 blocks (allocated + Ghana), paginated\ncurl \"http://localhost:8000/v1/stats/ipv4?rir=AFRINIC\u0026include_blocks=true\u0026status=allocated\u0026country=GH\u0026limit=5\u0026offset=0\u0026format=json\"\n\n# DNS — enumerate all record types for a domain\ncurl http://localhost:8000/v1/dns/enumerate/cloudflare.com\n\n# DNS — check email security (SPF/DMARC/DKIM)\ncurl http://localhost:8000/v1/dns/email/cloudflare.com\n\n# DNS — check if an IP is on any blocklist\ncurl http://localhost:8000/v1/dns/dnsbl/1.2.3.4\n\n# DNS — check propagation across 10 global resolvers\ncurl \"http://localhost:8000/v1/dns/propagation/cloudflare.com?record_type=A\"\n\n# TLS — inspect certificate for a hostname\ncurl http://localhost:8000/v1/tls/cloudflare.com\n\n# Certificates — find all certs ever issued for a domain\ncurl http://localhost:8000/v1/ct/cloudflare.com\n\n# Threat intel — open ports, CVEs, risk score for an IP\ncurl http://localhost:8000/v1/threat/1.2.3.4\n\n# Passive DNS — what hostnames has this IP served?\ncurl http://localhost:8000/v1/pdns/1.1.1.1\n```\n\nInteractive docs (Swagger UI): http://localhost:8000/docs\n\n---\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────┐\n│                    PEERGLASS                            │\n│                                                         │\n│   Claude (LLM)              REST Clients                │\n│       │ MCP / stdio              │ HTTP                 │\n│       ▼                          ▼                      │\n│   server.py (42 tools)      api.py (41 endpoints)       │\n│       │                          │                      │\n│       └──────────┬───────────────┘                      │\n│                  ▼                                       │\n│           rir_client.py  ── All async HTTP calls         │\n│                  │                                       │\n│       ┌──────────┼──────────────────┐                   │\n│       ▼          ▼                  ▼                   │\n│  normalizer.py  formatters.py    cache.py               │\n│  (unify RDAP)   (markdown/JSON)  (TTL tiers)            │\n│       │                                                  │\n│       ▼                                                  │\n│    models.py  (Pydantic v2 data models)                  │\n└─────────────────────────────────────────────────────────┘\n\nExternal APIs called at runtime:\n  ├── RDAP:    rdap.afrinic.net / rdap.apnic.net / rdap.arin.net\n  │            rdap.lacnic.net  / rdap.db.ripe.net\n  ├── RPKI:    rpki.cloudflare.com\n  ├── BGP:     stat.ripe.net (bgp-state, announced-prefixes,\n  │            routing-status, asn-neighbours, historical-whois)\n  ├── IXP:     peeringdb.com/api/net, /api/ix, /api/netixlan\n  ├── Stats:   NRO Extended Delegation Stats (all 5 RIRs)\n  └── Routing: data.iana.org/rdap/ (IANA bootstrap)\n```\n\n### How Parallel Queries Work\n\n```\nasyncio.gather() fires all 5 RIR queries at exactly the same time:\n\n  AFRINIC ──── responds in 1.1s ──── 404 Not Found\n  APNIC   ──── responds in 0.9s ──── ✅ 200 OK  ← authoritative\n  ARIN    ──── responds in 1.2s ──── 404 Not Found\n  LACNIC  ──── responds in 1.4s ──── 404 Not Found\n  RIPE    ──── responds in 0.8s ──── 404 Not Found\n\nTotal wall-clock time: ~1–2s (parallel) vs ~6–8s (sequential)\n```\n\n---\n\n## Web UI (Sprint 7)\n\nPeerGlass includes a search-first, dark terminal-themed web frontend built with **React 18 + Vite 5 + TypeScript + Tailwind CSS**.\n\n### Features\n\n- **Auto-detection**: Paste any IP, ASN, prefix, domain, or 2-letter country code — the UI auto-detects the type and runs the right tool\n- **7 tool categories**: Registry · Routing · DNS · TLS · Threat · Crisis · Peering\n- **Dark terminal theme**: Dark background, monospace font (JetBrains Mono), cyan/green accent palette\n- **Markdown rendering**: All API results rendered as formatted markdown with syntax highlighting\n- **Crisis dashboard**: One-click country health check for Syria, Myanmar, Ukraine, Belarus, Iran, Russia and more\n- **41 tools accessible**: Every REST endpoint is exposed via the UI\n\n### Configuration\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `VITE_API_BASE_URL` | `http://localhost:8000` | PeerGlass API base URL |\n\n### Directory structure\n\n```\nui/\n├── src/\n│   ├── App.tsx                  # Main layout + tab state\n│   ├── components/\n│   │   ├── SearchBar.tsx        # Search input with auto-type detection\n│   │   ├── ResultPanel.tsx      # Markdown result renderer\n│   │   ├── TabBar.tsx           # Category + tool tab navigation\n│   │   ├── StatusBadge.tsx      # RPKI/BGP/shutdown status indicators\n│   │   └── CountryDashboard.tsx # Crisis country quick-access grid\n│   ├── hooks/\n│   │   └── usePeerGlass.ts      # Query state management hook\n│   └── api/\n│       └── client.ts            # Typed wrappers for all 41 endpoints\n└── dist/                        # Production build output\n```\n\n---\n\n## Testing\n\nPeerGlass has **two separate test suites** serving different purposes.\nYou should run both — they catch different categories of problems.\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                   TESTING PYRAMID                           │\n│                                                             │\n│      🔺 INTEGRATION TESTS  (test_integration.py)            │\n│      /\\   Real internet. Real APIs. Real data.              │\n│     /  \\  Proves the product actually works end-to-end.     │\n│    /────\\  Run this on your machine or a GCP VM.            │\n│                                                             │\n│   🔺 UNIT / STATIC TESTS  (test_peerglass.py)              │\n│   /\\   In-memory. No network. Instant.                      │\n│  /  \\  Proves code structure, branding, and wiring.         │\n│ /────\\  Runs anywhere including CI/CD.                      │\n└─────────────────────────────────────────────────────────────┘\n```\n\n---\n\n### Test 1 — Static / Unit Tests (`test_peerglass.py`)\n\n**What it checks:**\n\n| # | Test | What It Verifies |\n|---|------|-----------------|\n| 1 | Compile check | All 7 `.py` files parse without syntax errors |\n| 2 | Branding audit | No stale legacy product/server identity strings |\n| 3 | RDAP endpoints | All 5 RIR RDAP URLs are present and correct |\n| 4 | Protocol header | `Accept: application/rdap+json` is set |\n| 5 | User-Agent | Updated to `peerglass/1.0.0` |\n| 6 | MCP server name | `\"peerglass\"` |\n| 7 | Tool count | Exactly 42 `@mcp.tool()` decorators in `server.py` |\n| 8 | REST endpoints | All 41 routes present in `api.py` |\n| 9 | FastAPI runtime | TestClient hits 3 endpoints in-memory, validates responses |\n| 10 | README | PeerGlass branding, 42 tools, RDAP note all present |\n\n**How to run:**\n\n```bash\ncd peerglass\npython test_peerglass.py\n```\n\n**Expected output:**\n\n```\n============================================================\nPEERGLASS — COMPLETE TEST SUITE\n============================================================\n1. COMPILE CHECK\n   ✅ server.py  ✅ rir_client.py  ✅ formatters.py\n   ✅ models.py  ✅ cache.py  ✅ normalizer.py  ✅ api.py\n\n2. BRANDING AUDIT — no stale WHOIS identity strings\n   ✅ server.py  ✅ rir_client.py  ✅ README.md  ...\n\n...\n\n✅ ALL TESTS PASSED — 0 errors\n  Python files: 7  |  MCP tools: 42  |  REST endpoints: 41\n  Protocol: RDAP throughout (RFC 7480-7484)\n  Branding: PeerGlass throughout\n============================================================\n```\n\n**When to run:** Before every commit. Runs in under 3 seconds. No internet required.\n\n---\n\n### Test 2 — Integration Tests (`test_integration.py`)\n\n**What it checks:**\n\nReal HTTP calls to external internet registries/data providers using\nwell-known, stable test fixtures\n(Cloudflare AS13335, Google AS15169, 1.1.1.0/24). Every test asserts on\nactual response data — not just that the server responded.\n\n| # | Test | API Called | Fixture | Assertion |\n|---|------|-----------|---------|-----------|\n| 1 | RDAP reachability | All 5 RIRs | `1.1.1.1` | HTTP 200 or 404 (both mean server is up) |\n| 2 | RDAP IP lookup | APNIC | `1.1.1.1` | `objectClassName=ip network`, `startAddress=1.1.1.0` |\n| 3 | RDAP ASN lookup | ARIN | `AS13335` | `objectClassName=autnum`, name contains `CLOUDFLARE` |\n| 4 | RPKI validation | RIPE Stat | `1.1.1.0/24 AS13335` | `status=ok`, validation status is `valid` |\n| 5 | BGP status | RIPE Stat | `1.1.1.0/24` | Prefix visible to RIS peers, origin ASN present |\n| 6 | Announced prefixes | RIPE Stat | `AS13335` | \u003e= 5 prefixes, mix of IPv4 + IPv6 |\n| 7 | Historical data | RIPE Stat | `AS15169` | `status=ok`, historical object versions returned |\n| 8 | PeeringDB network | PeeringDB | `AS13335` | Network record found, peering policy present |\n| 9 | IANA Bootstrap | IANA | `AS13335` | Mapped to correct RDAP service URL |\n| 10 | AFRINIC RDAP | AFRINIC | `102.176.0.0` | `objectClassName=ip network`, African country code |\n| 11 | ASN neighbours | RIPE Stat | `AS13335` | Upstream / peer ASN list returned |\n| 12 | PeeringDB IXPs | PeeringDB | global | IXP list with name and country |\n| 13 | IANA consistency | IANA/ICANN | IPv4 + IPv6 + ASN bootstrap files | All 5 RIR service URLs present |\n\n**How to run:**\n\n```bash\n# On your local machine or a GCP VM (requires internet access)\ncd peerglass\npython test_integration.py\n```\n\n**Expected output (passing):**\n\n```\n============================================================\n  PEERGLASS — LIVE INTEGRATION TEST SUITE\n  Real HTTP calls. No mocks. No fakes.\n============================================================\n  Time: 2026-02-20 14:00:00 UTC\n  APIs: RIPE · ARIN · APNIC · LACNIC · AFRINIC · RIPE Stat · PeeringDB · IANA\n\n────────────────────────────────────────────\n  TEST 1 — RDAP Server Reachability (all 5 RIRs)\n────────────────────────────────────────────\n   ✅ PASS  RIPE RDAP reachable      HTTP 404\n   ✅ PASS  ARIN RDAP reachable      HTTP 404\n   ✅ PASS  APNIC RDAP reachable     HTTP 200\n   ✅ PASS  LACNIC RDAP reachable    HTTP 404\n   ✅ PASS  AFRINIC RDAP reachable   HTTP 404\n\n...\n\n============================================================\n  SUMMARY\n  Checks run   : 48\n  ✅ Passed    : 47\n  ❌ Failed    : 0\n  ⚠️  Skipped  : 1\n  Duration     : ~20-40s\n\n  🎉 ALL TESTS PASSED — PeerGlass live APIs confirmed working!\n============================================================\n```\n\n**When to run:**\n- Before a release\n- After any change to `rir_client.py` (the HTTP layer)\n- After any change to the external API URLs or parameters\n- On a schedule (e.g. daily cron on a GCP VM) to detect API changes upstream\n\n**Why this cannot run in CI/CD without configuration:**\nThe integration tests require outbound internet access to external APIs\n(RIPE, ARIN, APNIC, LACNIC, AFRINIC, RIPE Stat, PeeringDB, IANA). Standard CI runners (GitHub Actions\nfree tier) have internet access, so these tests can run there. Restricted\nsandboxes (Anthropic Claude environment, some corporate proxies) will block\nthe outbound calls and every test will fail with `403 Forbidden` — this is\nexpected behaviour of the sandbox, not a bug in PeerGlass.\n\n---\n\n### Understanding Test Results\n\n#### Why does RDAP return 404 and still pass?\n\n```\nAPNIC owns 1.1.1.0/24 (Cloudflare's block). If you ask RIPE for 1.1.1.1:\n\n  You:  GET https://rdap.db.ripe.net/ip/1.1.1.1\n  RIPE: HTTP 404\n\nThis 404 is RIPE saying \"I know about this IP but it's not mine.\"\nThe server is alive and working correctly. 404 = server reachable.\n200 = server reachable AND it's the authoritative RIR for that IP.\nBoth are valid success states for the reachability test.\n```\n\n#### Why does Test 7 sometimes SKIP?\n\nRIPE Stat's `historical-whois` endpoint has variable coverage. For some\nASNs it returns rich history; for others the `objects` array is empty.\nAn empty array is a valid API response — the skip is logged to distinguish\n\"no data\" from \"API broken\".\n\n---\n\n### Running Both Suites Together\n\n```bash\ncd peerglass\n\n# Step 1: Always run static tests first (fast, catches code errors)\npython test_peerglass.py\necho \"Exit code: $?\"\n\n# Step 2: Only run integration tests if static tests pass\nif [ $? -eq 0 ]; then\n    python test_integration.py\nfi\n```\n\n---\n\n### Verification — Source-Pinned Retrieval vs Model Recall\n\nIf you want to verify that PeerGlass is deterministic and source-pinned\n(real API retrieval) rather than model recall, run this quick check.\n\n1. Ask ChatGPT to fetch all 5 RIR RDAP endpoints for `1.1.1.1` and return raw JSON only.\n2. Repeat the exact same prompt multiple times.\n3. Compare ChatGPT output against direct endpoint results below.\n\nUse this prompt in ChatGPT:\n\n```text\nFor IP 1.1.1.1, query these exact RDAP endpoints and return ONLY JSON:\n\n- AFRINIC: https://rdap.afrinic.net/rdap/ip/1.1.1.1\n- APNIC: https://rdap.apnic.net/ip/1.1.1.1\n- ARIN: https://rdap.arin.net/registry/ip/1.1.1.1\n- LACNIC: https://rdap.lacnic.net/rdap/ip/1.1.1.1\n- RIPE: https://rdap.db.ripe.net/ip/1.1.1.1\n\nOutput schema per RIR:\n{\n  \"rir\": \"...\",\n  \"url\": \"...\",\n  \"http_status\": ...,\n  \"objectClassName\": \"... or null\",\n  \"handle\": \"... or null\",\n  \"error\": \"... or null\"\n}\n\nDo not summarize. Do not infer.\n```\n\nGround-truth script (direct endpoint calls):\n\n```bash\npython - \u003c\u003c'PY'\nimport asyncio\nimport json\nimport httpx\n\nENDPOINTS = {\n    \"AFRINIC\": \"https://rdap.afrinic.net/rdap/ip/1.1.1.1\",\n    \"APNIC\": \"https://rdap.apnic.net/ip/1.1.1.1\",\n    \"ARIN\": \"https://rdap.arin.net/registry/ip/1.1.1.1\",\n    \"LACNIC\": \"https://rdap.lacnic.net/rdap/ip/1.1.1.1\",\n    \"RIPE\": \"https://rdap.db.ripe.net/ip/1.1.1.1\",\n}\n\nasync def query_one(client, rir, url):\n    try:\n        r = await client.get(url, timeout=20)\n        content_type = r.headers.get(\"content-type\", \"\")\n        data = r.json() if \"json\" in content_type else {}\n        return {\n            \"rir\": rir,\n            \"url\": url,\n            \"http_status\": r.status_code,\n            \"objectClassName\": data.get(\"objectClassName\"),\n            \"handle\": data.get(\"handle\"),\n            \"error\": None,\n        }\n    except Exception as exc:\n        return {\n            \"rir\": rir,\n            \"url\": url,\n            \"http_status\": None,\n            \"objectClassName\": None,\n            \"handle\": None,\n            \"error\": str(exc),\n        }\n\nasync def main():\n    headers = {\n        \"Accept\": \"application/rdap+json\",\n        \"User-Agent\": \"peerglass/verification\",\n    }\n    async with httpx.AsyncClient(headers=headers, follow_redirects=True) as client:\n        results = await asyncio.gather(*[\n            query_one(client, rir, url) for rir, url in ENDPOINTS.items()\n        ])\n    print(json.dumps(results, indent=2))\n\nasyncio.run(main())\nPY\n```\n\nIf ChatGPT outputs are inconsistent or fail to fetch while direct calls are stable,\nthat difference is exactly why PeerGlass uses deterministic source-pinned retrieval.\n\n---\n\n### Adding Your Own Integration Tests\n\nThe `test_integration.py` script is designed to be extended. Each test follows\nthis pattern:\n\n```python\nasync def test_your_thing():\n    section(\"TEST N — Short description\")\n    print(\"  What API, what fixture, what you expect\")\n\n    url = \"https://example-api.com/endpoint\"\n    params = {\"resource\": \"your-fixture\", \"sourceapp\": \"peerglass-test\"}\n    headers = {\"Accept\": \"application/json\", \"User-Agent\": \"peerglass/1.0.0 (integration-test)\"}\n\n    try:\n        async with httpx.AsyncClient(timeout=25.0) as client:\n            resp = await client.get(url, params=params, headers=headers)\n\n        if resp.status_code != 200:\n            fail(\"API returned 200\", f\"HTTP {resp.status_code}\"); return\n\n        data = resp.json()\n        value = data.get(\"some\", {}).get(\"field\", \"\")\n\n        ok(\"Field is what I expected\", f\"value='{value}'\") if value == \"expected\" else fail(\"Field check\", f\"Got '{value}'\")\n\n    except Exception:\n        fail(\"API call failed\", traceback.format_exc()[-120:])\n```\n\nThen add it to the `main()` coroutine:\n\n```python\nasync def main():\n    ...\n    await test_your_thing()   # ← add here\n    ...\n```\n\n---\n\n## External APIs Used\n\n| API | Purpose | Cache TTL |\n|-----|---------|-----------|\n| All 5 RIR RDAP endpoints | IP/ASN registration data (RDAP JSON) | 1 hr |\n| `data.iana.org/rdap/` | Bootstrap: which RIR owns which IP/ASN range | permanent |\n| `rpki.cloudflare.com` | RPKI/ROA validation (Validated ROA Payloads) | 15 min |\n| `stat.ripe.net/data/bgp-state` | BGP routing table visibility | 5 min |\n| `stat.ripe.net/data/announced-prefixes` | Prefixes announced by an ASN | 5 min |\n| `stat.ripe.net/data/asn-neighbours` | BGP peer/upstream/downstream ASNs | 1 hr |\n| `stat.ripe.net/data/historical-whois` | RDAP object change history (RIPE's naming) | 12 hr |\n| `stat.ripe.net/data/allocation-history` | Allocation lifecycle events | 12 hr |\n| `stat.ripe.net/data/prefix-overview` | Prefix hierarchy metadata | 1 hr |\n| `stat.ripe.net/data/routing-status` | Routing status and visibility | 5 min |\n| `peeringdb.com/api/net` | Network peering policies | 1 hr |\n| `peeringdb.com/api/ix` | Internet Exchange Point directory | 6 hr |\n| `peeringdb.com/api/netixlan` | Network-to-IXP membership records | 1 hr |\n| NRO Extended Delegation Stats | Authoritative IPv4/IPv6/ASN allocation counts (all 5 RIRs) | 24 hr |\n\n---\n\n## Phase 3 Data Sources Explained\n\n### RIPE Stat historical-whois\nRecords every change ever made to an RDAP object: when the org field changed,\nwhen the status changed, when a new maintainer was added. Used by\n`rir_prefix_history` and `rir_detect_transfers`. The name *\"historical-whois\"*\nis RIPE Stat's own endpoint naming — PeerGlass uses RDAP protocol throughout.\n\n**Coverage:** Best for RIPE NCC resources. Partial for other RIRs.\n\n### RIPE Stat allocation-history\nLogs the full allocation lifecycle: when a block was first allocated from the\nRIR pool, when it was sub-allocated to an ISP, when it was returned.\n\n### NRO Extended Delegation Stats\nPublished daily by each RIR as a pipe-delimited text file. Contains every\nsingle IP and ASN record ever created, with current status. Authoritative\nsource for `rir_ipv4_stats`.\n\n### RIPE Stat prefix-overview / less-specifics / more-specifics\nThree APIs queried in parallel to build the prefix hierarchy tree for\n`rir_prefix_overview`.\n\n---\n\n## Use Case Workflows\n\n### BGP Hijack Investigation\n```\n1. rir_query_ip(suspicious_ip)         → Who registered this IP?\n2. rir_check_bgp_status(prefix)        → Which ASN is announcing it right now?\n3. rir_check_rpki(prefix, asn)         → Is the announcement RPKI-valid?\n4. rir_prefix_overview(prefix)         → Any unexpected more-specifics?\n5. rir_detect_transfers(prefix)        → Did this block recently change hands?\n```\n\n### M\u0026A Due Diligence\n```\n1. rir_audit_org(company_name)         → What IP blocks does this company own?\n2. rir_prefix_history(each_prefix)     → When were they acquired?\n3. rir_detect_transfers(each_prefix)   → Were any transferred recently?\n4. rir_get_announced_prefixes(asn)     → What are they actively routing?\n```\n\n### Peering \u0026 IXP Analysis\n```\n1. rir_get_peering_info(asn)           → Where does this network peer?\n2. rir_lookup_ixps(city or ixp_name)  → Find IXPs in a region\n3. rir_network_health(asn)             → Is the network healthy?\n4. rir_change_monitor(asn)             → Any changes since last check?\n```\n\n### Policy Research / ISOC Report\n```\n1. rir_ipv4_stats()                    → Full global IPv4/IPv6/ASN dashboard\n2. rir_ipv4_stats(rir_filter=\"AFRINIC\")→ Africa-specific detail\n3. Compare ipv6_total_prefixes across  → IPv6 adoption rates by region\n```\n\n---\n\n## IPv4 Exhaustion Context\n\n| RIR | IPv4 Free Pool Exhausted |\n|-----|--------------------------|\n| APNIC | 15 April 2011 |\n| RIPE NCC | 14 September 2012 |\n| ARIN | 24 September 2015 |\n| LACNIC | June 2020 |\n| AFRINIC | 2020–2021 |\n\nAll RIRs now operate under transfer policies. IPv4 addresses trade on the\nsecondary market. `rir_ipv4_stats` tracks remaining pools in real time.\n\n---\n\n## License\n\nMIT","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fduksh%2Fpeerglass","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fduksh%2Fpeerglass","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fduksh%2Fpeerglass/lists"}