SSD Nodes Learn Hosting plans →
راهنماها Matt Connorتوسط Matt Connor

اجرای MCP روی VPS برای agentهای کدنویسی هوش مصنوعی

راه‌اندازی MCP روی VPS با transportهای stdio و HTTP، اجرای systemd، TLS و احراز هویت را یاد بگیرید و خطاهای JSON-RPC و endpointهای بدون احراز هویت را رفع کنید.

آنچه می‌سازید

دو پیکربندی عملیاتی MCP روی یک VPS. اولی یک سرور stdio است؛ ابزاری برای فایل‌سیستم یا دیتابیس که Claude Code آن را به عنوان یک child process اجرا کرده و از طریق pipe با آن ارتباط برقرار می‌کند. دومی یک سرور remote HTTP است که به عنوان یک سرویس شبکهٔ پایدار تحت systemd و یک reverse proxy از نوع nginx با TLS اجرا می‌شود و توسط هر کلاینت MCP که به آن متصل شوید، قابل دسترسی است. نصب هر یک از این دو مورد ساده است. بخش عمدهٔ این راهنما به دو موردی اختصاص دارد که در عمل چالش‌برانگیز هستند: تمیز نگه داشتن جریان JSON-RPC و عدم قرار دادن هیچ endpoint ابزاریِ بدون احراز هویت روی اینترنت عمومی.

MCP واقعاً چیست

پروتکل Model Context Protocol یک روش استاندارد برای کلاینت‌های هوش مصنوعی، مانند Claude Code، Claude Desktop، Gemini CLI روی یک VPS یا اسکریپت شخصی شماست تا ابزارهای خارجی را فراخوانی کرده و منابع خارجی را بخوانند. خود مدل هیچ چیزی را اجرا نمی‌کند. مدل از کلاینت درخواست می‌کند، کلاینت با استفاده از JSON-RPC 2.0 با یک سرور MCP صحبت می‌کند، سرور ابزار را اجرا کرده و نتیجه را بازمی‌گرداند. آن کلاینت همان چیزی است که مردم هنگام صحبت درباره agent harness به آن اشاره می‌کنند: حلقه‌ای پیرامون مدل که مالک لیست ابزارها، بررسی‌های مجوز و وضعیت نشست (session state) است و MCP صرفاً روشی برای گسترش بخش ابزارهای آن است. یک پروتکل واحد باعث می‌شود سروری که یک‌بار می‌نویسید، با هر کلاینتی که از MCP پشتیبانی می‌کند کار کند. اگر این تفکیک برای شما جدید است، و به‌ویژه اگر این پرسش مطرح است که مدل چگونه تصمیم می‌گیرد از یک ابزار استفاده کند، یک مسیر مرحله‌بندی‌شده از اصول اولیه عامل‌ها ارزش یک ساعت وقت گذاشتن را دارد، پیش از آنکه اعتبارنامه‌های واقعی را به یکی از این سرورها بسپارید.

دو نوع انتقال (transport) وجود دارد و باقی این راهنما بر اساس آن‌ها تقسیم می‌شود:

  • stdio. کلاینت، سرور را به‌عنوان یک پردازش فرزند (child process) ایجاد می‌کند و پیام‌های JSON-RPC محدود به خط جدید را از طریق ورودی استاندارد و خروجی استاندارد رد و بدل می‌کند. بدون شبکه، بدون پورت و بدون احراز هویت؛ مرز اعتماد، خودِ پردازش است. تقریباً تمام ابزارهای محلی از این روش استفاده می‌کنند.
  • HTTP قابل استریم (و نسخه قدیمی‌تر آن، HTTP+SSE). سرور یک سرویس وب است که به‌طور مداوم اجرا می‌شود. کلاینت از طریق HTTP متصل می‌شود و سرور می‌تواند پاسخ‌ها را به‌عنوان Server-Sent Events استریم کند. این روشی است که با آن می‌توانید یک سرور را با چندین کلاینت به اشتراک بگذارید یا ابزاری را اجرا کنید که باید به‌طور دائم روی سیستم باقی بماند.

