{"_id":"hilan-mcp","_rev":"6-a5548274ebde71b7c9143d527cf1bb8d","name":"hilan-mcp","dist-tags":{"latest":"0.3.3"},"versions":{"0.1.0":{"name":"hilan-mcp","version":"0.1.0","keywords":["mcp","hilan","hilanet","payslip","form106","israel"],"author":{"name":"udah1"},"license":"MIT","_id":"hilan-mcp@0.1.0","maintainers":[{"name":"udah1","email":"udah10@gmail.com"}],"homepage":"https://github.com/udah1/hilan-mcp#readme","bugs":{"url":"https://github.com/udah1/hilan-mcp/issues"},"bin":{"hilan-mcp":"dist/cli/index.js"},"dist":{"shasum":"06fc6d6cfd007afa297013aac9a9d4f9703fc9e5","tarball":"https://registry.npmjs.org/hilan-mcp/-/hilan-mcp-0.1.0.tgz","fileCount":54,"integrity":"sha512-kca9/CQB89JUfvkUXYuc7n2Ds6VrE7dd917JrzLLYWLip+BOoJEPcqGINwPmPHt5TXQ4HFQGt2Gnx+bWEdwvLA==","signatures":[{"sig":"MEUCIB634kkxD9EV8tom6WCeAAYx2HUjUEykKBCxTCb1twKYAiEA3FbWA1nHAony0f9c8+8AKOwclTo3CXiwO89wn/poak4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":221486},"type":"module","engines":{"node":">=18.0.0"},"gitHead":"a08fc54915e26b51a6a69f892b833ec778a10b4a","scripts":{"cli":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts","test":"vitest run","build":"tsc","setup":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup","postbuild":"chmod +x dist/cli/index.js","setup-key":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup-key","start:mcp":"tsx src/mcp/Server.ts","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsc --watch","ingest-creds":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts ingest-creds","prepublishOnly":"npm run typecheck && npm test && npm run build"},"_npmUser":{"name":"udah1","email":"udah10@gmail.com"},"repository":{"url":"git+https://github.com/udah1/hilan-mcp.git","type":"git"},"_npmVersion":"11.12.1","description":"MCP server for Hilan (Hilanet, net.hilan.co.il) employee self-service — pull payslips and Form 106 into an AI assistant, for any tenant.","directories":{},"_nodeVersion":"26.0.0","dependencies":{"zod":"^3.25.28","chalk":"^5.4.1","inquirer":"^9.2.12","commander":"^11.1.0","playwright":"^1.62.1","node-notifier":"^10.0.1","node-sql-parser":"^5.3.9","@modelcontextprotocol/sdk":"^1.12.0","better-sqlite3-multiple-ciphers":"^12.11.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.4","vitest":"^1.6.1","typescript":"^5.3.3","@types/node":"^20.11.28","@types/inquirer":"^9.0.8","@types/node-notifier":"^8.0.5"},"_npmOperationalInternal":{"tmp":"tmp/hilan-mcp_0.1.0_1786016178386_0.5644545303049371","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"hilan-mcp","version":"0.2.0","keywords":["mcp","hilan","hilanet","payslip","form106","israel"],"author":{"name":"udah1"},"license":"MIT","_id":"hilan-mcp@0.2.0","maintainers":[{"name":"udah1","email":"udah10@gmail.com"}],"homepage":"https://github.com/udah1/hilan-mcp#readme","bugs":{"url":"https://github.com/udah1/hilan-mcp/issues"},"bin":{"hilan-mcp":"dist/cli/index.js"},"dist":{"shasum":"a7f4dc440864cdd8cebf55cf5a1361e9cafc201a","tarball":"https://registry.npmjs.org/hilan-mcp/-/hilan-mcp-0.2.0.tgz","fileCount":57,"integrity":"sha512-rp7BltKbaGs9Gio8oGiuSE0nuy00tI5sYIeybyLOuBhhpXOIifnvbJVU/ILRZSn8J/QdVjZ+6xyTpwr3GQaYJA==","signatures":[{"sig":"MEYCIQCzhA1Js0uriNIyc/mC2GRKLRr46jeABtxmn6w6DJmq4gIhAKO4tXrhPUnPW9YqGNlqYYyaHldQfIK1dYgX73wqLQG/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":238732},"type":"module","engines":{"node":">=18.0.0"},"gitHead":"94134ffff110e851289cfd53528443d0ef2713ff","scripts":{"cli":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts","test":"vitest run","build":"tsc","setup":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup","postbuild":"chmod +x dist/cli/index.js","setup-key":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup-key","start:mcp":"tsx src/mcp/Server.ts","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsc --watch","ingest-creds":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts ingest-creds","prepublishOnly":"npm run typecheck && npm test && npm run build"},"_npmUser":{"name":"udah1","email":"udah10@gmail.com"},"repository":{"url":"git+https://github.com/udah1/hilan-mcp.git","type":"git"},"_npmVersion":"11.12.1","description":"MCP server for Hilan (Hilanet, net.hilan.co.il) employee self-service — pull payslips and Form 106 into an AI assistant, for any tenant.","directories":{},"_nodeVersion":"26.0.0","dependencies":{"zod":"^3.25.28","chalk":"^5.4.1","inquirer":"^9.2.12","commander":"^11.1.0","playwright":"^1.62.1","node-notifier":"^10.0.1","node-sql-parser":"^5.3.9","@modelcontextprotocol/sdk":"^1.12.0","better-sqlite3-multiple-ciphers":"^12.11.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.4","vitest":"^1.6.1","typescript":"^5.3.3","@types/node":"^20.11.28","@types/inquirer":"^9.0.8","@types/node-notifier":"^8.0.5"},"_npmOperationalInternal":{"tmp":"tmp/hilan-mcp_0.2.0_1786018109973_0.38516462772327564","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"hilan-mcp","version":"0.3.0","keywords":["mcp","hilan","hilanet","payslip","form106","israel"],"author":{"name":"udah1"},"license":"MIT","_id":"hilan-mcp@0.3.0","maintainers":[{"name":"udah1","email":"udah10@gmail.com"}],"homepage":"https://github.com/udah1/hilan-mcp#readme","bugs":{"url":"https://github.com/udah1/hilan-mcp/issues"},"bin":{"hilan-mcp":"dist/cli/index.js"},"dist":{"shasum":"0a925466caffe6eb05b9260d0275cfbe10de5a67","tarball":"https://registry.npmjs.org/hilan-mcp/-/hilan-mcp-0.3.0.tgz","fileCount":57,"integrity":"sha512-PZd84io2ISZHUWyrvuJo5nfgeuJ+gzH5csb/Ar5jgp8rjZ1TEcj1nq/xDvtAGdeOKRtfpad+l9ppTF34ION1Hg==","signatures":[{"sig":"MEUCICQqmPDbnqW3vsFnOHRl74h1WHGUvXPVfq0bWBQRItDOAiEA9gG7MpSEmPK6P9q4amkLRHAWj2YnZXzdkIMYYisqbGc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":257930},"type":"module","engines":{"node":">=18.0.0"},"gitHead":"b1f07215b32ac2f022146ee83b04c6602415ef46","scripts":{"cli":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts","test":"vitest run","build":"tsc","setup":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup","postbuild":"chmod +x dist/cli/index.js","setup-key":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup-key","start:mcp":"tsx src/mcp/Server.ts","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsc --watch","ingest-creds":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts ingest-creds","prepublishOnly":"npm run typecheck && npm test && npm run build"},"_npmUser":{"name":"udah1","email":"udah10@gmail.com"},"repository":{"url":"git+https://github.com/udah1/hilan-mcp.git","type":"git"},"_npmVersion":"11.12.1","description":"MCP server for Hilan (Hilanet / חילן, חילנט, net.hilan.co.il) employee self-service — pull payslips and Form 106 into an AI assistant, for any tenant.","directories":{},"_nodeVersion":"26.0.0","dependencies":{"zod":"^3.25.28","chalk":"^5.4.1","inquirer":"^9.2.12","commander":"^11.1.0","playwright":"^1.62.1","node-notifier":"^10.0.1","node-sql-parser":"^5.3.9","@modelcontextprotocol/sdk":"^1.12.0","better-sqlite3-multiple-ciphers":"^12.11.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.4","vitest":"^1.6.1","typescript":"^5.3.3","@types/node":"^20.11.28","@types/inquirer":"^9.0.8","@types/node-notifier":"^8.0.5"},"_npmOperationalInternal":{"tmp":"tmp/hilan-mcp_0.3.0_1786021803010_0.8955906803992759","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"hilan-mcp","version":"0.3.1","keywords":["mcp","hilan","hilanet","payslip","form106","israel"],"author":{"name":"udah1"},"license":"MIT","_id":"hilan-mcp@0.3.1","maintainers":[{"name":"udah1","email":"udah10@gmail.com"}],"homepage":"https://github.com/udah1/hilan-mcp#readme","bugs":{"url":"https://github.com/udah1/hilan-mcp/issues"},"bin":{"hilan-mcp":"dist/cli/index.js"},"dist":{"shasum":"21cd4e9f1fab1a904724f60ec019c3cb875202c1","tarball":"https://registry.npmjs.org/hilan-mcp/-/hilan-mcp-0.3.1.tgz","fileCount":57,"integrity":"sha512-DTgBq6taIWhMMsLO6ctQc5IAVjf1rFYhP2IZ+/imMjLBUt5uiSCbR2elLCd4fyy6PNMMRQYcORFDDhSMOslQJg==","signatures":[{"sig":"MEUCIArhm1a10rGzeFBFuVVFH6q+amz32HwEnUmYHpEY29H3AiEA/uZO5c56lfS26MNWoWvJJ4TkeqMKy9u5dGEeiIpDC/Y=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":263510},"type":"module","engines":{"node":">=18.0.0"},"gitHead":"0718d3e1e3103e6659d0eff4997e4b35593f7572","scripts":{"cli":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts","test":"vitest run","build":"tsc","setup":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup","postbuild":"chmod +x dist/cli/index.js","setup-key":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup-key","start:mcp":"tsx src/mcp/Server.ts","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsc --watch","ingest-creds":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts ingest-creds","prepublishOnly":"npm run typecheck && npm test && npm run build"},"_npmUser":{"name":"udah1","email":"udah10@gmail.com"},"repository":{"url":"git+https://github.com/udah1/hilan-mcp.git","type":"git"},"_npmVersion":"11.12.1","description":"MCP server for Hilan (Hilanet / חילן, חילנט, net.hilan.co.il) employee self-service — pull payslips and Form 106 into an AI assistant, for any tenant.","directories":{},"_nodeVersion":"26.0.0","dependencies":{"zod":"^3.25.28","chalk":"^5.4.1","inquirer":"^9.2.12","commander":"^11.1.0","playwright":"^1.62.1","node-notifier":"^10.0.1","node-sql-parser":"^5.3.9","@modelcontextprotocol/sdk":"^1.12.0","better-sqlite3-multiple-ciphers":"^12.11.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.4","vitest":"^1.6.1","typescript":"^5.3.3","@types/node":"^20.11.28","@types/inquirer":"^9.0.8","@types/node-notifier":"^8.0.5"},"_npmOperationalInternal":{"tmp":"tmp/hilan-mcp_0.3.1_1786032520352_0.009728922872249468","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"hilan-mcp","version":"0.3.2","keywords":["mcp","hilan","hilanet","payslip","form106","israel"],"author":{"name":"udah1"},"license":"MIT","_id":"hilan-mcp@0.3.2","maintainers":[{"name":"udah1","email":"udah10@gmail.com"}],"homepage":"https://github.com/udah1/hilan-mcp#readme","bugs":{"url":"https://github.com/udah1/hilan-mcp/issues"},"bin":{"hilan-mcp":"dist/cli/index.js"},"dist":{"shasum":"8c0271d8e944f2387b37ebbe44be8404d366be21","tarball":"https://registry.npmjs.org/hilan-mcp/-/hilan-mcp-0.3.2.tgz","fileCount":57,"integrity":"sha512-ReOTUJlnUYWFwOroLIyTtS1CvOBdflJqsHOmIbY8r+egXOLu9M/KPVU6xJ9qy8MI+BrgLi2lIo9fCF0oVrztgg==","signatures":[{"sig":"MEUCIG0YoZYT/ZoCy899UVQ4/UaHUV2tGCYryUIKas1HFw4nAiEApL3NqQxsmkZ6xh9OVaIayf6zSEacbXorBC5LOtCyjZ0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":263552},"type":"module","engines":{"node":">=18.0.0"},"gitHead":"7bc8c3c22c74117fd7d3f27835341ac49d5f84b5","mcpName":"io.github.udah1/hilan-mcp","scripts":{"cli":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts","test":"vitest run","build":"tsc","setup":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup","postbuild":"chmod +x dist/cli/index.js","setup-key":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup-key","start:mcp":"tsx src/mcp/Server.ts","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsc --watch","ingest-creds":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts ingest-creds","prepublishOnly":"npm run typecheck && npm test && npm run build"},"_npmUser":{"name":"udah1","email":"udah10@gmail.com"},"repository":{"url":"git+https://github.com/udah1/hilan-mcp.git","type":"git"},"_npmVersion":"11.12.1","description":"MCP server for Hilan (Hilanet / חילן, חילנט, net.hilan.co.il) employee self-service — pull payslips and Form 106 into an AI assistant, for any tenant.","directories":{},"_nodeVersion":"26.0.0","dependencies":{"zod":"^3.25.28","chalk":"^5.4.1","inquirer":"^9.2.12","commander":"^11.1.0","playwright":"^1.62.1","node-notifier":"^10.0.1","node-sql-parser":"^5.3.9","@modelcontextprotocol/sdk":"^1.12.0","better-sqlite3-multiple-ciphers":"^12.11.1"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.4","vitest":"^1.6.1","typescript":"^5.3.3","@types/node":"^20.11.28","@types/inquirer":"^9.0.8","@types/node-notifier":"^8.0.5"},"_npmOperationalInternal":{"tmp":"tmp/hilan-mcp_0.3.2_1786037832485_0.3974570545632421","host":"s3://npm-registry-packages-npm-production"}},"0.3.3":{"name":"hilan-mcp","version":"0.3.3","mcpName":"io.github.udah1/hilan-mcp","description":"MCP server for Hilan (Hilanet / חילן, חילנט, net.hilan.co.il) employee self-service — pull payslips and Form 106 into an AI assistant, for any tenant.","type":"module","bin":{"hilan-mcp":"dist/cli/index.js"},"scripts":{"start:mcp":"tsx src/mcp/Server.ts","build":"tsc","postbuild":"chmod +x dist/cli/index.js","build:watch":"tsc --watch","typecheck":"tsc --noEmit","cli":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts","setup":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup","ingest-creds":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts ingest-creds","setup-key":"node --no-warnings=ExperimentalWarning --import tsx ./src/cli/index.ts setup-key","test":"vitest run","test:watch":"vitest","prepublishOnly":"npm run typecheck && npm test && npm run build"},"allowScripts":{"better-sqlite3-multiple-ciphers":true,"esbuild":true,"fsevents":false},"keywords":["mcp","hilan","hilanet","payslip","form106","israel"],"author":{"name":"udah1"},"repository":{"type":"git","url":"git+https://github.com/udah1/hilan-mcp.git"},"homepage":"https://github.com/udah1/hilan-mcp#readme","bugs":{"url":"https://github.com/udah1/hilan-mcp/issues"},"license":"MIT","dependencies":{"@modelcontextprotocol/sdk":"^1.12.0","better-sqlite3-multiple-ciphers":"^12.11.1","chalk":"^5.4.1","commander":"^11.1.0","inquirer":"^9.2.12","node-notifier":"^10.0.1","node-sql-parser":"^5.3.9","playwright":"^1.62.1","zod":"^3.25.28"},"devDependencies":{"@types/inquirer":"^9.0.8","@types/node":"^20.11.28","@types/node-notifier":"^8.0.5","tsx":"^4.19.4","typescript":"^5.3.3","vitest":"^1.6.1"},"engines":{"node":">=18.0.0"},"gitHead":"b1c2ac36492efac7098b35c06874ae2cd0ef0efa","_id":"hilan-mcp@0.3.3","_nodeVersion":"24.18.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-lJyxoDzMBb+PGJmP1yH5NgPcQ+95wbkWwRFs8RdPLdPC/LxrOagvPgJhTZNWkeYwSenzWa/Jpo14zg5e0MKu3A==","shasum":"a7d89cde4050181e7e0fa0a6deca78400a3aee25","tarball":"https://registry.npmjs.org/hilan-mcp/-/hilan-mcp-0.3.3.tgz","fileCount":57,"unpackedSize":266361,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/hilan-mcp@0.3.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDv9IjxxX99rpNHR0p3UtiV2XFtVQFteEFg1xpeutCxrAiEAwdRg1CSlK5YZsiGIEbIpIhCJIhhS6O7hPKC/lFnYnLA="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:e118ab62-7265-450c-945f-b6f4780c3ea8"}},"directories":{},"maintainers":[{"name":"udah1","email":"udah10@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/hilan-mcp_0.3.3_1786084013130_0.0669287422043352"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-06T11:36:18.251Z","modified":"2026-08-07T06:26:53.657Z","0.1.0":"2026-08-06T11:36:18.536Z","0.2.0":"2026-08-06T12:08:30.161Z","0.3.0":"2026-08-06T13:10:03.168Z","0.3.1":"2026-08-06T16:08:40.515Z","0.3.2":"2026-08-06T17:37:12.643Z","0.3.3":"2026-08-07T06:26:53.287Z"},"bugs":{"url":"https://github.com/udah1/hilan-mcp/issues"},"author":{"name":"udah1"},"license":"MIT","homepage":"https://github.com/udah1/hilan-mcp#readme","keywords":["mcp","hilan","hilanet","payslip","form106","israel"],"repository":{"type":"git","url":"git+https://github.com/udah1/hilan-mcp.git"},"description":"MCP server for Hilan (Hilanet / חילן, חילנט, net.hilan.co.il) employee self-service — pull payslips and Form 106 into an AI assistant, for any tenant.","maintainers":[{"name":"udah1","email":"udah10@gmail.com"}],"readme":"<div align=\"center\">\n\n# hilan-mcp\n\n**An MCP server for [Hilan](https://net.hilan.co.il) (Hilanet / חילן, חילנט)** — pull your payslips and Form 106 into any AI assistant, for any Hilan tenant.\n\n[![npm version](https://img.shields.io/npm/v/hilan-mcp.svg?color=cb3837&logo=npm)](https://www.npmjs.com/package/hilan-mcp)\n[![npm downloads](https://img.shields.io/npm/dm/hilan-mcp.svg?color=blue)](https://www.npmjs.com/package/hilan-mcp)\n[![license](https://img.shields.io/npm/l/hilan-mcp.svg?color=green)](https://github.com/udah1/hilan-mcp/blob/master/LICENSE)\n[![node](https://img.shields.io/node/v/hilan-mcp.svg)](https://www.npmjs.com/package/hilan-mcp)\n\n</div>\n\n---\n\nThere's no official Hilan API. `hilan-mcp` drives a real (headless) Chromium\nbrowser via [Playwright](https://playwright.dev) to log in, then reuses\nthat same authenticated session to call Hilan's internal endpoints and\ndownload PDFs — no fragile hand-rolled cookie replay, and no plaintext\nsecrets stored anywhere.\n\n## Table of contents\n\n- [Features](#features)\n- [How it works](#how-it-works)\n- [Setup](#setup)\n  - [0. If you're on npm 12 or newer](#0-if-youre-on-npm-12-or-newer)\n  - [1. Register a tenant and enter credentials](#1-register-a-tenant-and-enter-credentials)\n  - [2. Add the server to your MCP client config](#2-add-the-server-to-your-mcp-client-config)\n  - [3. Use it](#3-use-it)\n- [For AI agents setting this up on a user's behalf](#for-ai-agents-eg-claude-setting-this-up-on-a-users-behalf)\n- [MCP tools](#mcp-tools)\n- [Scope](#scope-v1)\n- [Reliability notes](#reliability-notes-learned-from-a-real-end-to-end-run)\n- [Security model](#security-model)\n- [Development](#development)\n- [License](#license)\n\n## Features\n\n- 📄 **Payslips** — structured Bruto/Neto/salary-parts data, plus the PDF, for any month in the tenant's history.\n- 🧾 **Form 106** — annual tax summary PDFs, per year.\n- 🔐 **Encrypted local storage** — SQLCipher-encrypted SQLite, key never written to disk in plaintext; OS-native credential store (Keychain/DPAPI/Secret Service) support.\n- 🏢 **Any Hilan tenant** — login form fields are detected per-tenant instead of hardcoded to one employer.\n- 💬 **Answers, not raw JSON** — tools like `query` answer \"how much did I earn in June?\" straight from local data, no network round-trip.\n- 🔄 **Self-updating awareness** — the server checks for newer versions and lets your AI agent offer to update you, without needing its own UI.\n\n## How it works\n\nThis server drives a real (headless) Chromium browser via\n[Playwright](https://playwright.dev) to log in, then reuses that same\nauthenticated browser context's HTTP client (`context.request`) to call\nHilan's internal `.asmx` JSON endpoints and download PDFs — so it\nautomatically inherits whatever cookies/headers a real page load would\nhave set up, instead of a fragile hand-rolled cookie replay.\n\nLogin form fields differ per Hilan tenant (some have 2 fields, some 3), so\nthe login form is inspected fresh for each tenant the first time you ingest\ncredentials, rather than hardcoded to one company.\n\n## Setup\n\n### 0. If you're on npm 12 or newer\n\nnpm 12 blocks dependency install scripts unless you allow them, and one of\nthis package's dependencies (`better-sqlite3-multiple-ciphers`, the\nencrypted SQLite engine) needs its install script to compile a native\nbinding. Without it, setup appears to succeed and then every tool that\ntouches the database fails. Allow it once, for all future `npx` and global\ninstalls:\n\n```bash\nnpm config set allow-scripts=better-sqlite3-multiple-ciphers --location=user\n```\n\nOr per install, if you'd rather not set it globally:\n\n```bash\nnpm install -g --allow-scripts=better-sqlite3-multiple-ciphers hilan-mcp\n```\n\nOn npm 11 and older this isn't needed — install scripts still run by\ndefault. Check with `npm --version`.\n\n### 1. Register a tenant and enter credentials\n\nRun this **yourself**, directly in your own terminal — never paste real\ncredentials into an AI chat. It installs the Chromium browser Playwright\nneeds (one-time), launches a headless browser, detects your tenant's actual\nlogin fields, and prompts you for each one (passwords are masked). No local\nclone or `npm install` needed — `npx` fetches the package on the fly:\n\n```bash\nnpx hilan-mcp setup --tenant amdocs        # tenant subdomain\n# or\nnpx hilan-mcp setup --tenant 5227          # numeric org code, resolved automatically\n```\n\n> If you already have the package installed some other way, the equivalent\n> lower-level command is `npx hilan-mcp ingest-creds --tenant <...>` —\n> `setup` just also handles the one-time Chromium install first.\n\nThe first time you run this you'll be asked to set an **encryption key**\nfor the local database (min 6 characters) — this key is never written to\ndisk in plaintext.\n\nFor the MCP server to open the database on its own (without a human\npresent to respond to a prompt every time), it looks for the key in this\norder:\n\n1. **OS credential store** (recommended) — run `npx hilan-mcp setup-key`\n   once, yourself, in your own terminal. You'll be asked to type a key (or\n   press Enter to generate a strong random one), and it's stored in your\n   OS's native secure storage: **macOS Keychain** (verified — same file\n   that holds your Wi-Fi/browser passwords, silent, no dialogs),\n   **Windows** (DPAPI, tied to your Windows user account), or **Linux**\n   (Secret Service / `secret-tool`, needs a keyring daemon like GNOME\n   Keyring or KWallet). Nothing ends up in any config file at all.\n2. **`HILAN_DB_KEY` env var** — set it in the server's `env` block (see\n   step 2). Same trust model as any other API key/secret configured for an\n   MCP server in `mcp.json` (local file, not committed to git).\n3. **Desktop notification** (last resort) — the server falls back to\n   asking via a system notification (with a plain terminal prompt as a\n   further fallback), but that requires a human to respond within ~30s of\n   every tool call that needs the database, so it's not recommended for\n   normal use.\n\n### 2. Add the server to your MCP client config\n\nRunning the package with **no arguments** starts the MCP server itself\n(stdio transport) — that's what your MCP client's config actually invokes:\n\n```json\n{\n  \"mcpServers\": {\n    \"hilan\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"hilan-mcp\"]\n    }\n  }\n}\n```\n\n<details>\n<summary><strong>Single-employer setup — skip passing <code>tenant</code> on every tool call</strong></summary>\n\nIf you only ever use this for **one** employer, you can set a default\ntenant in the server's `env` block. Only the **first** of these that's set\nis used, in this order — `COMPANY_URL` → `COMPANY_TENANT` → `COMPANY_CODE`:\n\n```json\n{\n  \"mcpServers\": {\n    \"hilan\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"hilan-mcp\"],\n      \"env\": {\n        \"COMPANY_TENANT\": \"amdocs\",\n        \"HILAN_DB_KEY\": \"the-encryption-key-you-set-during-ingest-creds\",\n        \"NODE_EXTRA_CA_CERTS\": \"/path/to/your-corporate-ca-bundle.pem\"\n      }\n    }\n  }\n}\n```\n\n`HILAN_DB_KEY` is only needed if you didn't run `npx hilan-mcp setup-key`\n(see step 1) — the OS credential store takes priority when both are\npresent. `NODE_EXTRA_CA_CERTS` is only needed on networks with a\nTLS-intercepting corporate proxy (see [Reliability notes](#reliability-notes-learned-from-a-real-end-to-end-run)) — omit it otherwise.\n\n| Env var | Example | Notes |\n|---|---|---|\n| `COMPANY_URL` | `https://amdocs.net.hilan.co.il` | Highest priority. |\n| `COMPANY_TENANT` | `amdocs` | Bare subdomain. Used only if `COMPANY_URL` isn't set. |\n| `COMPANY_CODE` | `5227` | Numeric org code. Used only if neither above is set — requires an extra lookup round-trip. |\n\nThis same fallback also applies to `npx hilan-mcp ingest-creds` —\n`--tenant` is optional if one of these env vars is set. An explicit\n`tenant` argument (or `--tenant` flag) always overrides the env vars.\n\n</details>\n\n<details>\n<summary><strong>Running from a local clone instead of the published package</strong></summary>\n\nUse `\"command\": \"node\", \"args\": [\"/absolute/path/to/hilan-mcp/dist/cli/index.js\"]`\n(after `npm install && npm run build` in that clone) instead of the `npx`\nform above.\n\n</details>\n\n### 3. Use it\n\nAsk your assistant things like \"sync my last 3 payslips from Hilan\",\n\"download my Form 106 for 2025\", or \"how much net/gross did I earn in June\n2026?\" (answered instantly from the local database via the `query` tool,\nno network round-trip, once that month has been synced at least once) — see\nthe full [tool list](#mcp-tools) below.\n\n## For AI agents (e.g. Claude) setting this up on a user's behalf\n\nIf a user asks you to set up `hilan-mcp` for them:\n\n1. **You must not** run `ingest-creds`/`setup`/`setup-key` yourself via a\n   shell tool — they prompt for a password and/or an encryption key\n   interactively, and your shell tool's transcript could capture what the\n   user types. Tell the user to open their **own** terminal and run\n   `npx hilan-mcp setup --tenant <their-org-code-or-subdomain>` themselves,\n   then (recommended) `npx hilan-mcp setup-key`.\n   Check their `npm --version` first: on npm 12+ they also need\n   [step 0](#0-if-youre-on-npm-12-or-newer) or the database will fail to\n   open later, in a way that doesn't look related to installation.\n2. Once that's done, **you** can safely add the MCP server entry to the\n   client's config file (the JSON blocks above) — that part has no secrets\n   in it as long as the user used `setup-key` (OS credential store) rather\n   than the `HILAN_DB_KEY` env var fallback.\n3. After the config is added, the user (or their client) needs to reload/\n   restart the MCP connection for the new tools to appear.\n4. Don't enable `HILAN_ENABLE_DEBUG_TOOLS` unless the user explicitly asks\n   for the raw `.asmx`/PDF exploration tools — they're an intentional\n   escape hatch (see [Security model](#security-model)), not needed for\n   normal use.\n5. If a tool response includes an `_updateNotice` field, tell the user and\n   ask if they'd like to update; if they decline, call `dismissUpdateNotice`\n   with that version (see [MCP tools](#mcp-tools)).\n\n## MCP tools\n\n| Tool | Summary |\n|---|---|\n| `listTenants` | List configured tenants — no secrets. |\n| `syncPayslips` | Fetch structured payslip data + PDFs for the last N months. |\n| `resyncPayslipMonth` | Re-fetch one specific month, overwriting what's stored. |\n| `syncForm106` | Download the Form 106 PDF for a given year. |\n| `downloadCombinedPayslipsPdf` | One PDF covering a whole month range. |\n| `getSalaryTrends` | Yearly averages, YoY %, CAGR, and notable raises — computed locally. |\n| `listTables` / `describeTable` | Inspect the queryable local schema. |\n| `query` | Run read-only SQL against local payslip/Form106 data. |\n| `getPersonalDetails` | Live lookup of personal details (not persisted). |\n| `dismissUpdateNotice` | Suppress a specific version's update notice. |\n| `debugCallAsmx` / `debugDownloadPdf` | Raw API exploration — off by default. |\n\n<details>\n<summary><strong>Full tool reference (arguments, behavior, notes)</strong></summary>\n\n#### `listTenants()`\nConfigured tenants (subdomain, capability, whether login has succeeded before) — no secrets.\n\n#### `syncPayslips(tenant?, monthsBack?, skipPdf?)`\nLogs in (reusing a saved session if it still works), fetches structured Bruto/Neto/salary-parts data for the last N months (default 3), and downloads each payslip PDF. Set `skipPdf: true` for a much faster numbers-only sync (e.g. \"what did I earn this year\"). `tenant` is optional if `COMPANY_URL`/`COMPANY_TENANT`/`COMPANY_CODE` is set. The tenant's own archive length caps how far back this can actually go — no need to guess a start date.\n\n#### `resyncPayslipMonth(tenant?, period, skipPdf?)`\nRe-fetches **one specific month** (e.g. `\"10_2019\"` or `\"10/2019\"`), overwriting whatever's stored. Use this instead of re-running `syncPayslips` over the whole history just to retry one bad/truncated PDF or to answer a one-off \"how much did I make in month X\" question.\n\n#### `syncForm106(tenant?, year?)`\nLogs in and downloads the Form 106 PDF for a given year (or the tenant's default year). Same `tenant` fallback as above.\n\n#### `downloadCombinedPayslipsPdf(tenant?, fromMonth, toMonth, destDir?)`\nDownloads **one PDF covering a whole month range** (e.g. all of 2015–2024) instead of one file per month. Automatically splits into multiple files if the range would otherwise exceed the server's URL length limit (see [Reliability notes](#reliability-notes-learned-from-a-real-end-to-end-run)) — practical ceiling is roughly 10–15 years per chunk.\n\n#### `getSalaryTrends(tenant?, fromYear?, toYear?)`\nComputes salary growth trends **from locally-synced payslips only** — no live login, no network call. Returns yearly average Bruto/Neto, year-over-year % change between adjacent years, CAGR between the first and last full (12-month) calendar year, overall growth (first vs. last synced month), and \"notable jumps\" (≥10% YoY change in Bruto). Optional `fromYear`/`toYear` scope the calendar years considered. Prefer this over hand-rolling aggregation with `query` — grouping/sorting by plain `period` text gets cross-year chronology wrong (e.g. `\"01_2011\"` sorts before `\"02_2010\"`); this tool sorts by actual calendar month internally.\n\n#### `listTables()` / `describeTable(table)`\nLists queryable tables (`payslips`, `form106` only) and their columns/indexes.\n\n#### `query(sql)`\nSELECT-only SQL against `payslips`/`form106`. **`credentials`, `sessions`, and `tenants` are hard-blocked** — see [Security model](#security-model). Good for quick questions like \"how much net/gross did I earn in June 2026\" without touching the network at all.\n\n#### `getPersonalDetails(tenant?)`\nLogs in and returns personal details (name, national ID, birthdate, address, phone, email, work start date, job type). Live lookup only — **not persisted** to the local database.\n\n#### `dismissUpdateNotice(version)`\nCall this if the user declines an update you offered them (see below), so that exact version isn't mentioned again.\n\n#### `debugCallAsmx(tenant?, servicePath, body?)` / `debugDownloadPdf(tenant?, relativeUrl, fileName)`\n**Debug/exploration only, disabled by default** — raw pass-through to any `.asmx` endpoint, or download of an arbitrary relative PDF URL, on a logged-in session. Requires `HILAN_ENABLE_DEBUG_TOOLS=1` in the server's `env` block; not registered otherwise. Filenames are sanitized (no path traversal) and URLs are restricted to the tenant's own origin (no SSRF).\n\n</details>\n\n**Update notifications**: this server has no UI of its own, so update\nchecks piggyback on whatever tool the agent happens to call next. At most\nonce a day, a successful tool response may include an extra\n`_updateNotice` field with a message like *\"A new hilan-mcp version is\navailable: 0.1.0 -> 0.2.0...\"* — if you're the AI agent reading this, tell\nthe user and ask if they'd like to update; if they say no, call\n`dismissUpdateNotice` with that version so it isn't raised again (a newer\nversion will still be announced later). The check itself hits the npm\nregistry with a 3s timeout and silently no-ops if it's unreachable (e.g.\noffline, corporate proxy) — it never fails a tool call.\n\n**Concurrency note**: every tool call for a given tenant is automatically\nqueued and run one-at-a-time internally (see `TenantService.runExclusive`)\n— Hilan's site (and/or the network path to it) doesn't handle concurrent\nrequests from one session well and everything times out otherwise. You\ndon't need to serialize calls yourself; different tenants still run fully\nin parallel.\n\n## Scope (v1)\n\n- ✅ Payslips (list, structured Bruto/Neto/parts, PDF)\n- ✅ Form 106 (PDF, per year)\n- ⛔ General file archive, Form 101 — not built (v1.1+)\n- ⛔ One-time-code / 2FA login — tenants that require it return a clear\n  \"unsupported\" error instead of hanging\n\nTenants whose internal API response doesn't match the structured shape seen\non the reference tenant (Amdocs) are marked `pdf_only`: payslip syncs still\nwork and still save a PDF, just without the parsed Bruto/Neto numbers.\n\n## Reliability notes (learned from a real end-to-end run)\n\n<details>\n<summary>Corporate TLS-intercepting proxies, PDF URL quirks, truncation retries, URL-length limits, and OTP false positives</summary>\n\n- **Corporate TLS-intercepting proxies** (e.g. Amdocs's network) break\n  Playwright's Node-side `context.request` for the internal API/PDF calls\n  with `self-signed certificate in certificate chain`. Set\n  `NODE_EXTRA_CA_CERTS` to your organization's exported CA bundle in the\n  server's `env` block if you hit this.\n- **Direct PDF download URLs are relative to `/Hilannetv2`**, not the\n  domain root — a URL returned by the JSON API like\n  `PersonalFile/PdfPaySlip.aspx/...` actually lives at\n  `<tenant>/Hilannetv2/PersonalFile/PdfPaySlip.aspx/...`. Confirmed via a\n  live browser network capture.\n- PDF downloads go through an **in-page `fetch()`** (same-origin, raw URL)\n  rather than `context.request`, which silently re-encodes literal `/` in\n  query strings to `%2F` and gets 404'd by some endpoints.\n- The same corporate proxy occasionally **truncates a PDF response\n  mid-stream** (observed: suspiciously round 32768-byte cutoffs) without\n  the fetch itself erroring. `downloadPdf` checks the whole buffer for the\n  standard `%%EOF` PDF trailer (not just the tail — some genuine Hilan\n  PDFs have tens/hundreds of KB of trailing null-byte padding *after* a\n  valid `%%EOF`) and retries up to 3 times before giving up.\n- Combined multi-month PDFs (`PaySlipApiapi.asmx/GetMultiplePaySlipData`,\n  one file covering a date range instead of one per month) hit a hard\n  **HTTP 404 once the URL exceeds ~2048 characters** — that's IIS's\n  default `requestFiltering maxQueryString` limit, not a Hilan-specific\n  cap (each month adds ~11 chars to the URL). Verified working at 120\n  months (10 years, ~1.4KB URL); verified failing at 194 months (~16\n  years, 2.2KB URL) — so a ~15-year request is the realistic practical\n  ceiling per combined PDF. Not an issue for per-month syncing\n  (`syncPayslips`), which never builds one huge URL.\n- A tenant's *regular* login page can unconditionally show a \"log in with\n  a one-time code\" button/link even when normal username+password login\n  is fully supported and working — don't treat that text alone as proof\n  OTP is required. Only genuinely new OTP-input fields (or navigating to\n  an OTP-specific route) after a failed login count as OTP being required.\n\n</details>\n\n## Security model\n\n- The whole SQLite database (`~/Library/Application Support/HilanMcp/hilan.db`\n  on macOS, XDG/AppData equivalents elsewhere) is encrypted at rest via\n  SQLCipher (`better-sqlite3-multiple-ciphers`), keyed by a passphrase you\n  choose that is **never persisted to disk**. PDFs live alongside it under\n  `.../HilanMcp/pdfs/<tenant>/`, and rolling log files under\n  `.../HilanMcp/logs/` (redacted — see below). App-data, PDF, and log\n  directories are created with `0700` permissions; the DB and PDF files\n  themselves with `0600`.\n- `credentials` (your login fields) and `sessions` (saved browser cookies)\n  are ordinary tables *inside* that encrypted database, but they are **never\n  reachable through the `query`/`listTables`/`describeTable` tools** — those\n  are hard-allowlisted to `payslips` and `form106` only, validated via a\n  full SQL AST parse (not a regex) so `JOIN`s, subqueries, and quoted\n  identifiers can't be used to sneak past the allowlist (see\n  `src/utils/sqlValidation.ts` and `src/tests/sqlValidation.test.ts`).\n- Cookies and auth headers are redacted from log files and from any error\n  message a tool call returns, so a Playwright error can't leak your\n  session into your chat history (see `src/utils/redact.ts`).\n- Credential entry (`ingest-creds`) must be run directly by you in your own\n  terminal — never through an assistant's shell tool, whose transcript could\n  capture what you typed.\n- `debugCallAsmx` is an intentional escape hatch: it lets a logged-in\n  session call **any** `.asmx` endpoint with **any** body, bypassing the\n  curated tools above. It was added for API exploration during\n  development and is one-employee-scoped (same session, same\n  permissions you already have on the site) — it can't reach other\n  employees' data or other tenants — but it could in principle call a\n  write endpoint (Hilan generally gates writes behind approval flows, but\n  this hasn't been audited). Off by default (`HILAN_ENABLE_DEBUG_TOOLS`);\n  treat it like `query`'s SQL escape hatch — fine for an AI assistant you\n  trust to explore with, not something to expose to untrusted input.\n\n## Development\n\nClone this repo to work on the code itself (not needed just to *use* the\nserver — see [Setup](#setup) above for that):\n\n```bash\nnpm install\nnpm run typecheck\nnpm test                 # unit tests (SQL allowlist, path-traversal, redaction, update checks, ...)\nnpm run build            # compile TypeScript -> dist/\nnpm run start:mcp        # run the server directly from source (stdio transport)\nnpm run setup            # local equivalent of `npx hilan-mcp setup`\nnpm run ingest-creds     # register a tenant + credentials (interactive, run yourself)\nnpm run setup-key        # store the DB encryption key in your OS credential store (interactive, run yourself)\n```\n\n`npm publish` ships only `dist/`, `README.md`, and `LICENSE` (see the\n`files` field in `package.json`) — source, tests, and local research\nartifacts are excluded. `prepublishOnly` runs typecheck + tests + build\nfirst, so a broken build can't be published. The published `bin` entry\n(`dist/cli/index.js`) is what `npx hilan-mcp` and `npx hilan-mcp <subcommand>`\nboth invoke.\n\n## License\n\n[MIT](./LICENSE)\n","readmeFilename":"README.md"}