import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpAgent } from "agents/mcp";
import { z } from "zod";
// Define environment bindings
interface Env {
AUTORAG_ID?: string;
AI: Ai;
DB: D1Database;
}
// Define MCP agent for Taiwan Government Open Data Search
export class TwGovDataMCP extends McpAgent<Env> {
server = new McpServer({
name: "Taiwan Government Open Data Search",
version: "1.0.0",
});
async init() {
// Tool 1: Search datasets using AI Search
this.server.tool(
"search_dataset",
"搜尋台灣政府開放資料集。根據關鍵詞查詢相關的資料集,會返回前幾筆最相關的資料集資訊。",
{
query: z.string().describe("搜尋關鍵詞,例如:交通事故、健康保險、環境監測等"),
max_results: z.number().optional().default(10).describe("最多返回幾筆結果,預設為10"),
},
async ({ query, max_results }) => {
try {
console.log(`[search_dataset] 開始搜尋: query="${query}", max_results=${max_results || 10}`);
// Check if AutoRAG ID is configured
const autoragId = this.env.AUTORAG_ID;
if (!autoragId) {
console.error("[search_dataset] 錯誤: AUTORAG_ID 未設定");
return {
content: [
{
type: "text",
text: "錯誤:尚未設定 AutoRAG ID。請在 wrangler.toml 中設定 AUTORAG_ID 變數。",
},
],
};
}
console.log(`[search_dataset] 使用 AutoRAG ID: ${autoragId}`);
// Step 1: Use AI Search to find relevant datasets (without AI generation)
console.log("[search_dataset] Step 1: 開始執行 AI Search");
const searchResult = await this.env.AI.autorag(autoragId).search({
query: query,
max_num_results: max_results || 10,
ranking_options: {
score_threshold: 0.3,
},
rewrite_query: true,
});
console.log(`[search_dataset] AI Search 完成,找到 ${searchResult.data?.length || 0} 筆結果`);
if (!searchResult.data || searchResult.data.length === 0) {
console.log("[search_dataset] 未找到任何結果");
return {
content: [
{
type: "text",
text: `找不到與「${query}」相關的資料集。`,
},
],
};
}
// Step 2: Extract dataset IDs from filenames and query D1 for full details
console.log("[search_dataset] Step 2: 提取資料集 ID");
const datasetIds: string[] = [];
searchResult.data.forEach((item) => {
// Extract ID from filename (e.g., "10001.json" -> "10001")
const match = item.filename?.match(/(\d+)\.json/);
if (match && match[1]) {
datasetIds.push(match[1]);
}
});
console.log(`[search_dataset] 成功提取 ${datasetIds.length} 個資料集 ID: ${datasetIds.join(", ")}`);
if (datasetIds.length === 0) {
console.error("[search_dataset] 無法從檔名提取資料集 ID");
return {
content: [
{
type: "text",
text: "無法從搜尋結果中提取資料集 ID。",
},
],
};
}
// Step 3: Query D1 database for full dataset information
console.log("[search_dataset] Step 3: 查詢 D1 資料庫");
const placeholders = datasetIds.map(() => "?").join(",");
console.log(`SELECT * FROM datasets WHERE id IN (${placeholders})`, datasetIds)
const dbResult = await this.env.DB.prepare(
`SELECT * FROM datasets WHERE id IN (${placeholders})`
)
.bind(...datasetIds)
.all();
console.log(`[search_dataset] D1 查詢完成,取得 ${dbResult.results?.length || 0} 筆資料`);
// Format the response
console.log("[search_dataset] Step 4: 格式化回應資料");
let response = `## 搜尋結果:「${query}」\n\n`;
response += `找到 ${dbResult.results?.length || 0} 筆相關資料集:\n\n`;
if (dbResult.results && dbResult.results.length > 0) {
dbResult.results.forEach((dataset: any, index: number) => {
const searchItem = searchResult.data.find((item) =>
item.filename?.includes(`${dataset.id}.json`)
);
const score = searchItem?.score || 0;
response += `### ${index + 1}. ${dataset.name}\n`;
response += `- **資料集ID**: ${dataset.id}\n`;
response += `- **相關度**: ${(score * 100).toFixed(1)}%\n`;
response += `- **提供機關**: ${dataset.provider || "無資料"}\n`;
response += `- **服務分類**: ${dataset.service_category || "無資料"}\n`;
response += `- **資料提供屬性**: ${dataset.data_attribute || "無資料"}\n`;
response += `- **品質檢測**: ${dataset.quality_check || "無資料"}\n`;
response += `- **檔案格式**: ${dataset.file_format || "無資料"}\n`;
response += `- **編碼格式**: ${dataset.encoding || "無資料"}\n`;
if (dataset.download_url) {
response += `- **下載網址**: ${dataset.download_url}\n`;
}
response += `- **上架方式**: ${dataset.upload_method || "無資料"}\n`;
if (dataset.description) {
const snippet =
dataset.description.length > 200
? dataset.description.substring(0, 200) + "..."
: dataset.description;
response += `- **描述**: ${snippet}\n`;
}
if (dataset.main_fields) {
const fieldsSnippet =
dataset.main_fields.length > 150
? dataset.main_fields.substring(0, 150) + "..."
: dataset.main_fields;
response += `- **主要欄位說明**: ${fieldsSnippet}\n`;
}
response += `- **更新頻率**: ${dataset.update_frequency || "無資料"}\n`;
response += `- **授權方式**: ${dataset.license || "無資料"}\n`;
if (dataset.related_url) {
response += `- **相關網址**: ${dataset.related_url}\n`;
}
response += `- **計費方式**: ${dataset.fee_type || "無資料"}\n`;
if (dataset.contact_name) {
response += `- **聯絡人**: ${dataset.contact_name}\n`;
}
if (dataset.contact_phone) {
response += `- **聯絡電話**: ${dataset.contact_phone}\n`;
}
if (dataset.upload_date) {
response += `- **上架日期**: ${dataset.upload_date}\n`;
}
if (dataset.metadata_update_time) {
response += `- **詮釋資料更新時間**: ${dataset.metadata_update_time}\n`;
}
if (dataset.notes) {
const notesSnippet =
dataset.notes.length > 100
? dataset.notes.substring(0, 100) + "..."
: dataset.notes;
response += `- **備註**: ${notesSnippet}\n`;
}
response += `- **資料量**: ${dataset.data_volume || "無資料"}\n`;
response += `\n`;
});
} else {
response += "無法從資料庫取得詳細資料。\n";
}
console.log(`[search_dataset] 搜尋完成,返回 ${dbResult.results?.length || 0} 筆結果`);
return {
content: [{ type: "text", text: response }],
};
} catch (error: any) {
console.error(`[search_dataset] 發生錯誤:`, error);
return {
content: [
{
type: "text",
text: `搜尋時發生錯誤:${error.message || "未知錯誤"}`,
},
],
};
}
},
);
}
}
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url);
// Handle SSE protocol (deprecated but still supported)
if (url.pathname === "/sse" || url.pathname === "/sse/message") {
return TwGovDataMCP.serveSSE("/sse").fetch(request, env, ctx);
}
// Handle MCP protocol (recommended)
if (url.pathname === "/mcp") {
return TwGovDataMCP.serve("/mcp").fetch(request, env, ctx);
}
// Homepage with instructions
if (url.pathname === "/") {
return new Response(
`
<!DOCTYPE html>
<html>
<head>
<title>台灣政府開放資料 MCP 伺服器</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; line-height: 1.6; }
h1 { color: #2c3e50; }
code { background: #f4f4f4; padding: 2px 5px; border-radius: 3px; }
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
</style>
</head>
<body>
<h1>🇹🇼 台灣政府開放資料 MCP 伺服器</h1>
<p>這是一個用於搜尋台灣政府開放資料的 Model Context Protocol (MCP) 伺服器。</p>
<h2>可用的工具</h2>
<ul>
<li><strong>search_dataset</strong> - 使用 AI 搜尋相關的政府開放資料集</li>
</ul>
<h2>如何連接</h2>
<h3>使用 MCP Inspector</h3>
<pre>npx @modelcontextprotocol/inspector@latest</pre>
<p>然後輸入伺服器 URL: <code>${url.origin}/mcp</code></p>
<h3>使用 GitHub Copilot</h3>
<p>在 GitHub Copilot 的 MCP 設定中加入:</p>
<pre>{
"mcpServers": {
"tw-gov-data": {
"url": "${url.origin}/sse",
"type": "http"
}
}
}</pre>
<h2>文件</h2>
<p>
<a href="https://github.com/modelcontextprotocol/servers">MCP 文件</a> |
<a href="https://data.gov.tw/">台灣政府資料開放平臺</a>
</p>
</body>
</html>
`,
{
headers: { "Content-Type": "text/html; charset=utf-8" },
},
);
}
return new Response("Not found", { status: 404 });
},
};