IPs-LE

io.github.nolindnaidoov1.0.0Updated Oct 4, 2026

Extract every IP address, CIDR block and MAC, normalized and classified by scope.

VerifiedSTDIODesktop onlyDeveloper ToolsSecurity & Monitoring

Overview

AI-generated overview

Extracts every IPv4, IPv6, CIDR block and MAC address from text, normalizing and classifying each by scope.

What it does
The server exposes an extract_ips tool that scans supplied content and returns each address with its kind (ipv4, ipv6, cidr, mac), canonical form, line and column, and a scope class such as loopback, private, link-local or documentation. CIDR findings include prefix, network, last address and host count. Text with more than one defensible reading is reported as a named refusal rather than guessed at. It performs no DNS, lookups or network requests and does not read files.
When to use it
Useful when reviewing configuration files, allow-lists or logs where the same address may appear in several spellings, or when an assistant needs to compare addresses by canonical form instead of raw text. It is meant for inspection and reporting, not for resolving or rewriting addresses.
Requirements
Runs locally over stdio via npx from the npm package ips-le-mcp; Node.js is required. No environment variables, API keys or configuration are needed. The tool takes content as an argument and needs no network access.
Before you install
The server reads no files and makes no network calls, and it never rewrites files or gives a verdict on whether an address should be present. Findings are capped at 500 by default with a truncation flag. Ambiguous input is reported as a refusal rather than resolved, so results may include entries that are not addresses.

Installation

In SourceWeft

  1. Open IPs-LE in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

[IPs-LE Logo]

IPs-LE: One Address, One Spelling

Find every IP address, CIDR block and MAC in a document, normalized and classified, and refuse the ambiguous ones by name
IPv4 · IPv6 (RFC 5952) · CIDR · MAC — no DNS, no lookups, no sockets

[Install from VS Code Marketplace] [Open VSX downloads] [ips-le-mcp on npm] [ips-le on crates.io] [LE Tools]


Useful? A star or rating is how other developers find it — ★ GitHub · ★ Open VSX · ★ Marketplace

What it does

An allow-list review asks whether 2001:0db8::0001 is already on the list. The list says 2001:db8::1. A diff of the raw text calls them two addresses; they are one.

Open a document, press Ctrl+Alt+A (Cmd+Alt+A on Mac), and every IPv4 and IPv6 address, CIDR block and MAC address in it is listed by kind with its line and column, the key it sits under, its canonical form and what it is for — loopback, private, link-local, documentation and the rest. A CIDR block comes with its network, its last address and how many addresses it holds. The report opens beside the editor. Works in VS Code and in VS Code–based editors like Cursor and VSCodium (installable from Open VSX).

  • Reviewing a config or an allow-list — one spelling per address, and the private ones named as private
  • Reading a log — every peer and upstream, even inside a URL or a [host]:port
  • Before trusting 010.1.1.1 — which is two different hosts depending on who reads it

Text it cannot read unambiguously is reported with the reason, never guessed at. It resolves nothing, looks nothing up and rewrites nothing.

Install

WhereWhat you getInstall
VS CodeThe extraction, in your editor, on a keystrokeMarketplace
Cursor, VSCodium, WindsurfThe same extensionOpen VSX
A terminal or a CI stepA whole tree, with an exit codecargo install ips-le · crates.io
Any MCP agent, via Nodeextract_ips over stdionpx ips-le-mcp · npm
ZedThe MCP server as a context serveradd it by hand (no listing yet)

What it answers

One address, one form. IPv6 is normalized per RFC 5952 — 2001:0db8:0000:0000:0000:0000:0000:0001, 2001:db8:0:0:0:0:0:1, 2001:0db8::0001 and 2001:DB8::1 all come back as 2001:db8::1. Sorting the raw text gives four addresses; sorting the normalized form gives one.

Four kinds. ipv4, ipv6, cidr, mac.

Ten classes, closed. A class this cannot name is a class it does not claim.

classIPv4IPv6
loopback127.0.0.0/8::1
private10/8, 172.16/12, 192.168/16—
link-local169.254/16fe80::/10
cgnat100.64/10—
multicast224/4ff00::/8
broadcast255.255.255.255— (IPv6 has none)
documentation192.0.2/24, 198.51.100/24, 203.0.113/242001:db8::/32
unique-local—fc00::/7
reserved0/8, 192.0.0/24, 198.18/15, 240/4::, 2001::/23, 100::/64
globaleverything elseeverything else

An IPv4-mapped IPv6 address takes the IPv4 class, so ::ffff:127.0.0.1 is loopback rather than global — which is the miss an allow-list review is looking for.

Where it is. Line, column, and the key it sits under: JSON, YAML, TOML, INI, dotenv, CSV and logs all supply one. Everything else is still scanned — the search runs over the bytes, so a .tf, a .rules or a rotated access.log.1 yields its addresses and only loses the key path.

