Một công cụ linh hoạt hơn trăm công cụ chuyên dụng: Tại sao CLI lại thắng thế?
Bài viết phân tích lý do tại sao giao diện dòng lệnh (CLI) lại vượt trội hơn các máy chủ MCP khi tương tác với các tác nhân AI. Thay vì sử dụng hàng loạt công cụ chuyên dụng gây lãng phí ngữ cảnh, việc cấp quyền truy cập terminal cho phép AI linh hoạt kết nối các lệnh, tiết kiệm token và xử lý dữ liệu hiệu quả hơn.

Vào đầu năm 2026, xu hướng mặc định khi muốn một tác nhân LLM (Large Language Model) giao tiếp với một hệ thống là cài đặt một máy chủ MCP (Model Context Protocol) cho nó. GitHub, Jira, Slack, Linear, Postgres, Neo4j... mỗi dịch vụ đều cung cấp một máy chủ exposing ra một danh sách các công cụ gọn gàng như create_issue, list_pull_requests, merge_pull_request, v.v.
Điều này mang lại trải nghiệm khởi đầu rất tốt. Tuy nhiên, đối với khá nhiều khối lượng công việc thực tế, đây lại là một thiết kế chưa tối ưu.
Luận điểm rất ngắn gọn: Thiết kế MCP thường bao bọc mỗi dịch vụ thành một chồng các công cụ chuyên dụng riêng lẻ; trong khi đó, một CLI (Giao diện dòng lệnh) trao cho tác nhân một công cụ duy nhất nhưng cực kỳ linh hoạt. Với các mô hình hiện nay, công cụ linh hoạt là người chiến thắng.
So sánh giữa cách tiếp cận MCP và CLI
Hai hình thức này yêu cầu mô hình thực hiện các công việc khác nhau. Với một chồng các công cụ chuyên dụng, tác nhân chỉ việc chọn đúng cái từ thực đơn. Với một công cụ linh hoạt, nó phải tự tìm cách lắp ghép các mảnh lại với nhau. Trước đây, phần thứ hai từng là điểm khó. Các mô hình thường bị ảo giác về các cờ (flag) lệnh, mất dấu trong các đường dẫn dài, hay đọc sai văn bản trợ giúp, nên việc bao bọc mỗi thao tác trong một công cụ được chuẩn bị sẵn là một sự phòng thủ hợp lý. Nhưng điều đó không còn đúng nữa.
Ngày nay, các mô hình có thể đọc trang --help hoặc tệp SKILL.md khi cần, biết các lệnh CLI chuẩn từ quá trình huấn luyện, xâu chuỗi các lệnh bash mà không cần giám sát và tự động thử lại khi gõ sai cờ. Phần khó đã trở nên dễ dàng, phần dễ vốn dĩ luôn dễ dàng, và tất cả những công cụ được gói gọn neat đó giờ đây chủ yếu chỉ làm phình to ngữ cảnh của mô hình mà không đem lại lợi ích.
Sự đánh đổi về bảo mật
Tất nhiên, không phải mọi thứ đều hoàn hảo. Trao cho tác nhân một terminal cũng đồng nghĩa với việc trao cho nó một phạm vi ảnh hưởng (blast radius) lớn hơn nhiều. Chính sự linh hoạt cho phép nó kết hợp gh | jq | xargs thành thứ hữu ích cũng cho phép một cuộc tấn công prompt injection dẫn nó làm những việc tồi tệ hơn nhiều so với một truy vấn Cypher thù địch.
Vì vậy, đây là một sự đánh đổi, và bạn thực sự phải cân nhắc kỹ: sandbox, danh sách cho phép (allowlist), người dùng hệ điều hành riêng, vai trò chỉ đọc tại cơ sở dữ liệu, những việc thông thường đó.
Nhưng khi bạn có thể cấp terminal cho tác nhân theo một cách tương đối an toàn, phía linh hoạt vẫn chiếm ưu thế.
CLI tỏa sáng ở đâu?
Mô hình "bao bọc một dịch vụ thành một chồng công cụ chuyên dụng" xuất hiện ở bất cứ đâu MCP có mặt. Postgres MCP so với psql. Kubernetes MCP so với kubectl. Filesystem MCP so với cat, ls, mv, grep được kết nối bởi các đường ống (pipes). Cùng một bản năng mỗi lần, cùng một đối thủ CLI mỗi lần. Và cùng ba chế độ thất bại, bởi chúng không thực sự liên quan đến bất kỳ sản phẩm cụ thể nào.
Không có gì trong đặc tả MCP thực sự yêu cầu cách tiếp cận xếp chồng các công cụ chuyên dụng này. Giao thức yêu cầu các công cụ được định kiểu, không hơn gì; nó không nói gì về việc mỗi công cụ phải hẹp đến mức nào. Các bản triển khai chỉ có xu hướng hướng tới nhiều công cụ nhỏ hẹp vì những lý do lịch sử. Bạn có thể xây dựng các công cụ linh hoạt chấp nhận một đầu vào biểu đạt duy nhất mà tác nhân có thể định hình theo ý muốn, và hầu hết thời gian bạn lẽ ra nên làm như vậy.
Để cụ thể hóa, chúng ta sẽ xem xét một ví dụ so sánh máy chủ Neo4j MCP với Neo4j CLI.
Lưu ý trước: Tôi làm việc tại Neo4j. Lựa chọn này chỉ vì sự tiện lợi, nhưng bài học áp dụng cho hầu hết các CLI khác.
Máy chủ Neo4j MCP là máy chủ chính thức exposing Neo4j cho các tác nhân thông qua MCP, cung cấp một số ít công cụ chuyên dụng như đọc truy vấn, ghi truy vấn và lấy lược đồ (schema). neo4j.sh là giao diện dòng lệnh chính thức cho Neo4j, một tệp nhị phân duy nhất bạn chạy trong terminal với các hồ sơ thông tin xác thực cho mỗi cơ sở dữ liệu bạn nói chuyện. Để giữ sự so sánh trung thực, chúng ta chỉ xem xét cặp đọc-truy-vấn và lược đồ ở phía MCP so với lời gọi truy vấn tương đương trong neo4j.sh. Cùng thao tác, cùng cơ sở dữ liệu, cùng lệnh Cypher được gửi đi. Điều duy nhất thay đổi là liệu tác nhân tiếp cận chúng qua lược đồ công cụ được định kiểu hay qua một chuỗi được đưa vào shell.
Truy vấn qua nhiều môi trường
Chúng ta đã thấy cách một chồng công cụ chuyên dụng ăn mòn cửa sổ ngữ cảnh (context window) với các mô tả, và rằng một số máy chủ hiện nay vận chuyển các công cụ trì hoãn để đẩy chi phí đó đến khi tác nhân thực sự với tới chúng. Nhưng có một hệ số nhân thứ hai mà ít người bàn đến: điều gì xảy ra khi bạn muốn nói chuyện với nhiều hơn một thực thể của cùng một dịch vụ. Với MCP, số lượng công cụ không chỉ tăng theo tính năng, nó tăng theo môi trường.
Kết nối nhiều cơ sở dữ liệu qua MCP hoặc CLI
Tác nhân muốn số lượng nút từ dev, staging và prod. Thông qua MCP, bạn dựng một neo4j-mcp-server cho mỗi môi trường, mỗi cái mang bốn lược đồ công cụ của nó vào ngữ cảnh của tác nhân ở mỗi lượt. Ba cơ sở dữ liệu là mười hai lược đồ trong cửa sổ của mô hình, cùng bốn lược đồ đó ba lần, trước khi tác nhân làm bất cứ điều gì.
Thông qua CLI, đó là một vòng lặp for:
$ for c in dev staging prod-ro; do
neo4j-cli query -c $c --format toon \
"MATCH (n) RETURN count(n) AS nodes"
done
Một tệp nhị phân, ba hồ sơ thông tin xác thực, chi phí ngữ cảnh bằng 0 mỗi lượt. Thêm môi trường thứ tư là thêm một dbms add thông tin xác thực nữa, không phải thêm một quá trình máy chủ MCP nữa. Cùng hình dạng đó áp dụng cho bất kỳ quy trình công việc "tiếp cận N thứ tương tự" nào bạn có thể muốn: snapshot prod trước một triển khai rủi ro, so sánh lược đồ giữa staging và prod, chạy kiểm tra sức khỏe trên mọi cơ sở dữ liệu mà tác nhân biết.
Chuỗi hóa truy vấn
Giả sử tác nhân đang điều tra một tài khoản gian lận đã biết: từ một hạt giống đơn lẻ, tìm mọi tài khoản mà nó giao dịch, sau đó tìm những tài khoản khác mà các đối tác đó giao dịch thường xuyên nhất. Hai truy vấn chống lại cùng một cơ sở dữ liệu, trong đó tham số của cái thứ hai là đầu ra của cái thứ nhất.
Chuỗi hóa các truy vấn
Thông qua MCP, mô hình phải là đường ống. Nó gọi read-cypher, kết quả quay lại dưới dạng danh sách, nói là 80 ID đối tác, 80 ID đó giờ nằm trong ngữ cảnh của mô hình, mô hình định dạng chúng thành tham số cho lệnh gọi read-cypher thứ hai, và chỉ khi đó truy vấn hai có thể chạy. Danh sách trung gian đi qua cuộc đối chuyện nguyên văn, và mọi ID thêm vào là một hàng ngữ cảnh khác mà tác nhân phải trả tiền dù nó có đọc lại hay không.
Thông qua CLI, đường ống là một | theo nghĩa đen:
$ neo4j-cli query -c prod-ro --format json \
--param "seed=acct_19f3" \
"MATCH (:Account {id: $seed})-[:TRANSACTED]-(c:Account)
WHERE c.id <> $seed
RETURN collect(DISTINCT c.id) AS counterparties" \
| neo4j-cli query -c prod-ro --params-from-stdin \
"MATCH (a:Account)-[:TRANSACTED]-(b:Account)
WHERE a.id IN $counterparties
AND NOT b.id IN $counterparties + ['acct_19f3']
RETURN b.id, count(DISTINCT a) AS edges_into_cluster
ORDER BY edges_into_cluster DESC LIMIT 20"
--params-from-stdin đọc kết quả JSON của truy vấn trước và liên kết nó như một tham số cho cái tiếp theo. Danh sách đối tác không bao giờ đi vào ngữ cảnh của mô hình, chi phí token của tác nhân giống nhau dù cụm có 5 đối tác hay 500.
Đây là lúc shell bắt đầu cảm thấy như một loại công cụ hoàn toàn khác. Tác nhân không còn chọn từ thực đơn các thao tác nữa, nó đang soạn các đường ống, và dữ liệu trung tâm không bao giờ cần nổi lên. Một truy vấn hai bước trở thành một |. Một fan-out trở thành một vòng lặp for. Một kết nối qua hai cơ sở dữ liệu trở thành một truy vấn được dẫn vào một cái khác với --params-from-stdin. Mỗi cái trong số đó sẽ là ba hoặc bốn lượt khứ hồi MCP với mọi kết quả trung tâm được diễu hành qua cửa sổ ngữ cảnh, và tại thời điểm đó, tác nhân đã tiêu nhiều token hơn để xáo trộn các hàng hơn là để suy nghĩ về chúng.
Dẫn qua nhiều CLI
Cùng một vấn đề, quy mô lớn hơn. Giả sử tác nhân muốn hiện thực hóa các vấn đề GitHub gần đây của một dự án vào Neo4j: một nút :Issue cho mỗi vé, một nút :User cho mỗi tác giả, một mối quan hệ :TAGGED cho mỗi nhãn. Dữ liệu sống trong một CLI (gh), muốn được định hình lại (jq làm việc đó), và hạ cánh trong một CLI khác (neo4j-cli). Ba công cụ khác nhau trong một dòng. Thông qua MCP, bạn sẽ đánh máy chủ MCP của GitHub cho danh sách vấn đề, mọi nội dung vấn đề hạ cánh trong ngữ cảnh của mô hình, mô hình trích xuất các trường nó muốn, và write-cypher kích hoạt một lần cho mỗi vấn đề. Hàng trăm lượt khứ hồi qua mô hình, mọi nội dung vấn đề ngồi trong cuộc dọc đường.
Thông qua CLI, ba chương trình trong một đường ống:
$ gh issue list --repo neo4j/neo4j --limit 100 \
--json number,title,author,labels \
| jq -c '.[]' \
| while read issue; do
neo4j-cli query --rw -c prod \
--param "data=$issue" \
"WITH apoc.convert.fromJsonMap($data) AS i
MERGE (n:Issue {number: i.number}) SET n.title = i.title
MERGE (u:User {login: i.author.login})
MERGE (u)-[:OPENED]->(n)
FOREACH (label IN i.labels |
MERGE (l:Label {name: label.name})
MERGE (n)-[:TAGGED]->(l))"
done
gh kéo các vấn đề, jq định hình lại mỗi cái thành một dòng JSON đơn, vòng lặp while trao mỗi dòng cho neo4j-cli như một tham số Cypher. Mô hình viết kịch bản này một lần và sau đó bước ra; dữ liệu chảy qua bash, không phải qua tác nhân. Một trăm vấn đề hay mười nghìn, chi phí token của tác nhân giống nhau.
Hình dạng này tổng quát hóa tốt hơn nhiều GitHub. Đổi gh cho bất kỳ CLI nào khác phát ra JSON (danh sách vấn đề jira, linear, curl chống lại một webhook, lệnh dump nội bộ của bạn), đổi mẫu Cypher cho bất kỳ cơ sở dữ liệu nào bạn đang xây dựng, và đường ống mang theo. Hai công cụ MCP không thể dẫn đến nhau; hai CLI có thể, và mười cũng vậy.
Kiểm soát terminal là mạnh mẽ, và đó là cái bẫy
Terminal không phải là một bề mặt cố định, nó là công cụ linh hoạt nhất bạn có thể trao cho một tác nhân vì nó kết hợp với mọi thứ khác trên hộp.
Sức mạnh đó cũng là cái bẫy. Một công cụ linh hoạt được sử dụng sai gây ra thiệt hại linh hoạt. Với quyền truy cập terminal lớn đi đến trách nhiệm hiển nhiên: sandbox shell, allowlist các động từ bạn thực sự muốn, chạy tác nhân như một người dùng hệ điều hành riêng, liên kết thông tin xác thực với các vai trò vật lý không thể làm việc phá hủy. Không có cái nào trong số này mới mẻ, nó chỉ là vệ sinh sysadmin được áp dụng cho một LLM gõ nhanh. Và nếu bạn không thể làm bất kỳ cái nào trong số đó, một máy chủ MCP với một bề mặt cố định nhỏ vẫn là câu trả lời đúng; sự đảm bảo cấp giao thức rằng tác nhân không thể cat ~/.ssh/id_rsa là một điều thực sự.
Điểm rộng hơn vẫn giữ đúng ngay cả khi bạn ở hoàn toàn bên trong MCP. Lý do terminal thắng không phải vì bash đặc biệt, mà vì bash là một công cụ với đầu vào rất linh hoạt. Đường ống, biến, thay thế, lặp. Đó là hình dạng đáng để sao chép. Đọc terminal như trường hợp giới hạn của MCP và thiết kế hướng tới nó: ít công cụ hơn, mỗi cái chấp nhận đầu vào biểu đạt, tác nhân làm việc lắp ghép thay vì bạn dự đoán mọi kết hợp trước. Hầu hết các máy chủ MCP là một danh sách dài các điểm cuối hẹp vì đó là cách API cơ bản đã được định hình, không phải vì tác nhân hoạt động tốt hơn theo cách đó. Các máy chủ tồn tại tốt theo thời gian sẽ là những cái đã chọn một bề mặt nhỏ hơn, biểu đạt hơn có chủ đích.
Bài viết liên quan

Phần mềm
Intel và AMD vá tổng cộng 70 lỗ hổng bảo mật trong Patch Tuesday tháng 5
13 tháng 5, 2026

Phần mềm
Google tung ra Antigravity 2.0: Ứng dụng lập trình thế hệ mới với công cụ CLI và gói đăng ký AI Ultra
19 tháng 5, 2026

Phần mềm
Plugin Checkmarx Jenkins bị xâm phạm trong cuộc tấn công chuỗi cung ứng
11 tháng 5, 2026
