taiwan-data-mcp 0.3.0 → 0.5.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 +6 -0
- package/package.json +2 -2
- package/src/server.mjs +50 -1
- package/src/sources.mjs +90 -0
package/README.md
CHANGED
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
| `taiwan_realprice_area` | 查某縣市 / 行政區成交行情統計 | housetw.com |
|
|
19
19
|
| `taiwan_realprice_estimate` | 自動估價:單價區間與推估總價 | housetw.com |
|
|
20
20
|
| `taiwan_realprice_road` | 查某路段成交行情與逐年走勢 | housetw.com |
|
|
21
|
+
| `taiwan_drug_search` | 用中文藥名搜尋核准藥品、取許可證字號 | [health-hub](https://health-hub-epx.pages.dev)(衛福部食藥署) |
|
|
22
|
+
| `taiwan_drug_info` | 用許可證字號查藥品成分/適應症/健保價/回收/短缺 | health-hub |
|
|
23
|
+
| `taiwan_gov_tender_by_company` | 查某公司投標/得標的政府採購案 | 政府電子採購網(g0v PCC API) |
|
|
24
|
+
| `taiwan_gov_tender_search` | 用標案名稱關鍵字搜尋政府採購案 | 政府電子採購網(g0v PCC API) |
|
|
21
25
|
|
|
22
26
|
跨工具串接是重點:例如「查這家公司 → 看它登記地址那區的房價 → 查它官網是不是詐騙」,一次問答內 AI 自己串起來。
|
|
23
27
|
|
|
@@ -72,6 +76,8 @@ node test/e2e.mjs # MCP 協定層測試
|
|
|
72
76
|
- 實價登錄行情 — **[housetw.com](https://housetw.com)**(實價雷達)
|
|
73
77
|
- 公司登記查核 — **[inc.com.tw](https://inc.com.tw)**(台灣公司登記網)
|
|
74
78
|
- 165 防詐查詢 — **[fraud.tw](https://fraud.tw)**(防詐雷達)
|
|
79
|
+
- 藥品/健康查詢 — **[health-hub](https://health-hub-epx.pages.dev)**(衛福部食藥署資料)
|
|
80
|
+
- 政府標案查詢 — 政府電子採購網開放資料(透過 [g0v PCC API](https://pcc.g0v.ronny.tw))
|
|
75
81
|
|
|
76
82
|
三站皆為聚合台灣政府開放資料的免費查詢服務。
|
|
77
83
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taiwan-data-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Taiwan public data —
|
|
3
|
+
"version": "0.5.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
|
@@ -5,6 +5,8 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprot
|
|
|
5
5
|
import {
|
|
6
6
|
scamCheck, companySearch, companyProfile, personCompanies, companyRisk,
|
|
7
7
|
realpriceSearch, realpriceLocate, realpriceArea, realpriceEstimate, realpriceRoad,
|
|
8
|
+
drugSearch, drugInfo,
|
|
9
|
+
tenderByCompany, tenderSearch,
|
|
8
10
|
} from './sources.mjs';
|
|
9
11
|
|
|
10
12
|
const TOOLS = [
|
|
@@ -134,6 +136,53 @@ const TOOLS = [
|
|
|
134
136
|
},
|
|
135
137
|
run: (a) => realpriceRoad(a.county, a.district, a.road),
|
|
136
138
|
},
|
|
139
|
+
{
|
|
140
|
+
name: 'taiwan_drug_search',
|
|
141
|
+
description:
|
|
142
|
+
'搜尋台灣核准的藥品(用中文藥名關鍵字),回傳符合的藥品與其衛福部許可證字號。要看成分請接著用 taiwan_drug_info。資料來源:衛福部食藥署 · health-hub。',
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: 'object',
|
|
145
|
+
properties: { name: { type: 'string', description: '藥品中文名關鍵字,例如「普拿疼」「斯斯」' } },
|
|
146
|
+
required: ['name'],
|
|
147
|
+
},
|
|
148
|
+
run: (a) => drugSearch(a.name),
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'taiwan_drug_info',
|
|
152
|
+
description:
|
|
153
|
+
'用藥品許可證字號查該藥詳情:主成分、適應症、用法、劑型、藥品類別/管制分級、健保價、是否被回收、供應短缺狀態。資料來源:衛福部食藥署 · health-hub。用藥請依醫師、藥師指示。',
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: { license_no: { type: 'string', description: '藥品許可證字號,例如「衛署藥輸字第024600號」(可先用 taiwan_drug_search 取得)' } },
|
|
157
|
+
required: ['license_no'],
|
|
158
|
+
},
|
|
159
|
+
run: (a) => drugInfo(a.license_no),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'taiwan_gov_tender_by_company',
|
|
163
|
+
description:
|
|
164
|
+
'查某公司/廠商參與投標或得標的政府採購案,回傳標案名稱、機關、公告類型、日期與相關廠商。可搭配公司查核做盡職調查(這家公司接過哪些政府標案)。資料來源:政府電子採購網開放資料(g0v PCC API)。',
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: { company: { type: 'string', description: '公司/廠商名稱,例如「大同股份有限公司」' } },
|
|
168
|
+
required: ['company'],
|
|
169
|
+
},
|
|
170
|
+
run: (a) => tenderByCompany(a.company),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'taiwan_gov_tender_search',
|
|
174
|
+
description:
|
|
175
|
+
'用標案名稱關鍵字搜尋政府採購案,回傳標案、機關、得標廠商與日期(可翻頁)。資料來源:政府電子採購網開放資料(g0v PCC API)。',
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: {
|
|
179
|
+
keyword: { type: 'string', description: '標案名稱關鍵字,例如「口罩」「資訊服務」' },
|
|
180
|
+
page: { type: 'number', description: '頁碼(可選,預設 1)' },
|
|
181
|
+
},
|
|
182
|
+
required: ['keyword'],
|
|
183
|
+
},
|
|
184
|
+
run: (a) => tenderSearch(a.keyword, a.page),
|
|
185
|
+
},
|
|
137
186
|
];
|
|
138
187
|
|
|
139
188
|
const server = new Server(
|
|
@@ -158,4 +207,4 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
158
207
|
|
|
159
208
|
const transport = new StdioServerTransport();
|
|
160
209
|
await server.connect(transport);
|
|
161
|
-
console.error('taiwan-data-mcp running (stdio) —
|
|
210
|
+
console.error('taiwan-data-mcp running (stdio) — 14 tools: 防詐 / 公司登記 / 實價登錄 / 藥品健康 / 政府標案');
|
package/src/sources.mjs
CHANGED
|
@@ -107,6 +107,96 @@ export async function companyRisk(id) {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// ── 政府標案 PCC(g0v 政府採購開放資料)─────────────────────────
|
|
111
|
+
const PCC_BASE = 'https://pcc-api.openfun.app';
|
|
112
|
+
|
|
113
|
+
function _fmtTenderDate(d) {
|
|
114
|
+
const s = String(d || '');
|
|
115
|
+
return s.length === 8 ? `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}` : s || null;
|
|
116
|
+
}
|
|
117
|
+
function _mapTender(r) {
|
|
118
|
+
const b = r.brief || {};
|
|
119
|
+
const names = b.companies?.names || [];
|
|
120
|
+
return {
|
|
121
|
+
date: _fmtTenderDate(r.date),
|
|
122
|
+
title: b.title || null,
|
|
123
|
+
type: b.type || null,
|
|
124
|
+
agency: r.unit_name || null,
|
|
125
|
+
companies: names,
|
|
126
|
+
tender_url: r.url ? `https://pcc.g0v.ronny.tw${r.url}` : null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function tenderByCompany(name) {
|
|
131
|
+
const q = String(name || '').trim();
|
|
132
|
+
if (q.length < 2) return { error: '公司名稱至少 2 個字' };
|
|
133
|
+
const { ok, body } = await fetchJson(`${PCC_BASE}/api/searchbycompanyname?query=${encodeURIComponent(q)}`, { timeout: 20000 });
|
|
134
|
+
if (!ok || !body || !Array.isArray(body.records)) return { error: '查詢失敗(政府採購開放資料)', query: q };
|
|
135
|
+
return {
|
|
136
|
+
query: q,
|
|
137
|
+
total: body.total_records ?? body.records.length,
|
|
138
|
+
showing: Math.min(body.records.length, 25),
|
|
139
|
+
tenders: body.records.slice(0, 25).map(_mapTender),
|
|
140
|
+
note: '含該廠商參與投標或得標的採購案;以廠商名比對',
|
|
141
|
+
source: '政府電子採購網(公共工程委員會)開放資料 · 透過 g0v PCC API',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function tenderSearch(keyword, page = 1) {
|
|
146
|
+
const q = String(keyword || '').trim();
|
|
147
|
+
if (q.length < 2) return { error: '搜尋關鍵字至少 2 個字' };
|
|
148
|
+
const p = Math.max(1, Number(page) || 1);
|
|
149
|
+
const { ok, body } = await fetchJson(`${PCC_BASE}/api/searchbytitle?query=${encodeURIComponent(q)}&page=${p}`, { timeout: 20000 });
|
|
150
|
+
if (!ok || !body || !Array.isArray(body.records)) return { error: '查詢失敗(政府採購開放資料)', query: q };
|
|
151
|
+
return {
|
|
152
|
+
query: q,
|
|
153
|
+
page: p,
|
|
154
|
+
total: body.total_records ?? null,
|
|
155
|
+
total_pages: body.total_pages ?? null,
|
|
156
|
+
showing: Math.min(body.records.length, 25),
|
|
157
|
+
tenders: body.records.slice(0, 25).map(_mapTender),
|
|
158
|
+
source: '政府電子採購網(公共工程委員會)開放資料 · 透過 g0v PCC API',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── 藥品/健康 health-hub ───────────────────────────────────────
|
|
163
|
+
const HEALTH_BASE = 'https://health-hub-epx.pages.dev';
|
|
164
|
+
|
|
165
|
+
export async function drugSearch(name) {
|
|
166
|
+
const q = String(name || '').trim();
|
|
167
|
+
if (q.length < 1) return { error: '請提供藥品名稱關鍵字' };
|
|
168
|
+
const { ok, body } = await fetchJson(`${HEALTH_BASE}/api/suggest?q=${encodeURIComponent(q)}`);
|
|
169
|
+
if (!ok || !Array.isArray(body)) return { error: '查詢失敗(health-hub)', query: q };
|
|
170
|
+
const drugs = body.map((d) => {
|
|
171
|
+
let lic = '';
|
|
172
|
+
try { lic = decodeURIComponent((d.href || '').replace(/^\/drug\//, '')); } catch { lic = (d.href || '').replace(/^\/drug\//, ''); }
|
|
173
|
+
return { name: d.name, license_no: lic, detail: d.href ? `${HEALTH_BASE}${d.href}` : undefined };
|
|
174
|
+
});
|
|
175
|
+
return { query: q, count: drugs.length, drugs,
|
|
176
|
+
source: '衛福部食藥署藥品許可證 · 健康查詢 health-hub' };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function drugInfo(licenseNo) {
|
|
180
|
+
const lic = String(licenseNo || '').trim();
|
|
181
|
+
if (!lic) return { error: '請提供藥品許可證字號(license_no),可先用 taiwan_drug_search 取得' };
|
|
182
|
+
// 優先用完整端點(含適應症/健保價/回收/短缺);未部署時自動退回成分端點
|
|
183
|
+
const full = await fetchJson(`${HEALTH_BASE}/api/drug.json?lic=${encodeURIComponent(lic)}`);
|
|
184
|
+
if (full.ok && full.body && full.body.name) return full.body;
|
|
185
|
+
if (full.status === 404 && full.body && full.body.error) return full.body; // 確實查無此藥
|
|
186
|
+
|
|
187
|
+
const { ok, body } = await fetchJson(`${HEALTH_BASE}/api/drug-ingredients?lic=${encodeURIComponent(lic)}`);
|
|
188
|
+
if (!ok || !body) return { error: '查詢失敗(health-hub)', license_no: lic };
|
|
189
|
+
if (!body.name) return { error: `查無此許可證字號 ${lic}`, license_no: lic };
|
|
190
|
+
return {
|
|
191
|
+
license_no: lic,
|
|
192
|
+
name: body.name,
|
|
193
|
+
active_ingredients: body.ingredients || [],
|
|
194
|
+
detail: `${HEALTH_BASE}/drug/${encodeURIComponent(lic)}`,
|
|
195
|
+
note: '藥品資訊僅供參考,用藥請依醫師、藥師指示',
|
|
196
|
+
source: '衛福部食藥署藥品許可證 · 健康查詢 health-hub',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
110
200
|
// ── 實價登錄 housetw.com ───────────────────────────────────────
|
|
111
201
|
export async function realpriceSearch(q) {
|
|
112
202
|
const query = String(q || '').trim();
|