زمانی که ابزار متعلق به یک ماشین و یک کاربر است، stdio را انتخاب کنید. زمانی که ابزار یک سرویس اشتراکی است، HTTP را انتخاب کنید.

پیش‌نیازها و نکات مهم

فرض بر این است که یک VPS با سیستم‌عامل Ubuntu 24.04 و دسترسی root یا sudo در اختیار دارید. علاوه بر این:

  • محیط اجرایی (Runtime) برای زبان برنامه‌نویسی سرور. اکثر سرورهای مرجع با Node یا Python نوشته شده‌اند. Ubuntu 24.04 به‌صورت پیش‌فرض Node 18 را ارائه می‌دهد، اما بسیاری از بسته‌های فعلی MCP به Node 20 یا جدیدتر نیاز دارند؛ بنابراین به‌جای اعتماد به apt، یک نسخه LTS فعلی را از طریق NodeSource یا nvm نصب کنید. Python 3.12 از قبل موجود است.
  • یک دامنه و رکورد DNS A. این مورد فقط برای سرور HTTP از راه دور لازم است، زیرا TLS به نامی نیاز دارد که به این VPS اشاره کند. مثال‌های stdio اصلاً به DNS نیاز ندارند.
  • 512 MB رم کافی است. سرورهای MCP فرآیندهای سبک JSON-RPC هستند؛ هزینه حافظه بستگی به ابزاری دارد که استفاده می‌کنید (مانند درایور دیتابیس یا کش فایل)، نه خود پروتکل.
  • این مشخصات (Spec) جدید و در حال تغییر است. در بازبینی 2025-03-26، پروتکل HTTP+SSE با Streamable HTTP جایگزین شد و SSE منسوخ اعلام گردید. SSE همچنان کار می‌کند و بسیاری از سرورها از آن پشتیبانی می‌کنند؛ بنابراین هرگونه وابستگی به transport را به‌جای یک اصل ثابت، موردی در نظر بگیرید که باید با یادداشت‌های انتشار (release notes) سرور مطابقت داده شود.

گام 1: متصل کردن یک سرور stdio به Claude Code

با سرور فایل‌سیستم شروع کنید؛ این سرور رسمی است، به‌طور فعال نگهداری می‌شود و تنها به 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 در ریشه پروژه ایجاد می‌کند:

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

هنوز چیزی در حال اجرا نیست. دفعه بعد که Claude Code را در این دایرکتوری اجرا کنید، agent فایل .mcp.json را می‌خواند، npx -y @modelcontextprotocol/server-filesystem ... را به عنوان یک child process فراخوانی می‌کند و handshake مربوط به MCP را از طریق stdin/stdout آن پردازش انجام می‌دهد. برای تأیید موفقیت‌آمیز بودن آن:

claude mcp list

یک سرور سالم، دستور خود و یک تیک سبز رنگ یعنی filesystem: npx -y @modelcontextprotocol/server-filesystem ... - ✔ Connected را نمایش می‌دهد. در داخل نشست، دستور اسلش /mcp ابزارهایی که سرور در دسترس قرار داده است (read_text_file، write_file، list_directory) را لیست می‌کند و agent اکنون می‌تواند آن‌ها را روی مسیرهایی که اجازه داده‌اید، فراخوانی کند. ابزار دیتابیس نیز ساختار مشابهی دارد؛ کافی است پکیج را جایگزین کرده و یک connection string به عنوان آرگومان نهایی ارسال کنید. البته حتماً مخزن خودِ سرور را برای نام پکیج فعلی بررسی کنید، چرا که سرور مرجع Postgres چندین بار تغییر مالکیت داده است.

