Skip to content

Powered by Grav

Coming soon — KahunaCart is in final testing. Join the list and be first to know. Join the list

Run the store from an AI assistant

This page is for two readers: the merchant who would rather ask an assistant "which products are low on stock?" than click through the admin, and the developer wiring KahunaCart into an AI workflow or an agent of their own.

The Model Context Protocol (MCP) is the standard an AI client uses to call tools on an outside system. grav-mcp is the MCP server for Grav: it connects an assistant to a Grav site over the REST API, and it picks up the 92 KahunaCart tools automatically once your store is reachable.

Before you begin

  • Grav 2.0 or later.
  • The Grav API plugin 1.0.22 or later, installed and enabled. GET /mcp/tools arrived in 1.0.22.
  • grav-mcp 0.1.2 or later.
  • Node.js 18 or later on the machine running the assistant.
  • An API key belonging to a user who holds api.access plus the kahunacart.* permissions you want the assistant to have. See REST API for generating one, and Permissions and safety for choosing which.

How it works

KahunaCart ships an mcp.yaml manifest in its plugin root that describes its admin routes as tools. The API plugin serves the union of every enabled plugin's manifest at GET /mcp/tools, filtered to the tools your API key is allowed to call. grav-mcp fetches that list at startup and registers one MCP tool per entry, named kahunacart_<name>kahunacart_list_products, kahunacart_refund_order. When the assistant calls one, grav-mcp calls the matching API route, and the same permissions and the same rules apply as when a person clicks the button in the admin.

Piece Where it lives What it does
mcp.yaml KahunaCart's plugin root Describes each route as a tool: name, description, method, path, permission, input schema
GET /mcp/tools The API plugin Serves every enabled plugin's manifest, filtered to what the calling key may see
grav-mcp The machine running the assistant Registers one MCP tool per entry and calls the API on the assistant's behalf

Note

Nothing in grav-mcp is written per plugin. Every KahunaCart tool comes from the manifest, so a KahunaCart update that adds a route adds a tool with no change to grav-mcp.

Connect Claude Code

  1. Open your Claude Code MCP config.
  2. Add a grav server:
JSON
{
  "mcpServers": {
    "grav": {
      "command": "npx",
      "args": ["-y", "grav-mcp"],
      "env": {
        "GRAV_API_URL": "https://mysite.com/api",
        "GRAV_API_KEY": "grav_your_api_key_here"
      }
    }
  }
}
  1. Restart Claude Code.

The assistant now has the core Grav tools and every KahunaCart tool your key can call.

GRAV_API_URL is the API plugin's base — your site plus /api, without the version prefix. GRAV_API_KEY is the key you generated, starting with grav_.

Warning

The key carries its owner's permissions. Do not commit it, and do not paste it into a shared config file. grav-mcp reads it from the environment and never logs it or returns it.

Other MCP clients

Any MCP client that launches a stdio server takes the same command and the same two environment variables. Point it at npx -y grav-mcp with GRAV_API_URL and GRAV_API_KEY set, and consult that client's own documentation for where its MCP configuration lives — Claude Desktop, Cursor and VS Code each keep it somewhere different.

grav-mcp also runs standalone, which is how you test it outside a client:

BASH
GRAV_API_URL=https://mysite.com/api GRAV_API_KEY=grav_abc123 npx grav-mcp

The same settings are available as CLI flags: --url, --key, --environment, --plugin-tools, --transport and --port. For a remote deployment, run it over HTTP instead of stdio:

BASH
npx grav-mcp --url https://mysite.com/api --key grav_abc123 --transport http --port 3100

Check the connection

  1. Ask the assistant to run discover_plugins.
  2. Read the mcp_tools block in the answer. It lists the loaded tools grouped by plugin, and kahunacart should be one of them.
  3. If your client can show its tool list, look for names starting with kahunacart_ instead.

When kahunacart is not there, check these in order:

  • A warning on grav-mcp's stderr. grav-mcp never fails on a plugin-tools problem; it prints a warning and starts with the core Grav tools only. The warning names the cause.
  • The API plugin's version. A 404 from /mcp/tools means an API plugin older than 1.0.22.
  • The key's permissions. The key needs api.access before any KahunaCart permission is consulted, and it only sees tools whose permission it holds.
  • The plugin. Only enabled plugins are read, so a disabled KahunaCart contributes nothing.

Fix the cause, then ask the assistant to run refresh_plugin_tools. No restart is needed.

What the assistant can do

KahunaCart's manifest publishes 92 tools. Which of them exist in your session depends on your key's permissions.

A few routes the admin uses are not published as tools. File, image and category-picture uploads and the release publish route are multipart, which manifest version 1 does not cover. The CSV and PDF report exports are not JSON. The sidebar badge and the two admin script routes are left out as well. Every other JSON route the admin calls is here.

Products and variants

kahunacart.products.manage. 18 tools.

Tool What it does
list_products Catalog products, newest first, with variants, categories, tags and attribute values. Takes q, page, per_page
get_product One product with everything on it, plus its ten most recent completed orders
create_product Create a product and at least one variant
update_product Change fields, variants, categories, tags, attributes and translations
delete_product Delete the product, its variants, options, releases, file rows and images
delete_variant Remove one variant and return the ones that remain
list_product_files The downloadable files attached to a product
update_product_file Edit a downloadable's name, download limit, expiry and position
delete_product_file Detach a downloadable and delete the bytes
list_product_images The product's gallery, in position order
update_product_image Change an image's alt text or its place in the gallery
delete_product_image Detach an image and delete the bytes
list_releases A product's versioned releases with their files, plus its evergreen files
create_release Cut a new version of a product
update_release Edit a release's version, date, changelog or published flag
delete_release Delete a release and the rows of the files on it
attach_release_file Move a file already on the product into a release
detach_release_file Take a file back out of its release, making it evergreen

Options

kahunacart.products.manage. 9 tools.

Tool What it does
list_product_options The product's option axes, their values, and the variants they describe
create_product_option Add an axis such as Size or Colour, optionally with its values
update_product_option Rename an axis, change how it is rendered, or move it
delete_product_option Remove an axis. The variants stay
create_option_value Add one value to an axis
update_option_value Relabel a value, change its swatch colour, or move it
delete_option_value Remove one value from an axis
generate_variants Report which combinations have no variant yet. A dry run unless apply is true
assign_variant_options Point one existing variant at one combination of option values

Categories

kahunacart.settings. 7 tools.

Tool What it does
list_categories The whole tree as a flat list, with counts, pictures and translations
create_category Add a category to the tree
update_category Change a category's fields
delete_category Delete a category and its picture. Its children are adopted by its parent
move_category Give a category a new parent, a new place among its siblings, or both
reorder_category Swap a category with the sibling above or below it
delete_category_image Remove a category's picture, the row and the bytes

Tags

kahunacart.settings. 4 tools.

Tool What it does
list_tags Every tag with its label, slug, translations and product count
create_tag Create a tag
update_tag Rename a tag, change its slug, or replace its translations
delete_tag Delete a tag and every assignment, and report how many products were carrying it

Attributes

kahunacart.products.manage. 6 tools.

Tool What it does
list_attributes Every attribute definition in display order, with the type list
get_attribute One definition with its options, translations and product count
create_attribute Define a store-wide attribute products can answer
update_attribute Change a definition
reorder_attributes Put the whole list in the order the ids are given
delete_attribute Delete a definition and its values. Refused with 409 while products answer it unless force is true

Tax

kahunacart.settings. 5 tools.

Tool What it does
list_tax_zones Every zone with its criteria decoded and the rates hanging off it
create_tax_zone Create a zone and its rates in one call
update_tax_zone Change a zone's fields. A rates key replaces the whole set
delete_tax_zone Delete a zone and its rates
import_eu_vat_presets Create a zone and rate per EU member state at its standard rate

Shipping

kahunacart.settings. 4 tools.

Tool What it does
list_shipping_zones Every zone in the merchant's own order, with the methods it offers
create_shipping_zone Create a zone and the methods it offers
update_shipping_zone Change a zone's fields. A methods key replaces the whole set
delete_shipping_zone Delete a zone and the methods it offered

Coupons

kahunacart.settings. 4 tools.

Tool What it does
list_coupons Discount codes with their type, limits, schedule and redemption counts
create_coupon Create a discount code
update_coupon Change a coupon's fields
delete_coupon Delete a discount code

Sales

kahunacart.sales.manage. 5 tools.

Tool What it does
list_sales Every sale with its header and its rules
get_sale One sale with its rules in order and its label translations
create_sale Create an automatic discount. name and label are both required
update_sale Change a sale's header, its rules, or both
delete_sale Delete a sale, its rules and the translations of its label

Reports

kahunacart.reports. 6 tools, all read-only.

Tool What it does
report_summary Revenue, orders, average order value, refunds, top products and a daily series
report_products What sold by product and variant, sorted by revenue
report_customers Who bought, new against returning
report_taxes Tax collected per label and rate, with the taxable base beside it
report_refunds Every refund raised in the range, whatever became of it
report_low_stock Stock-managed variants at or below a threshold, right now. Default 5

