درس ۷ از ۳۳

پیاده‌سازی client (Implementing a client)

عنوان اصلی: Implementing a client

هدف یادگیری: ساخت یک MCP client حداقلی در Python که initialize می‌کند، tools را list می‌کند، یک tool را call می‌کند و یک notification را handle می‌کند.

مفاهیم کلیدی: ClientSession، stdio_client، initialize()، list_tools()، call_tool()، notification handler.

ClientSession در SDK Python ماشین JSON-RPC را wrap می‌کند، طوری که کد اپلیکیشن فقط methodهای high-level را می‌بیند. الگوی استاندارد: یک transport context باز کنید (stdio_client(...) یا streamablehttp_client(...))، read/write streamها را در یک ClientSession بپیچید، await session.initialize() کنید، سپس از list_tools()، call_tool()، list_resources()، read_resource()، list_prompts()، get_prompt() استفاده کنید. session هم‌چنین hook برای notificationها و primitiveهای سمت client (sampling، elicitation، roots) عرضه می‌کند.

مثال عملی — Python (client کامل minimal با notification handler)

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

server_params = StdioServerParameters(command="python", args=["server.py"])

async def main():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            init = await session.initialize()
            print("Server:", init.server_info.name, init.server_info.version)
            print("Capabilities:", init.capabilities)

            tools = await session.list_tools()
            for t in tools.tools:
                print(f"  - {t.name}: {t.description}")

            result = await session.call_tool("add", {"a": 2, "b": 3})
            print("Result:", result.content[0].text)

asyncio.run(main())

مثال عملی — TypeScript (client مشابه)

import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';

const client = new Client({ name: 'demo-client', version: '1.0.0' }, {});
const transport = new StdioClientTransport({
  command: 'node',
  args: ['build/index.js'],
});
await client.connect(transport);

const tools = await client.listTools();
const result = await client.callTool({ name: 'greet', arguments: { name: 'Alice' } });
console.log(result.content);

دیاگرام معماری (متنی): Client مالک یک read stream و یک write stream است. requestهای خروجی JSON-RPC به‌صورت newline-delimited روی stdin نوشته می‌شوند؛ responseها و notificationهای ورودی از stdout می‌رسند و بر اساس id (responseها) یا method (notificationها) dispatch می‌شوند.

اشتباهات رایج

  • فراموش کردن await session.initialize() — تمام صداهای دیگر fail می‌شوند.
  • نگه داشتن session بیرون از بلاک async with — بسته می‌شود.
  • بلاک کردن event loop با I/O sync داخل notification handler.