درس ۵ از ۳۳

تعریف tools (Defining tools)

عنوان اصلی: Defining tools

هدف یادگیری: تعریف یک tool: name، description، inputSchema با JSON Schema، و یک handler که content برمی‌گرداند.

مفاهیم کلیدی: tools/list، tools/call، inputSchema، outputSchema، tool annotations (readOnlyHint، destructiveHint، idempotentHint، openWorldHint)، انواع content (text/image/audio/resource_link/embedded resource)، isError.

ابزارها model-controlled هستند — LLM بر اساس قصد کاربر آن‌ها را انتخاب می‌کند. تعریف یک tool شامل: یک name یکتا، title انسانی اختیاری، یک description که LLM برای انتخاب از آن استفاده می‌کند، یک inputSchema (JSON Schema)، و اختیاری یک outputSchema برای نتایج structured. سرورها باید capability tools را اعلام کنند و بهتر است اگر می‌خواهند tool را به‌صورت dynamic اضافه/حذف کنند، listChanged: true را ست کنند.

نتیجه یک tool یک آرایه از content item است. انواع content: text، image (base64 + MIME)، audio، resource_link (مرجع URI)، و resource embedded (محتوای کامل inline). وقتی چیزی اشتباه می‌شود، server یک نتیجه عادی با isError: true و یک text item توضیحی برمی‌گرداند — این به LLM اجازه می‌دهد خطا را بخواند و واکنش نشان دهد. JSON-RPC error response (-32602 Invalid params، -32601 Method not found، -32603 Internal error) را برای خطاهای protocol-level مثل tool name ناشناس نگه دارید.

annotationها (readOnlyHint، destructiveHint، idempotentHint، openWorldHint) advisory هستند — clientها باید آن‌ها را untrusted بدانند مگر اینکه خود server trusted باشد. وجود دارند تا host بتواند تصمیم بگیرد auto-approve کند، از کاربر تایید بخواهد، یا رد کند.

مثال عملی — Python (سه tool)

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Demo")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

@mcp.tool()
def get_weather(city: str, unit: str = "celsius") -> str:
    """Get weather for a city."""
    return f"Weather in {city}: 22 degrees{unit[0].upper()}"

@mcp.tool()
async def long_running(items: list[str]) -> str:
    """Example async tool that does I/O without blocking the STDIO loop."""
    return f"Processed {len(items)} items"

مثال عملی — TypeScript (tool با Zod typed)

import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';

const server = new McpServer({ name: 'greeting-server', version: '1.0.0' });

server.registerTool(
  'greet',
  {
    description: 'Greet someone by name',
    inputSchema: z.object({ name: z.string() }),
  },
  async ({ name }) => ({
    content: [{ type: 'text', text: `Hello, ${name}!` }],
  }),
);

await server.connect(new StdioServerTransport());

مثال عملی — JSON-RPC خام روی wire

{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
  "params": { "name": "get_weather", "arguments": { "city": "New York" } } }

دیاگرام معماری (متنی): یک swim-lane با LLM، Client، Server. LLM → Client: «select tool». Client → Server: tools/call. Server → Client: result content. Client → LLM: «tool result here, continue».

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

  • برگرداندن traceback Python از handler یک tool به‌جای content با isError: true. LLM خطاهای protocol را نمی‌بیند.
  • نام‌گذاری بیش از حد عمومی tools (run, query) که وقتی چند server وصل‌اند، مدل نمی‌تواند تشخیص دهد. domain را به‌صورت prefix بیاورید (github_create_issue).
  • فراموش کردن required در JSON Schema — مدل هرچه بخواهد می‌فرستد و validation رد می‌شود.
  • اعتبارسنجی نکردن ورودی سمت server. annotationها فقط hint هستند؛ server همچنان باید enforce کند.