Lens exposes a Model Context Protocol server so agents can do the same work as the UI: run read-only SQL, save questions, pin dashboards, switch engines, and manage column protection. Field policy still masks every result.

Mount

Add the plug after Plug.Parsers in the host endpoint. That keeps MCP out of the browser CSRF / accepts: html pipeline.

plug Plug.Parsers,
  parsers: [:urlencoded, :multipart, :json],
  pass: ["*/*"],
  json_decoder: Jason

plug PhoenixLensWeb.Plugs.MCP, path: "/lens/mcp"

The dummy app already does this. Open Settings → MCP.

Project tokens

Each token has:

  • Token id (plt_…) — public, listed in Settings
  • Secret (lns_…) — shown once, stored as SHA-256

Send the secret on every request:

Authorization: Bearer lns_

You can bind id and secret together: Authorization: Bearer plt_…:lns_….

Revoking a token in Settings immediately rejects MCP clients that used it. Creating and revoking tokens is UI-only (a stolen MCP token cannot mint more tokens).

The optional Settings → Security lock (authenticator / passkey) applies to the notebook UI only. Bearer MCP calls are not asked for a second factor.

Client config

{
  "mcpServers": {
    "phoenix-lens": {
      "url": "http://localhost:4000/lens/mcp",
      "headers": {
        "Authorization": "Bearer lns_…"
      }
    }
  }
}

Replace the URL with your host (https://app.example.com/lens/mcp). Protocol: Streamable HTTP JSON-RPC (initialize, tools/list, tools/call).

Tools

ToolUI equivalent
list_tables, describe_tableData catalog
run_sql, compile_notebookAsk (native SQL / notebook)
list_questions, get_question, save_question, delete_questionQuestions
export_csv, export_jsonExport
list_dashboards, get_dashboard, save_dashboard, delete_dashboardDashboards
pin_question, unpin_card, layout_dashboard, run_dashboardPin / arrange / open dashboard
list_auditAudit
get_settings, set_engine, set_audit_retention, reconnect_engineSettings → Engine
add_source, remove_sourceExtra DuckDB sources
list_protections, add_protection, remove_protectionColumn protection
list_integrations, save_email_integration, add_webhook, remove_webhookSettings → Integrations
list_alerts, create_alert, delete_alert, run_alertQuestion alerts

run_sql is still SELECT/WITH only. Masked cells are [redacted] in tool output. Audit actor is mcp:<token_id>.