SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor

Chạy MCP server trên VPS cho AI coding agent

Tự chạy MCP server trên VPS cho AI agent với stdio, HTTP remote, systemd, TLS và auth. Tránh lỗi JSON-RPC bẩn và endpoint tool không xác thực bị lộ.

Bạn sẽ xây dựng gì

Hai MCP setup hoạt động trên cùng một VPS. Đầu tiên là server stdio, chẳng hạn tool filesystem hoặc database mà Claude Code khởi chạy dưới dạng tiến trình con và giao tiếp qua pipe. Sau đó là server HTTP remote chạy lâu dài dưới dạng network service, nằm sau systemd và reverse proxy nginx có TLS. Server này có thể được bất kỳ MCP client nào bạn cấu hình để kết nối truy cập. Cài đặt cho mỗi loại đều ngắn gọn. Phần lớn hướng dẫn này tập trung vào hai vấn đề thường gây lỗi: giữ cho stream JSON-RPC luôn sạch và tuyệt đối không đưa một tool endpoint chưa có xác thực lên Internet công cộng.

MCP thực sự là gì

Model Context Protocol là một cách chuẩn để AI client, Claude Code, Claude Desktop, Gemini CLI trên một VPS hoặc script của bạn gọi các công cụ bên ngoài và đọc các resource bên ngoài. Bản thân model không chạy gì cả. Nó gửi yêu cầu cho client. Client dùng JSON-RPC 2.0 để giao tiếp với một server MCP. Server chạy tool rồi trả kết quả về. Client đó chính là thành phần mà mọi người gọi là agent harness: vòng lặp bao quanh model, quản lý danh sách tool, kiểm tra quyền và trạng thái session. MCP chỉ là cách mở rộng phần tool của vòng lặp này. Chỉ cần viết server một lần, bạn có thể dùng nó với mọi client hỗ trợ MCP. Nếu cách phân tách này còn mới với bạn, đặc biệt là câu hỏi model quyết định gọi tool như thế nào, hãy xem lộ trình từng bước về nền tảng agent trong khoảng một giờ trước khi cấp credential thật cho các server này.

Có 2 transport. Phần còn lại của hướng dẫn này được chia theo 2 transport đó:

  • stdio. Client khởi chạy server dưới dạng child process rồi trao đổi các message JSON-RPC được phân tách bằng newline qua standard input và standard output. Không có network, không có port, không có auth; ranh giới tin cậy chính là process. Hầu hết tool chạy local đều dùng cách này.
  • Streamable HTTP (và biến thể cũ hơn là HTTP+SSE). Server là một web service chạy liên tục. Client kết nối qua HTTP, còn server có thể stream response về dưới dạng Server-Sent Events. Cách này phù hợp khi bạn muốn chia sẻ một server cho nhiều client hoặc chạy một tool phải luôn tồn tại trên máy.

Chọn stdio khi tool chỉ phục vụ một máy và một user. Chọn HTTP khi tool là một service dùng chung.

Điều kiện cần và những điểm cần lưu ý

Giả sử bạn dùng một Ubuntu 24.04 KVM VPS mới cài, có quyền root hoặc sudo. Ngoài ra, bạn cần:

  • Runtime mà server sử dụng. Phần lớn server tham chiếu dùng Node hoặc Python. Ubuntu 24.04 cung cấp Node 18, trong khi một số MCP package hiện tại yêu cầu Node 20 trở lên. Vì vậy, hãy cài bản LTS hiện tại từ NodeSource hoặc nvm thay vì mặc định dùng apt. Python 3.12 đã có sẵn.
  • Một domain và DNS A record, nhưng chỉ cần cho remote HTTP server. TLS cần một tên miền phân giải về VPS này. Ví dụ stdio hoàn toàn không cần DNS.
  • 512 MB RAM là đủ. MCP server là các tiến trình JSON-RPC gọn nhẹ. Lượng RAM sử dụng phụ thuộc vào tool mà bạn gọi, chẳng hạn database driver hoặc file cache, chứ không phụ thuộc vào protocol.
  • Spec này còn mới và vẫn đang thay đổi. Bản revision 2025-03-26 đã thay HTTP+SSE bằng Streamable HTTP và đánh dấu SSE là deprecated. SSE vẫn hoạt động và nhiều server vẫn sử dụng nó. Vì vậy, hãy xem mọi thiết lập transport cố định là thứ cần kiểm tra lại trong release notes của server, thay vì mặc định coi đó là thông tin chắc chắn.

