Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0c5e313
Add ruby_ui MCP server design spec
djalmaaraujo May 9, 2026
e7d25df
Add ruby_ui MCP server implementation plan
djalmaaraujo May 9, 2026
3bc9150
[Feature] Scaffold ruby_ui-mcp Rails engine gem
djalmaaraujo May 11, 2026
10d2e71
[Feature] MCP Registry data model + tests
djalmaaraujo May 11, 2026
fc5f333
[Feature] MCP RegistryBuilder reads gem source
djalmaaraujo May 11, 2026
9340f72
[Feature] MCP build CLI + initial registry.json
djalmaaraujo May 11, 2026
8804a1a
[Feature] MCP tools: list, search, view
djalmaaraujo May 11, 2026
cf5a85f
[Feature] MCP tools: examples, add_command, audit
djalmaaraujo May 11, 2026
b6210f3
[Feature] MCP Server wires 7 tools to ruby-sdk
djalmaaraujo May 11, 2026
e4c40bc
[Feature] MCP Rails engine + Rack endpoint
djalmaaraujo May 11, 2026
e57d479
[Feature] Mount ruby_ui-mcp engine in docs app at /mcp
djalmaaraujo May 11, 2026
3cb1671
[Documentation] Add MCP docs page with multi-client install
djalmaaraujo May 11, 2026
e7d0277
[CI] Add MCP test + registry drift jobs
djalmaaraujo May 11, 2026
ee8b1e2
[Documentation] Add MCP README
djalmaaraujo May 11, 2026
73091a2
[Bug Fix] Standardrb whitespace + deterministic registry build
djalmaaraujo May 11, 2026
ef57d6f
[Bug Fix] COPY mcp/ in docs Dockerfile for ruby_ui-mcp path dep
djalmaaraujo May 11, 2026
b097675
[Feature] Extract VisualCodeExample blocks + richer docs_markdown
djalmaaraujo May 11, 2026
b2df2cd
[Feature] MCP tool get_install_command_for_project
djalmaaraujo May 11, 2026
3eb0821
[Refactor] Drop dead code + centralize tool response helpers
djalmaaraujo May 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,49 @@ jobs:
- name: Run tests
run: bin/rails db:test:prepare test

mcp:
name: MCP (Ruby ${{ matrix.ruby }})
runs-on: ubuntu-latest
defaults:
run:
working-directory: mcp
strategy:
fail-fast: false
matrix:
ruby: ["3.3", "3.4"]
steps:
- uses: actions/checkout@v6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
rubygems: latest
working-directory: mcp
- name: Run tests
run: bundle exec rake test

mcp-registry-check:
name: MCP registry up to date
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true
working-directory: mcp
- name: Rebuild registry
working-directory: mcp
run: bundle exec exe/ruby-ui-mcp-build
- name: Fail on diff
run: |
if ! git diff --exit-code mcp/data/registry.json; then
echo "registry.json out of date — run 'cd mcp && bundle exec exe/ruby-ui-mcp-build' and commit"
exit 1
fi

docker-build:
name: Docker build (Devcontainer)
if: github.ref == 'refs/heads/main'
Expand Down
4 changes: 3 additions & 1 deletion docs/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz
npm install -g pnpm@$PNPM_VERSION && \
rm -rf /tmp/node-build-master

# Copy the gem first so docs/Gemfile's `path: "../gem"` resolves during bundle install.
# Copy the gem and mcp sources first so docs/Gemfile's path: "../gem" / "../mcp" resolve during bundle install.
COPY gem /gem
COPY mcp /mcp

# Install application gems (cwd = /rails)
COPY docs/Gemfile docs/Gemfile.lock ./
Expand Down Expand Up @@ -78,6 +79,7 @@ FROM base
# Copy built artifacts: gems, application, and the gem subtree
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /gem /gem
COPY --from=build /mcp /mcp
COPY --from=build /rails /rails

# Run and own only the runtime files as a non-root user for security
Expand Down
2 changes: 2 additions & 0 deletions docs/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,5 @@ gem "tailwind_merge", "~> 1.4.0"
gem "rss", "0.3.2"

gem "rouge", "~> 4.7"

gem "ruby_ui-mcp", path: "../mcp"
19 changes: 19 additions & 0 deletions docs/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ PATH
specs:
ruby_ui (1.2.0)

PATH
remote: ../mcp
specs:
ruby_ui-mcp (0.1.0)
mcp (>= 0.1)
rack-attack (>= 6.7)
rails (>= 8.0)
reverse_markdown (>= 2.1)

