Pro Admin MCP Server
Viewing latest docs.
Switch version: v3

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. This is especially useful during development and operations: you can interrogate production (carefully) or a staging environment without leaving your editor.

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
Copy
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
Copy
# runtime.exs already reads this — just set the env var
export MCP_ADMIN_TOKEN="your-long-random-secret"

On Fly.io:

shell
Copy
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
Copy
{
  "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
Copy
{
  "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

Tools are native Jido.Action modules. Define one under lib/petal_pro/ai/admin_chat/actions/:

elixir
Copy
defmodule MyApp.AI.AdminChat.Actions.GetRevenueStats do
  @moduledoc "Fetch monthly revenue figures."

  use Jido.Action,
    name: "get_revenue_stats",
    description:
      "Get monthly recurring revenue and new subscription counts. " <>
        "Specify a number of months to look back (default 3).",
    schema: [
      months: [
        type: :pos_integer,
        default: 3,
        doc: "Months to look back (default 3)."
      ]
    ]

  alias MyApp.Billing

  @impl true
  def run(%{months: months}, _context) do
    {:ok, Billing.monthly_revenue_stats(months)}
  end
end

The schema is a NimbleOptions keyword list — Jido validates incoming arguments against it and emits a JSON Schema for the LLM. Use :in constraints (type: {:in, ["a", "b"]}) to surface enum choices to clients.

Then append it to @actions in PetalPro.AI.AdminChat.ActionRegistry:

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

That’s it. The tool immediately appears in tools/list responses and can be called via tools/call. Both the admin chat UI and the MCP server read from the same registry, so one registration covers both interfaces.

Add a test under test/petal_pro/ai/admin_chat/actions/ — call your action’s run/2 directly to assert behaviour, and let Jido handle schema validation.

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.