All but report_low_stock take from and to as YYYY-MM-DD, and default to the last 30 days.

Orders

kahunacart.orders.view to read, kahunacart.orders.manage to act, kahunacart.orders.refund to refund. 8 tools.

Tool What it does
list_orders Every order that got past the cart, newest first. Takes q, page, per_page
get_order One order with its lines, adjustments, transactions and timeline
create_order Record an order taken over the phone, at a stall or by email
refund_order Refund through the provider that took the payment. Omit amount for the rest
mark_order_paid Settle an order whose payment arrived outside KahunaCart
fulfill_order Mark the whole order shipped and queue the shipped email
cancel_order Call off an unpaid sale and reverse stock, coupon and download links
resend_order_confirmation Queue the confirmation email again

Downloads

kahunacart.orders.view to read, kahunacart.orders.manage to revoke. 2 tools.

Tool What it does
list_order_downloads Every download grant on an order, with its file, uses, expiry and revocation
revoke_download Kill one grant. There is no un-revoke

Customers

kahunacart.customers.view to read, kahunacart.customers.manage to act. 5 tools.

Tool What it does
list_customers Everyone who has bought, with order count and lifetime revenue
get_customer One customer with their orders, addresses, account state and add-on panels
invite_customer Open a store account if needed and queue the set-password email
clear_customer_2fa Destroy the authenticator secret on a customer's account
set_customer_state Switch a customer's ability to sign in on or off

Webhook log

kahunacart.settings. 2 tools, both read-only.

Tool What it does
webhook_summary Per-provider counts, the current run of failures, and the retention window
list_webhook_deliveries Recent deliveries, newest first. Takes provider, page, per_page

Providers

kahunacart.settings. 5 tools.

Tool What it does
list_providers Every registered payment method and what it declares it can do
check_provider Ask a provider to prove its configured credentials work
sync_provider Push the catalog to a provider, or pull the provider's into it
setup_provider_webhook Ask a provider to register this store's webhook endpoint
make_default_provider Write checkout.default_provider so checkout starts here

Config and admin

kahunacart.orders.view. 2 tools, both read-only.

Tool What it does
get_config Currency, decimal places, storefront route, languages, providers and job health
list_admin_sections The extra admin screens other plugins have contributed

Six things you can ask for

What you type What the assistant calls
"How many orders are unpaid from last week?" kahunacart_list_orders. Every row carries payment_status and created_at, and the assistant filters and counts. The list has no status filter of its own
"Add a Material attribute with cotton and linen, and set it on every tee." kahunacart_create_attribute with type: select, then kahunacart_list_products with q=tee, then kahunacart_update_product on each. attributes merges, so nothing else on the product is touched
"Refund order 482 in full." kahunacart_list_orders with q=482 to turn the order number into an id, then kahunacart_refund_order with no amount
"Which products are low on stock?" kahunacart_report_low_stock, threshold 5 unless you name another
"Create a 20% summer sale on the Apparel category until 31 August." kahunacart_list_categories for the id, then kahunacart_create_sale with one rule of scope: category, mode: percent, percent: "20" and an expires_at timestamp
"Is Stripe still connected?" kahunacart_check_provider. A failed check is a successful call reporting ok: false with the provider's own message

Permissions and safety

Three things stand between an assistant and your store.

The key decides which tools exist. GET /mcp/tools leaves out every tool whose permission the calling key does not hold, so a key with only kahunacart.reports produces a session where the refund tool is not present at all. A super admin sees everything.

Every call is checked again. grav-mcp checks the permission before it calls, and the API plugin checks it again on the route. A tool that slipped through would still be refused with 403.

Annotations tell the client what a tool is. Every GET is marked read-only. Deletes are marked destructive, and so are refund_order, cancel_order and revoke_download. An MCP client that asks before running destructive tools has what it needs to ask.

A store with demo.readonly on refuses every write with 403 before the handler runs, whatever the key's permissions say. Reads are never blocked.

Tip

Give the assistant its own API key rather than reusing yours, with only the permissions the job needs. Revoking one key then costs you nothing else.

Goal Permissions on the key's account
Answer questions about the numbers api.access, kahunacart.reports
Answer questions about orders and customers api.access, kahunacart.orders.view, kahunacart.customers.view
Run the catalog api.access, kahunacart.products.manage
Run promotions api.access, kahunacart.sales.manage, kahunacart.settings
Process orders, without refunds api.access, kahunacart.orders.view, kahunacart.orders.manage
Process orders, refunds included the row above plus kahunacart.orders.refund
Everything the admin can do api.access and every kahunacart.* permission

