MCP Server (Use Mako from Claude Code)
Mako is itself an MCP server: point Claude Code, Cursor, Codex, ChatGPT, or any MCP client at your workspace and your agent can explore your databases, validate queries, and build full Mako apps — using your AI subscription’s tokens, not Mako’s in-product agent.
Where MCP Connectors let Mako’s agent use other systems’ tools, the MCP server is the reverse: it lets your agent use Mako.
Want Claude Code or Codex inside the Mako UI instead? See Coding Agents (ACP).
Connecting a client gives it everything your workspace role allows — there are no per-permission opt-ins on the sign-in page. That includes running governed dbt models and jobs (which can create, replace or modify warehouse relations) and creating source connections; a viewer stays read-only because those tools require at least the member role, checked live on every call. Raw SQL stays read-only: SQL writes remain a narrower, double-gated API-key opt-in requiring both a key with query:write and a connection explicitly marked Allow agent writes. Inviting workspace members is the other explicit API-key opt-in (members:write).
Connect by signing in (no API key)
Section titled “Connect by signing in (no API key)”Give your client one URL — https://your-mako-host/api/mcp — and it discovers the OAuth sign-in flow itself. Your browser opens once: sign in with your Mako account, pick a workspace, and approve. The page lists what the connection can do; there is nothing to tick.
Inside the app, everything lives at Settings → Connect Agents: per-client setup with one-click Add to Claude / Add to Cursor buttons, plus a Connected agents list showing every agent with access (who connected it, when it was last used) with one-click disconnect.
Claude Code
claude mcp add --transport http mako https://your-mako-host/api/mcpThen type /mcp inside a session to trigger the sign-in.
Claude (web / desktop) — Settings → Connect Agents has a one-click Add to Claude button that opens claude.ai with the connector prefilled (you review and confirm, then click Connect and sign in). Manually: Settings → Connectors → Add custom connector, name it mako, paste the URL. The install-link format, if you want to share it, is:
https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=mako&connectorUrl=<percent-encoded MCP URL>ChatGPT — Mako implements ChatGPT’s connector contract (top-level search / fetch tools), so it can be added as a ChatGPT connector and used in regular chat and deep research with citations back into your workspace. In ChatGPT (Plus/Pro/Business/Enterprise): Settings → Connectors → Create — if you don’t see the option, enable Developer mode under Settings → Connectors → Advanced settings first. Name it mako, paste https://your-mako-host/api/mcp, choose OAuth authentication, and sign in when prompted (same consent flow: pick a workspace, read-only). In chat and deep research ChatGPT uses search / fetch to find and cite saved consoles, dashboards, apps, and skills; with developer mode on, the full Mako tool surface (SQL exploration, the apps loop, dbt) is available too.
Cursor — add to .cursor/mcp.json (project) or ~/.cursor/mcp.json (global); Cursor prompts you to sign in on first use. Settings → Connect Agents also has a one-click Add to Cursor button.
{ "mcpServers": { "mako": { "url": "https://your-mako-host/api/mcp" } }}Codex — add to ~/.codex/config.toml; Codex opens your browser to sign in on first use:
[mcp_servers.mako]url = "https://your-mako-host/api/mcp"Verify the connection (Claude Code): claude mcp list should show mako … ✓ Connected.
CLI and external MCP sign-ins stay connected until you revoke them, even after long periods of inactivity. Access tokens last 8 hours and clients renew them automatically with a rotating refresh token that has no expiry. You can revoke a connection from your workspace settings.
Under the hood this is standard OAuth 2.1 for MCP: RFC 9728 protected-resource discovery, dynamic client registration, PKCE, and rotating refresh tokens. Every grant carries mcp query:read warehouse:write connections:write, whatever the client requests; workspace roles decide what a viewer can do.
Headless / CI: API keys
Section titled “Headless / CI: API keys”Where a browser sign-in isn’t possible, use a workspace API key instead. Go to Workspace Settings → API Keys → Create API Key — new keys carry the mcp and query:read scopes and the key-created dialog shows ready-to-paste per-client snippets with the key filled in:
claude mcp add --transport http mako https://your-mako-host/api/mcp \ --header "Authorization: Bearer revops_..."Or team-shared via .mcp.json in your repo (key kept in an env var):
{ "mcpServers": { "mako": { "type": "http", "url": "https://your-mako-host/api/mcp", "headers": { "Authorization": "Bearer ${MAKO_API_KEY}" } } }}What your agent can do
Section titled “What your agent can do”Try: “Using the mako tools, explore my data and build a dashboard app showing revenue by month, then give me a preview link.”
The server ships usage instructions with the handshake, so agents discover this workflow on their own:
- Discover —
list_connectionslists every configured credential, of two kinds:databaseconnections (BigQuery, Postgres, MongoDB, …) thatlist_databases/list_tables/inspect_tabledescribe (schemas + sample rows; they dispatch on engine) and SQL queries, andsourceconnections (a Stripe key, a Vercel key, …) that flows read from andprobe_connectionreads live. Every row names itskindand itsconnector— the code it was configured with.search_consoles/search_dashboardsfind existing workspace work. Skills:list_skills(index),get_relevant_skills(ranked bodies for your task — same retrieval as the in-product agent), thenload_skill/read_skill_resourceas needed. - Validate queries —
sql_execute_query(read-only, short exploration timeout). Results come back as one compact markdown table (column names once, long cells shortened, rows added until a size budget runs out — 50 rows by default; passmaxRowsup to 200 for more, but prefer aggregating in SQL), with a note saying what was left out. Slow warehouse?create_console→run_console→check_query_statusfor long-running queries. - Build apps —
app_list_apps/app_create_appto discover or scaffold (apps/<slug>/, a real Vite project), thenapp_write_file/app_edit_file/app_bashfor ordinary file and shell work, andapp_materializeto build a binding’s parquet artifact (bindings arebindings/<name>.sqlfiles with the validated query). - Verify with real eyes —
app_open_appstarts the dev server (and focuses the app in the user’s UI),app_dev_logreturns the boot/vite log plus browser-console output, andapp_browsedrives a headless browser against the running dev server: click, navigate, and screenshot what a user would actually see. - Publish —
app_commit(durability,git pushsemantics) andapp_merge_to_main(mainis what publishes buildable state). - Dashboards —
search_dashboardsfinds existing dashboards andupdate_data_source_queryedits them in place: rewrite a source query (replace/patch/append), toggle live vs. materialized (parquet), and set the dashboard-level cron refresh schedule (materializationSchedule). Server writes bump the dashboard version, push adashboard.updatedrealtime poke to open tabs, and queue a Parquet rebuild when the definition changes (schedule-only changes don’t). Widget/layout editing stays in-product — those tools are client-only. - dbt —
dbt_create_projectandread_dbt_project_treemanage projects;create_dbt_file/read_dbt_file/edit_dbt_file/modify_dbt_file/delete_dbt_fileprovide model and config CRUD;dbt_parse/dbt_compile_model/dbt_showvalidate asynchronously (start a run, polldbt_get_run); anddbt_create_job/dbt_update_job/dbt_delete_jobmanage jobs and schedules. Project and job mutations require an admin/owner role; file mutations and runs require at least member. - Connectors and connections — a connector is code (
stripe,ws:vercel-ai-gateway); a connection is a credential configured with one.list_connectorsis the catalog of code available to the workspace, with the connections configured with each;inspect_connectordescribes a type (entities, incremental support, config field names — never values);inspect_connectiondescribes one configured connection of either kind; andprobe_connectionruns a source connection live against the platform behind it: the credential check plus one bounded page of an entity (default 20 records, max 200;fieldsto keep only some columns,sincewhere the connector supports it), written nowhere. That is how an agent verifies a freshly configured key, sees the real shape of an entity before authoring aflows/<slug>.yml, or answers an exploratory question from a platform that is not in the warehouse yet. Credential values never appear in a result; the probe scrubs them even out of vendor error messages.probe_connectionreads external data, so likesql_execute_queryit needs thequery:readscope.create_connectionis the one write in this group: it stores a new source connection for a connector (config keyed byinspect_connector’s field names, secrets encrypted by the connector’s schema), runs its credential check, and returns the id — a failed check keeps nothing. It requires at least the member role and cannot read, change or delete existing connections.
If an expected tool is absent, call get_mcp_capabilities. It reports the connection’s effective scopes and grants, every available tool, and hidden grant-gated tools with the exact scope needed to enable them.
Optional helpers: web_search / fetch_url for public docs (annotated openWorldHint).
For ChatGPT compatibility the server also exposes a top-level search / fetch pair — workspace-wide search over saved consoles, dashboards, apps, and skills, plus document retrieval by id (console:…, dashboard:…, app:…, skill:…). ChatGPT requires exactly these two tools to accept a connector for chat and deep research; other clients can use them as a quick workspace search.
The MCP tool surface is a curated subset of the in-product agent tools. Classification lives in api/src/mcp/bridge-policy.ts — every agent tool is either bridged, MCP-only, or explicitly excluded (client-only UI, security, in-product UX, or deferred). Adding an agent tool without classifying it fails the MCP inventory test.
Read-only tools are annotated per the MCP spec (readOnlyHint), so well-behaved clients run the whole discovery/query loop without approval prompts. If you keep a Mako tab open on the app being edited, it live-reloads on every agent change.
Security model
Section titled “Security model”- SQL is read-only unless double-gated otherwise. By default SQL must be a single
SELECT/WITHstatement; enforcement also happens inside the database where supported (PostgreSQL/Cloud SQL/Redshift read-only transactions, MySQLSTART TRANSACTION READ ONLY, ClickHousereadonly=2). Arbitrary MongoDB JavaScript is not exposed at all — Mongo is discovery/inspection only. SQL writes require an API key with thequery:writescope and a connection a workspace admin markedallowAgentWrites— the key scope alone stays read-only against every other connection, the connection flag alone does nothing for read-scoped keys, and console runs, app data bindings, and materializations stay read-only regardless. - Warehouse mutations are governed. The only write path to a warehouse over MCP is dbt execution, which builds committed, reviewable model definitions — never ad-hoc SQL. It needs at least the member role; viewers never see these tools.
- Creating connections stays narrow.
create_connectionneeds at least the member role, refuses connectors that declare no config schema (their secrets could not be encrypted) and unknown config keys, never returns or logs a config value, keeps nothing whose credential check fails, and cannot touch existing connections. - Non-SQL engines fail closed (MongoDB shell code, Cloudflare KV): the lexical analyzer cannot validate them, so read-only execution refuses them outright. SQL engines without a session-level read-only mode (BigQuery, MSSQL, Cloudflare D1) rely on the validated single-
SELECT/WITHstatement instead. - MCP credentials are MCP-only. OAuth access tokens and scoped keys are rejected on every other API endpoint, so an MCP credential can never be replayed against REST mutation routes.
- OAuth grants are least-privilege by construction: public clients use mandatory PKCE, single-use authorization codes, rotating refresh tokens hashed at rest, and a binding to the one workspace chosen at consent. A grant gives everything the person’s role allows; there are no per-permission consent options.
- Key management requires a browser session — API keys cannot create or delete other API keys.
- App data bindings and materializations are always read-only.
- Dashboard writes edit definitions, never data.
update_data_source_querychanges the dashboard document (query text, live/parquet toggle, refresh schedule) under the same query-access check as app bindings — an agent can point a source at a different saved query, but the query itself still executes read-only. Widget and layout mutations stay client-only and are not bridged.
Headless & CI usage
Section titled “Headless & CI usage”Non-interactive runs (claude -p …) don’t show permission dialogs — allowlist the server explicitly:
claude -p "explore my mako data and summarize revenue" --allowedTools "mcp__mako"To keep agent context lean, agents can pass includeScreenshot: false to app_browse while iterating and fetch one screenshot at the end.
Working from a local checkout
Section titled “Working from a local checkout”Every workspace repo carries a small template Mako keeps current: AGENTS.md
(imported by CLAUDE.md) telling your agent what the repo is and how to work
in it, .mcp.json wiring the mako MCP server, .envrc for direnv, and the
vendored @makoai/app-sdk. The whole setup, no key to paste:
git clone <your workspace repo> && cd <repo>claude # the mako MCP server prompts a browser sign-innpx @makoai/cli login # same sign-in for the app dev server, kept in ~/.mako/credentials.jsonnpx @makoai/cli dev <app> # or: cd apps/<app> && pnpm install && pnpm devThe app renders with real data: the scaffold’s vite.config.ts includes
makoData() from @makoai/app-sdk/vite, which serves __data/<binding>.parquet
from your Mako host with that login, built from your local
bindings/<binding>.sql: unchanged text is the committed artifact (built on
first request if it never was), edited text is a draft built from your SQL and
never stored, so you see real data for a query before committing it. Results
are cached under node_modules/.mako-data/ next to a fingerprint of the text
they came from (five minutes; ?refresh bypasses). MAKO_DBT_ENV=<env> (or
makoData({ dbtEnvironment })) renders {{ dbt_schema }} against that dbt
environment, such as your personal one, instead of production — per relation,
like dbt --defer: a {{ dbt_schema }}.<model> your environment has not built
reads the production schema instead (BigQuery; other warehouses render every
reference to the environment). This is the one
place MCP credentials — OAuth tokens and scoped keys alike — are accepted
outside /api/mcp: with query:read they may call the read-only binding
routes (GET …/bindings, GET …/bindings/<name>/artifact,
POST …/bindings/<name>/materialize, POST …/bindings/<name>/dev-build,
GET …/binding-jobs/<jobId>[/artifact], GET …/viewer) and nothing else —
except mako dbt, below. Builds longer than a proxy’s request limit run as
jobs: ?async=1 on materialize (or "async": true on dev-build) answers 202
with a jobId to poll; the plugin does this for you.
dbt from your checkout. npx @makoai/cli dbt run -s <selector> (also
build and test; --full-refresh, --no-defer) runs dbt in Mako’s runner
on your checkout’s dbt/ folder, uncommitted edits included: the CLI sends
the files that differ from where your branch forked from main along with the
run request, nothing is committed or pushed, and the log streams to your
terminal (Ctrl-C cancels the run; the exit code is dbt’s). No warehouse
credentials on your laptop. Any mako login can run it (at least the member
role in the workspace; there is no separate opt-in): your dbt
code runs with the target environment’s warehouse credentials, and macros,
hooks and schema configs can write beyond your own schema — personal
environments currently share the production connection, so this is not a
sandbox. It builds your personal environment by default (created on first
use, schema dbt_<you>; --env picks a shared development one), never
another person’s, and never production — production is built from main by a
job. Known connection secrets are redacted from the streamed log. Runs appear
in the project’s run history as local checkout on <branch>. The routes:
POST /api/workspaces/:id/dbt/local-runs, GET …/local-runs/:runId,
POST …/local-runs/:runId/cancel (your own runs only).
Headless / CI: put a workspace API key in the repo’s gitignored .env
(MAKO_API_KEY=revops_…, scopes mcp + query:read) and register the server
with the header (claude mcp add --transport http mako $MAKO_API_URL/api/mcp --header "Authorization: Bearer $MAKO_API_KEY"); the
dev server picks the key up automatically. Self-hosted: MAKO_API_URL in
.env, exported (.envrc does it for direnv users) — .mcp.json expands
${MAKO_API_URL:-https://app.mako.ai}.
Two things AGENTS.md tells the agent that are easy to get wrong:
- Edit files with your own tools. The
app_*file tools (app_write_file,app_bash,app_commit, …) act on Mako’s sandbox copy of the repo, not on your checkout. - Push to deploy.
mainis production; a commit onmain— from your terminal, a merged PR, or the Publish button — is what builds and serves the app.
The hosted server is described for MCP directories in server.json at the
repository root (ai.mako/mako, streamable HTTP at https://app.mako.ai/api/mcp).
Apps toolset
Section titled “Apps toolset”The app is a folder in the workspace’s git monorepo and the agent works like a developer in a checkout:
app_list_apps/app_create_app— discover or scaffold (apps/<slug>/, a real Vite project).app_read_file/app_write_file/app_edit_file/app_glob/app_grep— ordinary file work;app_bashruns any shell command in the app’s sandbox. Output is capped (same limits as the in-app agent):app_read_filetakesoffset/limitand returns at most 2000 lines or ~50k chars per call,app_grepreturns at most 200 matches, andapp_bashkeeps the head and tail of long output, saving the full text to a sandbox file whose path is in the result.app_materialize— build a binding’s parquet artifact (bindings arebindings/<name>.sqlfiles with front matter, not documents).- Verify with real eyes —
app_open_appstarts the dev server (and focuses the app in the user’s UI),app_dev_logreturns the boot/vite log plus browser-console output, andapp_browsedrives a headless browser against the running dev server: click, navigate, and screenshot what a user would actually see. app_status/app_commit/app_merge_to_main— commits are durability (git pushsemantics); merging tomainis what publishes buildable state.
Use app_list_apps first: if the workspace has v2 apps (or you’re asked to
create one), stay in the app_* loop and skip the v1 tools above; the two
systems must not be mixed on one app.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause / fix |
|---|---|
| Client never opens the sign-in browser | The client predates MCP OAuth support — update it, or fall back to an API key header. |
401 Invalid or expired MCP access token | The access token expired or the connection was revoked. The client should refresh automatically; reconnect if the grant was revoked or the client lost its saved credentials. |
403 … created before MCP scopes existed | Legacy key. Sign in via OAuth or create a new key under Workspace Settings → API Keys. |
403 … does not include the mcp scope | Key was created without the mcp scope — create a new key. |
Mako MCP access is read-only: the query was rejected… | The agent attempted a write (UPDATE/INSERT/DDL). Expected — run writes with your own database tooling. |
Read-only execution is not supported for mongodb… (or cloudflare-kv) | Non-SQL engine — the SQL analyzer can’t validate it, so it fails closed. For MongoDB, use the discovery/inspection tools instead; arbitrary Mongo execution is not available over MCP. |
| Client shows the server but tools error with 401 | The OAuth token or Authorization: Bearer key is missing/revoked — reconnect or rotate. |
| ChatGPT rejects the connector (“does not implement our spec”) | The deployment predates the search / fetch connector tools — update Mako. Custom MCP connectors also require Developer mode to be enabled under ChatGPT’s connector settings. |