Sign In

@toolstop/check-digits

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@toolstop/check-digits - npm Package Compare versions

Comparing version
0.1.1
to
0.2.0
+5
-83
dist/lib.mjs

@@ -48,46 +48,8 @@ // Check-digit algorithms for common high-stakes identifiers.

// ------------------------------------------------------------------------ IBAN
// IBAN, ABA routing and payment card validators lived here and were removed
// under clause F6: this server does not accept sensitive input at all, and the
// trigger is receipt, not storage. The arithmetic was never the problem. Asking
// a stranger to paste a bank account or a card number into a remote server was.
// See CLAUDE.md, "Constraints that are not negotiable".
// Official registry lengths. An IBAN of the wrong length for its country is
// invalid even when the checksum happens to pass.
const IBAN_LENGTHS = {
AD: 24, AE: 23, AL: 28, AT: 20, AZ: 28, BA: 20, BE: 16, BG: 22, BH: 22,
BR: 29, BY: 28, CH: 21, CR: 22, CY: 28, CZ: 24, DE: 22, DK: 18, DO: 28,
EE: 20, EG: 29, ES: 24, FI: 18, FO: 18, FR: 27, GB: 22, GE: 22, GI: 23,
GL: 18, GR: 27, GT: 28, HR: 21, HU: 28, IE: 22, IL: 23, IQ: 23, IS: 26,
IT: 27, JO: 30, KW: 30, KZ: 20, LB: 28, LC: 32, LI: 21, LT: 20, LU: 20,
LV: 21, LY: 25, MC: 27, MD: 24, ME: 22, MK: 19, MR: 27, MT: 31, MU: 30,
NL: 18, NO: 15, PK: 24, PL: 28, PS: 29, PT: 25, QA: 29, RO: 24, RS: 22,
SA: 24, SC: 31, SD: 18, SE: 24, SI: 19, SK: 24, SM: 27, ST: 25, SV: 28,
TL: 23, TN: 24, TR: 26, UA: 29, VA: 22, VG: 24, XK: 20,
};
export function validateIban(input) {
const s = clean(input);
if (!/^[A-Z]{2}\d{2}[A-Z0-9]+$/.test(s)) {
return { valid: false, code: "format", reason: "must start with 2 letters then 2 digits" };
}
const country = s.slice(0, 2);
const expected = IBAN_LENGTHS[country];
if (expected === undefined) {
return { valid: false, code: "unknown_country", reason: `unknown IBAN country code "${country}"` };
}
if (s.length !== expected) {
return {
valid: false,
code: "length",
reason: `${country} IBANs are ${expected} characters, got ${s.length}`,
};
}
const rearranged = lettersToDigits(s.slice(4) + s.slice(0, 4));
const ok = mod97(rearranged) === 1;
return {
valid: ok,
country,
length: s.length,
normalized: s,
...(ok ? {} : { code: "checksum", reason: "mod-97 checksum failed" }),
};
}
// ------------------------------------------------------------------------- LEI

@@ -206,43 +168,5 @@

// ---------------------------------------------------------------- ABA routing
export function validateAba(input) {
const s = clean(input);
if (!/^\d{9}$/.test(s)) return { valid: false, code: "format", reason: "ABA routing number is 9 digits" };
const d = [...s].map((c) => c.charCodeAt(0) - 48);
const sum =
3 * (d[0] + d[3] + d[6]) + 7 * (d[1] + d[4] + d[7]) + 1 * (d[2] + d[5] + d[8]);
const ok = sum % 10 === 0;
return { valid: ok, normalized: s, ...(ok ? {} : { code: "checksum", reason: "ABA weighted checksum failed" }) };
}
// -------------------------------------------------------------- credit cards
const CARD_BRANDS = [
{ brand: "Visa", re: /^4\d{12}(\d{3})?(\d{3})?$/ },
{ brand: "Mastercard", re: /^(5[1-5]\d{14}|2(2[2-9]\d{12}|[3-6]\d{13}|7[01]\d{12}|720\d{12}))$/ },
{ brand: "American Express", re: /^3[47]\d{13}$/ },
{ brand: "Discover", re: /^(6011\d{12}|65\d{14}|64[4-9]\d{13})$/ },
{ brand: "JCB", re: /^35(2[89]|[3-8]\d)\d{12}$/ },
{ brand: "Diners Club", re: /^3(0[0-5]|[68]\d)\d{11}$/ },
{ brand: "UnionPay", re: /^62\d{14,17}$/ },
];
export function validateCard(input) {
const s = clean(input);
if (!/^\d{12,19}$/.test(s)) return { valid: false, code: "format", reason: "card numbers are 12-19 digits" };
const ok = luhn(s);
const match = CARD_BRANDS.find((b) => b.re.test(s));
return {
valid: ok,
brand: match?.brand ?? "unknown",
length: s.length,
...(ok ? {} : { code: "checksum", reason: "Luhn checksum failed" }),
};
}
// ------------------------------------------------------------------ dispatch
export const VALIDATORS = {
iban: validateIban,
lei: validateLei,

@@ -255,4 +179,2 @@ isbn10: validateIsbn10,

isin: validateIsin,
aba: validateAba,
card: validateCard,
};

