Skip to main content
Glama

drupal-mcp

MCP server for Drupal sites via the core JSON:API. List, search, create, update, and delete nodes / taxonomy terms / users on any Drupal 10/11 site with the jsonapi module enabled.

A standalone MCP server: node drupal-mcp.js with the env vars set. Plug it into Claude Code, Claude Desktop, Cline, or any other MCP-compatible client.

(It was also published as a Claude Code plugin through the lucaspretti-plugins marketplace until 2026-07-10, and that marketplace was archived on 2026-09-23. Register the server directly, as below.)

Why JSON:API and not the older Drupal MCP module?

The contrib drupal/mcp module currently has ~250 installs and is in flux ("merging with the MCP Server module"). JSON:API is in Drupal core, stable, and standard. This server is a thin wrapper around endpoints your site already exposes — no new module to maintain on the Drupal side.

Related MCP server: drupalorg-mcp

Drupal-side setup (one-time)

Required modules

drush en jsonapi serialization basic_auth -y
  • jsonapi — exposes /jsonapi/* endpoints. Core module.

  • serialization — JSON:API dependency. Core module.

  • basic_auth — required for Authorization: Basic header authentication. Core module, not enabled by default. Without it, only anonymous reads work; every write returns 401.

JSON:API writes

By default JSON:API is read-only. If you need create / update / delete, flip the switch:

drush config:set jsonapi.settings read_only false -y

Or in config/sync/jsonapi.settings.yml (CMI-managed sites):

read_only: false

Keep read_only: true for read-only deployments — the plugin still works for drupal_list_* / drupal_get_node / drupal_query_jsonapi.

Bot role + user

Two patterns, pick one based on how much you trust the bot:

A) Admin role (simplest, recommended for full-access bots). Setting is_admin: true bypasses every permission check, same as the default administrator role. The single setting + a strong password gates all access.

langcode: en
status: true
dependencies: {}
id: mcp_bot
label: 'MCP bot'
weight: 10
is_admin: true
permissions: {}

B) Scoped role (when you want explicit limits). List exactly the perms the bot may use. Note that administer nodes alone does not grant create / edit / delete on bundles — those need either bundle-specific perms ('create article content', 'delete any page content', etc.) or 'bypass node access'.

is_admin: false
permissions:
  - 'access content'
  - 'access user profiles'
  - 'bypass node access'        # or per-bundle perms
  - 'administer taxonomy'
  - 'view own unpublished content'

Then create the user:

drush user:create mcp_bot --password='<strong-pw>'
drush user:role:add mcp_bot mcp_bot

Store the password in your secrets manager.

Install

The server needs these env vars (via shell, .env, or your secrets manager):

DRUPAL_BASE_URL=https://your-site.example.com
DRUPAL_USER=mcp_bot
DRUPAL_PASSWORD=••••••••

Clone and install once (the last line is a quick standalone check):

git clone https://github.com/lucaspretti/drupal-mcp.git
cd drupal-mcp
npm install
cp .env.example .env  # fill in
node drupal-mcp.js

Claude Code

claude mcp add drupal -s user \
  -e DRUPAL_BASE_URL=https://your-site.example.com \
  -e DRUPAL_USER=mcp_bot \
  -e DRUPAL_PASSWORD='<strong-pw>' \
  -- node /absolute/path/to/drupal-mcp/drupal-mcp.js

Other MCP clients

In the client's config (Claude Desktop claude_desktop_config.json, Cline, etc.):

{
  "mcpServers": {
    "drupal": {
      "command": "node",
      "args": ["/absolute/path/to/drupal-mcp/drupal-mcp.js"],
      "env": {
        "DRUPAL_BASE_URL": "https://your-site.example.com",
        "DRUPAL_USER": "mcp_bot",
        "DRUPAL_PASSWORD": "••••••••"
      }
    }
  }
}

Tools

Tool

What it does

drupal_list_nodes

List nodes of a bundle, with filter / sort / paginate

drupal_get_node

Fetch one node by UUID

drupal_create_node

Create a node (POST)

drupal_update_node

Patch attributes / relationships

drupal_delete_node

Delete by UUID (irreversible)

drupal_list_taxonomy_terms

List terms in a vocabulary

drupal_list_users

List users

drupal_query_jsonapi

Arbitrary GET against /jsonapi/* (escape hatch)

Filter shorthand: { field_category: '<uuid>' } → filter[field_category]=<uuid>. Use { field: { value, operator } } for non-equality operators.

CLI flags

Each env var has an equivalent flag, useful when running outside an .env-aware shell:

node drupal-mcp.js \
  --base-url=https://your-site.example.com \
  --user=mcp_bot \
  --password=•••• \
  --jsonapi-prefix=/jsonapi \
  --timeout=30000

node drupal-mcp.js --help for the full list.

Troubleshooting

Error: fetch failed (cause: getaddrinfo ENOTFOUND <host>${var_name})

Claude Code's ${VAR} interpolation in .mcp.json substitutes from process.env, not from the settings.json env block alone. When a referenced var is unset upstream, the literal string ${var_name} is passed to the spawned process and concatenated into the URL.

Fix: ensure every var in .mcp.json env is also set in ~/.claude/settings.json (env block) or your shell environment. Or remove optional vars from .mcp.json and rely on the script's defaults.

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '...zod-to-json-schema/dist/esm/index.js'

The @modelcontextprotocol/sdk postinstall race occasionally leaves zod-to-json-schema half-built. Reinstall:

cd /absolute/path/to/drupal-mcp
rm -rf node_modules package-lock.json && npm install

401 Unauthorized on every request

  • The basic_auth core module is not enabled. drush en basic_auth -y.

  • Or the password is wrong / the user is blocked.

403 Forbidden on a specific bundle

The bot role lacks the permission for that bundle. Either grant 'create <bundle> content' / 'edit any <bundle> content' / 'delete any <bundle> content' per bundle, or grant 'bypass node access' for blanket node access, or set is_admin: true on the role for full-trust service accounts.

Note that administer nodes alone is NOT enough — it grants the admin UI but not the per-content-type CRUD permissions.

405 Method Not Allowed on writes

jsonapi.settings.read_only is still true. See "JSON:API writes" above.

301 redirects to a language-prefixed URL

If the language module is enabled, /jsonapi/... redirects to /<langcode>/jsonapi/.... Node's fetch follows automatically; curl needs -L. Nothing to fix on the server side.

Authentication

The plugin currently uses HTTP Basic auth (basic_auth core module). This is the simplest path that works out of the box on any Drupal site.

For a small / single-tenant deployment with a dedicated bot user and HTTPS-only, Basic auth is acceptable. For production or multi-integration setups, OAuth2 via simple_oauth is the correct choice for service accounts:

Basic auth (current)

OAuth2 client_credentials (roadmap)

Drupal module

basic_auth (core)

simple_oauth (contrib)

Wire format

Authorization: Basic <base64(user:pass)> on every request

Authorization: Bearer <token>, token cached + refreshed

Revocation

Change user password (affects UI login too)

Revoke token / consumer atomically

Scopes

None (role permissions only)

Per-token scopes

Setup

Enable module, create user

Install module, generate RSA keys, create consumer

OAuth2 support is planned as an opt-in mode (DRUPAL_AUTH_MODE=oauth + DRUPAL_OAUTH_CLIENT_ID / _SECRET / _TOKEN_URL). Until then, treat the bot password as you would any service-account credential: store it in a secrets manager, scope the role tightly, and rotate periodically.

Security notes

  • HTTPS only. Basic auth means the bot password travels on every request — don't run against http://.

  • The Drupal-side bot user's role is the security boundary. Keep it scoped to the bundles and operations you actually need.

  • JSON:API respects field access, but not entity-access-bypass — be careful with admin-bypass perms on the bot role.

  • .env is gitignored. Don't commit credentials.

License

MIT

Available Tools

8 tools
drupal_create_nodeA

Create a new node. Pass attributes (and optional relationships) using JSON:API field names.

Inputs: bundle (required), attributes (required), relationships.

drupal_delete_nodeA

Delete a node by bundle + UUID. Irreversible.

Inputs: bundle (required), uuid (required).

drupal_get_nodeA

Fetch a single node by bundle + UUID.

Inputs: bundle (required), uuid (required).

drupal_list_nodesA

List nodes of a given bundle (content type). Returns a paginated set of nodes with their attributes flattened. Default sort is by created date desc.

Inputs: bundle (required), filter, sort, limit, +3 more.

drupal_list_taxonomy_termsB

List terms in a vocabulary (e.g. "category", "tags"). Default sort is "weight".

Inputs: vocabulary (required), filter, sort, limit.

drupal_list_usersC

List Drupal users. Requires the configured account to have permission to view users.

Inputs: filter, sort, limit.

drupal_query_jsonapiA

Escape hatch: arbitrary GET against the JSON:API. Use when the higher-level tools do not cover what you need (custom resources, /jsonapi/index, etc.).

Inputs: path (required), query.

drupal_update_nodeB

Patch an existing node by UUID. Pass only the attributes / relationships you want to change.

Inputs: bundle (required), uuid (required), attributes, relationships.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.6/5.0

    Scored across 8 tools

    Disambiguation5/5

    Each tool targets a distinct Drupal resource or action: node CRUD, taxonomy listing, user listing, and a general query fallback. No overlap or ambiguity.

    Naming Consistency5/5

    All tools follow the consistent 'drupal_verb_noun' pattern in snake_case, e.g., drupal_create_node, drupal_list_users, drupal_query_jsonapi.

    Tool Count5/5

    8 tools is a well-scoped set for a Drupal MCP server, covering core node operations, taxonomy, users, and an escape hatch, without being overwhelming.

    Completeness3/5

    Node CRUD is complete, but taxonomy and user operations are limited to listing only, missing create/update/delete. The generic query tool mitigates gaps but does not fully compensate.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      MCP server for drupal.org's public REST API. Enables querying projects, issues, comments, and user information on drupal.org.
      10 npm
      GPL 2.0
    • A
      license
      Not graded
      quality
      C
      maintenance
      MCP server for WordPress content management via REST API, supporting posts, pages, media, comments, and terms through natural language interfaces like Cursor, ChatGPT, Codex, and Claude.
      18 npm
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      An MCP server for Sitecore Personalize / CDP that exposes decisioning flows, experiences, audiences, guest profiles, events, and datasets as tools, enabling natural-language management and interaction with Sitecore Personalize.
      17
      MIT