my-server MCP Server

#!/usr/bin/env node /** * 這是一個 MCP 伺服器範本,用於實現一個簡單的筆記系統。 * 它展示了 MCP 的核心概念,如資源和工具,提供以下功能: * - 列出作為資源的筆記 * - 讀取單個筆記 * - 透過工具創建新筆記 * - 透過提示總結所有筆記 */ console.log("Starting server..."); import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { MongoClient, Collection } from "mongodb"; // MongoDB 連接設定 const MONGODB_URI = process.env.MONGODB_URI || "mongodb://mongodb.orb.local:27017"; const MONGODB_DB = process.env.MONGODB_DB || "MCP"; const MONGODB_COLLECTION = "notes"; let mongoClient: MongoClient; let notesCollection: Collection<Note>; // MongoDB 連接函數 async function connectToMongoDB() { try { mongoClient = new MongoClient(MONGODB_URI); await mongoClient.connect(); const db = mongoClient.db(MONGODB_DB); notesCollection = db.collection<Note>(MONGODB_COLLECTION); console.log("Successfully connected to MongoDB"); } catch (error) { console.error("Failed to connect to MongoDB:", error); process.exit(1); } } /** * 定義筆記物件的型別 */ type Note = { _id?: string; title: string; content: string; createdAt: Date; }; /** * 建立一個 MCP 伺服器,具有資源(用於列出/讀取筆記)、工具(用於創建新筆記)以及提示(用於總結筆記)的功能。 */ const server = new Server( { name: "my-server", version: "0.1.0", }, { capabilities: { resources: {}, tools: {}, prompts: {}, }, } ); /** * 處理列出可用筆記作為資源的請求。 * 每個筆記以資源的形式呈現,具有: * - note:// URI 格式 * - 純文字 MIME 類型 * - 具有人性化的名稱和描述(包含筆記標題) */ server.setRequestHandler(ListResourcesRequestSchema, async () => { const notesList = await notesCollection.find({}).toArray(); return { resources: notesList.map((note) => ({ uri: `note:///${note._id}`, mimeType: "text/plain", name: note.title, description: `A text note: ${note.title}`, })), }; }); /** * 處理讀取特定筆記內容的請求。 * 接收 note:// URI 並以純文字格式返回筆記內容。 */ server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const url = new URL(request.params.uri); const id = url.pathname.replace(/^\//, ""); const note = await notesCollection.findOne({ _id: id }); if (!note) { throw new Error(`Note ${id} not found`); } return { contents: [ { uri: request.params.uri, mimeType: "text/plain", text: note.content, }, ], }; }); /** * 處理列出可用工具的請求。 * 暴露一個 "create_note" 工具,讓客戶端能夠創建新筆記。 */ server.setRequestHandler(ListToolsRequestSchema, async () => { // 此請求處理器用於回應列出可用工具的請求 // 我們在此僅暴露一個工具 "create_note",用來讓客戶端創建新筆記 // 此工具定義包含三個部分: // 1. name:工具的名稱 // 2. description:對工具功能的文字描述 // 3. inputSchema:利用 JSON Schema 規範工具所需的輸入結構,確保客戶端提供的資料中, // 必須包含 title(筆記標題)和 content(筆記內容)這兩個字串型別的欄位 return { tools: [ { name: "create_note", // 工具名稱,代表此功能用於創建新筆記 description: "Create a new note", // 工具描述,解釋功能是用來創建新筆記 inputSchema: { type: "object", // 輸入資料必須為物件格式 properties: { title: { type: "string", // 標題必須是字串 description: "Title of the note", // 標題欄位的描述 }, content: { type: "string", // 筆記內容必須是字串 description: "Text content of the note", // 筆記內容欄位的描述 }, }, required: ["title", "content"], // 指定 title 與 content 為必填欄位 }, }, ], }; }); /** * 處理 "create_note" 工具的請求。 * 使用提供的標題和內容創建新筆記,並返回成功訊息。 */ // 下方代碼是用來處理工具呼叫請求,主要是處理 "create_note" 工具的邏輯。 // 這邊使用 switch...case 結構,可以根據 request 參數中的 name 來決定要執行哪一個工具的邏輯。 // 當收到 "create_note" 的請求,會檢查必填的 title 與 content,接著創建一個新筆記並寫入資料庫,然後返回建立成功的訊息。 server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case "create_note": { // 從 request 參數中擷取 title 與 content,並強制轉型為字串 const title = String(request.params.arguments?.title); const content = String(request.params.arguments?.content); // 若 title 或 content 為空,則拋出錯誤,告知使用者這兩個欄位皆為必填 if (!title || !content) { throw new Error("Title and content are required"); } // 建立一個新的筆記物件,其中包含標題、內容以及建立時間 const newNote = { title, content, createdAt: new Date(), // 使用當前時間記錄筆記的建立時間 }; // 將新的筆記寫入資料庫,並從結果中獲取新筆記的 _id const result = await notesCollection.insertOne(newNote); const id = result.insertedId.toString(); // 回傳包含建立成功訊息的回應,訊息中包含新筆記的 id 與標題 return { content: [ { type: "text", text: `Created note ${id}: ${title}`, }, ], }; } // 若請求的工具名稱不被支援,則拋出未知工具的錯誤 default: throw new Error("Unknown tool"); } }); /** * 處理列出可用提示的請求。 * 暴露一個 "summarize_notes" 提示,用於總結所有筆記。 */ server.setRequestHandler(ListPromptsRequestSchema, async () => { return { prompts: [ { name: "summarize_notes", description: "Summarize all notes", }, ], }; }); /** * 處理 "summarize_notes" 提示的請求。 * 返回一個提示,要求總結所有筆記,並將筆記內容嵌入為資源。 */ server.setRequestHandler(GetPromptRequestSchema, async (request) => { if (request.params.name !== "summarize_notes") { throw new Error("Unknown prompt"); } const embeddedNotes = await notesCollection.find({}).toArray(); const notesList = await notesCollection.find({}).toArray(); const embeddedNotesList = notesList.map((note) => ({ type: "resource" as const, resource: { uri: `note:///${note._id}`, mimeType: "text/plain", text: note.content, }, })); return { messages: [ { role: "user", content: { type: "text", text: "Please summarize the following notes:", }, }, ...embeddedNotesList.map((note) => ({ role: "user" as const, content: note, })), { role: "user", content: { type: "text", text: "Provide a concise summary of all the notes above.", }, }, ], }; }); /** * 使用 stdio 傳輸啟動伺服器。 * 這使得伺服器可以通過標準輸入/輸出流進行通訊。 */ async function main() { await connectToMongoDB(); const transport = new StdioServerTransport(); await server.connect(transport); } // 在程式結束時關閉 MongoDB 連接 process.on("SIGINT", async () => { if (mongoClient) { await mongoClient.close(); console.log("MongoDB connection closed."); } process.exit(0); }); main().catch((error) => { console.error("Server error:", error); process.exit(1); });