این دقیقاً هدف اصلی اجرای agent روی سرور است: نشست Claude Code روی VPS داخل tmux باقی می‌ماند و سرورهای stdio آن دقیقاً در کنارش اجرا می‌شوند و دسترسی مستقیم به فایل‌های پروژه و سرویس‌های محلی دارند، بدون نیاز به رفت‌وبرگشت‌های شبکه. هنگامی که agent هم write_file و هم read_text_file را در اختیار داشته باشد، ارزش دارد که این دسترسی را با مهارتی که آن را به سمت کوچک‌ترین تغییرِ کارآمد سوق می‌دهد ترکیب کنید؛ زیرا ابزار فایل‌سیستم باعث می‌شود یک بازنویسی گسترده، دقیقاً به اندازه یک اصلاح دو خطی ارزان تمام شود. این اتصال فراتر از فایل‌های محلی نیز گسترش می‌یابد: اگر از قبل یک موتور جستجو روی VPS خود اجرا می‌کنید، می‌توانید نمونه SearXNG خود را به عنوان یک ابزار جستجو به agent بدهید؛ این کار باعث می‌شود کوئری‌ها روی سرور خودتان باقی بمانند، اما متن صفحات غیرقابل‌اعتماد مستقیماً به کانتکستی که agent روی آن کار می‌کند، وارد شود.

گام 2: ساخت یک سرور HTTP از راه دور

یک سرور stdio همراه با والد خود خاتمه می‌یابد و به ازای هر کلاینت یک بار اجرا می‌شود؛ بنابراین اگر دو نشست Claude Code روی دستگاه اجرا کنید که کارها را به یکدیگر محول می‌کنند، هر کدام یک کپی اختصاصی از ابزار را دریافت می‌کنند. زمانی که به ابزاری نیاز دارید که برای همه کلاینت‌ها فعال بماند، مانند یک ابزار عملیاتی اشتراکی، یک درگاه دیتابیس، یا چیزی که هم لپ‌تاپ شما و هم سیستم CI شما آن را فراخوانی می‌کنند، به transport از نوع HTTP و یک سرویس واقعی نیاز دارید. در اینجا یک سرور حداقلی پایتون با استفاده از 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" توجه کنید. سرور فقط به localhost متصل می‌شود و هیچ‌چیز خارج از دستگاه نمی‌تواند مستقیماً به آن دسترسی داشته باشد؛ این دقیقاً همان چیزی است که پیش از پیاده‌سازی احراز هویت به آن نیاز دارید. این نسخه 2.x از SDK است که pip install "mcp[cli]" امروزه دریافت می‌کند (نسخه 2.3.0 تا تاریخ 2026-10-20). در این نسخه، نام FastMCP به MCPServer تغییر یافته و host و port از سازنده (constructor) به run() منتقل شده‌اند، و import قدیمی mcp.server.fastmcp اکنون با خطای ModuleNotFoundError مواجه می‌شود. آن را در یک virtualenv اختصاصی نصب کنید تا systemd مسیر مفسر پایداری داشته باشد:

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

ابزاری که هنگام نیاز عامل (agent) در دسترس نباشد، از نبودن آن بدتر است. این موضوع زمانی اهمیت بیشتری پیدا می‌کند که کلاینت خود یک پردازش با طول عمر زیاد باشد: یک عامل همیشه فعال که حافظه و زمان‌بندی‌های خود را در طول reboot حفظ می‌کند، این ابزارها را طبق زمان‌بندی و بدون نظارت انسانی فراخوانی خواهد کرد؛ بنابراین سرور نیز باید به‌طور خودکار بازیابی شود. فایل /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/mcp

خروجی status باید active (running) را نشان دهد. curl با یک خطای JSON-RPC در بدنه پاسخ HTTP/1.1 400 Bad Request بازمی‌گردد؛ درخواست فاقد نشست (session) و payload معتبر JSON بوده است و این دقیقاً همان چیزی است که می‌خواهید: این ثابت می‌کند که پورت پاسخ می‌دهد و پروتکل را می‌فهمد. Connection refused یا پاسخ خالی به این معنی است که پردازش در جایی که فکر می‌کنید bind نشده است؛ journalctl -u mcp-ops -n 50 را مطالعه کنید.

گام 4: قرار دادن TLS و یک reverse proxy در مقابل سرویس

