Pro AI & Developer Tooling
Viewing latest docs.
Switch version: v3

AI & Developer Tooling

Petal Pro v4 includes a suite of AI-powered admin tools and first-class Claude Code integration. This page covers the in-app AI chat, the MCP server for external AI clients, and the Claude Code configuration that ships with every project.

AI Admin Chat

The AI Admin Chat is an admin-only chat interface that lets you interrogate your running Petal Pro app using natural language. Ask it to pull stats, look up users, manage orgs, or check AI usage costs — it figures out which tools to call, chains multiple calls if needed, and presents the results as readable text. It’s powered by Jido and ReqLLM using Google Gemini, and shares the same action registry as the Admin MCP Server.

v4 migration note: The AI Admin Chat was rewritten in v4 to use Jido and ReqLLM (replacing the LangChain-based implementation from v3). If you added custom LangChain tools in v3, you’ll need to reimplement them as native Jido.Action modules described below. The tool surface is the same; only the implementation pattern changed.

Architecture

Admin LiveView
    │
    ▼
PetalPro.AI.Actions.AdminChatAction   (Jido.Action)
    │ streams responses from Gemini via ReqLLM
    │ runs tool loop until no more tool calls
    ▼
PetalPro.AI.AdminChat.ActionRegistry  (single source of registered actions)
    │ Jido.AI.Turn validates args + dispatches by name
    ▼
PetalPro.AI.AdminChat.Actions.*       (individual Jido.Action modules)
    ▼
Your Repo / Contexts

The default model is google:gemini-2.5-flash-lite, overridable via the :ai_admin_chat_model app env. Every call goes through PetalPro.AI.HTTP.client/1, which attaches PetalPro.AI.CostTrackerPlugin as a Req response step — token counts and cost are logged to ai_call_logs automatically.

Tools Are Native Jido Actions

Every tool is a Jido.Action module. The action declares its name, description, and a NimbleOptions schema, and Jido handles argument validation, JSON Schema generation for the LLM, and dispatch.

elixir
defmodule PetalPro.AI.AdminChat.Actions.ManageUser do
  use Jido.Action,
    name: "manage_user",
    description: "Manage a user account.",
    schema: [
      user_id: [type: :string, required: true, doc: "The user's UUID."],
      action: [
        type: {:in, ["suspend", "unsuspend", "delete", "undelete", "make_admin", "remove_admin"]},
        required: true,
        doc: "Action to perform."
      ]
    ]

  @impl true
  def run(%{user_id: user_id, action: action}, _context) do
    # ...
  end
end
Field Purpose
name Unique string identifier used in tools/call
description Shown to the model — write it like a good function docstring
schema NimbleOptions keyword list — emits as JSON Schema for the LLM
run/2 Receives validated, atom-keyed params and a context map

:in constraints emit as JSON Schema enum arrays so strict MCP clients see typed choices instead of free-form strings.

Action Registry

PetalPro.AI.AdminChat.ActionRegistry holds the @actions list and exposes two helpers used by both the chat LiveView and the MCP server:

elixir
# ReqLLM-shaped tool definitions (cached)
ActionRegistry.req_llm_tools()

# Action lookup map used by Jido.AI.Turn for dispatch (cached)
ActionRegistry.action_map()

Both results are memoised in :persistent_term, so the registry has zero per-request overhead.

Action modules live in lib/petal_pro/ai/admin_chat/actions/. The full list registered by default:

  • get_site_stats — user/org counts by period
  • list_recent_users — recently registered users
  • search_users — text search with filters
  • manage_user — suspend, unsuspend, delete, undelete, change role
  • list_orgs — organizations with member counts
  • manage_org — rename, delete, list members
  • get_ai_call_stats — AI usage costs and token breakdown
  • list_changelog_updates — read changelog entries
  • create_changelog_update — add a new changelog entry
  • manage_changelog — update or delete changelog entries

AI Cost Tracking

Every Gemini call is automatically tracked. The mechanism:

  1. PetalPro.AI.HTTP.client/1 returns a Req client with CostTrackerPlugin attached.
  2. When a response comes back from a known AI provider, the plugin extracts token counts and snapshots the current per-million-token price.
  3. An AICallLog record is inserted asynchronously via Task.Supervisor — cost tracking failure never interrupts the AI call.
  4. The operation and user_id are read from the process dictionary (:ai_cost_context).

