Module: RobotLab::MCP::ServerDiscovery Private
- Defined in:
- lib/robot_lab/mcp/server_discovery.rb
Overview
This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.
Selects relevant MCP servers for a given user query using TF cosine similarity between the query and each server’s topic text (name + description).
This is used as a fallback mechanism when a robot has many MCP servers configured but only some are relevant to a particular user message. Instead of connecting to all servers upfront, the robot can enable discovery so only the semantically matching servers are connected.
Usage
robot = RobotLab.build(
name: "assistant",
mcp_discovery: true,
mcp: [
{
name: "filesystem",
description: "Read, write, and search local files and directories",
transport: { type: "stdio", command: "mcp-server-fs" }
},
{
name: "github",
description: "GitHub repos, issues, pull requests, code search",
transport: { type: "stdio", command: "mcp-server-github" }
},
{
name: "brew",
description: "Install, update, and manage macOS packages via Homebrew",
transport: { type: "stdio", command: "mcp-server-brew" }
}
]
)
# Discovery connects only the :brew server for this query:
robot.run("install imagemagick")
Fallback Behaviour
The full server list is returned unchanged when:
-
No server has a
:descriptionfield -
The ‘classifier’ gem is unavailable
-
The query is blank
-
No server scores at or above
threshold(minimum relevance)
Constant Summary collapse
- DEFAULT_THRESHOLD =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Minimum cosine similarity score for a server to be considered relevant. Low by design — server descriptions are short, so scores are naturally low even for on-topic queries.
0.05
Class Method Summary collapse
- .any_descriptions?(servers) ⇒ Boolean private
-
.description_for(server) ⇒ String
private
Description or empty string.
-
.score(query, server) ⇒ Float
private
Cosine similarity in [0.0, 1.0].
-
.select(query, from:, threshold: DEFAULT_THRESHOLD) ⇒ Array<Hash, MCP::Server>
private
Select MCP servers relevant to the given query.
-
.topic_text(server) ⇒ String
private
Build the topic string used for similarity scoring: name + description.
Class Method Details
.any_descriptions?(servers) ⇒ Boolean
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.
82 83 84 |
# File 'lib/robot_lab/mcp/server_discovery.rb', line 82 def self.any_descriptions?(servers) servers.any? { |s| !description_for(s).empty? } end |
.description_for(server) ⇒ String
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.
Returns description or empty string.
97 98 99 100 |
# File 'lib/robot_lab/mcp/server_discovery.rb', line 97 def self.description_for(server) desc = server.is_a?(Hash) ? server[:description] : server.respond_to?(:description) && server.description desc.to_s.strip end |
.score(query, server) ⇒ Float
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.
Returns cosine similarity in [0.0, 1.0].
105 106 107 |
# File 'lib/robot_lab/mcp/server_discovery.rb', line 105 def self.score(query, server) TextAnalysis.tf_cosine_similarity(query, topic_text(server)) end |
.select(query, from:, threshold: DEFAULT_THRESHOLD) ⇒ Array<Hash, MCP::Server>
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.
Select MCP servers relevant to the given query.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/robot_lab/mcp/server_discovery.rb', line 63 def self.select(query, from:, threshold: DEFAULT_THRESHOLD) return from if from.empty? return from if query.to_s.strip.empty? return from unless any_descriptions?(from) TextAnalysis.require_classifier! scored = from.map { |server| [server, score(query, server)] } matches = scored.select { |_, s| s >= threshold }.map(&:first) matches.empty? ? from : matches rescue DependencyError # Classifier gem not available — connect to all servers from end |
.topic_text(server) ⇒ String
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.
Build the topic string used for similarity scoring: name + description.
90 91 92 93 |
# File 'lib/robot_lab/mcp/server_discovery.rb', line 90 def self.topic_text(server) name = server.is_a?(Hash) ? server[:name].to_s : server.name.to_s "#{name} #{description_for(server)}".strip end |