Class: Homebrew::McpServer Private
Overview
This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.
Provides a Model Context Protocol (MCP) server for Homebrew. See https://modelcontextprotocol.io/introduction for more information.
https://modelcontextprotocol.io/docs/tools/inspector is useful for testing.
Constant Summary collapse
- HOMEBREW_BREW_FILE =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
T.let(ENV.fetch("HOMEBREW_BREW_FILE").freeze, String)
- HOMEBREW_VERSION =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
T.let(ENV.fetch("HOMEBREW_VERSION").freeze, String)
- JSON_RPC_VERSION =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
T.let("2.0", String)
- MCP_PROTOCOL_VERSION =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
T.let("2025-03-26", String)
- ERROR_CODE =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
T.let(-32601, Integer)
- SERVER_INFO =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
T.let({ name: "brew-mcp-server", version: HOMEBREW_VERSION, }.freeze, T::Hash[Symbol, String])
- FORMULA_OR_CASK_PROPERTIES =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
T.let({ formula_or_cask: { type: "string", description: "Formula or cask name", }, }.freeze, T::Hash[Symbol, T.anything])
- TOOLS =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
Note:Cursor (as of June 2025) will only query/use a maximum of 40 tools.
T.let({ search: { name: "search", description: "Perform a substring search of cask tokens and formula names for <text>. " \ "If <text> is flanked by slashes, it is interpreted as a regular expression.", command: "brew search", inputSchema: { type: "object", properties: { text_or_regex: { type: "string", description: "Text or regex to search for", }, }, }, required: ["text_or_regex"], }, info: { name: "info", description: "Display brief statistics for your Homebrew installation. " \ "If a <formula> or <cask> is provided, show summary of information about it.", command: "brew info", inputSchema: { type: "object", properties: FORMULA_OR_CASK_PROPERTIES }, }, install: { name: "install", description: "Install a <formula> or <cask>.", command: "brew install", inputSchema: { type: "object", properties: FORMULA_OR_CASK_PROPERTIES }, required: ["formula_or_cask"], }, update: { name: "update", description: "Fetch the newest version of Homebrew and all formulae from GitHub using `git` and " \ "perform any necessary migrations.", command: "brew update", inputSchema: { type: "object", properties: {} }, }, upgrade: { name: "upgrade", description: "Upgrade outdated casks and outdated, unpinned formulae using the same options they were " \ "originally installed with, plus any appended brew formula options. If <cask> or <formula> " \ "are specified, upgrade only the given <cask> or <formula> kegs (unless they are pinned).", command: "brew upgrade", inputSchema: { type: "object", properties: FORMULA_OR_CASK_PROPERTIES }, }, uninstall: { name: "uninstall", description: "Uninstall a <formula> or <cask>.", command: "brew uninstall", inputSchema: { type: "object", properties: FORMULA_OR_CASK_PROPERTIES }, required: ["formula_or_cask"], }, list: { name: "list", description: "List all installed formulae and casks. " \ "If <formula> is provided, summarise the paths within its current keg. " \ "If <cask> is provided, list its artifacts.", command: "brew list", inputSchema: { type: "object", properties: FORMULA_OR_CASK_PROPERTIES }, }, config: { name: "config", description: "Show Homebrew and system configuration info useful for debugging. " \ "If you file a bug report, you will be required to provide this information.", command: "brew config", inputSchema: { type: "object", properties: {} }, }, doctor: { name: "doctor", description: "Check your system for potential problems. Will exit with a non-zero status " \ "if any potential problems are found. " \ "Please note that these warnings are just used to help the Homebrew maintainers " \ "with debugging if you file an issue. If everything you use Homebrew for " \ "is working fine: please don't worry or file an issue; just ignore this.", command: "brew doctor", inputSchema: { type: "object", properties: {} }, }, commands: { name: "commands", description: "Show lists of built-in and external commands.", command: "brew commands", inputSchema: { type: "object", properties: {} }, }, help: { name: "help", description: "Outputs the usage instructions for `brew` <command>.", command: "brew help", inputSchema: { type: "object", properties: { command: { type: "string", description: "Command to get help for", }, }, }, }, }.freeze, T::Hash[Symbol, T::Hash[Symbol, T.anything]])
Instance Method Summary collapse
- #debug(text) ⇒ void private
- #debug_logging? ⇒ Boolean private
- #handle_request(request) ⇒ Hash{Symbol => T.anything}? private
- #initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr) ⇒ void constructor private
- #log(text) ⇒ void private
- #ping_switch? ⇒ Boolean private
- #respond_error(id, message) ⇒ Hash{Symbol => T.anything} private
- #respond_result(id = nil, result = {}) ⇒ Hash{Symbol => T.anything}? private
- #run ⇒ void private
Constructor Details
#initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr) ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
136 137 138 139 140 141 142 |
# File 'mcp_server.rb', line 136 def initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr) @debug_logging = T.let(ARGV.include?("--debug") || ARGV.include?("-d"), T::Boolean) @ping_switch = T.let(ARGV.include?("--ping"), T::Boolean) @stdin = T.let(stdin, T.any(IO, StringIO)) @stdout = T.let(stdout, T.any(IO, StringIO)) @stderr = T.let(stderr, T.any(IO, StringIO)) end |
Instance Method Details
#debug(text) ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
This method returns an undefined value.
186 187 188 189 190 |
# File 'mcp_server.rb', line 186 def debug(text) return unless debug_logging? log(text) end |
#debug_logging? ⇒ Boolean
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
145 |
# File 'mcp_server.rb', line 145 def debug_logging? = @debug_logging |
#handle_request(request) ⇒ Hash{Symbol => T.anything}?
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'mcp_server.rb', line 199 def handle_request(request) id = request["id"] return if id.nil? case request["method"] when "initialize" respond_result(id, { protocolVersion: MCP_PROTOCOL_VERSION, capabilities: { tools: { listChanged: false }, prompts: {}, resources: {}, logging: {}, roots: {}, }, serverInfo: SERVER_INFO, }) when "resources/list" respond_result(id, { resources: [] }) when "resources/templates/list" respond_result(id, { resourceTemplates: [] }) when "prompts/list" respond_result(id, { prompts: [] }) when "ping" respond_result(id) when "get_server_info" respond_result(id, SERVER_INFO) when "logging/setLevel" @debug_logging = request["params"]["level"] == "debug" respond_result(id) when "notifications/initialized", "notifications/cancelled" respond_result when "tools/list" respond_result(id, { tools: TOOLS.values }) when "tools/call" if (tool = TOOLS.fetch(request["params"]["name"].to_sym, nil)) require "shellwords" arguments = request["params"]["arguments"] argument = arguments.fetch("formula_or_cask", "") argument = arguments.fetch("text_or_regex", "") if argument.strip.empty? argument = arguments.fetch("command", "") if argument.strip.empty? argument = nil if argument.strip.empty? brew_command = T.cast(tool.fetch(:command), String) .delete_prefix("brew ") full_command = [HOMEBREW_BREW_FILE, brew_command, argument].compact .map { |arg| Shellwords.escape(arg) } .join(" ") output = `#{full_command} 2>&1`.strip respond_result(id, { content: [{ type: "text", text: output }] }) else respond_error(id, "Unknown tool") end else respond_error(id, "Method not found") end end |
#log(text) ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
This method returns an undefined value.
193 194 195 196 |
# File 'mcp_server.rb', line 193 def log(text) @stderr.puts(text) @stderr.flush end |
#ping_switch? ⇒ Boolean
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
148 |
# File 'mcp_server.rb', line 148 def ping_switch? = @ping_switch |
#respond_error(id, message) ⇒ Hash{Symbol => T.anything}
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
268 269 270 |
# File 'mcp_server.rb', line 268 def respond_error(id, ) { jsonrpc: JSON_RPC_VERSION, id:, error: { code: ERROR_CODE, message: } } end |
#respond_result(id = nil, result = {}) ⇒ Hash{Symbol => T.anything}?
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
261 262 263 264 265 |
# File 'mcp_server.rb', line 261 def respond_result(id = nil, result = {}) return if id.nil? { jsonrpc: JSON_RPC_VERSION, id:, result: } end |
#run ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
This method returns an undefined value.
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'mcp_server.rb', line 151 def run @stderr.puts "==> Started Homebrew MCP server..." loop do input = if ping_switch? { jsonrpc: JSON_RPC_VERSION, id: 1, method: "ping" }.to_json else @stdin.gets end next if input.nil? || input.strip.empty? request = JSON.parse(input) debug("Request: #{JSON.pretty_generate(request)}") response = handle_request(request) if response.nil? debug("Response: nil") next end debug("Response: #{JSON.pretty_generate(response)}") output = JSON.dump(response).strip @stdout.puts(output) @stdout.flush break if ping_switch? end rescue Interrupt exit 0 rescue => e log("Error: #{e.}") exit 1 end |