1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude に T番号を投げたら 0.5 秒で適格請求書かどうか判定してくれた話【国税庁 API × MCP】

1
Last updated at Posted at 2026-05-16

この記事で実装する国税庁インボイス検証は、pay-per-call-mcp に check_tax ツールとして組み込み済みです。Claude Desktop に追加するだけですぐ使えます。

この記事でわかること

  • 国税庁インボイス API を MCP ツールとして Claude に組み込む方法
  • check_tax ツールで適格請求書番号を自然言語で検証する仕組み
  • 源泉徴収対象判定を自動化するロジックの実装
  • freee 仕訳登録前に番号チェックを挟む実装パターン
  • Claude Desktop から「T123...の番号は有効ですか?」と聞くだけで動く設定

はじめに

2023年10月のインボイス制度開始以降、取引先から受け取った請求書の適格請求書発行事業者番号(T + 13桁)が有効かどうかを確認する業務が発生しています。

手作業で国税庁サイトを検索するのは非効率です。この記事では:

  1. 国税庁 Web-API を TypeScript で叩く基本実装
  2. MCP サーバーのツールとして組み込む方法
  3. Claude Desktop から自然言語で番号検証できるようにする

までを解説します。


国税庁インボイス検索 Web-API の基本

項目 内容
エンドポイント https://invoice.e-tax.nta.go.jp/api/1/
認証 アプリケーション ID(無料取得)
形式 JSON / XML
レート制限 1リクエスト/秒(目安)

アプリケーション ID は 国税庁インボイス制度適格請求書発行事業者公表サイト から無料取得できます。


1. 基本実装(TypeScript)

型定義

// types/invoice.ts

export interface InvoiceResult {
  registrationNumber: string;  // T + 13桁
  name: string;                // 登録名称
  address: string;             // 所在地
  status: "valid" | "invalid" | "cancelled";
  registrationDate: string;    // 登録年月日
  expiryDate: string | null;   // 失効・取消日(null = 有効中)
  isActive: boolean;
}

検証関数

// lib/nta-invoice.ts

const NTA_API_BASE = "https://invoice.e-tax.nta.go.jp/api/1";

export async function verifyInvoiceNumber(
  registrationNumber: string,
  appId: string
): Promise<InvoiceResult> {
  // T を除いた13桁を使用
  const num = registrationNumber.replace(/^T/, "");
  if (!/^\d{13}$/.test(num)) {
    throw new Error(`不正な番号形式: ${registrationNumber}`);
  }

  const url = `${NTA_API_BASE}/chk?id=${appId}&number=${num}&type=01`;
  const res = await fetch(url, {
    headers: { Accept: "application/json" },
    signal: AbortSignal.timeout(10_000),
  });

  if (!res.ok) throw new Error(`API エラー: ${res.status}`);
  const data = await res.json();

  const hit = data?.registrations?.[0];
  if (!hit) {
    return {
      registrationNumber,
      name: "",
      address: "",
      status: "invalid",
      registrationDate: "",
      expiryDate: null,
      isActive: false,
    };
  }

  const isActive = hit.process === "01" && !hit.close_date;
  return {
    registrationNumber,
    name: hit.name ?? "",
    address: `${hit.address ?? ""}`,
    status: isActive ? "valid" : "cancelled",
    registrationDate: hit.registration_date ?? "",
    expiryDate: hit.close_date ?? null,
    isActive,
  };
}

一括検証(複数番号)

export async function verifyBatch(
  numbers: string[],
  appId: string,
  concurrency = 3
): Promise<InvoiceResult[]> {
  const results: InvoiceResult[] = [];
  for (let i = 0; i < numbers.length; i += concurrency) {
    const chunk = numbers.slice(i, i + concurrency);
    const batch = await Promise.all(chunk.map(n => verifyInvoiceNumber(n, appId)));
    results.push(...batch);
    if (i + concurrency < numbers.length) {
      await new Promise(r => setTimeout(r, 1000)); // 1秒待機
    }
  }
  return results;
}

2. MCP ツールとして組み込む

MCP サーバーに組み込むことで、Claude Desktop から自然言語で番号検証できるようになります。

// mcp-server/src/tools/check-tax.ts

import { verifyInvoiceNumber } from "../lib/nta-invoice.js";

export const checkTaxTool = {
  name: "check_tax",
  description: `
適格請求書発行事業者の登録番号(T + 13桁)が有効かどうかを国税庁 API で確認する。
請求書を受け取ったとき、仕入税額控除を適用する前に使う。
登録取消・廃業の場合は isActive=false が返る。
認証不要で呼び出し可能。
  `.trim(),
  inputSchema: {
    type: "object",
    properties: {
      registrationNumber: {
        type: "string",
        description: "適格請求書発行事業者登録番号(例: T1234567890123)",
      },
      description: {
        type: "string",
        description: "取引の説明(任意)。源泉徴収判定のヒントに使う",
      },
      amountJpy: {
        type: "number",
        description: "取引金額(円)。指定すると源泉徴収額も計算する",
      },
    },
    required: ["registrationNumber"],
  },
};

export async function handleCheckTax(args: {
  registrationNumber: string;
  description?: string;
  amountJpy?: number;
}) {
  const APP_ID = process.env.NTA_APP_ID ?? "demo";
  const result = await verifyInvoiceNumber(args.registrationNumber, APP_ID);

  // 源泉徴収判定(フリーランス・個人事業主向け)
  let withholdingInfo = null;
  if (args.amountJpy && args.description) {
    const isWithholdingSubject = isSubjectToWithholding(args.description);
    if (isWithholdingSubject) {
      const base = args.amountJpy <= 1_000_000
        ? args.amountJpy
        : 1_000_000;
      const excess = Math.max(0, args.amountJpy - 1_000_000);
      const tax = Math.floor(base * 0.1021 + excess * 0.2042);
      withholdingInfo = {
        required: true,
        rate: args.amountJpy <= 1_000_000 ? "10.21%" : "20.42% (超過分)",
        estimatedTax: tax,
      };
    }
  }

  return {
    content: [{
      type: "text",
      text: JSON.stringify({ ...result, withholdingInfo }, null, 2),
    }],
  };
}

// 源泉徴収対象判定(簡易版)
function isSubjectToWithholding(description: string): boolean {
  const keywords = [
    "原稿", "デザイン", "イラスト", "講演", "翻訳", "通訳",
    "コンサルティング", "弁護士", "税理士", "社労士", "システム開発",
  ];
  return keywords.some(kw => description.includes(kw));
}

MCP サーバーへの登録

// mcp-server/src/index.ts(抜粋)

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [checkTaxTool, /* 他のツール */],
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  if (req.params.name === "check_tax") {
    return handleCheckTax(req.params.arguments as any);
  }
  // ...
});

3. Claude Desktop での使い方

設定(claude_desktop_config.json)

{
  "mcpServers": {
    "invoice-checker": {
      "command": "npx",
      "args": ["-y", "pay-per-call-mcp"],
      "env": {}
    }
  }
}

環境変数なし(Demo Mode)でも check_tax は動作します。

実際の会話例

ユーザー: 請求書に T1234567890123 という番号があるんだけど、有効か調べて。
         取引は「Webサイト制作費」で110,000円。

Claude: check_tax ツールで確認します...

結果:
- 登録番号: T1234567890123
- 事業者名: 株式会社〇〇デザイン
- ステータス: 有効(valid)
- 登録日: 2023-10-01
- 仕入税額控除: 適用可能

源泉徴収について:
- 「Webサイト制作費」はデザイン業務として源泉徴収の対象になる可能性があります
- 110,000円の場合: 源泉徴収税額 約 11,231円(税込額から算出)

4. freee と組み合わせる

freee API と組み合わせると、請求書インポート時に自動で番号チェックできます。

async function importInvoiceWithValidation(
  freeeClient: FreeeClient,
  invoiceData: {
    partnerName: string;
    registrationNumber: string;
    amount: number;
    issueDate: string;
  }
) {
  // ① インボイス番号を検証
  const taxResult = await verifyInvoiceNumber(
    invoiceData.registrationNumber,
    process.env.NTA_APP_ID!
  );

  if (!taxResult.isActive) {
    throw new Error(
      `${invoiceData.registrationNumber} は無効な登録番号です(${taxResult.status})`
    );
  }

  // ② 有効な場合のみ freee に仕訳登録
  const deal = await freeeClient.createDeal({
    issue_date: invoiceData.issueDate,
    type: "expense",
    details: [{
      account_item_id: 820,  // 仕入高
      tax_code: 1,           // 課税仕入 10%
      amount: invoiceData.amount,
      description: `${invoiceData.partnerName}(${invoiceData.registrationNumber})`,
    }],
  });

  return { deal, taxResult };
}

まとめ

実装 ポイント
基本検証 T + 13桁 → 国税庁 API → isActive で判定
一括処理 verifyBatch で concurrency 制御
MCP 組み込み check_tax ツールで Claude から自然言語検証
freee 連携 仕訳登録前に番号チェックを挟む

インボイス制度は仕入税額控除に直結するため、チェック漏れが税務リスクになります。Claude に自動検証させることで確認コストをゼロにできます。


すぐ試す

Claude Desktop に以下を追加するだけで check_tax が使えます:

{
  "mcpServers": {
    "pay-per-call": {
      "command": "npx",
      "args": ["-y", "pay-per-call-mcp"]
    }
  }
}

試したい人へ

英語の Glama Playground が苦手な人は、以下のコマンドで日本のターミナルから動かせます:

npx -y pay-per-call-mcp@latest
# → 8 つのデモ API がすぐ使えます

設定不要、課金なし、サインアップ不要。

よくある質問

Q. pay-per-call-mcp の check_tax は無料で使えますか?

A. はい。check_tax は認証不要で Demo Mode でも動作します。国税庁 API 自体が無料公開されているため、課金は発生しません。

Q. 適格請求書番号の形式は?

A. T + 13桁の数字です(例: T1234567890123)。法人番号(13桁)をベースにしたものが多いですが、個人事業主の場合は異なる番号が割り振られます。

Q. 登録が取り消された番号を使って仕入税額控除を申請してしまった場合は?

A. 税務調査で指摘される可能性があります。請求書受領時にリアルタイムで番号チェックする仕組みを作ることで、このリスクを事前に防げます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?