bizgate-mcp-server 0.3.0 → 0.3.3

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/README.md CHANGED
@@ -33,7 +33,7 @@ Claude に話しかけるだけで、企業の担当者名・電話番号・住
33
33
 
34
34
  ---
35
35
 
36
- ## セットアップ(所要時間:約10分)
36
+ ## セットアップ(所要時間:約3分)
37
37
 
38
38
  ### 事前に必要なもの
39
39
 
@@ -54,90 +54,43 @@ Mac の場合:
54
54
 
55
55
  ---
56
56
 
57
- ### ステップ 2:MCP サーバーを登録する
57
+ ### ステップ 2:インストールスクリプトを実行する
58
58
 
59
59
  ターミナルに以下を **そのままコピーして貼り付け** → Enter を押してください。
60
60
 
61
- ```
62
- claude mcp add --scope user bizgate -- npx bizgate-mcp-server
63
- ```
64
-
65
- 以下のように表示されれば OK です。
66
- ```
67
- Added stdio MCP server bizgate...
68
- ```
69
-
70
- ---
71
-
72
- ### ステップ 3:設定ファイルを編集する
73
-
74
- ターミナルに以下を **そのままコピーして貼り付け** → Enter を押してください。
75
-
76
- ```
77
- open ~/.claude.json
78
- ```
79
-
80
- テキストエディタで設定ファイルが開きます。
81
-
82
- ファイルの中から `"bizgate"` というセクションを探してください。
83
- 以下のように **書き換え** ます。
84
-
85
- #### 書き換え前
86
-
87
- ```json
88
- "bizgate": {
89
- "type": "stdio",
90
- "command": "npx",
91
- "args": ["bizgate-mcp-server"],
92
- "env": {}
93
- }
94
- ```
95
-
96
- #### 書き換え後
97
-
98
- ```json
99
- "bizgate": {
100
- "type": "stdio",
101
- "command": "npx",
102
- "args": ["bizgate-mcp-server"],
103
- "env": {
104
- "BIZGATE_USERNAME": "ここにユーザー名を入力",
105
- "BIZGATE_PASSWORD": "ここにパスワードを入力",
106
- "BIZGATE_AUTH_MODE": "basic",
107
- "BIZGATE_DAILY_LIMIT": "200",
108
- "BIZGATE_SKEY_COMPANY": "ここに企業APIキーを入力",
109
- "BIZGATE_SKEY_DEPARTMENT": "ここに部署APIキーを入力",
110
- "BIZGATE_SKEY_MARKETING": "ここにマーケAPIキーを入力",
111
- "BIZGATE_SKEY_KEYMAN": "ここにキーマン(人名なし)APIキーを入力",
112
- "BIZGATE_SKEY_KEYMAN_NAME": "ここにキーマン(人名あり)APIキーを入力"
113
- }
114
- }
115
- ```
116
-
117
- > **注意(よくあるミス)**
118
- > - `ここに〇〇を入力` の部分を、管理者から受け取った **実際の値** に置き換えてください
119
- > - ダブルクォーテーション `"` は **消さないで** ください
120
- > - カンマ `,` の位置を **変えないで** ください
121
- > - 最後の行(`"BIZGATE_SKEY_KEYMAN_NAME"` の行)の末尾には **カンマをつけないで** ください
122
- > - 編集後、`Command + S` で保存 → ファイルを閉じる
61
+ ```smalltalk
62
+ claude mcp add bizgate --scope user \
63
+ -e "BIZGATE_USERNAME=digi-man_bizg1" \
64
+ -e "BIZGATE_PASSWORD=digi-man_bizg1" \
65
+ -e "BIZGATE_AUTH_MODE=basic" \
66
+ -e "BIZGATE_DAILY_LIMIT=200" \
67
+ -e "BIZGATE_SKEY_COMPANY=/EhMJ9YMCJtJgo73.DjLuew8rnnTlb.F6/MuiESFXmZwlCKvG8bMm" \
68
+ -e "BIZGATE_SKEY_DEPARTMENT=i08d/E8OwOtb2L0cdzbh1.Y5LvlTwpvZ49o3rBFVOnzRL7lDvR4Om" \
69
+ -e "BIZGATE_SKEY_MARKETING=X96mHxuTt4RExyo94/eGIOJwsFKr6aVlYQ80UozjTZaUVf9t/1CDu" \
70
+ -e "BIZGATE_SKEY_KEYMAN=3Rc3.RMt53yzVJ5zv28bjOIZVx8KjfIfo2PiCryGtF81feOv3hPZ." \
71
+ -e "BIZGATE_SKEY_KEYMAN_NAME=qT42YqynPg0uhPoGvquwbOUAYCotYSzxHhwiCHG5gZbTFMk75waAG" \
72
+ -- npx bizgate-mcp-server
73
+ ```
74
+
75
+ - サービスキーを誤って入力した場合
76
+
77
+ ```bash
78
+ claude mcp remove bizgate -s user
79
+ ```
80
+
81
+ その後、再度入力してください。
123
82
 