Bước 1: kết nối một stdio server vào Claude Code

Bắt đầu với filesystem server. Đây là server chính thức, vẫn được duy trì tích cực và chỉ cần Node. Lệnh duy nhất dưới đây đăng ký server với Claude Code và giới hạn phạm vi vào project hiện tại để cấu hình được ghi vào một file có thể commit:

cd /home/matt/projects/api
claude mcp add --scope project --transport stdio filesystem \
  -- npx -y @modelcontextprotocol/server-filesystem /home/matt/projects/api

Dấu phân cách -- rất quan trọng: mọi thứ đứng sau nó là command mà Claude Code sẽ chạy, không phải flag của Claude Code. Lệnh này ghi một .mcp.json vào thư mục gốc của project:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/home/matt/projects/api"
      ]
    }
  }
}

Hiện chưa có gì đang chạy. Lần tiếp theo bạn khởi động Claude Code trong thư mục này, agent sẽ đọc .mcp.json, spawn npx -y @modelcontextprotocol/server-filesystem ... dưới dạng child process và thực hiện MCP handshake qua stdin/stdout của process đó. Xác nhận cấu hình đã được áp dụng:

claude mcp list

Server hoạt động bình thường sẽ hiển thị command của nó cùng dấu tick màu xanh, filesystem: npx -y @modelcontextprotocol/server-filesystem ... - ✔ Connected. Trong session, slash command /mcp liệt kê các tool mà server cung cấp (read_text_file, write_file, list_directory). Agent lúc này có thể gọi các tool đó trên những path bạn đã cho phép. Database tool cũng có cấu trúc tương tự: thay package và truyền connection string làm đối số cuối cùng. Tuy nhiên, hãy kiểm tra repository của chính server để biết package name hiện tại, vì Postgres server tham chiếu đã đổi maintainer nhiều lần.

Đây là mục đích chính của việc chạy agent trên máy chủ: session Claude Code chạy trên VPS bên trong tmux, còn các stdio server chạy ngay bên cạnh nó và có quyền truy cập trực tiếp vào file project cùng các service cục bộ, không cần network round-trip. Khi agent có cả write_file và read_text_file, bạn nên kết hợp quyền truy cập đó với một skill hướng agent đến thay đổi nhỏ nhất nhưng vẫn giải quyết được vấn đề, vì filesystem tool khiến một lần rewrite lớn có chi phí ngang với một bản sửa hai dòng. Cách kết nối này cũng mở rộng ra ngoài file cục bộ: nếu bạn đã chạy search engine trên VPS, bạn có thể cấp cho agent instance SearXNG của riêng bạn làm search tool. Cách này giữ các query trên máy của bạn nhưng đưa nội dung trang không đáng tin cậy thẳng vào context, nơi agent sẽ tiếp tục dùng nội dung đó để thực hiện hành động.

Bước 2: xây dựng HTTP server từ xa

Một stdio server sẽ dừng cùng tiến trình cha và được tạo một lần cho mỗi client. Vì vậy, nếu bạn chạy hai phiên Claude Code trên máy và cho chúng chuyển công việc cho nhau, mỗi phiên sẽ có một bản sao riêng của tool. Khi cần một tool luôn chạy để mọi client sử dụng, chẳng hạn như tool vận hành dùng chung, database gateway hoặc dịch vụ mà cả laptop và CI đều gọi, bạn cần HTTP transport và một service thực sự. Dưới đây là Python server tối thiểu dùng SDK chính thức và expose một tool:

# /opt/mcp-ops/server.py
from mcp.server.mcpserver import MCPServer
import subprocess

mcp = MCPServer("ops-tools")

@mcp.tool()
def disk_free() -> str:
    """Return `df -h` for the server."""
    out = subprocess.run(["df", "-h"], capture_output=True, text=True)
    return out.stdout

if __name__ == "__main__":
    # Serves Streamable HTTP at /mcp on 127.0.0.1:8000
    mcp.run(transport="streamable-http", host="127.0.0.1", port=8000)

