bizgate-mcp-server 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +139 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ if (authMode === "ip" && !process.env.BIZGATE_APP) {
37
37
  process.exit(1);
38
38
  }
39
39
  const dailyLimit = Number(process.env.BIZGATE_DAILY_LIMIT ?? "200");
40
+ const seedApiUrl = process.env.BIZGATE_SEED_API_URL ?? "";
40
41
  const usageFile = process.env.BIZGATE_USAGE_FILE ??
41
42
  join(homedir(), ".bizgate-mcp-usage.json");
42
43
  const baseUrl = process.env.BIZGATE_BASE_URL ??
@@ -204,7 +205,7 @@ server.tool("bizgate__company_search", "会社名または法人番号で企業
204
205
  }
205
206
  });
206
207
  // ---------- Tool 2: 部署検索 ----------
207
- server.tool("bizgate__department_search", "部署情報(部署名・住所・電話番号・カテゴリ)を検索する。会社名・法人番号での検索に加え、部署名キーワード(bKwd)やカテゴリ(cList)のみでの全企業横断検索も可能。最大500件。デフォルトで上位30件を表示(limitで変更可能)。", {
208
+ server.tool("bizgate__department_search", "会社名または法人番号で部署情報(部署名・住所・電話番号・カテゴリ)を検索する。最大500件。都道府県・カテゴリ・部署名キーワードで絞り込み可能。デフォルトで上位30件を表示(limitで変更可能)。", {
208
209
  shogo: z.string().optional().describe("会社名(例:株式会社○○)"),
209
210
  compno: z.string().optional().describe("法人番号(13桁)"),
210
211
  pList: z.string().optional().describe("都道府県コード(カンマ区切り。例: 13,14 = 東京都,神奈川県)"),
@@ -213,13 +214,9 @@ server.tool("bizgate__department_search", "部署情報(部署名・住所・
213
214
  bKOpr: z.string().optional().describe("キーワード結合演算子(0=OR(デフォルト), 1=AND)"),
214
215
  limit: z.number().optional().describe("表示する件数の上限(デフォルト: 30)"),
215
216
  }, async ({ shogo, compno, pList, cList, bKwd, bKOpr, limit }) => {
216
- // 部署検索: bKwd or cList があれば会社名なしでも検索可能
217
- if (!shogo && !compno && !bKwd && !cList) {
218
- return { content: [{ type: "text", text: "エラー: 会社名(shogo)、法人番号(compno)、部署名キーワード(bKwd)、カテゴリ(cList)のいずれかを入力してください。" }] };
219
- }
220
- if (compno && !/^\d{13}$/.test(compno)) {
221
- return { content: [{ type: "text", text: "エラー: 法人番号は13桁の数字で入力してください。" }] };
222
- }
217
+ const err = validateInput(shogo, compno);
218
+ if (err)
219
+ return { content: [{ type: "text", text: err }] };
223
220
  const displayLimit = limit ?? 30;
224
221
  try {
225
222
  const { docs, numFound } = await client.searchDepartments({
@@ -424,6 +421,140 @@ server.tool("bizgate__usage_status", "本日のBizGate API残り利用回数を
424
421
  ],
425
422
  };
426
423
  });
424
+ // ---------- Tool 7: プロスペクトリスト(シードAPI連携) ----------
425
+ const REVENUE_CATEGORIES = {
426
+ 1: "1. 1000万未満",
427
+ 2: "2. 1000万-3000万未満",
428
+ 3: "3. 3000万-1億未満",
429
+ 4: "4. 1億-5億未満",
430
+ 5: "5. 5億-10億未満",
431
+ 6: "6. 10億-20億未満",
432
+ 7: "7. 20億-100億未満",
433
+ 8: "8. 100億以上",
434
+ };
435
+ const MAX_API_CALLS_HARD_LIMIT = 150;
436
+ async function fetchSeeds(params) {
437
+ const url = new URL("/search", seedApiUrl);
438
+ for (const [k, v] of Object.entries(params))
439
+ url.searchParams.set(k, v);
440
+ const res = await fetch(url);
441
+ if (!res.ok)
442
+ throw new Error(`seed-api エラー: HTTP ${res.status}`);
443
+ return (await res.json());
444
+ }
445
+ function revenueMatchesMin(revenue, minCategory) {
446
+ if (!revenue)
447
+ return false;
448
+ const match = revenue.match(/^(\d+)\./);
449
+ if (!match)
450
+ return false;
451
+ return Number(match[1]) >= minCategory;
452
+ }
453
+ server.tool("bizgate__prospect_list", "シードリスト(国税庁法人データ)から条件に合う企業を抽出し、BizGate APIで詳細を照会してプロスペクトリストを作成する。BIZGATE_SEED_API_URLの設定が必要。", {
454
+ pref: z.string().optional().describe("都道府県(例: 東京都)"),
455
+ keyword: z.string().optional().describe("会社名キーワード(部分一致)"),
456
+ industry: z.string().optional().describe("業種キーワード(例: IT,製造)。BizGateの業種情報でフィルタリング"),
457
+ minRevenue: z.number().optional().describe("最低売上カテゴリ番号(1=1000万未満, 2=1000万-3000万, 3=3000万-1億, 4=1億-5億, 5=5億-10億, 6=10億-20億, 7=20億-100億, 8=100億以上)"),
458
+ targetCount: z.number().optional().describe("取得したい企業数(デフォルト: 10、最大: 20)"),
459
+ maxApiCalls: z.number().optional().describe("このツール1回で使うAPI上限(デフォルト: 50、最大: 150)"),
460
+ }, async ({ pref, keyword, industry, minRevenue, targetCount: rawTarget, maxApiCalls: rawMax }) => {
461
+ if (!seedApiUrl) {
462
+ return {
463
+ content: [{ type: "text", text: "エラー: BIZGATE_SEED_API_URL が設定されていません。管理者に連絡してください。" }],
464
+ };
465
+ }
466
+ const targetCount = Math.min(rawTarget ?? 10, 20);
467
+ const apiLimit = Math.min(rawMax ?? 50, MAX_API_CALLS_HARD_LIMIT);
468
+ const remaining = usageTracker.remaining();
469
+ const effectiveLimit = Math.min(apiLimit, remaining);
470
+ if (effectiveLimit < 5) {
471
+ return {
472
+ content: [{ type: "text", text: `エラー: 残りAPI回数が${remaining}回しかありません。明日以降にお試しください。${usageFooter()}` }],
473
+ };
474
+ }
475
+ // Step 1: シードAPIから候補取得
476
+ const seedParams = {
477
+ limit: String(targetCount * 5),
478
+ shuffle: "true",
479
+ };
480
+ if (pref)
481
+ seedParams.pref = pref;
482
+ if (keyword)
483
+ seedParams.keyword = keyword;
484
+ let seeds;
485
+ let seedTotal;
486
+ try {
487
+ const result = await fetchSeeds(seedParams);
488
+ seeds = result.results;
489
+ seedTotal = result.total;
490
+ }
491
+ catch (e) {
492
+ return {
493
+ content: [{ type: "text", text: `シードAPIへの接続に失敗しました: ${e instanceof Error ? e.message : "不明なエラー"}` }],
494
+ };
495
+ }
496
+ if (seeds.length === 0) {
497
+ return {
498
+ content: [{ type: "text", text: `条件に合う企業がシードリストに見つかりませんでした。${usageFooter()}` }],
499
+ };
500
+ }
501
+ // Step 2: BizGate APIで詳細照会 + フィルタリング
502
+ const matched = [];
503
+ let apiCalls = 0;
504
+ const batchSize = 5;
505
+ for (let i = 0; i < seeds.length && matched.length < targetCount && apiCalls < effectiveLimit; i += batchSize) {
506
+ const batch = seeds.slice(i, Math.min(i + batchSize, seeds.length));
507
+ const remaining2 = effectiveLimit - apiCalls;
508
+ const actualBatch = batch.slice(0, remaining2);
509
+ const results = await Promise.all(actualBatch.map(async (seed) => {
510
+ try {
511
+ const { docs } = await client.searchCompany({ compno: seed.compno });
512
+ apiCalls++;
513
+ return { seed, doc: docs[0] ?? null };
514
+ }
515
+ catch {
516
+ apiCalls++;
517
+ return { seed, doc: null };
518
+ }
519
+ }));
520
+ for (const { seed, doc } of results) {
521
+ if (!doc)
522
+ continue;
523
+ // 売上フィルタ
524
+ if (minRevenue && !revenueMatchesMin(doc.revenue, minRevenue))
525
+ continue;
526
+ // 業種フィルタ
527
+ if (industry) {
528
+ const docIndustry = f(doc.gyoshu_facet);
529
+ const keywords = industry.split(",").map((k) => k.trim());
530
+ if (!keywords.some((k) => docIndustry.includes(k)))
531
+ continue;
532
+ }
533
+ matched.push({ doc, seed });
534
+ }
535
+ }
536
+ // Step 3: 結果フォーマット
537
+ if (matched.length === 0) {
538
+ return {
539
+ content: [{
540
+ type: "text",
541
+ text: `条件に合う企業が見つかりませんでした。\n(シード候補: ${seeds.length}社 / API照会: ${apiCalls}回 / シード全体: ${seedTotal}社)${usageFooter()}`,
542
+ }],
543
+ };
544
+ }
545
+ const header = `## プロスペクトリスト(${matched.length}社)\n\n`;
546
+ const table = `| # | 会社名 | 業種 | 売上 | 従業員 | 所在地 | HP |\n|---|--------|------|------|--------|--------|----|\n` +
547
+ matched
548
+ .map((m, i) => {
549
+ const d = m.doc;
550
+ return `| ${i + 1} | ${f(d.shogo)} | ${f(d.gyoshu_facet)} | ${d.revenue ?? "-"} | ${d.emp ?? "-"} | ${f(d.add)} | ${f(d.hpurl) || "-"} |`;
551
+ })
552
+ .join("\n");
553
+ const stats = `\n\n---\nシード候補: ${seeds.length}社(全${seedTotal}社中) / API照会: ${apiCalls}回 / マッチ: ${matched.length}社`;
554
+ return {
555
+ content: [{ type: "text", text: header + table + stats + usageFooter() }],
556
+ };
557
+ });
427
558
  // ---------- 起動 ----------
428
559
  async function main() {
429
560
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bizgate-mcp-server",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "BizGate APIとClaudeを連携するMCPサーバー",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",