taiwan-data-mcp 0.4.0 → 0.6.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 -1
- package/package.json +2 -2
- package/src/server.mjs +43 -2
- package/src/sources.mjs +102 -0
package/README.md
CHANGED
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
| `taiwan_realprice_estimate` | 自動估價:單價區間與推估總價 | housetw.com |
|
|
20
20
|
| `taiwan_realprice_road` | 查某路段成交行情與逐年走勢 | housetw.com |
|
|
21
21
|
| `taiwan_drug_search` | 用中文藥名搜尋核准藥品、取許可證字號 | [health-hub](https://health-hub-epx.pages.dev)(衛福部食藥署) |
|
|
22
|
-
| `taiwan_drug_info` |
|
|
22
|
+
| `taiwan_drug_info` | 用許可證字號查藥品成分/適應症/健保價/回收/短缺 | health-hub |
|
|
23
|
+
| `taiwan_gov_tender_by_company` | 查某公司投標/得標的政府採購案 | 政府電子採購網(g0v PCC API) |
|
|
24
|
+
| `taiwan_gov_tender_search` | 用標案名稱關鍵字搜尋政府採購案 | 政府電子採購網(g0v PCC API) |
|
|
25
|
+
| `taiwan_farm_price` | 查蔬果批發市場最新行情(菜價) | 農業部 data.moa.gov.tw |
|
|
23
26
|
|
|
24
27
|
跨工具串接是重點:例如「查這家公司 → 看它登記地址那區的房價 → 查它官網是不是詐騙」,一次問答內 AI 自己串起來。
|
|
25
28
|
|
|
@@ -75,6 +78,8 @@ node test/e2e.mjs # MCP 協定層測試
|
|
|
75
78
|
- 公司登記查核 — **[inc.com.tw](https://inc.com.tw)**(台灣公司登記網)
|
|
76
79
|
- 165 防詐查詢 — **[fraud.tw](https://fraud.tw)**(防詐雷達)
|
|
77
80
|
- 藥品/健康查詢 — **[health-hub](https://health-hub-epx.pages.dev)**(衛福部食藥署資料)
|
|
81
|
+
- 政府標案查詢 — 政府電子採購網開放資料(透過 [g0v PCC API](https://pcc.g0v.ronny.tw))
|
|
82
|
+
- 農產批發行情 — 農業部開放資料([data.moa.gov.tw](https://data.moa.gov.tw))
|
|
78
83
|
|
|
79
84
|
三站皆為聚合台灣政府開放資料的免費查詢服務。
|
|
80
85
|
|
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.6.0",
|
|
4
|
+
"description": "MCP server for Taiwan public data — 實價登錄、公司登記查核、165 防詐、藥品查詢、政府標案、農產行情。Query real Taiwan data from Claude, Cursor, etc.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"taiwan-data-mcp": "src/server.mjs"
|
package/src/server.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
scamCheck, companySearch, companyProfile, personCompanies, companyRisk,
|
|
7
7
|
realpriceSearch, realpriceLocate, realpriceArea, realpriceEstimate, realpriceRoad,
|
|
8
8
|
drugSearch, drugInfo,
|
|
9
|
+
tenderByCompany, tenderSearch,
|
|
10
|
+
farmPrice,
|
|
9
11
|
} from './sources.mjs';
|
|
10
12
|
|
|
11
13
|
const TOOLS = [
|
|
@@ -149,7 +151,7 @@ const TOOLS = [
|
|
|
149
151
|
{
|
|
150
152
|
name: 'taiwan_drug_info',
|
|
151
153
|
description:
|
|
152
|
-
'
|
|
154
|
+
'用藥品許可證字號查該藥詳情:主成分、適應症、用法、劑型、藥品類別/管制分級、健保價、是否被回收、供應短缺狀態。資料來源:衛福部食藥署 · health-hub。用藥請依醫師、藥師指示。',
|
|
153
155
|
inputSchema: {
|
|
154
156
|
type: 'object',
|
|
155
157
|
properties: { license_no: { type: 'string', description: '藥品許可證字號,例如「衛署藥輸字第024600號」(可先用 taiwan_drug_search 取得)' } },
|
|
@@ -157,6 +159,45 @@ const TOOLS = [
|
|
|
157
159
|
},
|
|
158
160
|
run: (a) => drugInfo(a.license_no),
|
|
159
161
|
},
|
|
162
|
+
{
|
|
163
|
+
name: 'taiwan_gov_tender_by_company',
|
|
164
|
+
description:
|
|
165
|
+
'查某公司/廠商參與投標或得標的政府採購案,回傳標案名稱、機關、公告類型、日期與相關廠商。可搭配公司查核做盡職調查(這家公司接過哪些政府標案)。資料來源:政府電子採購網開放資料(g0v PCC API)。',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: { company: { type: 'string', description: '公司/廠商名稱,例如「大同股份有限公司」' } },
|
|
169
|
+
required: ['company'],
|
|
170
|
+
},
|
|
171
|
+
run: (a) => tenderByCompany(a.company),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'taiwan_gov_tender_search',
|
|
175
|
+
description:
|
|
176
|
+
'用標案名稱關鍵字搜尋政府採購案,回傳標案、機關、得標廠商與日期(可翻頁)。資料來源:政府電子採購網開放資料(g0v PCC API)。',
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: 'object',
|
|
179
|
+
properties: {
|
|
180
|
+
keyword: { type: 'string', description: '標案名稱關鍵字,例如「口罩」「資訊服務」' },
|
|
181
|
+
page: { type: 'number', description: '頁碼(可選,預設 1)' },
|
|
182
|
+
},
|
|
183
|
+
required: ['keyword'],
|
|
184
|
+
},
|
|
185
|
+
run: (a) => tenderSearch(a.keyword, a.page),
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: 'taiwan_farm_price',
|
|
189
|
+
description:
|
|
190
|
+
'查台灣農產品批發市場最新交易行情:某蔬果的平均、最高、最低批發價(元/公斤)與交易量。颱風季菜價查詢常用。涵蓋蔬果花卉,不含禽蛋肉品。資料來源:農業部 data.moa.gov.tw。',
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: 'object',
|
|
193
|
+
properties: {
|
|
194
|
+
crop: { type: 'string', description: '蔬果名稱,例如「高麗菜」「香蕉」「青蔥」「番茄」' },
|
|
195
|
+
market: { type: 'string', description: '批發市場(可選,預設「台北一」),例如「台北一」「台北二」「台中」「三重」「高雄」' },
|
|
196
|
+
},
|
|
197
|
+
required: ['crop'],
|
|
198
|
+
},
|
|
199
|
+
run: (a) => farmPrice(a.crop, a.market),
|
|
200
|
+
},
|
|
160
201
|
];
|
|
161
202
|
|
|
162
203
|
const server = new Server(
|
|
@@ -181,4 +222,4 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
181
222
|
|
|
182
223
|
const transport = new StdioServerTransport();
|
|
183
224
|
await server.connect(transport);
|
|
184
|
-
console.error('taiwan-data-mcp running (stdio) —
|
|
225
|
+
console.error('taiwan-data-mcp running (stdio) — 15 tools: 防詐 / 公司登記 / 實價登錄 / 藥品健康 / 政府標案 / 農產行情');
|
package/src/sources.mjs
CHANGED
|
@@ -107,6 +107,103 @@ export async function companyRisk(id) {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// ── 農產批發行情 MOA(農業部農產品交易行情)──────────────────────
|
|
111
|
+
function _rocToAd(s) {
|
|
112
|
+
const m = String(s || '').match(/^(\d{2,3})\.(\d{2})\.(\d{2})$/);
|
|
113
|
+
return m ? `${Number(m[1]) + 1911}-${m[2]}-${m[3]}` : s || null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 常見俗名 → 農業部資料作物名(資料用正式名,民眾用俗名)
|
|
117
|
+
const CROP_ALIAS = { 高麗菜: '甘藍', 蕃茄: '番茄', 西紅柿: '番茄', 小蕃茄: '小番茄', 聖女番茄: '小番茄' };
|
|
118
|
+
|
|
119
|
+
export async function farmPrice(crop, market) {
|
|
120
|
+
let c = String(crop || '').trim();
|
|
121
|
+
if (!c) return { error: '請提供農產品名稱,例如「高麗菜」「香蕉」「青蔥」' };
|
|
122
|
+
if (CROP_ALIAS[c]) c = CROP_ALIAS[c];
|
|
123
|
+
const mkt = String(market || '台北一').trim();
|
|
124
|
+
const { ok, body } = await fetchJson(
|
|
125
|
+
`https://data.moa.gov.tw/Service/OpenData/FromM/FarmTransData.aspx?Market=${encodeURIComponent(mkt)}`,
|
|
126
|
+
{ timeout: 20000 }
|
|
127
|
+
);
|
|
128
|
+
if (!ok || !Array.isArray(body)) return { error: '查詢失敗(農業部農產行情)', crop: c, market: mkt };
|
|
129
|
+
const matched = body
|
|
130
|
+
.filter((r) => String(r['作物名稱'] || '').includes(c))
|
|
131
|
+
.map((r) => ({
|
|
132
|
+
crop: r['作物名稱'],
|
|
133
|
+
date: _rocToAd(r['交易日期']),
|
|
134
|
+
market: r['市場名稱'],
|
|
135
|
+
avg_price_ntd_kg: r['平均價'],
|
|
136
|
+
high_ntd_kg: r['上價'],
|
|
137
|
+
low_ntd_kg: r['下價'],
|
|
138
|
+
volume_kg: r['交易量'],
|
|
139
|
+
}));
|
|
140
|
+
if (!matched.length) {
|
|
141
|
+
return { crop: c, market: mkt, count: 0,
|
|
142
|
+
note: `${mkt}市場最新交易日查無「${c}」,可換關鍵字或市場(如 台北一/台北二/台中/三重/高雄)`,
|
|
143
|
+
source: '農業部農產品批發市場交易行情 data.moa.gov.tw' };
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
crop: c,
|
|
147
|
+
market: mkt,
|
|
148
|
+
count: matched.length,
|
|
149
|
+
items: matched.slice(0, 30),
|
|
150
|
+
note: '價格單位:元/公斤;交易量:公斤;為該市場最新交易日資料',
|
|
151
|
+
source: '農業部農產品批發市場交易行情 data.moa.gov.tw',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── 政府標案 PCC(g0v 政府採購開放資料)─────────────────────────
|
|
156
|
+
const PCC_BASE = 'https://pcc-api.openfun.app';
|
|
157
|
+
|
|
158
|
+
function _fmtTenderDate(d) {
|
|
159
|
+
const s = String(d || '');
|
|
160
|
+
return s.length === 8 ? `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}` : s || null;
|
|
161
|
+
}
|
|
162
|
+
function _mapTender(r) {
|
|
163
|
+
const b = r.brief || {};
|
|
164
|
+
const names = b.companies?.names || [];
|
|
165
|
+
return {
|
|
166
|
+
date: _fmtTenderDate(r.date),
|
|
167
|
+
title: b.title || null,
|
|
168
|
+
type: b.type || null,
|
|
169
|
+
agency: r.unit_name || null,
|
|
170
|
+
companies: names,
|
|
171
|
+
tender_url: r.url ? `https://pcc.g0v.ronny.tw${r.url}` : null,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function tenderByCompany(name) {
|
|
176
|
+
const q = String(name || '').trim();
|
|
177
|
+
if (q.length < 2) return { error: '公司名稱至少 2 個字' };
|
|
178
|
+
const { ok, body } = await fetchJson(`${PCC_BASE}/api/searchbycompanyname?query=${encodeURIComponent(q)}`, { timeout: 20000 });
|
|
179
|
+
if (!ok || !body || !Array.isArray(body.records)) return { error: '查詢失敗(政府採購開放資料)', query: q };
|
|
180
|
+
return {
|
|
181
|
+
query: q,
|
|
182
|
+
total: body.total_records ?? body.records.length,
|
|
183
|
+
showing: Math.min(body.records.length, 25),
|
|
184
|
+
tenders: body.records.slice(0, 25).map(_mapTender),
|
|
185
|
+
note: '含該廠商參與投標或得標的採購案;以廠商名比對',
|
|
186
|
+
source: '政府電子採購網(公共工程委員會)開放資料 · 透過 g0v PCC API',
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function tenderSearch(keyword, page = 1) {
|
|
191
|
+
const q = String(keyword || '').trim();
|
|
192
|
+
if (q.length < 2) return { error: '搜尋關鍵字至少 2 個字' };
|
|
193
|
+
const p = Math.max(1, Number(page) || 1);
|
|
194
|
+
const { ok, body } = await fetchJson(`${PCC_BASE}/api/searchbytitle?query=${encodeURIComponent(q)}&page=${p}`, { timeout: 20000 });
|
|
195
|
+
if (!ok || !body || !Array.isArray(body.records)) return { error: '查詢失敗(政府採購開放資料)', query: q };
|
|
196
|
+
return {
|
|
197
|
+
query: q,
|
|
198
|
+
page: p,
|
|
199
|
+
total: body.total_records ?? null,
|
|
200
|
+
total_pages: body.total_pages ?? null,
|
|
201
|
+
showing: Math.min(body.records.length, 25),
|
|
202
|
+
tenders: body.records.slice(0, 25).map(_mapTender),
|
|
203
|
+
source: '政府電子採購網(公共工程委員會)開放資料 · 透過 g0v PCC API',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
110
207
|
// ── 藥品/健康 health-hub ───────────────────────────────────────
|
|
111
208
|
const HEALTH_BASE = 'https://health-hub-epx.pages.dev';
|
|
112
209
|
|
|
@@ -127,6 +224,11 @@ export async function drugSearch(name) {
|
|
|
127
224
|
export async function drugInfo(licenseNo) {
|
|
128
225
|
const lic = String(licenseNo || '').trim();
|
|
129
226
|
if (!lic) return { error: '請提供藥品許可證字號(license_no),可先用 taiwan_drug_search 取得' };
|
|
227
|
+
// 優先用完整端點(含適應症/健保價/回收/短缺);未部署時自動退回成分端點
|
|
228
|
+
const full = await fetchJson(`${HEALTH_BASE}/api/drug.json?lic=${encodeURIComponent(lic)}`);
|
|
229
|
+
if (full.ok && full.body && full.body.name) return full.body;
|
|
230
|
+
if (full.status === 404 && full.body && full.body.error) return full.body; // 確實查無此藥
|
|
231
|
+
|
|
130
232
|
const { ok, body } = await fetchJson(`${HEALTH_BASE}/api/drug-ingredients?lic=${encodeURIComponent(lic)}`);
|
|
131
233
|
if (!ok || !body) return { error: '查詢失敗(health-hub)', license_no: lic };
|
|
132
234
|
if (!body.name) return { error: `查無此許可證字號 ${lic}`, license_no: lic };
|