Blocks, with their arithmetic. A CIDR finding carries prefix, network, broadcast (IPv4 only — IPv6 has none), last and hosts. hosts is a decimal string, because ::/0 holds 2^128 addresses, which is one more than a u128 and far more than a JSON number.

json
{  "kind": "cidr",  "text": "10.0.0.0/8",  "normalized": "10.0.0.0/8",  "class": "private",  "cidr": {    "prefix": 8,    "network": "10.0.0.0",    "broadcast": "10.255.255.255",    "last": "10.255.255.255",    "hosts": "16777216"  }}

What it refuses

Where the text supports more than one reading, ips-le reports the text, names the ambiguity, and stops.

Six reasons, each a place where two answers are equally defensible:

reasonfires on
octal_hazard010.1.1.1, 0177.0.0.1, 192.168.001.1
ambiguous_version10.0.1, 1.2.3 — unless the key says version
integer_form2130706433 under an address key
malformed_address256.1.1.1, 2001:db8:::1, 12345::1
prefix_out_of_range10.0.0.0/33, 2001:db8::/129
mac_ambiguousdeadbeefcafe

The two that matter most:

  • 010.1.1.1 is not resolved. A leading-zero octet is octal to some resolvers and decimal to others, so that text names two different hosts. Neither reading appears anywhere in the output — a tool that picked one would be the thing hiding the bug.
  • 2130706433 is decoded only next to the flag. Under an address key it is reported as integer_form, with 127.0.0.1 inside the refusal message. What you never get is a loopback address quietly appearing in a list of addresses with the flag gone.

A refusal is a finding, not a failure. It does not move the exit code, and no filter can hide it — filtering to private still shows you the octal hazard, because that is the finding a filtered report would most regret dropping. --strict is there for the pipeline that wants an unresolved ambiguity to stop the build.

crate/SPEC.md says exactly when each reason fires.

It never touches a network

No DNS, no geolocation, no ASN, no WHOIS, no reachability check, no telemetry. Not behind a flag, not once. Classification is arithmetic over the bits and the IANA registries; a lookup would make the answer depend on the network the auditor happened to be sitting on.

It also never rewrites a file, and it never gives a verdict. It says what an address is, never whether it should be there.

Use it from an AI agent

The same engine runs as an MCP server, so an agent can call it directly instead of deciding by eye whether two spellings are one address.

EditorHow
VS Code 1.101+Nothing to install — the extension registers extract_ips with agent mode
ZedNo listing yet — add the MCP server by hand
Claude Codeclaude mcp add ips-le -- npx -y ips-le-mcp
Cursor, Windsurf, anything elsepoint it at npx ips-le-mcp
extract_ips(content, format?, filename?, kind?, class?, maxResults?)

It returns the findings the editor renders, refusals included, as data — capped at 500 by default with meta.truncated. It reads no files and makes no network requests. Published as ips-le-mcp on npm and as io.github.nolindnaidoo/ips-le in the MCP registry. It answers exactly as the Rust CLI's server does: one corpus runs against both, and a differential test feeds both thousands of generated documents in every format — broken JSON included, where both report the parser's own words and position — and compares every answer.

Configuring it by hand — any host with an MCP config file
json
{  "mcpServers": {    "ips-le": {      "command": "npx",      "args": ["-y", "ips-le-mcp"]    }  }}

Or install it once with npm install -g ips-le-mcp and point at ips-le-mcp. It needs no environment variables, no API key and no configuration of its own. To check it:

bash
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | npx -y ips-le-mcp

The CLI

The same extraction runs over a whole tree from a terminal or a CI step: a Rust CLI in crate/, sharing one corpus with the extension — crate/fixtures/ — so the two can never read an address differently.

[ips-le in a terminal]

bash
ips-le .                                   # every address in the tree, one JSON line per fileips-le --class private --class loopback .  # what should not be reachableips-le --kind cidr infra/                  # every block, with its arithmeticips-le --strict config/                    # exit 2 on any ambiguityips-le mcp                                 # extract_ips and ips_le_scan over MCP on stdio

Exit codes follow grep — 0 at least one address named, 1 none, 2 the question was malformed. A refusal does not move the exit code; --strict is how a pipeline turns one into a failure.

Commands

CommandDescription
IPs-LE: Extract Addresses (Ctrl+Alt+A / Cmd+Alt+A)Extract every address in the active document
IPs-LE: Open SettingsOpen IPs-LE settings
IPs-LE: Help & TroubleshootingBuilt-in documentation

Settings