To set attribution context before making AI calls:

elixir
Process.put(:ai_cost_context, %{operation: :my_feature, user_id: user.id})

AdminChatAction already does this automatically (operation: :admin_chat).

Logs are viewable in the admin panel under AI → Call Logs, and queryable via the get_ai_call_stats tool. The AICallLog schema:

elixir
typed_schema "ai_call_logs" do
  field :provider, :string
  field :model, :string
  field :operation, :string
  field :input_tokens, :integer
  field :output_tokens, :integer
  field :input_price_per_million, :integer
  field :output_price_per_million, :integer
  field :total_cost_cents, :integer
  field :response_time_ms, :integer
  field :http_status, :integer
  field :error_message, :string
  belongs_to :user, PetalPro.Accounts.User
  timestamps(type: :utc_datetime, updated_at: false)
end

Daily Budget

The chat action enforces a per-user daily budget (default 500 cents / $5.00). Configure it in config/config.exs:

elixir
config :petal_pro, ai_daily_budget_cents: 500

Configuration

The API key is read from config/runtime.exs via the :jido_ai keyring:

elixir
config :jido_ai, keyring: [
  google_gemini_api_key: System.get_env("GOOGLE_GEMINI_API_KEY")
]

Set GOOGLE_GEMINI_API_KEY in your environment. Without it, the action returns a descriptive error rather than crashing.

The model used is gemini-3.1-flash-lite-preview (configured in AdminChatAction). The action supports up to 8 tool-call iterations per message (configurable via config :petal_pro, ai_max_tool_iterations: 8).

Admin MCP Server

Petal Pro ships with a built-in Model Context Protocol (MCP) server that exposes your app’s admin tools to external AI clients such as Claude Code and Claude Desktop. Instead of logging into an admin panel to look up a user or check billing stats, you can ask your AI assistant directly — it talks to your running app over JSON-RPC 2.0, executes the relevant tool, and hands you back real data.

The Endpoint

All MCP traffic goes through a single HTTP endpoint:

POST /api/mcp
Authorization: Bearer <MCP_ADMIN_TOKEN>
Content-Type: application/json

The server speaks JSON-RPC 2.0 and accepts both single messages and batched arrays. Supported methods:

Method Description
initialize Protocol handshake — returns server name, version, and capabilities
tools/list Returns all registered tools with their input schemas
tools/call Executes a named tool with arguments
ping Health check

Quick sanity check with curl:

shell
curl -X POST http://localhost:4000/api/mcp \
  -H "Authorization: Bearer dev-mcp-token" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Setup

The Token

The server authenticates via a static bearer token read from the MCP_ADMIN_TOKEN environment variable. In dev, a default of "dev-mcp-token" is used so you can get started immediately. Set a real secret for staging and production:

shell
# runtime.exs already reads this — just set the env var
export MCP_ADMIN_TOKEN="your-long-random-secret"

On Fly.io:

shell
fly secrets set MCP_ADMIN_TOKEN="your-long-random-secret"

The token is compared with Plug.Crypto.secure_compare/2 to avoid timing attacks.

The Route

The route is already wired in the router. You don’t need to add anything. For reference, the controller that handles requests is PetalProWeb.McpController.

Connecting Claude Code

Create a .mcp.json file at the root of your project (it’s gitignored by default):

json
{
  "mcpServers": {
    "petal-pro-dev": {
      "type": "http",
      "url": "http://localhost:4000/api/mcp",
      "headers": {
        "Authorization": "Bearer dev-mcp-token"
      }
    },
    "petal-pro-prod": {
      "type": "http",
      "url": "https://yourapp.fly.dev/api/mcp",
      "headers": {
        "Authorization": "Bearer your-long-random-secret"
      }
    }
  }
}

After saving, run claude mcp list or restart Claude Code — the petal-pro-dev server should appear. You can now ask things like “list the 5 most recent users” or “suspend user with id abc-123” directly in the chat.

Connecting Claude Desktop

Add the server to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):

