سرور مرجع زمان MCP روی نسخه‌ی 2.3.0 کتابخانه‌ی mcp بالا نمی‌آید و با خطای ImportError: cannot import name 'McpError' می‌میرد. در این نوشته روی همین نسخه‌ها اندازه گرفتم که uvx mcp-server-time سالم کار می‌کند، نخستین فراخوانی واقعی چه برمی‌گرداند، و چه اتفاقی می‌افتد وقتی کسی mcp>=2 را به همان محیط اضافه کند. لحظه‌ی خواندن داده: ۱۱ مهر ۱۴۰۵، برابر با ۳ اکتبر ۲۰۲۶.

سرور زمان کجاست و چه چیزی در آن شکسته است

سرور زمان یکی از هفت سرور مرجع پروژه‌ی modelcontextprotocol/servers است: دو ابزار می‌دهد، زمان فعلی یک منطقه‌ی زمانی و تبدیل زمان میان دو منطقه. نام بسته‌ی پایتونی‌اش mcp-server-time است و آخرین نسخه‌ی پایدارش ۲۰۲۶.8.18 است که در ۱۸ اوت ۲۰۲۶ روی PyPI نشست.

فایل pyproject آن وابستگی را این‌طور می‌نویسد: mcp>=1.29.0,<2. یعنی بسته عمدا به خط 1 پین شده است. کامیتی که این پین را گذاشت در ۱۸ اوت ۲۰۲۶ با عنوان fix(python): require mcp>=1.29.0,<2 in fetch, git, and time ثبت شده است.

راهنمای خود سرور هم دلیل را صریح می‌نویسد: SDK نسخه‌ی 2 نام‌هایی را که این سرور استفاده می‌کند تغییر داده و پورت به نسخه‌ی 2 در جریان است. پس این یک خرابی ناگهانی نیست، بلکه یک پین آگاهانه است که هنوز برداشته نشده.

اولین اجرا: uvx چه چیزی را واقعا بالا می‌آورد

راه نصبی که راهنما توصیه می‌کند uvx است و همان را اجرا کردم. مستندات uvx توضیح می‌دهد این ابزار یک محیط موقت برای اجرای یک بسته می‌سازد و بعد از اجرا رهایش می‌کند.

$ uvx mcp-server-time --help
usage: mcp-server-time [-h] [--local-timezone LOCAL_TIMEZONE]

give a model the ability to handle time queries and timezone conversions

options:
  -h, --help            show this help message and exit
  --local-timezone LOCAL_TIMEZONE
                        Override local timezone
Installed 32 packages in 70ms

عدد ۳۲ بسته در هفتاد میلی‌ثانیه یعنی این مسیر یک نصب تازه نیست، بلکه یک کش آماده است. نسخه‌ای که واقعا بالا می‌آید این است، نه آنچه در README نوشته شده:

$ uvx --from mcp-server-time -- python -c "import importlib.metadata as m; print(m.version('mcp'))"
1.30.0

$ uvx --from mcp-server-time -- python -c \
    "import mcp.shared.exceptions as e; print([n for n in dir(e) if 'rror' in n])"
['ErrorData', 'McpError', 'UrlElicitationRequiredError']

یعنی uvx خودش به پین بسته پایبند است و روی خط 1 می‌ماند. نام McpError در فهرست هست و سرور کار می‌کند. مشکل از جای دیگری شروع می‌شود.

یک کلاینت کوچک و پاسخ واقعی آن

سرور روی stdio حرف می‌زند، پس بدون کلاینت هیچ پاسخی نمی‌بینید. فایل کلاینتی که اجرا کردم ۴۲ خط است و هر پیام JSON-RPC را روی ورودی استاندارد می‌نویسد و یک خط از خروجی را می‌خواند. تنها چیزی که به کد اصلی اضافه کرده‌ام توضیح‌های فارسی داخل بلوک است.

import json
import subprocess

# سرور زمان روی stdio بالا می‌آید و uvx خودش محیط موقت را می‌سازد
proc = subprocess.Popen(
    ["uvx", "mcp-server-time"],
    stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
    text=True, bufsize=1,
)


def send(msg):
    # هر درخواست یک خط JSON روی ورودی است و یک خط پاسخ از خروجی
    proc.stdin.write(json.dumps(msg) + "\n")
    proc.stdin.flush()
    return json.loads(proc.stdout.readline())


print("initialize ->", json.dumps(send({
    "jsonrpc": "2.0", "id": 1, "method": "initialize",
    "params": {"protocolVersion": "2025-06-18", "capabilities": {},
               "clientInfo": {"name": "probe", "version": "0.0.1"}},
})["result"], separators=(",", ":")))

# این اعلان شماره ندارد و پاسخی هم نمی‌آید
proc.stdin.write('{"jsonrpc":"2.0","method":"notifications/initialized"}\n')
proc.stdin.flush()

tools = send({"jsonrpc": "2.0", "id": 2, "method": "tools/list"})["result"]["tools"]
print("tools/list ->", [t["name"] for t in tools])


