drupal / varbase_ai_mcp_server_base
Varbase AI MCP Server: an MCP server for external assistants (Claude.ai and similar) with OAuth 2.1 and a read-only starter tool set. Experimental. Opt-in: never applied by default.
Package info
git.drupalcode.org/project/varbase_ai_mcp_server_base.git
Type:drupal-recipe
pkg:composer/drupal/varbase_ai_mcp_server_base
Requires
- php: >=8.3
- drupal/consumers: ^1.24
- drupal/core: ^11.4
- drupal/mcp: 2.0.x-dev
- drupal/mcp_server: ^2.0@beta
- drupal/mcp_server_oauth: ^1.0@alpha
- drupal/mcp_server_tool_bridge: ^1.0@beta
- drupal/simple_oauth: ^6.1
- drupal/tool: ^1.0@beta
- drupal/tool_belt: ^1.0@alpha
- e0ipso/simple_oauth_21: ^1.13
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-22 16:36:36 UTC
README
Varbase AI MCP Server Base
An opt-in Drupal recipe that lets external assistants (Claude.ai, Claude Desktop, other MCP clients)
read a Varbase 11 site and draft blog posts and pages over the Model Context Protocol:
an MCP server on /mcp, OAuth 2.1, eight read tools and four draft-writing tools. Nothing it writes is ever published:
publishing stays a human action in Drupal.
Varbase never applies it by default.
Experimental. The whole stack is pre-stable (drupal/mcp 2.0.x-dev, MCP Server 2.0 beta, MCP Server OAuth 1.0
alpha, Tool Belt 1.0 alpha), so it is not covered by the Drupal security advisory policy. There is no custom PHP
and no custom shell code in the recipe. Its no-key checks run inline in CI, so the repository holds no PHP file at all.
What it adds
| Part | What it does |
|---|---|
| Modules | MCP Server, Tool Bridge, MCP Server OAuth, Simple OAuth 6 with Simple OAuth 2.1 (discovery metadata, PKCE, dynamic client registration), Tool API, Tool Belt (entity, content) and the drupal/mcp umbrella module. Each is justified in recipe.yml. |
| Scopes | mcp:read (read) and mcp:write (draft) allow the authorization-code, refresh-token and client-credentials grants, bounded by the mcp_agent role. mcp:publish exists with every grant disabled: no token can carry it. |
Role mcp_agent | access mcp server, access content, grant simple_oauth codes, use text format content_format, and drafts only: create and edit own blog and page content, view own unpublished content, and the create_new_draft transition of varbase_editorial_workflow. No delete, review, publish or archive. |
| Eight read tools | entity_type_list, entity_bundle_field_definitions, entity_field_value_definitions, entity_metadata (explain the site) and entity_list, entity_load_by_id, entity_load_by_property, entity_field_values (read content). Each requires mcp:read. |
| Four draft tools | entity_stub, entity_revision_add, field_set_value, entity_save. Each requires mcp:write. On the wire every tool is tool_api__tool_belt_<name>. |
| PKCE settings | Mandatory, S256 only (OAuth 2.1 drops plain). See Known issues for what this does and does not change. |
| Server instructions | Which tools to start with; every write is a draft or a new non-default revision; show the user a diff before saving; never publish. |
What an assistant can do
- Read (
mcp:read): explain the content model and read content the person may see. - Draft (
mcp:write): create a blog post or page (entity_stub->field_set_value->entity_save), saved unpublished in thedraftstate; or edit one the person owns (entity_load_by_id->entity_revision_add->field_set_value, withmoderation_statedraft->entity_save), which adds a pending revision and leaves the live version unchanged. - Never: publish, archive, delete, or change the content model.
The safety model
Four independent layers, each enough on its own to stop a publish:
- No publish scope.
mcp:publishhas every grant disabled; a token request for it answersinvalid_scope. - No publish permission.
mcp_agenthas only thecreate_new_drafttransition, so settingmoderation_statetopublishedfails validation. - The publish guard (
drupal/mcp): over MCP, saving a published entity withoutmcp:publishis refused, and new content is kept unpublished. - No dangerous tool.
entity_delete,entity_createandentity_update(one-shot writes that skip the stub and revision steps), the schema tools andmcp_publish_contentare not exposed.
The role bounds every token, so an assistant never gets more than both the person and mcp_agent allow. A person
reviews and publishes drafts in Drupal as usual.
Scope names
The recipe keeps drupal/mcp's scope names, because its scope enforcement and drush mcp:client:create know only
those. Sites that used project-specific scopes map like this:
| Project-specific scope | Here |
|---|---|
| a content read scope plus a "connect" scope | mcp:read (connecting is the access mcp server permission on mcp_agent) |
| a content write scope | mcp:write, drafts only |
| Canvas read or build scopes | not covered: no Canvas tools are exposed |
Search over the vector index (content_vector, from Varbase AI Search Base)
and a site status tool are not part of this version.
Why OAuth 2.1
See docs/decisions/0001. In short: each person connects their own assistant and approves it once, tokens expire in minutes, one client can be revoked alone, and a token never carries more than the person behind it. A shared site login (Basic auth) has none of that.
No dependency on Varbase AI Base
MCP does not use the AI module, a provider or a key, so the recipe stands alone, like Varbase AI Content Review Base. It works on any Drupal 11.4 site; CI builds Varbase Starter.
Requirements
- Drupal core
^11.4, PHP 8.3 or later, Drush 13 drupal/mcp2.0.x-devand the stack it requires (composer.jsonlists them). The root project must allow alpha packages; Varbase projects already useminimum-stability: devwithprefer-stable.- The PHP
opensslandsodiumextensions, for the OAuth signing keys.
Install and apply
ddev composer config --json extra.drupal-recipe-unpack.ignore '["drupal/varbase_ai_mcp_server_base"]'
ddev composer require drupal/varbase_ai_mcp_server_base:1.0.x-dev
# Rebuild first: the module list is cached from before the require, and the apply
# stops with "is not a known module" without it.
ddev drush cr
ddev drush recipe ../recipes/varbase_ai_mcp_server_base
ddev drush cr
When the project already ignores other packages, add to the unpack list rather
than replacing it. Re-applying is safe. On a site that applied 1.0.x, re-applying turns the mcp:write grants on and
adds the draft permissions and tools. The blog and page content types and varbase_editorial_workflow must
exist (Varbase 11 has them).
After the apply
Keys and clients are runtime state and never belong in config or Git, so the recipe leaves them to you.
# 1. Signing keys, outside the docroot, never committed (add /keys/ to .gitignore).
# On a hosted site the directory must persist across deploys.
ddev exec mkdir -p /var/www/html/keys
ddev drush simple-oauth:generate-keys /var/www/html/keys
ddev drush config:set simple_oauth.settings public_key /var/www/html/keys/public.key -y
ddev drush config:set simple_oauth.settings private_key /var/www/html/keys/private.key -y
# 2. Give the MCP agent role to the people who may connect an assistant.
ddev drush user:role:add mcp_agent <username>
# 3. Check.
ddev drush mcp:status
People then add https://<site>/mcp as a custom connector in their assistant, log in to Drupal and approve. The
assistant registers itself through /oauth/register; no client needs to be created by hand.
A headless agent gets a client-credentials client (the secret is printed once). Give it mcp:read alone to
read, or mcp:read,mcp:write to draft; its service user needs the mcp_agent role:
ddev drush mcp:client:create reporting-agent --scopes=mcp:read --label="Reporting agent"
Do not run drush mcp:setup or drush en mcp on its own: setup creates agent clients and exposes the publish
tool. Never enable an mcp:publish grant.
Automated testing
GitLab CI on git.drupalcode.org (.gitlab-ci.yml) runs on every merge
request and every push to 1.0.x, like the other Varbase AI recipes: composer validate and a lint of every YAML
file.
The functional tests in tests/features (Playwright and Cucumber through
@vardot/varbase-e2e) run against a site of your own:
yarn install, then LAUNCH_URL=https://your-site.ddev.site yarn test.
Checked
By hand on 12 October 2026 on a fresh Varbase 11.0.x-dev build (DDEV, Drupal 11.4.x, PHP 8.4, drupal/mcp
2.0.x-dev ad2b2a0, MCP Server 2.0.0-beta5, Tool Bridge 1.0.0-beta3, MCP Server OAuth 1.0.0-alpha1, Simple OAuth
6.1.1, Simple OAuth 2.1 1.13.1, Tool Belt 1.0.0-alpha6):
- Applied twice, the recipe check PASS,
drush mcp:statusas expected (keys and the write scopes missing until the runtime steps). - The three discovery documents answer 200 JSON;
POST /mcpwithout a token answers 401 with a Bearer challenge pointing at/.well-known/oauth-protected-resource/mcp. - With a client-credentials token for
mcp:read:initializereturns the read-only instructions,tools/listreturns the six tools,entity_listreturns content, and an unexposed write tool answers "Tool not found". - Save path: with write tools exposed temporarily behind
mcp:write(removed afterwards), themcp:readtoken got HTTP 403insufficient_scopeonentity_stub,field_set_valueandentity_save, and no node was created. That enforcement comes from thedrupal/mcpmodule; without itmcp_server_oauth1.0.0-alpha1 enforces nothing on Tool Bridge tools.
Draft writes (1.0.4), by hand on 12 October 2026 on a Varbase 11.0.x-dev DDEV site that had 1.0.3 applied
(drupal/mcp 2.0.x-dev), applied twice over it, inline checks PASS, with temporary client-credentials clients:
- An
mcp:readtoken got HTTP 403insufficient_scopeonentity_stub. - An
mcp:writetoken created a blog post (stub,field_content, save) that ended unpublished indraft. - Editing a published post the user owns: saved without a state change, the publish guard refused it; with
moderation_statedraft, it saved a pending non-default revision and the live version stayed unchanged. - Setting
moderation_statetopublishedwas refused ("no access to transition from Draft to Published"). - A token request for
mcp:publishansweredinvalid_scope.
Not checked: a connection from Claude.ai itself, Educare, Horizon Aid and The Rightup, and Upsun (where the keys directory needs a writable mount).
Known issues
- PKCE
plainis still advertised.simple_oauth_server_metadata1.13.1 hardcodes["S256", "plain"]in the discovery documents, andsimple_oauth_pkcesettings enforce nothing by themselves (PKCE is demanded by each consumer'spkcefield, whichdrupal/mcpforces on for public clients). Whether the token endpoint refuses aplainchallenge was not tested. mcp_server_oauth1.0.0-alpha1 does not match Tool Bridge tool names, so its own scope check never fires.drupal/mcp's subscriber enforces the scopes instead, which is why themcpmodule is installed./oauth/registeris open to anonymous POSTs with no rate limit (see the decision record).- User 1 bypasses every scope. Connect with a real account.
- Access tokens last 300 seconds per client by default (the consumer's
access_token_expiration).
Related recipes
| Recipe | What it does |
|---|---|
| Varbase AI Search Base | A vector index of the site's content. |
| Varbase AI Content Review Base | AI review of content against the site's style guide. |
drupal/mcp recipes/agent_ready | The upstream read, write and publish setup this recipe trims to read and draft. |