Lưu ý host="127.0.0.1". Server chỉ bind vào localhost, nên không có máy nào bên ngoài có thể truy cập trực tiếp. Đây chính là điều bạn muốn trước khi có auth. Đây là SDK 2.x mà pip install "mcp[cli]" hiện cài (2.3.0 tính đến 2026-10-20). SDK này đã đổi tên FastMCP thành MCPServer, đồng thời chuyển host và port từ constructor sang run(). Import mcp.server.fastmcp cũ hiện sẽ fail với ModuleNotFoundError. Hãy cài SDK trong virtualenv riêng để systemd có một đường dẫn interpreter ổn định:

sudo useradd --system --home /opt/mcp-ops --shell /usr/sbin/nologin mcp
sudo install -d -o mcp -g mcp /opt/mcp-ops
sudo -H -u mcp python3 -m venv /opt/mcp-ops/.venv
sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install "mcp[cli]"

Bước 3: duy trì hoạt động bằng systemd

Một công cụ không hoạt động khi agent cần dùng còn tệ hơn không có công cụ. Điều này đặc biệt quan trọng khi client cũng là một tiến trình chạy lâu dài: một agent luôn hoạt động, giữ nguyên bộ nhớ và lịch chạy qua các lần reboot sẽ gọi các công cụ này theo lịch mà không có ai theo dõi, vì vậy server cũng phải tự khởi động lại.

Tạo /etc/systemd/system/mcp-ops.service:

[Unit]
Description=MCP ops-tools server
After=network.target

[Service]
Type=simple
User=mcp
WorkingDirectory=/opt/mcp-ops
ExecStart=/opt/mcp-ops/.venv/bin/python /opt/mcp-ops/server.py
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target

Đường dẫn tuyệt đối đến Python trong venv tại ExecStart là bắt buộc. Trỏ nó đến /usr/bin/python3 để tiến trình khởi động bằng ModuleNotFoundError: No module named 'mcp', vì system interpreter chưa biết pip install của bạn. Bật và kiểm tra:

sudo systemctl daemon-reload
sudo systemctl enable --now mcp-ops
sudo systemctl status mcp-ops
curl -si -H 'Accept: application/json, text/event-stream' \
  -H 'Content-Type: application/json' \
  -X POST http://127.0.0.1:8000/mcp

status phải hiển thị active (running). curl trả về HTTP/1.1 400 Bad Request kèm lỗi JSON-RPC trong phần body. Request không có session và cũng không có payload JSON hợp lệ. Đây chính xác là kết quả cần có: nó chứng minh cổng đang trả lời và giao tiếp đúng protocol. Connection refused hoặc phản hồi rỗng nghĩa là tiến trình không bind tại vị trí bạn nghĩ. Hãy xem journalctl -u mcp-ops -n 50.

Bước 4: đặt TLS và reverse proxy phía trước

Server lắng nghe trên localhost. Để truy cập từ bất kỳ đâu, bạn termination TLS tại nginx rồi proxy vào bên trong. Cài nginx, lấy certificate bằng Certbot và Let’s Encrypt trên nginx, sau đó viết location block. Phần quan trọng là tắt buffering, vì hành vi mặc định của nginx giữ response cho đến khi hoàn tất. Điều này làm SSE stream bị treo vĩnh viễn:

server {
    listen 443 ssl;
    server_name mcp.example.com;

    # ssl_certificate lines managed by Certbot

    location /mcp {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;

        # The four lines that make SSE work through nginx:
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 3600s;
        chunked_transfer_encoding off;
    }
}

Reload bằng sudo nginx -t && sudo systemctl reload nginx. Nếu bạn đã chạy một fleet container, Traefik sẽ tự thực hiện công việc tương tự bằng reverse proxy Traefik với TLS tự động. Nó cấp certificate và định tuyến theo hostname; bạn chỉ cần thêm label vào container MCP. Dù dùng cách nào, reverse proxy hiện là thành phần duy nhất nằm trên public port và đang trỏ đến một service chưa được bảo vệ. Hãy xử lý việc đó trước khi đăng ký URL ở bất kỳ đâu.

Bước 5: quy tắc bảo mật quan trọng nhất trong chủ đề này