SettingDefaultDescription
ips-le.kinds[]Report only these kinds; empty reports every kind. Refusals are always reported
ips-le.classes[]Report only these classes; empty reports every class. Refusals are always reported
ips-le.openResultsSideBySidetrueOpen the report beside the current editor
ips-le.copyToClipboardEnabledfalseAlso copy the report to the clipboard
ips-le.safety.enabledtrueWarn before extracting from a large file
ips-le.safety.fileSizeWarnBytes1000000The size that warning starts at
ips-le.notificationsLevelsilentall = every notification, important = warnings + errors, silent = errors only
ips-le.statusBar.enabledtrueShow the status bar item
ips-le.telemetryEnabledfalseLocal-only event log (see Privacy)

Languages

Twelve languages besides English:

German · Spanish · French · Indonesian · Italian · Japanese · Korean · Portuguese (Brazil) · Russian · Ukrainian · Vietnamese · Chinese (Simplified)

Both halves are covered — the manifest (command titles, setting names and descriptions) and everything shown while the extension runs (notifications, the status bar and the report's headings). A refusal's detail is the engine's English, identical to the CLI's.

Privacy & security

  • No network access. The extension never sends data anywhere: no DNS, no geolocation, no lookups of any kind. The telemetryEnabled setting only writes events to a local Output Channel you can inspect (IPs-LE).
  • The MCP server holds the same line. It takes content as an argument and returns data: no filesystem access, no network calls, no telemetry.
  • Error notifications redact home directories and credential-shaped fragments.

Documentation

WhatWhere
What the tool is allowed to say — kinds, classes, refusals, the output contract, non-goalscrate/SPEC.md
How the extension is built and held together — architecture, invariants, toolchain, releaseAGENTS.md
How the CLI is built and held togethercrate/AGENTS.md
What changedCHANGELOG.md · crate/CHANGELOG.md
The tool's page, and the other fifteenletools.dev/tools/ips-le

Performance

InputSizeFoundTimeRateScan speed
Access log3.42 MB80,000155.25 ms515,312/sec22 MB/s
JSON config2.34 MB60,000100.48 ms597,123/sec23.3 MB/s
Prose with no addresses2.51 MB40,00070.2 ms569,780/sec35.7 MB/s

Median of 7 runs after warmup, on Apple M5 Pro, 24 GB RAM, Node 24.3.0. Inputs are generated by scripts/benchmark.ts rather than checked in, so the sizes above are exactly what was measured. Reproduce with bun run benchmark.

These are machine-specific and are not asserted in CI — a benchmark that gates a build only tells you how busy the runner was.

Testing

MetricCoverage
Statements82.75%
Branches76.65%
Functions90.68%
Lines84.07%

103 test cases across 11 files, plus an integration suite that runs in a real VS Code extension host and an end-to-end test that installs the built .vsix into a clean profile.

Generated from a real run — coverage/coverage-summary.json and coverage/test-results.json — by scripts/coverage-readme.js; CI fails if this section drifts. Reproduce with bun run test:coverage, and the case count is the one vitest prints.

More from the LE family

Sixteen single-purpose tools for the work in front of every model. Each ships a Rust CLI and an MCP server. One page: letools.dev

Get it out

  • String-LE — Extract every string in a codebase, with its position, so a person can read them
  • Numbers-LE — Extract every hardcoded number in a codebase, so a person can check them
  • Units-LE — Extract every quantity with its unit, normalized, and refuse the ambiguous ones by name
  • Dates-LE — Extract every date and timestamp, and the exact instant each one resolves to
  • IDs-LE — Extract every UUID, ULID, NanoID, ObjectId and Snowflake, and decode the time inside
  • IPs-LE — Extract every IP address, CIDR block and MAC, normalized and classified by scope
  • URLs-LE — Extract every URL in a codebase, with its protocol and exact position
  • Paths-LE — Extract every file path in a codebase, and say whether it still points at anything
  • Colors-LE — Extract every color in a codebase, and say which ones are not in your palette

Check it

  • Regex-LE — Find every regex in a codebase, and report which can be driven into catastrophic backtracking
  • Versions-LE — Find where one dependency is constrained differently across a repository's manifests
  • i18n-LE — Identify the i18n library a project uses, then audit its catalogs by that library's rules
  • Scrape-LE — Check whether a page is scrapeable before the scraper is written, and say when it cannot tell

Guard it

  • Secrets-LE — Find hardcoded credentials in a codebase, and never print one into the report
  • EnvSync-LE — Compare the dotenv files in a tree, and say which keys are missing from which
  • Unicode-LE — Find the Unicode that hides meaning — bidi controls, invisibles, homoglyphs, mixed scripts

Each stands on its own: no shared crate, no published core. Where two of them agree, it is because the same answer was right twice.

Contact — nolindnaidoo.com · GitHub · LinkedIn

Also by nolindnaidoo

Rust — pixelcoords and pixelactions are one loop: pixelcoords answers where, pixelactions acts there. Their own tools, their own voice — not part of the LE family.

License

MIT © nolindnaidoo

Source: README.md at commit 79f19c5

Tools

0
Tool metadata has not been indexed yet.

Version history

1
  1. v1.0.0LatestOct 4, 2026