Class: LLM::MCP

Inherits:
Object
  • Object
show all
Defined in:
lib/llm/mcp.rb,
lib/llm/mcp/rpc.rb,
lib/llm/mcp/pipe.rb,
lib/llm/mcp/error.rb,
lib/llm/mcp/command.rb

Overview

The LLM::MCP class provides access to servers that implement the Model Context Protocol. MCP defines a standard way for clients and servers to exchange capabilities such as tools, prompts, resources, and other structured interactions.

In llm.rb, LLM::MCP currently supports stdio and HTTP transports and focuses on discovering tools that can be used through LLM::Context and LLM::Agent.

Like LLM::Context, an MCP client is stateful and is expected to remain isolated to a single thread.

Defined Under Namespace

Modules: Transport Classes: Command, Pipe

Constant Summary collapse

Error =
Class.new(LLM::Error) do
  attr_reader :code, :data

  ##
  # @param [Hash] response
  #  The full response from the MCP process, including the error object
  # @return [LLM::MCP::Error]
  def self.from(response:)
    error = response.fetch("error")
    new(*error.values_at("message", "code", "data"))
  end

  ##
  # @param [String] message
  #  The error message
  # @param [Integer] code
  #  The error code
  # @param [Object] data
  #  Additional error data provided by the MCP process
  def initialize(message, code = nil, data = nil)
    super(message)
    @code = code
    @data = data
  end
end
MismatchError =
Class.new(Error) do
  ##
  # @return [Integer, String]
  #  The request id the client was waiting for
  attr_reader :expected_id

  ##
  # @return [Integer, String]
  #  The response id received from the server
  attr_reader :actual_id

  ##
  # @param [Integer, String] expected_id
  #  The request id the client was waiting for
  # @param [Integer, String] actual_id
  #  The response id received from the server instead
  def initialize(expected_id:, actual_id:)
    @expected_id = expected_id
    @actual_id = actual_id
    super(message)
  end

  ##
  # @return [String]
  def message
    "mismatched MCP response id #{actual_id.inspect} " \
    "while waiting for #{expected_id.inspect}"
  end
end
TimeoutError =
Class.new(Error)
@@clients =
{}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(llm = nil, stdio: nil, http: nil, timeout: 30) ⇒ LLM::MCP

Returns A new MCP instance.

Parameters:

  • llm (LLM::Provider, nil) (defaults to: nil)

    The provider to use for MCP transports that need one

  • stdio (Hash, nil) (defaults to: nil)

    The configuration for the stdio transport

  • http (Hash, nil) (defaults to: nil)

    The configuration for the HTTP transport

  • timeout (Integer) (defaults to: 30)

    The maximum amount of time to wait when reading from an MCP process

Options Hash (stdio:):

  • :argv (Array<String>)

    The command to run for the MCP process

  • :env (Hash)

    The environment variables to set for the MCP process

  • :cwd (String, nil)

    The working directory for the MCP process

Options Hash (http:):

  • :url (String)

    The URL for the MCP HTTP endpoint

  • :headers (Hash)

    Extra headers for requests



71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/llm/mcp.rb', line 71

def initialize(llm = nil, stdio: nil, http: nil, timeout: 30)
  @llm = llm
  @timeout = timeout
  if stdio && http
    raise ArgumentError, "stdio and http are mutually exclusive"
  elsif stdio
    @command = Command.new(**stdio)
    @transport = Transport::Stdio.new(command:)
  elsif http
    @transport = Transport::HTTP.new(**http, timeout:)
  else
    raise ArgumentError, "stdio or http is required"
  end
end

Class Method Details

.stdio(llm = nil, **stdio) ⇒ LLM::MCP

Builds an MCP client that uses the stdio transport.

Parameters:

  • llm (LLM::Provider, nil) (defaults to: nil)

    An instance of LLM::Provider. Optional.

  • stdio (Hash)

    The stdio transport configuration

Returns:



38
39
40
# File 'lib/llm/mcp.rb', line 38

def self.stdio(llm = nil, **stdio)
  new(llm, stdio:)
end

.http(llm = nil, **http) ⇒ LLM::MCP

Builds an MCP client that uses the HTTP transport.

Parameters:

  • llm (LLM::Provider, nil) (defaults to: nil)

    An instance of LLM::Provider. Optional.

  • http (Hash)

    The HTTP transport configuration

Returns:



49
50
51
# File 'lib/llm/mcp.rb', line 49

def self.http(llm = nil, **http)
  new(llm, http:)
end

.clientsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
# File 'lib/llm/mcp.rb', line 29

def self.clients = @@clients

Instance Method Details

#call_tool(name, arguments = {}) ⇒ Object

Calls a tool by name with the given arguments

Parameters:

  • name (String)

    The name of the tool to call

  • arguments (Hash) (defaults to: {})

    The arguments to pass to the tool

Returns:

  • (Object)

    The result of the tool call



157
158
159
160
# File 'lib/llm/mcp.rb', line 157

def call_tool(name, arguments = {})
  res = call(transport, "tools/call", {name:, arguments:})
  adapt_tool_result(res)
end

#startvoid

This method returns an undefined value.

Starts the MCP process.



89
90
91
92
93
# File 'lib/llm/mcp.rb', line 89

def start
  transport.start
  call(transport, "initialize", {clientInfo: {name: "llm.rb", version: LLM::VERSION}})
  call(transport, "notifications/initialized")
end

#stopvoid

This method returns an undefined value.

Stops the MCP process.



98
99
100
101
# File 'lib/llm/mcp.rb', line 98

def stop
  transport.stop
  nil
end

#persist!LLM::MCP Also known as: persistent

Configures an HTTP MCP transport to use a persistent connection pool via the optional dependency Net::HTTP::Persistent

Examples:

mcp = LLM.mcp(http: {url: "https://example.com/mcp"}).persistent
# do something with 'mcp'

Returns:



110
111
112
113
# File 'lib/llm/mcp.rb', line 110

def persist!
  transport.persist!
  self
end

#toolsArray<Class<LLM::Tool>>

Returns the tools provided by the MCP process.

Returns:



119
120
121
122
# File 'lib/llm/mcp.rb', line 119

def tools
  res = call(transport, "tools/list")
  res["tools"].map { LLM::Tool.mcp(self, _1) }
end

#promptsArray<LLM::Object>

Returns the prompts provided by the MCP process.

Returns:



127
128
129
130
# File 'lib/llm/mcp.rb', line 127

def prompts
  res = call(transport, "prompts/list")
  LLM::Object.from(res["prompts"])
end

#find_prompt(name:, arguments: nil) ⇒ LLM::Object Also known as: get_prompt

Returns a prompt by name.

Parameters:

  • name (String)

    The prompt name

  • arguments (Hash<String, String>, nil) (defaults to: nil)

    The prompt arguments

Returns:



137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/llm/mcp.rb', line 137

def find_prompt(name:, arguments: nil)
  params = {name:}
  params[:arguments] = arguments if arguments
  res = call(transport, "prompts/get", params)
  res["messages"] = [*res["messages"]].map do |message|
    LLM::Message.new(
      message["role"],
      adapt_content(message["content"]),
      {original_content: message["content"]}
    )
  end
  LLM::Object.from(res)
end