def call(name, args, label):
    # متن ابزار داخل content برمی‌گردد، نه در سطح پاسخ
    r = send({"jsonrpc": "2.0", "id": 9, "method": "tools/call",
              "params": {"name": name, "arguments": args}})
    body = r["result"]
    print(label, "->", body["content"][0]["text"].replace("\n", " ").replace("  ", ""))


call("get_current_time", {"timezone": "Asia/Tehran"}, "get_current_time Asia/Tehran")
# تبدیل ۰۹:۳۰ تهران به برلین، پرکاربردترین ابزار این سرور
call("convert_time", {"source_timezone": "Asia/Tehran", "time": "09:30",
                      "target_timezone": "Europe/Berlin"}, "convert_time 09:30 Tehran->Berlin")
call("get_current_time", {"timezone": "Tehran/Tehran"}, "get_current_time bad key")
proc.terminate()
$ python time_client.py
initialize -> {"protocolVersion":"2025-06-18","capabilities":{"experimental":{},
  "tools":{"listChanged":false}},"serverInfo":{"name":"mcp-time","version":"1.30.0"}}
tools/list -> ['get_current_time', 'convert_time']
convert_time 09:30 Tehran->Berlin -> { "source": { "timezone": "Asia/Tehran",
  "datetime": "2026-10-04T09:30:00+03:30", "day_of_week": "Sunday", "is_dst": false },
  "target": { "timezone": "Europe/Berlin", "datetime": "2026-10-04T08:00:00+02:00",
  "day_of_week": "Sunday", "is_dst": true }, "time_difference": "-1.5h" }

سه چیز را از همین خروجی بخوانید. نخست اینکه برلین در این تاریخ روی ساعت تابستانی است و تهران نیست، پس اختلاف ۱٫۵ ساعت است و نه ۲ ساعت. دوم اینکه is_dst برای هر طرف جدا حساب شده و اگر به آن نگاه نکنید هر تبدیلی را غلط می‌خوانید. سوم اینکه فیلد time_difference از تفاضل دو زمان مشتق شده است و با اعداد دیگر همین پست نمی‌شود سنجید.

نکته‌ای که فقط با تست منفی معلوم می‌شود: کلید منطقه‌ی زمانی باید دقیقا نام IANA باشد و مقدار بی‌ربط خطای پروتکل نمی‌دهد، بلکه یک پاسخ با پرچم isError برمی‌گرداند که متنش قابل خواندن است.

$ python time_client.py   # همان فایل، با کلید اشتباه
get_current_time bad key -> Error processing mcp-server-time query: Invalid timezone: 'No time zone found with key Tehran/Tehran'

پس وقتی ایجنت شما به این خطا برخورد کرد، مشکل از پیکربندی کلاینت نیست. نام منطقه را با Asia/Tehran بنویسید، نه Tehran/Tehran.

شکستن واقعی: نسخه‌ی 2.3.0 را به محیط سرور اضافه کنید

uvx محیط موقت خودش را می‌سازد و شما را نجات می‌دهد. خطر آنجاست که سرور را در یک محیط دائمی نصب کرده باشید و در همان محیط چیز دیگری mcp را بالا ببرد. این را در یک محیط تازه انجام دادم.

$ uv venv cap5
$ uv pip install --python cap5/bin/python mcp-server-time
mcp-server-time 2026.8.18
mcp 1.30.0

$ uv pip install --python cap5/bin/python "mcp>=2"
 - mcp==1.30.0
 + mcp==2.3.0
 + mcp-types==2.3.0
 + httpx2==2.13.1
mcp 2.3.0
mcp-server-time 2026.8.18

$ python -m mcp_server_time
Traceback (most recent call last):
  File ".../mcp_server_time/__init__.py", line 1, in <module>
    from .server import serve
  File ".../mcp_server_time/server.py", line 12, in <module>
    from mcp.shared.exceptions import McpError
ImportError: cannot import name 'McpError' from 'mcp.shared.exceptions'. Did you mean: 'MCPError'?

نکته‌ی مهم این خطا این است که uv pip install هیچ هشداری نداد. نسخه‌ی سرور روی دیسک همان ۲۰۲۶.8.18 ماند و فقط بسته‌ی mcp از 1.30.0 به 2.3.0 رفت. سرور اصلا فرصت چاپ یک خط هم ندارد.

این شکست از یک غلط املایی ساده است. راهنمای مهاجرت رسمی SDK در جدول خود تغییر McpError به MCPError را با همین نشانه‌ی اولین خطا فهرست کرده است. سرور زمان هنوز نام قدیمی را در خط ۱۲ فایل خود صدا می‌زند.

گران‌ترین قسمت ماجرا اینجاست: اگر به جای دو دستور بالا یک بار همه‌چیز را با هم بخواهید، مخاطب پین را نمی‌بیند.

$ uv pip install --python cap5/bin/python "mcp>=2" mcp-server-time
 + mcp==2.3.0
 + mcp-server-time==2026.7.10