سرور روی localhost گوش می‌دهد. برای دسترسی به آن از هر مکانی، باید TLS را در nginx خاتمه دهید و درخواست‌ها را به داخل پروکسی کنید. ابتدا nginx را نصب کنید، یک گواهی با استفاده از Certbot و Let's Encrypt روی nginx دریافت کنید و سپس بلوک location را بنویسید. بخش حیاتی، غیرفعال کردن buffering است؛ زیرا رفتار پیش‌فرض nginx این است که پاسخ را تا زمان تکمیل نهایی نگه می‌دارد که باعث می‌شود جریان SSE برای همیشه متوقف بماند:

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 خودکار برای شما انجام می‌شود؛ این ابزار گواهی را صادر کرده و بر اساس نام دامنه مسیریابی می‌کند و شما فقط کافی است برچسب‌های (labels) لازم را به کانتینر MCP اضافه کنید. در هر صورت، اکنون reverse proxy تنها چیزی است که روی پورت عمومی قرار دارد و به سرویسی اشاره می‌کند که هنوز آن را ایمن نکرده‌اید. پیش از ثبت URL در هر جایی، این مورد را اصلاح کنید.

گام 5: قانون امنیتی که بر این مبحث حاکم است

هرگز یک MCP endpoint بدون احراز هویت را در معرض دید قرار ندهید. یک سرور MCP صرفاً یک API فقط‌خواندنی نیست. این سرور دسترسی به ابزارها، فایل‌ها، پایگاه داده و گاهی اوقات یک shell را فراهم می‌کند. یک /mcp باز روی اینترنت عمومی، به غریبه‌ای اجازه می‌دهد همان دسترسی‌هایی را داشته باشد که عامل هوش مصنوعی شما دارد: آن‌ها ابزارهای شما را لیست کرده و سپس آن‌ها را فراخوانی می‌کنند. با آن دقیقاً مانند یک سوکت مدیریتی بدون احراز هویت رفتار کنید، زیرا ماهیت آن همین است. میزان خسارت ناشی از سرقت توکن، به سروری که پشت آن قرار دارد نیز بستگی دارد: سرور MCP فقط‌خواندنی که همراه با ردیاب تمرین openGym عرضه می‌شود تنها می‌تواند داده‌های تمرینی را بازگرداند، در حالی که یک ابزار فایل‌سیستم یا shell، کنترل کل سیستم را واگذار می‌کند.

سه روش دفاعی، به ترتیب اولویت:

  1. آن را منتشر نکنید. سرور را روی 127.0.0.1 نگه دارید و با استفاده از یک تونل SSH از لپ‌تاپ خود به آن دسترسی پیدا کنید: ssh -L 8000:127.0.0.1:8000 matt@vps، سپس کلاینت را به http://127.0.0.1:8000/mcp هدایت کنید. در این حالت هیچ چیزی در معرض دید قرار نمی‌گیرد.
  2. آن را در یک شبکه خصوصی قرار دهید. آدرس تونل را به یک VPN مبتنی بر WireGuard که خودتان میزبانی می‌کنید متصل کنید و اجازه دهید فقط همتایان VPN به آن دسترسی داشته باشند. اینترنت عمومی فقط یک پورت بسته را مشاهده خواهد کرد.
  3. اگر مجبور به عمومی‌سازی هستید، توکن الزامی کنید. پاسخ صحیح، استفاده از جریان OAuth در MCP است که transport پروتکل HTTP به‌طور بومی از آن پشتیبانی می‌کند. حداقلِ عمل‌گرایانه، استفاده از یک bearer token مشترک است که در پروکسی بررسی می‌شود؛ این روش کم‌هزینه است و حملات تصادفی (drive-by) را کاملاً متوقف می‌کند:
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...
}

توکن را با openssl rand -hex 32 تولید کنید و هرگز سرور را بدون یکی از این لایه‌های حفاظتی در مقابل، به 0.0.0.0 متصل نکنید. کلاینت سپس توکن را به عنوان یک 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 به‌طور عمدی متغیرهای اعتباری خود و ارائه‌دهنده ابری شما مانند ANTHROPIC_API_KEY را به عنوان مقدار خالی می‌خواند، که این موضوع شامل سایر اعتبارنامه‌های موجود در محیط شما مانند NPM_TOKEN نیز می‌شود.

