วิธีรัน MCP Servers บน VPS สำหรับ AI Coding Agents
เรียนรู้วิธีติดตั้ง MCP Servers บน VPS ทั้งรูปแบบ stdio และ remote HTTP พร้อมการตั้งค่า systemd, TLS และระบบยืนยันตัวตน เพื่อให้ AI Agents ใช้งานเครื่องมือได้อย่างปลอดภัย
สิ่งที่คุณกำลังสร้าง
การตั้งค่า MCP สองรูปแบบบน VPS เดียวกัน รูปแบบแรกคือเซิร์ฟเวอร์แบบ stdio ซึ่งเป็นเครื่องมือสำหรับจัดการระบบไฟล์หรือฐานข้อมูล โดย Claude Code จะเรียกใช้งานในฐานะ child process และสื่อสารผ่าน pipe รูปแบบที่สองคือเซิร์ฟเวอร์แบบ remote HTTP ซึ่งทำงานเป็น network service ระยะยาวภายใต้ systemd และ nginx reverse proxy ที่มี TLS ทำให้ client ของ MCP ใดก็ตามที่คุณกำหนดค่าไว้สามารถเข้าถึงได้ การติดตั้งทั้งสองรูปแบบนั้นใช้ทรัพยากรน้อย คู่มือนี้จะเน้นไปที่สองประเด็นสำคัญที่มักก่อให้เกิดปัญหา คือการรักษาความสะอาดของ JSON-RPC stream และการไม่เปิดเผย endpoint ของเครื่องมือที่ไม่มีการตรวจสอบสิทธิ์ไว้บนอินเทอร์เน็ตสาธารณะโดยเด็ดขาด
MCP คืออะไรกันแน่
Model Context Protocol เป็นมาตรฐานที่ช่วยให้ AI client ไม่ว่าจะเป็น Claude Code, Claude Desktop, Gemini CLI บน VPS หรือสคริปต์ที่คุณเขียนขึ้นเอง สามารถเรียกใช้เครื่องมือภายนอกและอ่านทรัพยากรภายนอกได้ ตัวโมเดลเองไม่ได้เป็นผู้รันคำสั่งใดๆ แต่จะส่งคำขอไปยัง client จากนั้น client จะสื่อสารผ่าน JSON-RPC 2.0 ไปยัง MCP server เพื่อให้ server เป็นผู้รันเครื่องมือและส่งผลลัพธ์กลับมา ตัว client นี้เองคือสิ่งที่ผู้คนมักเรียกว่า agent harness ซึ่งหมายถึงลูปที่ครอบตัวโมเดลไว้ ทำหน้าที่จัดการรายการเครื่องมือ การตรวจสอบสิทธิ์ และสถานะของเซสชัน โดย MCP เป็นเพียงวิธีการที่คุณใช้ขยายขีดความสามารถด้านเครื่องมือของมันเท่านั้น ด้วยความเป็นมาตรฐานเดียว server ที่คุณเขียนขึ้นเพียงครั้งเดียวจึงสามารถทำงานร่วมกับทุก client ที่รองรับ MCP ได้ หากแนวคิดการแยกส่วนนี้ยังเป็นเรื่องใหม่สำหรับคุณ โดยเฉพาะในประเด็นที่ว่าโมเดลตัดสินใจเลือกใช้เครื่องมือได้อย่างไร เส้นทางการเรียนรู้พื้นฐานของ agent เป็นสิ่งที่คุ้มค่าที่จะใช้เวลาศึกษาประมาณหนึ่งชั่วโมง ก่อนที่คุณจะอนุญาตให้ server เหล่านี้เข้าถึงข้อมูลรับรองจริง
โปรโตคอลนี้มีรูปแบบการรับส่งข้อมูล (transport) สองแบบ ซึ่งเนื้อหาที่เหลือในคู่มือนี้จะแบ่งตามรูปแบบดังกล่าว:
- stdio: client จะเรียกใช้ server ในฐานะ child process และแลกเปลี่ยนข้อความ JSON-RPC ที่คั่นด้วยบรรทัดใหม่ผ่าน standard input และ standard output โดยไม่มีการใช้เครือข่าย ไม่มีพอร์ต และไม่มีการตรวจสอบสิทธิ์ ขอบเขตความปลอดภัยจะอยู่ที่ตัว process เอง เครื่องมือที่ใช้งานภายในเครื่องส่วนใหญ่ใช้วิธีนี้
- Streamable HTTP (และรูปแบบเดิมอย่าง HTTP+SSE): server จะทำงานเป็นเว็บเซอร์วิสที่รันอยู่ตลอดเวลา client จะเชื่อมต่อผ่าน HTTP และ server สามารถส่งการตอบกลับแบบสตรีมผ่าน Server-Sent Events ได้ วิธีนี้เหมาะสำหรับการแชร์ server หนึ่งตัวให้กับหลาย client หรือการรันเครื่องมือที่จำเป็นต้องทำงานอยู่บนเซิร์ฟเวอร์ตลอดเวลา
ให้เลือกใช้ stdio เมื่อเครื่องมือดังกล่าวใช้งานบนเครื่องเดียวและโดยผู้ใช้คนเดียว และเลือกใช้ HTTP เมื่อเป็นบริการที่ต้องใช้งานร่วมกันหลายคน
ข้อกำหนดเบื้องต้นและข้อควรระวังที่ต้องทราบ
สมมติว่าคุณใช้ Ubuntu 24.04 KVM VPS ใหม่ที่เข้าถึงด้วยสิทธิ์ root หรือ sudo นอกจากนี้ยังมีประเด็นที่ควรทราบดังนี้:
- Runtime สำหรับรันเซิร์ฟเวอร์: เซิร์ฟเวอร์อ้างอิงส่วนใหญ่เขียนด้วย Node หรือ Python โดย Ubuntu 24.04 มาพร้อมกับ Node 18 แต่แพ็กเกจ MCP ปัจจุบันหลายรายการต้องการ Node 20 หรือใหม่กว่า ดังนั้นควรติดตั้งเวอร์ชัน LTS ปัจจุบันจาก NodeSource หรือ nvm แทนการเชื่อถือ
aptส่วน Python 3.12 นั้นมีติดตั้งมาให้พร้อมใช้งานอยู่แล้ว - โดเมนและ DNS A record: จำเป็นเฉพาะสำหรับเซิร์ฟเวอร์ HTTP ระยะไกลเท่านั้น เนื่องจาก TLS ต้องการชื่อที่ชี้มายัง VPS นี้ ส่วนตัวอย่าง stdio ไม่จำเป็นต้องใช้ DNS เลย
- RAM ขนาด 512 MB ก็เพียงพอ: เซิร์ฟเวอร์ MCP เป็นกระบวนการ JSON-RPC ขนาดเล็ก การใช้หน่วยความจำจะขึ้นอยู่กับสิ่งที่เครื่องมือของคุณเรียกใช้งาน (เช่น ไดรเวอร์ฐานข้อมูล หรือแคชไฟล์) ไม่ใช่ตัวโปรโตคอลเอง
- ข้อกำหนด (spec) ยังอยู่ในช่วงเริ่มต้นและมีการเปลี่ยนแปลง: การแก้ไขเมื่อวันที่ 2025-03-26 ได้เปลี่ยนจากการใช้ HTTP+SSE มาเป็น Streamable HTTP และระบุว่า SSE เป็นสิ่งที่เลิกใช้แล้ว (deprecated) อย่างไรก็ตาม SSE ยังคงทำงานได้และเซิร์ฟเวอร์จำนวนมากยังคงรองรับอยู่ ดังนั้นให้ถือว่าการกำหนด transport เป็นสิ่งที่ควรตรวจสอบซ้ำกับบันทึกประจำรุ่น (release notes) ของเซิร์ฟเวอร์ แทนที่จะยึดถือเป็นกฎตายตัว
ขั้นตอนที่ 1: เชื่อมต่อ stdio server เข้ากับ Claude Code
เริ่มต้นด้วย filesystem server ซึ่งเป็นเครื่องมืออย่างเป็นทางการ มีการดูแลรักษาอย่างต่อเนื่อง และต้องการเพียง Node เท่านั้น คำสั่งเดียวด้านล่างนี้จะลงทะเบียนเครื่องมือดังกล่าวกับ Claude Code และกำหนดขอบเขตไว้เฉพาะโปรเจกต์ปัจจุบัน เพื่อให้บันทึกการตั้งค่าลงในไฟล์ที่สามารถ commit ได้:
cd /home/matt/projects/api
claude mcp add --scope project --transport stdio filesystem \
-- npx -y @modelcontextprotocol/server-filesystem /home/matt/projects/apiตัวคั่น -- มีความสำคัญ: ทุกอย่างที่อยู่หลังตัวคั่นนี้คือคำสั่งที่ Claude Code จะเรียกใช้งาน ไม่ใช่ flag สำหรับ Claude Code คำสั่งนี้จะเขียนไฟล์ .mcp.json ไว้ที่ root ของโปรเจกต์:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/home/matt/projects/api"
]
}
}
}ในขณะนี้ยังไม่มีการรันกระบวนการใดๆ เมื่อคุณเริ่มใช้งาน Claude Code ในไดเรกทอรีนี้ในครั้งถัดไป เอเจนต์จะอ่านไฟล์ .mcp.json, สั่ง spawn npx -y @modelcontextprotocol/server-filesystem ... เป็น child process และดำเนินการ MCP handshake ผ่าน stdin/stdout ของกระบวนการนั้น ตรวจสอบความสำเร็จด้วยคำสั่ง:
claude mcp listเซิร์ฟเวอร์ที่ทำงานปกติจะแสดงคำสั่งของตนเองพร้อมเครื่องหมายถูกสีเขียว filesystem: npx -y @modelcontextprotocol/server-filesystem ... - ✔ Connected ภายในเซสชัน คำสั่ง slash /mcp จะแสดงรายการเครื่องมือที่เซิร์ฟเวอร์เปิดให้ใช้งาน (read_text_file, write_file, list_directory) และเอเจนต์จะสามารถเรียกใช้เครื่องมือเหล่านั้นบน path ที่คุณอนุญาตได้ เครื่องมือฐานข้อมูลก็มีลักษณะการทำงานเช่นเดียวกัน เพียงแค่เปลี่ยน package และส่ง connection string เป็นอาร์กิวเมนต์สุดท้าย แต่ควรตรวจสอบ repository ของเซิร์ฟเวอร์นั้นๆ เพื่อดูชื่อ package ปัจจุบัน เนื่องจากเซิร์ฟเวอร์ Postgres อ้างอิงมีการเปลี่ยนผู้ดูแลมาแล้วหลายครั้ง
นี่คือหัวใจสำคัญของการรันเอเจนต์บนเครื่อง: เซสชันของ Claude Code ทำงานอยู่บน VPS ภายใน tmux และ stdio server ของมันก็ทำงานอยู่ข้างๆ กัน ทำให้สามารถเข้าถึงไฟล์โปรเจกต์และบริการภายในเครื่องได้โดยตรงโดยไม่ต้องผ่าน network round-trip เมื่อเอเจนต์ถือครองทั้ง write_file และ read_text_file แล้ว ควรจับคู่ความสามารถนี้เข้ากับ ทักษะที่ผลักดันให้เอเจนต์เลือกการแก้ไขที่เล็กที่สุดที่ใช้งานได้ เพราะเครื่องมือจัดการไฟล์ทำให้การเขียนโค้ดใหม่ทั้งหมดมีต้นทุนต่ำพอๆ กับการแก้ไขเพียงสองบรรทัด การเชื่อมต่อนี้ยังขยายไปไกลกว่าไฟล์ในเครื่อง: หากคุณรัน search engine บน VPS อยู่แล้ว คุณสามารถ ส่ง instance ของ SearXNG ของคุณให้เอเจนต์เป็นเครื่องมือค้นหา ซึ่งจะช่วยให้การสืบค้นยังคงอยู่บนเครื่องของคุณ แต่ดึงข้อความจากหน้าเว็บที่ไม่น่าเชื่อถือเข้ามาในบริบทที่เอเจนต์จะดำเนินการต่อไปได้โดยตรง
ขั้นตอนที่ 2: สร้าง HTTP server แบบรีโมท
stdio server จะหยุดทำงานเมื่อ process แม่สิ้นสุดลง และจะถูกสร้างขึ้นใหม่ทุกครั้งที่มี client เชื่อมต่อเข้ามา ดังนั้นหากคุณรัน Claude Code สอง session บนเครื่องเดียวกันที่ส่งงานให้กัน แต่ละ session จะได้รับสำเนาของเครื่องมือแยกกัน หากคุณต้องการเครื่องมือที่ทำงานตลอดเวลาสำหรับทุก client เช่น เครื่องมือจัดการระบบ (ops tool) แบบแชร์, database gateway หรือบริการที่ทั้งแล็ปท็อปและ CI ของคุณเรียกใช้งาน คุณจำเป็นต้องใช้ HTTP transport และบริการที่ทำงานจริง นี่คือตัวอย่าง Python server ขนาดเล็กที่ใช้ SDK อย่างเป็นทางการ โดยเปิดใช้งานเครื่องมือหนึ่งรายการ:
# /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)โปรดสังเกต host="127.0.0.1" เซิร์ฟเวอร์นี้จะ bind เฉพาะ localhost เท่านั้น ไม่มีสิ่งใดจากภายนอกเครื่องที่สามารถเข้าถึงได้โดยตรง ซึ่งเป็นสิ่งที่คุณต้องการก่อนที่จะมีการตั้งค่าการยืนยันตัวตน นี่คือ SDK เวอร์ชัน 2.x ที่ pip install "mcp[cli]" ดึงมาใช้งานในปัจจุบัน (เวอร์ชัน 2.3.0 ณ วันที่ 2026-10-20) โดยมีการเปลี่ยนชื่อ FastMCP เป็น MCPServer และย้าย host กับ port ออกจาก constructor ไปไว้ที่ run() นอกจากนี้การ import mcp.server.fastmcp แบบเดิมจะทำให้เกิดข้อผิดพลาด ModuleNotFoundError ให้ติดตั้งใน virtualenv ของตนเองเพื่อให้ systemd มี path ของ interpreter ที่เสถียร:
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]"ขั้นตอนที่ 3: รักษาการทำงานด้วย systemd
เครื่องมือที่หยุดทำงานในขณะที่เอเจนต์พยายามเรียกใช้งานนั้นแย่ยิ่งกว่าการไม่มีเครื่องมือเสียอีก สิ่งนี้มีความสำคัญอย่างยิ่งเมื่อตัวไคลเอนต์เองเป็นกระบวนการที่ทำงานต่อเนื่องยาวนาน: เอเจนต์ที่ทำงานตลอดเวลาซึ่งรักษาหน่วยความจำและตารางเวลาไว้แม้ผ่านการรีบูต จะเรียกใช้เครื่องมือเหล่านี้ตามกำหนดการโดยไม่มีผู้ดูแล ดังนั้นเซิร์ฟเวอร์จึงต้องกลับมาทำงานได้ด้วยตัวเอง ให้เขียนไฟล์ /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การระบุพาธแบบสัมบูรณ์ (absolute path) ไปยัง Python ใน venv ที่ ExecStart เป็นสิ่งที่จำเป็น ต้องชี้ไปยัง /usr/bin/python3 เพื่อให้กระบวนการเริ่มต้นด้วย ModuleNotFoundError: No module named 'mcp' เนื่องจากตัวแปลภาษาของระบบไม่เคยเห็น pip install ของคุณ ให้เปิดใช้งานและตรวจสอบ:
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/mcpstatus ควรแสดงผลเป็น active (running) ส่วน curl จะตอบกลับมาเป็น HTTP/1.1 400 Bad Request พร้อมข้อความแสดงข้อผิดพลาด JSON-RPC ในเนื้อหา เนื่องจากคำขอไม่มีเซสชันและไม่มี payload JSON ที่ถูกต้อง ซึ่งนั่นคือสิ่งที่คุณต้องการอย่างแท้จริง: มันพิสูจน์ว่าพอร์ตมีการตอบสนองและสื่อสารด้วยโปรโตคอลที่ถูกต้อง หากได้รับ Connection refused หรือการตอบกลับที่ว่างเปล่า แสดงว่ากระบวนการไม่ได้ผูกอยู่กับพอร์ตที่คุณคิดไว้ ให้ตรวจสอบ journalctl -u mcp-ops -n 50
ขั้นตอนที่ 4: ติดตั้ง TLS และ reverse proxy ไว้ด้านหน้า
เซิร์ฟเวอร์จะฟังคำขอที่ localhost หากต้องการเข้าถึงจากภายนอก ให้ทำ TLS termination ที่ nginx แล้วทำ proxy ส่งต่อเข้ามาภายใน ให้ติดตั้ง nginx และขอใบรับรองด้วย Certbot และ Let's Encrypt บน nginx จากนั้นเขียน location block ส่วนที่สำคัญที่สุดคือการปิดใช้งาน buffering เนื่องจากพฤติกรรมปกติของ nginx คือการพักข้อมูลตอบกลับไว้จนกว่าจะเสร็จสิ้น ซึ่งจะทำให้ SSE stream ค้างตลอดไป:
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;
}
}โหลดการตั้งค่าใหม่ด้วย sudo nginx -t && sudo systemctl reload nginx หากคุณรันกลุ่มคอนเทนเนอร์อยู่แล้ว งานเดียวกันนี้สามารถทำได้โดย Traefik reverse proxy พร้อม TLS อัตโนมัติ ซึ่งจะออกใบรับรองและกำหนดเส้นทางตามชื่อโฮสต์ คุณเพียงแค่เพิ่ม label ให้กับคอนเทนเนอร์ MCP ไม่ว่าจะใช้วิธีใด reverse proxy จะกลายเป็นสิ่งเดียวที่อยู่บนพอร์ตสาธารณะ และมันจะชี้ไปยังบริการที่คุณยังไม่ได้รักษาความปลอดภัย โปรดแก้ไขส่วนนี้ก่อนที่จะนำ URL ไปลงทะเบียนที่ใดก็ตาม
ขั้นตอนที่ 5: กฎความปลอดภัยที่สำคัญที่สุดในหัวข้อนี้
ห้ามเปิดเผย MCP endpoint ที่ไม่มีการยืนยันตัวตนโดยเด็ดขาด MCP server ไม่ใช่ API แบบอ่านได้อย่างเดียว มันให้สิทธิ์ในการเข้าถึงเครื่องมือ ไฟล์ ฐานข้อมูล และบางครั้งอาจรวมถึง shell การเปิด /mcp บนอินเทอร์เน็ตสาธารณะเปรียบเสมือนการเปิดช่องทางให้คนแปลกหน้ามีสิทธิ์เข้าถึงเท่ากับ AI agent ของคุณ พวกเขาสามารถดูรายการเครื่องมือและเรียกใช้งานได้ทันที ให้ถือว่ามันเป็น admin socket ที่ไม่มีการป้องกัน เพราะนั่นคือสิ่งที่มันเป็น ความเสียหายจากการถูกขโมย token ขึ้นอยู่กับเซิร์ฟเวอร์ที่อยู่เบื้องหลังด้วย เช่น MCP server แบบอ่านได้อย่างเดียวที่มาพร้อมกับ openGym workout tracker จะส่งคืนได้เพียงข้อมูลการฝึกซ้อมเท่านั้น ในขณะที่เครื่องมือจัดการระบบไฟล์หรือ shell จะทำให้ผู้บุกรุกเข้าควบคุมเครื่องได้ทั้งระบบ
แนวทางการป้องกัน 3 ระดับ เรียงตามลำดับความสำคัญ:
- อย่าเผยแพร่ออกสู่สาธารณะ ให้รันเซิร์ฟเวอร์บน
127.0.0.1และเข้าถึงจากแล็ปท็อปของคุณผ่าน SSH tunnel ด้วยคำสั่งssh -L 8000:127.0.0.1:8000 matt@vpsจากนั้นชี้ client ไปที่http://127.0.0.1:8000/mcpวิธีนี้จะไม่มีการเปิดเผยบริการใดๆ ออกไป - วางไว้บนเครือข่ายส่วนตัว ผูก tunnel address ไว้กับ WireGuard VPN ที่โฮสต์เอง และอนุญาตให้เฉพาะผู้ที่อยู่ใน VPN เท่านั้นที่เข้าถึงได้ อินเทอร์เน็ตสาธารณะจะมองเห็นเพียงพอร์ตที่ปิดอยู่
- หากจำเป็นต้องเปิดสาธารณะ ต้องมีการใช้ token วิธีที่ถูกต้องคือการใช้ MCP OAuth flow ซึ่ง HTTP transport รองรับโดยตรง วิธีที่ง่ายและได้ผลคือการใช้ shared bearer token ที่ตรวจสอบผ่าน proxy ซึ่งมีค่าใช้จ่ายต่ำและสามารถป้องกันการโจมตีแบบสุ่มได้ทันที:
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...
}สร้าง token ด้วย openssl rand -hex 32 และห้ามผูกเซิร์ฟเวอร์เข้ากับ 0.0.0.0 โดยไม่มีมาตรการเหล่านี้ป้องกันไว้เด็ดขาด จากนั้น client จะส่ง token ผ่าน header ใน Claude Code ให้ทำดังนี้:
claude mcp add --scope project --transport http ops-tools https://mcp.example.com/mcp \
--header 'Authorization: Bearer ${MCP_TOKEN}'ตั้งค่า MCP_TOKEN ใน shell ของคุณ เพื่อไม่ให้ secret ตกค้างอยู่ใน .mcp.json ในรูปแบบข้อความธรรมดา Claude Code จะขยายค่า ${MCP_TOKEN} จาก environment ในขณะที่อ่านค่า หากตัวแปรไม่ได้ถูกตั้งค่าไว้ Claude Code จะไม่แทนที่ด้วยค่าว่าง แต่จะแจ้งเตือนตัวแปรที่หายไปใน claude mcp list และใช้ข้อความ ${MCP_TOKEN} ตามจริง คุณควรตั้งชื่อตัวแปรเองด้วย เพราะใน header ของเซิร์ฟเวอร์ระยะไกล Claude Code จะอ่านค่าตัวแปร credential ของตัวเองและของผู้ให้บริการคลาวด์ เช่น ANTHROPIC_API_KEY เป็นค่าว่างโดยเจตนา รวมถึง credential อื่นๆ ที่อยู่ใน environment ของคุณ เช่น NPM_TOKEN
การป้องกันทุกวิธีข้างต้นเป็นการปกป้องที่ตัว endpoint ไม่ใช่ที่ตัว agent ที่ถือ token อยู่ ซึ่งเป็นปัญหาอีกด้านหนึ่ง หาก client ของคุณคือ DeepSeek Harness การใช้ ปลั๊กอินที่คัดกรองว่า agent สามารถเรียกใช้เครื่องมือใดได้บ้างและตรวจสอบผลลัพธ์ของเครื่องมือเพื่อป้องกันการฉีดคำสั่ง (injected instructions) จะช่วยดูแลความปลอดภัยในส่วนนี้ได้
ขั้นตอนที่ 6: การดีบั๊กด้วย MCP Inspector
เมื่อเซิร์ฟเวอร์ทำงานผิดปกติ อย่าคาดเดาจากภายในตัว agent ให้ควบคุมโดยตรงด้วย Inspector ซึ่งเป็นไคลเอนต์ทดสอบอย่างเป็นทางการบนเว็บ สำหรับ stdio server ให้ใช้คำสั่งเดียวกับที่ agent เรียกใช้งาน:
npx @modelcontextprotocol/inspector \
npx -y @modelcontextprotocol/server-filesystem /tmpเครื่องมือนี้จะให้บริการ UI ผ่านเว็บเซิร์ฟเวอร์หนึ่งตัวบน http://localhost:6274 และแสดง URL ที่มี query string เป็น MCP_INSPECTOR_API_TOKEN ให้เปิดลิงก์นั้นแทนการพิมพ์ที่อยู่จากความจำ ตั้งแต่ v2 เป็นต้นมา (เวอร์ชัน 2.11.0 ณ วันที่ 2026-10-20) แบ็กเอนด์จะแทรก token ลงในหน้าเว็บที่ให้บริการทุกครั้งที่โหลด ทำให้การรีโหลดหน้าเว็บยังคงใช้งานได้ต่อเนื่อง และ token จะทำหน้าที่ป้องกันเส้นทาง /api/* ที่สามารถสร้างกระบวนการทำงาน (process) ได้ สำหรับ v1 proxy บนพอร์ต 6277 นั้นถูกยกเลิกไปแล้ว แต่เว็บเซิร์ฟเวอร์ไม่ใช่ผู้ฟังเพียงรายเดียว v2 ยังเริ่ม sandbox สำหรับ MCP Apps บน MCP_SANDBOX_PORT (ค่าเริ่มต้นคือ 6275) และเซิร์ฟเวอร์เฉพาะสำหรับ app-origin บน MCP_APP_ORIGIN_PORT (ค่าเริ่มต้นคือ 6278) ซึ่งจะให้บริการ MCP App ใดก็ตามที่ resource ของ UI ประกาศค่า _meta.ui.domain หากคุณเข้าถึง Inspector ผ่าน SSH tunnel หรือรันในคอนเทนเนอร์และต้องการใช้งานแท็บ Apps ให้ทำการ forward พอร์ต 6275 และ 6278 เพิ่มเติมจาก 6274 ให้เชื่อมต่อ เปิดแท็บ Tools เลือกเครื่องมือ กรอกอาร์กิวเมนต์จริง แล้วเรียกใช้งาน หากเครื่องมือทำงานได้ใน Inspector แต่ล้มเหลวใน agent แสดงว่าข้อผิดพลาดอยู่ที่การตั้งค่าไคลเอนต์ของคุณ ไม่ใช่ที่เซิร์ฟเวอร์ สำหรับ remote HTTP server ให้เปิด Inspector โดยไม่ต้องระบุคำสั่ง เพิ่มเซิร์ฟเวอร์ด้วย transport แบบ Streamable HTTP และ https://mcp.example.com/mcp ใส่ header Authorization ลงในการตั้งค่า แล้วเชื่อมต่อ วิธีนี้เป็นวิธีที่เร็วที่สุดในการตรวจสอบว่าการยืนยันตัวตนและพร็อกซีถูกต้องก่อนที่จะเริ่มใช้งาน agent จริง Inspector จะบันทึก header ไว้ในแคตตาล็อกเซิร์ฟเวอร์ตามที่ระบุไว้ทุกประการ ดังนั้น token ดังกล่าวจะถูกจัดเก็บไว้ใน ~/.mcp-inspector/mcp.json ด้วยเช่นกัน
การอัปเดตเซิร์ฟเวอร์ให้เป็นปัจจุบัน
MCP มีการพัฒนาอย่างรวดเร็ว ดังนั้นควรวางกำหนดการแพตช์ให้ชัดเจน เซิร์ฟเวอร์ Node ที่รันด้วย npx -y โดยไม่ได้ระบุเวอร์ชันไว้จะรันเวอร์ชันใดก็ตามที่ npx ตรวจพบ ซึ่งอาจเป็นเวอร์ชันที่ติดตั้งอยู่ในโปรเจกต์ภายในเครื่อง หรือเวอร์ชันที่ถูกติดตั้งลงใน npm cache โดยอัตโนมัติ -y ทำหน้าที่เพียงข้ามขั้นตอนการยืนยันการติดตั้งเท่านั้น ไม่ได้เป็นการบังคับอัปเดต ดังนั้นคุณจะไม่สามารถติดตามเวอร์ชันล่าสุดหรือยืนยันได้ว่ากำลังรันเวอร์ชันใดอยู่ เมื่อเซิร์ฟเวอร์เริ่มมีความสำคัญ คุณควรระบุเวอร์ชันที่ผ่านการทดสอบแล้วให้ชัดเจน โดยอ่านค่าจาก npm view @modelcontextprotocol/server-filesystem version แล้วนำไปต่อท้ายชื่อแพ็กเกจใน .mcp.json (@modelcontextprotocol/server-filesystem@<version>) และทำการปรับเวอร์ชันอย่างตั้งใจ สำหรับเซิร์ฟเวอร์ Python ที่รันภายใต้ systemd ให้ทำการอัปเดตด้วย sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install -U "mcp[cli]" ตามด้วย sudo systemctl restart mcp-ops โปรดอ่านบันทึกประจำรุ่น (release notes) ก่อนการอัปเดตเวอร์ชันหลัก (major bump) เนื่องจาก Python SDK เวอร์ชัน 1.x ไปยัง 2.x จะทำให้โค้ดเดิมที่ใช้ 1.x ไม่สามารถ import ได้ และคู่มือการย้ายระบบของ SDK เองก็แนะนำให้กำหนดขอบเขตบนของ <2 ไว้ เช่น "mcp[cli]<2" จนกว่าคุณจะดำเนินการย้ายระบบเสร็จสิ้น นอกจากนี้ ให้ตรวจสอบเวอร์ชันของ spec ที่ SDK ของคุณรองรับเมื่อทำการอัปเกรด เพราะการเปลี่ยนผ่านจาก SSE ไปเป็น Streamable-HTTP อาจส่งผลต่อรูปแบบการรับส่งข้อมูลที่ไคลเอนต์ของคุณต้องร้องขอ
รูปแบบความล้มเหลวและข้อความที่พบ
เอเจนต์แจ้งว่าเซิร์ฟเวอร์ล้มเหลว claude mcp list จะแสดง ✘ Failed to connect และในเวอร์ชันปัจจุบันจะต่อท้ายรายละเอียดความล้มเหลวไว้ที่บรรทัดนั้น ให้รัน claude --debug ซึ่งมักจะพบ Error: spawn npx ENOENT หมายความว่าคำสั่งดังกล่าวไม่อยู่ใน PATH ของเอเจนต์ สาเหตุเกิดจาก runtime หายไปหรือไม่อยู่ในตำแหน่งที่เอเจนต์ตรวจสอบ เช่น ไม่ได้ติดตั้ง Node, ไม่มี npx หรือมีการอ้างอิง virtualenv Python ด้วยชื่อเปล่าๆ ให้แก้ไขคำสั่งเป็น absolute path หรือติดตั้ง runtime ให้เรียบร้อยแล้วเชื่อมต่อใหม่
เซิร์ฟเวอร์ stdio เชื่อมต่อแล้วตัดการเชื่อมต่อทันที ฝั่งไคลเอนต์จะบันทึกข้อผิดพลาด JSON parse error เช่น Unexpected token 'S', "Server sta"... is not valid JSON หรือ Failed to parse message สาเหตุเกิดจากเซิร์ฟเวอร์เขียน log ลงใน stdout เสมอ ในการสื่อสารผ่าน stdio นั้น stdout คือช่องทางของ JSON-RPC ดังนั้นข้อความใดๆ ที่หลุดเข้าไปจะทำให้ stream เสียหายและ handshake ล้มเหลว ใน Node การใช้ console.log จะส่งผลไปยัง stdout ให้ใช้ console.error แทน ใน Python การใช้ print() เปล่าๆ จะส่งผลไปยัง stdout ให้เขียน log ด้วย logging ที่กำหนดค่าเป็น sys.stderr หรือส่งผ่าน file=sys.stderr กฎนี้เป็นข้อบังคับเด็ดขาด: บน stdio ต้องมีเฉพาะ JSON-RPC บน stdout เท่านั้น ส่วนข้อความสำหรับมนุษย์ต้องส่งออกทาง stderr ทั้งหมด
เซิร์ฟเวอร์ระยะไกลหมดเวลาหรือปิดการเชื่อมต่อระหว่าง handshake ไคลเอนต์จะแจ้งความล้มเหลวด้วย MCP error -32000: Connection closed หรือ Inspector ค้างอยู่ที่สถานะ Connect และไม่แสดงรายการเครื่องมือ สาเหตุเมื่ออยู่หลัง nginx คือการทำ buffering: พร็อกซีจะกักเก็บ SSE stream ไว้แทนที่จะส่งออกทันที ทำให้ไคลเอนต์รอการตอบกลับที่ไม่มาถึง ให้เพิ่ม proxy_buffering off; (และส่วนที่เหลือของบล็อกในขั้นตอนที่ 4) ลงใน location ตรวจสอบด้วย curl -N กับ URL สาธารณะ คุณควรเห็นข้อมูล event ทยอยเข้ามา ไม่ใช่มาพร้อมกันทั้งหมดในตอนท้าย
การยืนยันตัวตนถูกปฏิเสธ ไคลเอนต์รายงาน Error POSTing to endpoint (HTTP 401) หรือ 401 Unauthorized อาจเกิดจาก header หายไป, token ไม่ถูกต้อง หรือตัวแปรไม่ถูกขยายค่า ซึ่งเป็นปัญหาที่พบบ่อย หาก MCP_TOKEN ไม่ถูกตั้งค่าไว้เมื่อไคลเอนต์อ่าน config, Claude Code จะแจ้งเตือนเกี่ยวกับตัวแปรที่หายไปใน claude mcp list และส่งข้อความตามตัวอักษร ทำให้ nginx เห็นเป็น Bearer ${MCP_TOKEN} หากตัวแปรมีชื่อซ้ำกับของ Claude Code, ผู้ให้บริการคลาวด์ หรือ credential อื่นๆ ในสภาพแวดล้อมของคุณ ค่าจะถูกอ่านเป็นค่าว่าง และ nginx จะเห็นเป็น Bearer โดยไม่มีค่า ให้ลอง echo ตัวแปรนั้น, เพิ่ม header ใหม่อีกครั้ง และตรวจสอบว่าข้อมูลไบต์ตรงกับ token ใน if ของ nginx อย่างถูกต้อง
บริการไม่สามารถเริ่มทำงานภายใต้ systemd journalctl -u mcp-ops จะแสดง ModuleNotFoundError: No module named 'mcp' โดย ExecStart ชี้ไปยัง Python ของระบบแทนที่จะเป็น venv interpreter หรืออาจเกิดจาก Address already in use เนื่องจากมีกระบวนการอื่นใช้งานพอร์ต 8000 อยู่ ให้ค้นหาด้วย sudo ss -ltnp | grep 8000
FAQ
MCP server คืออะไรกันแน่?
มันคือโปรแกรมที่เปิดเผยเครื่องมือและทรัพยากรให้กับ AI client ผ่านทาง Model Context Protocol โดยใช้ JSON-RPC 2.0 ตัวโมเดล AI จะไม่รันเครื่องมือด้วยตัวเอง แต่จะส่งคำขอไปยัง client ของมัน จากนั้น client จะเรียกใช้งาน MCP server และ server จะเป็นผู้ดำเนินการพร้อมส่งผลลัพธ์กลับมา เนื่องจากโปรโตคอลนี้เป็นมาตรฐาน เซิร์ฟเวอร์ตัวเดียวจึงสามารถทำงานร่วมกับ client ใดก็ได้ที่รองรับ ไม่ว่าจะเป็น Claude Code, Claude Desktop หรือ Gemini CLI
stdio และ HTTP transport แตกต่างกันอย่างไร?
stdio server จะถูกเปิดใช้งานโดย client ในฐานะ child process และสื่อสารผ่าน stdin/stdout ดังนั้นมันจึงมีวงจรชีวิตผูกติดกับ client หนึ่งตัวบนเครื่องเดียวโดยไม่จำเป็นต้องใช้เครือข่ายหรือการยืนยันตัวตน ส่วน HTTP server เป็นบริการเครือข่ายที่ทำงานต่อเนื่องซึ่ง client หลายตัวสามารถเข้าถึงได้พร้อมกัน จึงเป็นเหตุผลที่ต้องใช้ TLS และการยืนยันตัวตน ให้ใช้ stdio สำหรับเครื่องมือที่ใช้งานเฉพาะที่และเป็นผู้ใช้คนเดียว และใช้ HTTP (หรือ Streamable HTTP ในเซิร์ฟเวอร์ปัจจุบัน) สำหรับสิ่งที่ต้องมีการแชร์หรือทำงานตลอดเวลา
ฉันจะรักษาความปลอดภัยให้กับ remote MCP server ได้อย่างไร?
ให้สันนิษฐานว่ามันมีสิทธิ์เข้าถึงไฟล์ ฐานข้อมูล หรือ shell ของคุณ ดังนั้นห้ามเปิดเผยมันโดยไม่มีการยืนยันตัวตนเด็ดขาด วิธีที่ดีที่สุดคือผูกไว้กับ localhost และเข้าถึงผ่าน SSH tunnel หรือ VPN ส่วนตัว หากจำเป็นต้องเปิดเป็นสาธารณะ ให้วางไว้หลัง reverse proxy ที่บังคับใช้ bearer token หรือ MCP OAuth flow ให้สร้าง token ด้วย openssl rand -hex 32 และห้ามผูกเซิร์ฟเวอร์ไว้กับ 0.0.0.0 โดยไม่มีมาตรการเหล่านี้ป้องกันอยู่ข้างหน้า
ฉันจะแก้ไขปัญหาเซิร์ฟเวอร์ที่ไม่ยอมเริ่มทำงานได้อย่างไร?
ขั้นแรกให้ตรวจสอบ claude mcp list และ ✘ Failed to connect หากพบ spawn ... ENOENT หมายความว่าคำสั่งหรือ runtime นั้นหายไป ให้แก้ไข path หรือติดตั้งให้เรียบร้อย หากเซิร์ฟเวอร์เชื่อมต่อได้แต่หลุดพร้อมข้อความ JSON parse error แสดงว่าเซิร์ฟเวอร์กำลังบันทึก log ลงใน stdout ซึ่งทำให้ stream ของ JSON-RPC เสียหาย ให้ย้ายการบันทึก log ทั้งหมดไปที่ stderr แทน สำหรับปัญหาอื่น ๆ ให้รันคำสั่งนั้นโดยตรงผ่าน MCP Inspector ซึ่งจะช่วยรันเซิร์ฟเวอร์แบบแยกส่วน เพื่อให้คุณสามารถแยกแยะได้ว่าปัญหาเกิดจากบั๊กของเซิร์ฟเวอร์หรือการตั้งค่าของ client