Lokal

io.github.lokal-idPublished by GitHub account lokal-idv0.18.0Updated Nov 5, 2026

Indonesia-ready agents: NIK, phone, Rupiah, regions, postal codes, holidays, and PII masking.

Overview

AI-generated overview

Local MCP server that makes an assistant Indonesia-ready: NIK/NPWP/phone validation, Rupiah, regions, postal codes, holidays, QRIS, and PII masking of files.

What you can do
  • Validate and decode Indonesian identifiers: NIK, NPWP, phone numbers, and QRIS payment payloads, including setting a QRIS amount
  • Format and parse Rupiah amounts, including short forms and spelled-out terbilang
  • Search and list Kemendagri regions from province down to village, look up postal codes, and parse free-text addresses into region codes
  • List national holidays and cuti bersama, and do working-day arithmetic for SLAs and delivery estimates
  • Scan files and folders for Indonesian personal data, read files with stable placeholders, write results with real values restored, and produce anonymized copies
  • Clear the session vault so stored real values are forgotten
When to use it
Useful when an assistant works with Indonesian data or users: address forms, invoices, phone and ID validation, delivery or SLA dates, or production exports containing personal data that should not be pasted into a chat.
Requirements
Runs locally over stdio, typically via npx with the npm package. Node.js is needed for npx. The optional LOKAL_ROOT environment variable sets the folder the file tools may access; it defaults to the working directory. No account, API key, or authentication is declared.
Before you install
The file tools read and write real personal data on the machine; write_file_unmasked restores real values into output files, and anonymize_file writes copies of production data. The vault keeps real values in memory until clear_vault or the configured TTL. The guard is described as not a sandbox, and files it cannot scan should not be treated as clean. Policy settings can allow a data type to stay unmasked.

Installation

In SourceWeft

  1. Open Lokal 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

@lokal-id/mcp

[npm] [License: MIT]

An MCP server that makes AI agents Indonesia-ready: accurate Kemendagri region and postal code data, holidays and working days, NIK/NPWP/phone validation, Rupiah, and Samaran, which lets an agent read production files (CSV, Excel, logs) with Indonesian personal data replaced by stable placeholders, then write results with the real values restored, locally.

Part of Lokal · Bahasa Indonesia

Install

Claude Code: install the Lokal plugin, which bundles this server with the skills and the Samaran Guard hook:

text
/plugin marketplace add lokal-id/lokal/plugin install lokal@lokal

Cursor, Claude Desktop, Windsurf (.cursor/mcp.json, claude_desktop_config.json, ~/.codeium/windsurf/mcp_config.json):

json
{  "mcpServers": {    "lokal": {      "command": "npx",      "args": ["-y", "@lokal-id/mcp"],      "env": { "LOKAL_ROOT": "/path/to/your/project" }    }  }}

VS Code (.vscode/mcp.json):

json
{ "servers": { "lokal": { "type": "stdio", "command": "npx", "args": ["-y", "@lokal-id/mcp"] } } }

Codex CLI (~/.codex/config.toml):

toml
[mcp_servers.lokal]command = "npx"args = ["-y", "@lokal-id/mcp"]

The file tools can only access LOKAL_ROOT, which defaults to CLAUDE_PROJECT_DIR and then to the working directory.

Tools

ToolWhat it does
parse_nikValidate and decode a NIK: region, date of birth, sex
validate_npwp15-digit NPWP, 16-digit NPWP, or NIK as NPWP
parse_phoneNormalize numbers, display format, WhatsApp link, carrier
parse_qrisRead and check a QRIS payload: merchant, NMID, criteria, amount, CRC
set_qris_amountMake a merchant's QRIS dynamic for an amount, with a new CRC
format_rupiah / parse_rupiahRp1.500.000, terbilang; "2jtan", "goceng" → numbers
search_wilayah / list_wilayahProvinces down to villages, with codes and postal codes
lookup_kode_posPostal code → villages, or all postal codes in a region
parse_alamatFree-text address → street, RT/RW, kode pos, and Kemendagri regions with their codes
list_holidays / working_daysNational holidays and cuti bersama (SKB 2026, 2027), SLA due dates
scan_piiScan files and folders for personal data by UU PDP category; reports files it could not scan
read_file_maskedRead a file (text, CSV, JSON, logs, .xlsx, .docx, text-based PDF) with personal data replaced by placeholders
write_file_unmaskedWrite results with placeholders restored, on your machine
anonymize_fileCopy dumps and exports with realistic stand-ins for every person (Kembaran), returning only counts
clear_vaultForget every real value behind this session's placeholders