هر یک از روش‌های دفاعی فوق، از endpoint محافظت می‌کنند، نه از عاملی که در حال حاضر توکن را در اختیار دارد؛ این نیمه دیگر مشکل است: اگر کلاینت شما DeepSeek Harness باشد، پلاگین‌هایی که تعیین می‌کنند یک عامل مجاز به فراخوانی کدام ابزارهاست و خروجی ابزارها را برای دستورات تزریق‌شده اسکن می‌کنند، این جنبه را پوشش می‌دهند.

گام 6: عیب‌یابی با MCP Inspector

هنگامی که سرور به درستی کار نمی‌کند، به جای حدس زدن از داخل agent، آن را مستقیماً با Inspector که کلاینت تست رسمی مبتنی بر وب است، هدایت کنید. برای یک سرور stdio، همان دستوری را به آن بدهید که agent اجرا می‌کند:

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

این ابزار رابط کاربری خود را از یک وب‌سرور روی http://localhost:6274 ارائه می‌دهد و یک URL حاوی رشته پرس‌وجوی MCP_INSPECTOR_API_TOKEN چاپ می‌کند؛ به جای تایپ کردن آدرس از حافظه، همان لینک را باز کنید. از نسخه v2 (نسخه 2.11.0 که در تاریخ 2026-10-20 جاری است)، بک‌اند توکن را در هر بار بارگذاری به صفحه‌ای که ارائه می‌دهد تزریق می‌کند، بنابراین reload کردن همچنان کار می‌کند و توکن از مسیرهای /api/* که می‌توانند فرآیندها را ایجاد کنند، محافظت می‌نماید. پروکسی v1 جداگانه روی 6277 حذف شده است، اما وب‌سرور تنها شنونده نیست: v2 همچنین یک sandbox برای MCP Apps روی MCP_SANDBOX_PORT (به‌طور پیش‌فرض 6275) و یک سرور اختصاصی app-origin روی MCP_APP_ORIGIN_PORT (به‌طور پیش‌فرض 6278) راه‌اندازی می‌کند که هر MCP App که منبع UI آن _meta.ui.domain را اعلام کند، ارائه می‌دهد. اگر از طریق تونل SSH به Inspector دسترسی پیدا می‌کنید یا آن را در یک کانتینر اجرا می‌کنید و به تب Apps نیاز دارید، پورت‌های 6275 و 6278 را به همراه 6274 فوروارد کنید. متصل شوید، تب Tools را باز کنید، یک ابزار را انتخاب کنید، آرگومان‌های واقعی را وارد کرده و آن را فراخوانی کنید. اگر در Inspector کار می‌کند اما در agent با خطا مواجه می‌شود، باگ در پیکربندی کلاینت شماست، نه در سرور. برای سرور HTTP از راه دور، Inspector را بدون دستور اجرا کنید، یک سرور با transport نوع Streamable HTTP و https://mcp.example.com/mcp اضافه کنید، هدر Authorization را در تنظیمات آن قرار دهید و متصل شوید؛ این سریع‌ترین راه برای اثبات صحت احراز هویت و پروکسی پیش از درگیر شدن هرگونه agent است. Inspector هدرها را دقیقاً همان‌طور که نوشته شده‌اند در کاتالوگ سرور خود ذخیره می‌کند، بنابراین آن توکن اکنون در ~/.mcp-inspector/mcp.json نیز قرار دارد.

به‌روز نگه داشتن سرورها