json
{
  "mcpServers": {
    "petal-pro": {
      "type": "http",
      "url": "https://yourapp.fly.dev/api/mcp",
      "headers": {
        "Authorization": "Bearer your-long-random-secret"
      }
    }
  }
}

Restart Claude Desktop and the tools will appear in the tools panel.

Built-in Tools

The following tools ship with Petal Pro. They’re all native Jido.Action modules under lib/petal_pro/ai/admin_chat/actions/ and registered in PetalPro.AI.AdminChat.ActionRegistry.

get_site_stats

Returns platform metrics — total users, total orgs, and new signups/orgs for a given period (today, week, month, all). Useful for a quick health check.

list_recent_users

Lists recently registered users with basic profile info.

search_users

Text search across user accounts with optional status filters.

manage_user

Performs account management actions on a single user identified by UUID. Available actions:

Action Effect
suspend Prevents login
unsuspend Re-enables login
delete Soft-deletes the account
undelete Reverses soft-delete
make_admin Grants admin role
remove_admin Revokes admin role

All actions are audit-logged via Logs.log_async/2.

list_orgs

Lists organizations with member counts. Accepts an optional search term and result limit.

manage_org

Manages an organization by slug or UUID. Available actions: rename (requires new_name), delete, list_members.

get_ai_call_stats

Returns AI usage and cost statistics for a configurable lookback period (default 30 days, max 365). Includes total calls, token counts, costs in both cents and dollars, and breakdowns by model and operation.

list_changelog_updates, create_changelog_update, manage_changelog

Tools for reading and managing your app’s changelog entries from the AI client.

Adding a Custom Tool

Define a Jido.Action module under lib/petal_pro/ai/admin_chat/actions/:

elixir
defmodule MyApp.AI.AdminChat.Actions.GetFeedbackStats do
  @moduledoc "Admin chat tool: feedback submission counts by status."

  use Jido.Action,
    name: "get_feedback_stats",
    description:
      "Get feedback submission counts grouped by status (open, in_progress, resolved). " <>
        "Optionally filter by number of days to look back.",
    schema: [
      days: [
        type: {:or, [:pos_integer, {:in, [nil]}]},
        default: nil,
        doc: "Look back this many days. Omit to return all-time counts."
      ]
    ]

  import Ecto.Query

  alias MyApp.Feedback.Submission
  alias MyApp.Repo

  @impl true
  def run(%{days: days}, _context) do
    base =
      if days do
        since = DateTime.add(DateTime.utc_now(), -days, :day)
        from(s in Submission, where: s.inserted_at >= ^since)
      else
        Submission
      end

    counts =
      base
      |> group_by([s], s.status)
      |> select([s], {s.status, count(s.id)})
      |> Repo.all()
      |> Map.new()

    {:ok, %{period_days: days, counts: counts}}
  end
end

Append it to @actions in PetalPro.AI.AdminChat.ActionRegistry:

elixir
@actions [
  # ... existing actions ...
  MyApp.AI.AdminChat.Actions.GetFeedbackStats
]

The tool immediately appears in both the admin chat UI and the MCP server — one registration covers both interfaces. Add a test under test/petal_pro/ai/admin_chat/actions/ calling run/2 directly.

Removing a Tool

Delete the module from @actions in ActionRegistry. The tool disappears from tools/list and tools/call at the next compile. You can delete the module file (and its test) too if you don’t need it.

Security Considerations

  • The token is the only auth mechanism. Treat MCP_ADMIN_TOKEN like a root password — use a long random string in production and rotate it if compromised.
  • Tools run with full database access. Every tool executes in your app process with your Repo. Don’t add tools that expose raw SQL execution or unfiltered bulk data.
  • Destructive actions are logged. manage_user and manage_org both call Logs.log_async/2 so you have an audit trail.
  • Don’t commit .mcp.json. It’s gitignored by default. If you ever accidentally commit it with a production token, rotate the token immediately.
  • Consider network restrictions for production. If your MCP client only needs to reach production from your own IP, add a firewall rule or Fly.io private networking rather than exposing the endpoint publicly.

Claude Code Integration

Petal Pro v4 ships with first-class Claude Code support. Sub-agents, skills, recipes, and scoped CLAUDE.md files are all pre-configured — you get an AI-aware codebase out of the box.

