Class: Rigor::MCP::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/mcp/server.rb

Overview

JSON-RPC 2.0 dispatcher for the MCP server.

Each public ‘handle` call takes a parsed request hash and returns a response hash (or nil for notifications that require no reply). Tool implementations delegate to `CLI.new(argv, out:, err:).run` with StringIO capture — every tool stays in sync with its CLI counterpart automatically (ADR-33 WD4).

Constant Summary collapse

PROTOCOL_VERSION =

rubocop:disable Metrics/ClassLength

"2024-11-05"
TOOLS =
[
  {
    name: "rigor_check",
    description: "Analyze Ruby files for type errors, undefined methods, arity mismatches, " \
                 "and nil-receiver risks. Returns a JSON diagnostic report.",
    inputSchema: {
      type: "object",
      properties: {
        paths: {
          type: "array",
          items: { type: "string" },
          description: "Files or directories to analyze (required)"
        },
        config: { type: "string", description: "Path to .rigor.yml (optional)" }
      },
      required: ["paths"]
    }
  },
  {
    name: "rigor_type_of",
    description: "Get the inferred type of the expression at a specific location in a Ruby file.",
    inputSchema: {
      type: "object",
      properties: {
        file: { type: "string",  description: "Path to the Ruby file" },
        line: { type: "integer", description: "1-based line number" },
        col: { type: "integer", description: "1-based column number" },
        config: { type: "string", description: "Path to .rigor.yml (optional)" }
      },
      required: %w[file line col]
    }
  },
  {
    name: "rigor_triage",
    description: "Summarize a project's diagnostics: rule distribution, per-file hotspots, " \
                 "and heuristic hints for the most common error clusters. Returns JSON. " \
                 "Useful for understanding the shape of a diagnostic set before deciding what to fix.",
    inputSchema: {
      type: "object",
      properties: {
        paths: {
          type: "array",
          items: { type: "string" },
          description: "Files or directories to analyze (defaults to configured paths)"
        },
        top: { type: "integer", description: "Number of hotspot files to include (default: 10)" },
        config: { type: "string", description: "Path to .rigor.yml (optional)" }
      }
    }
  },
  {
    name: "rigor_annotate",
    description: "Return the given Ruby source file with each line's last-expression type " \
                 "appended as a comment. Useful for understanding how Rigor infers types " \
                 "through a file.",
    inputSchema: {
      type: "object",
      properties: {
        file: { type: "string", description: "Path to the Ruby file to annotate" },
        config: { type: "string", description: "Path to .rigor.yml (optional)" }
      },
      required: ["file"]
    }
  },
  {
    name: "rigor_sig_gen",
    description: "Generate RBS skeleton signatures inferred from Ruby source files. " \
                 "Returns a JSON report of candidates with their classifications " \
                 "(new-file, new-method, tighter-return, equivalent, skipped).",
    inputSchema: {
      type: "object",
      properties: {
        paths: {
          type: "array",
          items: { type: "string" },
          description: "Files or directories to generate signatures for (defaults to configured paths)"
        },
        params: {
          type: "string",
          enum: %w[untyped observed],
          description: "Parameter policy: untyped (default) or observed " \
                       "(harvests call-site argument types from spec/)"
        },
        config: { type: "string", description: "Path to .rigor.yml (optional)" }
      }
    }
  },
  {
    name: "rigor_explain",
    description: "Look up the description of one or all Rigor diagnostic rules. " \
                 "Returns JSON. Without a rule argument, returns the full catalog.",
    inputSchema: {
      type: "object",
      properties: {
        rule: {
          type: "string",
          description: "Rule ID, legacy alias, or family prefix (call, flow, assert, dump, def). " \
                       "Omit to list every rule."
        }
      }
    }
  },
  {
    name: "rigor_coverage",
    description: "Report type-precision coverage: the ratio of expressions Rigor types as " \
                 "Constant / Nominal / shaped / refined (precise) vs Dynamic or top (opaque). " \
                 "Returns JSON. Useful for measuring the impact of adding new fold rules.",
    inputSchema: {
      type: "object",
      properties: {
        paths: {
          type: "array",
          items: { type: "string" },
          description: "Files or directories to scan (required)"
        },
        config: { type: "string", description: "Path to .rigor.yml (optional)" }
      },
      required: ["paths"]
    }
  }
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(config_path: nil, err: $stderr) ⇒ Server

Returns a new instance of Server.



140
141
142
143
# File 'lib/rigor/mcp/server.rb', line 140

def initialize(config_path: nil, err: $stderr)
  @config_path = config_path
  @err = err
end

Instance Method Details

#handle(request) ⇒ Object

Dispatches a parsed JSON-RPC request hash. Returns nil for notifications (requests without an ‘id`).



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/rigor/mcp/server.rb', line 147

def handle(request)
  id = request["id"]
  method_name = request["method"]

  # Notifications carry no `id` and require no response.
  return nil if id.nil?

  case method_name
  when "initialize"  then handle_initialize(id)
  when "ping"        then success(id, {})
  when "tools/list"  then success(id, { tools: TOOLS })
  when "tools/call"
    call_tool(id,
              request.dig("params", "name"),
              request.dig("params", "arguments") || {})
  else
    error(id, -32_601, "Method not found: #{method_name.inspect}")
  end
end