Skip to main content
Glama
junter1989k-ai

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:

[![Deploy with Vercel](https://vercel.com/button)](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.

Maintenance

ActivityMaintained
ResponsivenessSyncing