MCP 服务器指南

本指南介绍了如何从你的 Finch 应用程序中暴露一个 Model Context Protocol(MCP) 端点。MCP 是一种基于 JSON-RPC 的协议,旨在让 AI 智能体和语言模型以标准化的方式发现并调用服务器端的能力——工具(tools)、资源(resources)和提示(prompts)。

Finch 的 McpServerController 同时支持两个 MCP 协议版本——2025-11-252026-07-28——并会针对每个请求自动选择使用哪一个版本进行通信(参见下方的协议版本协商)。你只需通过 McpBuilder 注册一次工具/资源/提示;无论连接的客户端使用哪个版本,控制器都能为其正确地路由请求。

Finch 与 mcp_models 包集成,提供了:

  • McpServerController —— 一个抽象的 Finch 控制器,自动处理所有 MCP JSON-RPC 路由。
  • McpBuilder —— 一个声明式构建器,用于注册工具、资源、提示以及自定义方法处理程序。
  • mcp_models —— 一个零依赖的 Dart 库,完整覆盖了 MCP 2026-07-28 版本的模式(schema)。

工作原理

当客户端(AI 智能体、IDE 插件等)发送一个 MCP 请求时,它会通过 HTTP 向你的 Finch 端点发送一个标准的 JSON-RPC 负载。McpServerController 会解码该负载,将其路由到正确的内置处理程序,或路由到你通过 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) 是一个可供 AI 智能体调用的函数。使用 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 唯一的工具标识符
description String 展示给 AI 智能体的可读描述
inputSchema ToolSchema 描述预期参数的 JSON Schema
outputSchema ToolSchema? 描述结构化输出的 JSON Schema(可选)
handler Future<CallToolResult> Function(CallToolRequest) tools/call 时被调用的异步处理程序

资源

资源(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 唯一的资源名称
uri String 用于标识该资源的 URI
description String? 可选的描述
handler Future<ReadResourceResult> Function(ReadResourceRequest) resources/read 时被调用的处理程序

你也可以在 configure() 内部使用 rq.url('path') 动态构建绝对 URI,因为 configure 是带着实时请求上下文被调用的。

资源模板

对于带参数的 URI(例如 file:///books/{id}),使用 mcp.resourceTemplate():

mcp.resourceTemplate(
  name: 'book',
  uriTemplate: 'db:///books/{id}',
  description: 'Fetches a single book by ID.',
);

提示

提示(prompt) 是一个可供 AI 智能体获取的可复用消息模板:

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 唯一的提示标识符
description String? 可选的描述
handler Future<GetPromptResult> Function(GetPromptRequest) prompts/get 时被调用的处理程序

自定义方法处理程序

使用 mcp.method() 覆盖或扩展任意 JSON-RPC 方法——包括内置方法:

mcp.method(
  'notifications/initialized',
  (Map<String, Object?> payload) async {
    // 客户端初始化通知的自定义逻辑。
    return JSONRPCNotification(method: 'notifications/initialized');
  },
);

通过 mcp.method() 注册的自定义处理程序,其优先级高于内置的分发器。

注册路由

将 MCP 控制器添加到你的 Finch 路由树中,并使用 Methods.ALL 以同时接受 GETPOST 请求:

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 来保护你的 MCP 端点:

import 'controllers/mcp_auth_controller.dart';

FinchRoute(
  key: 'mcp.my_server',
  path: 'mcp/my-server',
  methods: Methods.ALL,
  index: MyMcpController().index,
  auth: McpAuthController(),
),

一个典型的用于 MCP 的 AuthController 会校验 Bearer API 密钥:

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 为 MCP 2026-07-28 模式(schema)中的每一种类型都提供了纯 Dart 类。它不涉及任何代码生成——每个类都自带:

  • 一个用于序列化的 toMap() 方法。
  • 一个用于反序列化的具名工厂方法 TypeName.toMCP(Map)

关键类型

类别 类型
JSON-RPC JSONRPCRequest, JSONRPCResultResponse, JSONRPCErrorResponse, JSONRPCNotification
生命周期 InitializeRequest, InitializeResult, InitializeResultResponse
工具 Tool, ToolSchema, Schema, CallToolRequest, CallToolResult, CallToolResultResponse, ListToolsResult, ListToolsResultResponse
资源 Resource, ResourceTemplate, ReadResourceRequest, ReadResourceResult, TextResourceContents, BlobResourceContents, ListResourcesResult, ListResourceTemplatesResult
提示 Prompt, PromptMessage, GetPromptRequest, GetPromptResult, ListPromptsResult
内容 TextContent, ImageContent, AudioContent, EmbeddedResource
能力 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 编码)。
final json = request.toMap();

// 反序列化回来。
final restored = InitializeRequest.toMCP(json);

ToolSchema 与 Schema

ToolSchemaSchema 都用于描述工具输入/输出校验所使用的 JSON Schema 对象:

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 本身(而不是一个包装对象)的类型,mcp_models 提供了两个基类:

  • MapMC<K, V> —— 继承自 MCP,直接以其 map 形式进行序列化。
  • MapModel<K, V> —— 一个不继承 MCP 基类的、纯粹类似 map 的模型。

ServerCapabilitiesClientCapabilities 都是基于 MapMC 构建的。

错误处理

在工具处理程序中抛出任意 Exception——McpServerController 会将未处理的异常包装成一个 JSONRPCErrorResponse(错误码为 -32600),并向客户端流式返回一个 400 的 SSE 响应。若需要结构化的错误传递,可以返回一个带有 isError: trueCallToolResult:

handler: (req) async {
  final id = req.params.arguments?['id'];
  if (id == null) {
    return CallToolResult(
      isError: true,
      content: [TextContent(text: 'Missing required argument: id')],
    );
  }
  // ...
},

另请参阅