استفاده از سرورهای MCP با آشا

سرورهای MCP (Model Context Protocol) راهی پرطرفدار برای دادن «ابزار» به مدل‌های زبانی‌اند. در این راهنما ابزارهای یک سرور MCP را به فرمت سازگار با OpenAI تبدیل می‌کنیم و از همان endpoint استاندارد آشا فراخوانی‌شان می‌کنیم — بدون هیچ endpoint اضافه.

سرورهای MCP جایگزینی برای فراخوانی ابزار به سبک «سازگار با OpenAI» محسوب می‌شوند. با تبدیل تعریف ابزارهای MCP به تعریف ابزارهای سازگار با OpenAI، می‌توانید از سرورهای MCP به‌همراه درگاه آشا استفاده کنید؛ هرچند پروتکل MCP «داری‌حالت» (stateful) است و به مدیریت نشست (session) نیاز دارد. مثال زیر از کلاینت رسمی MCP استفاده می‌کند و با همین وجود نسبتاً پیچیده است.

ن

تعامل با سرورهای MCP پیچیده‌تر از فراخوانی یک endpoint REST است. برای اپلیکیشن‌های ساده با فراخوانی ابزار، پیشنهاد می‌کنیم مستقیماً از پارامتر tools در endpoint سازگار با OpenAI استفاده کنید و فقط وقتی به سرورهای MCP خاصی نیاز دارید این راهنما را دنبال کنید.

پیش‌نیازها و راه‌اندازی

برای اجرا، اول بسته‌های زیر را نصب کنید:

Terminal bash
pip install mcp openai python-dotenv

سپس فایل .env را با کلید آشا بسازید (قالب sk-asha-...):

ENV .env
ASHA_API_KEY=sk-asha-...

این مثال فرض می‌کند کلاینتی هستید که سرور فایل‌سیستم را با npx اجرا می‌کند؛ پس Node.js هم باید نصب باشد. در سیستم‌عامل مک مسیر /Applications/ فرض شده — روی ویندوز یا لینوکس آن را با پوشهٔ دلخواه‌تان (مثلاً C:UsersyouDocuments یا /home/you/Documents) جایگزین کنید.

کد اولیه و پیکربندی مدل

Python mcp-client.py
import asyncio
from typing import Optional
from contextlib import AsyncExitStack

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

from openai import OpenAI
from dotenv import load_dotenv
import json

load_dotenv()  # load environment variables from .env

MODEL = "~anthropic/claude-sonnet-latest"

SERVER_CONFIG = {
    "command": "npx",
    "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Applications/",
    ],
    "env": None,
}
ن

شناسهٔ مدل را از فهرست مدل‌های آشا انتخاب کنید؛ مدل باید از پارامتر tools پشتیبانی کند (برای مدل‌های چت و کد برقرار است).

تبدیل ابزارهای MCP به ابزارهای OpenAI

تابع کمکی زیر تعریف ابزار MCP را به تعریف ابزار سازگار با OpenAI تبدیل می‌کند:

Python mcp-client.py
def convert_tool_format(tool):
    converted_tool = {
        "type": "function",
        "function": {
            "name": tool.name,
            "description": tool.description,
            "parameters": {
                "type": "object",
                "properties": tool.inputSchema["properties"],
                "required": tool.inputSchema["required"],
            },
        },
    }
    return converted_tool

کلاینتی MCP کامل

و این خودِ کلاینتی است — حدود ۱۰۰ خط ناگزیر. توجه کنید که SERVER_CONFIG داخل کلاینت ثابت نوشته شده؛ بدیهی است می‌توان آن را برای سرورهای MCP دیگر پارامتریزه کرد. تنها تفاوت این کلاینت با نسخه‌های دیگر سرویس‌ها، آدرس base_url است که به https://app.asha-ai.ir/v1/ تغییر کرده:

Python mcp-client.py
class MCPClient:
    def __init__(self):
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()
        self.openai = OpenAI(
            base_url="https://app.asha-ai.ir/v1/"
        )

    async def connect_to_server(self, server_config):
        server_params = StdioServerParameters(**server_config)
        stdio_transport = await self.exit_stack.enter_async_context(
            stdio_client(server_params)
        )
        self.stdio, self.write = stdio_transport
        self.session = await self.exit_stack.enter_async_context(
            ClientSession(self.stdio, self.write)
        )

        await self.session.initialize()

        # List available tools from the MCP server
        response = await self.session.list_tools()
        print(
            "nConnected to server with tools:",
            [tool.name for tool in response.tools],
        )

        self.messages = []

    async def process_query(self, query: str) -> str:
        self.messages.append({"role": "user", "content": query})

        response = await self.session.list_tools()
        available_tools = [convert_tool_format(tool) for tool in response.tools]

        response = self.openai.chat.completions.create(
            model=MODEL,
            tools=available_tools,
            messages=self.messages,
        )
        self.messages.append(response.choices[0].message.model_dump())

        final_text = []
        content = response.choices[0].message
        if content.tool_calls is not None:
            tool_name = content.tool_calls[0].function.name
            tool_args = content.tool_calls[0].function.arguments
            tool_args = json.loads(tool_args) if tool_args else {}

            # Execute tool call
            try:
                result = await self.session.call_tool(tool_name, tool_args)
                final_text.append(
                    f"[Calling tool {tool_name} with args {tool_args}]"
                )
            except Exception as e:
                print(f"Error calling tool {tool_name}: {e}")
                result = None

            self.messages.append(
                {
                    "role": "tool",
                    "tool_call_id": content.tool_calls[0].id,
                    "name": tool_name,
                    "content": result.content,
                }
            )

            response = self.openai.chat.completions.create(
                model=MODEL,
                max_tokens=1000,
                messages=self.messages,
            )

            final_text.append(response.choices[0].message.content)
        else:
            final_text.append(content.content)

        return "n".join(final_text)

    async def chat_loop(self):
        """Run an interactive chat loop"""
        print("nMCP Client Started!")
        print("Type your queries or 'quit' to exit.")

        while True:
            try:
                query = input("nQuery: ").strip()
                result = await self.process_query(query)
                print("Result:")
                print(result)
            except Exception as e:
                print(f"Error: {str(e)}")

    async def cleanup(self):
        await self.exit_stack.aclose()