Tuyệt đối không public một MCP endpoint không có xác thực. MCP server không phải là API chỉ cho phép đọc. Nó cấp quyền truy cập tool vào file, database và đôi khi cả shell của bạn. Một /mcp mở trên Internet công cộng cho phép người lạ có quyền truy cập tương đương AI agent của bạn: họ liệt kê các tool rồi gọi chúng. Hãy xử lý nó chính xác như một admin socket không có xác thực, vì bản chất nó là như vậy. Mức độ thiệt hại khi token bị đánh cắp còn phụ thuộc vào server phía sau: MCP server chỉ đọc đi kèm trình theo dõi bài tập openGym chỉ có thể trả về dữ liệu tập luyện, còn tool filesystem hoặc shell có thể trao toàn quyền trên máy chủ.

Ba cách phòng vệ, theo thứ tự ưu tiên:

  1. Không public nó. Giữ server trên 127.0.0.1 và truy cập từ laptop qua SSH tunnel: ssh -L 8000:127.0.0.1:8000 matt@vps, sau đó trỏ client vào http://127.0.0.1:8000/mcp. Không có gì bị public.
  2. Đặt nó trên mạng riêng. Bind địa chỉ tunnel của VPN WireGuard tự host và chỉ cho các peer VPN truy cập. Internet công cộng sẽ thấy cổng đóng.
  3. Nếu bắt buộc phải public, hãy yêu cầu token. Cách đúng là MCP OAuth flow, được HTTP transport hỗ trợ sẵn. Mức tối thiểu thực tế là dùng shared bearer token được kiểm tra tại proxy. Cách này rẻ và chặn hoàn toàn các lượt truy cập ngẫu nhiên:
location /mcp {
    if ($http_authorization != "Bearer REPLACE_WITH_LONG_RANDOM") {
        return 401;
    }
    proxy_pass http://127.0.0.1:8000;
    # ...buffering-off block from above...
}

Tạo token bằng openssl rand -hex 32 và không bao giờ bind chính server vào 0.0.0.0 nếu không có một lớp bảo vệ như trên ở phía trước. Sau đó client gửi token trong header. Trong Claude Code:

claude mcp add --scope project --transport http ops-tools https://mcp.example.com/mcp \
  --header 'Authorization: Bearer ${MCP_TOKEN}'

Đặt MCP_TOKEN trong shell để secret không bao giờ được ghi dạng plaintext vào .mcp.json. Claude Code sẽ expand ${MCP_TOKEN} từ environment tại thời điểm đọc. Nếu biến chưa được set, Claude Code không thay thế bằng chuỗi rỗng. Thay vào đó, nó báo biến bị thiếu trong claude mcp list và sử dụng nguyên văn nội dung ${MCP_TOKEN}. Bạn cũng nên tự chọn một tên biến, vì trong header của remote server, Claude Code cố ý đọc các biến credential của chính nó và của cloud provider dưới dạng rỗng, chẳng hạn ANTHROPIC_API_KEY, cùng với các credential khác mà environment của bạn đang chứa, chẳng hạn NPM_TOKEN.

Các biện pháp trên đều bảo vệ endpoint, không bảo vệ agent đã giữ token. Đây là nửa còn lại của vấn đề: nếu client của bạn là DeepSeek Harness, plugin giới hạn các tool agent được phép gọi và quét output của tool để tìm instruction bị chèn sẽ xử lý phần đó.

Bước 6: debug bằng MCP Inspector

Khi server hoạt động không đúng, đừng đoán từ bên trong agent. Hãy điều khiển trực tiếp bằng Inspector, client kiểm thử chính thức chạy trên web. Với server stdio, truyền cho nó đúng command mà agent chạy:

npx @modelcontextprotocol/inspector \
  npx -y @modelcontextprotocol/server-filesystem /tmp

