bizgate-mcp-server 0.1.0 → 0.3.0

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
@@ -1,161 +1,242 @@
1
1
  # bizgate-mcp-server
2
2
 
3
- BizGate API と Claude を接続する MCP サーバーです。
4
- 会社名・法人番号で企業情報・部署・人事情報を検索し、Claude が自然言語で回答できるようにします。
3
+ BizGate API と Claude Code を接続する MCP サーバーです。
4
+ Claude に話しかけるだけで、企業の担当者名・電話番号・住所・部署情報などを調べられます。
5
5
 
6
- ## 仕組み
6
+ ```
7
+ 例:
8
+ 「富士フイルムBIの営業担当者の名前と電話番号を教えて」
9
+ 「株式会社エージェントの企業情報を調べて」
10
+ 「アサヒペンのマーケティングタグを見せて」
11
+ 「この会社の全情報を調べて、Excelに入れて」
12
+ ```
13
+
14
+ ### 仕組み
7
15
 
8
16
  ```
9
- [ユーザー] → [Claude] ←── MCP (stdio) ──→ [本サーバー] ──→ [BizGate API]
17
+ [ユーザー] → [Claude Code] ←── MCP ──→ [本サーバー] ──→ [BizGate API]
10
18
  ```
11
19
 
12
- 1. ユーザーが Claude に「○○の人事部の電話番号を教えて」と依頼
13
- 2. Claude が MCP プロトコル経由で本サーバーのツールを呼び出す
14
- 3. 本サーバーが BizGate API に問い合わせ → JSON レスポンスを解析
15
- 4. 構造化された情報を Claude に返却
16
- 5. Claude がユーザーに自然言語で回答(スプレッドシートへの入力も可能)
20
+ > 現在は **Claude Code 専用** です。Claude ウェブサイト(claude.ai)では使用できません。
17
21
 
18
- ## 提供ツール
22
+ ---
19
23
 
20
- | ツール名 | 説明 | API消費 |
21
- |---------|------|--------|
22
- | `bizgate__company_search` | 企業の基本情報(住所・電話・代表者・業種・資本金 等) | 1回(結果なしでも課金) |
23
- | `bizgate__department_search` | 部署情報(部署名・住所・電話番号)最大500件 | 1回(データなしは課金なし) |
24
- | `bizgate__marketing_tags` | マーケティングタグ(SNS・MA・サービス・活動) | 1回(データなしは課金なし) |
25
- | `bizgate__keyman_search` | 人事情報(部署・役職・発令日)最大500件 | 1回(データなしは課金なし) |
26
- | `bizgate__company_full` | 企業+部署+人事を一括取得 | 3回 |
27
- | `bizgate__usage_status` | 本日の残りAPI回数を確認 | 0回 |
24
+ ## できること
28
25
 
29
- ## セットアップ
26
+ | 調べたいこと | 取得できる情報 |
27
+ |------------|-------------|
28
+ | 企業の基本情報 | 住所、代表電話、代表者名、業種、資本金、売上、従業員数、HP |
29
+ | 部署情報 | 部署名、部署の電話番号、部署の住所(最大500件) |
30
+ | 担当者情報 | **担当者の実名**、役職、電話番号、住所、発令日 |
31
+ | マーケティングタグ | SNS導入、MAツール、事業サービス、採用情報、活動状況 |
32
+ | 一括取得 | 上記すべてをまとめて取得 |
30
33
 
31
- ### 前提条件
34
+ ---
32
35
 
33
- - Node.js 18 以上
34
- - Claude Code がインストール済み
35
- - BizGate API のアカウント(ユーザー名・パスワード・サービスキー)
36
+ ## セットアップ(所要時間:約10分)
36
37
 
37
- ### 1. リポジトリをクローン&ビルド
38
+ ### 事前に必要なもの
38
39
 
39
- ```bash
40
- git clone https://github.com/digiman-hq/bizgate-mcp-server.git
41
- cd bizgate-mcp-server
42
- npm install
43
- npm run build
40
+ - **Claude Code** がパソコンにインストール済み
41
+ - **Node.js 18 以上** がインストール済み
42
+ - **BizGate のアカウント情報**(管理者から受け取ってください)
43
+ - ユーザー名
44
+ - パスワード
45
+ - サービスキー(企業用・部署用は必須、マーケ・キーマン用は任意)
46
+
47
+ ---
48
+
49
+ ### ステップ 1:ターミナルを開く
50
+
51
+ Mac の場合:
52
+ 1. `Command + Space` で Spotlight を開く
53
+ 2. 「ターミナル」と入力して Enter
54
+
55
+ ---
56
+
57
+ ### ステップ 2:MCP サーバーを登録する
58
+
59
+ ターミナルに以下を **そのままコピーして貼り付け** → Enter を押してください。
60
+
61
+ ```
62
+ claude mcp add --scope user bizgate -- npx bizgate-mcp-server
44
63
  ```
45
64
 
46
- ### 2. Claude Code に MCP サーバーを登録
65
+ 以下のように表示されれば OK です。
66
+ ```
67
+ Added stdio MCP server bizgate...
68
+ ```
69
+
70
+ ---
71
+
72
+ ### ステップ 3:設定ファイルを編集する
47
73
 
48
- 以下のコマンドを **1行ずつ** 実行してください。サービスキーの値は管理者から受け取ってください。
74
+ ターミナルに以下を **そのままコピーして貼り付け** → Enter を押してください。
49
75
 
50
- ```bash
51
- claude mcp add --scope user bizgate \
52
- --transport stdio \
53
- -- node /path/to/bizgate-mcp-server/dist/index.js
76
+ ```
77
+ open ~/.claude.json
54
78
  ```