124
83
  ---
125
84
 
126
- ### ステップ 4:Claude Code を再起動する
127
-
128
- もし Claude Code が開いている場合は、一度閉じてください。
85
+ ### ステップ 3:Claude Code で接続を確認する
129
86
 
130
- ターミナルで以下を実行してください。
87
+ Claude Code を起動(またはすでに開いていれば再起動)してください。
131
88
 
132
89
  ```
133
90
  claude
134
91
  ```
135
92
 
136
- ---
137
-
138
- ### ステップ 5:接続を確認する
139
-
140
- Claude Code が起動したら、以下を入力してください。
93
+ 起動したら、以下を入力してください。
141
94
 
142
95
  ```
143
96
  /mcp
@@ -165,6 +118,33 @@ Claude Code が起動したら、以下を入力してください。
165
118
  | 採用活動をしている会社か確認 | 「○○のマーケティングタグを教えて」 |
166
119
  | 全情報をまとめて見たい | 「○○の全情報を調べて」 |
167
120
  | Excelに入力したい | 「○○の情報を調べてExcelに入れて」 |
121
+ | DigiManのサービスに合う部署を探したい | 「○○にDigiManのサービスが売れそうな部署を探して」 |
122
+
123
+ ### prospect-match スキル(部署マッチング)
124
+
125
+ 企業の部署一覧から、DigiManの各サービス(営業代行・Solution・AI)に適した営業ターゲット部署を自動で提案するスキルです。
126
+
127
+ ```
128
+ /prospect-match 兼松株式会社
129
+ ```
130
+
131
+ または自然言語でもOK:
132
+ ```
133
+ 「兼松株式会社にDigiManのサービスが売れそうな部署を探して」
134
+ ```
135
+
136
+ **出力内容:**
137
+ - サービスごとの推薦部署(最大5件ずつ)+ 電話番号
138
+ - マッチ理由
139
+ - 最優先アプローチ先
140
+
141
+ **すでにBizGate MCPをインストール済みの方** は、以下でスキルだけ追加できます:
142
+
143
+ ```bash
144
+ bash install-skill.sh
145
+ ```
146
+
147
+ 新規インストールの場合は `install.sh` にスキルも含まれています。
168
148
 
169
149
  ### 注意点
170
150
 
@@ -176,6 +156,21 @@ Claude Code が起動したら、以下を入力してください。
176
156
 
177
157
  ---
178
158
 
159
+ ## パフォーマンス最適化
160
+
161
+ 本サーバーには以下の最適化が組み込まれています。
162
+
163
+ ### APIコール節約
164
+ - **5分キャッシュ**: 同じ会社を5分以内に再検索した場合、APIを消費しません
165
+ - **キーマン詳細の並列取得**: 5件ずつ並列リクエストで高速化
166
+
167
+ ### LLMトークン節約
168
+ - **部署検索**: デフォルト30件のみ表示(`limit`で変更可能)。残りは絞り込みで取得
169
+ - **一括取得**: 部署は上位15件、人事は役職一覧のみ(人名が必要な場合は個別ツールで)
170
+ - 超過分は「残りN件あります」と案内し、必要に応じて追加取得
171
+
172
+ ---
173
+
179
174
  ## 提供ツール(参考情報)
180
175
 
181
176
  通常はツール名を意識する必要はありません。Claude が自動で適切なツールを選びます。
@@ -183,10 +178,10 @@ Claude Code が起動したら、以下を入力してください。
183
178
  | ツール名 | 説明 | API消費 |
184
179
  |---------|------|--------|
185
180
  | `bizgate__company_search` | 企業の基本情報(住所・電話・代表者・業種・資本金 等) | 1回(結果なしでも課金) |
186
- | `bizgate__department_search` | 部署情報(部署名・住所・電話番号)最大500件 | 1回(データなしは課金なし) |
181
+ | `bizgate__department_search` | 部署情報(部署名・住所・電話番号)デフォルト30件表示 | 1回(データなしは課金なし) |
187
182
  | `bizgate__marketing_tags` | マーケティングタグ(SNS・MA・採用・活動) | 1回(データなしは課金なし) |
188
- | `bizgate__keyman_search` | 担当者情報(実名・役職・電話番号・住所) | 1回 + 詳細取得N回 |
189
- | `bizgate__company_full` | 企業+部署+キーマンを一括取得 | 3回〜 |
183
+ | `bizgate__keyman_search` | 担当者情報(実名・役職・電話番号・住所)デフォルト10名 | 1回 + 詳細N回 |
184
+ | `bizgate__company_full` | 企業+部署(15件)+キーマン(要約)を一括取得 | 3 |
190
185
  | `bizgate__usage_status` | 本日の残りAPI回数を確認 | 0回 |
191
186
 
192
187
  ---
@@ -32,7 +32,7 @@ export declare class BizGateClient {
32
32
  private cacheKey;
33
33
  /**
34
34
  * Company lookup (企業).
35
- * Cached for 60s. Billed even on no-result.
35
+ * Cached for 5 minutes. Billed even on no-result.
36
36
  */
37
37
  searchCompany(params: {
38
38
  shogo?: string;
@@ -43,7 +43,7 @@ export declare class BizGateClient {
43
43
  }): Promise<CompanyResult>;
44
44
  /**
45
45
  * Department lookup (部署).
46
- * Cached for 60s. Not billed on failure.
46
+ * Cached for 5 minutes. Not billed on failure.
47
47
  */
48
48
  searchDepartments(params: {
49
49
  shogo?: string;
@@ -55,7 +55,7 @@ export declare class BizGateClient {
55
55
  }): Promise<DepartmentResult>;
56
56
  /**
57
57
  * Marketing tags lookup (マーケティングタグ).
58
- * Cached for 60s. Not billed on failure.
58
+ * Cached for 5 minutes. Not billed on failure.
59
59
  */
60
60
  searchMarketingTags(params: {
61
61
  shogo?: string;
@@ -63,7 +63,7 @@ export declare class BizGateClient {
63
63
  }): Promise<MarketingResult>;
64
64
  /**
65
65
  * Keyman lookup (キーマン・人名なし).
66
- * Cached for 60s. Not billed on failure.
66
+ * Cached for 5 minutes. Not billed on failure.
67
67
  */
68
68
  searchKeyman(params: {
69
69
  shogo?: string;
@@ -6,8 +6,14 @@ export function first(field) {
6
6
  return field[0] ?? "";
7
7
  return field ?? "";
8
8
  }
9
- // Cache TTL: 60 seconds — same company searched within 60s won't hit the API again
10
- const CACHE_TTL = 60_000;
9
+ // Cache TTL: 5 minutes — same company searched within 5min won't hit the API again
10
+ const CACHE_TTL = 300_000;
11
+ /** BizGate error codes that do NOT consume a billable request. */
12
+ const NON_BILLABLE_ERRORS = new Set(["404", "504", "604"]);
13
+ /** Pick only defined (non-null, non-undefined) string values from an object. */
14
+ function pickDefined(obj) {
15
+ return Object.fromEntries(Object.entries(obj).filter((entry) => entry[1] != null));
16
+ }
11
17
  export class BizGateClient {
12
18
  config;
13
19
  usageTracker;
@@ -62,7 +68,7 @@ export class BizGateClient {
62
68
  }
63
69
  /**
64
70
  * Company lookup (企業).
65
- * Cached for 60s. Billed even on no-result.
71
+ * Cached for 5 minutes. Billed even on no-result.
66
72
  */
67
73
  async searchCompany(params) {
68
74
  const key = this.cacheKey(params);
@@ -70,17 +76,7 @@ export class BizGateClient {
70
76
  if (cached)
71
77
  return cached;
72
78
  this.usageTracker.increment();
73
- const queryParams = {};
74
- if (params.shogo)
75
- queryParams.shogo = params.shogo;
76
- if (params.compno)
77
- queryParams.compno = params.compno;
78
- if (params.hpurl)
79
- queryParams.hpurl = params.hpurl;
80
- if (params.email)
81
- queryParams.email = params.email;
82
- if (params.tel)
83
- queryParams.tel = params.tel;
79
+ const queryParams = pickDefined(params);
84
80
  const result = await this.request(this.config.skeyCompany, queryParams);
85
81
  const data = {
86
82
  matchPattern: result.responseHeader.matchpatern ?? "",
@@ -91,7 +87,7 @@ export class BizGateClient {
91
87
  }
92
88
  /**
93
89
  * Department lookup (部署).
94
- * Cached for 60s. Not billed on failure.
90
+ * Cached for 5 minutes. Not billed on failure.
95
91
  */
96
92
  async searchDepartments(params) {
97
93
  const key = this.cacheKey(params);
@@ -99,30 +95,26 @@ export class BizGateClient {
99
95
  if (cached)
100
96
  return cached;
101
97
  this.usageTracker.increment();
102
- const queryParams = {};
103
- if (params.shogo)
104
- queryParams.shogo = params.shogo;
105
- if (params.compno)
106
- queryParams.compno = params.compno;
107
- if (params.pList)
108
- queryParams.pList = params.pList;
109
- if (params.cList)
110
- queryParams.cList = params.cList;
111
- if (params.bKwd)
112
- queryParams.bKwd = params.bKwd;
113
- if (params.bKOpr)
114
- queryParams.bKOpr = params.bKOpr;
115
- const result = await this.request(this.config.skeyDepartment, queryParams);
116
- const data = {
117
- docs: result.response?.docs ?? [],
118
- numFound: result.response?.numFound ?? 0,
119
- };
120
- this.departmentCache.set(key, data);
121
- return data;
98
+ try {
99
+ const queryParams = pickDefined(params);
100
+ const result = await this.request(this.config.skeyDepartment, queryParams);
101
+ const data = {
102
+ docs: result.response?.docs ?? [],
103
+ numFound: result.response?.numFound ?? 0,
104
+ };
105
+ this.departmentCache.set(key, data);
106
+ return data;
107
+ }
108
+ catch (e) {
109
+ if (e instanceof BizGateApiError && NON_BILLABLE_ERRORS.has(e.code)) {
110
+ this.usageTracker.decrement();
111
+ }
112
+ throw e;
113
+ }
122
114
  }
123
115
  /**
124
116
  * Marketing tags lookup (マーケティングタグ).
125
- * Cached for 60s. Not billed on failure.
117
+ * Cached for 5 minutes. Not billed on failure.
126
118
  */
127
119
  async searchMarketingTags(params) {
128
120
  const key = this.cacheKey(params);
@@ -130,21 +122,25 @@ export class BizGateClient {
130
122
  if (cached)
131
123
  return cached;
132
124
  this.usageTracker.increment();
133
- const queryParams = {};
134
- if (params.shogo)
135
- queryParams.shogo = params.shogo;
136
- if (params.compno)
137
- queryParams.compno = params.compno;
138
- const result = await this.request(this.config.skeyMarketing, queryParams);
139
- const data = {
140
- docs: result.response?.docs ?? [],
141
- };
142
- this.marketingCache.set(key, data);
143
- return data;
125
+ try {
126
+ const queryParams = pickDefined(params);
127
+ const result = await this.request(this.config.skeyMarketing, queryParams);
128
+ const data = {
129
+ docs: result.response?.docs ?? [],
130
+ };
131
+ this.marketingCache.set(key, data);
132
+ return data;
133
+ }
134
+ catch (e) {
135
+ if (e instanceof BizGateApiError && NON_BILLABLE_ERRORS.has(e.code)) {
136
+ this.usageTracker.decrement();
137
+ }
138
+ throw e;
139
+ }
144
140
  }
145
141
  /**
146
142
  * Keyman lookup (キーマン・人名なし).
147
- * Cached for 60s. Not billed on failure.
143
+ * Cached for 5 minutes. Not billed on failure.
148
144
  */
149
145
  async searchKeyman(params) {
150
146
  const key = this.cacheKey(params);
@@ -152,26 +148,22 @@ export class BizGateClient {
152
148
  if (cached)
153
149
  return cached;
154
150
  this.usageTracker.increment();
155
- const queryParams = {};
156
- if (params.shogo)
157
- queryParams.shogo = params.shogo;
158
- if (params.compno)
159
- queryParams.compno = params.compno;
160
- if (params.pList)
161
- queryParams.pList = params.pList;
162
- if (params.cList)
163
- queryParams.cList = params.cList;
164
- if (params.bKwd)
165
- queryParams.bKwd = params.bKwd;
166
- if (params.bKOpr)
167
- queryParams.bKOpr = params.bKOpr;
168
- const result = await this.request(this.config.skeyKeyman, queryParams);
169
- const data = {
170
- docs: result.response?.docs ?? [],
171
- numFound: result.response?.numFound ?? 0,
172
- };
173
- this.keymanCache.set(key, data);
174
- return data;
151
+ try {
152
+ const queryParams = pickDefined(params);
153
+ const result = await this.request(this.config.skeyKeyman, queryParams);
154
+ const data = {
155
+ docs: result.response?.docs ?? [],
156
+ numFound: result.response?.numFound ?? 0,
157
+ };
158
+ this.keymanCache.set(key, data);
159
+ return data;
160
+ }
161
+ catch (e) {
162
+ if (e instanceof BizGateApiError && NON_BILLABLE_ERRORS.has(e.code)) {
163
+ this.usageTracker.decrement();
164
+ }
165
+ throw e;
166
+ }
175
167
  }
176
168
  /**
177
169
  * Keyman with name lookup (キーマン・人名あり).
@@ -186,7 +178,10 @@ export class BizGateClient {
186
178
  });
187
179
  return result.response?.docs?.[0] ?? null;
188
180
  }
189
- catch {
181
+ catch (e) {
182
+ if (e instanceof BizGateApiError && NON_BILLABLE_ERRORS.has(e.code)) {
183
+ this.usageTracker.decrement();
184
+ }
190
185
  return null;
191
186
  }
192
187
  };
package/dist/index.js CHANGED
@@ -6,7 +6,9 @@ import { BizGateClient, first as f } from "./bizgate-client.js";
6
6
  import { BizGateApiError, DEPARTMENT_CATEGORIES } from "./types.js";
7
7
  import { UsageTracker } from "./usage-tracker.js";
8
8
  import { homedir } from "node:os";
9
- import { join } from "node:path";
9
+ import { join, dirname } from "node:path";
10
+ import { readFileSync } from "node:fs";
11
+ import { fileURLToPath } from "node:url";
10
12
  // ---------- 環境変数読み込み ----------
11
13
  const skeyCompany = process.env.BIZGATE_SKEY_COMPANY;
12
14
  const skeyDepartment = process.env.BIZGATE_SKEY_DEPARTMENT;
@@ -50,9 +52,11 @@ const config = {
50
52
  const usageTracker = new UsageTracker(usageFile, dailyLimit);
51
53
  const client = new BizGateClient(config, usageTracker);
52
54
  // ---------- MCPサーバー ----------
55
+ const __dirname = dirname(fileURLToPath(import.meta.url));
56
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
53
57
  const server = new McpServer({
54
58
  name: "bizgate-mcp-server",
55
- version: "0.3.0",
59
+ version: pkg.version,
56
60
  });
57
61
  // ---------- ヘルパー ----------
58
62
  function usageFooter() {
@@ -345,73 +349,54 @@ server.tool("bizgate__company_full", "会社名または法人番号で企業情
345
349
  const err = validateInput(shogo, compno);
346
350
  if (err)
347
351
  return { content: [{ type: "text", text: err }] };
348
- const sections = [];
349
- // 1. Company info
350
- try {
351
- const { matchPattern, docs } = await client.searchCompany({
352
- shogo, compno, hpurl, email,
353
- });
352
+ // Parallel fetch: company + department + keyman simultaneously
353
+ const companyPromise = client.searchCompany({ shogo, compno, hpurl, email })
354
+ .then(({ matchPattern, docs }) => {
354
355
  if (docs.length > 0) {
355
- sections.push(docs.map(formatCompanyDoc).join("\n\n"));
356
- sections.push(`マッチパターン: ${matchPattern}`);
356
+ return docs.map(formatCompanyDoc).join("\n\n") + `\nマッチパターン: ${matchPattern}`;
357
357
  }
358
- else {
359
- sections.push(`企業情報: 該当なし(マッチパターン: ${matchPattern})`);
360
- }
361
- }
362
- catch (e) {
363
- sections.push(`企業情報: ${e instanceof Error ? e.message : "取得失敗"}`);
364
- }
365
- // 2. Department info (top 15 for token efficiency)
366
- try {
367
- const { docs, numFound } = await client.searchDepartments({
368
- shogo, compno, bKwd, cList,
369
- });
358
+ return `企業情報: 該当なし(マッチパターン: ${matchPattern})`;
359
+ })
360
+ .catch((e) => `企業情報: ${e instanceof Error ? e.message : "取得失敗"}`);
361
+ const deptPromise = client.searchDepartments({ shogo, compno, bKwd, cList })
362
+ .then(({ docs, numFound }) => {
370
363
  if (docs.length > 0) {
371
364
  const displayed = docs.slice(0, 15);
372
365
  const companyName = f(docs[0].shogo);
373
- sections.push(`\n## 部署情報${companyName ? `: ${companyName}` : ""}(全${numFound}件中${displayed.length}件表示)`);
374
- sections.push(displayed.map((d, i) => formatDepartmentDoc(d, i + 1)).join("\n\n"));
366
+ let text = `\n## 部署情報${companyName ? `: ${companyName}` : ""}(全${numFound}件中${displayed.length}件表示)\n`;
367
+ text += displayed.map((d, i) => formatDepartmentDoc(d, i + 1)).join("\n\n");
375
368
  if (numFound > 15) {
376
- sections.push(`\n> 残り${numFound - 15}件はbizgate__department_searchで個別に取得できます。`);
369
+ text += `\n\n> 残り${numFound - 15}件はbizgate__department_searchで個別に取得できます。`;
377
370
  }