Inspector cung cấp giao diện từ một web server trên http://localhost:6274 và in ra một URL có query string MCP_INSPECTOR_API_TOKEN. Hãy mở link đó thay vì tự nhập địa chỉ theo trí nhớ. Từ v2 (2.11.0 là phiên bản hiện tại vào ngày 2026-10-20), backend cũng inject token vào page được cung cấp ở mỗi lần load. Vì vậy, reload vẫn hoạt động. Token này bảo vệ các route /api/* có thể spawn process. Proxy v1 riêng trên 6277 đã bị loại bỏ. Tuy nhiên, web server không phải listener duy nhất: v2 cũng khởi động một sandbox cho MCP Apps trên MCP_SANDBOX_PORT (mặc định là 6275) và một app-origin server riêng trên MCP_APP_ORIGIN_PORT (mặc định là 6278). Server này cung cấp mọi MCP App có UI resource khai báo _meta.ui.domain. Nếu truy cập Inspector qua SSH tunnel hoặc chạy Inspector trong container và cần tab Apps, hãy forward 6275 và 6278 cùng với 6274. Kết nối, mở tab Tools, chọn một tool, điền các argument thực tế rồi gọi tool. Nếu tool hoạt động trong Inspector nhưng lỗi trong agent, lỗi nằm ở cấu hình client, không phải server. Với remote HTTP server, hãy khởi chạy Inspector mà không truyền command, thêm server bằng transport Streamable HTTP và https://mcp.example.com/mcp, thêm header Authorization trong settings rồi kết nối. Đây là cách nhanh nhất để xác nhận auth và proxy hoạt động đúng trước khi có agent tham gia. Inspector lưu các header trong server catalog đúng như đã viết, vì vậy token hiện cũng nằm trong ~/.mcp-inspector/mcp.json.

Cập nhật server

MCP phát triển nhanh, vì vậy hãy cập nhật theo lịch cố định. Các Node server được khởi chạy bằng npx -y không ghim version sẽ chạy bất kỳ version nào mà npx resolve: version đã được cài trong project cục bộ nếu có, nếu không thì npx cài một bản vào npm cache. -y chỉ bỏ qua prompt xác nhận cài đặt, không buộc cập nhật, vì vậy bạn không theo dõi được release mới nhất và cũng không biết chắc mình đang chạy version nào. Khi server đã quan trọng, hãy ghim exact version đã kiểm thử, đọc version đó từ npm view @modelcontextprotocol/server-filesystem version rồi thêm vào tên package trong .mcp.json (@modelcontextprotocol/server-filesystem@<version>), sau đó chủ động nâng version. Python server chạy dưới systemd được cập nhật bằng sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install -U "mcp[cli]" rồi chạy sudo systemctl restart mcp-ops. Hãy đọc release notes trước khi nâng major version: việc chuyển từ Python SDK 1.x sang 2.x khiến code 1.x lỗi ngay khi import, và migration guide của SDK khuyến nghị giữ upper bound là <2, chẳng hạn "mcp[cli]<2", cho đến khi bạn hoàn tất việc migrate. Khi upgrade, hãy theo dõi revision của spec mà SDK nhắm tới. Việc chuyển qua ranh giới giữa SSE và Streamable HTTP có thể làm thay đổi transport mà client phải request.

Các trường hợp lỗi và chuỗi bạn sẽ thấy

Agent báo máy chủ đã lỗi. claude mcp list in ✘ Failed to connect, và các phiên bản hiện tại thêm chi tiết lỗi vào dòng đó. Chạy claude --debug; bạn thường sẽ thấy Error: spawn npx ENOENT, vì command không nằm trong PATH của agent. Runtime bị thiếu hoặc không nằm ở vị trí agent tìm kiếm: Node chưa được cài, thiếu npx hoặc Python trong virtualenv được tham chiếu bằng tên ngắn. Sửa command thành đường dẫn tuyệt đối hoặc cài runtime, rồi kết nối lại.

Stdio server kết nối rồi ngắt ngay. Client ghi log lỗi phân tích JSON, chẳng hạn Unexpected token 'S', "Server sta"... is not valid JSON hoặc Failed to parse message. Nguyên nhân luôn giống nhau: server ghi một dòng log vào stdout. Với stdio, stdout chính là kênh JSON-RPC, nên mọi văn bản thừa đều làm hỏng stream và khiến handshake thất bại. Trong Node, console.log ghi vào stdout; hãy dùng console.error. Trong Python, print() không có cấu hình sẽ ghi vào stdout; hãy ghi log bằng logging được cấu hình thành sys.stderr hoặc truyền file=sys.stderr. Quy tắc này là bắt buộc: với stdio, stdout chỉ chứa JSON-RPC, mọi nội dung dành cho người đọc phải đi qua stderr.

