korea-invoice-mcp
README.md
# Korea Invoice MCP ๐ฐ๐ท โ How can my AI agent issue a ์ ์์ธ๊ธ๊ณ์ฐ์ (electronic tax invoice) in Korea?
<!-- install-cta -->
## Use it in 60 seconds
Paste this into your MCP client config (Claude Desktop, Cursor, Windsurf, or any MCP-capable agent):
```json
{
"mcpServers": {
"korea-invoice": {
"type": "http",
"url": "https://inv-kr.wishpool.app/mcp"
}
}
}
```
Nothing to install. Credentials, when you need them, travel as HTTP headers on each request and are never stored โ see the [threat model](https://mcp.wishpool.app/trust).
### Or run it yourself
Would you rather not send production credentials to a server you do not control? Deploy this identical code to your own account and point your agent at your own URL:
[](https://vercel.com/new/clone?repository-url=https://github.com/junter1989k-ai/korea-invoice-mcp)
```bash
git clone https://github.com/junter1989k-ai/korea-invoice-mcp && cd korea-invoice-mcp && npx vercel --prod
```
MIT-licensed. Self-hosting removes us from the picture entirely, at no cost and with no loss of function.
---
Remote MCP server that lets any AI agent issue, query and transmit Korean **์ ์์ธ๊ธ๊ณ์ฐ์** (electronic tax invoices) through **POPBiLL / Linkhub** โ Korea's national-scale e-tax-invoice ASP that issues the invoice to the buyer and reports it to **๊ตญ์ธ์ฒญ (NTS / National Tax Service)**. It mints a Linkhub session token (HMAC-SHA256) per request, computes **๋ถ๊ฐ์ธ (VAT)**, issues in one step (`registIssue` / ์ฆ์๋ฐํ), and returns the **๋ฌธ์๊ด๋ฆฌ๋ฒํธ** and **๊ตญ์ธ์ฒญ ์น์ธ๋ฒํธ**. Stateless, bring-your-own credentials, never stores anything.
> **Pure BYO โ we are not an authentication intermediary.** You self-issue your **own** Linkhub `LinkID` + `SecretKey` (free at [linkhub.co.kr](https://www.linkhub.co.kr), ํํธ๋ ์ ์ฒญ, ~20 min), join [popbill.co.kr](https://www.popbill.com) (์ฐ๋ํ์๊ฐ์
), and register your **๊ณต๋์ธ์ฆ์** (certificate) on your **own** POPBiLL account. This server never holds a LinkID and never issues invoices on a shared identity. See [Why no ISV-managed mode](#why-no-isv-managed-mode).
**Live endpoint:** `https://inv-kr.wishpool.app/mcp` ยท Registry: `app.wishpool/korea-invoice-mcp`
## Quick start
```json
{
"mcpServers": {
"korea-invoice": {
"type": "http",
"url": "https://inv-kr.wishpool.app/mcp",
"headers": {
"x-popbill-linkid": "your-linkhub-linkid",
"x-popbill-secretkey": "your-linkhub-secretkey",
"x-popbill-corpnum": "1234567890",
"x-popbill-mode": "prod"
}
}
}
}
```
`x-popbill-corpnum` is your 10-digit ์ฌ์
์๋ฒํธ โ the **invoicer/seller**; invoices are issued *from* this business. Omit `x-popbill-mode` to stay in **POPBILL_TEST** (no fiscal effect). Your keys and certificate stay with you โ this server never stores them.
### Getting your credentials (self-service, free)
1. **Linkhub ํํธ๋ ์ ์ฒญ** at [linkhub.co.kr](https://www.linkhub.co.kr) โ you receive your own `LinkID` + `SecretKey` (issued in ~20 minutes).
2. **POPBiLL ์ฐ๋ํ์๊ฐ์
** at [popbill.com](https://www.popbill.com) with the same business.
3. **Register your ๊ณต๋์ธ์ฆ์** (์ ์์ธ๊ธ๊ณ์ฐ์์ฉ ์ธ์ฆ์) on your POPBiLL account โ required by the NTS to issue tax invoices. It stays on your account; it never passes through this server.
## Tools
| Tool | What it does |
|---|---|
| `create_invoice` | Issue a ์ ์์ธ๊ธ๊ณ์ฐ์ in one step (`registIssue` = ์ฆ์๋ฐํ) โ `invoicer_corp_name`, `invoicer_ceo_name`, `invoicee_corp_name` (+ `invoicee_corp_num` for ์ฌ์
์ buyers), `items` (`item_name`, `qty`, `unit_cost` KRW/NET), `tax_type` (๊ณผ์ธ/์์ธ/๋ฉด์ธ), `purpose_type` (์์/์ฒญ๊ตฌ) in; **mgt_key + ntsConfirmNum** out. The seller ์ฌ์
์๋ฒํธ comes from the header; **๋ถ๊ฐ์ธ is computed for you**. `mgt_key` auto-generated if omitted. |
| `query_invoice` | Look up one invoice by `mgt_key` + `mgt_key_type` (SELL default / BUY / TRUSTEE) โ returns the `stateCode` (200 ๋ฐํ์๋ฃ, 300 ๊ตญ์ธ์ฒญ ์ ์ก๋๊ธฐ, 320 ๊ตญ์ธ์ฒญ ์ ์ก์๋ฃ, 330 ์ ์ก์คํจ, 400 ๋ฐํ์ทจ์), `ntsConfirmNum`, timestamps and totals. |
| `send_to_nts` | **Force immediate** ๊ตญ์ธ์ฒญ (NTS) transmission of an already-issued invoice. Normally transmission is automatic the **next day** (์ต์ผ์ ์ก); this only sends it now instead of waiting for the batch. |
Owner policy guardrails ride optional headers (`x-agentpay-max-amount`, `x-agentpay-approval-above`, `x-agentpay-allowed-tools`) โ set by the human owner in client config; the agent cannot relax them. The cap applies to the invoice **total (KRW)**.
## ๋ถ๊ฐ์ธ (VAT) โ computed server-side
POPBiLL does not compute VAT; this server does, deterministically:
- **๊ณผ์ธ** (standard): per-line `tax = floor(supplyCost ร 0.10)` โ ์ ๋จ์ ์ ์ฌ (integer truncation).
- **์์ธ** (zero-rated export) / **๋ฉด์ธ** (exempt): `tax = 0`.
Each line carries a NET `unit_cost` in whole won; the server sums `supplyCostTotal`, `taxTotal` and `totalAmount` and stringifies the amounts as POPBiLL expects.
## How it works (POPBiLL / Linkhub flow)
1. **Session token (HMAC-SHA256, per request, never cached):** `POST auth.linkhub.co.kr/{POPBILL|POPBILL_TEST}/Token` with body `{"access_id": "<์ฌ์
์๋ฒํธ>", "scope": ["member", "110"]}` (110 = ์ธ๊ธ๊ณ์ฐ์ scope). The request is signed:
`signature = base64( HMAC_SHA256( base64_decode(SecretKey), "POST\n" + base64(sha256(body)) + "\n" + callDT + "\n2.0\n/{ServiceID}/Token" ) )`,
with headers `x-lh-date`, `x-lh-version: 2.0`, `Authorization: LINKHUB {LinkID} {signature}`. Returns `session_token`.
2. **Issue:** `POST /Taxinvoice` with header `X-HTTP-Method-Override: ISSUE` and the `Taxinvoice` JSON (`forceIssue` set for ์ฆ์๋ฐํ). Business host `popbill-test.linkhub.co.kr` (test) / `popbill.linkhub.co.kr` (prod), `Authorization: Bearer {session_token}`, `x-pb-version: 1.0`. Returns the ๊ตญ์ธ์ฒญ ์น์ธ๋ฒํธ.
3. **Transmit / status:** `POST /Taxinvoice/{MgtKeyType}/{MgtKey}` (override `NTS`) forces transmission; `GET /Taxinvoice/{MgtKeyType}/{MgtKey}` reads the stateCode.
The token signing and forwarding happen in memory per-request; nothing is embedded or stored.
## Why no ISV-managed mode
An ASP integrator *could* hold one master `LinkID`/`SecretKey` and issue invoices for many merchants on that shared identity (an "ISV-managed" or ๋ํ model). This server deliberately **does not** do that: it would make us an authentication intermediary holding other businesses' issuing power and, in Korea, the ๊ณต๋์ธ์ฆ์ that legally binds a ์ ์์ธ๊ธ๊ณ์ฐ์. Pure BYO keeps issuing authority โ and liability โ entirely with the merchant, and keeps this server a stateless forwarder that stores nothing. It costs the merchant one free 20-minute signup; we think that is the right trade.
## Scope & honest gaps
- **Issue + query + force-transmit in v0.1.** Cancelling/amending an issued invoice (์์ ์ธ๊ธ๊ณ์ฐ์ / ๋ฐํ์ทจ์) is a separate POPBiLL operation kept out of this version to stay focused. To correct a mistake, issue a ์์ ์ธ๊ธ๊ณ์ฐ์ through your POPBiLL account.
- **KRW only**, whole-won integer amounts.
- **NTS transmission timing:** `create_invoice` issues to the buyer immediately and gets a ์น์ธ๋ฒํธ; the actual ๊ตญ์ธ์ฒญ ์ ์ก defaults to next-day (์ต์ผ์ ์ก). `send_to_nts` forces it now. There is no way to make issuance itself skip the buyer โ issuing *is* delivering to the buyer.
- **No real-credential live issue was run here** โ see below.
## Develop
```bash
node test/serve.js # local server on :3244
node test/e2e.js # protocol + validation + ๋ถ๊ฐ์ธ-math + HMAC-SHA256 test vectors + LIVE probes
```
The e2e suite asserts the **Linkhub signature algorithm** against a fixed test vector (fixed input โ fixed HMAC-SHA256 base64), the VAT math (๊ณผ์ธ floor / ์์ธ / ๋ฉด์ธ), and makes **two real calls** to the government-facing POPBiLL/Linkhub test infrastructure:
- Fake `LinkID` โ `auth.linkhub.co.kr/POPBILL_TEST/Token` returns `-99004021` ("๋งํฌ์์ด๋๊ฐ ์กด์ฌํ์ง ์์ต๋๋ค.") โ the auth endpoint is alive and the full HMAC signing path is exercised.
- Fake `Bearer` โ `popbill-test.linkhub.co.kr/Taxinvoice/SELL/...` returns `-99910004` ("Token decryption failed.") โ the business endpoint is alive.
These are the deepest live proofs possible without a portal-minted `LinkID` and a registered ๊ณต๋์ธ์ฆ์.
## Safety
Pure stateless translation layer to the POPBiLL / Linkhub API. Credentials travel per-request in headers; the session token is minted (HMAC-SHA256) and used in memory only, then discarded; the ๊ณต๋์ธ์ฆ์ stays on the merchant's own POPBiLL account. Nothing is stored or logged. [Privacy policy](https://inv-kr.wishpool.app/privacy).
## Sister servers
National e-invoices: Mexico CFDI 4.0 [inv-mx](https://inv-mx.wishpool.app) ยท Brazil NFS-e [inv-br](https://inv-br.wishpool.app) ยท Chile DTE [inv-cl](https://inv-cl.wishpool.app) ยท Poland KSeF [inv-pl](https://inv-pl.wishpool.app) ยท Hungary NAV [inv-hu](https://inv-hu.wishpool.app) ยท Malaysia MyInvois [inv-my](https://inv-my.wishpool.app). Korea payments (card / KakaoPay / NaverPay / ๊ฐ์๊ณ์ข via Toss) [mcp-kr](https://mcp-kr.wishpool.app) ยท Shipping for AI agents [logi-tw](https://logi-tw.wishpool.app) ยท Local payments in 81+ countries, one family: [mcp.wishpool.app](https://mcp.wishpool.app) (Taiwan e-invoice ้ปๅญ็ผ็ฅจ included).
MIT licensed.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing