class RubyLLM::MCP
An MCP is a client for a Model Context Protocol server. Describe the server to connect to in a subclass, the way you describe a Tool or an Agent, then hand an instance to a chat:
class Linear < RubyLLM::MCP url "https://mcp.linear.app/mcp" inputs :user bearer_token { user.linear_token } end linear = Linear.new(user: current_user) linear.tools # => [#<RubyLLM::MCP::Tool name: "list_issues", ...>, ...] linear.list_issues(query: "bug") # => #<RubyLLM::MCP::Result ...>
A url connects over Streamable HTTP; a command starts a local server that speaks over stdio:
class Files < RubyLLM::MCP command "npx", "-y", "@modelcontextprotocol/server-filesystem", "." end
Settings that depend on runtime state take a block or a method name, evaluated on the instance, so declared ::inputs and private methods are available. RubyLLM.mcp builds one inline when a class is not worth writing.
Public Class Methods
# File lib/ruby_llm/mcp.rb, line 258 def after_progress(method = nil, &block) add_callback(:after_progress, method, block) end
Registers a callback for the progress the server reports while it works on a request. Pass a method name or a block; either runs on the MCP instance with an MCP::Progress.
after_progress :broadcast_progress after_progress { |progress| puts progress.message }
# File lib/ruby_llm/mcp.rb, line 118 def bearer_token(value = nil, &block) return @bearer_token if value.nil? && block.nil? @bearer_token = block || value end
Sets the bearer token sent in the Authorization header. Pass the token, a method name, or a block. Called with no argument, returns the configured value.
bearer_token ENV.fetch("LINEAR_API_KEY") bearer_token { user.linear_token }
# File lib/ruby_llm/mcp.rb, line 270 def before_input_request(method = nil, &block) add_callback(:before_input_request, method, block) end
Registers a callback for the server’s requests for input from the user. Pass a method name or a block; either runs on the MCP instance with an MCP::InputRequest to answer or decline. In a chat, a request no callback answers pauses the tool call; see Chat#pending_inputs.
before_input_request :ask_operator before_input_request { |request| request.answer(environment: "staging") }
Source
# File lib/ruby_llm/mcp.rb, line 70 def command(*argv) return @command if argv.empty? @command = argv.flatten end
Sets the command that starts a local server speaking over stdio. The process starts on the first request. Called with no arguments, returns the configured command.
command "npx", "-y", "@modelcontextprotocol/server-filesystem", "."
Source
# File lib/ruby_llm/mcp.rb, line 80 def directory(value = nil) return @directory if value.nil? @directory = value end
Sets the working directory for a stdio server’s process.
directory Rails.root
Source
# File lib/ruby_llm/mcp.rb, line 91 def env(**variables) return @env || {} if variables.empty? @env = env.merge(variables) end
Adds environment variables for a stdio server’s process. Values may be blocks or method names. Called with no arguments, returns them.
env NODE_ENV: "production", API_KEY: -> { user.api_key }
Source
# File lib/ruby_llm/mcp.rb, line 182 def except(*names) return @except || [] if names.empty? @except = names.flatten.map(&:to_s) end
Hides the named server tools from the model.
except :delete_repository
# File lib/ruby_llm/mcp.rb, line 103 def header(name, value = nil, &block) @headers = headers.merge(name.to_s => block || value) end
Adds an HTTP header sent with every request to the server. Pass the value, a method name, or a block.
header "X-MCP-Toolsets", "issues,pull_requests" header("X-Account") { user.account_id }
Source
# File lib/ruby_llm/mcp.rb, line 161 def inputs(*names) return @input_names || [] if names.empty? @input_names = names.flatten.map(&:to_sym) @input_names.each { |input| define_method(input) { @inputs[input] } } end
Declares named inputs. Instances take them as keywords and read them as methods, so blocks such as a bearer_token can use them. Called with no arguments, returns the declared names.
inputs :user
Source
# File lib/ruby_llm/mcp.rb, line 320 def initialize(**inputs) unknown = inputs.keys - self.class.inputs raise ArgumentError, "Unknown MCP inputs: #{unknown.join(', ')}" if unknown.any? @inputs = inputs end
# File lib/ruby_llm/mcp.rb, line 136 def oauth(owner: nil, scopes: nil, client_id: nil, client_secret: nil) @oauth = { owner:, scopes:, client_id:, client_secret: } end
Authorizes requests with OAuth, as the MCP authorization spec describes. RubyLLM discovers the server’s authorization server and registers itself unless you pass the client_id: and client_secret: of an app you registered, which servers such as Slack require. owner: names whose credentials these are, usually an input. scopes: overrides the scopes the server asks for.
oauth owner: :user oauth owner: :user, client_id: ENV["SLACK_CLIENT_ID"], client_secret: ENV["SLACK_CLIENT_SECRET"]
Send the user to MCP#authorization_url, then pass the callback’s parameters to MCP#authorize.
Source
# File lib/ruby_llm/mcp.rb, line 172 def only(*names) return @only if names.empty? @only = names.flatten.map(&:to_s) end
Limits the tools the model sees to the named server tools.
only :search_issues, :get_issue
Source
# File lib/ruby_llm/mcp.rb, line 194 def prefix(value = nil) return @prefix if value.nil? @prefix = value.to_s end
Prefixes the names of the server’s tools, so tools from servers that share names, such as two servers with a search tool, can join one chat. Tools renamed with ::tool keep the name you gave them.
prefix :github # search_issues becomes github_search_issues
# File lib/ruby_llm/mcp.rb, line 240 def requires_approval(*names, **options) unknown = options.keys - [:if] raise ArgumentError, "Unknown requires_approval options: #{unknown.join(', ')}" if unknown.any? @approvals = approvals + [[names.flatten.map(&:to_s), options[:if]]] end
Pauses the named server tools for approval before they run, using the flow of Tool.requires_approval. Without names, every tool needs approval. if: takes a Tool predicate, or a lambda that receives the tool:
requires_approval :create_issue, :merge_pull_request requires_approval if: :destructive?
Source
# File lib/ruby_llm/mcp.rb, line 149 def timeout(seconds = nil) return @timeout if seconds.nil? @timeout = seconds end
Sets how many seconds a request to the server may take. Defaults to the configured request_timeout.
timeout 30
# File lib/ruby_llm/mcp.rb, line 219 def tool(tool, as: nil, description: nil, fixed_arguments: nil, wrap: nil) @tool_declarations = tool_declarations.dup @tool_declarations << if tool.is_a?(Class) tool else [tool.to_s, { as:, description:, fixed_arguments:, wrap: }.compact] end end
Shapes a server tool, or adds one of your own.
Given a server tool’s name, as: renames it, description: rewrites what the model reads, and fixed_arguments: removes arguments from the model’s view and always sends your values, which may be lambdas. wrap: names a method that receives the server’s Result and the call’s arguments and returns what the model sees:
tool :search_files, as: :drive_search, description: "Search the user's Drive" tool :search_issues, fixed_arguments: { owner: "crmne", repo: "ruby_llm" } tool :read_file, wrap: :extract_text
Given a Tool class, adds it next to the server’s tools. The tool is created with this MCP when its initialize takes an argument, so it can call the server:
tool SearchWithPreviews
Source
# File lib/ruby_llm/mcp.rb, line 58 def url(value = nil) return @url if value.nil? @url = value end
Sets the server’s Streamable HTTP endpoint. Plain HTTP is only allowed for loopback addresses. Called with no argument, returns the configured value.
url "https://mcp.linear.app/mcp"
Public Instance Methods
Source
# File lib/ruby_llm/mcp.rb, line 357 def call(name, **arguments) Result.new(request('tools/call', { name: name.to_s, arguments: })) end
Calls the server tool name with arguments and returns an MCP::Result. Every server tool is also a method:
linear.call(:list_issues, query: "bug") linear.list_issues(query: "bug")
Raises MCP::Error when the server answers with a protocol error. A tool that fails returns a Result whose #error? is true.
Source
# File lib/ruby_llm/mcp.rb, line 485 def close @client&.close end
Closes the connection, stopping a stdio server’s process. The next request reconnects.
Source
# File lib/ruby_llm/mcp.rb, line 440 def instructions client.server['instructions'] end
Returns the instructions the server gives for using it, or nil.
Source
# File lib/ruby_llm/mcp.rb, line 332 def name self.class.default_name end
Returns the name that identifies this MCP in a chat, derived from the class name.
GoogleDrive.new.name # => "google_drive"
Source
# File lib/ruby_llm/mcp.rb, line 398 def prompt(name, **arguments) result = request('prompts/get', { name: name.to_s, arguments: arguments.transform_values(&:to_s) }) messages = result.fetch('messages', []).map do |message| content, attachments = Content.read([message['content']]) Message.new(role: message['role'].to_sym, content:, attachments:) end Prompt.new(self, { 'name' => name.to_s, 'description' => result['description'] }, messages:) end
Fills in the server prompt name with arguments and returns an MCP::Prompt with its messages, ready for Chat#ask.
chat.ask github.prompt(:code_review, code: diff)
Source
# File lib/ruby_llm/mcp.rb, line 389 def prompts client.list('prompts/list', 'prompts').map { |data| Prompt.new(self, data) } end
Returns the prompts the server offers, as MCP::Prompt objects.
Source
# File lib/ruby_llm/mcp.rb, line 373 def resource(uri, **variables) uri = ResourceTemplate.expand(uri, variables) unless variables.empty? contents = request('resources/read', { uri: }).fetch('contents', []) data = contents.find { |content| content['uri'] == uri } || contents.first raise Error, "#{name} returned no content for #{uri}" unless data Resource.new(self, data) end
Reads the resource at uri and returns an MCP::Resource. Given a template from resource_templates, variables fill it in.
files.resource("file:///project/README.md") files.resource("file:///{path}", path: "Gemfile")
Source
# File lib/ruby_llm/mcp.rb, line 384 def resource_templates client.list('resources/templates/list', 'resourceTemplates').map { |data| ResourceTemplate.new(self, data) } end
Returns the server’s resource templates as MCP::ResourceTemplate objects.
Source
# File lib/ruby_llm/mcp.rb, line 363 def resources client.list('resources/list', 'resources').map { |data| Resource.new(self, data) } end
Returns the resources the server lists, as MCP::Resource objects whose content is read when you first ask for it.
Source
# File lib/ruby_llm/mcp.rb, line 342 def tools @tools ||= begin check_declared_tools server_tools.filter_map { |definition| shape(definition) } + added_tools end end
Source
# File lib/ruby_llm/mcp.rb, line 445 def version server_info['version'] end
Returns the version the server reports for itself, or nil.