Skip to main content
Glama

Notion Knowledge MCP Server

by YuHuanHsu
worker.js16.1 kB
// Notion Knowledge MCP Server - Cloudflare Workers // 🔧 修復版本 - 使用正確的資料庫 ID class NotionMCPServer { constructor(env) { this.notionToken = env.NOTION_TOKEN; this.databaseId = env.NOTION_DATABASE_ID; if (!this.databaseId) { throw new Error("NOTION_DATABASE_ID environment variable is required"); } this.headers = { 'Authorization': `Bearer ${this.notionToken}`, 'Content-Type': 'application/json', 'Notion-Version': '2022-06-28' }; } // MCP 工具定義 getTools() { return [ { name: "add_knowledge", description: "添加新知識到程式開發知識庫", inputSchema: { type: "object", properties: { title: { type: "string", description: "知識條目標題" }, content: { type: "string", description: "詳細內容,支援 Markdown 格式" }, project: { type: "string", description: "專案分類", enum: ["Web應用", "Mobile應用", "後端API", "DevOps工具", "數據分析", "其他"], default: "其他" }, type: { type: "string", description: "知識類型", enum: ["代碼片段", "解決方案", "錯誤記錄", "學習筆記", "配置文件", "最佳實踐"], default: "學習筆記" }, keywords: { type: "array", items: { type: "string" }, description: "關鍵字標籤", default: [] }, language: { type: "string", description: "程式語言", enum: ["JavaScript", "TypeScript", "Python", "Go", "Rust", "Java", "HTML/CSS", "SQL", "Shell", ""], default: "" }, importance: { type: "string", description: "重要程度", enum: ["高", "中", "低"], default: "中" }, file_path: { type: "string", description: "相關檔案路徑(可選)", default: "" } }, required: ["title", "content"] } }, { name: "search_knowledge", description: "在知識庫中搜索相關內容", inputSchema: { type: "object", properties: { query: { type: "string", description: "搜索關鍵字" }, project_filter: { type: "string", description: "按專案過濾", enum: ["", "Web應用", "Mobile應用", "後端API", "DevOps工具", "數據分析", "其他"], default: "" }, type_filter: { type: "string", description: "按知識類型過濾", enum: ["", "代碼片段", "解決方案", "錯誤記錄", "學習筆記", "配置文件", "最佳實踐"], default: "" }, limit: { type: "integer", description: "返回結果數量", default: 10 } }, required: ["query"] } }, { name: "get_recent_knowledge", description: "獲取最近的知識條目", inputSchema: { type: "object", properties: { limit: { type: "integer", description: "返回數量", default: 5 } } } }, { name: "get_knowledge_stats", description: "獲取知識庫統計信息", inputSchema: { type: "object", properties: {} } } ]; } // 添加知識條目 async addKnowledge(params) { try { const { title, content, project = "其他", type = "學習筆記", keywords = [], language = "", importance = "中", file_path = "" } = params; const keywordOptions = keywords.length > 0 ? keywords.map(kw => ({ name: kw })) : []; const data = { parent: { database_id: this.databaseId }, properties: { "標題": { title: [{ text: { content: title } }] }, "專案名稱": { select: { name: project } }, "知識類型": { select: { name: type } }, "重要程度": { select: { name: importance } } }, children: [{ object: "block", type: "paragraph", paragraph: { rich_text: [{ type: "text", text: { content: content } }] } }] }; if (keywordOptions.length > 0) { data.properties["關鍵字"] = { multi_select: keywordOptions }; } if (language) { data.properties["程式語言"] = { select: { name: language } }; } if (file_path) { data.properties["檔案路徑"] = { rich_text: [{ text: { content: file_path } }] }; } const response = await fetch('https://api.notion.com/v1/pages', { method: 'POST', headers: this.headers, body: JSON.stringify(data) }); if (response.ok) { const result = await response.json(); return `✅ 知識已成功保存到 Notion 📄 標題: ${title} 📁 專案: ${project} 📋 類型: ${type} 🔗 連結: ${result.url}`; } else { const errorText = await response.text(); return `❌ 保存失敗: ${response.status} - ${errorText}`; } } catch (error) { return `❌ 添加知識時發生錯誤: ${error.message}`; } } // 搜索知識 async searchKnowledge(params) { try { const { query, project_filter = "", type_filter = "", limit = 10 } = params; const filters = []; if (project_filter) { filters.push({ property: "專案名稱", select: { equals: project_filter } }); } if (type_filter) { filters.push({ property: "知識類型", select: { equals: type_filter } }); } const searchData = { page_size: Math.min(limit, 100), sorts: [{ property: "最後修改", direction: "descending" }] }; if (filters.length > 0) { searchData.filter = filters.length === 1 ? filters[0] : { and: filters }; } const response = await fetch(`https://api.notion.com/v1/databases/${this.databaseId}/query`, { method: 'POST', headers: this.headers, body: JSON.stringify(searchData) }); if (response.ok) { const data = await response.json(); const filtered = data.results.filter(result => { const title = this.extractTitle(result); return title.toLowerCase().includes(query.toLowerCase()); }); return this.formatSearchResults(filtered, query, { project_filter, type_filter }); } else { const errorText = await response.text(); return `❌ 搜索失敗: ${response.status} - ${errorText}`; } } catch (error) { return `❌ 搜索時發生錯誤: ${error.message}`; } } // 獲取最近的知識條目 async getRecentKnowledge(params) { try { const { limit = 5 } = params; const searchData = { page_size: Math.min(limit, 20), sorts: [{ property: "最後修改", direction: "descending" }] }; const response = await fetch(`https://api.notion.com/v1/databases/${this.databaseId}/query`, { method: 'POST', headers: this.headers, body: JSON.stringify(searchData) }); if (response.ok) { const data = await response.json(); return this.formatRecentResults(data.results); } else { const errorText = await response.text(); return `❌ 獲取最近知識失敗: ${response.status} - ${errorText}`; } } catch (error) { return `❌ 獲取最近知識時發生錯誤: ${error.message}`; } } // 獲取統計信息 async getKnowledgeStats() { try { const response = await fetch(`https://api.notion.com/v1/databases/${this.databaseId}/query`, { method: 'POST', headers: this.headers, body: JSON.stringify({ page_size: 100 }) }); if (response.ok) { const data = await response.json(); return this.formatStats(data.results); } else { const errorText = await response.text(); return `❌ 獲取統計失敗: ${response.status} - ${errorText}`; } } catch (error) { return `❌ 獲取統計時發生錯誤: ${error.message}`; } } // 輔助方法 extractTitle(result) { return result.properties["標題"]?.title?.[0]?.text?.content || "無標題"; } extractSelectValue(result, property) { return result.properties[property]?.select?.name || "未設定"; } extractMultiSelectValues(result, property) { return result.properties[property]?.multi_select?.map(item => item.name) || []; } formatSearchResults(results, query, filters) { if (!results || results.length === 0) { return `🔍 搜索 "${query}" 沒有找到相關結果`; } let output = [`🔍 搜索 "${query}" 找到 ${results.length} 個結果:\n`]; results.forEach((result, index) => { const title = this.extractTitle(result); const project = this.extractSelectValue(result, "專案名稱"); const type = this.extractSelectValue(result, "知識類型"); const keywords = this.extractMultiSelectValues(result, "關鍵字"); output.push(`${index + 1}. 📄 ${title}`); output.push(` 📁 專案: ${project} | 📋 類型: ${type}`); if (keywords.length > 0) output.push(` 🏷️ 標籤: ${keywords.join(", ")}`); output.push(` 🔗 連結: ${result.url}`); output.push(""); }); return output.join("\n"); } formatRecentResults(results) { if (!results || results.length === 0) { return `📅 最近的知識條目: 沒有找到結果`; } let output = [`📅 最近的知識條目 (${results.length} 個):\n`]; results.forEach((result, index) => { const title = this.extractTitle(result); const project = this.extractSelectValue(result, "專案名稱"); const type = this.extractSelectValue(result, "知識類型"); output.push(`${index + 1}. 📄 ${title}`); output.push(` 📁 ${project} | 📋 ${type}`); output.push(` 🔗 ${result.url}`); output.push(""); }); return output.join("\n"); } formatStats(results) { if (!results || results.length === 0) { return "📊 知識庫統計: 暫無數據"; } const stats = { total: results.length, byProject: {}, byType: {}, byLanguage: {} }; results.forEach(result => { const project = this.extractSelectValue(result, "專案名稱"); const type = this.extractSelectValue(result, "知識類型"); const language = this.extractSelectValue(result, "程式語言"); stats.byProject[project] = (stats.byProject[project] || 0) + 1; stats.byType[type] = (stats.byType[type] || 0) + 1; if (language !== "未設定") { stats.byLanguage[language] = (stats.byLanguage[language] || 0) + 1; } }); let output = [ "📊 程式開發知識庫統計報告", "==============================", "", `📈 總計: ${stats.total} 個知識條目`, "", "📁 按專案分布:" ]; Object.entries(stats.byProject) .sort(([,a], [,b]) => b - a) .forEach(([project, count]) => { output.push(` • ${project}: ${count} 個`); }); output.push(""); output.push("📋 按類型分布:"); Object.entries(stats.byType) .sort(([,a], [,b]) => b - a) .forEach(([type, count]) => { output.push(` • ${type}: ${count} 個`); }); if (Object.keys(stats.byLanguage).length > 0) { output.push(""); output.push("💻 按程式語言分布:"); Object.entries(stats.byLanguage) .sort(([,a], [,b]) => b - a) .forEach(([language, count]) => { output.push(` • ${language}: ${count} 個`); }); } return output.join("\n"); } // 處理 MCP 工具調用 async handleToolCall(toolName, params) { switch (toolName) { case "add_knowledge": return await this.addKnowledge(params); case "search_knowledge": return await this.searchKnowledge(params); case "get_recent_knowledge": return await this.getRecentKnowledge(params); case "get_knowledge_stats": return await this.getKnowledgeStats(); default: return `❌ 未知工具: ${toolName}`; } } } // Cloudflare Workers 主處理器 export default { async fetch(request, env, ctx) { const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }; if (request.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } try { if (!env.NOTION_TOKEN) { return new Response(JSON.stringify({ error: "Missing NOTION_TOKEN environment variable. Please set it in Workers settings." }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } const server = new NotionMCPServer(env); const url = new URL(request.url); // 健康檢查端點 if (url.pathname === '/health') { return new Response(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString(), database_id: server.databaseId, server_url: request.url.replace(url.pathname, '') }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } // 工具列表端點 if (url.pathname === '/tools') { return new Response(JSON.stringify({ tools: server.getTools() }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } // MCP 工具調用端點 if (url.pathname === '/call' && request.method === 'POST') { const { method, params } = await request.json(); const result = await server.handleToolCall(method, params); return new Response(JSON.stringify({ result, timestamp: new Date().toISOString() }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } // 默認回應 return new Response(` 🎉 Notion Knowledge MCP Server 運行正常! ========================================== 📊 服務狀態: ✅ 正常運行 🔗 服務網址: ${request.url.replace(url.pathname, '')} 📅 當前時間: ${new Date().toISOString()} 💾 資料庫 ID: ${server.databaseId} 📡 可用 API 端點: • GET /health - 健康檢查和狀態信息 • GET /tools - MCP 工具列表 • POST /call - MCP 工具調用接口 🛠️ 可用工具: • add_knowledge - 添加新知識到 Notion 知識庫 • search_knowledge - 搜索現有知識內容 • get_recent_knowledge - 獲取最近添加的知識 • get_knowledge_stats - 查看知識庫統計信息 💡 使用方式: 在 Claude 中直接說話即可,例如: "請搜索關於 React 的知識" "請將這段代碼保存到知識庫" "請顯示知識庫統計信息" 🔧 GitHub 倉庫: https://github.com/etjang10/notion-knowledge-mcp powered by Cloudflare Workers ⚡ `.trim(), { headers: { ...corsHeaders, 'Content-Type': 'text/plain; charset=utf-8' } }); } catch (error) { return new Response(JSON.stringify({ error: error.message, timestamp: new Date().toISOString(), help: "請檢查 NOTION_TOKEN 環境變數是否正確設定" }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } } };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/YuHuanHsu/notion-knowledge-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server