توسعه MCP سریع است، بنابراین به‌روزرسانی‌ها را طبق برنامه انجام دهید. سرورهای Node که با npx -y بدون تعیین نسخه (unpinned) اجرا می‌شوند، هر نسخه‌ای را که npx تشخیص دهد اجرا می‌کنند: اگر در پروژه محلی نسخه‌ای نصب شده باشد همان، در غیر این صورت نسخه‌ای که در کش npm نصب می‌شود. -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 به‌روزرسانی می‌شوند. پیش از ارتقای اصلی (major bump)، یادداشت‌های انتشار را بخوانید: انتقال از SDK نسخه 1.x به 2.x پایتون، کد 1.x را در زمان import از کار می‌اندازد و راهنمای مهاجرت خودِ SDK توصیه می‌کند تا زمان تکمیل مهاجرت، یک حد بالای <2 مانند "mcp[cli]<2" تعیین کنید. هنگام ارتقا، به نسخه مشخصات (spec revision) که SDK شما هدف قرار می‌دهد دقت کنید؛ جهش از مرز SSE به Streamable-HTTP می‌تواند پروتکل انتقالی که کلاینت‌های شما باید درخواست کنند را تغییر دهد.

حالت‌های شکست و پیام‌های مرتبط

ایجنت نشان می‌دهد که سرور با شکست مواجه شده است. claude mcp list عبارت ✘ Failed to connect را چاپ می‌کند و نسخه‌های فعلی، جزئیات خطا را به انتهای همان خط اضافه می‌کنند. دستور claude --debug را اجرا کنید؛ معمولاً با Error: spawn npx ENOENT مواجه می‌شوید که نشان می‌دهد دستور در PATH ایجنت یافت نمی‌شود. محیط اجرا (runtime) وجود ندارد یا در مسیری که ایجنت جستجو می‌کند نیست: Node نصب نشده است، npx وجود ندارد، یا یک Python virtualenv با نام ساده فراخوانی شده است. دستور را به یک مسیر مطلق تغییر دهید یا محیط اجرا را نصب کرده و سپس دوباره متصل شوید.

یک سرور stdio متصل می‌شود، اما بلافاصله قطع می‌گردد. کلاینت یک خطای تجزیه JSON گزارش می‌دهد، چیزی شبیه به Unexpected token 'S', "Server sta"... is not valid JSON یا Failed to parse message. علت همیشه یکسان است: سرور یک خط لاگ را در stdout نوشته است. در stdio، خروجی stdout همان کانال JSON-RPC است، بنابراین هر متن اضافی باعث خرابی جریان داده و شکست در handshake می‌شود. در Node، دستور console.log به stdout می‌رود؛ از console.error استفاده کنید. در Python، یک دستور ساده print() به stdout می‌رود؛ لاگ‌ها را با logging که روی sys.stderr تنظیم شده بنویسید، یا از file=sys.stderr استفاده کنید. قانون مطلق این است: در stdio، فقط JSON-RPC روی stdout مجاز است و هر متن انسانی باید به stderr ارسال شود.

یک سرور از راه دور دچار timeout می‌شود یا در میانه handshake قطع می‌گردد. کلاینت با خطای MCP error -32000: Connection closed مواجه می‌شود، یا Inspector روی وضعیت Connect گیر می‌کند و ابزارها را لیست نمی‌کند. دلیل این امر در پشت nginx، بافرینگ است: پروکسی جریان SSE را به جای ارسال فوری (flush)، نگه می‌دارد، بنابراین کلاینت منتظر پاسخی می‌ماند که هرگز نمی‌رسد. دستور proxy_buffering off; (و بقیه بلوک در مرحله 4) را به location اضافه کنید. با دستور curl -N در برابر URL عمومی بررسی کنید؛ باید داده‌های رویداد را به‌صورت تدریجی ببینید، نه اینکه همه آن‌ها یکجا در پایان ارسال شوند.

احراز هویت رد می‌شود. کلاینت Error POSTing to endpoint (HTTP 401) یا مستقیماً 401 Unauthorized را گزارش می‌دهد. یا هدر وجود ندارد، یا توکن اشتباه است، یا متغیر مقداردهی نشده است که یک تله رایج است. اگر MCP_TOKEN هنگام خواندن پیکربندی توسط کلاینت تنظیم نشده باشد، Claude Code در claude mcp list درباره متغیر گمشده هشدار می‌دهد و متن تحت‌اللفظی را ارسال می‌کند، بنابراین nginx مقدار Bearer ${MCP_TOKEN} را می‌بیند. متغیری که همنام با یکی از اعتبارنامه‌های Claude Code، ارائه‌دهنده ابری شما یا سایر محیط‌های کاری باشد، خالی خوانده می‌شود و nginx مقدار Bearer را بدون هیچ مقداری دریافت می‌کند. متغیر را echo کنید، هدر را دوباره اضافه کنید و تأیید کنید که بایت‌های دقیق با توکن موجود در if در nginx مطابقت دارند.

