Skip to content

Add embedded console MCP server#17

Merged
LeahArmstrong merged 4 commits intomainfrom
feat/embedded-console
Feb 28, 2026
Merged

Add embedded console MCP server#17
LeahArmstrong merged 4 commits intomainfrom
feat/embedded-console

Conversation

@LeahArmstrong
Copy link
Owner

Summary

  • EmbeddedExecutor: Drop-in replacement for ConnectionManager+Bridge that executes AR queries directly. Implements 9 Tier 1 tools (count, sample, find, pluck, aggregate, association_count, schema, recent, status) with real database queries. Unsupported Tier 2-4 tools return clean unsupported errors for incremental follow-up.
  • Three invocation paths: stdio via rake task, rails runner, and HTTP middleware — all zero-config.
  • Safety: All queries wrapped in rolled-back transactions (SafeContext), column validation (ModelValidator), aggregate function whitelist, DB-dialect-aware random/timeout.

Consumer setup (the payoff)

Stdio (Claude Code / Cursor):

{
  "mcpServers": {
    "codebase-console": {
      "command": "bundle",
      "args": ["exec", "rake", "codebase_index:console"],
      "cwd": "/path/to/rails/app"
    }
  }
}

Docker:

{
  "mcpServers": {
    "codebase-console": {
      "command": "docker",
      "args": ["exec", "-i", "my_container", "bundle", "exec", "rake", "codebase_index:console"]
    }
  }
}

HTTP (always-on, zero latency):

CodebaseIndex.configure { |c| c.console_mcp_enabled = true }

What's NOT changed

Existing bridge architecture (ConnectionManager, Bridge, exe/codebase-console-mcp, console.yml) stays intact for backward compatibility and Docker/SSH remote mode.

Test plan

  • 33 new specs (embedded_executor + server_embedded) — all pass
  • Full suite: 3151 examples, 0 failures
  • Rubocop: 340 files, 0 offenses
  • End-to-end in host Rails app: bundle exec rake codebase_index:console starts, responds to MCP initialize, tool calls return real data

🤖 Generated with Claude Code

Replace the two-process bridge architecture (MCP server + rails runner)
with an embedded executor that runs ActiveRecord queries directly. This
eliminates the need for a YAML config file, bridge process management,
and the JSON-lines protocol — one command, no config, real results.

EmbeddedExecutor implements 9 Tier 1 tools (count, sample, find, pluck,
aggregate, association_count, schema, recent, status) with real AR
queries. Unsupported Tier 2-4 tools return clean error responses for
incremental follow-up. All queries are wrapped in SafeContext (rolled-
back transactions + statement timeout) with ModelValidator column checks
and an aggregate function whitelist.

Three invocation paths:
- stdio: `bundle exec rake codebase_index:console`
- rails runner: `bundle exec rails runner exe/codebase-console`
- HTTP middleware: mount via `console_mcp_enabled` config flag
Leah Armstrong added 2 commits February 27, 2026 18:46
Two bugs found during live validation against compose-dev admin (mcp 0.7.1):

- MCP::Tool::Response API changed from `is_error:` (0.6.0) to `error:` (0.7.1).
  Updated both error response paths in send_to_bridge.

- Tier1 tool builders return symbol-keyed hashes. The bridge path stringifies
  via JSON round-trip, but the embedded executor expected string keys. Added
  deep_stringify_keys to replicate the JSON serialization behavior.
@LeahArmstrong
Copy link
Owner Author

Code review

Found 2 issues:

  1. exe/codebase-console is not listed in spec.executables in the gemspec. All other executables in exe/ are declared — codebase-index-mcp, codebase-index-mcp-start, codebase-console-mcp, codebase-index-mcp-http — but codebase-console is missing. This means bundle exec codebase-console won't work after gem install, and it breaks the established pattern for every prior executable in this repo. The primary invocation paths (rake task, rails runner) still work since they use load with a resolved path, but the omission is inconsistent.

spec.bindir = 'exe'
spec.executables = %w[codebase-index-mcp codebase-index-mcp-start codebase-console-mcp codebase-index-mcp-http]
spec.require_paths = ['lib']

  1. Multiple YARD documentation gaps (CLAUDE.md says "YARD documentation on every public method and class"):

    • Server.build_embedded is missing @param connection — the method accepts 4 keyword arguments but only 3 are documented.

    # Build a configured MCP::Server using embedded ActiveRecord execution.
    #
    # No bridge process needed — queries run directly via ActiveRecord.
    # Pass the returned server to StdioTransport or StreamableHTTPTransport.
    #
    # @param model_validator [ModelValidator] Validates model/column names
    # @param safe_context [SafeContext] Wraps queries in rolled-back transactions
    # @param redacted_columns [Array<String>] Column names to redact from output
    # @return [MCP::Server] Configured server ready for transport
    def build_embedded(model_validator:, safe_context:, redacted_columns: [], connection: nil)
    require_relative 'embedded_executor'

    • register_tier1_tools through register_tier4_tools document @param conn_mgr [ConnectionManager] but now also accept EmbeddedExecutor. The private build_server method was correctly updated to [ConnectionManager, EmbeddedExecutor] but the four public methods were not.

    # @param server [MCP::Server] The MCP server instance
    # @param conn_mgr [ConnectionManager] Bridge connection
    # @param safe_ctx [SafeContext, nil] Optional context for column redaction
    # @return [void]
    def register_tier1_tools(server, conn_mgr, safe_ctx = nil, renderer: nil)
    TIER1_TOOLS.each { |tool| send(:"define_#{tool}", server, conn_mgr, safe_ctx, renderer: renderer) }
    end

    • random_function documents @return [Arel::Nodes::NamedFunction] but Arel.sql() returns Arel::Nodes::SqlLiteral. This is a private method so YARD is not required, but the annotation that exists is factually incorrect.

    #
    # @return [Arel::Nodes::NamedFunction]
    def random_function
    adapter = active_connection.adapter_name.downcase
    func = adapter.include?('mysql') ? 'RAND' : 'RANDOM'
    Arel.sql("#{func}()")
    end

Generated with Claude Code

If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Add codebase-console to spec.executables (was missing, breaking the
  pattern set by all other exe/ entries)
- Add @param connection to Server.build_embedded YARD
- Update register_tier*_tools @param conn_mgr type to include
  EmbeddedExecutor alongside ConnectionManager
- Fix random_function @return from NamedFunction to SqlLiteral
@LeahArmstrong LeahArmstrong merged commit 18fb1fa into main Feb 28, 2026
5 checks passed
@LeahArmstrong LeahArmstrong deleted the feat/embedded-console branch February 28, 2026 00:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant