# 輸入実行関税率表をMCPサーバー化する！

**公開日**: 2025-06-28T23:56:52+09:00Z
**カテゴリー**: Diary
**タグ**: プログラミング

---

今回は輸入実行関税率表をMCPサーバー化してみた・・・という日記。

## 輸入実行関税率表とは

輸入実行関税率表とは、日本に輸入される各種貨物に適用される関税率をHSコードと呼ばれる統計品目番号ごとに定めた一覧表のことを指す。

商取引で日本に輸入される貨物は例外をのぞきほぼ全てこの実行関税率表を元にコードを特定して、税関に申告を行う必要がある。

### 構成は？

アイテムの種類ごとに部と類で区分されている。分類は第１部の動物から２１部の美術品までと多岐にわたる。この世の全てのアイテムが網羅されている。

第1部　動物（生きているものに限る。）及び動物性生産品　部注
分類
第1類	動物（生きているものに限る。)
第2類	肉及び食用のくず肉
... 

第2部　植物性生産品　部注
分類
第6類	生きている樹木その他の植物及びりん茎、根その他これらに類する物品並びに切花及び装飾用の葉	
第7類	食用の野菜、根及び塊茎
...

第21部　美術品、収集品及びこつとう
分類
第97類	美術品、収集品及びこつとう

- 21部（Section）が大枠。部にぶらさがる形で97章（Chapter） で分類
- 各章は 4桁の項目（Heading）→6桁の小項目（Sub-heading）→9桁の品目（Statistical line） へと細分化

詳細ページ。