parse_alamat, parse_nik, parse_phone, and validate_npwp also take placeholders from read_file_masked ([ADDRESS_3], [NIK_1]): the real value is read on your machine, and the street, RT/RW, and birth date come back as placeholders.

Samaran Guard

lokal-mcp guard is a hook for Claude Code (UserPromptSubmit, and PreToolUse for Read|Grep|Bash|PowerShell), Codex CLI, Cursor, and Gemini CLI. It holds prompts that contain NIK, bank account, or health data, blocks commands that would print a personal data file into the conversation, pointing the agent to read_file_masked instead, and asks before photos of documents and scanned PDFs are read. The Lokal plugin sets it up for Claude Code; see the Lokal README for the other agents.

SAMARAN_GUARD=strict makes it fail closed: what it cannot check is blocked, including a guard error, a file it cannot read, output from beyond the first 5 MB of a big file, a Grep over more than 300 data files, and a check that runs longer than 7 seconds. One-liners such as python -c and node -e that name a data file with personal data are blocked like cat. It is still not a sandbox; see Defense in depth.

Your own data types

.samaran.json at the project root adds rules for formats Samaran cannot know, and values it must never mask. The file tools, the guard, lokal-mcp scan, and the proxy read it; restart the server after changing it. A broken file makes the file tools fail rather than mask less.

json
{  "custom": [    { "label": "EMPLOYEE_ID", "pattern": "\\bEMP-\\d{6}\\b" },    { "label": "MEMBER", "fields": ["no_member"] }  ],  "allow": ["0800-1-500-500"]}

Policy and audit

.samaran.json can also set an action per type: "policy": { "HEALTH": "block", "FINANCIAL": "allow", "PASSPORT": "route" }. block makes read_file_masked refuse a file and the proxy refuse a request; allow leaves a type unmasked; route sends proxied requests to a local model ("route": { "openai": "http://127.0.0.1:11434/v1", "model": "llama3.2" }). "audit": true writes one JSON line per decision, with counts and never values, to .lokal/audit.jsonl; lokal-mcp audit summarizes it. "vaultTtlMinutes": 60 makes the server forget the real values after an hour without use. "guard": "strict" turns on the strict guard for everyone in the repository. "ner": true adds Lokal's NER model for names without a label or cue, such as "saya di chat sama rianto surya", to read_file_masked, scan_pii, and the proxy; install it next to the server with npx -y -p @lokal-id/mcp -p @lokal-id/ner lokal-mcp (see @lokal-id/ner). See the Lokal README.

lokal-mcp init --profile personal|team|strict [--dry-run] writes .samaran.json, .samaranignore, and for teams a GitHub workflow; lokal-mcp check validates the configuration and explains what it does.

Kembaran

lokal-mcp kembaran prod.sql export.csv -o staging/ [--secret <text>] [--keep FINANCIAL] [--keep-birth-date] [--dry-run] copies text files with every person replaced by a realistic stand-in, consistent across the files. See the Lokal README.

Lokal Scan

lokal-mcp scan --staged (pre-commit) or lokal-mcp scan --diff origin/main (CI) fails when a commit or pull request adds Indonesian personal data, and shows each value partially masked. lokal-mcp scan [path…] checks whole files. There is a GitHub Action too: uses: lokal-id/[email protected]. See the Lokal README.

Remote (HTTP) mode

lokal-mcp serve --http --port 3000 serves the data tools over Streamable HTTP at /mcp, for ChatGPT and Claude.ai connectors. The file tools are not available in this mode.

Samaran Proxy

lokal-mcp proxy is an OpenAI- and Anthropic-compatible endpoint for apps: it masks Indonesian personal data in every request and restores it in the response, streaming and tool calls included.

