Skip to content

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).

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

Terminal window
claude mcp add --transport http mako https://your-mako-host/api/mcp

Then 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.

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:

Terminal window
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}" }
}
}
}

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:

  1. Discover — list_connections lists every configured credential, of two kinds: database connections (BigQuery, Postgres, MongoDB, …) that list_databases / list_tables / inspect_table describe (schemas + sample rows; they dispatch on engine) and SQL queries, and source connections (a Stripe key, a Vercel key, …) that flows read from and probe_connection reads live. Every row names its kind and its connector — the code it was configured with. search_consoles / search_dashboards find existing workspace work. Skills: list_skills (index), get_relevant_skills (ranked bodies for your task — same retrieval as the in-product agent), then load_skill / read_skill_resource as needed.
  2. 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; pass maxRows up 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_status for long-running queries.
  3. Build apps — app_list_apps / app_create_app to discover or scaffold (apps/<slug>/, a real Vite project), then app_write_file / app_edit_file / app_bash for ordinary file and shell work, and app_materialize to build a binding’s parquet artifact (bindings are bindings/<name>.sql files with the validated query).
  4. Verify with real eyes — app_open_app starts the dev server (and focuses the app in the user’s UI), app_dev_log returns the boot/vite log plus browser-console output, and app_browse drives a headless browser against the running dev server: click, navigate, and screenshot what a user would actually see.
  5. Publish — app_commit (durability, git push semantics) and app_merge_to_main (main is what publishes buildable state).
  6. Dashboards — search_dashboards finds existing dashboards and update_data_source_query edits 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 a dashboard.updated realtime 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.
  7. dbt — dbt_create_project and read_dbt_project_tree manage projects; create_dbt_file / read_dbt_file / edit_dbt_file / modify_dbt_file / delete_dbt_file provide model and config CRUD; dbt_parse / dbt_compile_model / dbt_show validate asynchronously (start a run, poll dbt_get_run); and dbt_create_job / dbt_update_job / dbt_delete_job manage jobs and schedules. Project and job mutations require an admin/owner role; file mutations and runs require at least member.
  8. Connectors and connections — a connector is code (stripe, ws:vercel-ai-gateway); a connection is a credential configured with one. list_connectors is the catalog of code available to the workspace, with the connections configured with each; inspect_connector describes a type (entities, incremental support, config field names — never values); inspect_connection describes one configured connection of either kind; and probe_connection runs a source connection live against the platform behind it: the credential check plus one bounded page of an entity (default 20 records, max 200; fields to keep only some columns, since where 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 a flows/<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_connection reads external data, so like sql_execute_query it needs the query:read scope. create_connection is the one write in this group: it stores a new source connection for a connector (config keyed by inspect_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.

  • SQL is read-only unless double-gated otherwise. By default SQL must be a single SELECT/WITH statement; enforcement also happens inside the database where supported (PostgreSQL/Cloud SQL/Redshift read-only transactions, MySQL START TRANSACTION READ ONLY, ClickHouse readonly=2). Arbitrary MongoDB JavaScript is not exposed at all — Mongo is discovery/inspection only. SQL writes require an API key with the query:write scope and a connection a workspace admin marked allowAgentWrites — 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_connection needs 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/WITH statement 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_query changes 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.

Non-interactive runs (claude -p …) don’t show permission dialogs — allowlist the server explicitly:

Terminal window
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.

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:

Terminal window
git clone <your workspace repo> && cd <repo>
claude # the mako MCP server prompts a browser sign-in
npx @makoai/cli login # same sign-in for the app dev server, kept in ~/.mako/credentials.json
npx @makoai/cli dev <app> # or: cd apps/<app> && pnpm install && pnpm dev

The 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. main is production; a commit on main — 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).

The app is a folder in the workspace’s git monorepo and the agent works like a developer in a checkout:

  1. app_list_apps / app_create_app — discover or scaffold (apps/<slug>/, a real Vite project).
  2. app_read_file / app_write_file / app_edit_file / app_glob / app_grep — ordinary file work; app_bash runs any shell command in the app’s sandbox. Output is capped (same limits as the in-app agent): app_read_file takes offset/limit and returns at most 2000 lines or ~50k chars per call, app_grep returns at most 200 matches, and app_bash keeps the head and tail of long output, saving the full text to a sandbox file whose path is in the result.
  3. app_materialize — build a binding’s parquet artifact (bindings are bindings/<name>.sql files with front matter, not documents).
  4. Verify with real eyes — app_open_app starts the dev server (and focuses the app in the user’s UI), app_dev_log returns the boot/vite log plus browser-console output, and app_browse drives a headless browser against the running dev server: click, navigate, and screenshot what a user would actually see.
  5. app_status / app_commit / app_merge_to_main — commits are durability (git push semantics); merging to main is 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.

SymptomCause / fix
Client never opens the sign-in browserThe client predates MCP OAuth support — update it, or fall back to an API key header.
401 Invalid or expired MCP access tokenThe 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 existedLegacy key. Sign in via OAuth or create a new key under Workspace Settings → API Keys.
403 … does not include the mcp scopeKey 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 401The 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.