[https://www.customs.go.jp/tariff/2025_04_01/index.htm](https://www.customs.go.jp/tariff/2025_04_01/index.htm)

### 何種類あるのか？

品目数（11桁レベル）は約9,000行。日本で輸入され流通している全てのアイテムがこの中のいずれかに該当される。

### 利用のされ方は？

輸入を行う商社やメーカー（荷主）は一般的に通関業を専門で営む会社に輸入申告を依頼する。

そのため、通関業を行っている会社が荷主より提供された輸入書類をもとに、この関税率表から品目を特定し、税関に代理で申告を行う。これを一般的に通関手続と呼ぶ。

これを行うための「通関士」という国家資格がある。通関を業として営む事業所は通関士を置かなければいけない、という法律がある。

ただし、通関業務を行うこと自体は「通関士」の資格を保有していなくても可能。

## MCPサーバー化したい

自分は上に書いた「通関士」の資格を保有しておりこの領域にドメイン知識がある。

上述の通り9000以上の品目から税番を特定する作業は結構難しい印象。これをAIでなんとかできないか・・・ということを考えていた。

難しいというか情報量が多い。絞り込みにコツと時間がかかる。試しにやってみよう。

### ギターを輸入する場合

例えば「ギター」を輸入しようとする。第１部から順番にざっと眺めてみると、第18部に「楽器」という単語が見つかる。

第18部　光学機器、写真用機器、映画用機器、測定機器、検査機器、精密機器、医療用機器、時計及び楽器並びにこれらの部分品及び附属品

ぶらさがる「類」をみていくと９２類が該当しそうだ。

第92類	楽器並びにその部分品及び附属品

このようにまずは部と類からそれとなくあたりをつけていく。そうして細かい税率表の中をみていく。

![](https://images.microcms-assets.io/assets/c91d91814d4c435aac6eccfc4f87a846/406ee41e0c724e68ba4ab92565a2d53f/image.png)
9202.90-010にまさに「ギター」という単語があるのでこれで確定したかのように思える。

しかしまだ安心してはいけない。

あなたが輸入するギターはアコースティックギターだろうか？

下をみていくと、92.07の項目にもギターという単語が含まれている。

![](https://images.microcms-assets.io/assets/c91d91814d4c435aac6eccfc4f87a846/d5571056beb74e1a87feb051b696b5a9/image.png)
92.07　電気的に音を発生し又は増幅する楽器（例えば、オルガン、ギター及びアコーディオン）

そのまま下をみていくと、9207.90-010に「電気ギター」という項目があり、どうやらエレキギターの場合はこちらに該当しそう、ということがわかる。

このように一言で「ギター」というだけでは税番の特定はできない。そのようなアイテムは数多く存在する。この申告を元に関税を徴収する仕組みとなっているため、税番を特定することは重要な営みである。

もし「ギターを輸入したい。税番を調べてくれ」と頼まれたらこう聞き返さなければならない。

「ギターってのは電気的に音を発生し又は増幅する楽器のことであってる？」

### WEB検索システムも一応あるが・・・

ちなみに日本関税協会という団体がWEBタリフを公開している。

これを用いるとキーワードで税番を検索することができる。

[https://www.kanzei.or.jp/statistical/tariff/top/index/j](https://www.kanzei.or.jp/statistical/tariff/top/index/j)

「ギター」といれて検索すると、先ほど調べたような内容がヒットする。

![](https://images.microcms-assets.io/assets/c91d91814d4c435aac6eccfc4f87a846/15dc7b0d1d014753b751bc443af6e4e1/image.png)
これは結構使えそうな気もするが・・・

しかしこういうケースは割とレアで、輸入される品目と一致する名称がそのまま関税率表に載っていないケースもかなり存在する。

例えば「チューハイ」を調べてみよう。

![](https://images.microcms-assets.io/assets/c91d91814d4c435aac6eccfc4f87a846/721f00263d0244ceb416f86b56d6924b/image.png)
該当しない。ちなみにより一般的な名称である「酎ハイ」と調べてもヒットしない。「焼酎」でも同じだ。

「チューハイ」は３ー９%と低いアルコール度数の調整アルコール飲料であることから、2208.70-000のリキュール及びコーディアルに該当するとされるのが一般的である。これをキーワード検索から導き出すのは難しい。

### どのくらいこういう仕事がされてるのか。

財務省関税局が 2025 年 3 月 7 日に公表した資料「我が国の貿易を巡る諸情勢について」によれば、2024 年の輸入申告（許可・承認）件数は、航空・海上を併せて約 **1億8,972 万件**となっている。

もちろん「前回と同じ荷物なので前回と同じ内容で申告する」というケースが圧倒的に多い。ただ母数の1.9億という数字から、少なくとも数百万回、多ければ数千万回くらいの単位でこのような税番調査が一年間に行われていたのではないかと推測する。

我が国の貿易を巡る諸情勢について

[https://www.naccs.jp/archives/unkyou/20250307/shiryo7.pdf](https://www.naccs.jp/archives/unkyou/20250307/shiryo7.pdf)

## MCPツールで解決したい

以上のように税番の特定をするだけでも品目によっては手こずる案件も多い。

現状はこれらの業務を専門的な通関知識を持った人員に頼っており、属人化が問題になっているという話もよく聞く。通関の業務については税番を調べるだけだけではなく、関連法令を調べて書類揃えたりなどなど範囲は広い。ただ、少なくとも税番の特定においてだけはMCPツールを使うと結構効率化されるんじゃないかと思う。

## 実装方法について

Cloudflare Worker上にhono/mcpを使ってリモートMCPサーバーを建てる。

肝心のデータはWEBスクレイピングして収集した。

### データ収集

PythonのBeautifulSoupを使って地道に収集した。

![](https://images.microcms-assets.io/assets/c91d91814d4c435aac6eccfc4f87a846/2276fb4ebee54b8da21c6c7e55e42bf7/image.png)
難しいのはhtmlのテーブルで記述されているものの、RDB的な表データではなく、階層を持った木構造的なデータであること。上の写真のデータを図で表すとこのような感じになる。

![](https://images.microcms-assets.io/assets/c91d91814d4c435aac6eccfc4f87a846/af0c17afaa384a4c814f4f294953658d/image.png)
このあたりの階層が文字のインデントや-という記号で表されている。

まずは上から順番に読み取り深さのレベルを記録しながら１行ずつ配列化する。そして、配列の行をnode化しながら、最終的にjsonで表す木構造に組み替えていく・・・というような処理を行った。一部を抜粋すると、こんなコード。これで木構造を作る。

def build_forest(que):
    ret = deque()
    nodes = deque()
    for row in que:
        nodes.append(row_to_node(row))
    while nodes:
        node = nodes.pop()
        lvl = node["level"]
        if lvl == 0:
            ret.appendleft(node)
            continue
        for j in reversed(range(len(nodes))):
            parent_level = nodes[j]["level"]
            if parent_level < lvl:
                nodes[j]["children"].insert(0, node)
                break

    return ret最終的には以下のようなjsonデータを作った。

各nodeにchildrenを持たせて階層を表現。

[
  {
    "level": 0,
    "stat_code": "92.01",
    "hs_code": "",
    "desc": "ピアノ（自動ピアノを含む。）、ハープシコードその他鍵盤のある弦楽器",
    "rate": {},
    "unit": {},
    "law": [],
    "children": [
      {
        "level": 1,
        "stat_code": "9201.10",
        "hs_code": "000",
        "desc": "アップライトピアノ",
        "rate": {
          "基本": "無税",
          "WTO協定": "(無税)",
          "EPA各国": "無税..."
        },
        "unit": {
          "I": "NO",
          "II": "KG"
        },
        "law": [],
        "children": []
      },
      {
        "level": 1,
        "stat_code": "9201.20",
        "hs_code": "000",
        "desc": "グランドピアノ",
        "rate": {
          "基本": "無税",
          "WTO協定": "(無税)",
          "EPA各国": "無税..."
        },
        "unit": {
          "I": "NO",
          "II": "KG"
        },
        "law": [],
        "children": []
      },
      {
        "level": 1,
        "stat_code": "9201.90",
        "hs_code": "000",
        "desc": "その他のもの",
        "rate": {
          "基本": "無税",
          "WTO協定": "(無税)",
          "EPA各国": "無税..."
        },
        "unit": {
          "I": "NO",
          "II": "KG"
        },
        "law": [],
        "children": []
      }
    ]
  },
  {
    "level": 0,
    "stat_code": "92.02",
    "hs_code": "",
    "desc": "その他の弦楽器（例えば、ギター、バイオリン及びハープ）",
    "rate": {},
    "unit": {},
    "law": [],
    "children": [
      {
        "level": 1,
        "stat_code": "9202.10",
        "hs_code": "000",
        "desc": "弓で弾くもの",
        "rate": {
          "基本": "無税",
          "WTO協定": "(無税)",
          "EPA各国": "無税..."
        },
        "unit": {
          "I": "NO",
          "II": "KG"
        },
        "law": [
          "WA"
        ],
        "children": []
      },
      {
        "level": 1,
        "stat_code": "9202.90",
        "hs_code": "",
        "desc": "その他のもの",
        "rate": {
          "基本": "無税",
          "WTO協定": "(無税)",
          "EPA各国": "..."
        },
        "unit": {
          "I": "",
          "II": ""
        },
        "law": [],
        "children": [
          {
            "level": 2,
            "stat_code": "9202.90",
            "hs_code": "010",
            "desc": "− ギター",
            "rate": {
              "基本": "",
              "WTO協定": "",
              "EPA各国": "無税..."
            },
            "unit": {
              "I": "NO",
              "II": "KG"
            },
            "law": [
              "WA"
            ],
            "children": []
          },
          {
            "level": 2,
            "stat_code": "9202.90",
            "hs_code": "090",
            "desc": "− その他のもの",
            "rate": {
              "基本": "",
              "WTO協定": "",
              "EPA各国": "無税..."
            },
            "unit": {
              "I": "NO",
              "II": "KG"
            },
            "law": [
              "WA"
            ],
            "children": []
          }
        ]
      }
    ]
  },
  ...
]このような関税率表データに加えて、部や章の説明や、関連法令のページもjson化している。

## hono/mcpで配信する

あとはhono/mcpを使ってこのjsonデータを検索するツールを持たせる。

まずは検索ユーティリティクラスを定義。ここら辺はほぼAIに書いてもらいました。

// tarif-service.ts
export class TariffSearchService {
  /** 関税データを検索する */
  async searchTariffData(keyword: string) {
    const results: any[] = []

    // indexファイルから章リストを取得
    const indexData = await import(&apos;./tariffdata/index.json&apos;)

    for (const chapter of indexData.chapters) {
      try {
        // 各章のデータファイルを動的にインポート
        const chapterData = await import(
          `./tariffdata/j_${chapter.chapter.padStart(2, &apos;0&apos;)}_tariff_data.json`
        )

        // 階層データを再帰的に検索
        this.searchItemsRecursively(
          chapterData.default || chapterData,
          keyword,
          results
        )
      } catch (error) {
        // ファイルが存在しない場合はスキップ
        continue
      }
    }
    return results
  }
}
続いて最新のhono/mcpヘルパーを用いてツールを定義して配信する。

// mcp.ts
import { StreamableHTTPTransport } from &apos;@hono/mcp&apos;
import { McpServer } from &apos;@modelcontextprotocol/sdk/server/mcp.js&apos;
import { z } from &apos;zod&apos;
import { Context } from &apos;hono&apos;
import { HTTPException } from &apos;hono/http-exception&apos;
import { Hono } from &apos;hono&apos;
import type { Env } from &apos;hono&apos;
import { TariffSearchService } from &apos;./tariff-service.js&apos;

export const getMcpServer = async (c: Context<Env>) => {
  const server = new McpServer({
    name: &apos;japan-tariff-mcp&apos;,
    version: &apos;0.0.1&apos;,
  })

  // TariffSearchServiceのインスタンスを作成
  const searchService = new TariffSearchService()

  server.tool(
    &apos;searchTariff&apos;,
    &apos;関税データをキーワードで検索します&apos;,
    { keyword: z.string() },
    async ({ keyword }) => {
      try {
        const results = await searchService.searchTariffData(keyword)
        return {
          content: [
            {
              type: &apos;text&apos;,
              text: JSON.stringify(
                {
                  keyword,
                  found: results.length,
                  results: results.slice(0, 10), // 最大10件まで返す
                },
                null,
                2
              ),
            },
          ],
        }
      } catch (error) {
        return {
          content: [
            {
              type: &apos;text&apos;,
              text: `検索エラー: ${
                error instanceof Error ? error.message : &apos;Unknown error&apos;
              }`,
            },
          ],
        }
      }
    }
  )
  return server
}

const app = new Hono()

app.all(&apos;/&apos;, async (c) => {
  const mcpServer = await getMcpServer(c)
  const transport = new StreamableHTTPTransport()
  await mcpServer.connect(transport)
  return transport.handleRequest(c)
})

app.onError((err, c) => {
  console.log(err.message)

  if (err instanceof HTTPException && err.res) {
    return err.res
  }

  return c.json(
    {
      jsonrpc: &apos;2.0&apos;,
      error: {
        code: -32603,
        message: &apos;Internal server error&apos;,
      },
      id: null,
    },
    500
  )
})

export default app長いのでツールとしてはキーワード検索のみを掲載。他にも税率比較、法令索引、HSコード検索などを仕込んでいる。

あとはデプロイして、Claude Desktopなどから接続すれば輸入税番検索が行える。

## 使ってみた感じ

けっこういい。詳細は動画。

輸入実行関税検索MCPで税番を調べてみた。 [pic.twitter.com/P41oBUzD2R](https://t.co/P41oBUzD2R)

— qlitre (@kuri_tter) [June 28, 2025](https://twitter.com/kuri_tter/status/1938855338100564421?ref_src=twsrc%5Etfw)

MCPならではの良さ。いったん探して見つからなかったらひたすら関連するキーワードで探す、ということをやってくれる。この例でいくと、スピーカー、ラウドスピーカー、音響機器、音響、拡声器、、などなど。こういう動きが人間っぽい。

知り合いのこの通関の仕事をやっている有識者に試しに使ってもらったところかなり感触がいいというコメントももらった。

メインの処理は240行ほどのコードであるが、うお、AIすげー！ってことが実感できた事例。

## ソースコード

githubに公開している。

[https://github.com/qlitre/japan-tariff-mcp](https://github.com/qlitre/japan-tariff-mcp)

タリフのデータも同梱しているのであとはREADME.mdに沿ってwrangler.jsonc.templateを編集してもらえればすぐにリモートMCPサーバーを建てられます。

面倒だったら私が建てたサーバーを公開しているので、試してもらってもいいです。

{
  "mcpServers": {   
    "tariff-search": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@latest",
        "https://japan-tariff-mcp.qlitre.workers.dev/mcp"
      ],
      "env": {}
    }
}
## おわりに

ちなみにこの手のAI税番特定ソフトウェアは市場に存在している。ただし当然営利目的で作り込まれているものなのでそれなりに費用がかかる。

（おそらくは）同じレベルの精度且つ低コストで似たようなことができることに価値があると思っている。ではでは。