371
+ return text;
378
372
  }
379
- else {
380
- sections.push("\n## 部署情報\n該当なし");
381
- }
382
- }
383
- catch (e) {
384
- if (e instanceof BizGateApiError && e.code === "504") {
385
- sections.push("\n## 部署情報\nデータなし");
386
- }
387
- else {
388
- sections.push(`\n## 部署情報\n${e instanceof Error ? e.message : "取得失敗"}`);
389
- }
390
- }
391
- // 3. Keyman info
392
- if (skeyKeyman) {
393
- try {
394
- const { docs, numFound } = await client.searchKeyman({
395
- shogo, compno, bKwd, cList,
396
- });
373
+ return "\n## 部署情報\n該当なし";
374
+ })
375
+ .catch((e) => {
376
+ if (e instanceof BizGateApiError && e.code === "504")
377
+ return "\n## 部署情報\nデータなし";
378
+ return `\n## 部署情報\n${e instanceof Error ? e.message : "取得失敗"}`;
379
+ });
380
+ const keymanPromise = skeyKeyman
381
+ ? client.searchKeyman({ shogo, compno, bKwd, cList })
382
+ .then(({ docs, numFound }) => {
397
383
  if (docs.length > 0) {
398
384
  const companyName = f(docs[0].shogo);
399
- sections.push(`\n## 人事情報${companyName ? `: ${companyName}` : ""}(全${numFound}件)`);
400
- sections.push(docs.map((d, i) => formatKeymanDoc(d, i + 1)).join("\n\n"));
401
- }
402
- else {
403
- sections.push("\n## 人事情報\n該当なし");
404
- }
405
- }
406
- catch (e) {
407
- if (e instanceof BizGateApiError && e.code === "604") {
408
- sections.push("\n## 人事情報\nデータなし");
409
- }
410
- else {
411
- sections.push(`\n## 人事情報\n${e instanceof Error ? e.message : "取得失敗"}`);
385
+ return `\n## 人事情報${companyName ? `: ${companyName}` : ""}(全${numFound}件)\n` +
386
+ docs.map((d, i) => formatKeymanDoc(d, i + 1)).join("\n\n");
412
387
  }
413
- }
414
- }
388
+ return "\n## 人事情報\n該当なし";
389
+ })
390
+ .catch((e) => {
391
+ if (e instanceof BizGateApiError && e.code === "604")
392
+ return "\n## 人事情報\nデータなし";
393
+ return `\n## 人事情報\n${e instanceof Error ? e.message : "取得失敗"}`;
394
+ })
395
+ : Promise.resolve("");
396
+ const [companyText, deptText, keymanText] = await Promise.all([
397
+ companyPromise, deptPromise, keymanPromise,
398
+ ]);
399
+ const sections = [companyText, deptText, keymanText].filter(Boolean);
415
400
  return {
416
401
  content: [{ type: "text", text: sections.join("\n") + usageFooter() }],
417
402
  };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import { mkdirSync, writeFileSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ const SKILL_DIR = join(homedir(), ".claude", "skills", "prospect-match");
6
+ const SKILL_MD = `---
7
+ name: prospect-match
8
+ description: |
9
+ 企業の部署リストとDigiManサービスをマッチングし、営業ターゲット部署を提案する。
10
+ "売れそうな部署", "部署マッチング", "prospect", "ターゲット部署", "영업 타겟",
11
+ "DigiManのサービスが売れそうな", "サービスが売れそう", "営業代行", "Solution", "AI",
12
+ "どの部署に売れる", "ターゲット探して", "部署を探して"
13
+ user-invocable: true
14
+ argument-hint: "[会社名] [サービス種別(営業代行/Solution/AI/全部)]"
15
+ allowed-tools: mcp__bizgate__bizgate__department_search, mcp__bizgate__bizgate__keyman_search, mcp__bizgate__bizgate__company_search, Read, Grep, Glob, AskUserQuestion
16
+ ---
17
+
18
+ # Prospect Match: 企業部署 × DigiManサービス マッチング
19
+
20
+ 企業名を受け取り、BizGate APIで部署情報を取得し、DigiManのサービスに適した営業ターゲット部署を提案するスキルです。
21
+
22
+ ## DigiManサービス定義
23
+
24
+ ### 1. 営業代行
25
+ - **内容**: 営業チームのKPIダッシュボード、架電/アポ進捗管理、担当者別パフォーマンス分析
26
+ - **ターゲット部署の特徴**: 営業組織を持つ部署、営業企画、営業管理、営業推進
27
+ - **マッチキーワード**: 営業, 販売, 販促, 商品, 事業開発, マーケティング, 顧客
28
+
29
+ ### 2. Solution
30
+ - **内容**: 内部管理システム(DB・データ分析・ダッシュボード構築)
31
+ - **ターゲット部署の特徴**: DX推進、経営企画、総務、システム、業務改善担当
32
+ - **マッチキーワード**: ICT, システム, DX, 情報, 企画, 経営企画, 総務, 管理, イノベーション, デジタル
33
+
34
+ ### 3. AI
35
+ - **内容**: Claude × BizGate連携MCP。自然言語で企業情報を検索・取得
36
+ - **ターゲット部署の特徴**: 新規事業、事業創造、戦略推進、営業企画
37
+ - **マッチキーワード**: 戦略, 成長, 事業創造, イノベーション, 企画, 開発, 推進, DX, AI, ICT
38
+
39
+ ## プロセス
40
+
41
+ ### Step 1: 入力を解析
42
+ - ユーザーの入力から **会社名** と **対象サービス** を特定する
43
+ - サービスが指定されていない場合は「全部」として3サービスすべてをマッチングする
44
+ - 会社名が不明な場合は \`AskUserQuestion\` で確認する
45
+
46
+ ### Step 2: 部署情報を取得
47
+ - \`bizgate__department_search\` で部署一覧を取得する(limit: 200)
48
+ - 電話番号がある部署は電話番号も含める
49
+
50
+ ### Step 3: サービスとマッチング
51
+ 各サービスについて、以下の基準で部署をマッチングする:
52
+
53
+ 1. **部署名にマッチキーワードが含まれるか** を確認
54
+ 2. **部署のカテゴリ(ctg_01〜15)** も参考にする:
55
+ - 営業代行 → ctg_02(営業企画), ctg_13(営業)
56
+ - Solution → ctg_01(経営企画), ctg_05(総務), ctg_10(システム)
57
+ - AI → ctg_01(経営企画), ctg_02(営業企画), ctg_10(システム)
58
+ 3. キーワードマッチだけでなく、**部署の役割から判断** して適切な部署を選ぶ
59
+
60
+ ### Step 4: 結果を出力
61
+ 以下のフォーマットで出力する:
62
+
63
+ ## [会社名] × DigiMan サービスマッチング
64
+
65
+ ### 1. 営業代行
66
+ | 部署名 | 電話番号 | マッチ理由 |
67
+ |--------|---------|----------|
68
+
69
+ ### 2. Solution
70
+ | 部署名 | 電話番号 | マッチ理由 |
71
+ |--------|---------|----------|
72
+
73
+ ### 3. AI
74
+ | 部署名 | 電話番号 | マッチ理由 |
75
+ |--------|---------|----------|
76
+
77
+ ### 最優先アプローチ先
78
+ [3サービスに共通して関連性が高い部署をピックアップ]
79
+
80
+ ## 注意事項
81
+ - 電話番号がない部署は「-」と表示する
82
+ - 各サービスにつき最大5部署まで提案する(多すぎると選べない)
83
+ - 明らかに関連のない部署(畜産、食品など事業部門)はスキップする
84
+ - マッチ理由は1行で簡潔に書く
85
+ - ユーザーの言語(日本語 or 韓国語)に合わせて出力する
86
+
87
+ ---
88
+
89
+ $ARGUMENTS
90
+ `;
91
+ function main() {
92
+ const existed = existsSync(join(SKILL_DIR, "SKILL.md"));
93
+ mkdirSync(SKILL_DIR, { recursive: true });
94
+ writeFileSync(join(SKILL_DIR, "SKILL.md"), SKILL_MD, "utf-8");
95
+ if (existed) {
96
+ console.log("✔ prospect-match スキルを更新しました");
97
+ }
98
+ else {
99
+ console.log("✔ prospect-match スキルをインストールしました");
100
+ }
101
+ console.log(` 場所: ${SKILL_DIR}/SKILL.md`);
102
+ console.log("");
103
+ console.log("使い方:");
104
+ console.log(" /prospect-match 会社名");
105
+ console.log(' または「○○にDigiManのサービスが売れそうな部署を探して」');
106
+ }
107
+ main();
@@ -8,6 +8,10 @@ export declare class UsageTracker {
8
8
  private writeState;
9
9
  /** Increment counter before making an API call. Throws if limit exceeded. */
10
10
  increment(cost?: number): number;
11
+ /** Check if limit allows the call, but don't increment yet. Throws if limit exceeded. */
12
+ checkLimit(cost?: number): void;
13
+ /** Decrement counter (rollback on API failure). */
14
+ decrement(cost?: number): void;
11
15
  /** Remaining requests for today. */
12
16
  remaining(): number;
13
17
  /** Current usage count for today. */
@@ -36,6 +36,19 @@ export class UsageTracker {
36
36
  this.writeState(state);
37
37
  return state.count;
38
38
  }
39
+ /** Check if limit allows the call, but don't increment yet. Throws if limit exceeded. */
40
+ checkLimit(cost = 1) {
41
+ const state = this.readState();
42
+ if (state.count + cost > this.limit) {
43
+ throw new Error(`本日のAPI利用上限(${this.limit}回)に達しました。明日以降に再度お試しください。`);
44
+ }
45
+ }
46
+ /** Decrement counter (rollback on API failure). */
47
+ decrement(cost = 1) {
48
+ const state = this.readState();
49
+ state.count = Math.max(0, state.count - cost);
50
+ this.writeState(state);
51
+ }
39
52
  /** Remaining requests for today. */
40
53
  remaining() {
41
54
  const state = this.readState();
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "bizgate-mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
4
4
  "description": "BizGate APIとClaudeを連携するMCPサーバー",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
- "bizgate-mcp-server": "dist/index.js"
8
+ "bizgate-mcp-server": "dist/index.js",
9
+ "bizgate-install-skill": "dist/install-skill.js"
9
10
  },
10
11
  "files": [
11
12
  "dist"