SQL Primitives
MCP Servers
Register MCP servers, discover tools, call them from SQL, and use them inside Cascades.MCP brings external tool ecosystems into RVBBIT without adding a separate app API. The registry, discovery cache, calls, health checks, and audit trail all live behind SQL.
The practical result is that an MCP tool can be called directly, treated as a row source, or embedded as a step inside a Cascade.
How It Runs#
RVBBIT talks to an MCP gateway runtime. The gateway manages stdio or HTTP MCP servers and shares them across Postgres backends. Postgres does not fork tool subprocesses for every query.
The SQL catalog is still the source of truth:
| Surface | Purpose |
|---|---|
rvbbit.mcp_servers |
Registered MCP servers. |
rvbbit.mcp_gateways |
Ready gateway runtimes and endpoints. |
rvbbit.mcp_tools |
Discovered tools and input schemas. |
rvbbit.mcp_resources |
Discovered read-only resources. |
rvbbit.mcp_invocations |
Per-call audit log. |
rvbbit.mcp_usage |
Per-server/tool latency and error rollups. |
rvbbit.mcp_health |
Passive server status snapshot. |
Register And Discover#
Register a server once, then refresh it to discover tools and resources:
SELECT rvbbit.register_mcp_server(
server_name => 'github',
server_transport => 'stdio',
server_command => 'npx',
server_args => ARRAY['-y', '@modelcontextprotocol/server-github'],
server_env => '{"GITHUB_PERSONAL_ACCESS_TOKEN":"${GITHUB_TOKEN}"}'::jsonb,
server_timeout_ms => 60000,
server_description => 'GitHub repos/issues/PRs via MCP'
);
SELECT rvbbit.refresh_mcp_server('github');
Secrets are referenced with ${VAR} placeholders. The value is
resolved from the gateway runtime environment and is not persisted in the SQL
catalog.
Call A Tool#
Use mcp_call when you want the full MCP response envelope:
SELECT rvbbit.mcp_call(
'github',
'search_repositories',
'{"query":"postgres extension datafusion","perPage":3}'::jsonb
);
Use mcp_rows when the response is naturally a list and should compose with
SQL:
SELECT r->>'full_name' AS repo,
r->>'language' AS language,
(r->>'stargazers_count')::int AS stars
FROM rvbbit.mcp_rows(
'github',
'search_repositories',
'{"query":"vector database","perPage":10}'::jsonb
) r
WHERE r->>'language' = 'Rust'
ORDER BY stars DESC;
Join Tool Output With Local Data#
Because mcp_rows is a set-returning SQL function, tool results can join with
ordinary tables:
SELECT a.account_id,
a.company_name,
repo->>'full_name' AS matching_repo,
(repo->>'stargazers_count')::int AS stars
FROM accounts a
JOIN LATERAL rvbbit.mcp_rows(
'github',
'search_repositories',
jsonb_build_object(
'query', a.company_name || ' postgres',
'perPage', 3
)
) repo ON true
WHERE a.tier = 'strategic'
ORDER BY stars DESC;
This is the key design point: external context and local relational data can be planned, filtered, and inspected in one SQL surface.
Use MCP Inside Cascades#
MCP is also a Cascade step kind alongside llm, specialist, code, and
sql:
SELECT rvbbit.create_operator(
op_name => 'summarize_repo',
op_arg_names => ARRAY['q'],
op_return_type => 'text',
op_steps => $j$[
{
"name": "fetch",
"kind": "mcp",
"server": "github",
"tool": "search_repositories",
"inputs": {"query": "{{ inputs.q }}", "perPage": 1}
},
{
"name": "summarize",
"kind": "llm",
"model": "openai/gpt-5.4-mini",
"system": "Summarize in one sentence.",
"user": "Repo: {{ steps.fetch.output.items.0.full_name }}"
}
]$j$::jsonb
);
Tool output becomes addressable by later steps. If the tool returns JSON, RVBBIT
parses it when possible so downstream nodes can reference paths such as
{{ steps.fetch.output.items.0.full_name }}.
Ship It As A Capability#
A registered server works on one database. To make it portable — browsable, installable on any database, even baked into the extension — publish it into the same capability catalog that holds model and runtime capabilities:
-- Scan a working server into a kind='mcp' catalog entry (id: mcp/<server>).
SELECT rvbbit.publish_mcp_capability(
'firecrawl',
'Firecrawl (MCP)',
ARRAY['mcp','web','scrape'],
true
);
The entry now appears in the Capabilities window in Data Rabbit. Its manifest
records the connection, the tool and resource surface, one operator definition
per tool, and the declared secrets — the ${VAR} names an installer must
supply. No key values are stored.
Installing an entry supplies those keys and generates the operators:
SELECT rvbbit.install_mcp_register('mcp/firecrawl'); -- register from the manifest
-- the UI pushes your key to the gateway's encrypted store here (never Postgres)
SELECT rvbbit.install_mcp_finalize('mcp/firecrawl'); -- refresh + generate operators
The Capabilities window renders the declared secrets as key inputs — you install an MCP server by pasting its API key, exactly like installing a model.
Tools Become Operators#
install_mcp_finalize (or rvbbit.generate_mcp_operators('<server>')) creates
one scalar SQL operator per tool, named <server>_<tool>, with typed arguments
lifted from the tool's input schema:
SELECT rvbbit.iplocate_lookup_ip_address_location(ip => '1.1.1.1'); -- Sydney, AU
These compose with the rest of RVBBIT — pair a web search with classify, or a
scrape with embed for retrieval. High-arity tools (some have 20+ optional
arguments) are easiest to call via mcp_call(...); low-arity tools call cleanly
by name.
The Secret Model#
Keys never touch Postgres. rvbbit.mcp_servers.env and the published manifest
store only ${VAR} references; the gateway resolves them at spawn time from a
per-server encrypted store (Fernet) or its own environment. Install-time keys go
straight from the UI to the gateway's store. A published or shipped catalog entry
is therefore commit-safe — it carries no credential material.
Servers That Ship By Default#
github, firecrawl, time, iplocate, tavily, exa, apollo, ipgeo,
maps (Google Maps), brave, openweather, and linear are baked into the
catalog seed and browsable out of the box. time and iplocate are keyless;
the rest declare a single API key you provide at install. These are seeded
catalog entries (mcp/<server>), not file-backed packs — they live only in the
capability catalog seed.
Observability#
These catalog views expose gateway state — discovery, health, usage — without a direct gateway connection:
SELECT name, endpoint_url, status, health, updated_at
FROM rvbbit.mcp_gateways
ORDER BY (status = 'ready') DESC, updated_at DESC
LIMIT 1;
SELECT *
FROM rvbbit.mcp_health
ORDER BY name;
SELECT server, tool, n_calls, n_errors, avg_latency_ms, p95_latency_ms
FROM rvbbit.mcp_usage
ORDER BY n_calls DESC;
Individual calls are auditable:
SELECT server, tool, latency_ms, cache_hit, error, invocation_at
FROM rvbbit.mcp_invocations
ORDER BY invocation_at DESC
LIMIT 50;
Transport-level failures raise SQL errors and may roll back the audit insert.
Tool-level errors return an MCP envelope with isError=true and are recorded in
rvbbit.mcp_invocations.
Useful Extras#
For idempotent tools, cache results by tool:
SELECT rvbbit.set_mcp_tool_caching('github', 'search_repositories', 300);
SELECT rvbbit.purge_mcp_cache('github', 'search_repositories');
For nicer SQL ergonomics, generate typed wrapper functions in a schema named after the server:
SELECT rvbbit.generate_mcp_wrappers('github');
SELECT r->>'full_name'
FROM github.search_repositories(query => 'rust', perpage => 5) r;
Wrappers still return SETOF jsonb, so they remain easy to join and filter.