The LakehouseBox MCP server
https://mcp.lakehousebox.com/mcp: there is nothing to install. lhbox mcp install (CLI reference) adds it to Claude Code without the key in its configuration: the host asks the CLI for the saved key when it connects.The MCP server at https://mcp.lakehousebox.com/mcp gives an MCP host your data: Claude.ai, Claude Desktop, Claude Code, Cursor, anything that speaks the protocol. You find tables, see their columns and freshness, run read-only SQL on our side and read files such as photos. With write approved, you change data with SQL: save a result as a table, add, correct or remove rows.
It is for people and agents in a chat, an agent whose sandbox cannot reach LakehouseBox, and a quick look from anywhere. Administration (catalogs, grants, tokens, members, the organisation) lives in the CLI (lhbox), the API and the account page, not here.
The short path
claude mcp add --transport http lakehousebox https://mcp.lakehousebox.com/mcp # Claude Code; approve in the browser
lhbox mcp install # or: the same, with the CLI's key
The person: in Claude.ai or Claude Desktop, adds https://mcp.lakehousebox.com/mcp as a custom connector, then approves it on LakehouseBox. Ticks write if you should change data.
It is a hosted server (Streamable HTTP; OAuth 2.1 or an agent key): nothing to install. Start with list_tables, then describe_table, then query.
Connect
As a plugin, with skills
The LakehouseBox plugin installs this server together with skills that teach the agent the service. It works in Claude Code, Codex and Gemini CLI: see Agent skills.
From the Claude directory
LakehouseBox is a community connector at claude.ai/directory/lakehousebox.
The person: connects it there and approves it on LakehouseBox.
In ChatGPT
The person: opens Settings → Plugins → Add → Add custom MCP server. They give a name, https://mcp.lakehousebox.com/mcp and authentication OAuth. Then they connect it and approve it on LakehouseBox.
A person at a browser: OAuth
Add https://mcp.lakehousebox.com/mcp as a custom connector in Claude.ai or Claude Desktop. In Claude Code, run claude mcp add --transport http lakehousebox https://mcp.lakehousebox.com/mcp.
The host finds the authorization server from the server's 401. The protected-resource metadata is at /.well-known/oauth-protected-resource/mcp. The host registers itself with Client ID Metadata Documents, or Dynamic Client Registration as a fallback. It then sends the person to LakehouseBox.
The person: approves the connection and picks its access:
- All catalogs (the default), at read or at read and write. It includes catalogs created later, so a new catalog needs no new approval. It never gives more on a catalog than the person holds there when it is used. A catalog they lose access to leaves the connection at once.
- Only select catalogs, each at read or write. A catalog created later is not in the list: the connection answers
connection_not_grantedfor it until the person gives it the catalog. They can grant it under the catalog's Access tab (Grant access, then the connection's token), or reconnect and choose All catalogs.
The approval creates a token under Settings › Access keys on the account page. The server needs the scope catalogs:read. The four tools that change things (query_rw, document_table, set_agents_md, keep_account) also need catalogs:write. When the client asks for write, the consent page has it ticked. A connection approved read-only answers insufficient_scope to query_rw until it is reconnected with write.
With no account yet
A person who is not signed in can let the client set an account up on the consent page. The button names the client, e.g. Let Claude set it up. The click accepts the Terms and the Acceptable Use Policy (Terms, clause 2.5).
Until the person keeps it, the client's catalog:
- is its only one, and holds at most 1 GB;
- cannot make anything public;
- is deleted with its data after 72 hours.
To keep it, call keep_account. It returns a link and a code.
The person: opens the link, signs up or logs in, and types the code. The connection then stops working: they reconnect once, signed in, to the same data.
A scheduled or headless agent: a key
Send an agent key as a bearer token. Keep it off the command line: anyone on the machine can read a running command line. Put it in the environment Claude Code starts with, and let Claude Code fill it in when it connects. The single quotes stop your shell from expanding it:
export LHBOX_API_KEY=al_live_... # in the environment Claude Code is started from
claude mcp add --transport http lakehousebox https://mcp.lakehousebox.com/mcp \
--header 'Authorization: Bearer ${LHBOX_API_KEY}'
The server checks the key with the API and forwards it unchanged. The key's grants decide what it can read. Create a key on the account page (Tokens), or run lhbox token create --name reporter --grant <catalog>:read.
With the CLI
lhbox mcp install adds the server to Claude Code with no key in its configuration. The entry's headersHelper is lhbox --url … --profile … mcp headers. Claude Code runs it when it connects, and again after a 401. It answers with the key the CLI holds: LHBOX_API_KEY in Claude Code's environment, else the login lhbox login saved.
So the key is never on a command line or in Claude Code's files, and a new login needs no reinstall. For an agent's key, export LHBOX_API_KEY before installing and where Claude Code runs. A key given only with --api-key is refused: the helper could not find it later.
lhbox mcp install --client claude-desktop prints the custom-connector steps above and writes nothing. Claude Desktop's configuration file takes only local servers, so the hosted server is added in the app.
Developers only: a local server
The same code runs as a local stdio process, for working on the server against a local stack. From a checkout of this repository, run lhbox mcp install --transport stdio.
- For Claude Code it writes an entry that runs
lhbox mcp run --from platform/mcp. That startsuvx --from platform/mcpwithLHBOX_API_KEYin the server's environment. - For Claude Desktop it writes a
uvx --from platform/mcpentry with the key in its environment. It goes in Claude Desktop's own configuration file, readable only by its owner.
The package is not published on PyPI. The hosted server is the supported route.
MCP tools, a page of its own: Each tool of the LakehouseBox MCP server, public catalogs, a catalog's AGENTS.md, how the queries run, where to compute, and the limits of the shared queue.
When it fails
A failed call is an isError result. Its content is the API's error envelope, unchanged, plus the HTTP status:
{"error": {"code": "read_only", "message": "Only read statements run here; this is DROP.",
"statement_type": "DROP", "remedy": "Use SELECT, WITH … SELECT, DESCRIBE, SUMMARIZE or EXPLAIN.",
"request_id": "…"},
"http_status": 400}
The code says what to change and the remedy how. Act on the remedy rather than retrying unchanged.
| code | what to do |
|---|---|
read_only |
query runs reads only: SELECT, WITH … SELECT, DESCRIBE, SUMMARIZE or EXPLAIN |
insufficient_scope |
a read-only connection: reconnect with write |
function_not_allowed |
a function outside the allow list: rewrite without it |
table_not_allowed |
a DuckDB system view: read your catalogs' tables |
sql_error |
fix the SQL |
catalog_required |
name the table catalog.namespace.table |
namespace_required |
a bare SHOW TABLES: use SHOW ALL TABLES or SHOW TABLES FROM catalog.namespace |
not_found |
check did_you_mean and list_tables |
connection_not_granted |
the catalog exists and the person reads it, but this connection was not granted it: tell the person the remedy |
query_timeout |
past 20 s: aggregate or filter; or your own DuckDB |
query_memory |
past the 768 MB a server-side query has: fewer distinct GROUP BY keys, no huge lists or sorts; or your own DuckDB |
busy |
retry after retry_after_seconds; limit names the limit that was full |
rate_limited |
more requests a second from one key than query_requests_per_second: retry after retry_after_seconds |
not_unclaimed |
keep_account on an account a person already owns |
missing_credential |
no key configured (a local developer server) |
unreachable |
no answer from the API: safe to retry |
route_missing |
the API behind the server predates a route |
redirected |
the API answered with a redirect (an http:// address, a redirecting proxy): set LHBOX_URL to location |
The last four are the server's own envelopes. The full list: Errors and help.
Security model
- The caller's grants bound every tool. The server adds no permission and holds no credential of its own. Over the remote transport, each request's token or key is verified and forwarded to the API unchanged.
- A connection the person approves never changes who has access. Its access token reads. With
catalogs:writeit also writes tables and creates catalogs. - The API refuses such a connection everything that grants or removes access, whatever its scopes. That covers API keys and tokens, grants, invitations, members and roles, domains, integrations, sinks and uploaders. It covers publishing or deleting a catalog, the password, two-factor and the account itself. The answer is
403 mcp_human_only, or403 mcp_no_credentialsfor a key or token. Those stay on the account page, inlhboxor with an API key. - It never receives a catalog's static key pair.
GET /v1/connectionanswers itcredentials: "vended_only": only storage credentials that expire within about an hour. The one exception is a public catalog's shared reader key, which is public by design (GET /v1/public/{handle}/{catalog}/connection, theopen_public_catalogtool). - With
catalogs:writeit can also drop a namespace and set lifecycle rules that delete blob files by age. It can turn on a catalog's virtual-hosted address, whose certificate lists the bucket names in the public Certificate Transparency logs. It can edit a catalog's AGENTS.md, which other agents read and which is public on a public catalog. - No credential reaches the conversation.
queryruns on our side with read-only identities.query_rwruns with the write access the person approved. The model never sees either: rows come back, never a key. In a local developer server the key reaches the process through the environment. It leaves only as theAuthorizationheader to the API. - The server keeps nothing, and sends no telemetry. Every tool call is one or a few HTTP requests made now. A repeated query may be answered from the API's result cache, which the answer says (
cache.hit).
Numbers and dates
| what | figure | measured |
|---|---|---|
query_rw, one statement |
0.3-0.7 s, 25 s limit | production |
| Claude directory listing | listed as a community connector | since 2026-10-11 |
| ChatGPT, free account | connected; list_tables answered in a chat. Before that day its sign-in was refused: its client describes itself as able to use a signed key or none, and only the second was accepted |
2026-10-11 |
| a public catalog made private | stops answering query at once |
since 2026-10-11 |
a Claude sandbox refuses a script holding a key (why keyless_duckdb_sql comes first) |
2026-10-12 | |
| Claude cloud sandbox: outside hosts refused until the domain was allowed; pip needed the proxy bypass off | 2026-10-12 | |
read_agents_md added; clients connected earlier need a reconnect |
2026-09-28 |
The server is one file built on the official mcp Python package, pinned.