Remote server timeout hoặc đóng kết nối giữa handshake. Client lỗi với MCP error -32000: Connection closed, hoặc Inspector bị treo tại Connect và không bao giờ liệt kê tool. Khi chạy sau nginx, nguyên nhân là buffering: proxy giữ lại SSE stream thay vì flush, nên client chờ một response không bao giờ đến. Thêm proxy_buffering off; (và phần còn lại của block trong Step 4) vào location. Xác nhận bằng curl -N tới public URL; bạn phải thấy event data đến từng phần, không phải tất cả chỉ xuất hiện một lần ở cuối.

Auth bị từ chối. Client báo Error POSTing to endpoint (HTTP 401) hoặc ghi rõ 401 Unauthorized. Có thể header bị thiếu, token sai hoặc biến không được expand; đây là một lỗi thường gặp. Nếu MCP_TOKEN chưa được set khi client đọc config, Claude Code cảnh báo về biến bị thiếu trong claude mcp list và gửi nguyên văn phần text đó, nên nginx thấy Bearer ${MCP_TOKEN}. Nếu đặt tên biến theo một credential khác của Claude Code, cloud provider hoặc môi trường, biến đó sẽ được đọc thành rỗng; nginx sẽ thấy Bearer không có giá trị. In biến ra, thêm lại header và xác nhận các byte khớp chính xác với token trong if của nginx.

Service không khởi động được dưới systemd. journalctl -u mcp-ops hiển thị ModuleNotFoundError: No module named 'mcp', còn ExecStart trỏ tới Python hệ thống thay vì interpreter của venv. Hoặc Address already in use; một tiến trình khác đang giữ cổng 8000. Tìm tiến trình đó bằng sudo ss -ltnp | grep 8000.

FAQ

MCP server chính xác là gì?

Đó là một chương trình cung cấp tools và resources cho AI client qua Model Context Protocol, sử dụng JSON-RPC 2.0. AI model không tự chạy tool. Nó gửi yêu cầu cho client, client gọi MCP server, rồi server thực thi và trả về kết quả. Vì protocol này là tiêu chuẩn, một server có thể hoạt động với mọi client tuân thủ chuẩn, dù đó là Claude Code, Claude Desktop hay Gemini CLI.

Transport stdio khác HTTP như thế nào?

Stdio server được client khởi chạy dưới dạng child process và giao tiếp qua stdin/stdout. Vì vậy, nó chỉ tồn tại cùng một client trên một máy và không cần network hoặc auth. HTTP server là một network service chạy lâu dài, cho phép nhiều client truy cập cùng lúc. Vì vậy, nó cần TLS và authentication. Dùng stdio cho các tool cục bộ, chỉ dành cho một user. Dùng HTTP (Streamable HTTP trên các server hiện tại) cho mọi thứ dùng chung hoặc cần chạy liên tục.

Làm cách nào để bảo mật MCP server từ xa?

Hãy giả định server có quyền truy cập vào file, database hoặc shell của bạn và không bao giờ expose server mà không có authentication. Cách tốt nhất là chỉ bind server vào localhost, rồi truy cập qua SSH tunnel hoặc private VPN. Nếu bắt buộc phải public, hãy đặt server sau reverse proxy để enforce bearer token hoặc MCP OAuth flow. Tạo token bằng openssl rand -hex 32 và không bao giờ bind server vào 0.0.0.0 nếu không có một trong các lớp bảo vệ này ở phía trước.

Làm cách nào để debug server không khởi động?

Trước tiên, kiểm tra claude mcp list, ✘ Failed to connect bằng spawn ... ENOENT cho biết command hoặc runtime đang thiếu, vì vậy hãy sửa path hoặc cài đặt nó. Nếu server kết nối được rồi ngắt với lỗi phân tích cú pháp JSON, server đang ghi log vào stdout và làm hỏng JSON-RPC stream. Hãy chuyển toàn bộ log sang stderr. Với các lỗi khác, hãy chạy đúng command đó trong MCP Inspector. Tool này điều khiển server độc lập, giúp bạn phân biệt lỗi trong server với lỗi trong cấu hình client.