55
79
 
56
- > **注意**: 上記コマンドでは環境変数が設定されません。次のステップで手動設定が必要です。
80
+ テキストエディタで設定ファイルが開きます。
57
81
 
58
- ### 3. 環境変数を設定
82
+ ファイルの中から `"bizgate"` というセクションを探してください。
83
+ 以下のように **書き換え** ます。
59
84
 
60
- `~/.claude.json` を開き、`mcpServers.bizgate` の `env` セクションに以下を設定します。
85
+ #### 書き換え前
61
86
 
62
87
  ```json
63
- {
64
- "mcpServers": {
65
- "bizgate": {
66
- "type": "stdio",
67
- "command": "node",
68
- "args": ["/path/to/bizgate-mcp-server/dist/index.js"],
69
- "env": {
70
- "BIZGATE_USERNAME": "あなたのユーザー名",
71
- "BIZGATE_PASSWORD": "あなたのパスワード",
72
- "BIZGATE_AUTH_MODE": "basic",
73
- "BIZGATE_DAILY_LIMIT": "200",
74
- "BIZGATE_SKEY_COMPANY": "企業APIのサービスキー",
75
- "BIZGATE_SKEY_DEPARTMENT": "部署APIのサービスキー",
76
- "BIZGATE_SKEY_MARKETING": "マーケAPIのサービスキー(任意)",
77
- "BIZGATE_SKEY_KEYMAN": "キーマンAPIのサービスキー(任意)"
78
- }
79
- }
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キーを入力"
80
113
  }
81
114
  }
82
115
  ```
83
116
 
84
- > `/path/to/` はクローンした実際のパスに置き換えてください。
117
+ > **注意(よくあるミス)**
118
+ > - `ここに〇〇を入力` の部分を、管理者から受け取った **実際の値** に置き換えてください
119
+ > - ダブルクォーテーション `"` は **消さないで** ください
120
+ > - カンマ `,` の位置を **変えないで** ください
121
+ > - 最後の行(`"BIZGATE_SKEY_KEYMAN_NAME"` の行)の末尾には **カンマをつけないで** ください
122
+ > - 編集後、`Command + S` で保存 → ファイルを閉じる
123
+
124
+ ---
125
+
126
+ ### ステップ 4:Claude Code を再起動する
85
127
 
86
- ### 4. Claude Code を再起動して確認
128
+ もし Claude Code が開いている場合は、一度閉じてください。
87
129
 
88
- ```bash
130
+ ターミナルで以下を実行してください。
131
+
132
+ ```
89
133
  claude
90
134
  ```
91
135
 
92
- 起動後、`/mcp` コマンドで `bizgate` が **connected** になっていれば成功です。
136
+ ---
93
137
 
94
- ## 使い方
138
+ ### ステップ 5:接続を確認する
95
139
 
96
- Claude Code で自然言語で聞くだけです。
97
-
98
- ### 基本的な使い方
140
+ Claude Code が起動したら、以下を入力してください。
99
141
 
100
142
  ```
101
- エージェントの企業情報を教えて
143
+ /mcp
144
+ ```
102
145
 
103
- 富士フイルムBIの営業部署を調べて
146
+ 一覧が表示されます。 `bizgate` の横に **✔ connected** と表示されていれば成功です。
104
147
 
105
- GMOインターネットのマーケティングタグを教えて
106
- ```
148
+ うまくいかない場合は、このページの一番下の「うまくいかないとき」を確認してください。
107
149
 
108
- ### 部署の絞り込み
150
+ ---
109
151
 
110
- ```
111
- 富士フイルムBIの東京都にある人事部を調べて
152
+ ## 使い方
112
153
 
113
- トヨタ自動車の経理と総務の部署情報を教えて
114
- ```
154
+ 設定完了後は、Claude に **日本語で話しかけるだけ** です。特別なコマンドは不要です。
115
155
 
116
- ### 一括取得
156
+ ### よく使うパターン
117
157
 
118
- ```
119
- 株式会社エージェントの全情報を調べて
120
- (→ 企業情報・部署・人事情報をまとめて取得)
121
- ```
158
+ | やりたいこと | Claude への聞き方 |
159
+ |------------|-----------------|
160
+ | 担当者の名前と電話番号 | 「○○の営業担当者の名前と電話番号を教えて」 |
161
+ | 会社の基本情報を知りたい | 「株式会社○○の企業情報を教えて」 |
162
+ | 電話番号を知りたい | 「○○の電話番号は?」 |
163
+ | 部署の一覧を見たい | 「○○の部署一覧を出して」 |
164
+ | 特定の部署を探したい | 「○○の人事部の連絡先を教えて」 |
165
+ | 採用活動をしている会社か確認 | 「○○のマーケティングタグを教えて」 |
166
+ | 全情報をまとめて見たい | 「○○の全情報を調べて」 |
167
+ | Excelに入力したい | 「○○の情報を調べてExcelに入れて」 |
122
168
 
123
- ### スプレッドシートとの連携
169
+ ### 注意点
124
170
 
