taiwan-data-mcp 0.2.0 → 0.4.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
@@ -12,11 +12,14 @@
12
12
  | `taiwan_company_search` | 用公司名搜尋,拿統一編號與負責人 | [inc.com.tw](https://inc.com.tw)(經濟部公司登記) |
13
13
  | `taiwan_company_profile` | 用統編查公司完整登記資料(含董監事) | inc.com.tw |
14
14
  | `taiwan_person_companies` | 用人名查他擔任負責人/董監事的公司 | inc.com.tw |
15
+ | `taiwan_company_risk` | 公司風險查核:拒絕往來/勞動/環保裁罰紅旗 | inc.com.tw |
15
16
  | `taiwan_realprice_search` | 搜尋實價登錄的地址 / 路段 / 行政區 | [housetw.com](https://housetw.com)(內政部實價登錄) |
16
17
  | `taiwan_realprice_locate` | 用經緯度反查行政區與行情頁 | housetw.com |
17
18
  | `taiwan_realprice_area` | 查某縣市 / 行政區成交行情統計 | housetw.com |
18
19
  | `taiwan_realprice_estimate` | 自動估價:單價區間與推估總價 | housetw.com |
19
20
  | `taiwan_realprice_road` | 查某路段成交行情與逐年走勢 | housetw.com |
21
+ | `taiwan_drug_search` | 用中文藥名搜尋核准藥品、取許可證字號 | [health-hub](https://health-hub-epx.pages.dev)(衛福部食藥署) |
22
+ | `taiwan_drug_info` | 用許可證字號查藥品主要成分 | health-hub |
20
23
 
21
24
  跨工具串接是重點:例如「查這家公司 → 看它登記地址那區的房價 → 查它官網是不是詐騙」,一次問答內 AI 自己串起來。
22
25
 
@@ -71,6 +74,7 @@ node test/e2e.mjs # MCP 協定層測試
71
74
  - 實價登錄行情 — **[housetw.com](https://housetw.com)**(實價雷達)
72
75
  - 公司登記查核 — **[inc.com.tw](https://inc.com.tw)**(台灣公司登記網)
73
76
  - 165 防詐查詢 — **[fraud.tw](https://fraud.tw)**(防詐雷達)
77
+ - 藥品/健康查詢 — **[health-hub](https://health-hub-epx.pages.dev)**(衛福部食藥署資料)
74
78
 
75
79
  三站皆為聚合台灣政府開放資料的免費查詢服務。
76
80
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "taiwan-data-mcp",
3
- "version": "0.2.0",
4
- "description": "MCP server for Taiwan public data — 實價登錄行情、公司登記查核、165 防詐查詢。Lets AI assistants (Claude, Cursor, etc.) query real Taiwan data.",
3
+ "version": "0.4.0",
4
+ "description": "MCP server for Taiwan public data — 實價登錄行情、公司登記查核、165 防詐查詢、藥品成分查詢。Lets AI assistants (Claude, Cursor, etc.) query real Taiwan data.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "taiwan-data-mcp": "src/server.mjs"
package/src/server.mjs CHANGED
@@ -3,8 +3,9 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
5
  import {
6
- scamCheck, companySearch, companyProfile, personCompanies,
6
+ scamCheck, companySearch, companyProfile, personCompanies, companyRisk,
7
7
  realpriceSearch, realpriceLocate, realpriceArea, realpriceEstimate, realpriceRoad,
8
+ drugSearch, drugInfo,
8
9
  } from './sources.mjs';
9
10
 
10
11
  const TOOLS = [
@@ -52,6 +53,17 @@ const TOOLS = [
52
53
  },
53
54
  run: (a) => personCompanies(a.name),
54
55
  },
56
+ {
57
+ name: 'taiwan_company_risk',
58
+ description:
59
+ '公司風險查核(盡職調查紅旗):用 8 位統編查該公司有無政府採購拒絕往來、勞動法令裁罰、環保裁罰,回傳風險等級與紅旗清單。資料來源:inc.com.tw(聚合政府公開資料)。',
60
+ inputSchema: {
61
+ type: 'object',
62
+ properties: { unified_business_no: { type: 'string', description: '8 位統一編號,例如 22099131' } },
63
+ required: ['unified_business_no'],
64
+ },
65
+ run: (a) => companyRisk(a.unified_business_no),
66
+ },
55
67
  {
56
68
  name: 'taiwan_realprice_search',
57
69
  description:
@@ -123,6 +135,28 @@ const TOOLS = [
123
135
  },
124
136
  run: (a) => realpriceRoad(a.county, a.district, a.road),
125
137
  },
138
+ {
139
+ name: 'taiwan_drug_search',
140
+ description:
141
+ '搜尋台灣核准的藥品(用中文藥名關鍵字),回傳符合的藥品與其衛福部許可證字號。要看成分請接著用 taiwan_drug_info。資料來源:衛福部食藥署 · health-hub。',
142
+ inputSchema: {
143
+ type: 'object',
144
+ properties: { name: { type: 'string', description: '藥品中文名關鍵字,例如「普拿疼」「斯斯」' } },
145
+ required: ['name'],
146
+ },
147
+ run: (a) => drugSearch(a.name),
148
+ },
149
+ {
150
+ name: 'taiwan_drug_info',
151
+ description:
152
+ '用藥品許可證字號查該藥的主要成分。資料來源:衛福部食藥署藥品許可證 · health-hub。用藥請依醫師、藥師指示。',
153
+ inputSchema: {
154
+ type: 'object',
155
+ properties: { license_no: { type: 'string', description: '藥品許可證字號,例如「衛署藥輸字第024600號」(可先用 taiwan_drug_search 取得)' } },
156
+ required: ['license_no'],
157
+ },
158
+ run: (a) => drugInfo(a.license_no),
159
+ },
126
160
  ];
127
161
 
128
162
  const server = new Server(
@@ -147,4 +181,4 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
147
181
 
148
182
  const transport = new StdioServerTransport();
149
183
  await server.connect(transport);
150
- console.error('taiwan-data-mcp running (stdio) — 9 tools: 防詐 / 公司登記 / 實價登錄');
184
+ console.error('taiwan-data-mcp running (stdio) — 12 tools: 防詐 / 公司登記 / 實價登錄 / 藥品健康');
package/src/sources.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // 台灣公開資料來源:薄客戶端,呼叫各站既有 JSON API,資料留在來源站。
2
2
  // 每個工具回傳都夾帶 source 與 link(署名 + 導流)。
3
3
 
4
- const UA = 'taiwan-data-mcp/0.1 (+https://github.com/kwlin/taiwan-data-mcp)';
4
+ const UA = 'taiwan-data-mcp (+https://github.com/kewelin/taiwan-data-mcp)';
5
5
 
6
6
  export async function fetchJson(url, { timeout = 12000 } = {}) {
7
7
  const ctrl = new AbortController();
@@ -53,7 +53,7 @@ export async function companySearch(name) {
53
53
  export async function companyProfile(id) {
54
54
  const tin = String(id || '').replace(/\D/g, '');
55
55
  if (tin.length !== 8) return { error: '統一編號必須是 8 位數字' };
56
- const { ok, status, body } = await fetchJson(`https://inc.com.tw/api/company/${tin}`);
56
+ const { ok, status, body } = await fetchJson(`https://inc.com.tw/api/company/${tin}`, { timeout: 20000 });
57
57
  if (status === 404) return { error: `查無此統一編號 ${tin}` };
58
58
  if (!ok || !body) return { error: '查詢失敗(inc.com.tw)', unified_business_no: tin };
59
59
  const { source: _drop, ...rest } = body;
@@ -77,6 +77,69 @@ export async function personCompanies(name) {
77
77
  source: '台灣公司登記網 inc.com.tw' };
78
78
  }
79
79
 
80
+ export async function companyRisk(id) {
81
+ const tin = String(id || '').replace(/\D/g, '');
82
+ if (tin.length !== 8) return { error: '統一編號必須是 8 位數字' };
83
+ const { ok, status, body } = await fetchJson(`https://inc.com.tw/api/company/${tin}`, { timeout: 20000 });
84
+ if (status === 404) return { error: `查無此統一編號 ${tin}` };
85
+ if (!ok || !body) return { error: '查詢失敗(inc.com.tw)', unified_business_no: tin };
86
+ const f = body.flags || {};
87
+ const redFlags = [];
88
+ if (f.government_debarment) redFlags.push('政府採購拒絕往來');
89
+ if (f.labor_penalties) redFlags.push(`勞動法令裁罰 ${f.labor_penalties} 筆`);
90
+ if (f.environmental_penalties) redFlags.push(`環保裁罰 ${f.environmental_penalties} 筆`);
91
+ const level = f.government_debarment ? 'high' : redFlags.length ? 'medium' : 'low';
92
+ return {
93
+ unified_business_no: tin,
94
+ name: body.name,
95
+ status: body.status,
96
+ listing: body.listing || null,
97
+ risk_level: level,
98
+ red_flags: redFlags,
99
+ detail: {
100
+ government_debarment: !!f.government_debarment,
101
+ labor_penalties: f.labor_penalties || 0,
102
+ environmental_penalties: f.environmental_penalties || 0,
103
+ },
104
+ note: '裁罰以公司名稱比對,可能含同名同稱;僅供參考,以政府原始公告為準',
105
+ profile_url: `https://inc.com.tw/c/${tin}`,
106
+ source: '台灣公司登記網 inc.com.tw',
107
+ };
108
+ }
109
+
110
+ // ── 藥品/健康 health-hub ───────────────────────────────────────
111
+ const HEALTH_BASE = 'https://health-hub-epx.pages.dev';
112
+
113
+ export async function drugSearch(name) {
114
+ const q = String(name || '').trim();
115
+ if (q.length < 1) return { error: '請提供藥品名稱關鍵字' };
116
+ const { ok, body } = await fetchJson(`${HEALTH_BASE}/api/suggest?q=${encodeURIComponent(q)}`);
117
+ if (!ok || !Array.isArray(body)) return { error: '查詢失敗(health-hub)', query: q };
118
+ const drugs = body.map((d) => {
119
+ let lic = '';
120
+ try { lic = decodeURIComponent((d.href || '').replace(/^\/drug\//, '')); } catch { lic = (d.href || '').replace(/^\/drug\//, ''); }
121
+ return { name: d.name, license_no: lic, detail: d.href ? `${HEALTH_BASE}${d.href}` : undefined };
122
+ });
123
+ return { query: q, count: drugs.length, drugs,
124
+ source: '衛福部食藥署藥品許可證 · 健康查詢 health-hub' };
125
+ }
126
+
127
+ export async function drugInfo(licenseNo) {
128
+ const lic = String(licenseNo || '').trim();
129
+ if (!lic) return { error: '請提供藥品許可證字號(license_no),可先用 taiwan_drug_search 取得' };
130
+ const { ok, body } = await fetchJson(`${HEALTH_BASE}/api/drug-ingredients?lic=${encodeURIComponent(lic)}`);
131
+ if (!ok || !body) return { error: '查詢失敗(health-hub)', license_no: lic };
132
+ if (!body.name) return { error: `查無此許可證字號 ${lic}`, license_no: lic };
133
+ return {
134
+ license_no: lic,
135
+ name: body.name,
136
+ active_ingredients: body.ingredients || [],
137
+ detail: `${HEALTH_BASE}/drug/${encodeURIComponent(lic)}`,
138
+ note: '藥品資訊僅供參考,用藥請依醫師、藥師指示',
139
+ source: '衛福部食藥署藥品許可證 · 健康查詢 health-hub',
140
+ };
141
+ }
142
+
80
143
  // ── 實價登錄 housetw.com ───────────────────────────────────────
81
144
  export async function realpriceSearch(q) {
82
145
  const query = String(q || '').trim();
@@ -137,3 +200,14 @@ export async function realpriceRoad(county, district, road) {
137
200
  if (!ok) return { error: '查詢失敗(housetw.com)', road: r };
138
201
  return body;
139
202
  }
203
+
204
+ export async function realpriceRent(county, district) {
205
+ const c = String(county || '').trim();
206
+ if (!c) return { error: '請提供縣市 county(可加行政區 district)' };
207
+ const qp = new URLSearchParams({ county: c });
208
+ if (district) qp.set('district', String(district).trim());
209
+ const { ok, body, status } = await fetchJson(`https://housetw.com/api/rent.json?${qp}`);
210
+ if (status === 404 || status === 302 || !body) return { error: '租金端點暫不可用(housetw.com,待部署)', county: c };
211
+ if (!ok) return { error: '查詢失敗(housetw.com)', county: c };
212
+ return body;
213
+ }