$ python -c "import importlib.metadata as m; print(m.version('mcp'), m.version('mcp-server-time'))"
2.3.0 2026.7.10

بسته‌ی سرور به نسخه‌ی ۲۰۲۶.7.10 عقب نشست تا با mcp 2.3.0 کنار بیاید، و آن نسخه هنوز هم McpError را صدا می‌زند، پس خطای بالا تکرار می‌شود. یعنی هیچ نسخه‌ای از سرور زمان با خط 2 کار نمی‌کند. این را روی صفحه‌ی بسته در PyPI و صفحه‌ی mcp هم بررسی کردم: requires_dist نسخه‌ی ۲۰۲۶.8.18 همان mcp<2,>=1.29.0 است.

قاعده‌ی عملی و تاریخ انتشار

قاعده‌ی کاری ساده است: تا وقتی سرور زمان به MCPError مهاجرت نکرده، در هر محیطی که mcp را دستی نصب می‌کنید کران بالای <2 را نگه دارید. اگر از uvx یا uv run استفاده می‌کنید، پین بسته برایتان کار می‌کند و دست به کاری برندارید.

نگاهی به تاریخ انتشار، تصویر روشنی می‌دهد. ساعت‌ها به وقت UTC و از کلید انتشارهای PyPI خوانده شده‌اند.

نسخهتاریخ آپلودجایگاه در تاریخ
2.0.0۲۸ ژوئیه ۲۰۲۶ ۱۳:۴۵نخستین نسخه‌ی پایدار خط 2
2.0.1۲۶ اوت ۲۰۲۶ ۱۰:۴۸پایداری خط 2، پس از 1.29.1
2.2.0۷ سپتامبر ۲۰۲۶ ۱۶:۰۶نسخه‌ی پایدار خط 2 هنگام خواندن این نوشته
2.3.0۲ اکتبر ۲۰۲۶ ۲۲:۰۶نسخه‌ای که سرور زمان را می‌شکند
1.30.0۷ سپتامبر ۲۰۲۶ ۱۴:۳۴آخرین نسخه‌ی پایدار خط 1، هنوز زنده
2026.8.18۱۸ اوت ۲۰۲۶ ۱۶:۰۶آخرین نسخه‌ی سرور زمان، پین‌شده روی خط 1

فاصله‌ی آخرین نسخه‌ی خط 1 تا آخرین نسخه‌ی خط 2، ۲۵ روز و ۷ ساعت است. خط 1 متوقف نشده و یادداشت انتشار SDK می‌گوید این خط در حالت نگهداری است و تنها با اصلاح اشکال بحرانی و وصله‌ی امنیتی به‌روز می‌شود. برای سروری که فقط زمان می‌دهد، همین کافی است.

برای زمینه‌ی پروتکل، پست MCP پایتون نسخه‌ی 2 همین مهاجرت را از سمت سرور نشان داد و پست تست سرور MCP با اینسپکتور راه بررسی یک سرور را بدون نوشتن کلاینت به شما داد. اینجا زاویه‌ی دیگری بود: یک بسته‌ی آماده که هنوز روی خط 1 نشسته است.

یک نکته‌ی جدا که راهنما پوشش نمی‌دهد

نام بسته در npm منتشر نشده است. نه حذف شده، بلکه هرگز آنجا نبوده: رجیستری برای @modelcontextprotocol/server-time کد ۴۰۴ می‌دهد و جست‌وجوی npm هم نامی با این نشانی پیدا نمی‌کند. خواهرانش در رجیستری‌اند؛ server-everything آخرین انتشارش را در ۳۱ اوت ۲۰۲۶ داشت.

پس هر آموزشی که npx -y @modelcontextprotocol/server-time را نشان می‌دهد، از پیش شکسته است. اگر در پیکربندی کلاینت خود سرور زمان را با npx صدا زده‌اید، همان‌جا را به uvx با آرگومان mcp-server-time تغییر دهید و پیکربندی درست را از همان راهنما بردارید.

منابع

  1. راهنمای سرور زمان: اعلام نیاز به SDK خط 1 و وضعیت پورت به نسخه‌ی 2
  2. وابستگی‌های اعلام‌شده‌ی بسته، با کران mcp>=1.29.0,<2
  3. کامیت ۱۸ اوت ۲۰۲۶ که کران بالای <2 را اضافه کرد
  4. مخزن سرورهای مرجع و فهرست سرورهای باقی‌مانده
  5. نسخه‌های منتشرشده‌ی mcp-server-time و تاریخ هر انتشار
  6. نسخه‌های خط 1 و خط 2 بسته‌ی mcp با تاریخ آپلود
  7. یادداشت انتشار SDK پایتون، شامل سیاست نگهداری خط 1
  8. راهنمای مهاجرت رسمی: جدول تغییرات نسخه‌ی 1 به 2
  9. مستندات uvx: ساخت محیط موقت برای اجرای یک بسته