GEM
remote: https://rubygems.org/
specs:
Expand Down Expand Up @@ -138,6 +147,9 @@ GEM
jsbundling-rails (1.3.1)
railties (>= 6.0.0)
json (2.19.4)
json-schema (6.2.0)
addressable (~> 2.8)
bigdecimal (>= 3.1, < 5)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.7.0)
Expand All @@ -154,6 +166,8 @@ GEM
net-smtp
marcel (1.1.0)
matrix (0.4.2)
mcp (0.15.0)
json-schema (>= 4.1)
method_source (1.1.0)
mini_mime (1.1.5)
mini_portile2 (2.8.9)
Expand Down Expand Up @@ -200,6 +214,8 @@ GEM
nio4r (~> 2.0)
racc (1.8.1)
rack (3.2.6)
rack-attack (6.8.0)
rack (>= 1.0, < 4)
rack-session (2.1.2)
base64 (>= 0.1.0)
rack (>= 3.0.0)
Expand Down Expand Up @@ -246,6 +262,8 @@ GEM
regexp_parser (2.11.3)
reline (0.6.3)
io-console (~> 0.5)
reverse_markdown (3.0.2)
nokogiri
rexml (3.4.4)
rouge (4.7.0)
rss (0.3.2)
Expand Down Expand Up @@ -344,6 +362,7 @@ DEPENDENCIES
rouge (~> 4.7)
rss (= 0.3.2)
ruby_ui!
ruby_ui-mcp!
selenium-webdriver
sqlite3 (= 2.9.4)
standard
Expand Down
3 changes: 2 additions & 1 deletion docs/app/components/shared/menu.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ def getting_started_links
{name: "Dark mode", path: docs_dark_mode_path},
{name: "Theming", path: docs_theming_path},
{name: "Customizing components", path: docs_customizing_components_path},
{name: "Changelog", path: docs_changelog_path}
{name: "Changelog", path: docs_changelog_path},
{name: "MCP Server", path: docs_mcp_path}
]
end

Expand Down
4 changes: 4 additions & 0 deletions docs/app/controllers/docs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ class DocsController < ApplicationController
layout -> { Views::Layouts::DocsLayout }

# GETTING STARTED
def mcp
render Views::Docs::Mcp.new
end

def introduction
render Views::Docs::GettingStarted::Introduction.new
end
Expand Down
7 changes: 7 additions & 0 deletions docs/app/lib/site_files.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ class SiteFiles
description: "Recent RubyUI component and documentation changes.",
priority: 0.6,
changefreq: "weekly"
},
{
title: "MCP Server",
path: "/docs/mcp",
description: "Connect AI agents to Ruby UI components, source, examples, and install commands via the Model Context Protocol.",
priority: 0.8,
changefreq: "monthly"
}
].freeze

Expand Down
153 changes: 153 additions & 0 deletions docs/app/views/docs/mcp.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# frozen_string_literal: true

class Views::Docs::Mcp < Views::Base
def view_template
div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
render Docs::Header.new(
title: "MCP Server",
description: "Connect AI agents to Ruby UI components, source, examples, and install commands."
)

# About MCP
div(class: "space-y-4") do
Heading(level: 2) { "About MCP" }
p(class: "text-foreground/80 leading-relaxed") do
plain "MCP (Model Context Protocol) is an open standard for connecting AI assistants to external data sources and tools. "
plain "Ruby UI exposes an MCP server so your AI agent can list available components, view their source files, search the docs, and generate the exact install command for your app."
end
end

# Setup
div(class: "space-y-6") do
Heading(level: 2) { "Setup" }
p(class: "text-foreground/80") { "Add the Ruby UI MCP server to your editor or AI client using the snippets below." }

# Claude Code
div(class: "space-y-2") do
Heading(level: 3) { "Claude Code" }
Codeblock("claude mcp add --transport http ruby-ui https://rubyui.com/mcp", syntax: :shell, clipboard: true)
end

# Cursor
div(class: "space-y-2") do
Heading(level: 3) { "Cursor" }
p(class: "text-sm text-foreground/70") { "Add to .cursor/mcp.json:" }
Codeblock(cursor_config_json, syntax: :json, clipboard: true)
end

# Claude Desktop
div(class: "space-y-2") do
Heading(level: 3) { "Claude Desktop" }
p(class: "text-sm text-foreground/70") { "Add to claude_desktop_config.json:" }
Codeblock(generic_config_json, syntax: :json, clipboard: true)
end

# Windsurf
div(class: "space-y-2") do
Heading(level: 3) { "Windsurf" }
p(class: "text-sm text-foreground/70") { "Add to mcp_config.json:" }
Codeblock(generic_config_json, syntax: :json, clipboard: true)
end

# VS Code
div(class: "space-y-2") do
Heading(level: 3) { "VS Code" }
p(class: "text-sm text-foreground/70") { "Add to .vscode/mcp.json:" }
Codeblock(generic_config_json, syntax: :json, clipboard: true)
end

# Zed
div(class: "space-y-2") do
Heading(level: 3) { "Zed" }
p(class: "text-sm text-foreground/70") { "Add to settings.json:" }
Codeblock(zed_config_json, syntax: :json, clipboard: true)
end
end

# Usage
div(class: "space-y-4") do
Heading(level: 2) { "Usage" }
p(class: "text-foreground/80") { "Once connected, ask your agent questions like:" }
ul(class: "list-disc list-inside space-y-1 text-foreground/80") do
li { "Install Button and Dialog from Ruby UI." }
li { "Show me the source of the Card component." }
li { "Search Ruby UI for a date input." }
li { "Audit my Ruby UI install." }
end
end

