SSD Nodes Learn Hosting plans →
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-27

اجرای سرورهای MCP روی VPS برای ایجنت‌های هوش مصنوعی

راهنمای عملی اجرای سرورهای MCP به صورت stdio و remote HTTP روی VPS. نحوه تنظیم systemd، پیکربندی nginx با TLS و ایمن‌سازی JSON-RPC برای اتصال ایجنت‌ها بدون خطای احراز هویت.

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

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

MCP واقعاً چیست

پروتکل 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 محدودشده با خط جدید را از طریق ورودی استاندارد (stdin) و خروجی استاندارد (stdout) رد و بدل می‌کند. بدون شبکه، بدون پورت، بدون احراز هویت؛ مرز اعتماد، خودِ فرایند است. تقریباً تمام ابزارهای محلی به این صورت عرضه می‌شوند.
  • 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 را نمایش می‌دهد. در داخل نشست (session)، دستور اسلش /mcp ابزارهایی را که سرور ارائه می‌دهد لیست می‌کند (read_file، write_file، list_directory) و عامل اکنون می‌تواند آن‌ها را روی مسیرهایی که مجاز کرده‌اید، فراخوانی کند. یک ابزار دیتابیس نیز ساختار مشابهی دارد؛ کافی است پکیج را جایگزین کرده و یک connection string به عنوان آرگومان نهایی ارسال کنید. البته برای نام فعلی پکیج، مخزن خودِ سرور را بررسی کنید، چرا که سرور مرجع Postgres بیش از یک بار تغییر مالکیت داده است.

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

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

یک stdio server همراه با والد خود از بین می‌رود و برای هر کلاینت یک بار اجرا می‌شود؛ بنابراین اگر دو نشست Claude Code روی دستگاه اجرا کنید که کارها را به یکدیگر محول می‌کنند، هر کدام یک نسخهٔ اختصاصی از ابزار را دریافت می‌کنند. زمانی که به ابزاری نیاز دارید که برای همهٔ کلاینت‌ها فعال بماند، مانند یک ابزار عملیاتی مشترک، یک درگاه دیتابیس، یا چیزی که هم لپ‌تاپ شما و هم سیستم CI شما آن را فراخوانی می‌کنند، به transport از نوع HTTP و یک سرویس واقعی نیاز دارید. در اینجا یک سرور حداقلی پایتون با استفاده از SDK رسمی آورده شده است که یک ابزار را ارائه می‌دهد:

# /opt/mcp-ops/server.py
from mcp.server.fastmcp import FastMCP
import subprocess

mcp = FastMCP("ops-tools", host="127.0.0.1", port=8000)

@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" توجه کنید. سرور فقط به localhost متصل می‌شود و هیچ منبعی خارج از دستگاه نمی‌تواند مستقیماً به آن دسترسی داشته باشد؛ این دقیقاً همان چیزی است که پیش از پیاده‌سازی احراز هویت به آن نیاز دارید. آن را در یک 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]"

Step 3: keep it alive with systemd

A tool that is down when the agent reaches for it is worse than no tool. That matters most when the client is itself a long-lived process: an always-on agent that keeps its memory and schedules across reboots will call these tools on a schedule with nobody watching, so the server has to come back on its own too. Write /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

The absolute path to the venv Python in ExecStart is not optional, point it at /usr/bin/python3 and the process starts with ModuleNotFoundError: No module named 'mcp', because the system interpreter never saw your pip install. Enable and check:

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 should read active (running). The curl comes back HTTP/1.1 400 Bad Request with a JSON-RPC error in the body, the request carried no session and no valid JSON payload, and that is exactly what you want: it proves the port answers and speaks the protocol. Connection refused or an empty reply means the process is not bound where you think; read 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 خودکار برای شما انجام می‌شود؛ این ابزار گواهی را صادر کرده و بر اساس نام دامنه مسیریابی می‌کند و شما فقط کافی است labelهای مربوطه را به کانتینر MCP اضافه کنید. در هر صورت، اکنون reverse proxy تنها چیزی است که روی پورت عمومی قرار دارد و به سرویسی اشاره می‌کند که هنوز آن را ایمن نکرده‌اید. پیش از ثبت URL در هر مکانی، این مورد را برطرف کنید.

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

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

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

  1. آن را منتشر نکنید. سرور را روی 127.0.0.1 نگه دارید و از طریق یک SSH tunnel از لپ‌تاپ خود به آن متصل شوید: 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 bind نکنید. کلاینت سپس توکن را به عنوان یک 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) جایگذاری می‌کند.

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

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

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

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

این ابزار یک رابط کاربری روی http://localhost:6274 راه‌اندازی می‌کند (نسخه‌های جدید یک URL با رشته پرس‌وجوی MCP_PROXY_AUTH_TOKEN چاپ می‌کنند؛ از همان لینک دقیق استفاده کنید، در غیر این صورت رابط کاربری شما را رد می‌کند) و یک پروکسی روی 6277 ایجاد می‌نماید. روی Connect کلیک کنید، سپس List Tools را بزنید و در نهایت با آرگومان‌های واقعی Call Tool را اجرا کنید. اگر در Inspector کار می‌کند اما در agent با خطا مواجه می‌شود، باگ در پیکربندی کلاینت شماست، نه در سرور. برای سرور HTTP از راه دور، transport نوع Streamable HTTP را انتخاب کنید، https://mcp.example.com/mcp را وارد نمایید، هدر Authorization را اضافه کنید و متصل شوید؛ این سریع‌ترین راه برای اثبات صحت احراز هویت و پروکسی پیش از درگیر شدن هرگونه agent است.

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

تکنولوژی MCP با سرعت زیادی در حال پیشرفت است، بنابراین به‌روزرسانی‌ها را طبق یک برنامه زمانی مشخص انجام دهید. سرورهای Node که با npx -y راه‌اندازی می‌شوند، در هر بار اجرا آخرین نسخه را دریافت می‌کنند؛ این کار راحت است اما قابلیت بازتولید (reproducibility) را از بین می‌برد. هنگامی که یک سرور اهمیت پیدا کرد، نسخه دقیقی که تست کرده‌اید را از 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 به‌روزرسانی می‌شوند. هنگام ارتقا، به نسخه مشخصات (spec revision) که SDK شما هدف قرار داده است دقت کنید؛ جهش از مرز SSE به Streamable-HTTP می‌تواند پروتکل انتقالی که کلاینت‌های شما باید درخواست کنند را تغییر دهد.

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

ایجنت نشان می‌دهد که سرور با شکست مواجه شده است. claude mcp list عبارت ✗ Failed to connect را چاپ می‌کند و TUI گزارش MCP server 'filesystem' failed to start را می‌دهد. دستور 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 را گزارش می‌دهد. یا هدر وجود ندارد، یا توکن اشتباه است، یا متغیر shell هنگام خواندن پیکربندی توسط کلاینت خالی بوده است. این یک تله رایج است، زیرا ${MCP_TOKEN} اگر متغیر تنظیم نشده باشد به هیچ تبدیل می‌شود و 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 اجرا کنید که سرور را به‌صورت ایزوله هدایت می‌کند تا بتوانید تفاوت بین باگ سرور و باگ پیکربندی کلاینت را تشخیص دهید.