[{"data":1,"prerenderedAt":788},["ShallowReactive",2],{"news-item-\u002Fvi\u002Fnews\u002Fmcp-in-practice-building-an-ai-agent-that-organizes-files":3},{"id":4,"title":5,"body":6,"category":773,"created by":776,"date":777,"description":778,"extension":779,"meta":780,"navigation":781,"path":782,"sections":783,"seo":784,"stem":785,"thumbnail":786,"__hash__":787},"content_vi\u002Fvi\u002Fnews\u002Fmcp-in-practice-building-an-ai-agent-that-organizes-files.md","MCP trong thực tế: xây một AI Agent tổ chức file",{"type":7,"value":8,"toc":756},"minimark",[9,14,18,27,35,38,41,45,48,51,70,86,90,93,97,103,106,109,126,130,136,139,146,150,156,160,165,171,178,181,185,192,201,227,233,242,249,252,274,278,287,312,317,323,326,332,337,347,353,360,366,369,375,379,392,396,402,406,412,416,422,426,432,435,439,444,454,457,463,471,485,488,492,498,501,505,511,514,518,524,527,531,537,541,544,568,576,582,585,589,592,595,599,602,612,619,632,642,651,654,657,663,666,673,677,680,684],[10,11,13],"h2",{"id":12},"giới-thiệu","Giới thiệu",[15,16,17],"p",{},"Một LLM có thể trả lời câu hỏi, nhưng không tự biết cách đọc một thư mục hay gọi API nội bộ. Mỗi khả năng cần một điểm tích hợp để mô tả tool, kiểm tra input, gọi hệ thống thật và đưa kết quả trở lại cuộc hội thoại. Nếu mỗi ứng dụng AI tự kết nối với từng hệ thống, n ứng dụng và m hệ thống có thể tạo ra tới n × m adapter phải viết và bảo trì.",[19,20],"img",{"className":21,"alt":24,"src":25,"style":26},[22,23],"block","mx-auto","","https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage3.png","width: 100%;",[15,28,29],{},[30,31,34],"em",{"className":32},[33],"caption","Sơ đồ tổng quan về cách MCP kết nối AI Application với các hệ thống bên ngoài: API, Slack, database, GitHub, Gmail, file system.",[15,36,37],{},"Model Context Protocol (MCP) đặt ra một hợp đồng chung giữa hai phía. MCP Server công bố khả năng cùng schema input\u002Foutput; ứng dụng AI tương thích có thể khám phá và gọi chúng theo cùng một giao thức. Business logic và cách gọi API thật nằm trên server. Host quyết định tool nào được đưa cho model và cách xin xác nhận; server vẫn phải kiểm tra quyền đối với thao tác nó nhận được. Tài liệu của MCP ví cách chuẩn hóa kết nối này giống với USB-C.",[15,39,40],{},"Bài này kiểm tra ý tưởng đó bằng một demo chạy được: một agent dùng hai tool MCP để tổ chức 500 file mock theo phần mở rộng và ngày sửa đổi, mà không đọc nội dung bên trong file.",[10,42,44],{"id":43},"mcp-là-gì-một-giao-thức-không-phải-một-ai-model","MCP là gì: một giao thức, không phải một AI model",[15,46,47],{},"MCP là một đặc tả mở cho message trao đổi giữa ứng dụng AI và chương trình cung cấp dữ liệu hoặc hành động. Nó chuẩn hóa cách hai phía thỏa thuận phiên bản, công bố khả năng, mô tả input bằng schema, gọi tool và trả kết quả. MCP không thay thế API nghiệp vụ, không tự cấp quyền và cũng không quyết định tool nào nên được gọi.",[15,49,50],{},"Trong demo tổ chức file, một lượt làm việc diễn ra như sau:",[52,53,54,58,61,64,67],"ul",{},[55,56,57],"li",{},"Người dùng nhập yêu cầu vào ứng dụng AI, tức MCP Host.",[55,59,60],{},"Host tạo một MCP Client dành riêng cho File MCP Server và thiết lập kết nối.",[55,62,63],{},"Client lấy danh sách tool từ server qua tools\u002Flist; host chọn tool nào được đưa vào request gửi model.",[55,65,66],{},"Khi model chọn một tool, host gửi tools\u002Fcall qua client. Server kiểm tra input, thao tác trên file system và trả kết quả.",[55,68,69],{},"Host đưa kết quả của tool trở lại cuộc hội thoại để model quyết định bước tiếp theo.",[15,71,72,73,77,78,81,82,85],{},"Ba vai trò trong kiến trúc vì thế có ranh giới khá cụ thể. ",[74,75,76],"strong",{},"Host"," là ứng dụng người dùng đang mở, chẳng hạn Claude Desktop, Claude Code hoặc một IDE. ",[74,79,80],{},"Client"," là thành phần giao thức do host tạo ra. Mỗi client giữ một kết nối 1-1 với một server. ",[74,83,84],{},"Server"," là chương trình công bố khả năng và thực thi phần việc thật.",[10,87,89],{"id":88},"vì-sao-dùng-mcp-thay-vì-tích-hợp-api-tùy-chỉnh","Vì sao dùng MCP thay vì tích hợp API tùy chỉnh",[15,91,92],{},"So sánh trực quan nhất nằm ở số lần phải tích hợp.",[19,94],{"className":95,"alt":24,"src":96,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage13.png",[15,98,99],{},[30,100,102],{"className":101},[33],"So sánh số điểm tích hợp: tích hợp tùy chỉnh cần tối đa n × m adapter; MCP đưa chi phí gần về n + m.",[15,104,105],{},"Với cách làm tùy chỉnh, mỗi ứng dụng AI có thể cần một adapter cho từng API. Trong mô hình đơn giản, n ứng dụng và m API tạo ra tối đa n × m điểm tích hợp. Với MCP, mỗi hệ thống công bố một server theo chuẩn chung, còn mỗi host triển khai client một lần. Khi các thành phần thực sự dùng lại được, chi phí tích hợp gần với n + m hơn. Đây là cách ước lượng kiến trúc, không phải cam kết rằng mọi API chỉ cần đúng một server.",[15,107,108],{},"Đi kèm là vài lợi ích cụ thể:",[52,110,111,114,117,120,123],{},[55,112,113],{},"Server định nghĩa schema, kiểm tra input và thực thi business logic một lần cho mọi host kết nối tới nó.",[55,115,116],{},"Host hoặc adapter của model chuyển tool contract và kết quả sang định dạng mà provider yêu cầu.",[55,118,119],{},"Một MCP Server có thể phục vụ nhiều MCP Client thay vì gắn code gọi API vào một agent framework cụ thể.",[55,121,122],{},"Client khám phá tool hoặc resource tại runtime thay vì hard-code toàn bộ danh sách vào prompt.",[55,124,125],{},"Model chỉ thấy những tool mà host chọn đưa vào request. Giao diện xác nhận và permission policy phụ thuộc vào host, transport và cấu hình server.",[10,127,129],{"id":128},"use-case-minh-họa-một-inbox-không-dọn-suốt-một-năm","Use case minh họa: một Inbox không dọn suốt một năm",[15,131,132,135],{},[74,133,134],{},"Hình dung một thư mục không ai dọn trong cả năm trời — có thể là thư mục thật trên máy bạn: ảnh chụp màn hình, file Word họp hành, hóa đơn PDF, báo cáo doanh thu, vài file Excel, tất cả nằm lẫn lộn."," Không có agent, việc tổ chức lại đống này chỉ có hai lựa chọn: kéo-thả bằng tay từng file, hoặc viết một script phân loại cứng theo rule cố định (nếu đuôi file là X thì chuyển vào thư mục Y). Script đó vừa phải sửa lại mỗi khi thêm loại file mới, vừa khóa cứng vào một thư mục duy nhất — muốn dùng cho thư mục khác lại phải sửa code.",[15,137,138],{},"Demo trong bài dựng đúng tình huống đó: một script sinh 500 file mock vào một folder, ngày sửa đổi của mỗi file rải ngẫu nhiên trong 365 ngày gần nhất, mô phỏng một năm không dọn dẹp.",[15,140,141,142,145],{},"Người dùng đưa ra ",[74,143,144],{},"một câu lệnh tự nhiên kèm đường dẫn cần tổ chức."," Agent lấy danh sách file rồi gọi move_files; server tự chọn thư mục đích theo phần mở rộng và ngày sửa đổi, xếp từng file theo loại rồi theo năm\u002Ftháng\u002Fngày.",[19,147],{"className":148,"alt":24,"src":149,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage4.png",[15,151,152],{},[30,153,155],{"className":154},[33],"storage\u002Finbox trước khi xử lý: 500 file mock, nhiều loại đuôi lẫn lộn.",[10,157,159],{"id":158},"kiến-trúc","Kiến trúc",[19,161],{"className":162,"alt":24,"src":163,"style":164},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage11.png","width: 50%;",[15,166,167],{},[30,168,170],{"className":169},[33],"Luồng gọi trong demo: User → Custom MCP Host\u002FAgent (Gemini + MCP Client) → File MCP Server (list_files, move_files) → Local File Storage.",[15,172,173,177],{},[174,175,176],"code",{},"client.ts"," là custom host\u002Fagent application. Biến mcpClient bên trong nó giữ kết nối giao thức tới server; Gemini là model chọn tool và argument. File MCP Server liệt kê hoặc di chuyển file, còn file system lưu dữ liệu thật.",[15,179,180],{},"Server không có tool đọc nội dung file, nên nội dung hóa đơn, ảnh hoặc tài liệu không được gửi cho model. Phần lộ ra cho model chỉ là tên file, phần mở rộng và ngày sửa đổi.",[10,182,184],{"id":183},"xây-dựng-mcp-server-hai-tool-list_files-move_files-không-đọc-nội-dung-file","Xây dựng MCP Server: hai tool list_files, move_files — không đọc nội dung file",[15,186,187,188,191],{},"Server đăng ký hai tool. Snippet đầu giữ phần khai báo schema và handler của ",[174,189,190],{},"list_files","; các helper kiểm tra đường dẫn và đọc metadata nằm trong source đầy đủ.",[193,194,199],"pre",{"className":195,"code":197,"language":198},[196],"language-text","\u002F\u002F server\u002Fserver.ts\n\nimport { McpServer } from \"@modelcontextprotocol\u002Fserver\";\nimport { StdioServerTransport } from \"@modelcontextprotocol\u002Fserver\u002Fstdio\";\nimport { z } from \"zod\";\nimport fs from \"node:fs\u002Fpromises\";\nimport path from \"node:path\";\n\nconst server = new McpServer({ name: \"file-organizer\", version: \"1.0.0\" });\nserver.registerTool(\"list_files\", {\n  description: \"List direct child files and their modification dates.\",\n  inputSchema: z.object({ folder: z.string() }),\n}, async ({ folder }) => {\n  const root = resolveFolder(folder);\n  const entries = await fs.readdir(root, { withFileTypes: true });\n  const files = await Promise.all(entries.filter((entry) => entry.isFile()).map(async (entry) => ({\n    name: entry.name,\n    extension: extensionOf(entry.name),\n    modified: isoDateOf((await fs.stat(path.join(root, entry.name))).mtime),\n  })));\n  return { content: [{ type: \"text\", text: JSON.stringify(files) }] };\n});\n","text",[174,200,197],{"__ignoreMap":24},[15,202,203,206,207,210,211,214,215,218,219,222,223,226],{},[174,204,205],{},"move_files"," nhận một batch. ",[174,208,209],{},"to_folder"," là optional: bỏ trống thì server tự tính ",[174,212,213],{},"\u003Cextension>\u002F\u003Cyear>\u002F\u003Cmonth>\u002F\u003Cday>"," từ ngày sửa đổi của chính file đó, nên tiêu chí phân loại mặc định vẫn đúng dù câu lệnh của người dùng không nói ra. ",[174,216,217],{},"moveOneFile"," trong source đầy đủ thực hiện validation, tạo hard link theo cơ chế không ghi đè rồi xóa source; lỗi của một entry không dừng các entry còn lại. ",[174,220,221],{},"dry_run"," cho phép xem trước cả batch mà không chạm vào file system, và kết quả thật còn kèm một dòng ",[174,224,225],{},"summary"," đếm số file đã chuyển.",[193,228,231],{"className":229,"code":230,"language":198},[196],"server.registerTool(\"move_files\", {\n  description: \"Move files under organized\u002F without overwriting existing paths.\",\n  inputSchema: z.object({\n    folder: z.string(),\n    moves: z.array(z.object({ name: z.string(), to_folder: z.string().optional() })).min(1),\n    dry_run: z.boolean().optional(),\n  }),\n}, async ({ folder, moves, dry_run }) => {\n  const root = resolveFolder(folder);\n  const results = [];\n  for (const { name, to_folder } of moves) {\n    try {\n      const message = await moveOneFile(root, name, to_folder, dry_run);\n      results.push({ name, to_folder, ok: true, message });\n    } catch (error) {\n      results.push({ name, to_folder, ok: false, error: String(error) });\n    }\n  }\n  return { content: [{ type: \"text\", text: JSON.stringify({ results }) }] };\n});\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n",[174,232,230],{"__ignoreMap":24},[15,234,235],{},[236,237,241],"a",{"href":238,"rel":239},"https:\u002F\u002Fgithub.com\u002Fbwv-labs\u002Fmcp-file-organizer\u002Fblob\u002Fmain\u002Fserver\u002Fserver.ts",[240],"nofollow","Source đầy đủ của server",[15,243,244,245,248],{},"Hai dòng cuối là chỗ server thực sự lên sóng: ",[174,246,247],{},"StdioServerTransport"," khai báo rằng server nói chuyện qua stdin\u002Fstdout của chính process của nó, nên host chỉ cần biết cách spawn process là kết nối được.",[15,250,251],{},"Hai điểm thiết kế đáng chú ý. Thứ nhất, một MCP server tổ chức file không cần và không nên có khả năng đọc nội dung bên trong file người dùng — tên, phần mở rộng và ngày sửa đổi là đủ để phân loại. Thứ hai, folder là một đường dẫn tuyệt đối bất kỳ, không phải tên một subfolder cố định trong thư mục project — nó sẽ tổ chức được bất kỳ thư mục thật nào có trên máy.",[15,253,254,255,258,259,262,263,266,267,270,271,273],{},"Vì ",[174,256,257],{},"folder"," mở như vậy, server đọc thêm biến môi trường ",[174,260,261],{},"ALLOWED_ROOTS",": một danh sách đường dẫn tuyệt đối, phân cách bằng ",[174,264,265],{},";"," trên Windows và ",[174,268,269],{},":"," trên macOS\u002FLinux, và ",[174,272,257],{}," buộc phải nằm trong một trong các root đó. Nếu không set, server giữ hành vi mặc định của demo là nhận mọi đường dẫn tuyệt đối — chấp nhận được với dữ liệu mock, nhưng nên set khi trỏ vào dữ liệu thật.",[10,275,277],{"id":276},"xây-dựng-custom-hostagent-với-gemini","Xây dựng custom Host\u002FAgent với Gemini",[15,279,280,282,283,286],{},[174,281,176],{}," ghép custom host\u002Fagent, Gemini và một MCP Client trong cùng process. Gemini chọn tool; MCP Client chỉ đảm nhiệm kết nối giao thức tới File MCP Server. ",[74,284,285],{},"Gemini API có free tier"," tại thời điểm kiểm chứng, với quota phụ thuộc vào model và tài khoản.",[15,288,289,290,295,296,299,300,303,304,307,308,311],{},"Tạo API key tại ",[236,291,294],{"href":292,"rel":293},"https:\u002F\u002Faistudio.google.com\u002F",[240],"Google AI Studio",", lưu vào ",[174,297,298],{},"GEMINI_API_KEY"," trong ",[174,301,302],{},".env",". ",[174,305,306],{},"MODEL"," là tùy chọn; code mặc định dùng ",[174,309,310],{},"gemini-2.5-flash",".",[19,313],{"className":314,"alt":24,"src":315,"style":316},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage6.png","width: 80%;",[15,318,319],{},[30,320,322],{"className":321},[33],"Tạo API key Gemini tại Google AI Studio, đặt tên key và chọn project Gemini API.",[15,324,325],{},"Tiếp theo, custom host\u002Fagent tạo một MCP Client và kết nối với server qua stdio. Phần tính đường dẫn và giới hạn số tool call nằm trong source đầy đủ.",[193,327,330],{"className":328,"code":329,"language":198},[196],"\u002F\u002F client\u002Fclient.ts\n\nimport { Client } from \"@modelcontextprotocol\u002Fclient\";\nimport { StdioClientTransport } from \"@modelcontextprotocol\u002Fclient\u002Fstdio\";\n\nconst transport = new StdioClientTransport({\n  command: process.execPath,\n  args: [\"--import\", \"tsx\", serverPath],\n  env: { ALLOWED_ROOTS: process.env.ALLOWED_ROOTS ?? \"\" },\n});\nconst mcpClient = new Client({ name: \"file-organizer-agent\", version: \"1.0.0\" });\nawait mcpClient.connect(transport);\n",[174,331,329],{"__ignoreMap":24},[15,333,334],{},[30,335,336],{},"Môi trường kiểm chứng ngày 2026-08-27: Windows 11, Node.js 24.15.0, @modelcontextprotocol\u002Fclient 2.0.0, @modelcontextprotocol\u002Fserver 2.0.0, @google\u002Fgenai 2.17.0, Zod 4.4.3, tsx 4.23.12 và TypeScript 7.0.2. MCP TypeScript SDK v2 tách client\u002Fserver thành hai package; v1 dùng package gộp @modelcontextprotocol\u002Fsdk.",[15,338,339,342,343,346],{},[174,340,341],{},"mcpToTool"," chuyển các MCP tool đã khám phá sang interface mà Gemini dùng. Đây là integration riêng của ",[174,344,345],{},"@google\u002Fgenai"," và vẫn đang ở trạng thái experimental trong bản 2.17.0; SDK của provider khác có thể cần adapter hoặc vòng lặp tool-calling khác.",[193,348,351],{"className":349,"code":350,"language":198},[196],"import { GoogleGenAI, mcpToTool } from \"@google\u002Fgenai\";\n\nconst ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });\ntry {\n  const response = await ai.models.generateContent({\n    model: MODEL,\n    contents: USER_REQUEST,\n    config: {\n      tools: [mcpToTool(mcpClient)],\n      automaticFunctionCalling: { maximumRemoteCalls: MAX_TOOL_CALLS },\n    },\n  });\n  console.log(response.text);\n} finally {\n  await mcpClient.close();\n}\n",[174,352,350],{"__ignoreMap":24},[15,354,355],{},[236,356,359],{"href":357,"rel":358},"https:\u002F\u002Fgithub.com\u002Fbwv-labs\u002Fmcp-file-organizer\u002Fblob\u002Fmain\u002Fclient\u002Fclient.ts",[240],"Source đầy đủ của custom host\u002Fagent",[15,361,362,365],{},[74,363,364],{},"Claude Desktop và Claude Code là các MCP Host có thể đăng ký server này; nhưng nền tảng khác cũng có thể hỗ trợ MCP."," Khi đổi model provider, File MCP Server và tool contract có thể giữ nguyên, nhưng phần host\u002Fagent thường phải đổi SDK, API key, adapter và có thể cả vòng lặp tool-calling.",[15,367,368],{},"Muốn tổ chức một thư mục thật, chạy:",[193,370,373],{"className":371,"code":372,"language":198},[196],"npm run agent -- \"absolute_path\"\n",[174,374,372],{"__ignoreMap":24},[10,376,378],{"id":377},"chạy-demo-kết-quả-thật-trên-500-file","Chạy demo: kết quả thật trên 500 file",[15,380,381,382,384,385,387,388,391],{},"Lần chạy hoàn tất với 2 tool call: một lần ",[174,383,190],{}," và một lần ",[174,386,205],{},". Kết quả nằm dưới ",[174,389,390],{},"storage\u002Finbox\u002Forganized\u002F\u003Cextension>\u002F\u003Cyear>\u002F\u003Cmonth>\u002F\u003Cday>\u002F",". Các ảnh dưới đây là transcript và cấu trúc thư mục của lần chạy đó.",[19,393],{"className":394,"alt":24,"src":395,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage9.png",[15,397,398],{},[30,399,401],{"className":400},[33],"Terminal lúc bắt đầu chạy: lệnh npm run agent và lần gọi list_files đầu tiên.",[19,403],{"className":404,"alt":24,"src":405,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage12.png",[15,407,408],{},[30,409,411],{"className":410},[33],"Terminal lúc kết thúc: lần move_files cuối cùng, tổng 2 tool call và thông báo hoàn tất từ Gemini.",[19,413],{"className":414,"alt":24,"src":415,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage2.png",[15,417,418],{},[30,419,421],{"className":420},[33],"Thư mục organized sau khi chạy xong, được chia thành 7 phần mở rộng: xlsx, txt, png, pdf, jpg, docx, csv.",[19,423],{"className":424,"alt":24,"src":425,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage8.png",[15,427,428],{},[30,429,431],{"className":430},[33],"Một thư mục con cụ thể: organized\u002Fxlsx\u002F2026\u002F01\u002F29 chứa đúng 1 file khớp với ngày sửa đổi.",[15,433,434],{},"Gọn hơn nhiều rồi phải không? Khi tìm kiếm thì chỉ cần nhớ đuôi file và ngày sửa đổi, thế là xong.",[10,436,438],{"id":437},"đăng-ký-server-này-vào-claude-desktop-hoặc-claude-code","Đăng ký server này vào Claude Desktop hoặc Claude Code",[15,440,441,443],{},[174,442,176],{}," là một host tự viết cho demo. Một cách dùng khác là đăng ký file server đã build vào Claude Desktop hoặc Claude Code; host sẽ quản lý kết nối, tools\u002Flist và vòng lặp tool-calling.",[15,445,446,447,450,451,311],{},"Với Claude Desktop, mở Settings → Developer → Edit Config và sửa claude_desktop_config.json. Bản cài thông thường trên Windows dùng ",[174,448,449],{},"%APPDATA%\\\\Claude\\\\claude_desktop_config.json",". Bản Microsoft Store có thể dùng đường dẫn MSIX tại ",[174,452,453],{},"%LOCALAPPDATA%\\\\Packages\\\\\u003CPackageFamilyName>\\\\LocalCache\\\\Roaming\\\\Claude\\\\claude_desktop_config.json",[15,455,456],{},"Đường dẫn khai báo trong file này phải là đường dẫn tuyệt đối. Nếu chưa có file, hãy tạo mới:",[193,458,461],{"className":459,"code":460,"language":198},[196],"{\n  \"mcpServers\": {\n    \"file-organizer\": {\n      \"command\": \"node\",\n      \"args\": [\"absolute_path\u002Fdist\u002Fserver\u002Fserver.js\"],\n      \"env\": { \"ALLOWED_ROOTS\": \"C:\\\\Users\\\\you\\\\Downloads\" }\n    }\n  }\n}\n",[174,462,460],{"__ignoreMap":24},[15,464,465,467,468,311],{},[174,466,261],{}," phải chứa đúng thư mục sẽ tổ chức: ví dụ trên chỉ cho phép Downloads, nên nếu muốn thử trên dữ liệu mock của demo thì đổi sang đường dẫn tuyệt đối tới ",[174,469,470],{},"storage\\inbox",[15,472,473,474,477,478,480,481,484],{},"Claude Code, scope project dùng file ",[174,475,476],{},".mcp.json"," ở project root. Đường dẫn tương đối trong ",[174,479,476],{}," được resolve theo working directory của process Claude Code chứ không theo vị trí file, nên để đường dẫn tuyệt đối, hoặc dùng biến ",[174,482,483],{},"${CLAUDE_PROJECT_DIR:-.}\u002Fdist\u002Fserver\u002Fserver.js"," mà Claude Code set sẵn cho server subprocess.",[15,486,487],{},"Kết quả với Claude Desktop",[19,489],{"className":490,"alt":24,"src":491,"style":316},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage5.png",[15,493,494],{},[30,495,497],{"className":496},[33],"Claude Desktop: bật connector file-organizer trong mục Connectors.",[15,499,500],{},"Kết quả với Claude Code",[19,502],{"className":503,"alt":24,"src":504,"style":316},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage10.png",[15,506,507],{},[30,508,510],{"className":509},[33],"Claude Code: MCP server file-organizer hiện trạng thái Connected qua .mcp.json.",[15,512,513],{},"Tái hiện tại thư mục Downloads:",[19,515],{"className":516,"alt":24,"src":517,"style":316},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage7.png",[15,519,520],{},[30,521,523],{"className":522},[33],"Thư mục Downloads thật trên máy, dùng để thử lại demo ngoài dữ liệu mock.",[15,525,526],{},"Claude tự tìm tool cần dùng từ MCP server:",[19,528],{"className":529,"alt":24,"src":530,"style":316},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage1.png",[15,532,533],{},[30,534,536],{"className":535},[33],"Claude tự chọn gọi tool List files từ file-organizer và xin quyền trước khi đọc thư mục Downloads.",[10,538,540],{"id":539},"điều-mcp-thực-sự-chuẩn-hóa","Điều MCP thực sự chuẩn hóa",[15,542,543],{},"Demo cho thấy ba phần được chuẩn hóa:",[52,545,546,549,552],{},[55,547,548],{},"Discovery: client lấy tool và schema khi chạy qua tools\u002Flist.",[55,550,551],{},"Invocation: client gọi mọi tool qua tools\u002Fcall và nhận kết quả theo cấu trúc MCP.",[55,553,554,555,557,558,560,561,564,565,311],{},"Ranh giới triển khai: agent phụ thuộc vào ",[174,556,190],{},", ",[174,559,205],{},", không phụ thuộc trực tiếp vào ",[174,562,563],{},"fs.readdir"," hay ",[174,566,567],{},"fs.rename",[15,569,570,571,557,573,575],{},"Xét về mặt kiến trúc — backend phía sau ",[174,572,190],{},[174,574,205],{}," có thể đổi từ file system cục bộ sang Google Drive, OneDrive hay S3 mà cách agent hiểu và gọi tool không đổi:",[193,577,580],{"className":578,"code":579,"language":198},[196],"                MCP Server\n                    │\n        ┌───────────┼───────────┐\n        ▼           ▼           ▼\n   Google Drive   OneDrive      S3\n",[174,581,579],{"__ignoreMap":24},[15,583,584],{},"MCP tách việc agent hiểu hợp đồng tool khỏi cách server triển khai tool.",[10,586,588],{"id":587},"khi-nào-mcp-đáng-hơn-chi-phí-tích-hợp","Khi nào MCP đáng hơn chi phí tích hợp",[15,590,591],{},"MCP phù hợp khi một khả năng cần được nhiều host, model hoặc đội phát triển dùng lại; danh sách tool cần được khám phá lúc chạy; hoặc server cần giữ ranh giới giữa quyền và business logic độc lập với agent. Ví dụ gồm tra cứu tồn kho từ ERP, lấy trạng thái vận đơn, truy vấn báo cáo nội bộ và tạo ticket.",[15,593,594],{},"Nếu chỉ có một ứng dụng gọi một API đơn giản, adapter trực tiếp thường ít code hơn. Nếu bài toán hoàn toàn xác định như \"xếp file theo phần mở rộng\", một script chạy thẳng vẫn rẻ và dễ dự đoán hơn việc dựng cả một agent để tự quyết định — kể cả khi agent đó đã dùng tool batch như move_files.",[10,596,598],{"id":597},"hướng-dẫn-tự-chạy-lại-demo-này","Hướng dẫn tự chạy lại demo này",[15,600,601],{},"Cách trực tiếp nhất để tự trải nghiệm demo là đăng ký server vào một MCP host có sẵn — Claude Desktop hoặc Claude Code — rồi gõ yêu cầu bằng ngôn ngữ tự nhiên trong khung chat, đúng như cách MCP được dùng trong thực tế. Các bước sau áp dụng trên Windows với Node.js LTS.",[15,603,604,605,608,609,311],{},"1. Clone repo rồi mở PowerShell tại thư mục vừa clone: ",[174,606,607],{},"git clone https:\u002F\u002Fgithub.com\u002Fbwv-labs\u002Fmcp-file-organizer.git",", sau đó ",[174,610,611],{},"cd mcp-file-organizer",[15,613,614,615,618],{},"2. Chạy ",[174,616,617],{},"npm ci"," để cài đúng version trong lockfile.",[15,620,621,622,608,625,628,629,311],{},"3. Chạy ",[174,623,624],{},"npm run test",[174,626,627],{},"npm run generate-mock"," để tạo 500 file trong ",[174,630,631],{},"storage\u002Finbox",[15,633,634,635,638,639,311],{},"4. Chạy ",[174,636,637],{},"npm run build"," để tạo ",[174,640,641],{},"dist\u002Fserver\u002Fserver.js",[15,643,644,645,647,648,650],{},"5. Đăng ký file JavaScript đã build theo mục trên, và đặt ",[174,646,261],{}," bằng đúng thư mục sẽ tổ chức — ở đây là đường dẫn tuyệt đối tới ",[174,649,470],{}," của repo.",[15,652,653],{},"6. Khởi động lại host hoặc mở phiên mới, rồi yêu cầu tổ chức đúng thư mục mock.",[15,655,656],{},"Ví dụ prompt:",[193,658,661],{"className":659,"code":660,"language":198},[196],"Tổ chức tất cả file trong D:\\path\\to\\mcp-file-organizer\\storage\\inbox\n",[174,662,660],{"__ignoreMap":24},[15,664,665],{},"Kiểm tra permission đang áp dụng trên host trước khi cho phép tool ghi file. Sau khi chạy, xem kết quả tại storage\u002Finbox\u002Forganized.",[15,667,668],{},[236,669,672],{"href":670,"rel":671},"https:\u002F\u002Fgithub.com\u002Fbwv-labs\u002Fmcp-file-organizer",[240],"Source code",[10,674,676],{"id":675},"kết-luận","Kết luận",[15,678,679],{},"MCP giải quyết vấn đề nêu ở đầu bài: thay vì mỗi cặp ứng dụng–API cần một adapter riêng, một MCP Server có thể phục vụ nhiều host tương thích, đưa chi phí tích hợp gần từ n × m về n + m khi các thành phần thực sự được dùng lại. Một script cố định vẫn đơn giản hơn cho rule phân loại ổn định. Giá trị của demo nằm ở chỗ cùng một file MCP Server phục vụ được custom host\u002Fagent dùng Gemini và Claude Desktop mà không đổi contract của list_files và move_files.",[10,681,683],{"id":682},"tài-liệu-tham-khảo","Tài liệu tham khảo",[52,685,686,693,700,707,714,721,728,735,742,749],{},[55,687,688],{},[236,689,692],{"href":690,"rel":691},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fintroduction",[240],"What is MCP?",[55,694,695],{},[236,696,699],{"href":697,"rel":698},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002F2026-07-28\u002Flearn\u002Farchitecture",[240],"Architecture overview",[55,701,702],{},[236,703,706],{"href":704,"rel":705},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002F2026-07-28\u002Fdevelop\u002Fbuild-server",[240],"Build an MCP server",[55,708,709],{},[236,710,713],{"href":711,"rel":712},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002F2026-07-28\u002Fdevelop\u002Fbuild-client",[240],"Build an MCP client",[55,715,716],{},[236,717,720],{"href":718,"rel":719},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002F2026-07-28\u002Fdevelop\u002Fconnect-local-servers",[240],"Connect to local MCP servers",[55,722,723],{},[236,724,727],{"href":725,"rel":726},"https:\u002F\u002Fcode.claude.com\u002Fdocs\u002Fen\u002Fmcp",[240],"Claude Code: connect to tools via MCP",[55,729,730],{},[236,731,734],{"href":732,"rel":733},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@google\u002Fgenai",[240],"google\u002Fgenai on npm (Google Gen AI SDK for TypeScript\u002FJavaScript)",[55,736,737],{},[236,738,741],{"href":739,"rel":740},"https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Ftypescript-sdk\u002Fblob\u002Fmain\u002Fdocs\u002Fmigration\u002Fupgrade-to-v2.md",[240],"MCP TypeScript SDK: upgrading from v1 to v2",[55,743,744],{},[236,745,748],{"href":746,"rel":747},"https:\u002F\u002Fai.google.dev\u002Fgemini-api\u002Fdocs\u002Fpricing",[240],"Gemini Developer API pricing",[55,750,751],{},[236,752,755],{"href":753,"rel":754},"https:\u002F\u002Fgoogleapis.github.io\u002Fjs-genai\u002Frelease_docs\u002Findex.html",[240],"@google\u002Fgenai API reference",{"title":24,"searchDepth":757,"depth":757,"links":758},2,[759,760,761,762,763,764,765,766,767,768,769,770,771,772],{"id":12,"depth":757,"text":13},{"id":43,"depth":757,"text":44},{"id":88,"depth":757,"text":89},{"id":128,"depth":757,"text":129},{"id":158,"depth":757,"text":159},{"id":183,"depth":757,"text":184},{"id":276,"depth":757,"text":277},{"id":377,"depth":757,"text":378},{"id":437,"depth":757,"text":438},{"id":539,"depth":757,"text":540},{"id":587,"depth":757,"text":588},{"id":597,"depth":757,"text":598},{"id":675,"depth":757,"text":676},{"id":682,"depth":757,"text":683},[774,775],"AI","tech talk","Thai Phan Quang","2026-09-07","Giới thiệu. Một LLM có thể trả lời câu hỏi, nhưng không tự biết cách đọc một thư mục hay gọi API nội bộ. Mỗi khả năng cần một điểm tích hợp để mô tả tool, kiểm tra input, gọi hệ thống thật và đưa kết quả trở lại cuộc hội thoại.","md",{},true,"\u002Fvi\u002Fnews\u002Fmcp-in-practice-building-an-ai-agent-that-organizes-files",null,{"title":5,"description":778},"vi\u002Fnews\u002Fmcp-in-practice-building-an-ai-agent-that-organizes-files","https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fthumbnail.png","mYcoyV3We92v2x8muh240kNidhCSACoA_bWOqHE2MVQ",1789638846281]