راهنمای سرور MCP
این راهنما توضیح میدهد چگونه یک endpoint Model Context Protocol (MCP) را از اپلیکیشن فینچ خود در معرض دید قرار دهید. MCP یک پروتکل مبتنی بر JSON-RPC است که برای عاملهای هوش مصنوعی (AI agents) و مدلهای زبانی طراحی شده تا قابلیتهای سمت سرور — ابزارها (tools)، منابع (resources) و promptها — را بهشکلی استاندارد کشف و فراخوانی کنند.
McpServerController فینچ بهطور همزمان از دو نسخه پروتکل MCP پشتیبانی میکند — 2025-11-25 و 2026-07-28 — و بهازای هر درخواست بهصورت خودکار تعیین میکند کدام نسخه صحبت شود (به Protocol Version Negotiation در ادامه مراجعه کنید). شما tool ها/resource ها/prompt ها را فقط یکبار از طریق McpBuilder ثبت میکنید؛ کنترلر مسیریابی را برای هر نسخهای که کلاینت متصل صحبت میکند بهدرستی مدیریت میکند.
فینچ با پکیج mcp_models یکپارچه میشود تا موارد زیر را فراهم کند:
McpServerController— یک کنترلر انتزاعی فینچ که تمام مسیریابی JSON-RPC مربوط به MCP را بهصورت خودکار مدیریت میکند.McpBuilder— یک builder اعلانی (declarative) برای ثبت tool ها، resource ها، prompt ها و handlerهای متد سفارشی.mcp_models— یک کتابخانه Dart بدون وابستگی (zero-dependency) با پوشش کامل schema نسخه2026-07-28مربوط به MCP.
نحوه عملکرد
وقتی یک کلاینت (یک عامل هوش مصنوعی، افزونه IDE و غیره) یک درخواست MCP ارسال میکند، یک payload استاندارد JSON-RPC را از طریق HTTP به endpoint فینچ شما ارسال میکند. McpServerController این payload را decode میکند، آن را به handler داخلی مناسب یا به handlerی که با McpBuilder ثبت کردهاید مسیریابی میکند، و پاسخ را بهصورت Server-Sent Events (SSE) استریم میکند.
Client ──POST /mcp/books──► McpServerController.index()
│
▼
_dispatch(method, id, payload)
│
┌──────────────┼──────────────────┐
▼ ▼ ▼
tools/call resources/read prompts/get
│ │ │
▼ ▼ ▼
McpBuilder McpBuilder McpBuilder
.toolHandler() .resourceHandler() .promptHandler()
ثبت قابلیتها با McpBuilder
Tool ها
یک tool یک تابع قابلفراخوانی است که یک عامل هوش مصنوعی میتواند آن را invoke کند. یکی را با mcp.tool() ثبت کنید:
mcp.tool(
name: 'add',
description: 'Adds two integers and returns the sum.',
inputSchema: ToolSchema(
type: 'object',
properties: {
'a': Schema(type: 'integer', description: 'First operand', title: 'A'),
'b': Schema(type: 'integer', description: 'Second operand', title: 'B'),
},
required: ['a', 'b'],
),
outputSchema: ToolSchema(
type: 'object',
properties: {
'result': Schema(type: 'integer', description: 'The sum', title: 'Result'),
},
required: ['result'],
),
handler: (CallToolRequest req) async {
final args = req.params.arguments ?? {};
final sum = (args['a'] as int) + (args['b'] as int);
return CallToolResult(
content: [TextContent(text: '$sum', mimeType: 'text/plain')],
structuredContent: {'result': sum},
);
},
);
| پارامتر | نوع | توضیح |
|---|---|---|
name |
String |
شناسه یکتای tool |
description |
String |
توضیح قابلخوانش برای انسان که به عاملهای هوش مصنوعی نمایش داده میشود |
inputSchema |
ToolSchema |
JSON Schema که آرگومانهای مورد انتظار را توصیف میکند |
outputSchema |
ToolSchema? |
JSON Schema که خروجی ساختاریافته را توصیف میکند (اختیاری) |
handler |
Future<CallToolResult> Function(CallToolRequest) |
handler ناهمگام (async) که هنگام tools/call فراخوانی میشود |
Resource ها
یک resource یک منبع داده قابلآدرسدهی با URI را در معرض دید قرار میدهد (یک فایل، یک رکورد دیتابیس، یک پاسخ API و غیره):
mcp.resource(
name: 'config',
uri: 'file:///config.json',
description: 'Application configuration file.',
handler: (ReadResourceRequest req) async {
final content = await File('config.json').readAsString();
return ReadResourceResult(
contents: [
TextResourceContents(
uri: req.params.uri,
text: content,
mimeType: 'application/json',
),
],
);
},
);
| پارامتر | نوع | توضیح |
|---|---|---|
name |
String |
نام یکتای resource |
uri |
String |
URIای که این resource را شناسایی میکند |
description |
String? |
توضیح اختیاری |
handler |
Future<ReadResourceResult> Function(ReadResourceRequest) |
handler که هنگام resources/read فراخوانی میشود |
همچنین میتوانید از
rq.url('path')درونconfigure()برای ساخت URIهای مطلق بهصورت پویا استفاده کنید، زیراconfigureبا context زنده درخواست فراخوانی میشود.
Resource Template ها
برای URIهای پارامتریشده (مثلاً file:///books/{id})، از mcp.resourceTemplate() استفاده کنید:
mcp.resourceTemplate(
name: 'book',
uriTemplate: 'db:///books/{id}',
description: 'Fetches a single book by ID.',
);
Prompt ها
یک prompt یک قالب پیام قابلاستفادهمجدد است که یک عامل هوش مصنوعی میتواند آن را دریافت کند:
mcp.prompt(
name: 'greet',
description: 'Returns a greeting message.',
handler: (GetPromptRequest req) async {
final name = req.params.arguments?['name'] ?? 'World';
return GetPromptResult(
messages: [
PromptMessage(
role: Role.assistant,
content: TextContent(text: 'Hello, $name!'),
),
],
);
},
);
| پارامتر | نوع | توضیح |
|---|---|---|
name |
String |
شناسه یکتای prompt |
description |
String? |
توضیح اختیاری |
handler |
Future<GetPromptResult> Function(GetPromptRequest) |
handler که هنگام prompts/get فراخوانی میشود |
Handlerهای متد سفارشی
هر متد JSON-RPC — از جمله متدهای داخلی — را با mcp.method() override یا گسترش دهید:
mcp.method(
'notifications/initialized',
(Map<String, Object?> payload) async {
// منطق سفارشی برای اعلان (notification) مربوط به initialisation کلاینت.
return JSONRPCNotification(method: 'notifications/initialized');
},
);
handlerهای سفارشی ثبتشده از طریق mcp.method() نسبت به dispatcher داخلی اولویت دارند.
ثبت مسیر (Route)
کنترلر MCP را با Methods.ALL به route tree فینچ خود اضافه کنید تا هر دو درخواست GET و POST پذیرفته شوند:
import 'package:finch/finch_route.dart';
import 'controllers/my_mcp_controller.dart';
List<FinchRoute> getRoutes() {
return [
FinchRoute(
key: 'mcp.my_server',
path: 'mcp/my-server',
methods: Methods.ALL,
index: MyMcpController().index,
),
];
}
با احراز هویت
با فراهم کردن یک AuthController، از endpoint مربوط به MCP خود محافظت کنید:
import 'controllers/mcp_auth_controller.dart';
FinchRoute(
key: 'mcp.my_server',
path: 'mcp/my-server',
methods: Methods.ALL,
index: MyMcpController().index,
auth: McpAuthController(),
),
یک AuthController معمولی برای MCP یک Bearer API key را بررسی میکند:
import 'package:finch/finch_route.dart';
class McpAuthController extends AuthController<String> {
final List<String> _allowedKeys = ['your-secret-api-key'];
@override
Future<bool> auth() async => (await checkLogin()).success;
@override
Future<bool> authApi() async {
final auth = rq.authorization;
if (auth.type == AuthType.bearer) {
return _allowedKeys.contains(auth.token);
}
return false;
}
@override
Future<String> loginForm() async => rq.renderJson(
data: {'error': 'Unauthorized'},
status: 401,
);
}
پکیج mcp_models
mcp_models کلاسهای ساده Dart برای هر نوع در schema نسخه 2026-07-28 مربوط به MCP فراهم میکند. هیچ code generationای دخیل نیست — هر کلاس همراه با موارد زیر ارائه میشود:
- یک متد
toMap()برای سریالایز کردن. - یک factory با نام
TypeName.toMCP(Map)برای deserialize کردن.
انواع کلیدی
| دستهبندی | انواع |
|---|---|
| JSON-RPC | JSONRPCRequest, JSONRPCResultResponse, JSONRPCErrorResponse, JSONRPCNotification |
| چرخه عمر (Lifecycle) | InitializeRequest, InitializeResult, InitializeResultResponse |
| Tool ها | Tool, ToolSchema, Schema, CallToolRequest, CallToolResult, CallToolResultResponse, ListToolsResult, ListToolsResultResponse |
| Resource ها | Resource, ResourceTemplate, ReadResourceRequest, ReadResourceResult, TextResourceContents, BlobResourceContents, ListResourcesResult, ListResourceTemplatesResult |
| Prompt ها | Prompt, PromptMessage, GetPromptRequest, GetPromptResult, ListPromptsResult |
| محتوا (Content) | TextContent, ImageContent, AudioContent, EmbeddedResource |
| قابلیتها (Capabilities) | ServerCapabilities, ClientCapabilities, Implementation |
| خطاها | Error (شیء خطای JSON-RPC) |
| متفرقه | Result, EmptyResult, Role, LoggingLevel |
نمونه سریالایز کردن
import 'package:mcp_models/mcp_models.dart';
// یک درخواست initialize بساز.
final request = InitializeRequest(
id: '1',
params: InitializeRequestParams(
protocolVersion: '2026-07-28',
capabilities: ClientCapabilities({}),
clientInfo: Implementation(name: 'my_client', version: '1.0.0'),
),
);
// به یک Map سریالایز کن (آماده برای JSON encoding).
final json = request.toMap();
// دوباره deserialize کن.
final restored = InitializeRequest.toMCP(json);
ToolSchema و Schema
ToolSchema و Schema هر دو یک شیء JSON Schema را توصیف میکنند که برای اعتبارسنجی ورودی/خروجی tool استفاده میشود:
ToolSchema(
type: 'object',
properties: {
'name': Schema(
type: 'string',
description: 'The name of the item.',
defaultValue: '',
title: 'Name',
),
'count': Schema(
type: 'integer',
description: 'How many items.',
defaultValue: 1,
title: 'Count',
),
},
required: ['name'],
)
کلاسهای پایه MapMC و MapModel
برای انواعی که شکل سریالایزشده آنها خودِ map زیرین است (بهجای یک شیء wrapper)، mcp_models دو کلاس پایه ارائه میدهد:
MapMC<K, V>— ازMCPextend میشود و مستقیماً بهصورت map خودش سریالایز میشود.MapModel<K, V>— یک مدل ساده شبیهبهmap بدون کلاس پایهMCP.
ServerCapabilities و ClientCapabilities بر پایه MapMC ساخته شدهاند.
مدیریت خطا
هر Exception درون یک tool handler پرتاب کنید — McpServerController استثناهای مدیریتنشده را در یک JSONRPCErrorResponse با کد -32600 میپیچد و یک پاسخ SSE با کد 400 به کلاینت استریم میکند. برای انتشار خطای ساختاریافته، یک CallToolResult با isError: true برگردانید:
handler: (req) async {
final id = req.params.arguments?['id'];
if (id == null) {
return CallToolResult(
isError: true,
content: [TextContent(text: 'Missing required argument: id')],
);
}
// ...
},