Class: RailsAiContext::Server
- Inherits:
-
Object
- Object
- RailsAiContext::Server
- Defined in:
- lib/rails_ai_context/server.rb
Overview
Configures and starts an MCP server using the official Ruby SDK. Registers all introspection tools and handles transport selection.
Instance Attribute Summary collapse
-
#app ⇒ Object
readonly
Returns the value of attribute app.
-
#transport_type ⇒ Object
readonly
Returns the value of attribute transport_type.
Class Method Summary collapse
-
.builtin_tools ⇒ Object
All built-in tools, auto-discovered from Tools::BaseTool subclasses.
-
.const_missing(name) ⇒ Object
Backwards-compatible constant - delegates to the registry.
-
.resolve_custom_tools(config = RailsAiContext.configuration) ⇒ Object
Resolve config.custom_tools into MCP::Tool classes.
Instance Method Summary collapse
-
#build ⇒ Object
Build and return the configured MCP::Server instance.
-
#initialize(app, transport: :stdio) ⇒ Server
constructor
A new instance of Server.
-
#start ⇒ Object
Start the MCP server with the configured transport.
Constructor Details
#initialize(app, transport: :stdio) ⇒ Server
Returns a new instance of Server.
32 33 34 35 |
# File 'lib/rails_ai_context/server.rb', line 32 def initialize(app, transport: :stdio) @app = app @transport_type = transport end |
Instance Attribute Details
#app ⇒ Object (readonly)
Returns the value of attribute app.
9 10 11 |
# File 'lib/rails_ai_context/server.rb', line 9 def app @app end |
#transport_type ⇒ Object (readonly)
Returns the value of attribute transport_type.
9 10 11 |
# File 'lib/rails_ai_context/server.rb', line 9 def transport_type @transport_type end |
Class Method Details
.builtin_tools ⇒ Object
All built-in tools, auto-discovered from Tools::BaseTool subclasses. Kept as a class method (not a constant) so auto-registration works. Legacy constant accessor preserved for backwards compatibility.
14 15 16 |
# File 'lib/rails_ai_context/server.rb', line 14 def self.builtin_tools Tools::BaseTool.registered_tools end |
.const_missing(name) ⇒ Object
Backwards-compatible constant - delegates to the registry. Existing code referencing Server::TOOLS continues to work. Emits a deprecation notice once to guide migration.
21 22 23 24 25 26 27 28 29 30 |
# File 'lib/rails_ai_context/server.rb', line 21 def self.const_missing(name) if name == :TOOLS unless @tools_deprecation_warned @tools_deprecation_warned = true $stderr.puts "[rails-ai-context] DEPRECATION: Server::TOOLS is deprecated, use Server.builtin_tools instead" if ENV["DEBUG"] end return builtin_tools end super end |
.resolve_custom_tools(config = RailsAiContext.configuration) ⇒ Object
Resolve config.custom_tools into MCP::Tool classes. Entries may be classes or class-name strings: classes in app/ (e.g. app/mcp_tools/) are not autoloadable while config/initializers run, so referencing the constant there aborts boot - a string name defers resolution to here, where autoloading is ready. Invalid entries are warn-skipped so one bad entry cannot take down every tool invocation.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/rails_ai_context/server.rb', line 43 def self.resolve_custom_tools(config = RailsAiContext.configuration) config.custom_tools.filter_map do |entry| tool = entry if tool.is_a?(String) # NameError messages never carry a leading "::", so normalize it # away up front or a missing "::Foo::Bar" entry could never be # classified as class-not-found below. tool = tool.delete_prefix("::") begin tool = Object.const_get(tool) rescue NameError => e # "class not found" only when the missing constant IS the entry # (or a leading namespace of it); a NameError raised from inside # the tool file's class body about some other constant means the # class exists but is broken - say so. The full constant path in # the message disambiguates where a bare #name cannot (a body # error about `Elastic::Search` must not read as entry # "Tools::Search" being absent). missing = e.[/\Auninitialized constant ([\w:]+)/, 1] entry_missing = missing && (tool == missing || tool.start_with?("#{missing}::")) if entry_missing $stderr.puts "[rails-ai-context] WARNING: Skipping custom_tool #{entry.inspect} (class not found)" else $stderr.puts "[rails-ai-context] WARNING: Skipping custom_tool #{entry.inspect} (#{e.class}: #{e..lines.first.to_s.strip})" end next nil rescue StandardError, ScriptError => e # A syntax error or raising class body in the autoloaded file must # cost that one entry, not the whole server/CLI. $stderr.puts "[rails-ai-context] WARNING: Skipping custom_tool #{entry.inspect} (#{e.class}: #{e..lines.first.to_s.strip})" next nil end end if tool.is_a?(Class) && tool < MCP::Tool tool else $stderr.puts "[rails-ai-context] WARNING: Skipping invalid custom_tool #{entry.inspect} (must be an MCP::Tool subclass or its class name)" nil end end end |
Instance Method Details
#build ⇒ Object
Build and return the configured MCP::Server instance
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/rails_ai_context/server.rb', line 87 def build config = RailsAiContext.configuration validated_custom_tools = self.class.resolve_custom_tools(config) mcp_config = MCP::Configuration.new( # Anything that still escapes a tool (schema validation bugs, SDK-level # failures) gets a stderr backtrace instead of vanishing into a bare # JSON-RPC internal error. Routine protocol-level errors (unknown # tool, invalid params) are expected traffic, not bugs - the mcp gem # already turns them into a proper JSON-RPC error response, so here # they get one quiet line instead of a scary 10-line backtrace. exception_reporter: lambda { |exception, _server_context| if exception.is_a?(MCP::Server::RequestHandlerError) && exception.error_type != :internal_error $stderr.puts "[rails-ai-context] request error (#{exception.error_type}): #{exception.}" else $stderr.puts "[rails-ai-context] unhandled exception: #{exception.class}: #{exception.}" Array(exception.backtrace).first(10).each { |line| $stderr.puts " #{line}" } end }, instrumentation_callback: Instrumentation.callback ) server = MCP::Server.new( name: config.server_name, version: config.server_version, instructions: "Ground truth engine for Rails apps. Live Prism AST introspection. Zero stale data.", tools: active_tools(config) + validated_custom_tools, resource_templates: Resources.resource_templates, configuration: mcp_config ) Resources.register(server) server end |
#start ⇒ Object
Start the MCP server with the configured transport
125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/rails_ai_context/server.rb', line 125 def start server = build case transport_type when :stdio start_stdio(server) when :http, :streamable_http start_http(server) else raise ConfigurationError, "Unknown transport: #{transport_type}. Use :stdio or :http" end end |