Project · MCP server and RAG in Ruby on Rails

shop_mcp_server

An MCP server in Ruby on Rails that lets Claude read a store's products, orders and inventory, place orders with approval, and search by meaning.

Ruby 4.0 · Rails 8.1 · official MCP Ruby SDK · PostgreSQL · pgvector · Voyage AI · RSpec

The problem

An AI assistant can only talk. It cannot check your database, place an order or know what is really in stock. Left alone, it guesses, and a guess that sounds right is worse than no answer.

MCP (Model Context Protocol) fixes that by giving the assistant a toolbox. Each tool is one clearly labeled capability, and the assistant reads the labels and decides on its own when to use one.

What I built

A Rails app that is an MCP server for a small store. Counted from the code:

Piece Count Examples
Tools 7 get_product_tool, create_order_tool, semantic_search_products_tool
Resources 2 shop://products/catalog, shop://products/low-stock
Prompts 1 inventory_check

How it works

Flow diagram of shop_mcp_server in six steps: 1. an MCP client such as Claude or MCP Inspector sends a request, 2. to the /mcp endpoint over Streamable HTTP with JSON-RPC, 3. the MCP server built on the official Ruby SDK routes it, 4. to a tool call (six read-only tools and one write tool that needs approval), 5. the tool reads PostgreSQL through ActiveRecord and pgvector, 6. the model answers from real store data.

A normal chat reply is Question → Answer. With MCP it becomes Question → pick a tool → real data → Answer:

  1. The client sends tools/list and reads every tool’s description.
  2. The model picks the tool that fits, for example check_inventory_tool.
  3. The client sends tools/call with arguments.
  4. Rails runs plain ActiveRecord code against PostgreSQL.
  5. The model writes its answer from the real numbers.

What I verified

What broke, and what I learned

Claude reported an authentication problem. It was not one. I first built the server on the community fast-mcp gem. Claude’s connector setup kept failing with an auth error. The real cause was the transport: fast-mcp only speaks the older SSE transport, Claude probes with Streamable HTTP, and the probe hit a GET-only route and got a 405. I rewrote every tool on the official mcp Ruby SDK, which supports Streamable HTTP natively.

The docs were wrong about argument keys. The SDK’s docs show prompt arguments with string keys. At runtime they arrive as symbols. I found it with a debug log line, and a spec now guards against someone “fixing” it back.

The lesson I took from both: docs and tutorials are not the source of truth, running code is.

Approach: key decisions

Status

A working learning project, not production software. Sessions are in memory (single process), there is no authentication, and semantic search has been checked by hand but has no specs yet. The full list of limitations is in the README.

Related services

Need something like this in your own product? See AI engineering and integration.

Other projects