125
- ```
126
- エージェントの企業情報を調べて、Excelに入力して
171
+ - **会社名は正確に** 入力してください(例:「ソフバン」→ 「ソフトバンク株式会社」)
172
+ - 1日に使える回数は **200回** までです(残り回数は毎回表示されます)
173
+ - 企業検索は、結果が見つからなくても **1回分消費** されます
174
+ - 部署・マーケ・キーマン検索は、データが見つからなかった場合は **消費されません**
175
+ - 担当者検索は、**1名あたり1回分消費** されます(デフォルト上位10名)
127
176
 
128
- このリストの会社の電話番号をスプレッドシートのB列に入れて
129
- ```
177
+ ---
178
+
179
+ ## 提供ツール(参考情報)
180
+
181
+ 通常はツール名を意識する必要はありません。Claude が自動で適切なツールを選びます。
182
+
183
+ | ツール名 | 説明 | API消費 |
184
+ |---------|------|--------|
185
+ | `bizgate__company_search` | 企業の基本情報(住所・電話・代表者・業種・資本金 等) | 1回(結果なしでも課金) |
186
+ | `bizgate__department_search` | 部署情報(部署名・住所・電話番号)最大500件 | 1回(データなしは課金なし) |
187
+ | `bizgate__marketing_tags` | マーケティングタグ(SNS・MA・採用・活動) | 1回(データなしは課金なし) |
188
+ | `bizgate__keyman_search` | 担当者情報(実名・役職・電話番号・住所) | 1回 + 詳細取得N回 |
189
+ | `bizgate__company_full` | 企業+部署+キーマンを一括取得 | 3回〜 |
190
+ | `bizgate__usage_status` | 本日の残りAPI回数を確認 | 0回 |
191
+
192
+ ---
130
193
 
131
- ## 環境変数一覧
194
+ ## 環境変数一覧(参考情報)
132
195
 
133
196
  | 変数名 | 必須 | 説明 |
134
197
  |-------|------|------|
135
- | `BIZGATE_USERNAME` | ○(basic認証時) | BizGate ユーザー名 |
136
- | `BIZGATE_PASSWORD` | ○(basic認証時) | BizGate パスワード |
198
+ | `BIZGATE_USERNAME` | | BizGate ユーザー名 |
199
+ | `BIZGATE_PASSWORD` | | BizGate パスワード |
137
200
  | `BIZGATE_AUTH_MODE` | | `basic`(デフォルト)または `ip` |
138
- | `BIZGATE_APP` | ○(IP認証時) | アプリ識別子 |
201
+ | `BIZGATE_APP` | ○(IP認証時のみ) | アプリ識別子 |
139
202
  | `BIZGATE_SKEY_COMPANY` | ○ | 企業APIのサービスキー |
140
203
  | `BIZGATE_SKEY_DEPARTMENT` | ○ | 部署APIのサービスキー |
141
204
  | `BIZGATE_SKEY_MARKETING` | | マーケティングタグAPIのサービスキー |
142
- | `BIZGATE_SKEY_KEYMAN` | | キーマンAPIのサービスキー |
205
+ | `BIZGATE_SKEY_KEYMAN` | | キーマン(人名なし)APIのサービスキー |
206
+ | `BIZGATE_SKEY_KEYMAN_NAME` | | キーマン(人名あり)APIのサービスキー |
143
207
  | `BIZGATE_DAILY_LIMIT` | | 1日のAPI上限(デフォルト: 200) |
144
- | `BIZGATE_BASE_URL` | | APIエンドポイント(通常変更不要) |
145
208
 
146
- ## API利用上の注意
209
+ ---
210
+
211
+ ## うまくいかないとき
212
+
213
+ ### `/mcp` で `bizgate` が `failed` になっている
214
+
215
+ ステップ 3 の設定ファイルに入力ミスがある可能性が高いです。以下を確認してください。
216
+
217
+ - ダブルクォーテーション `"` が抜けていないか
218
+ - カンマ `,` が余分についていないか(特に **最後の行の末尾**)
219
+ - ユーザー名・パスワード・サービスキーが正しいか
220
+
221
+ 確認しても直らない場合は、管理者に `~/.claude.json` の中身を見せて相談してください。
222
+
223
+ ### 「サービスキーが無効です」と表示される
224
+
225
+ サービスキーが間違っている、または有効期限切れです。管理者に確認してください。
226
+
227
+ ### 「1日のリクエスト上限に達しました」と表示される
228
+
229
+ 本日の利用回数(200回)を使い切りました。翌日にリセットされます。
230
+
231
+ ### 「該当する企業が見つかりませんでした」と表示される
232
+
233
+ 会社名を **正式名称** で入力してみてください。
234
+ - 悪い例:「トヨタ」
235
+ - 良い例:「トヨタ自動車株式会社」
147
236
 
148
- - **企業検索は結果なしでも課金**されます。会社名は正確に入力してください
149
- - **部署・マーケ・キーマンはデータなしなら課金なし**です
150
- - 1日のAPI上限はエンリッチメントパイプラインと共有です(全体900回中、MCP用に200回を確保)
151
- - すべてのレスポンスに残りAPI回数が表示されます
237
+ 法人番号がわかる場合は、そちらでも検索できます。
152
238
 
153
- ## トラブルシューティング
239
+ ### 「複数の企業が存在します」と表示される
154
240
 
155
- | 症状 | 原因 | 対処法 |
156
- |------|------|--------|
157
- | `/mcp` で `failed` | 環境変数の設定ミス | `~/.claude.json` の env を確認 |
158
- | エラーコード 102 | サービスキーが無効 | 管理者に有効なサービスキーを確認 |
159
- | エラーコード 112 | 1日の上限超過 | 翌日まで待つ、または上限を確認 |
160
- | エラーコード 204 | 企業が見つからない | 正式名称(株式会社○○)で再検索 |
161
- | エラーコード 205 | 複数企業がマッチ | 法人番号やメールアドレスを追加で指定 |
241
+ 検索条件が曖昧で、複数の企業がヒットしています。
242
+ 会社名をより正確に入力するか、メールアドレスやホームページURLを追加で伝えてください。
@@ -2,17 +2,37 @@ import type { BizGateConfig, BizGateDoc } from "./types.js";
2
2
  import { UsageTracker } from "./usage-tracker.js";
3
3
  /** Extract first element from a string array field, or return empty string. */
4
4
  export declare function first(field: string[] | string | undefined): string;
5
+ interface CompanyResult {
6
+ matchPattern: string;
7
+ docs: BizGateDoc[];
8
+ }
9
+ interface DepartmentResult {
10
+ docs: BizGateDoc[];
11
+ numFound: number;
12
+ }
13
+ interface MarketingResult {
14
+ docs: BizGateDoc[];
15
+ }
16
+ interface KeymanResult {
17
+ docs: BizGateDoc[];
18
+ numFound: number;
19
+ }
5
20
  export declare class BizGateClient {
6
21
  private config;
7
22
  private usageTracker;
23
+ private companyCache;
24
+ private departmentCache;
25
+ private marketingCache;
26
+ private keymanCache;
8
27
  constructor(config: BizGateConfig, usageTracker: UsageTracker);
9
28
  private buildUrl;
10
29
  private getHeaders;
11
- /** Send GET request and return parsed response. */
12
30
  private request;
31
+ /** Build cache key from params. */
32
+ private cacheKey;
13
33
  /**
14
34
  * Company lookup (企業).
15
- * Billed even on no-result — usage tracker is incremented BEFORE the call.
35
+ * Cached for 60s. Billed even on no-result.
16
36
  */
17
37
  searchCompany(params: {
18
38
  shogo?: string;
@@ -20,13 +40,10 @@ export declare class BizGateClient {
20
40
  hpurl?: string;
21
41
  email?: string;
22
42
  tel?: string;
23
- }): Promise<{
24
- matchPattern: string;
25
- docs: BizGateDoc[];
26
- }>;
43
+ }): Promise<CompanyResult>;
27
44
  /**
28
45
  * Department lookup (部署).
29
- * Not billed on failure, but we count conservatively.
46
+ * Cached for 60s. Not billed on failure.
30
47
  */
31
48
  searchDepartments(params: {
32
49
  shogo?: string;
@@ -35,24 +52,18 @@ export declare class BizGateClient {
35
52
  cList?: string;
36
53
  bKwd?: string;
37
54
  bKOpr?: string;
38
- }): Promise<{
39
- docs: BizGateDoc[];
40
- numFound: number;
41
- }>;
55
+ }): Promise<DepartmentResult>;
42
56
  /**
43
57
  * Marketing tags lookup (マーケティングタグ).
44
- * Not billed on failure (company not found or no tags).
58
+ * Cached for 60s. Not billed on failure.
45
59
  */
46
60
  searchMarketingTags(params: {
47
61
  shogo?: string;
48
62
  compno?: string;
49
- }): Promise<{
50
- docs: BizGateDoc[];
51
- }>;
63
+ }): Promise<MarketingResult>;
52
64
  /**
53
65
  * Keyman lookup (キーマン・人名なし).
54
- * Returns personnel records (department + title, announcement date, etc.).
55
- * Not billed on failure.
66
+ * Cached for 60s. Not billed on failure.
56
67
  */
57
68
  searchKeyman(params: {
58
69
  shogo?: string;
@@ -61,8 +72,28 @@ export declare class BizGateClient {
61
72
  cList?: string;
62
73
  bKwd?: string;
63
74
  bKOpr?: string;
75
+ }): Promise<KeymanResult>;
76
+ /**
77
+ * Keyman with name lookup (キーマン・人名あり).
78
+ * Fetches details in parallel for speed.
79
+ */
80
+ getKeymanDetails(keymanIDs: string[]): Promise<{
81
+ docs: BizGateDoc[];
82
+ }>;
83
+ /**
84
+ * Full keyman search with names (2-step).
85
+ */
86
+ searchKeymanWithNames(params: {
87
+ shogo?: string;
88
+ compno?: string;
89
+ pList?: string;
90
+ cList?: string;
91
+ bKwd?: string;
92
+ bKOpr?: string;
93
+ limit?: number;
64
94
  }): Promise<{
65
95
  docs: BizGateDoc[];
66
- numFound: number;
96
+ totalFound: number;
67
97
  }>;
68
98
  }
99
+ export {};
@@ -1,13 +1,20 @@
1
1
  import { BizGateApiError, BIZGATE_ERROR_CODES } from "./types.js";
2
+ import { Cache } from "./cache.js";
2
3
  /** Extract first element from a string array field, or return empty string. */
3
4
  export function first(field) {
4
5
  if (Array.isArray(field))
5
6
  return field[0] ?? "";
6
7
  return field ?? "";
7
8
  }