To limit which plugins publish tools at all, set --plugin-tools or GRAV_MCP_PLUGIN_TOOLS. all is the default, none turns plugin tools off, and a comma-separated list of plugin slugs loads only those:

BASH
npx grav-mcp --url https://mysite.com/api --key grav_abc123 --plugin-tools kahunacart

Two conventions matter when you read what the assistant reports back. Money goes in as decimal strings in the store currency ("19.99") and comes back as minor-unit integers (price_minor, total_minor, refunded_minor); read currency_exponent from get_config to convert. And attributes on a product update is a merge keyed by attribute slug: a slug you do not send keeps its value, and a slug sent as null removes it. Every other list-valued field replaces its whole set. See Money conventions.

Add-ons

The Licenses and Subscriptions add-ons ship their own mcp.yaml manifests, so installing either one adds its tools to the same session. Both appear the moment the plugin is enabled and the tools are refreshed.

Licenses

Licenses publishes 20 tools under the kahunacart_licenses_ prefix. The admin tools need the kahunacart.licenses permission. The last three call the public validation routes your software already uses, so an assistant can check a customer's key without a permission of its own.

Tool What it does Permission
kahunacart_licenses_list_licenses List licenses kahunacart.licenses
kahunacart_licenses_get_license Get a license kahunacart.licenses
kahunacart_licenses_issue_license Issue a license by hand kahunacart.licenses
kahunacart_licenses_update_license Edit a license kahunacart.licenses
kahunacart_licenses_suspend_license Suspend a license kahunacart.licenses
kahunacart_licenses_reinstate_license Reinstate a license kahunacart.licenses
kahunacart_licenses_revoke_license Revoke a license kahunacart.licenses
kahunacart_licenses_regenerate_license Regenerate a license key kahunacart.licenses
kahunacart_licenses_free_license_seat Free an activation seat kahunacart.licenses
kahunacart_licenses_list_license_rules List licensing rules kahunacart.licenses
kahunacart_licenses_save_product_rule Write a product's licensing rule kahunacart.licenses
kahunacart_licenses_save_variant_rule Write a variant's licensing rule kahunacart.licenses
kahunacart_licenses_delete_product_rule Delete a product's licensing rule kahunacart.licenses
kahunacart_licenses_delete_variant_rule Delete a variant's licensing rule kahunacart.licenses
kahunacart_licenses_get_license_settings Read the licensing settings kahunacart.licenses
kahunacart_licenses_update_license_settings Save the licensing settings kahunacart.licenses
kahunacart_licenses_rotate_signing_secret Rotate the response signing secret kahunacart.licenses
kahunacart_licenses_validate_key Validate a license key none (public route)
kahunacart_licenses_check_updates Check an updates entitlement none (public route)
kahunacart_licenses_latest_release Get the newest release a key may have none (public route)

Subscriptions

Subscriptions publishes 17 tools under the kahunacart_subscriptions_ prefix. Reads need kahunacart.subscriptions.view; writes need kahunacart.subscriptions.manage. A renewal is an ordinary order, so marking one paid is kahunacart_mark_order_paid from the base plugin.

Tool What it does Permission
kahunacart_subscriptions_list_plans List subscription plans kahunacart.subscriptions.view
kahunacart_subscriptions_get_plan Get a subscription plan kahunacart.subscriptions.view
kahunacart_subscriptions_list_plan_variants List variants a plan can sell kahunacart.subscriptions.view
kahunacart_subscriptions_create_plan Create a subscription plan kahunacart.subscriptions.manage
kahunacart_subscriptions_update_plan Update a subscription plan kahunacart.subscriptions.manage
kahunacart_subscriptions_delete_plan Delete a subscription plan kahunacart.subscriptions.manage
kahunacart_subscriptions_set_plan_provider_refs Set a plan's provider references kahunacart.subscriptions.manage
kahunacart_subscriptions_list_subscriptions List subscriptions kahunacart.subscriptions.view
kahunacart_subscriptions_get_subscription Get a subscription kahunacart.subscriptions.view
kahunacart_subscriptions_get_metrics Subscription metrics kahunacart.subscriptions.view
kahunacart_subscriptions_cancel_subscription Cancel a subscription kahunacart.subscriptions.manage
kahunacart_subscriptions_uncancel_subscription Undo a scheduled cancellation kahunacart.subscriptions.manage
kahunacart_subscriptions_pause_subscription Pause billing kahunacart.subscriptions.manage
kahunacart_subscriptions_resume_subscription Resume billing kahunacart.subscriptions.manage
kahunacart_subscriptions_extend_subscription Extend the current period kahunacart.subscriptions.manage
kahunacart_subscriptions_add_subscription_note Add a note to a subscription kahunacart.subscriptions.manage
kahunacart_subscriptions_resend_pay_link Resend a renewal pay link kahunacart.subscriptions.manage

