Grav + AI: The MCP Server

A first-party AI integration that gives agentic AI the same access to your Grav site that you have

11 mins

I've been running my own Grav sites through an AI agent for a few months now, and the thing that kept nagging me was how every other CMS handles AI. It's always bolted on. A chatbot in the corner, an autocomplete button in the editor, a "summarize this post" feature that ships your content off to someone else's API and hopes for the best. None of it can actually do much, because the admin underneath was only ever built for a person clicking around in a browser.

I wanted the opposite. Let an AI do anything I can do in the Grav admin, through the exact same door I walk through. That's what the MCP server is.

If you've read the last two posts you can already see how this fits. The Grav API is the one interface to a Grav site. Admin Next is a client that talks to it. The MCP server is just another client. Admin Next is for humans, the MCP server is for AI, and both go through the same API with the same keys and the same permission checks. There's no special AI mode and no separate code path. An AI agent gets exactly the access a logged-in user would get, and you control it the same way you control any user.

What MCP Actually Is

MCP is the Model Context Protocol. It's an open standard that started at Anthropic for letting AI agents talk to outside tools and services, and it has spread well beyond Claude since. Most AI clients speak it now.

The simplest way to think about it: MCP lets an AI use software the way you'd use an admin panel. The server hands the AI a catalog of tools it can call, resources it can read, and prompts for common multi-step jobs. Each tool has a name, a description, and a typed set of parameters. The AI picks one, the server runs it, and the result comes back. The AI never sees the file-based PHP CMS on the other end. It just sees a list of things it's allowed to do, and every one of them maps to a real, permission-checked API call.

How the Grav MCP Server Fits Together

First, the thing everyone gets wrong: the Grav MCP server is not a Grav plugin. It's a small Node.js program published as the grav-mcp package on npm, and it runs inside your AI client (Claude Code, Claude Desktop, Cursor, whatever speaks MCP). It does not run inside Grav.

Here's the round trip:

  1. Your AI client launches the grav-mcp server as a subprocess (or connects to it over HTTP).
  2. The MCP server talks to your Grav site over HTTP, hitting the first-party Grav API plugin at /api/v1.
  3. Every request carries a Grav-issued API key in an X-API-Key header, so the API plugin authenticates and permission-checks it just like any other API consumer.
  4. The API plugin does the real work against your files and hands structured JSON back up the chain.

So the only thing that ever touches your content is the same API plugin that already powers Admin Next. The MCP server is a translator. It turns the AI's tool calls into authenticated HTTP requests, and turns the JSON that comes back into something the model can read. Your site has no idea the client on the other end is an AI, and it doesn't need to.

There are two ways to run it. stdio is the default, and it's what local clients like Claude Code and Claude Desktop use: the client launches the server itself. There's also an HTTP transport (--transport http --port 3100) for when you want one long-running server that several clients connect to, like a remote or shared setup.

Getting It Running

Setup takes about five minutes. Generate a key on the Grav side, then point your client at the server.

Step 1: Generate an API key

On your Grav site, with the API plugin installed and enabled, generate a key for the user the AI should act as:

bin/plugin api keys:generate --user=admin --name="Claude MCP"

That prints a key starting with grav_. Copy it right away, it's only shown once. The --name is just a label for your own benefit, and --expiry=30 makes the key expire after that many days. bin/plugin api keys:list --user=admin and bin/plugin api keys:revoke --user=admin handle the rest.

One thing to get right from the start: the key acts as that user. Generate it for admin and the AI can do everything admin can do. Generate it for a user with fewer permissions and the AI is boxed in to match. More on that further down, but pick the user on purpose.

Step 2: Point your AI client at it

For Claude Code, Claude Desktop, or any client that uses the standard MCP config format, add a server entry:

{
  "mcpServers": {
    "grav": {
      "command": "npx",
      "args": ["-y", "grav-mcp"],
      "env": {
        "GRAV_API_URL": "https://mysite.com/api",
        "GRAV_API_KEY": "grav_your_api_key_here"
      }
    }
  }
}