Sub-Agents

Sub-agents are specialized Claude Code agents that automatically activate based on context. They live in .claude/agents/ and are invoked by Claude Code when relevant.

Agent Description
schema-architect Designs database schemas following Petal Pro conventions (UUID v7 PKs, proper indexes, on_delete strategies)
test-runner Runs tests, interprets failures, and suggests fixes
elixir-reviewer Reviews Elixir code for correctness, conventions, and common pitfalls
heex-reviewer Reviews HEEx templates for proper interpolation, component usage, and accessibility
ui-ux-reviewer Reviews UI/UX for design quality, usability, and consistency with the app’s design system

You don’t invoke these directly — Claude Code activates the appropriate agent automatically.

Skills

Skills are slash commands for scaffolding new code. They enforce project conventions so generated code is consistent with the rest of the codebase. Run them from Claude Code with an optional argument describing what you want.

Scaffolding

Command Description
/new-liveview Scaffolds a new LiveView page — layout wrapping, imports, assign patterns, handle_params/handle_event structure
/new-context Scaffolds a new Phoenix context module — CRUD patterns, filters, error handling, logging
/new-email Creates a new transactional email template with inline styling and the deliver/render pipeline
/new-oban-worker Scaffolds an Oban background worker — queue config, retry strategy, logging, error handling
/schema-migration Guides schema and migration changes — UUID v7 PKs, indexes, Ecto schema patterns

Feature Addition

Command Description
/add-org-feature Scaffolds a new org-scoped resource end-to-end: schema with org_id, context, migration, LiveView pages, routes, GDPR updates
/add-notification Adds a new notification type end-to-end: schema, context function, email template, component renderer
/add-admin-page Scaffolds an admin section page with DataTable, Flop pagination, sorting, and filtering
/add-api-endpoint Adds a new REST API endpoint with OpenAPI specs, bearer auth, JSON views, and tests
/add-billing-feature Guides through billing additions: new plans, subscription gates, webhook handlers, Stripe calls
/add-route Safely adds routes to the router — enforces live_session grouping, pipeline selection, scope conventions

Utilities

Command Description
/ci Simulates the full GitHub Actions CI pipeline locally and fixes failures
/gdpr-compliance Ensures new user-associated schemas are included in data export and deletion workflows
/liveview-js Guides use of Phoenix.LiveView.JS for client-side UI state (show/hide, toggles, tabs) instead of server round-trips

Recipes

Recipes are slash commands for larger-scale customizations — removing features, switching architecture, adding integrations. See the Recipes page for the full list.

CLAUDE.md Files

The project ships with scoped CLAUDE.md files at multiple levels. Claude Code reads these automatically when working in each directory, giving it the right context without you having to explain it every time.

File Scope
CLAUDE.md (root) Project overview, feature table, dev scripts, architectural patterns, config keys
lib/CLAUDE.md Elixir conventions, Ecto guidelines, Mix guidelines
lib/petal_pro/CLAUDE.md Business logic patterns, multi-tenancy, auth, billing, background jobs
lib/petal_pro_web/CLAUDE.md Web layer conventions, LiveView patterns, layout system
assets/CLAUDE.md JS/CSS conventions, Tailwind v4 setup, Alpine.js patterns
test/CLAUDE.md Testing conventions, Mimic mocking, factory patterns

How to Use

Installation

shell
npm install -g @anthropic-ai/claude-code

Starting a session

shell
cd your-petal-pro-project
claude

Claude Code picks up all the CLAUDE.md context files automatically.

Running a skill

Type the slash command with an optional description of what you want:

/new-liveview A settings page for managing notification preferences
/add-org-feature Projects — users can create and manage projects within their org

Running a recipe

/recipes:remove-billing

Using sub-agents

Sub-agents activate automatically — when you ask Claude Code to review a template it will invoke heex-reviewer, when you ask it to design a schema it will use schema-architect. You don’t need to call them explicitly.

Asking Claude Code to work on a task

The workflow commands handle the full development cycle:

/plan Add a feedback widget to the dashboard

This generates a structured task file. Then:

/do_task

This implements it. Then:

/push

This runs CI, commits, and pushes.