Class: Vehicles::McpServer
- Inherits:
-
Object
- Object
- Vehicles::McpServer
- Defined in:
- lib/vehicles/mcp_server.rb
Overview
A tiny, read-only MCP (Model Context Protocol) server over the bundled
dataset — so agents and LLM apps can ground themselves in real vehicle
data instead of hallucinating nameplates. Runs on stdio, speaks JSON-RPC
2.0, needs no network, no API key, no database: vehicles-mcp and done.
{ "mcpServers": { "vehicles": { "command": "vehicles-mcp" } } }
Design notes:
* stdlib-only, like the rest of the gem (JSON over $stdin/$stdout).
* Read-only by construction — there is no tool that mutates anything.
* Answers come from the same snapshot the gem serves your app, so an
agent and your UI can never disagree about what exists.
* Protocol: newline-delimited JSON-RPC per the MCP stdio transport.
We answer `initialize`, `ping`, `tools/list`, `tools/call`, and
ignore notifications — the minimum a well-behaved server needs.
Constant Summary collapse
- PROTOCOL_VERSION =
"2025-06-18"- TOOLS =
Tool definitions, MCP-shaped. Descriptions are written for the MODEL reading them (what to call, when, and what comes back), not for humans.
[ { name: "search_makes", description: "Search vehicle manufacturers (makes) by name. Returns matching makes " \ "with their slug and which vehicle kinds they produce (car, motorcycle, " \ "moped, van, truck, bus). Call with an empty query to list every make. " \ "Forgiving: 'vw', 'merc' and 'škoda' resolve.", inputSchema: { type: "object", properties: { query: { type: "string", description: "Make name or fragment (empty = all makes)" }, kind: { type: "string", enum: %w[car motorcycle moped van truck bus], description: "Only makes producing this kind" } } } }, { name: "search_models", description: "Search vehicle models by name across all makes (e.g. 'golf', 'corolla', " \ "'transit'). Returns ranked matches with make, kind, body type, global " \ "popularity decile (1 = top 10%) and the countries where the model is " \ "evidenced by official sources.", inputSchema: { type: "object", properties: { query: { type: "string", description: "Model name or fragment; 'make model' works too" }, limit: { type: "integer", description: "Max results (default 20)" } }, required: ["query"] } }, { name: "get_model", description: "Look up ONE exact vehicle model by make + model name and get its full " \ "record: canonical names, slugs, kind, body type, popularity decile, and " \ "availability countries. Use after search_models, or directly when you " \ "already know the pair (e.g. make: 'Toyota', model: 'Hilux').", inputSchema: { type: "object", properties: { make: { type: "string", description: "Make name, slug, or alias ('vw' works)" }, model: { type: "string", description: "Model name or slug" } }, required: %w[make model] } }, { name: "top_models", description: "The most popular vehicle models by registration data, optionally filtered " \ "by kind and/or country (ISO alpha-2, e.g. 'nl', 'gb', 'th'). Popularity is " \ "measured from official registration/fleet counts across 12+ countries, " \ "expressed as deciles (1 = most popular).", inputSchema: { type: "object", properties: { kind: { type: "string", enum: %w[car motorcycle moped van truck bus] }, country: { type: "string", description: "ISO-3166-1 alpha-2 country code" }, limit: { type: "integer", description: "Max results (default 20)" } } } } ].freeze
Instance Method Summary collapse
-
#initialize(input: $stdin, output: $stdout) ⇒ McpServer
constructor
A new instance of McpServer.
-
#run ⇒ Object
Blocking serve loop: one JSON-RPC message per line until EOF.
Constructor Details
#initialize(input: $stdin, output: $stdout) ⇒ McpServer
Returns a new instance of McpServer.
89 90 91 92 |
# File 'lib/vehicles/mcp_server.rb', line 89 def initialize(input: $stdin, output: $stdout) @input = input @output = output end |
Instance Method Details
#run ⇒ Object
Blocking serve loop: one JSON-RPC message per line until EOF. Malformed lines get a -32700 parse error instead of killing the process — an agent host restarting us mid-message must not take the server down.
97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/vehicles/mcp_server.rb', line 97 def run @input.each_line do |line| line = line.strip next if line.empty? begin msg = JSON.parse(line) rescue JSON::ParserError reply(id: nil, error: { code: -32_700, message: "Parse error" }) next end handle(msg) end end |