That's it. npx fetches and runs the package, the two environment variables tell it where your site is and how to get in, and the toolset shows up the next time your client starts. There's also an optional GRAV_ENVIRONMENT variable if you want config changes to land in a specific user/env/* target, which is handy when you're aiming the AI at staging.

You need Node.js 18+ for the server, and that's the only local requirement. If you'd rather run it directly to test it, the env vars work on the command line too:

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

What the AI Can Actually Do

This is where being first-party pays off. The server doesn't expose some thin slice of Grav. It gives the AI 70 tools across 11 domains, and they're real operations, not raw REST endpoints. Things like create_page, compare_translations, and upgrade_grav, named so the model knows what they do.

The breakdown:

Content and structure
  • Pages (10): list_pages, get_page, create_page, update_page, delete_page, move_page, copy_page, reorder_pages, plus batch_pages and reorganize_pages for atomic multi-page operations. The AI can build out and reshape an entire content tree, not just edit one page at a time.
  • Multilingual (5): list_languages, get_page_translations, create_translation, adopt_page_language, and compare_translations for managing translated content and seeing where each language stands.
  • Media (8): upload, list, and delete both page-level and site-level media (upload_page_media, list_site_media, create_media_folder, and the rest).
Configuration and users
  • Configuration (3): list_config_scopes, get_config, update_config, with ETag-based optimistic concurrency so a config write knows if something changed underneath it, and per-environment overrides.
  • Users (6): full user CRUD plus manage_api_keys.
Site operations
  • Package manager (9): search_packages, install_package, update_package, update_all_packages, remove_package, and upgrade_grav. The AI can install a plugin, resolve its dependencies, and upgrade core.
  • System (10): get_system_info, clear_cache, get_logs, create_backup, list_backups, the scheduler controls, and environment management.
  • Dashboard and reports (7): get_dashboard_stats, run_reports, notifications, and dashboard widget/layout management.
  • Webhooks (4): list_webhooks, manage_webhook, test_webhook, and get_webhook_deliveries to inspect delivery logs.
Discovery
  • Blueprints and schema (6): get_blueprint, list_page_templates, get_taxonomy, get_permissions, and blueprint file management, so the AI can read the actual shape of your content before it tries to write any.
  • Plugin discovery (2): discover_plugins and plugin_action, which let the AI find admin features that other plugins contribute and trigger their actions.

On top of the tools there are 5 resources the AI can read for context (grav://system/info, grav://user/permissions, grav://languages, grav://templates, and grav://taxonomy), and 6 workflow prompts that wrap common multi-step jobs into a single guided flow: create_blog_post, translate_page, site_health_check, content_audit, plugin_setup, and bulk_update.

The schema and discovery tools matter more than they look. The AI can read a page's blueprint and the site's taxonomy before it writes anything, so it isn't guessing at your frontmatter. It calls get_blueprint, sees that your blog template wants a hero_image and a list of tags, and writes content that actually fits, instead of inventing fields you don't have.

Why This Matters

What's it good for? Depends on who you are.

If you run a site, the easy stuff is obvious: ask for a new blog post with the right tags and a featured image, rearrange your navigation, change a setting without digging through forms. The real payoff is the tedious work you never get around to. "Go through every post from 2023, add a legacy tag, and fix the three authors whose names are misspelled" is half a day of clicking in the old admin, or one sentence to an agent that can call batch_pages and reorganize_pages. Bulk reorganizations, content migrations, a consistent formatting pass across hundreds of pages: miserable by hand, easy for the AI.

If you're a developer, it's an automation surface. Scaffold a site, spin up a realistic page tree to test a theme against, drive content workflows from a script, wire it into CI to prep a staging site. It's the same API underneath, so anything you'd do through the API you can now hand off in plain language.

If you run an agency, it scales across clients. Same workflows, same content standards, same setup steps across a whole fleet of Grav sites, with the boring admin work handled so your people spend their time on the things that need a person.

Here's a real exchange. You say: "create a blog post announcing our spring sale, pull a hero image from the media library, tag it sales and announcements, and publish it next Monday." The agent reads the blog blueprint, calls list_site_media to find an image, calls create_page with the right frontmatter and body, and tells you the route it made. Or: "move the API reference under its own parent page in the docs section." It lists the affected pages, creates the parent, and moves the children over in one atomic step. You watch it happen in the chat, and the changes are sitting in Grav when it's done.

Security and Permissions

This is the part people get nervous about, and fair enough. "Give an AI full access to my site" sounds alarming. So here's exactly how it works.

The MCP server has no special powers. It logs in as a Grav user, with an API key, and it gets that user's permissions. Nothing more. The same api.pages.write permission that lets a person edit pages is the one that lets the AI edit pages. There's no separate AI permission system to worry about. You control what the AI can do the same way you control any user: by choosing whose key it holds.

So the advice is simple. Don't reach for an admin super-user key out of habit. Make a user with just the permissions the job needs, generate the key for that user, and the agent can't get past it. Want an AI that writes and edits content but can't touch config or install plugins? Give a user page and media permissions, nothing else. The key can never do more than the user can.

Two more things worth knowing:

  • Concurrency is safe. Every write goes through the API's ETag-based optimistic concurrency. Say the AI fetches a page, then you edit and save that same page in Admin Next a moment later, and then the AI tries to write its version. The API returns a 409 Conflict instead of quietly clobbering your change, and the agent has to re-fetch and reconcile. Exactly what you'd want a careful human client to do.
  • Changes are observable. Every write through the API fires a structured event (onApiPageCreated, onApiConfigUpdated, and so on), and those events feed the webhook system. Want a record of what the AI did? Point a webhook at your logging or notification target and every AI-initiated change lands there, the same as any other API change. The deliveries are HMAC-signed, so you can verify they really came from your site.

None of this is AI-specific. It's the same auth, the same permissions, the same concurrency checks, and the same events the rest of Grav 2.0 already uses. The AI is just one more well-behaved client.

The Bigger Picture

I keep landing on the same point: this works because the API is the one interface, and any client can use it. We didn't build an "AI feature." We built an API, and an AI happens to be one of the things that can talk to it. The MCP server itself is maybe 2,000 lines of TypeScript sitting between two existing standards. The hard part, the actual content operations with real permissions and real safety, was already finished before we started on it.

That's why this doesn't feel like a gimmick to me. It's just what you get when managing a site is something you do to it through a clean interface, instead of something glued to one particular browser admin. As AI agents get better, Grav gets more out of them for free. The tools are already there. The agent just keeps getting smarter about using them.

Admin Next for humans, the MCP server for AI, the same API for both. I think that's going to hold up well.

Quick Links and Help

If you try this on a real site, I'd genuinely love to hear what you point it at. Some of the things people are already running it through are well beyond anything I had in mind when we started.

Andy


Next in the series: Migrating to Grav 2.0

Grav Premium
Turbo-charge your Grav site - from the creators of Grav