# Tools
div(class: "space-y-4") do
Heading(level: 2) { "Tools" }
p(class: "text-foreground/80") { "The MCP server exposes the following tools:" }
div(class: "overflow-x-auto rounded-md border") do
table(class: "w-full text-sm") do
thead(class: "border-b bg-muted/50") do
tr do
th(class: "px-4 py-3 text-left font-medium") { "Tool" }
th(class: "px-4 py-3 text-left font-medium") { "Description" }
end
end
tbody do
tools_list.each_with_index do |(tool, description), i|
tr(class: i.even? ? "" : "bg-muted/30") do
td(class: "px-4 py-3 font-mono text-xs") { tool }
td(class: "px-4 py-3 text-foreground/80") { description }
end
end
end
end
end
end

# Troubleshooting
div(class: "space-y-4") do
Heading(level: 2) { "Troubleshooting" }
ul(class: "list-disc list-inside space-y-2 text-foreground/80") do
li { "Endpoint must be reachable; corporate proxies may block streamable HTTP." }
li { "If the agent can't find components, ask it to call get_project_registries first." }
li { "Run bundle exec rails g ruby_ui:component <Name> only inside a Rails app with ruby_ui in its Gemfile." }
end
end
end
end

private

def cursor_config_json
<<~JSON
{
"mcpServers": {
"ruby-ui": { "url": "https://rubyui.com/mcp" }
}
}
JSON
end

def generic_config_json
cursor_config_json
end

def zed_config_json
<<~JSON
{
"context_servers": {
"ruby-ui": { "source": "http", "url": "https://rubyui.com/mcp" }
}
}
JSON
end

def tools_list
[
["get_project_registries", "Lists available registries."],
["list_items_in_registries", "Returns all components with descriptions."],
["search_items_in_registries", "Fuzzy search by name, description, or docs."],
["view_items_in_registries", "Returns full source files and dependencies."],
["get_item_examples_from_registries", "Returns code examples per component."],
["get_add_command_for_items", "Returns a validated rails g ruby_ui:component … command."],
["get_audit_checklist", "Returns a post-install verification checklist."],
["get_install_command_for_project", "Returns commands to bootstrap ruby_ui in a fresh Rails project."]
]
end
end
11 changes: 11 additions & 0 deletions docs/config/initializers/rack_attack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require "rack/attack"

class Rack::Attack
throttle("mcp/ip", limit: 60, period: 60.seconds) do |req|
req.ip if req.path.start_with?("/mcp")
end
end

Rails.application.config.middleware.use Rack::Attack
3 changes: 3 additions & 0 deletions docs/config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
Rails.application.routes.draw do
mount RubyUI::MCP::Engine => "/mcp"

get "llms.txt", to: "site_files#llms", as: :llms_txt, format: false
get "llms-full.txt", to: "site_files#llms_full", as: :llms_full_txt, format: false
get "sitemap.xml", to: "site_files#sitemap", as: :sitemap_xml, format: false
Expand All @@ -7,6 +9,7 @@

scope "docs" do
# GETTING STARTED
get "mcp", to: "docs#mcp", as: :docs_mcp
get "introduction", to: "docs#introduction", as: :docs_introduction
get "installation", to: "docs#installation", as: :docs_installation
get "theming", to: "docs#theming", as: :docs_theming
Expand Down
5 changes: 5 additions & 0 deletions docs/public/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ This file expands the curated /llms.txt map into a compact reference that can be
- URL: https://rubyui.com/docs/changelog
- Summary: Recent RubyUI component and documentation changes.

### MCP Server

- URL: https://rubyui.com/docs/mcp
- Summary: Connect AI agents to Ruby UI components, source, examples, and install commands via the Model Context Protocol.


## Component catalog

Expand Down
1 change: 1 addition & 0 deletions docs/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Use the core docs first for installation, theming, dark mode, and customization
- [Customizing components](https://rubyui.com/docs/customizing_components): Adapt generated RubyUI components when theme-level customization is not enough.
- [Components](https://rubyui.com/docs/components): Catalog of available RubyUI components.
- [Changelog](https://rubyui.com/docs/changelog): Recent RubyUI component and documentation changes.
- [MCP Server](https://rubyui.com/docs/mcp): Connect AI agents to Ruby UI components, source, examples, and install commands via the Model Context Protocol.

## Component docs

Expand Down
5 changes: 5 additions & 0 deletions docs/public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://rubyui.com/docs/mcp</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://rubyui.com/docs/accordion</loc>
<changefreq>monthly</changefreq>
Expand Down
4 changes: 4 additions & 0 deletions mcp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/.bundle/
/Gemfile.lock
/pkg/
/tmp/
1 change: 1 addition & 0 deletions mcp/.standard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruby_version: 3.3
2 changes: 2 additions & 0 deletions mcp/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
source "https://rubygems.org"
gemspec
Loading