sh
npx @lokal-id/mcp proxy --port 8787 [--openai <url>] [--anthropic <url>] [--exclude FINANCIAL]

Then use OpenAI(base_url="http://127.0.0.1:8787/v1") or Anthropic(base_url="http://127.0.0.1:8787"). Requests with images or files inside are forwarded with x-samaran-status: partial, since they cannot be checked; --strict refuses them. See the Lokal README.

License

MIT. The library is @lokal-id/core.

Source: packages/mcp/README.md at commit b83e92a

Tools

18

parse_nik

Parse NIK

Validate an Indonesian NIK (16-digit national ID) by structure and decode it: province, regency, district (Kemendagri), birth date, gender. Cannot prove the NIK is registered at Dukcapil. Also takes a [NIK_n] placeholder from read_file_masked; then the birth date and gender come back as placeholders.

Parametersnikrequired

validate_npwp

Validate NPWP

Validate the format of an Indonesian tax ID (NPWP): legacy 15-digit (XX.XXX.XXX.X-XXX.XXX), 16-digit, or NIK used as NPWP for individuals. Returns the 16-digit form DJP uses today. Format only: it does not check the DJP check digit or registration. Also takes an [NPWP_n] or [NIK_n] placeholder from read_file_masked; then only validity and kind come back.

Parametersnpwprequired

parse_qris

Read a QRIS payload

Read and check a QRIS payload (the text inside an Indonesian QRIS payment code): static or dynamic, merchant name and city, NMID, merchant criteria (UMI, UKE, …), amount, tip, merchant accounts, and whether the CRC checksum is intact. Errors and warnings are in Indonesian.

Parameterspayloadrequired

set_qris_amount

Make a dynamic QRIS with an amount

Turn a QRIS payload into a dynamic one for an amount in Rupiah, optionally with a fixed or percentage fee, and recompute the CRC. The merchant account is unchanged. Returns the new payload, ready to render as a QR code.

Parametersamountrequiredpayloadrequiredfee_fixedfee_percent

parse_phone

Parse Indonesian phone number

Normalize an Indonesian phone number (08xx, +62, 62, landline with area code) to E.164, display format, WhatsApp form, and original operator (Telkomsel, Indosat, Tri, XL, Axis, Smartfren). Also takes a [PHONE_n] placeholder from read_file_masked; then only validity, type, and operator come back.

Parametersphonerequired

format_rupiah

Format Rupiah

Format an amount as Rupiah the Indonesian way (Rp1.500.000), short form (Rp1,5 jt), and spelled out in words (terbilang) for receipts and invoices.

Parametersamountrequired

parse_rupiah

Parse Rupiah from text

Turn how Indonesians write money into a number: "Rp 25.000", "1,5jt", "2jtan", "50rb-an", "goceng", "2 ceban", "sejuta setengah", "dua ratus ribu". "m" means miliar.

Parameterstextrequired

search_wilayah

Search Indonesian regions

Find official Kemendagri regions by name (provinsi, kabupaten/kota, kecamatan, kelurahan/desa) with their codes. Use for address forms, normalizing user-typed locations, and seed data instead of inventing region names.

Parameterslevellimitqueryrequiredwithin

list_wilayah

List child regions

List the direct children of a region code — the data behind cascading address dropdowns. No code returns the 38 provinces; "32" returns the kabupaten/kota in Jawa Barat, and so on down to kelurahan/desa (which include their kode pos).

Parametersparent_code

lookup_kode_pos

Look up kode pos

Kode pos (postal codes) for Indonesian regions. Give kode_pos to find the kelurahan/desa that use it, or wilayah_code to list every kode pos inside a region (e.g. "32.76" Kota Depok).

Parameterskode_poswilayah_code

parse_alamat

Parse an Indonesian address

Read a free-text Indonesian address ("Jl. Margonda Raya No. 100 RT 3/5 Kemiri Muka, Beji, Depok 16423") into street, RT/RW, kode pos, and the Kemendagri kelurahan/desa, kecamatan, kabupaten/kota, and provinsi with their codes. Handles missing commas, capitals, abbreviations (Kel., Kec., Kab., Jaksel, Jabar), and left-out levels. Check confidence and issues: a kode pos that belongs to another kelurahan, a village that is not in the data, or an address that fits more than one region (see alternatives). Use it to clean address columns, fill region dropdowns from typed addresses, or check a shipping address. Also takes an [ADDRESS_n] placeholder from read_file_masked: the regions come back as they are, the street and RT/RW as placeholders.

Parametersaddressrequired

list_holidays

Indonesian public holidays

Libur nasional and cuti bersama for a year, from the official SKB 3 Menteri. Only years with a published decree are available; other years return an error rather than a guess.

Parametersyearrequired

working_days

Indonesian working days

Working-day math with Indonesian holidays: is a date a working day, the date N working days after a date (SLA, delivery estimates), or the number of working days between two dates (inclusive). Cuti bersama counts as a day off unless cuti_bersama is false (private companies may work on them).

Parameterstoaddfromrequiredcuti_bersama

scan_pii

Scan files for Indonesian personal data

Scan a file or directory (text, CSV, JSON, logs, SQL, .xlsx, .docx, text-based PDF; up to 100 MB each) for personal data — identifiers (NIK, KK, NPWP, phone, email, bank account, BPJS, passport, SIM, NIP, card, plate, IP, coordinates), names, addresses, birth dates, and UU PDP categories (religion, marital status, gender, nationality, health, finances, criminal records, biometrics) — without revealing it: findings show only partially masked values. Also lists files and pages it could not scan (scanned or encrypted PDFs, .xls, .doc, images, archives). Use before reading data files and before committing fixtures or seeders.

Parameterspathtypesmin_confidence

read_file_masked

Read a file with personal data masked

Read a text, CSV, JSON, log, .xlsx, .docx, or text-based PDF file with Indonesian personal data replaced by stable placeholders ([NIK_1], [PHONE_2], [RELIGION_1] …). The same value always gets the same placeholder in this session, so joins, counts, grouping and deduplication still work. Real values never leave the user's machine. Spreadsheets come back as CSV, one block per sheet; Word documents as their text plus each table as CSV; PDFs one block per page.

Parameterspathrequiredmax_linesstart_line

write_file_unmasked

Write a file, restoring masked values

Write content to a file after replacing Samaran placeholders ([NIK_1], [PHONE_2] …) from this session with the real values, locally. Use this to produce output (cleaned CSV, reports, migrations) from data you read with read_file_masked.

Parameterspathrequiredcontentrequiredoverwrite

anonymize_file

Make a copy of data with realistic stand-ins (Kembaran)

Copy SQL dumps, CSV, JSON, or log files with every piece of Indonesian personal data replaced by a realistic stand-in, for staging, seeders, tests, or sharing with a vendor: valid NIKs in the same district with the same gender and birth year, phone numbers on the same operator, names with the same casing, addresses in the same area, e-mail at example.com. The same value gets the same stand-in in every file of one call, so joins still work. Runs on the user's machine and returns only counts: you never see the data. Use it instead of reading production data, and when asked to copy production data to staging or build seeders from it.

Parameterskeeppathsrequiredoutputrequiredsecretdry_runoverwritekeep_birth_date

clear_vault

Forget the masked values

Forget every real value behind the placeholders this session gave out. Afterwards write_file_unmasked can no longer restore them, and reading a file again gives new placeholders. Use it when a task with real data is done, or when the user asks to forget the data.

Takes no parameters.

Version history

14
  1. v0.18.0LatestNov 5, 2026
  2. v0.17.0Oct 31, 2026
  3. v0.15.0Oct 31, 2026
  4. v0.14.0Oct 30, 2026
  5. v0.12.0Oct 30, 2026
  6. v0.9.0Oct 29, 2026
  7. v0.7.2Oct 26, 2026
  8. v0.7.0Oct 26, 2026
  9. v0.6.0Oct 23, 2026
  10. v0.5.0Oct 22, 2026
  11. v0.4.1Oct 21, 2026
  12. v0.4.0Oct 17, 2026
  13. v0.3.0Oct 17, 2026
  14. v0.2.2Oct 16, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts