bizgate-mcp-server 0.3.6 → 0.3.7
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.
- package/dist/index.js +144 -55
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -37,7 +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.
|
|
40
|
+
const seedApiUrl = process.env.SEED_API_URL ?? "http://192.168.40.94:3456";
|
|
41
41
|
const usageFile = process.env.BIZGATE_USAGE_FILE ??
|
|
42
42
|
join(homedir(), ".bizgate-mcp-usage.json");
|
|
43
43
|
const baseUrl = process.env.BIZGATE_BASE_URL ??
|
|
@@ -422,16 +422,6 @@ server.tool("bizgate__usage_status", "本日のBizGate API残り利用回数を
|
|
|
422
422
|
};
|
|
423
423
|
});
|
|
424
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
425
|
const MAX_API_CALLS_HARD_LIMIT = 150;
|
|
436
426
|
async function fetchSeeds(params) {
|
|
437
427
|
const url = new URL("/search", seedApiUrl);
|
|
@@ -442,45 +432,67 @@ async function fetchSeeds(params) {
|
|
|
442
432
|
throw new Error(`seed-api エラー: HTTP ${res.status}`);
|
|
443
433
|
return (await res.json());
|
|
444
434
|
}
|
|
445
|
-
|
|
435
|
+
/** 売上カテゴリ文字列から数値を抽出して最低ラインと比較 */
|
|
436
|
+
function revenueMatchesMin(revenue, minLabel) {
|
|
446
437
|
if (!revenue)
|
|
447
438
|
return false;
|
|
439
|
+
// revenue例: "7. 20億-100億未満", minLabel例: "50億以上"
|
|
440
|
+
const thresholds = {
|
|
441
|
+
"50億以上": 7, // カテゴリ7(20億-100億) 以上
|
|
442
|
+
"300億以上": 8, // カテゴリ8(100億以上)
|
|
443
|
+
"1000億以上": 8, // カテゴリ8(100億以上)
|
|
444
|
+
};
|
|
445
|
+
const minCat = thresholds[minLabel];
|
|
446
|
+
if (!minCat)
|
|
447
|
+
return true; // 不明なラベルはフィルタしない
|
|
448
448
|
const match = revenue.match(/^(\d+)\./);
|
|
449
449
|
if (!match)
|
|
450
450
|
return false;
|
|
451
|
-
return Number(match[1]) >=
|
|
451
|
+
return Number(match[1]) >= minCat;
|
|
452
452
|
}
|
|
453
|
-
|
|
453
|
+
/** proposal_axis からアプローチキーワードを生成 */
|
|
454
|
+
function axisToKeywords(axis) {
|
|
455
|
+
const axes = Array.isArray(axis) ? axis : [axis];
|
|
456
|
+
const keywordMap = {
|
|
457
|
+
"DOMO": "経営企画,DX,システム,情報,データ,分析",
|
|
458
|
+
"営業DX/CRM": "営業,マーケティング,販売,顧客,CRM,販促",
|
|
459
|
+
"営業DX": "営業,マーケティング,販売,顧客,CRM,販促",
|
|
460
|
+
"CRM": "営業,マーケティング,顧客,CRM",
|
|
461
|
+
};
|
|
462
|
+
const keywords = axes.flatMap((a) => (keywordMap[a] ?? a).split(","));
|
|
463
|
+
return [...new Set(keywords)].join(",");
|
|
464
|
+
}
|
|
465
|
+
server.tool("bizgate__prospect_list", "シードリスト(国税庁572万法人)から条件に合う企業を抽出し、BizGate APIで部署・キーマンまで照会してプロスペクトリストを作成する。1回の実行で約40 APIコールを消費(1日約5回実行可能)。SEED_API_URLが必要。", {
|
|
454
466
|
pref: z.string().optional().describe("都道府県(例: 東京都)"),
|
|
455
|
-
|
|
456
|
-
industry: z.string().optional().describe("
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
467
|
+
city: z.string().optional().describe("市区町村名(例: 千代田区)"),
|
|
468
|
+
industry: z.string().optional().describe("BizGate業種分類キーワード(例: 製造業, サービス, IT)"),
|
|
469
|
+
revenue_min: z.string().optional().describe("最低売上(50億以上 / 300億以上 / 1000億以上)"),
|
|
470
|
+
employee_500plus: z.boolean().optional().describe("従業員500人以上のみ"),
|
|
471
|
+
proposal_axis: z.union([z.string(), z.array(z.string())]).optional().describe("提案軸(DOMO / 営業DX/CRM)。部署検索のキーワードに変換される"),
|
|
472
|
+
count: z.number().optional().describe("取得したい企業数(デフォルト: 10、最大: 30)"),
|
|
473
|
+
}, async ({ pref, city, industry, revenue_min, employee_500plus, proposal_axis, count: rawCount }) => {
|
|
461
474
|
if (!seedApiUrl) {
|
|
462
475
|
return {
|
|
463
|
-
content: [{ type: "text", text: "エラー:
|
|
476
|
+
content: [{ type: "text", text: "エラー: SEED_API_URL が設定されていません。管理者に連絡してください。" }],
|
|
464
477
|
};
|
|
465
478
|
}
|
|
466
|
-
const
|
|
467
|
-
const apiLimit = Math.min(rawMax ?? 50, MAX_API_CALLS_HARD_LIMIT);
|
|
479
|
+
const count = Math.min(rawCount ?? 10, 30);
|
|
468
480
|
const remaining = usageTracker.remaining();
|
|
469
|
-
|
|
470
|
-
if (effectiveLimit < 5) {
|
|
481
|
+
if (remaining < 10) {
|
|
471
482
|
return {
|
|
472
|
-
content: [{ type: "text", text: `エラー: 残りAPI回数が${remaining}
|
|
483
|
+
content: [{ type: "text", text: `エラー: 残りAPI回数が${remaining}回です。明日以降にお試しください。${usageFooter()}` }],
|
|
473
484
|
};
|
|
474
485
|
}
|
|
475
|
-
// Step 1: シードAPIから候補取得
|
|
486
|
+
// ---- Step 1: シードAPIから候補取得 (API 0回) ----
|
|
476
487
|
const seedParams = {
|
|
477
|
-
limit:
|
|
488
|
+
limit: "200",
|
|
478
489
|
shuffle: "true",
|
|
490
|
+
type: "株式会社",
|
|
479
491
|
};
|
|
480
492
|
if (pref)
|
|
481
493
|
seedParams.pref = pref;
|
|
482
|
-
if (
|
|
483
|
-
seedParams.
|
|
494
|
+
if (city)
|
|
495
|
+
seedParams.city = city;
|
|
484
496
|
let seeds;
|
|
485
497
|
let seedTotal;
|
|
486
498
|
try {
|
|
@@ -490,7 +502,7 @@ server.tool("bizgate__prospect_list", "シードリスト(国税庁法人デ
|
|
|
490
502
|
}
|
|
491
503
|
catch (e) {
|
|
492
504
|
return {
|
|
493
|
-
content: [{ type: "text", text:
|
|
505
|
+
content: [{ type: "text", text: `seed-api に接続できません(${seedApiUrl}): ${e instanceof Error ? e.message : "不明なエラー"}` }],
|
|
494
506
|
};
|
|
495
507
|
}
|
|
496
508
|
if (seeds.length === 0) {
|
|
@@ -498,17 +510,16 @@ server.tool("bizgate__prospect_list", "シードリスト(国税庁法人デ
|
|
|
498
510
|
content: [{ type: "text", text: `条件に合う企業がシードリストに見つかりませんでした。${usageFooter()}` }],
|
|
499
511
|
};
|
|
500
512
|
}
|
|
501
|
-
// Step 2: BizGate
|
|
502
|
-
const
|
|
513
|
+
// ---- Step 2: BizGate企業検索 → 売上/業種/従業員フィルタ ----
|
|
514
|
+
const verified = [];
|
|
503
515
|
let apiCalls = 0;
|
|
504
516
|
const batchSize = 5;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
const
|
|
509
|
-
const results = await Promise.all(actualBatch.map(async (seed) => {
|
|
517
|
+
const targetVerified = Math.ceil(count * 1.5); // 部署/キーマンで脱落分を見越す
|
|
518
|
+
for (let i = 0; i < seeds.length && verified.length < targetVerified && apiCalls < MAX_API_CALLS_HARD_LIMIT; i += batchSize) {
|
|
519
|
+
const batch = seeds.slice(i, i + batchSize);
|
|
520
|
+
const results = await Promise.all(batch.map(async (seed) => {
|
|
510
521
|
try {
|
|
511
|
-
const { docs } = await client.searchCompany({ compno: seed.
|
|
522
|
+
const { docs } = await client.searchCompany({ compno: seed.corporate_number });
|
|
512
523
|
apiCalls++;
|
|
513
524
|
return { seed, doc: docs[0] ?? null };
|
|
514
525
|
}
|
|
@@ -520,37 +531,115 @@ server.tool("bizgate__prospect_list", "シードリスト(国税庁法人デ
|
|
|
520
531
|
for (const { seed, doc } of results) {
|
|
521
532
|
if (!doc)
|
|
522
533
|
continue;
|
|
523
|
-
|
|
524
|
-
if (minRevenue && !revenueMatchesMin(doc.revenue, minRevenue))
|
|
534
|
+
if (revenue_min && !revenueMatchesMin(doc.revenue, revenue_min))
|
|
525
535
|
continue;
|
|
526
|
-
// 業種フィルタ
|
|
527
536
|
if (industry) {
|
|
528
537
|
const docIndustry = f(doc.gyoshu_facet);
|
|
529
|
-
|
|
530
|
-
if (!keywords.some((k) => docIndustry.includes(k)))
|
|
538
|
+
if (!industry.split(",").some((k) => docIndustry.includes(k.trim())))
|
|
531
539
|
continue;
|
|
532
540
|
}
|
|
533
|
-
|
|
541
|
+
if (employee_500plus && doc.emp) {
|
|
542
|
+
const empMatch = doc.emp.match(/(\d+)/);
|
|
543
|
+
if (empMatch) {
|
|
544
|
+
// emp例: "6. 500-1000人未満" → カテゴリ6以上
|
|
545
|
+
const empCat = Number(doc.emp.match(/^(\d+)\./)?.[1] ?? 0);
|
|
546
|
+
if (empCat < 6)
|
|
547
|
+
continue; // カテゴリ6 = 500-1000人
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
verified.push({ doc, seed });
|
|
534
551
|
}
|
|
535
552
|
}
|
|
536
|
-
|
|
537
|
-
if (matched.length === 0) {
|
|
553
|
+
if (verified.length === 0) {
|
|
538
554
|
return {
|
|
539
555
|
content: [{
|
|
540
556
|
type: "text",
|
|
541
|
-
text: `条件に合う企業が見つかりませんでした。\n(シード候補: ${seeds.length}社 / API照会: ${apiCalls}
|
|
557
|
+
text: `条件に合う企業が見つかりませんでした。\n(シード候補: ${seeds.length}社 / API照会: ${apiCalls}回)${usageFooter()}`,
|
|
542
558
|
}],
|
|
543
559
|
};
|
|
544
560
|
}
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
561
|
+
// ---- Step 3: 部署 + キーマン検索 ----
|
|
562
|
+
const deptKeywords = proposal_axis ? axisToKeywords(proposal_axis) : "";
|
|
563
|
+
const prospects = [];
|
|
564
|
+
for (const { doc, seed } of verified.slice(0, count)) {
|
|
565
|
+
const companyName = f(doc.shogo);
|
|
566
|
+
let department = "-";
|
|
567
|
+
let phone = f(doc.tel) || "-";
|
|
568
|
+
let keyman;
|
|
569
|
+
// 部署検索
|
|
570
|
+
if (deptKeywords) {
|
|
571
|
+
try {
|
|
572
|
+
const { docs: deptDocs } = await client.searchDepartments({
|
|
573
|
+
shogo: companyName, bKwd: deptKeywords, bKOpr: "0",
|
|
574
|
+
});
|
|
575
|
+
apiCalls++;
|
|
576
|
+
if (deptDocs.length > 0) {
|
|
577
|
+
department = f(deptDocs[0].bumon) || "-";
|
|
578
|
+
if (f(deptDocs[0].tel))
|
|
579
|
+
phone = f(deptDocs[0].tel);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
apiCalls++;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
// キーマン検索
|
|
587
|
+
if (skeyKeyman && skeyKeymanName) {
|
|
588
|
+
try {
|
|
589
|
+
const { docs: keyDocs } = await client.searchKeymanWithNames({
|
|
590
|
+
shogo: companyName, bKwd: deptKeywords || undefined, limit: 1,
|
|
591
|
+
});
|
|
592
|
+
apiCalls += 2; // 一覧1回 + 詳細1回
|
|
593
|
+
if (keyDocs.length > 0) {
|
|
594
|
+
keyman = {
|
|
595
|
+
name: f(keyDocs[0].ceo) || "(不明)",
|
|
596
|
+
title: f(keyDocs[0].bumon) || "(不明)",
|
|
597
|
+
};
|
|
598
|
+
if (f(keyDocs[0].tel))
|
|
599
|
+
phone = f(keyDocs[0].tel);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
apiCalls += 1;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
// アプローチノート生成
|
|
607
|
+
const axes = proposal_axis
|
|
608
|
+
? (Array.isArray(proposal_axis) ? proposal_axis : [proposal_axis])
|
|
609
|
+
: [];
|
|
610
|
+
const axisLabel = axes.join("・") || "汎用";
|
|
611
|
+
const approachNote = `${axisLabel}の導入提案。${f(doc.gyoshu_facet) || ""}${doc.emp ? `、${doc.emp}` : ""}。`;
|
|
612
|
+
prospects.push({
|
|
613
|
+
name: companyName,
|
|
614
|
+
prefecture: seed.prefecture,
|
|
615
|
+
city: seed.city,
|
|
616
|
+
corporate_number: seed.corporate_number,
|
|
617
|
+
industry: f(doc.gyoshu_facet) || "-",
|
|
618
|
+
revenue: doc.revenue ?? "-",
|
|
619
|
+
department,
|
|
620
|
+
keyman,
|
|
621
|
+
phone,
|
|
622
|
+
approach_note: approachNote,
|
|
623
|
+
});
|
|
624
|
+
if (apiCalls >= MAX_API_CALLS_HARD_LIMIT)
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
// ---- Step 4: 結果フォーマット ----
|
|
628
|
+
// キーマンあり > 部署あり > 会社のみ でソート
|
|
629
|
+
prospects.sort((a, b) => {
|
|
630
|
+
const scoreA = (a.keyman ? 2 : 0) + (a.department !== "-" ? 1 : 0);
|
|
631
|
+
const scoreB = (b.keyman ? 2 : 0) + (b.department !== "-" ? 1 : 0);
|
|
632
|
+
return scoreB - scoreA;
|
|
633
|
+
});
|
|
634
|
+
const header = `## プロスペクトリスト(${prospects.length}社)\n\n`;
|
|
635
|
+
const table = `| # | 会社名 | 部署 | キーマン | 電話 | アプローチ角度 |\n|---|--------|------|---------|------|---------------|\n` +
|
|
636
|
+
prospects
|
|
637
|
+
.map((p, i) => {
|
|
638
|
+
const keymanStr = p.keyman ? `${p.keyman.name}(${p.keyman.title})` : "-";
|
|
639
|
+
return `| ${i + 1} | ${p.name} | ${p.department} | ${keymanStr} | ${p.phone} | ${p.approach_note} |`;
|
|
551
640
|
})
|
|
552
641
|
.join("\n");
|
|
553
|
-
const stats = `\n\n---\n
|
|
642
|
+
const stats = `\n\n---\nシード: ${seedTotal}社中${seeds.length}社取得 / API照会: ${apiCalls}回 / マッチ: ${prospects.length}社`;
|
|
554
643
|
return {
|
|
555
644
|
content: [{ type: "text", text: header + table + stats + usageFooter() }],
|
|
556
645
|
};
|