9
+ // Cache TTL: 60 seconds — same company searched within 60s won't hit the API again
10
+ const CACHE_TTL = 60_000;
8
11
  export class BizGateClient {
9
12
  config;
10
13
  usageTracker;
14
+ companyCache = new Cache(CACHE_TTL);
15
+ departmentCache = new Cache(CACHE_TTL);
16
+ marketingCache = new Cache(CACHE_TTL);
17
+ keymanCache = new Cache(CACHE_TTL);
11
18
  constructor(config, usageTracker) {
12
19
  this.config = config;
13
20
  this.usageTracker = usageTracker;
@@ -28,7 +35,6 @@ export class BizGateClient {
28
35
  }
29
36
  return {};
30
37
  }
31
- /** Send GET request and return parsed response. */
32
38
  async request(skey, params) {
33
39
  const url = this.buildUrl(skey, params);
34
40
  const response = await fetch(url, { headers: this.getHeaders() });
@@ -36,7 +42,6 @@ export class BizGateClient {
36
42
  throw new BizGateApiError(String(response.status), `HTTP ${response.status}: ${response.statusText}`);
37
43
  }
38
44
  const json = (await response.json());
39
- // Error response: status=999, error.code + error.msg
40
45
  if (json.responseHeader.status === 999) {
41
46
  const err = json.responseHeader.error;
42
47
  const code = err?.code ?? "unknown";
@@ -47,11 +52,23 @@ export class BizGateClient {
47
52
  }
48
53
  return json;
49
54
  }
55
+ /** Build cache key from params. */
56
+ cacheKey(params) {
57
+ return Object.entries(params)
58
+ .filter(([, v]) => v)
59
+ .sort(([a], [b]) => a.localeCompare(b))
60
+ .map(([k, v]) => `${k}=${v}`)
61
+ .join("&");
62
+ }
50
63
  /**
51
64
  * Company lookup (企業).
52
- * Billed even on no-result — usage tracker is incremented BEFORE the call.
65
+ * Cached for 60s. Billed even on no-result.
53
66
  */
54
67
  async searchCompany(params) {
68
+ const key = this.cacheKey(params);
69
+ const cached = this.companyCache.get(key);
70
+ if (cached)
71
+ return cached;
55
72
  this.usageTracker.increment();
56
73
  const queryParams = {};
57
74
  if (params.shogo)
@@ -65,16 +82,22 @@ export class BizGateClient {
65
82
  if (params.tel)
66
83
  queryParams.tel = params.tel;
67
84
  const result = await this.request(this.config.skeyCompany, queryParams);
68
- return {
85
+ const data = {
69
86
  matchPattern: result.responseHeader.matchpatern ?? "",
70
87
  docs: result.response?.docs ?? [],
71
88
  };
89
+ this.companyCache.set(key, data);
90
+ return data;
72
91
  }
73
92
  /**
74
93
  * Department lookup (部署).
75
- * Not billed on failure, but we count conservatively.
94
+ * Cached for 60s. Not billed on failure.
76
95
  */
77
96
  async searchDepartments(params) {
97
+ const key = this.cacheKey(params);
98
+ const cached = this.departmentCache.get(key);
99
+ if (cached)
100
+ return cached;
78
101
  this.usageTracker.increment();
79
102
  const queryParams = {};
80
103
  if (params.shogo)
@@ -90,16 +113,22 @@ export class BizGateClient {
90
113
  if (params.bKOpr)
91
114
  queryParams.bKOpr = params.bKOpr;
92
115
  const result = await this.request(this.config.skeyDepartment, queryParams);
93
- return {
116
+ const data = {
94
117
  docs: result.response?.docs ?? [],
95
118
  numFound: result.response?.numFound ?? 0,
96
119
  };
120
+ this.departmentCache.set(key, data);
121
+ return data;
97
122
  }
98
123
  /**
99
124
  * Marketing tags lookup (マーケティングタグ).
100
- * Not billed on failure (company not found or no tags).
125
+ * Cached for 60s. Not billed on failure.
101
126
  */
102
127
  async searchMarketingTags(params) {
128
+ const key = this.cacheKey(params);
129
+ const cached = this.marketingCache.get(key);
130
+ if (cached)
131
+ return cached;
103
132
  this.usageTracker.increment();
104
133
  const queryParams = {};
105
134
  if (params.shogo)
@@ -107,16 +136,21 @@ export class BizGateClient {
107
136
  if (params.compno)
108
137
  queryParams.compno = params.compno;
109
138
  const result = await this.request(this.config.skeyMarketing, queryParams);
110
- return {
139
+ const data = {
111
140
  docs: result.response?.docs ?? [],
112
141
  };
142
+ this.marketingCache.set(key, data);
143
+ return data;
113
144
  }
114
145
  /**
115
146
  * Keyman lookup (キーマン・人名なし).
116
- * Returns personnel records (department + title, announcement date, etc.).
117
- * Not billed on failure.
147
+ * Cached for 60s. Not billed on failure.
118
148
  */
119
149
  async searchKeyman(params) {
150
+ const key = this.cacheKey(params);
151
+ const cached = this.keymanCache.get(key);
152
+ if (cached)
153
+ return cached;
120
154
  this.usageTracker.increment();
121
155
  const queryParams = {};
122
156
  if (params.shogo)
@@ -132,9 +166,64 @@ export class BizGateClient {
132
166
  if (params.bKOpr)
133
167
  queryParams.bKOpr = params.bKOpr;
134
168
  const result = await this.request(this.config.skeyKeyman, queryParams);
135
- return {
169
+ const data = {
136
170
  docs: result.response?.docs ?? [],
137
171
  numFound: result.response?.numFound ?? 0,
138
172
  };
173
+ this.keymanCache.set(key, data);
174
+ return data;
175
+ }
176
+ /**
177
+ * Keyman with name lookup (キーマン・人名あり).
178
+ * Fetches details in parallel for speed.
179
+ */
180
+ async getKeymanDetails(keymanIDs) {
181
+ const fetchOne = async (id) => {
182
+ this.usageTracker.increment();
183
+ try {
184
+ const result = await this.request(this.config.skeyKeymanName, {
185
+ keymanID: id,
186
+ });
187
+ return result.response?.docs?.[0] ?? null;
188
+ }
189
+ catch {
190
+ return null;
191
+ }
192
+ };
193
+ // Parallel fetch (max 5 concurrent)
194
+ const results = [];
195
+ const batchSize = 5;
196
+ for (let i = 0; i < keymanIDs.length; i += batchSize) {
197
+ const batch = keymanIDs.slice(i, i + batchSize);
198
+ const batchResults = await Promise.all(batch.map(fetchOne));
199
+ for (const doc of batchResults) {
200
+ if (doc)
201
+ results.push(doc);
202
+ }
203
+ }
204
+ return { docs: results };
205
+ }
206
+ /**
207
+ * Full keyman search with names (2-step).
208
+ */
209
+ async searchKeymanWithNames(params) {
210
+ const { docs: keymanDocs, numFound } = await this.searchKeyman({
211
+ shogo: params.shogo,
212
+ compno: params.compno,
213
+ pList: params.pList,
214
+ cList: params.cList,
215
+ bKwd: params.bKwd,
216
+ bKOpr: params.bKOpr,
217
+ });
218
+ if (keymanDocs.length === 0) {
219
+ return { docs: [], totalFound: 0 };
220
+ }
221
+ const maxDetails = params.limit ?? 10;
222
+ const ids = keymanDocs
223
+ .slice(0, maxDetails)
224
+ .map((d) => d.id)
225
+ .filter((id) => !!id);
226
+ const { docs: detailDocs } = await this.getKeymanDetails(ids);
227
+ return { docs: detailDocs, totalFound: numFound };
139
228
  }
140
229
  }
@@ -0,0 +1,9 @@
1
+ /** Simple in-memory TTL cache. */
2
+ export declare class Cache<T> {
3
+ private ttlMs;
4
+ private store;
5
+ constructor(ttlMs: number);
6
+ get(key: string): T | undefined;
7
+ set(key: string, data: T): void;
8
+ has(key: string): boolean;
9
+ }
package/dist/cache.js ADDED
@@ -0,0 +1,24 @@
1
+ /** Simple in-memory TTL cache. */
2
+ export class Cache {
3
+ ttlMs;
4
+ store = new Map();
5
+ constructor(ttlMs) {
6
+ this.ttlMs = ttlMs;
7
+ }
8
+ get(key) {
9
+ const entry = this.store.get(key);
10
+ if (!entry)
11
+ return undefined;
12
+ if (Date.now() > entry.expires) {
13
+ this.store.delete(key);
14
+ return undefined;
15
+ }
16
+ return entry.data;
17
+ }
18
+ set(key, data) {
19
+ this.store.set(key, { data, expires: Date.now() + this.ttlMs });
20
+ }
21
+ has(key) {
22
+ return this.get(key) !== undefined;
23
+ }
24
+ }
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ const skeyCompany = process.env.BIZGATE_SKEY_COMPANY;
12
12
  const skeyDepartment = process.env.BIZGATE_SKEY_DEPARTMENT;
13
13
  const skeyMarketing = process.env.BIZGATE_SKEY_MARKETING ?? "";
14
14
  const skeyKeyman = process.env.BIZGATE_SKEY_KEYMAN ?? "";
15
+ const skeyKeymanName = process.env.BIZGATE_SKEY_KEYMAN_NAME ?? "";
15
16
  if (!skeyCompany || !skeyDepartment) {
16
17
  console.error("Error: BIZGATE_SKEY_COMPANY and BIZGATE_SKEY_DEPARTMENT environment variables are required");
17
18
  process.exit(1);
@@ -39,6 +40,7 @@ const config = {
39
40
  skeyDepartment,
40
41
  skeyMarketing,
41
42
  skeyKeyman,
43
+ skeyKeymanName,
42
44
  authMode,
43
45
  username: process.env.BIZGATE_USERNAME,
44
46
  password: process.env.BIZGATE_PASSWORD,
@@ -192,17 +194,19 @@ server.tool("bizgate__company_search", "会社名または法人番号で企業
192
194
  }
193
195
  });
194
196
  // ---------- Tool 2: 部署検索 ----------
195
- server.tool("bizgate__department_search", "会社名または法人番号で部署情報(部署名・住所・電話番号・カテゴリ)を検索する。最大500件。都道府県・カテゴリ・部署名キーワードで絞り込み可能。", {
197
+ server.tool("bizgate__department_search", "会社名または法人番号で部署情報(部署名・住所・電話番号・カテゴリ)を検索する。最大500件。都道府県・カテゴリ・部署名キーワードで絞り込み可能。デフォルトで上位30件を表示(limitで変更可能)。", {
196
198
  shogo: z.string().optional().describe("会社名(例:株式会社○○)"),
197
199
  compno: z.string().optional().describe("法人番号(13桁)"),
198
200
  pList: z.string().optional().describe("都道府県コード(カンマ区切り。例: 13,14 = 東京都,神奈川県)"),
199
201
  cList: z.string().optional().describe("部署カテゴリ番号(カンマ区切り。1=経営企画,2=営業企画,3=人事,4=経理,5=総務,6=広報IR,7=法務,8=研究,9=購買,10=システム,11=海外,12=環境CSR,13=営業,14=製造工場,15=その他)"),
200
202
  bKwd: z.string().optional().describe("部署名キーワード(カンマ区切りで複数可。例: 人事部,経理部)"),
201
203
  bKOpr: z.string().optional().describe("キーワード結合演算子(0=OR(デフォルト), 1=AND)"),
202
- }, async ({ shogo, compno, pList, cList, bKwd, bKOpr }) => {
204
+ limit: z.number().optional().describe("表示する件数の上限(デフォルト: 30)"),
205
+ }, async ({ shogo, compno, pList, cList, bKwd, bKOpr, limit }) => {
203
206
  const err = validateInput(shogo, compno);
204
207
  if (err)
205
208
  return { content: [{ type: "text", text: err }] };
209
+ const displayLimit = limit ?? 30;
206
210
  try {
207
211
  const { docs, numFound } = await client.searchDepartments({
208
212
  shogo, compno, pList, cList, bKwd, bKOpr,
@@ -213,10 +217,14 @@ server.tool("bizgate__department_search", "会社名または法人番号で部
213
217
  };
214
218
  }
215
219
  const companyName = f(docs[0].shogo);
216
- const header = `## 部署情報${companyName ? `: ${companyName}` : ""}(全${numFound}件)\n`;
217
- const text = docs.map((d, i) => formatDepartmentDoc(d, i + 1)).join("\n\n");
220
+ const displayed = docs.slice(0, displayLimit);
221
+ const header = `## 部署情報${companyName ? `: ${companyName}` : ""}(全${numFound}件中${displayed.length}件表示)\n`;
222
+ const text = displayed.map((d, i) => formatDepartmentDoc(d, i + 1)).join("\n\n");
223
+ const truncNote = docs.length > displayLimit
224
+ ? `\n\n> 残り${numFound - displayLimit}件あります。キーワード(bKwd)やカテゴリ(cList)で絞り込むか、limitを増やしてください。`
225
+ : "";
218
226
  return {
219
- content: [{ type: "text", text: header + text + usageFooter() }],
227
+ content: [{ type: "text", text: header + text + truncNote + usageFooter() }],
220
228
  };
221
229
  }
222
230
  catch (e) {
@@ -247,14 +255,21 @@ server.tool("bizgate__marketing_tags", "会社名または法人番号で企業
247
255
  const lines = [`## マーケティングタグ: ${f(doc.shogo)}`];
248
256
  if (doc.compno)
249
257
  lines.push(`法人番号: ${doc.compno}`);
258
+ const formatTags = (raw) => {
259
+ return raw
260
+ .split("/")
261
+ .filter((t) => t.trim())
262
+ .map((t) => `- ${t.trim()}`)
263
+ .join("\n");
264
+ };
250
265
  if (doc.snstag)
251
- lines.push(`\n### SNSタグ\n${doc.snstag.replace(/¥\//g, "\n- ").replace(/^/, "- ")}`);
266
+ lines.push(`\n### SNSタグ\n${formatTags(doc.snstag)}`);
252
267
  if (doc.matag)
253
- lines.push(`\n### MAツール\n${doc.matag.replace(/¥\//g, "\n- ").replace(/^/, "- ")}`);
268
+ lines.push(`\n### MAツール\n${formatTags(doc.matag)}`);
254
269
  if (doc.servicetag)
255
- lines.push(`\n### 事業サービス\n${doc.servicetag.replace(/¥\//g, "\n- ").replace(/^/, "- ")}`);
270
+ lines.push(`\n### 事業サービス\n${formatTags(doc.servicetag)}`);
256
271
  if (doc.activtag)
257
- lines.push(`\n### 活動タグ\n${doc.activtag.replace(/¥\//g, "\n- ").replace(/^/, "- ")}`);
272
+ lines.push(`\n### 活動タグ\n${formatTags(doc.activtag)}`);
258
273
  return {
259
274
  content: [{ type: "text", text: lines.join("\n") + usageFooter() }],
260
275
  };
@@ -263,26 +278,28 @@ server.tool("bizgate__marketing_tags", "会社名または法人番号で企業
263
278
  return { content: [{ type: "text", text: errorText(e) }] };
264
279
  }
265
280
  });
266
- // ---------- Tool 4: キーマン検索 ----------
267
- server.tool("bizgate__keyman_search", "会社名または法人番号で人事情報(部署・役職、発表日、発令日)を検索する。最大500件。部署カテゴリやキーワードで絞り込み可能。人名は含まれない。課金なし(企業特定不可・データなしの場合)。", {
281
+ // ---------- Tool 4: キーマン検索(人名付き) ----------
282
+ server.tool("bizgate__keyman_search", "会社名または法人番号で人事情報(担当者名・部署・役職・電話番号・住所)を検索する。部署カテゴリやキーワードで絞り込み可能。デフォルトで上位10名の詳細を取得(limitで変更可能)。API消費: 1回(一覧取得)+ N回(詳細取得、Nはlimit数)。", {
268
283
  shogo: z.string().optional().describe("会社名(例:株式会社○○)"),
269
284
  compno: z.string().optional().describe("法人番号(13桁)"),
270
285
  pList: z.string().optional().describe("都道府県コード(カンマ区切り)"),
271
286
  cList: z.string().optional().describe("部署カテゴリ番号(カンマ区切り。3=人事,13=営業 など)"),
272
- bKwd: z.string().optional().describe("部署・役職キーワード(カンマ区切り。例: 役員,部長,ソリューション)"),
287
+ bKwd: z.string().optional().describe("部署・役職キーワード(カンマ区切り。例: 役員,部長,営業)"),
273
288
  bKOpr: z.string().optional().describe("キーワード結合演算子(0=OR(デフォルト), 1=AND)"),
274
- }, async ({ shogo, compno, pList, cList, bKwd, bKOpr }) => {
289
+ limit: z.number().optional().describe("詳細取得する人数の上限(デフォルト: 10、最大: 50)。API消費に注意。"),
290
+ }, async ({ shogo, compno, pList, cList, bKwd, bKOpr, limit }) => {
275
291
  const err = validateInput(shogo, compno);
276
292
  if (err)
277
293
  return { content: [{ type: "text", text: err }] };
278
- if (!skeyKeyman) {
294
+ if (!skeyKeyman || !skeyKeymanName) {
279
295
  return {
280
- content: [{ type: "text", text: "エラー: BIZGATE_SKEY_KEYMAN が設定されていません。" }],
296
+ content: [{ type: "text", text: "エラー: BIZGATE_SKEY_KEYMAN および BIZGATE_SKEY_KEYMAN_NAME が設定されていません。" }],
281
297
  };
282
298
  }
299
+ const maxLimit = Math.min(limit ?? 10, 50);
283
300
  try {
284
- const { docs, numFound } = await client.searchKeyman({
285
- shogo, compno, pList, cList, bKwd, bKOpr,
301
+ const { docs, totalFound } = await client.searchKeymanWithNames({
302
+ shogo, compno, pList, cList, bKwd, bKOpr, limit: maxLimit,
286
303
  });
287
304
  if (docs.length === 0) {
288
305
  return {
@@ -290,8 +307,24 @@ server.tool("bizgate__keyman_search", "会社名または法人番号で人事
290
307
  };
291
308
  }
292
309
  const companyName = f(docs[0].shogo);
293
- const header = `## 人事情報${companyName ? `: ${companyName}` : ""}(全${numFound}件)\n`;
294
- const text = docs.map((d, i) => formatKeymanDoc(d, i + 1)).join("\n\n");
310
+ const header = `## 人事情報${companyName ? `: ${companyName}` : ""}(全${totalFound}件中、上位${docs.length}件の詳細)\n`;
311
+ const text = docs
312
+ .map((d, i) => {
313
+ const lines = [`${i + 1}. ${f(d.ceo) || "(氏名不明)"}`];
314
+ lines.push(` 役職: ${f(d.bumon) || "(不明)"}`);
315
+ if (f(d.tel))
316
+ lines.push(` 電話: ${f(d.tel)}`);
317
+ if (f(d.add))
318
+ lines.push(` 住所: ${f(d.add)}`);
319
+ if (d.haturei)
320
+ lines.push(` 発令日: ${d.haturei}`);
321
+ if (d.pagedate)
322
+ lines.push(` 発表日: ${d.pagedate}`);
323
+ if (d.bikou)
324
+ lines.push(` 備考: ${d.bikou}`);
325
+ return lines.join("\n");
326
+ })
327
+ .join("\n\n");
295
328
  return {
296
329
  content: [{ type: "text", text: header + text + usageFooter() }],
297
330
  };
@@ -301,7 +334,7 @@ server.tool("bizgate__keyman_search", "会社名または法人番号で人事
301
334
  }
302
335
  });
303
336
  // ---------- Tool 5: 企業総合検索(企業+部署+キーマンを一括取得) ----------
304
- server.tool("bizgate__company_full", "会社名または法人番号で企業情報・部署情報・人事情報を一括取得する。個別ツールを3回呼ぶ代わりに1回で全情報を取得可能。API 3回分を消費。部署やキーワードで絞り込みも可能。", {
337
+ server.tool("bizgate__company_full", "会社名または法人番号で企業情報・部署情報・人事情報を一括取得する。個別ツールを3回呼ぶ代わりに1回で全情報を取得可能。API 3回分を消費。部署は上位15件、人事は役職一覧のみ(人名は含まない)。人名が必要な場合はbizgate__keyman_searchを別途使用。", {
305
338
  shogo: z.string().optional().describe("会社名(例:株式会社○○)"),
306
339
  compno: z.string().optional().describe("法人番号(13桁)"),
307
340
  hpurl: z.string().optional().describe("ホームページURL(企業特定精度向上)"),
@@ -329,15 +362,19 @@ server.tool("bizgate__company_full", "会社名または法人番号で企業情
329
362
  catch (e) {
330
363
  sections.push(`企業情報: ${e instanceof Error ? e.message : "取得失敗"}`);
331
364
  }
332
- // 2. Department info
365
+ // 2. Department info (top 15 for token efficiency)
333
366
  try {
334
367
  const { docs, numFound } = await client.searchDepartments({
335
368
  shogo, compno, bKwd, cList,
336
369
  });
337
370
  if (docs.length > 0) {
371
+ const displayed = docs.slice(0, 15);
338
372
  const companyName = f(docs[0].shogo);
339
- sections.push(`\n## 部署情報${companyName ? `: ${companyName}` : ""}(全${numFound}件)`);
340
- sections.push(docs.map((d, i) => formatDepartmentDoc(d, i + 1)).join("\n\n"));
373
+ sections.push(`\n## 部署情報${companyName ? `: ${companyName}` : ""}(全${numFound}件中${displayed.length}件表示)`);
374
+ sections.push(displayed.map((d, i) => formatDepartmentDoc(d, i + 1)).join("\n\n"));
375
+ if (numFound > 15) {
376
+ sections.push(`\n> 残り${numFound - 15}件はbizgate__department_searchで個別に取得できます。`);
377
+ }
341
378
  }
342
379
  else {
343
380
  sections.push("\n## 部署情報\n該当なし");
package/dist/types.d.ts CHANGED
@@ -4,6 +4,7 @@ export interface BizGateConfig {
4
4
  skeyDepartment: string;
5
5
  skeyMarketing: string;
6
6
  skeyKeyman: string;
7
+ skeyKeymanName: string;
7
8
  authMode: "basic" | "ip";
8
9
  username?: string;
9
10
  password?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bizgate-mcp-server",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "BizGate APIとClaudeを連携するMCPサーバー",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",