modelcontextprotocol/ruby-sdk

GitHub: modelcontextprotocol/ruby-sdk

Model Context Protocol 的官方 Ruby SDK,提供完整的 MCP 服务器和客户端实现,帮助 Ruby/Rails 应用与 LLM 工具链无缝集成。

Stars: 805 | Forks: 110

# MCP Ruby SDK [![Gem 版本](https://img.shields.io/gem/v/mcp)](https://rubygems.org/gems/mcp) [![Apache 2.0 授权](https://img.shields.io/badge/license-Apache%202.0-blue)](https://github.com/modelcontextprotocol/ruby-sdk/blob/main/LICENSE) [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/e8f460f1a0053115.svg)](https://github.com/modelcontextprotocol/ruby-sdk/actions/workflows/ci.yml) Model Context Protocol 服务器和客户端的官方 Ruby SDK。 ## 安装 将以下行添加到您的应用程序的 Gemfile 中: ``` gem 'mcp' ``` 然后执行: ``` $ bundle install ``` 或者自行安装: ``` $ gem install mcp ``` 您可能需要根据您希望访问的功能添加额外的依赖项。 ## 构建 MCP 服务器 `MCP::Server` 类是处理 JSON-RPC 请求和响应的核心组件。 它实现了 Model Context Protocol 规范,处理模型上下文请求和响应。 ### 主要功能 - 实现 JSON-RPC 2.0 消息处理 - 支持协议初始化和能力协商 - 管理 tool 注册和调用 - 支持 prompt 注册和执行 - 支持 resource 注册和检索 - 支持 stdio 和 Streamable HTTP(包括 SSE)传输 - 支持列表更改(tools、prompts、resources)的通知 - 支持 roots(服务器到客户端的文件系统边界查询) - 支持 sampling(服务器到客户端的 LLM 补全请求) - 支持列表操作的基于游标的分页 - 支持服务器端取消正在进行的请求(notifications/cancelled) ### 支持的方法 - `initialize` - 初始化协议并返回服务器能力 - `ping` - 简单的健康检查 - `tools/list` - 列出所有已注册的 tools 及其 schemas - `tools/call` - 使用提供的参数调用特定的 tool - `prompts/list` - 列出所有已注册的 prompts 及其 schemas - `prompts/get` - 按名称检索特定的 prompt - `resources/list` - 列出所有已注册的 resources 及其 schemas - `resources/read` - 按名称检索特定的 resource - `resources/templates/list` - 列出所有已注册的 resource 模板及其 schemas - `resources/subscribe` - 订阅特定 resource 的更新 - `resources/unsubscribe` - 取消订阅特定 resource 的更新 - `completion/complete` - 返回 prompt 参数和 resource URI 的自动补全建议 - `roots/list` - 从客户端请求文件系统 roots(服务器到客户端) - `sampling/createMessage` - 从客户端请求 LLM 补全(服务器到客户端) - `elicitation/create` - 从客户端请求用户输入(服务器到客户端) ### 用法 #### Stdio 传输 如果您想构建本地命令行应用程序,可以使用 stdio 传输: ``` require "mcp" # 创建一个简单的工具 class ExampleTool < MCP::Tool description "A simple example tool that echoes back its arguments" input_schema( properties: { message: { type: "string" }, }, required: ["message"] ) class << self def call(message:, server_context:) MCP::Tool::Response.new([{ type: "text", text: "Hello from example tool! Message: #{message}", }]) end end end # 设置服务器 server = MCP::Server.new( name: "example_server", tools: [ExampleTool], ) # 创建并启动 transport transport = MCP::Server::Transports::StdioTransport.new(server) transport.open ``` 您可以运行此脚本,然后在命令行向服务器输入请求。 ``` $ ruby examples/stdio_server.rb {"jsonrpc":"2.0","id":"1","method":"ping"} {"jsonrpc":"2.0","id":"2","method":"tools/list"} {"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"example_tool","arguments":{"message":"Hello"}}} ``` #### Streamable HTTP 传输 `MCP::Server::Transports::StreamableHTTPTransport` 是一个标准的 Rack 应用,因此它可以挂载到任何兼容 Rack 的框架中。 以下示例展示了在 Rails 中的两种常见集成方式。 ##### Rails(挂载) `StreamableHTTPTransport` 是一个 Rack 应用,可以直接在 Rails 路由中挂载: ``` # config/routes.rb server = MCP::Server.new( name: "my_server", title: "Example Server Display Name", version: "1.0.0", instructions: "Use the tools of this server as a last resort", tools: [SomeTool, AnotherTool], prompts: [MyPrompt], ) transport = MCP::Server::Transports::StreamableHTTPTransport.new(server) Rails.application.routes.draw do mount transport => "/mcp" end ``` `mount` 将 `/mcp` 上的所有 HTTP 方法定向到传输层。`StreamableHTTPTransport` 内部根据 [MCP Streamable HTTP 传输规范](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http) 分发 `POST`(客户端到服务器的 JSON-RPC 消息,其响应可选通过 SSE 流式传输)、`GET`(可选的独立 SSE 流,用于服务器到客户端消息)和 `DELETE`(会话终止),因此不需要额外的路由配置。 ##### Rails(控制器) 虽然 mount 方法在启动时创建单个服务器,但控制器方法会为每个请求创建一个新服务器。 这允许您根据请求自定义 tools、prompts 或配置(例如,不同的路由使用不同的 tools)。 `StreamableHTTPTransport#handle_request` 返回正确的 HTTP 状态码(例如,通知返回 202 Accepted): ``` class McpController < ActionController::API def create server = MCP::Server.new( name: "my_server", title: "Example Server Display Name", version: "1.0.0", instructions: "Use the tools of this server as a last resort", tools: [SomeTool, AnotherTool], prompts: [MyPrompt], server_context: { user_id: current_user.id }, ) # Since the `MCP-Session-Id` is not shared across requests, `stateless: true` is set. transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true) status, headers, body = transport.handle_request(request) render(json: body.first, status: status, headers: headers) end end ``` ### 配置 可以使用 `MCP.configure` 块配置该 gem: ``` MCP.configure do |config| config.exception_reporter = ->(exception, server_context) { # Your exception reporting logic here # For example with Bugsnag: Bugsnag.notify(exception) do |report| report.add_metadata(:model_context_protocol, server_context) end } config.around_request = ->(data, &request_handler) { logger.info("Start: #{data[:method]}") request_handler.call logger.info("Done: #{data[:method]}, tool: #{data[:tool_name]}") } end ``` 或者通过创建显式配置并将其传递给服务器。 这对于一个应用程序托管多个 MCP 服务器但它们可能需要不同配置的系统非常有用。 ``` configuration = MCP::Configuration.new configuration.exception_reporter = ->(exception, server_context) { # Your exception reporting logic here # For example with Bugsnag: Bugsnag.notify(exception) do |report| report.add_metadata(:model_context_protocol, server_context) end } configuration.around_request = ->(data, &request_handler) { logger.info("Start: #{data[:method]}") request_handler.call logger.info("Done: #{data[:method]}, tool: #{data[:tool_name]}") } server = MCP::Server.new( # ... all other options configuration:, ) ``` ### 服务器上下文和配置块数据 #### `server_context` `server_context` 是一个传递给服务器实例并供 tool 和 prompt 调用使用的用户自定义哈希。 它可用于提供上下文信息,如身份验证状态、用户 ID 或特定于请求的数据。 **类型:** ``` server_context: { [String, Symbol] => Any } ``` **示例:** ``` server = MCP::Server.new( name: "my_server", server_context: { user_id: current_user.id, request_id: request.uuid } ) ``` 这个哈希随后将作为 `server_context` 关键字参数传递给 tool 和 prompt 调用。 请注意,exception 和 instrumentation 回调不会接收此用户自定义哈希。 有关它们接收的参数,请参阅下面的相关部分。 #### 特定于请求的 `_meta` 参数 MCP 协议在请求中支持一个特殊的 [`_meta` 参数](https://modelcontextprotocol.io/specification/2025-06-18/basic#general-fields),允许客户端传递特定于请求的元数据。服务器会自动提取此参数,并将其作为 `server_context` 中的嵌套字段提供给 tools 和 prompts。 **访问模式:** 当客户端在请求参数中包含 `_meta` 时,它会作为 `server_context[:_meta]` 可用: ``` class MyTool < MCP::Tool def self.call(message:, server_context:) # Access provider-specific metadata session_id = server_context.dig(:_meta, :session_id) request_id = server_context.dig(:_meta, :request_id) # Access server's original context user_id = server_context.dig(:user_id) MCP::Tool::Response.new([{ type: "text", text: "Processing for user #{user_id} in session #{session_id}" }]) end end ``` **客户端请求示例:** ``` { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "my_tool", "arguments": { "message": "Hello" }, "_meta": { "session_id": "abc123", "request_id": "req_456" } } } ``` #### 配置块数据 ##### 异常报告器 异常报告器接收: - `exception`:引发的 Ruby 异常对象 - `server_context`:一个描述失败发生位置的哈希(例如,用于请求处理的 `{ request: <原始 JSON-RPC 请求> }`,用于通知传递的 `{ notification: "tools_list_changed" }`)。 这不是传递给 `Server.new` 的用户自定义 `server_context`。 **签名:** ``` exception_reporter = ->(exception, server_context) { ... } ``` ##### 包裹请求 `around_request` 钩子包裹请求处理,允许您在每个请求之前和之后执行代码。 这对于应用性能监控 (APM) 追踪、日志记录或其他可观测性需求非常有用。 该钩子接收一个 `data` 哈希和一个 `request_handler` 块。您必须调用 `request_handler.call` 来执行请求: **签名:** ``` around_request = ->(data, &request_handler) { request_handler.call } ``` **`data` 按时间点的可用性:** - 在 `request_handler.call` 之前:`method` - 在 `request_handler.call` 之后:`tool_name`、`tool_arguments`、`prompt_name`、`resource_uri`、`error`、`client` - 在 `around_request` 内部不可用:`duration`(在 `around_request` 返回后添加) **示例:** ``` MCP.configure do |config| config.around_request = ->(data, &request_handler) { logger.info("Start: #{data[:method]}") request_handler.call logger.info("Done: #{data[:method]}, tool: #{data[:tool_name]}") } end ``` ##### Instrumentation 回调(软弃用) Instrumentation 回调在每个请求完成后调用,无论成功还是出错。 它接收一个包含以下可能键的哈希: - `method`:(String) 调用的协议方法(例如,"ping"、"tools/list") - `tool_name`:(String, 可选) 调用的 tool 的名称 - `tool_arguments`:(Hash, 可选) 传递给 tool 的参数 - `prompt_name`:(String, 可选) 调用的 prompt 的名称 - `resource_uri`:(String, 可选) 调用的 resource 的 URI - `error`:(String, 可选) 查找失败时的错误代码 - `duration`:(Float) 调用持续时间(以秒为单位) - `client`:(Hash, 可选) 客户端信息,包含来自初始化请求的 `name` 和 `version` 键 **签名:** ``` instrumentation_callback = ->(data) { ... } ``` ### 服务器协议版本 可以使用 `protocol_version` 关键字参数覆盖服务器的协议版本: ``` configuration = MCP::Configuration.new(protocol_version: "2024-11-05") MCP::Server.new(name: "test_server", configuration: configuration) ``` 如果未指定协议版本,默认将应用最新的稳定版本。 最新的稳定版本包含来自[草案版本](https://modelcontextprotocol.io/specification/draft)的新功能。 这将使所有新服务器实例使用指定的协议版本而不是默认版本。可以通过将其设置为 `nil` 将协议版本重置为默认值: ``` MCP::Configuration.new(protocol_version: nil) ``` 如果设置了无效的 `protocol_version` 值,将引发 `ArgumentError`。 请务必查看 [MCP 规范](https://modelcontextprotocol.io/specification/versioning) 以了解协议版本,从而理解所设置版本支持的功能。 ### 异常报告 异常报告器接收两个参数: - `exception`:引发的 Ruby 异常对象 - `server_context`:一个包含有关错误发生位置的上下文信息的哈希 server_context 哈希包括: - 对于 tool 调用:`{ tool_name: "name", arguments: { ... } }` - 对于一般请求处理:`{ request: { ... } }` 当异常发生时: 1. 异常通过配置的报告器进行报告 2. 对于 tool 调用,向客户端返回一般性错误响应:`{ error: "Internal error occurred", isError: true }` 3. 对于其他请求,异常在报告后重新引发 如果未配置异常报告器,则使用默认的空操作报告器,该报告器会静默忽略异常。 ### Tools MCP 规范包含 [Tools](https://modelcontextprotocol.io/specification/latest/server/tools),为 LLM 应用提供功能。 此 gem 提供了一个 `MCP::Tool` 类,可以通过三种方式创建 tools: 1. 作为类定义: ``` class MyTool < MCP::Tool title "My Tool" description "This tool performs specific functionality..." input_schema( properties: { message: { type: "string" }, }, required: ["message"] ) output_schema( properties: { result: { type: "string" }, success: { type: "boolean" }, timestamp: { type: "string", format: "date-time" } }, required: ["result", "success", "timestamp"] ) annotations( read_only_hint: true, destructive_hint: false, idempotent_hint: true, open_world_hint: false, title: "My Tool" ) def self.call(message:, server_context:) MCP::Tool::Response.new([{ type: "text", text: "OK" }]) end end tool = MyTool ``` 2. 通过使用带有块的 `MCP::Tool.define` 方法: ``` tool = MCP::Tool.define( name: "my_tool", title: "My Tool", description: "This tool performs specific functionality...", annotations: { read_only_hint: true, title: "My Tool" } ) do |args, server_context:| MCP::Tool::Response.new([{ type: "text", text: "OK" }]) end ``` 3. 通过使用带有块的 `MCP::Server#define_tool` 方法: ``` server = MCP::Server.new server.define_tool( name: "my_tool", description: "This tool performs specific functionality...", annotations: { title: "My Tool", read_only_hint: true } ) do |args, server_context:| Tool::Response.new([{ type: "text", text: "OK" }]) end ``` server_context 参数是传递给服务器的 server_context,可用于传递每个请求的信息,例如身份验证状态。 ### Tool 注解 Tools 可以包含提供有关其行为额外元数据的注解。支持以下注解: - `destructive_hint`:指示 tool 是否执行破坏性操作。默认为 true - `idempotent_hint`:指示 tool 的操作是否是幂等的。默认为 false - `open_world_hint`:指示 tool 是否在开放世界上下文中运行。默认为 true - `read_only_hint`:指示 tool 是否仅读取数据(不修改状态)。默认为 false - `title`:tool 的人类可读标题 可以通过使用 `annotations` 类方法的类定义,或在使用 `define` 方法定义 tool 时设置注解。 ### Tool 输出 Schemas Tools 可以选择定义一个 `output_schema` 来指定其结果的预期结构。这与 `input_schema` 的定义方式类似,可以通过三种方式使用: 1. **带有 output_schema 的类定义:** ``` class WeatherTool < MCP::Tool tool_name "get_weather" description "Get current weather for a location" input_schema( properties: { location: { type: "string" }, units: { type: "string", enum: ["celsius", "fahrenheit"] } }, required: ["location"] ) output_schema( properties: { temperature: { type: "number" }, condition: { type: "string" }, humidity: { type: "integer" } }, required: ["temperature", "condition", "humidity"] ) def self.call(location:, units: "celsius", server_context:) # Call weather API and structure the response api_response = WeatherAPI.fetch(location, units) weather_data = { temperature: api_response.temp, condition: api_response.description, humidity: api_response.humidity_percent } output_schema.validate_result(weather_data) MCP::Tool::Response.new([{ type: "text", text: weather_data.to_json }]) end end ``` 2. **使用带有 output_schema 的 Tool.define:** ``` tool = MCP::Tool.define( name: "calculate_stats", description: "Calculate statistics for a dataset", input_schema: { properties: { numbers: { type: "array", items: { type: "number" } } }, required: ["numbers"] }, output_schema: { properties: { mean: { type: "number" }, median: { type: "number" }, count: { type: "integer" } }, required: ["mean", "median", "count"] } ) do |args, server_context:| # Calculate statistics and validate against schema MCP::Tool::Response.new([{ type: "text", text: "Statistics calculated" }]) end ``` 3. **使用 OutputSchema 对象:** ``` class DataTool < MCP::Tool output_schema MCP::Tool::OutputSchema.new( properties: { success: { type: "boolean" }, data: { type: "object" } }, required: ["success"] ) end ``` Output schema 也可以描述对象数组: ``` class WeatherTool < MCP::Tool output_schema( type: "array", items: { properties: { temperature: { type: "number" }, condition: { type: "string" }, humidity: { type: "integer" } }, required: ["temperature", "condition", "humidity"] } ) end ``` 请注意:在这种情况下,您必须提供 `type: "array"`。output schema 的默认类型是 `object`。 [Output Schema](https://modelcontextprotocol.io/specification/latest/server/tools#output-schema) 的 MCP 规范规定: - **服务器验证**:服务器必须提供符合 output schema 的结构化结果 - **客户端验证**:客户端应针对 output schema 验证结构化结果 - **更好的集成**:实现严格的 schema 验证、类型信息和改进的开发者体验 - **向后兼容性**:返回结构化内容的 Tools 也应包含 TextContent 块中序列化的 JSON Output schema 遵循标准的 JSON Schema 格式,有助于确保 MCP 服务器和客户端之间一致的数据交换。 ### 包含结构化内容的 Tool 响应 Tools 可以使用 `structured_content` 参数与文本内容一起返回结构化数据。 结构化内容将作为 `structuredContent` 字段包含在 JSON-RPC 响应中。 ``` class WeatherTool < MCP::Tool description "Get current weather and return structured data" def self.call(location:, units: "celsius", server_context:) # Call weather API and structure the response api_response = WeatherAPI.fetch(location, units) weather_data = { temperature: api_response.temp, condition: api_response.description, humidity: api_response.humidity_percent } output_schema.validate_result(weather_data) MCP::Tool::Response.new( [{ type: "text", text: weather_data.to_json }], structured_content: weather_data ) end end ``` ### 包含错误的 Tool 响应 Tools 可以使用 `error` 参数与文本内容一起返回错误信息。 该错误将作为 `isError` 字段包含在 JSON-RPC 响应中。 ``` class WeatherTool < MCP::Tool description "Get current weather and return structured data" def self.call(server_context:) # Do something here content = {} MCP::Tool::Response.new( [{ type: "text", text: content.to_json }], structured_content: content, error: true ) end end ``` ### Prompts MCP 规范包含 [Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts),允许服务器定义可重用的 prompt 模板和工作流,客户端可以轻松将其呈现给用户和 LLM。 `MCP::Prompt` 类提供了三种创建 prompts 的方法: 1. 作为带有元数据的类定义: ``` class MyPrompt < MCP::Prompt prompt_name "my_prompt" # Optional - defaults to underscored class name title "My Prompt" description "This prompt performs specific functionality..." arguments [ MCP::Prompt::Argument.new( name: "message", title: "Message Title", description: "Input message", required: true ) ] meta({ version: "1.0", category: "example" }) class << self def template(args, server_context:) MCP::Prompt::Result.new( description: "Response description", messages: [ MCP::Prompt::Message.new( role: "user", content: MCP::Content::Text.new("User message") ), MCP::Prompt::Message.new( role: "assistant", content: MCP::Content::Text.new(args["message"]) ) ] ) end end end prompt = MyPrompt ``` 2. 使用 `MCP::Prompt.define` 方法: ``` prompt = MCP::Prompt.define( name: "my_prompt", title: "My Prompt", description: "This prompt performs specific functionality...", arguments: [ MCP::Prompt::Argument.new( name: "message", title: "Message Title", description: "Input message", required: true ) ], meta: { version: "1.0", category: "example" } ) do |args, server_context:| MCP::Prompt::Result.new( description: "Response description", messages: [ MCP::Prompt::Message.new( role: "user", content: MCP::Content::Text.new("User message") ), MCP::Prompt::Message.new( role: "assistant", content: MCP::Content::Text.new(args["message"]) ) ] ) end ``` 3. 使用 `MCP::Server#define_prompt` 方法: ``` server = MCP::Server.new server.define_prompt( name: "my_prompt", description: "This prompt performs specific functionality...", arguments: [ Prompt::Argument.new( name: "message", title: "Message Title", description: "Input message", required: true ) ], meta: { version: "1.0", category: "example" } ) do |args, server_context:| Prompt::Result.new( description: "Response description", messages: [ Prompt::Message.new( role: "user", content: Content::Text.new("User message") ), Prompt::Message.new( role: "assistant", content: Content::Text.new(args["message"]) ) ] ) end ``` server_context 参数是传递给服务器的 server_context,可用于传递每个请求的信息,例如身份验证状态或用户偏好。 ### 主要组件 - `MCP::Prompt::Argument` - 定义 prompt 模板的输入参数,包括名称、标题、描述和必填标志 - `MCP::Prompt::Message` - 表示对话中具有角色和内容的消息 - `MCP::Prompt::Result` - prompt 模板的输出,包含描述和消息 - `MCP::Content::Text` - 消息的文本内容 ### 用法 在 MCP 服务器上注册 prompts: ``` server = MCP::Server.new( name: "my_server", prompts: [MyPrompt], server_context: { user_id: current_user.id }, ) ``` 服务器将通过 MCP 协议方法处理 prompt 列表和执行: - `prompts/list` - 列出所有已注册的 prompts 及其 schemas - `prompts/get` - 使用参数检索并执行特定的 prompt ### ResourcesMCP 规范包含 [Resources](https://modelcontextprotocol.io/specification/latest/server/resources)。 ### 读取 Resources `MCP::Resource` 类提供了一种向服务器注册 resources 的方法。 ``` resource = MCP::Resource.new( uri: "https://example.com/my_resource", name: "my-resource", title: "My Resource", description: "Lorem ipsum dolor sit amet", mime_type: "text/html", ) server = MCP::Server.new( name: "my_server", resources: [resource], ) ``` 服务器必须注册一个用于 `resources/read` 方法的处理器才能动态检索 resource。 ``` server.resources_read_handler do |params| [{ uri: params[:uri], mimeType: "text/plain", text: "Hello from example resource! URI: #{params[:uri]}" }] end ``` 否则 `resources/read` 请求将是一个空操作。 ### Resource 模板 `MCP::ResourceTemplate` 类提供了一种向服务器注册 resource 模板的方法。 ``` resource_template = MCP::ResourceTemplate.new( uri_template: "https://example.com/my_resource_template", name: "my-resource-template", title: "My Resource Template", description: "Lorem ipsum dolor sit amet", mime_type: "text/html", ) server = MCP::Server.new( name: "my_server", resource_templates: [resource_template], ) ``` ### Roots Model Context Protocol 允许服务器通过 `roots/list` 方法从客户端请求文件系统 roots。 Roots 定义了服务器可以操作的边界,提供客户端已使其可用的目录和文件列表。 **关键概念:** - **服务器到客户端请求**:与 sampling 一样,roots 列表由服务器发起 - **客户端能力**:客户端必须在初始化期间声明 `roots` 能力 - **更改通知**:支持 `roots.listChanged` 的客户端在 roots 更改时发送 `notifications/roots/list_changed` **在 Tools 中使用 Roots:** 接受 `server_context:` 参数的 Tools 可以在其上调用 `list_roots`。 该请求会自动路由到正确的客户端会话: ``` class FileSearchTool < MCP::Tool description "Search files within the client's project roots" input_schema( properties: { query: { type: "string" } }, required: ["query"] ) def self.call(query:, server_context:) roots = server_context.list_roots root_uris = roots[:roots].map { |root| root[:uri] } MCP::Tool::Response.new([{ type: "text", text: "Searching in roots: #{root_uris.join(", ")}" }]) end end ``` 结果包含一个 root 对象数组: ``` { roots: [ { uri: "file:///home/user/projects/myproject", name: "My Project" }, { uri: "file:///home/user/repos/backend", name: "Backend Repository" } ] } ``` **处理 Root 更改:** 注册一个回调以在客户端的 roots 更改时收到通知: ``` server.roots_list_changed_handler do puts "Client's roots have changed, tools will see updated roots on next call." end ``` **错误处理:** - 如果客户端不支持 `roots` 能力,则引发 `RuntimeError` - 如果客户端返回错误响应,则引发 `StandardError` ### Resource 订阅 Resource 订阅允许客户端监视特定 resources 的更改。 当订阅的 resource 更新时,服务器会向客户端发送通知。 SDK 内部不跟踪订阅状态。 服务器开发人员注册处理器并管理他们自己的订阅状态。 提供了三种方法: - `Server#resources_subscribe_handler` - 注册 `resources/subscribe` 请求的处理器 - `Server#resources_unsubscribe_handler` - 注册 `resources/unsubscribe` 请求的处理器 - `ServerContext#notify_resources_updated` - 向订阅的客户端发送 `notifications/resources/updated` 通知 ``` subscribed_uris = Set.new server = MCP::Server.new( name: "my_server", resources: [my_resource], capabilities: { resources: { subscribe: true } }, ) server.resources_subscribe_handler do |params| subscribed_uris.add(params[:uri].to_s) end server.resources_unsubscribe_handler do |params| subscribed_uris.delete(params[:uri].to_s) end server.define_tool(name: "update_resource") do |server_context:, **args| if subscribed_uris.include?("test://my-resource") server_context.notify_resources_updated(uri: "test://my-resource") end MCP::Tool::Response.new([MCP::Content::Text.new("Resource updated").to_h]) end ``` ### Sampling Model Context Protocol 允许服务器通过 `sampling/createMessage` 方法从客户端请求 LLM 补全。 这使得服务器能够利用客户端的 LLM 能力,而无需直接访问 AI 模型。 **关键概念:** - **服务器到客户端请求**:与典型的 MCP 方法(客户端到服务器)不同,sampling 由服务器发起 - **客户端能力**:客户端必须在初始化期间声明 `sampling` 能力 - **Tool 支持**:在 sampling 请求中使用 tools 时,客户端必须声明 `sampling.tools` 能力 - **人类参与循环**:客户端可以在将请求转发给 LLM 之前实现用户批准 **在 Tools 中使用 Sampling:** 接受 `server_context:` 参数的 Tools 可以在其上调用 `create_sampling_message`。 该请求会自动路由到正确的客户端会话: ``` class SummarizeTool < MCP::Tool description "Summarize text using LLM" input_schema( properties: { text: { type: "string" } }, required: ["text"] ) def self.call(text:, server_context:) result = server_context.create_sampling_message( messages: [ { role: "user", content: { type: "text", text: "Please summarize: #{text}" } } ], max_tokens: 500 ) MCP::Tool::Response.new([{ type: "text", text: result[:content][:text] }]) end end server = MCP::Server.new(name: "my_server", tools: [SummarizeTool]) ``` **参数:** 必需: - `messages:` (Array) - 带有 `role` 和 `content` 的消息对象数组 - `max_tokens:` (Integer) - 响应中的最大 token 数 可选: - `system_prompt:` (String) - LLM 的系统 prompt - `model_preferences:` (Hash) - 模型选择偏好(例如,`{ intelligencePriority: 0.8 }`) - `include_context:` (String) - 上下文包含:`"none"`、`"thisServer"` 或 `"allServers"`(软弃用) - `temperature:` (Float) - Sampling 温度 - `stop_sequences:` (Array) - 停止生成的序列 - `metadata:` (Hash) - 附加元数据 - `tools:` (Array) - LLM 可用的 tools(需要 `sampling.tools` 能力) - `tool_choice:` (Hash) - Tool 选择模式(例如,`{ mode: "auto" }`) **错误处理:** - 如果客户端不支持 `sampling` 能力,则引发 `RuntimeError` - 如果使用了 `tools` 但客户端缺少 `sampling.tools` 能力,则引发 `RuntimeError` - 如果客户端返回错误响应,则引发 `StandardError` ### 通知 服务器支持在 tools、prompts 或 resources 列表更改时向客户端发送通知。这可以实现无需轮询的实时更新。 #### 通知方法 服务器提供以下通知方法: - `notify_tools_list_changed` - 当 tools 列表更改时发送通知 - `notify_prompts_list_changed` - 当 prompts 列表更改时发送通知 - `notify_resources_list_changed` - 当 resources 列表更改时发送通知 - `notify_log_message` - 发送结构化日志通知消息 #### 会话作用域 在使用 Streamable HTTP 传输与多个客户端时,每个客户端连接都有自己的会话。通知的作用域如下: - 通过 tool 处理器内的 `server_context` 调用的 **`report_progress`** 和 **`notify_log_message`** 会自动仅发送给请求客户端。无需额外配置。 - **`notify_tools_list_changed`**、**`notify_prompts_list_changed`** 和 **`notify_resources_list_changed`** 始终广播到所有连接的客户端,因为它们代表服务器范围的状态更改。应直接在 `server` 实例上调用它们。 #### 通知格式 通知遵循 JSON-RPC 2.0 规范并使用以下方法名称: - `notifications/tools/list_changed` - `notifications/prompts/list_changed` - `notifications/resources/list_changed` - `notifications/cancelled` - `notifications/progress` - `notifications/message` ### 取消 MCP Ruby SDK 支持服务器端处理 [MCP `notifications/cancelled` 实用程序](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation)。 当客户端为正在进行的请求发送 `notifications/cancelled` 时,服务器会协作停止处理并抑制该请求的 JSON-RPC 响应。 取消是协作式的:SDK 不会强制终止 tool 代码。相反,一个 `MCP::Cancellation` token 会贯穿 `server_context`,长时间运行的 tools 会轮询它以提前退出。当 tool 在观察到取消后返回时,服务器会抑制 JSON-RPC 响应,与规范匹配。根据规范,`initialize` 请求永远不可取消。 #### 服务器端:检查取消的处理器 任何选择使用 `server_context:` 的处理器——tools(`Tool.call`)、prompt 模板、`resources_read_handler`、`completion_handler`、`resources_subscribe_handler`、`resources_unsubscribe_handler` 和 `define_custom_method` 块——都会接收一个连接到正在进行的请求的取消 token 的 `MCP::ServerContext`。 处理器在其工作循环中检查 `cancelled?`,或调用 `raise_if_cancelled!` 在安全点引发 `MCP::CancelledError`: ``` class LongRunningTool < MCP::Tool description "A tool that supports cancellation" input_schema(properties: { count: { type: "integer" } }, required: ["count"]) def self.call(count:, server_context:) count.times do |i| # Exit early if the client has sent `notifications/cancelled`. break if server_context.cancelled? do_work(i) end MCP::Tool::Response.new([{ type: "text", text: "Done" }]) end end ``` 或者,使用 `raise_if_cancelled!` 在下一个安全点引发: ``` def self.call(count:, server_context:) count.times do |i| server_context.raise_if_cancelled! do_work(i) end MCP::Tool::Response.new([{ type: "text", text: "Done" }]) end ``` 当处理器观察到取消时(无论是通过 `cancelled?` 提前返回,还是通过 `raise_if_cancelled!` 引发 `MCP::CancelledError`),服务器都会丢弃响应,并且不会向客户端发送任何 JSON-RPC 结果。 相同的模式适用于其他处理器类型: ``` # resources/read server.resources_read_handler do |params, server_context:| server_context.raise_if_cancelled! # read the resource end # completion/complete server.completion_handler do |params, server_context:| server_context.raise_if_cancelled! # compute completions end # 自定义方法 server.define_custom_method(method_name: "custom/slow") do |params, server_context:| server_context.raise_if_cancelled! # do work end # prompts(通过 Prompt 子类) class SlowPrompt < MCP::Prompt prompt_name "slow_prompt" def self.template(args, server_context:) server_context.raise_if_cancelled! MCP::Prompt::Result.new(messages: []) end end ``` 未声明 `server_context:` 关键字的处理器继续正常工作——仅在块签名需要时,选择加入检测才会包裹上下文。 #### 嵌套的服务器到客户端请求会自动取消 当 tool 处理器正在等待嵌套的服务器到客户端请求(`server_context.create_sampling_message`、`create_form_elicitation` 或 `create_url_elicitation`)时,取消父 tool 调用会自动从嵌套调用中引发 `MCP::CancelledError`,因此 tool 不需要在其自己的 `cancelled?` 检查中包裹它: ``` def self.call(server_context:) result = server_context.create_sampling_message(messages: messages, max_tokens: 100) # If the parent tools/call is cancelled while waiting above, MCP::CancelledError # is raised here and the tool can let it propagate or clean up as needed. MCP::Tool::Response.new([{ type: "text", text: result[:content][:text] }]) rescue MCP::CancelledError # Optional: run cleanup. Re-raising (or letting it propagate) is fine; the server # will still suppress the JSON-RPC response per the MCP spec. raise end ``` 嵌套取消传播仅在 `StreamableHTTPTransport` 上受支持。 `StdioTransport` 是单线程的,并会阻塞在 `$stdin.gets` 上,因此即使父 `tools/call` 被取消,tool 内部的嵌套 `server_context.create_sampling_message` 也会运行完成。父 tool 本身仍然通过嵌套调用之间的 `server_context.cancelled?` 观察取消。 ### Ping MCP Ruby SDK 支持 [MCP `ping` 实用程序](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/ping),它允许连接的任一端验证对端是否仍然响应。 `ping` 请求没有参数,接收方必须立即以空结果响应。 #### 服务器端 服务器会自动响应传入的 `ping` 请求——无需任何设置。 任何 `MCP::Server` 实例都会回复空结果。 #### 客户端 `MCP::Client` 暴露了 `ping` 方法,用于向服务器发送 ping: ``` client = MCP::Client.new(transport: transport) client.ping # => {} on success ``` 当服务器返回 JSON-RPC 错误时,`#ping` 会引发 `MCP::Client::ServerError`。 当响应 `result` 缺失或不是 Hash 时(符合规范要求的 `result` 必须是对象),它会引发 `MCP::Client::ValidationError`。 传输层错误(例如,`MCP::Client::Stdio` 的 `read_timeout:` 触发)作为传输层引发的异常进行传播。 ### 进度 MCP Ruby SDK 遵循 [MCP Progress 规范](https://modelcontextprotocol.io/specification/latest/server/utilities/progress),支持长时间运行的 tool 操作的进度跟踪。 #### 进度如何工作 1. **客户端请求**:客户端在调用 tool 时在 `_meta` 字段中发送 `progressToken` 2. **服务器通知**:服务器在 tool 执行期间向客户端发送回 `notifications/progress` 消息 3. **Tool 集成**:Tools 调用 `server_context.report_progress` 以报告增量进度 #### 服务器端:带有进度的 Tool 接受 `server_context:` 参数的 Tools 可以在其上调用 `report_progress`。 服务器自动将上下文包裹在提供此方法的 `MCP::ServerContext` 实例中: ``` class LongRunningTool < MCP::Tool description "A tool that reports progress during execution" input_schema( properties: { count: { type: "integer" }, }, required: ["count"] ) def self.call(count:, server_context:) count.times do |i| # Do work here. server_context.report_progress(i + 1, total: count, message: "Processing item #{i + 1}") end MCP::Tool::Response.new([{ type: "text", text: "Done" }]) end end ``` `server_context.report_progress` 方法接受: - `progress`(必需)— 当前进度值(数字) - `total:`(可选)— 预期总值,以便客户端可以显示百分比 - `message:`(可选)— 人类可读的状态消息 **主要特性:** - Tools 通过 `server_context.report_progress` 报告进度 - 当客户端未提供 `progressToken` 时,`report_progress` 是一个空操作 - 支持数字和字符串进度 token ### 补全 MCP 规范包含 [Completions](https://modelcontextprotocol.io/specification/latest/server/utilities/completion),它允许服务器为 prompt 参数和 resource URI 提供自动补全建议。 要启用补全,请声明 `completions` 能力并注册一个处理器: ``` server = MCP::Server.new( name: "my_server", prompts: [CodeReviewPrompt], resource_templates: [FileTemplate], capabilities: { completions: {} }, ) server.completion_handler do |params| ref = params[:ref] argument = params[:argument] value = argument[:value] case ref[:type] when "ref/prompt" values = case argument[:name] when "language" ["python", "pytorch", "pyside"].select { |v| v.start_with?(value) } else [] end { completion: { values: values, hasMore: false } } when "ref/resource" { completion: { values: [], hasMore: false } } end end ``` 处理器接收一个包含以下内容的 `params` 哈希: - `ref` - 引用(`{ type: "ref/prompt", name: "..." }` 或 `{ type: "ref/resource", uri: "..." }`) - `argument` - 正在补全的参数(`{ name: "...", value: "..." }`) - `context`(可选)- 先前已解析的参数(`{ arguments: { ... } }`) 处理器必须返回一个带有 `completion` 键的哈希,该键包含 `values`(字符串数组),以及可选的 `total` 和 `hasMore`。 SDK 根据 MCP 规范自动强制执行 100 项的限制。 服务器在调用处理器之前会验证所引用的 prompt、resource 或 resource 模板是否已注册。 对未知引用的请求将返回错误。 ### Elicitation MCP Ruby SDK 支持 [elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation),它允许服务器在 tool 执行期间通过客户端向用户请求额外信息。 Elicitation 是一种**服务器到客户端的请求**。服务器发送请求并阻塞,直到用户通过客户端响应。 #### 能力 客户端必须在初始化期间声明 `elicitation` 能力。服务器在发送任何 elicitation 请求之前会检查这一点,如果客户端不支持,则会引发 `RuntimeError`。 要支持 URL 模式,客户端还必须声明 `elicitation.url` 能力。 #### 在 Tools 中使用 Elicitation 接受 `server_context:` 参数的 Tools 可以在其上调用 `create_form_elicitation`: ``` server.define_tool(name: "collect_info", description: "Collect user info") do |server_context:| result = server_context.create_form_elicitation( message: "Please provide your name", requested_schema: { type: "object", properties: { name: { type: "string" } }, required: ["name"], }, ) MCP::Tool::Response.new([{ type: "text", text: "Hello, #{result[:content][:name]}" }]) end ``` #### 表单模式 表单模式直接通过 MCP 客户端从用户那里收集结构化数据: ``` server.define_tool(name: "collect_contact", description: "Collect contact info") do |server_context:| result = server_context.create_form_elicitation( message: "Please provide your contact information", requested_schema: { type: "object", properties: { name: { type: "string", description: "Your full name" }, email: { type: "string", format: "email", description: "Your email address" }, }, required: ["name", "email"], }, ) text = case result[:action] when "accept" "Hello, #{result[:content][:name]} (#{result[:content][:email]})" when "decline" "User declined" when "cancel" "User cancelled" end MCP::Tool::Response.new([{ type: "text", text: text }]) end ``` #### URL 模式 URL 模式将用户引导至外部 URL 以进行带外交互,例如 OAuth 流程: ``` server.define_tool(name: "authorize_github", description: "Authorize GitHub") do |server_context:| elicitation_id = SecureRandom.uuid result = server_context.create_url_elicitation( message: "Please authorize access to your GitHub account", url: "https://example.com/oauth/authorize?elicitation_id=#{elicitation_id}", elicitation_id: elicitation_id, ) server_context.notify_elicitation_complete(elicitation_id: elicitation_id) MCP::Tool::Response.new([{ type: "text", text: "Authorization complete" }]) end ``` #### URLElicitationRequiredError 当 tool 在完成带外 elicitation 之前无法继续时,引发 `MCP::Server::URLElicitationRequiredError`。 这会向客户端返回一个代码为 `-32042` 的 JSON-RPC 错误: ``` server.define_tool(name: "access_github", description: "Access GitHub") do |server_context:| raise MCP::Server::URLElicitationRequiredError.new([ { mode: "url", elicitationId: SecureRandom.uuid, url: "https://example.com/oauth/authorize", message: "GitHub authorization is required.", }, ]) end ``` ### 日志记录 MCP Ruby SDK 遵循 [MCP Logging 规范](https://modelcontextprotocol.io/specification/latest/server/utilities/logging),通过 `notify_log_message` 方法支持结构化日志记录。 `notifications/message` 通知用于客户端和服务器之间的结构化日志记录。 #### 日志级别 SDK 支持 8 个严重性递增的日志级别: - `debug` 详细的调试信息 - `info` - 一般信息性消息 - `notice` - 正常但重要的事件 - `warning` - 警告条件 - `error` - 错误条件 - `critical` - 严重条件 - `alert` - 必须立即采取行动 - `emergency` - 系统不可用 #### 日志记录如何工作 1. **客户端配置**:客户端发送 `logging/setLevel` 请求以配置最低日志级别 2. **服务器过滤**:服务器仅发送配置级别或更高严重性的日志消息 3. **通知传递**:日志消息作为 `notifications/message` 发送到客户端 例如,如果客户端将级别设置为 `"error"`(严重性 4),服务器将发送以下级别的消息:`error`、`critical`、`alert` 和 `emergency`。 有关更多详细信息,请参阅 [MCP Logging 规范](https://modelcontextprotocol.io/specification/latest/server/utilities/logging)。 **用法示例:** ``` server = MCP::Server.new(name: "my_server") transport = MCP::Server::Transports::StdioTransport.new(server) # 客户端首先配置日志级别(在客户端): transport.send_request( request: { jsonrpc: "2.0", method: "logging/setLevel", params: { level: "info" }, id: session_id # Unique request ID within the session } ) # 发送不同严重级别的日志消息 server.notify_log_message( data: { message: "Application started successfully" }, level: "info" ) server.notify_log_message( data: { message: "Configuration file not found, using defaults" }, level: "warning" ) server.notify_log_message( data: { error: "Database connection failed", details: { host: "localhost", port: 5432 } }, level: "error", logger: "DatabaseLogger" # Optional logger name ) ``` **主要特性:** - 支持基于 https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging#log-levels 的 8 个日志级别(debug、info、notice、warning、error、critical、alert、emergency) - 服务器具有发送日志消息的 `logging` 能力 - 仅在配置了传输层时才发送消息 - 消息根据客户端配置的日志级别进行过滤 - 如果客户端未设置日志级别,则不会发送任何消息 #### 传输层支持 - **stdio**:通知作为 JSON-RPC 2.0 消息发送到 stdout - **Streamable HTTP**:通知通过 HTTP 流式传输(分块传输或 SSE)作为 JSON-RPC 2.0 消息发送 #### 用法示例 ``` server = MCP::Server.new(name: "my_server") # 默认 Streamable HTTP - 面向会话 transport = MCP::Server::Transports::StreamableHTTPTransport.new(server) # 当 tools 更改时,通知客户端 server.define_tool(name: "new_tool") { |**args| { result: "ok" } } server.notify_tools_list_changed ``` 您可以使用无状态的 Streamable HTTP,其中不支持通知,所有调用都是请求/响应交互。 此模式允许轻松的多节点部署。 在 `MCP::Server::Transports::StreamableHTTPTransport.new` 中设置 `stateless: true`(`stateless` 默认为 `false`): ``` # 无状态 Streamable HTTP - 无会话 transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true) ``` 您可以启用 JSON 响应模式,其中服务器返回 `application/json` 而不是 `text/event-stream`。 在 `MCP::Server::Transports::StreamableHTTPTransport.new` 中设置 `enable_json_response: true`: ``` # JSON 响应模式 transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, enable_json_response: true) ``` 在 JSON 响应模式下,POST 响应是一个单一的 JSON 对象,因此在请求处理期间需要到达的服务器到客户端消息不受支持:请求范围的通知(`progress`、`log`)会被静默丢弃,并且所有服务器到客户端的请求(`sampling/createMessage`、`roots/list`、`elicitation/create`)都会引发错误。 会话范围的独立通知(`resources/updated`、`elicitation/complete`)和广播通知(`tools/list_changed` 等)仍然会流向连接到 GET SSE 流的客户端。 此模式适用于不需要服务器发起请求的简单 tool 服务器。 默认情况下,会话不会过期。为了降低会话劫持风险,您可以设置 `session_idle_timeout`(以秒为单位)。 配置后,在指定持续时间内未收到任何 HTTP 请求的会话将自动过期并清理: ``` # 30 分钟会话超时 transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, session_idle_timeout: 1800) ``` ### 分页 MCP Ruby SDK 为可能返回大型结果集的列表操作支持[分页](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/pagination)。分页使用带有从零开始的偏移量的字符串游标 token,客户端将其视为不透明的:服务器决定页面大小,客户端跟随 `nextCursor` 直到服务器省略它。 分页适用于 `tools/list`、`prompts/list`、`resources/list` 和 `resources/templates/list`。 #### 服务器端:启用分页 将 `page_size:` 传递给 `MCP::Server.new` 以将列表响应拆分为多个页面。当省略 `page_size`(默认值)时,列表响应在单个响应中包含所有项目,保留了分页前的行为。 ``` server = MCP::Server.new( name: "my_server", tools: tools, page_size: 50, ) ``` 当设置 `page_size` 时,只要有更多页面可用,列表响应就会包含 `nextCursor` 字段: ``` { "jsonrpc": "2.0", "id": 1, "result": { "tools": [ { "name": "example_tool" } ], "nextCursor": "50" } } ``` 无效的游标(例如,非数字、负数或超出范围)将根据 MCP 规范以 JSON-RPC 错误代码 `-32602 (Invalid params)` 被拒绝。 #### 客户端:迭代页面 `MCP::Client` 暴露了 `list_tools`、`list_prompts`、`list_resources` 和 `list_resource_templates`。 **每次调用仅发出一个 `*/list` JSON-RPC 请求并返回恰好一个页面**——而不是完整的集合。 返回的结果对象(`MCP::Client::ListToolsResult` 等)将页面项和下一个游标作为方法访问器暴露: ``` client = MCP::Client.new(transport: transport) cursor = nil loop do page = client.list_tools(cursor: cursor) page.tools.each { |tool| process(tool) } cursor = page.next_cursor break unless cursor end ``` 相同的模式适用于 `list_prompts`(`page.prompts`)、`list_resources`(`page.resources`)和 `list_resource_templates`(`page.resource_templates`)。`next_cursor` 在最后一页为 `nil`。 由于单次调用返回单个页面,返回多少项目取决于服务器的 `page_size` 配置: | 服务器 `page_size` | `client.list_tools(cursor: nil)` | |--------------------|---------------------------------------------------------------------| | 未设置(默认) | 在一个响应中返回所有项目。`next_cursor` 为 `nil`。 | | 设置为 `N` | 返回前 `N` 个项目。`next_cursor` 用于继续。 | 如果您的应用程序无论服务器如何配置都需要完整的集合,可以如上所示循环 `next_cursor`,或者使用下面描述的全集合方法。 #### 获取完整集合 `client.tools`、`client.resources`、`client.resource_templates` 和 `client.prompts` 会自动迭代所有页面并返回一个普通的项目数组,无论服务器的 `page_size` 设置如何,都保证完整的集合。当服务器分页时,它们每次调用会发出多次 JSON-RPC 往返,并且作为安全措施,如果服务器连续两次返回相同的 `nextCursor`,它们会跳出分页循环。 ``` tools = client.tools # => Array of every tool on the server. ``` 当您需要完整列表时使用这些方法;当您需要细粒度迭代(例如,流式处理页面而不将所有内容加载到内存中)时,请使用 `list_tools(cursor:)` 等。 ### 高级 #### 自定义方法 服务器允许您使用 `define_custom_method` 方法定义超出标准 MCP 协议方法的自定义 JSON-RPC 方法: ``` server = MCP::Server.new(name: "my_server") # 定义一个返回结果的自定义方法 server.define_custom_method(method_name: "add") do |params| params[:a] + params[:b] end # 定义一个自定义通知方法(返回 nil) server.define_custom_method(method_name: "notify") do |params| # Process notification nil end ``` **主要特性:** - 接受任何字符串作为方法名称 - 块接收请求参数作为哈希 - 可以处理常规方法(带响应)和通知 - 防止覆盖现有的 MCP 协议方法 - 支持用于监控的 instrumentation 回调 **用法示例:** ``` # 客户端请求 { "jsonrpc": "2.0", "id": 1, "method": "add", "params": { "a": 5, "b": 3 } } # 服务器响应 { "jsonrpc": "2.0", "id": 1, "result": 8 } ``` **错误处理:** - 如果尝试覆盖现有方法,则引发 `MCP::Server::MethodAlreadyDefinedError` - 支持与标准方法相同的异常报告和 instrumentation ## 构建 MCP 客户端 `MCP::Client` 类提供了一个用于与 MCP 服务器交互的接口。 此类支持: - 通过 `ping` 方法进行存活性检查(`MCP::Client#ping`) - 通过 `tools/list` 方法列出 tool(`MCP::Client#tools`) - 通过 `tools/call` 方法调用 tool(`MCP::Client#call_tools`) - 通过 `resources/list` 方法列出 resource(`MCP::Client#resources`) - 通过 `resources/templates/list` 方法列出 resource 模板(`MCP::Client#resource_templates`) - 通过 `resources/read` 方法读取 resource(`MCP::Client#read_resource`) - 通过 `prompts/list` 方法列出 prompt(`MCP::Client#prompts`) - 通过 `prompts/get` 方法检索 prompt(`MCP::Client#get_prompt`) - 通过 `completion/complete` 方法请求补全(`MCP::Client#complete`) - 自动 JSON-RPC 2.0 消息格式化 - UUID 请求 ID 生成 客户端使用传输层实例进行初始化,该实例处理底层通信机制。 授权由传输层处理。 ## 传输层接口 如果您需要的传输层不包含在 gem 中,您可以构建并传递您自己的实例,只要它们符合以下接口: ``` class CustomTransport # Sends a JSON-RPC request to the server and returns the raw response. # # @param request [Hash] A complete JSON-RPC request object. # https://www.jsonrpc.org/specification#request_object # @return [Hash] A hash modeling a JSON-RPC response object. # https://www.jsonrpc.org/specification#response_object def send_request(request:) # Your transport-specific logic here # - HTTP: POST to endpoint with JSON body # - WebSocket: Send message over WebSocket # - stdio: Write to stdout, read from stdin # - etc. end end ``` ### Stdio 传输层 使用 `MCP::Client::Stdio` 传输与作为子进程通过标准输入/输出运行的 MCP 服务器进行交互。 `MCP::Client::Stdio.new` 接受以下关键字参数: | 参数 | 必需 | 描述 | |---|---|---| | `command:` | 是 | 用于生成服务器进程的命令(例如,`"ruby"`、`"bundle"`、`"npx"`)。 | | `args:` | 否 | 传递给命令的参数数组。默认为 `[]`。 | | `env:` | 否 | 为服务器进程设置的环境变量哈希。默认为 `nil`。 | | `read_timeout:` | 否 | 等待服务器响应的超时时间(以秒为单位)。默认为 `nil`(无超时)。 | 用法示例: ``` stdio_transport = MCP::Client::Stdio.new( command: "bundle", args: ["exec", "ruby", "path/to/server.rb"], env: { "API_KEY" => "my_secret_key" }, read_timeout: 30 ) client = MCP::Client.new(transport: stdio_transport) # 列出可用的 tools。 tools = client.tools tools.each do |tool| puts "Tool: #{tool.name} - #{tool.description}" end # 调用特定的 tool。 response = client.call_tool( tool: tools.first, arguments: { message: "Hello, world!" } ) # 完成后关闭 transport。 stdio_transport.close ``` stdio 传输自动处理: - 使用 `Open3.popen3` 生成服务器进程 - MCP 协议初始化握手(`initialize` 请求 + `notifications/initialized`) - 基于换行符分隔的 JSON 的 JSON-RPC 2.0 消息帧封装 ### HTTP 传输层 使用 `MCP::Client::HTTP` 传输通过简单的 HTTP 请求与 MCP 服务器进行交互。 您需要将 `faraday` 添加为依赖项才能使用 HTTP 传输层。如果服务器使用 SSE(`text/event-stream`)响应,还需添加 `event_stream_parser`: ``` gem 'mcp' gem 'faraday', '>= 2.0' gem 'event_stream_parser', '>= 1.0' # optional, required only for SSE responses ``` 用法示例: ``` http_transport = MCP::Client::HTTP.new(url: "https://api.example.com/mcp") client = MCP::Client.new(transport: http_transport) # 列出可用的 tools tools = client.tools tools.each do |tool| puts <<~TOOL_INFORMATION Tool: #{tool.name} Description: #{tool.description} Input Schema: #{tool.input_schema} TOOL_INFORMATION end # 调用特定的 tool response = client.call_tool( tool: tools.first, arguments: { message: "Hello, world!" } ) # 调用带有进度跟踪的 tool。 response = client.call_tool( tool: tools.first, arguments: { count: 10 }, progress_token: "my-progress-token" ) ``` 服务器将在执行期间向客户端发送 `notifications/progress`。 #### HTTP 授权 默认情况下,HTTP 传输层不向服务器提供身份验证,但如果需要身份验证,您可以提供自定义请求头。例如,要使用 Bearer token 身份验证: ``` http_transport = MCP::Client::HTTP.new( url: "https://api.example.com/mcp", headers: { "Authorization" => "Bearer my_token" } ) client = MCP::Client.new(transport: http_transport) client.tools # will make the call using Bearer auth ``` 您可以添加身份验证方案或任何其他目的所需的任何自定义请求头。客户端将在每个请求中包含这些请求头。 #### 自定义 Faraday 连接 您可以将块传递给 `MCP::Client::HTTP.new` 以自定义底层 Faraday 连接。 该块在默认中间件配置之后调用,因此您可以添加中间件或替换 HTTP 适配器: ``` http_transport = MCP::Client::HTTP.new(url: "https://api.example.com/mcp") do |faraday| faraday.use MyApp::Middleware::HttpRecorder faraday.adapter :typhoeus end ``` ### Tool 对象 客户端为服务器返回的 tools 提供了一个包装类: - `MCP::Client::Tool` - 表示带有其元数据的单个 tool 此类提供了对 tool 属性(如名称、描述、input schema 和 output schema)的轻松访问。 ## 一致性测试 `conformance/` 目录包含一个测试服务器和运行器,它们使用 [`@modelcontextprotocol/conformance`](https://github.com/modelcontextprotocol/conformance) 根据 MCP 规范验证 SDK。 有关使用说明,请参阅 [conformance/README.md](conformance/README.md)。 ## 文档 - [SDK API 文档](https://rubydoc.info/gems/mcp) - [Model Context Protocol 文档](https://modelcontextprotocol.io)
标签:Apache 2.0, DLL 劫持, HTTP传输, JSON-RPC, LLM, MCP, Model Context Protocol, Prompt管理, Ruby, Ruby Gem, SSE, Unmanaged PE, 人工智能, 大语言模型, 客户端, 工具调用, 开发工具库, 开源, 批量扫描, 服务器, 用户模式Hook绕过, 知识库, 资源管理