سرویس تحت systemd اجرا نمی‌شود. دستور journalctl -u mcp-ops عبارت ModuleNotFoundError: No module named 'mcp' را نشان می‌دهد، ExecStart به جای مفسر venv به Python سیستم اشاره می‌کند. یا Address already in use رخ می‌دهد، یعنی یک پردازش دیگر پورت 8000 را اشغال کرده است؛ آن را با sudo ss -ltnp | grep 8000 پیدا کنید.

FAQ

سرور MCP دقیقاً چیست؟

این یک برنامه است که ابزارها و منابع را از طریق پروتکل Model Context Protocol و با استفاده از JSON-RPC 2.0 در اختیار کلاینت هوش مصنوعی قرار می‌دهد. مدل هوش مصنوعی هرگز ابزار را مستقیماً اجرا نمی‌کند؛ بلکه از کلاینت خود درخواست می‌کند، کلاینت با سرور MCP تماس می‌گیرد و سرور عملیات را اجرا کرده و نتیجه را بازمی‌گرداند. از آنجا که این پروتکل استاندارد است، یک سرور با هر کلاینت سازگاری کار می‌کند، خواه آن کلاینت Claude Code باشد، یا Claude Desktop یا Gemini CLI.

تفاوت بین transport از نوع stdio و HTTP چیست؟

سرور stdio توسط کلاینت به عنوان یک child process اجرا می‌شود و از طریق stdin/stdout ارتباط برقرار می‌کند؛ بنابراین با یک کلاینت روی یک ماشین متولد شده و از بین می‌رود و نیازی به شبکه یا احراز هویت ندارد. سرور HTTP یک سرویس شبکه است که به‌طور مداوم اجرا می‌شود و چندین کلاینت می‌توانند همزمان به آن دسترسی داشته باشند، به همین دلیل به TLS و احراز هویت نیاز دارد. برای ابزارهای محلی و تک‌کاربره از stdio استفاده کنید؛ برای هر سرویس اشتراکی یا دائمی از HTTP (در سرورهای فعلی Streamable HTTP) استفاده کنید.

چگونه یک سرور MCP از راه دور را ایمن کنم؟

فرض کنید این سرور به فایل‌ها، دیتابیس یا shell شما دسترسی ابزاری می‌دهد، بنابراین هرگز آن را بدون احراز هویت در معرض دید قرار ندهید. بهترین روش این است که آن را روی localhost محدود نگه دارید و از طریق یک SSH tunnel یا VPN خصوصی به آن دسترسی پیدا کنید؛ اگر مجبور به عمومی‌سازی آن هستید، آن را پشت یک reverse proxy قرار دهید که bearer token یا جریان MCP OAuth را اعمال کند. توکن را با openssl rand -hex 32 تولید کنید و هرگز سرور را بدون یکی از این لایه‌های حفاظتی روی 0.0.0.0 bind نکنید.

چگونه سروری که اجرا نمی‌شود را عیب‌یابی کنم؟

ابتدا claude mcp list را بررسی کنید؛ ✘ Failed to connect با spawn ... ENOENT به این معنی است که دستور یا runtime موجود نیست، پس مسیر را اصلاح یا آن را نصب کنید. اگر سرور متصل می‌شود اما با خطای JSON parse قطع می‌گردد، به این دلیل است که سرور در حال لاگ کردن در stdout است و جریان JSON-RPC را مختل می‌کند؛ تمام لاگ‌ها را به stderr منتقل کنید. برای سایر موارد، دستور دقیق را در MCP Inspector اجرا کنید که سرور را در محیطی ایزوله اجرا می‌کند تا بتوانید باگ سرور را از باگ پیکربندی کلاینت تشخیص دهید.