AI Không Thể Đọc File Của Tôi - Tôi Đã Tự Xây Một MCP Server "Zero-Dependency"
Chán ngấy việc copy-paste code vào chat AI để nhận phản hồi, tác giả đã xây dựng một máy chủ MCP bằng Python thuần túy mà không cần bất kỳ thư viện bên ngoài nào. Giải pháp này cho phép các công cụ AI truy cập trực tiếp vào dự án cục bộ với tốc độ dưới 50ms và mô hình bảo mật nghiêm ngặt.

Tôi đang trong quá trình tái cấu trúc (refactor) một bộ scraper. Các hàm đã trở nên quá dài dòng và tên biến thì không còn ý nghĩa nữa. Mỗi lần muốn nhận phản hồi về một file, tôi lại phải dừng lại, mở cửa sổ chat, copy toàn bộ nội dung vào đó và chờ đợi. Sau đó quay lại trình soạn thảo, áp dụng thay đổi, mở file tiếp theo và lặp lại quy trình.
Tôi đã đếm lần thử. Sáu file. Mười một lần dán. Hai mươi phút chuyển đổi qua lại trước khi viết được một dòng code mới.
Giải pháp hiển nhiên là cấp quyền truy cập trực tiếp vào thư mục dự án cho công cụ AI. Đó chính là lúc tôi tìm thấy MCP (Model Context Protocol) — một giao thức được xây dựng chính xác cho mục đích này. Một máy chủ chạy cục bộ, cung cấp các công cụ, và client AI sẽ gọi các công cụ đó trực tiếp thay vì đợi tôi copy-paste.
Tuy nhiên, khi nhìn vào các triển khai hiện có, phần lớn đều yêu cầu FastAPI, uvicorn, LangChain hoặc SDK chính thức của MCP. Chưa viết được một dòng logic nghiệp vụ nào, tôi đã thấy năm gói phụ thuộc trong tệp yêu cầu (requirements) và một máy chủ mà tôi không chắc chắn sẽ chạy ổn định trên Windows.
Tôi đã lùi lại và đọc kỹ đặc tả kỹ thuật của MCP. Giao thức này thực chất chỉ là JSON-RPC 2.0 trên một lớp truyền tải. Một đối tượng JSON trên mỗi dòng. Client gửi, server phản hồi. Đặc tả chỉ định chính xác hai phương thức truyền tải: stdio cho kết nối đơn lẻ cục bộ và HTTP với Server-Sent Events (SSE) cho nhiều client đồng thời.
Đó là toàn bộ giao thức.
Tôi tự đặt câu hỏi khác: Điều này thực sự cần những gì mà thư viện chuẩn của Python chưa cung cấp? sys.stdin, sys.stdout, http.server, threading, queue, pathlib, json. Chỉ có vậy. Không cần cài đặt pip nào cả.
Bài viết này là về triển khai đó — cả hai phương thức truyền tải, mô hình bảo mật sản phẩm, 50 bài kiểm thử và các số liệu hiệu quả khi chạy.
Tại sao MCP lại quan trọng?
Model Context Protocol là một cách chuẩn hóa để các client AI (như Claude hay ChatGPT) gọi các công cụ trên các máy chủ bên ngoài. Thay vì dựa vào việc copy-paste, AI có thể truy cập và lý luận trực tiếp trên các tệp cục bộ.
Quy trình bắt tay (handshake) có ba giai đoạn: Client khởi tạo, hỏi xem có công cụ nào, sau đó bắt đầu gọi chúng.
Kiến trúc tổng quan của MCP
Hầu hết các triển khai hiện nay thường chọn một trong hai phương thức truyền tải. Tuy nhiên, phiên bản này triển khai cả hai với cùng một bộ điều phối (dispatcher) và cùng một bộ công cụ phía sau.
Kiến trúc: Bốn lớp hệ thống
Hệ thống được chia thành bốn lớp chính để đảm bảo tính mô đun và an toàn.
Kiến trúc chi tiết các lớp của MCP
- Lớp Bảo mật (Security Layer): Xác thực mọi đường dẫn trước khi thực hiện bất kỳ thao tác hệ thống tệp nào. Nó chạy đầu tiên, trên mọi lệnh gọi.
- Lớp Công cụ (Tools Layer): Bốn công cụ chính cho hệ thống tệp:
list_directory,read_file,search_files,get_file_info. - Bộ điều phối (Dispatcher): Một bộ định tuyến JSON-RPC không trạng thái. Nó phân tích phương thức, gọi trình xử lý đúng và trả về phản hồi.
- Lớp Truyền tải (Transport Layer): Hai triển khai.
StdioTransportcho client AI cục bộ vàHTTPSSETransportcho các kết nối đồng thời.
Điểm vào của chương trình chỉ cần một cờ (flag) để chọn phương thức truyền tải:
if args.http:
HTTPSSETransport(dispatcher, host=args.host, port=args.port).run()
else:
StdioTransport(dispatcher).run()
Mô hình bảo mật "Sandbox"
Vấn đề đầu tiên khi xây dựng một máy chủ đọc tệp cục bộ là làm thế nào để ngăn client đọc những tệp không được phép. Tấn công phổ biến nhất là path traversal — thay vì gửi README.md, client gửi ../../etc/passwd.
Giải pháp là giải quyết (resolve) cả hai đường dẫn hoàn toàn trước khi so sánh chúng:
target.resolve().relative_to(base.resolve())
Path.resolve() sẽ mở rộng tất cả các liên kết tượng trưng (symlinks) và thu gọn tất cả các đoạn ... relative_to() sẽ gây ra lỗi ValueError nếu kết quả nằm ngoài thư mục gốc (base). Không cần phân tích chuỗi thủ công, hệ điều hành tự giải quyết đường dẫn và Python kiểm tra kết quả.
Các bài kiểm tra bảo mật đã xác minh điều này trên mọi bản dựng:
- Tấn công
../../etc/passwd: Từ chối truy cập. - Symlink trỏ ra ngoài thư mục gốc: Từ chối truy cập.
- Đường dẫn UNC của Windows
\\server\share: Từ chối truy cập. - Tệp
src/main.pybên trong thư mục gốc: Cho phép.
Các công cụ chính
Máy chủ cung cấp bốn công cụ cốt lõi để AI tương tác với mã nguồn:
- list_directory: Liệt kê mọi thứ trong một thư mục — tên, loại, kích thước, thời gian sửa đổi. Thư mục được xếp trước tệp, các mục ẩn bị loại trừ mặc định.
- read_file: Đọc nội dung tệp. Giới hạn 1 MB. Tệp nhị phân được trả về dưới dạng base64 để tránh lỗi giải mã.
- search_files: Tìm tệp theo mẫu glob. Quan trọng: Tìm kiếm mặc định là nông (shallow), client phải chủ động truyền
recursive=Trueđể quét sâu. Điều này ngăn chặn việc máy chủ bị treo khi quét nhầm thư mục hệ thống lớn. - get_file_info: Trả về siêu dữ liệu mà không cần đọc nội dung — hữu ích khi client cần kiểm tra quyền trước khi quyết định đọc.
Hai phương thức truyền tải
1. Stdio (Cục bộ)
Đ thiết lập cục bộ, truyền tải stdio chỉ là một vòng lặp for line in self._stdin. Tôi đã bỏ qua async, threads và event loops để giữ mọi thứ đơn giản nhất có thể.
Một lỗi phổ biến trên Windows là chế độ văn bản mặc định của Python sẽ tự động chuyển đổi \n thành \r\n, làm hỏng luồng JSON. Giải pháp là đặt chế độ nhị phân (O_BINARY) cho stdin và stdout trên Windows.
2. HTTP/SSE (Đồng thời)
Mỗi client mở một kết nối GET /sse duy trì trong suốt phiên làm việc, cho phép máy chủ đẩy phản hồi xuống dưới dạng Server-Sent Events. Khi client cần gửi yêu cầu, nó sẽ thực hiện một yêu cầu POST /message riêng biệt.
Vòng đời truyền tải SSE trong MCP
Để xử lý đồng thời, mỗi client có hàng đợi tin nhắn riêng. Trình xử lý POST gửi kết quả trực tiếp vào hàng đợi của client đó và trả về mã trạng thái 202 ngay lập tức mà không cần chờ giao tiếp SSE hoàn tất.
Hiệu suất và Kết quả
Bản demo đồng thời cho thấy khả năng thực tế của phương thức truyền tải HTTP/SSE:
- 5 client đồng thời.
- 5 lệnh gọi công cụ khác nhau.
- Tổng thời gian thực thi: dưới 50ms.
Không có client nào chặn lẫn nhau. Được đo trên Windows 11, Python 3.12.6, chỉ dùng CPU.
Bài học về thiết kế phần mềm
Tôi từng nghĩ xây dựng máy chủ MCP rất phức tạp vì các hướng dẫn và thư viện hiện có làm cho nó trông có vẻ vậy. Nhưng khi đọc đặc tả thực tế, giao thức chỉ là một vòng lặp đơn giản: Đọc một dòng. Phân tích JSON. Gọi hàm. Viết một dòng.
Thư viện chuẩn của Python là đủ vì vấn đề thực sự nhỏ. Tôi không cần một framework khổng lồ. Tôi chỉ cần http.server cho kết nối TCP, threading cho yêu cầu song song, queue để tách biệt SSE khỏi xử lý POST, và pathlib để giải quyết đường dẫn.
Điều ngạc nhiên nhất là tầm quan trọng của các giá trị mặc định (defaults). Mọi thất bại thực tế trong codebase này — từ việc treo máy mười phút do quét đệ quy, đến việc đọc tệp 200 MB gây đóng băng, hay lỗi JSON trên Windows — đều đến từ một giá trị mặc định hoạt động tốt trong thử nghiệm nhưng phá vỡ hệ thống thực.
Mã nguồn đầy đủ của dự án "local-mcp-server" này đã được công bố trên GitHub, chứng minh rằng đôi khi sự đơn giản và hiệu quả đến từ việc quay về những nguyên tắc cơ bản nhất.