async def main():
    client = MCPClient()
    try:
        await client.connect_to_server(SERVER_CONFIG)
        await client.chat_loop()
    finally:
        await client.cleanup()


if __name__ == "__main__":
    import sys
    asyncio.run(main())

اجرا و خروجی

همهٔ کد بالا را در mcp-client.py ذخیره کنید و اجرا کنید:

Terminal bash
% python mcp-client.py

Secure MCP Filesystem Server running on stdio
Allowed directories: [ '/Applications' ]

Connected to server with tools: ['read_file', 'read_multiple_files', 'write_file'...]

MCP Client Started!
Type your queries or 'quit' to exit.

Query: Do I have microsoft office installed?

Result:
[Calling tool list_allowed_directories with args {}]
I can check if Microsoft Office is installed in the Applications folder:

Query: continue

Result:
[Calling tool search_files with args {'path': '/Applications', 'pattern': 'Microsoft'}]
Now let me check specifically for Microsoft Office applications:

Query: continue

Result:
I can see from the search results that Microsoft Office is indeed installed on your system.
The search found the following main Microsoft Office applications:

1. Microsoft Excel - /Applications/Microsoft Excel.app
2. Microsoft PowerPoint - /Applications/Microsoft PowerPoint.app
3. Microsoft Word - /Applications/Microsoft Word.app
4. OneDrive - /Applications/OneDrive.app (which includes Microsoft SharePoint integ...

مدل از طریق آشا تصمیم می‌گیرد کدام ابزار را صدا بزند، نام‌ها و آرگومان‌ها را تولید می‌کند، ابزار روی سرور MCP اجرا می‌شود و نتیجه به‌صورت پیام «tool» به مدل برگردانده می‌شود تا پاسخ نهایی را بنویسد.

چرا با آشا؟

  • بدون endpoint اضافه: همین /v1/chat/completions استاندارد، پارامتر tools را به‌صورت کامل به سمت مدل عبور می‌دهد.
  • یک کلید برای همهٔ مدل‌ها: فقط MODEL را عوض کنید تا همین کلاینت روی مدل دیگری از فهرست مدل‌های آشا اجرا شود.
  • صورت‌حساب به تومان: هزینهٔ توکن‌ها از گزارشِ مصرفِ واقعیِ ارائه‌دهنده شارژ می‌شود؛ در داشبورد فعالیت آشا، هر فراخوانی ابزار و تمامِ راندهای رفت‌وبرگشت را می‌بینید.
  • کیف پول شفاف: بدون کارت خارجی؛ به‌اندازهٔ مصرف‌تان بپردازید و سقف هزینه برای تیم بگذارید.

خطاهای رایج

npx یا Node پیدا نمی‌شود

Node.js را نصب کنید تا سرور فایل‌سیستم بتواند با npx اجرا شود.

خطای «No API key» یا خطای احراز هویت

مطمئن شوید ASHA_API_KEY در فایل .env هست، کلید معتبر است و کیف پول موجودی کافی دارد.

مدل ابزارها را پشتیبانی نمی‌کند

مدلی انتخاب کنید که پارامتر tools را پشتیبانی می‌کند (معمولاً مدل‌های چت و کد). اگر پاسخ خطای «invalid parameter» گرفتید، شناسه را با فهرست مدل‌ها تطبیق دهید.

محتوای غیرمتنی نتیجهٔ ابزار

برخی سرورها نتیجهٔ غیرمتنی (مثل تصویر) برمی‌گردانند. در این حالت result.content را قبل از فرستادن به مدل، به متن قابل‌استفاده تبدیل (serialize) کنید.

جمع‌بندی

سرورهای MCP به‌همراه آشا بدون هیچ تغییر سمت سرور کار می‌کنند: فقط base_url را روی https://app.asha-ai.ir/v1/ بگذارید، کلید sk-asha-... را وارد کنید و ابزارهای MCP را به فرمت OpenAI تبدیل کنید. برای انتخاب مدل و دیدن قیمت‌ها به مدل‌های آشا سر بزنید.