@@ -259,0 +181,0 @@

+25
-27

@@ -27,3 +27,3 @@ // The single source of truth for this server. Both transports (Worker HTTP and

type: "string",
enum: ["format", "length", "unknown_country", "checksum"],
enum: ["format", "length", "checksum"],
description: "Bounded reason the check failed. Absent when valid.",

@@ -41,3 +41,3 @@ },

type: "string",
description: "ISO country code parsed from the identifier. IBAN and ISIN only.",
description: "ISO country code parsed from the identifier. ISIN only.",
},

@@ -49,10 +49,2 @@ width: {

},
brand: {
type: "string",
description: "Card scheme inferred from the prefix. Payment cards only.",
},
length: {
type: "integer",
description: "Length of the normalized identifier.",
},
expectedCheckDigit: {

@@ -70,7 +62,11 @@ type: "string",

name: "check-digits",
version: "0.1.1",
version: "0.2.0",
instructions:
"Catches mistyped bank accounts, payment cards, barcodes, VINs and other " +
"structured identifiers before a bad one causes a failed payment, a " +
"rejected claim or a corrupted record.\n\n" +
"Catches mistyped barcodes, VINs, ISBNs and other public structured " +
"identifiers before a bad one causes a rejected listing, a bounced claim " +
"or a corrupted record.\n\n" +
"This server deliberately does not handle bank accounts, IBANs, routing " +
"numbers or payment cards. Do not send them here. Validate those locally " +
"instead, with a library, so the value never leaves the machine that " +
"already has it.\n\n" +
"Call these tools instead of reasoning about whether an identifier is " +

@@ -97,9 +93,10 @@ "well-formed: the arithmetic is exact and guessing is not. Each check digit " +

"Verify a number when you already know what it is supposed to be. Use " +
"this whenever someone gives you a bank account (IBAN), a US routing " +
"number (ABA), a payment card, a product barcode (GTIN, UPC or EAN), a " +
"book number (ISBN-10 or ISBN-13), a vehicle VIN, a US healthcare " +
"this whenever someone gives you a product barcode (GTIN, UPC or EAN), " +
"a book number (ISBN-10 or ISBN-13), a vehicle VIN, a US healthcare " +
"provider NPI, a security ISIN, or a legal entity LEI, and acting on a " +
"wrong one would cost something: money sent nowhere, a declined " +
"transaction, a bounced claim, a rejected listing, a record that " +
"quietly corrupts a dataset.\n\n" +
"wrong one would cost something: a bounced claim, a rejected listing, " +
"a record that quietly corrupts a dataset.\n\n" +
"Every supported format is a public identifier. Bank accounts, IBANs, " +
"routing numbers and payment cards are not supported and must not be " +
"sent.\n\n" +
"Returns whether the checksum passes and, when it fails, the specific " +

@@ -110,3 +107,3 @@ "reason. If you do not already know the format, call identify_format " +

"only that the digits are internally consistent: it does not mean the " +
"account, card, book, vehicle or provider exists, is active, or belongs " +
"book, product, vehicle or provider exists, is active, or belongs " +
"to any particular person.",

@@ -186,3 +183,3 @@ inputSchema: {

"fields validate_identifier returns for that format, such as " +
"`country` for an IBAN or `brand` for a card. Only satisfied " +
"`country` for an ISIN or `width` for a GTIN. Only satisfied " +
"formats are listed, so no entry carries `code` or `reason`.",

@@ -223,4 +220,4 @@ items: {

"Given digits with the final check digit omitted, return the one that " +
"completes them. Payment cards, US healthcare NPIs and securities ISINs " +
"all use the Luhn formula.\n\n" +
"completes them. US healthcare NPIs and securities ISINs use the Luhn " +
"formula.\n\n" +
"Use it to build valid test fixtures, to recover a last digit that was " +

@@ -230,5 +227,6 @@ "lost or illegible, or to check an implementation against a reference. " +

"instead. Input must reduce to digits only once spaces and dashes are " +
"stripped; anything else is an error. This constructs a well-formed " +
"number and nothing more: it does not create, reserve or verify a real " +
"card, provider or security.",
"stripped; anything else is an error. Payment cards also use Luhn, and " +
"are deliberately out of scope here: do not pass card digits, partial " +
"or complete. This constructs a well-formed number and nothing more: " +
"it does not create, reserve or verify a real provider or security.",
inputSchema: {

@@ -235,0 +233,0 @@ type: "object",

{
"name": "@toolstop/check-digits",
"version": "0.1.1",
"version": "0.2.0",
"mcpName": "dev.toolstop/check-digits",
"description": "MCP server that catches mistyped bank accounts, payment cards, barcodes, VINs and other structured identifiers before a bad one causes a failed payment, a rejected claim or a corrupted record.",
"description": "MCP server that catches mistyped barcodes, ISBNs, VINs and other public structured identifiers before a bad one causes a rejected listing, a bounced claim or a corrupted record.",
"type": "module",

@@ -24,6 +24,2 @@ "bin": {

"typo-detection",
"iban",
"aba",
"routing-number",
"credit-card",
"luhn",

@@ -30,0 +26,0 @@ "gtin",

+17
-11

@@ -6,8 +6,14 @@ # mcp-check-digits

Assistants are bad at modular arithmetic and good at sounding confident about
it. This server does the arithmetic exactly, so a wrong IBAN or a mistyped VIN
gets caught instead of confirmed.
it. This server does the arithmetic exactly, so a wrong barcode or a mistyped
VIN gets caught instead of confirmed.
Supported: **IBAN**, **LEI**, **ISBN-10**, **ISBN-13**, **GTIN/UPC/EAN**,
**VIN**, **NPI**, **ISIN**, **ABA routing numbers**, and **payment cards**.
Supported: **LEI**, **ISBN-10**, **ISBN-13**, **GTIN/UPC/EAN**, **VIN**,
**NPI**, and **ISIN**.
**Not supported, on purpose: IBANs, bank account numbers, ABA routing numbers
and payment cards.** Every format here is a public identifier. A remote server
has no business being handed your bank details to do arithmetic that a local
library does just as well, so this one does not ask for them and will not accept
them. Validate those where the value already lives.
## Use it

@@ -52,7 +58,7 @@

**What a pass means.** A check digit is arithmetic. A valid IBAN is not a real
bank account, a valid card number is not a live card, and a valid ISBN is not a
book that was ever printed. These tools tell you an identifier is internally
consistent, which is what catches transposed digits and truncated copies. They
cannot tell you the thing it names exists.
**What a pass means.** A check digit is arithmetic. A valid ISBN is not a book
that was ever printed, a valid VIN is not a car that was ever built, and a valid
NPI is not a provider who is still practising. These tools tell you an
identifier is internally consistent, which is what catches transposed digits and
truncated copies. They cannot tell you the thing it names exists.

@@ -72,4 +78,4 @@ ## Privacy

A passing check digit means the identifier is *well-formed*, not that it exists,
is active, or belongs to anyone in particular. A valid IBAN checksum does not
mean the account is real.
is active, or belongs to anyone in particular. A valid GTIN checksum does not
mean the product was ever manufactured.

@@ -76,0 +82,0 @@ ## License