Refresh after installing a plugin

Install or enable a plugin while the assistant is connected, then ask it to run refresh_plugin_tools. The tool re-reads every manifest and reconciles the tool list in place: new tools are added, changed ones are updated, and tools whose plugin was disabled or removed are dropped. It reports added, updated, removed and any manifest warnings, and the MCP client is told the tool list changed.

No restart of grav-mcp or your editor is involved. refresh_plugin_tools needs api.access and nothing more.

Note

refresh_plugin_tools still works when plugin tools were started with --plugin-tools none. It reports that they are disabled rather than loading them.

Write tools for your own plugin

Any Grav plugin can publish its own routes the same way. Drop an mcp.yaml in the plugin root, beside blueprints.yaml and permissions.yaml, and describe each route: a name, a description written for the model, the method and path, the permission the route enforces, and a JSON Schema for the arguments. The API plugin serves it and grav-mcp registers it, with no code written on either side.

YAML
version: 1
prefix: kahunacart          # optional; defaults to the plugin slug. Tool name = "{prefix}_{name}"
tools:
  - name: list_products
    title: List products
    description: >
      List catalog products with paging, search and status filters. Returns product rows with their
      variants and attributes. Use get_product for one product with everything on it.
    method: GET
    path: /kahunacart/products
    permission: kahunacart.products.manage
    annotations:
      readOnly: true
    input:
      type: object
      properties:
        q: { type: string, description: "Search title, slug or SKU" }
        status: { type: string, enum: [draft, published, archived] }
        page: { type: integer, minimum: 1, default: 1 }
        per_page: { type: integer, minimum: 1, maximum: 100, default: 20 }

  - name: update_product
    title: Update a product
    description: Change one or more fields of a product. Only the fields sent are changed.
    method: PATCH
    path: /kahunacart/products/{id}
    permission: kahunacart.products.manage
    annotations:
      idempotent: true
    input:
      type: object
      required: [id]
      properties:
        id: { type: integer, description: "Product id" }
        title: { type: string }
        status: { type: string, enum: [draft, published, archived] }

Annotations default by method: GET is read-only and idempotent, DELETE is destructive and idempotent, PUT and PATCH are idempotent, and POST is none of them. Setting a key overrides that one default. Only JSON bodies are supported; leave multipart routes out.

If your tool list depends on data rather than a file, add entries in code instead. The API plugin fires onApiMcpTools with an McpToolCollector under $event['tools'], and $event['tools']->add('<your-slug>', [...]) takes the same fields a manifest entry does. Manifest files are read first, so a name your own mcp.yaml already claimed wins.

The full format, the JSON Schema subset a manifest may use, and what GET /mcp/tools returns are documented in the API plugin README. A typo costs you that one tool, never the rest of the file: the endpoint reports it under warnings.

Troubleshooting

Symptom Cause Fix
No kahunacart_ tools in the assistant's list grav-mcp could not load plugin tools at startup, or they are switched off Read grav-mcp's stderr for the warning. Check that --plugin-tools is not none, and that a slug list includes kahunacart. Then run refresh_plugin_tools
Warning: Failed to load plugin tools: … Plugin tools need the 'api.access' permission. The key's account has no api.access, so GET /mcp/tools answered 401 or 403 Grant api.access to that account, or use a key from an account that has it. A key created with a scope list is capped to those scopes whatever the account may do
Warning: Failed to load plugin tools: this site's API plugin has no /mcp/tools endpoint… The API plugin predates 1.0.22 Update the API plugin, then run refresh_plugin_tools
Some kahunacart_ tools are there and others are not The key holds some kahunacart.* permissions and not others Grant the missing permission. The tool list is filtered per caller
A tool answers 403 The key lacks that permission, or the store has demo.readonly on and the tool writes Grant the permission, or turn demo.readonly off on a store that is not a demo
delete_attribute answers 409 Products still carry a value for that attribute. The message names the count Send force: true to delete the values too, or clear them first
No tools from a plugin you just enabled grav-mcp registered its tools at startup, before the plugin existed Run refresh_plugin_tools. Only enabled plugins are read