- PHP 69.9%
- TypeScript 22.7%
- Shell 6.8%
- CSS 0.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Security: - Bifrost now refuses an authorization request from a Bifrost client with a plain code challenge, or none, and registers its consumers with PKCE required; drush updatedb turns that on for clients registered before. Midgard no longer relies on the OAuth server's PKCE settings. Also: - Bifrost runs on drupal/mcp_server 2.0.0-beta6, pinned to that exact release, and declares mcp/sdk ^0.8. The two known issues of alpha3 are fixed there: a tool that loads entities answers as plain JSON, and a client of revision 2026-07-28 is served. - Requires Config Guardian 1.1, which rolls back through Drupal core's configuration importer. Eir scores an import on the changes the importer applies, leaves Midgard's own rollbacks and their previews alone, and lists what Drupal also changed during a rollback. Heimdall records a recovery rollback it could not apply. - Requires e0ipso/simple_oauth_21 1.13.1 or later, which declares the packages the MCP Server OAuth companion depends on. By: andresmoreno28 |
||
| config | ||
| modules | ||
| recipes | ||
| src | ||
| tests | ||
| ui | ||
| .cspell.json | ||
| .eslintignore | ||
| .gitlab-ci.yml | ||
| .stylelintrc.json | ||
| CHANGELOG.md | ||
| composer.json | ||
| logo.png | ||
| midgard.info.yml | ||
| midgard.install | ||
| midgard.libraries.yml | ||
| midgard.links.menu.yml | ||
| midgard.module | ||
| midgard.permissions.yml | ||
| midgard.routing.yml | ||
| midgard.services.yml | ||
| phpcs.xml.dist | ||
| phpstan.neon.dist | ||
| README.md | ||
| SECURITY.md | ||
Midgard
Turn Drupal 11 into a safe, auditable AI operations layer.
What is Midgard
Midgard wires an AI Provider Layer (via drupal/ai), an MCP Bridge
(midgard_bifrost), and an HMAC-chained Audit Ledger (midgard_heimdall)
into a coherent whole on top of Drupal 11. The security primitive is simple:
the recipe declares config structure, the drush command injects secrets at
runtime from your process environment — the key never enters Drupal config or
git. Three paths reach the same out-of-the-box state: the setup wizard at
/admin/midgard/setup, the midgard_quickstart recipe, or
drush midgard:install.
Where it fits: Midgard is the governance layer for config-AI. As AI agents increasingly write real Drupal configuration, Midgard makes that writing safe — guarded, audited, and reversible. The output worth governing is config, not text: Midgard governs the agents that mutate your site rather than competing to generate the changes, and it complements the official governance work in core rather than replacing it.
Prerequisites
- Drupal core 11.3+
- PHP 8.3+
- Composer 2.7+
- Drush 13.7+
- DDEV (recommended for local development; production uses your hosting's standard tooling)
Drupal compatibility
Midgard targets Drupal 11. Its own code is already Drupal 12-clean —
upgrade_status reports zero deprecated API calls across the suite — and it
will declare ^12 as soon as its AI and OAuth dependencies (drupal/ai,
drupal/simple_oauth, drupal/mcp_server, drupal/key) do. Declaring
Drupal 12 support before then would be inaccurate, since Composer could not
resolve those dependencies on Drupal 12 yet. Drupal 11 receives upstream
support well into 2027.
Install
Midgard is a standard Drupal contrib module, currently in alpha. Its MCP
bridge runs on MCP Server 2.0.0-beta6 and the MCP Server OAuth companion
1.0.0-alpha1, pinned to those exact releases until MCP Server 2.0 is stable.
A project with the default minimum-stability: stable therefore needs a
stability flag for the three packages:
# In a Drupal 11 site (DDEV recommended for local dev):
ddev composer require 'drupal/midgard:^1.0@alpha' \
'drupal/mcp_server:2.0.0-beta6@beta' \
'drupal/mcp_server_oauth:1.0.0-alpha1@alpha'
ddev drush en midgard -y
A plain composer require drupal/midgard stops with "Could not find a
version of package drupal/midgard matching your minimum-stability (stable)",
and drupal/midgard:^1.0@alpha alone stops on drupal/mcp_server.
If you are starting from scratch, create a standard Drupal 11 project first:
composer create-project drupal/recommended-project my-site && cd my-site
ddev config --auto && ddev start
ddev composer require 'drupal/midgard:^1.0@alpha' \
'drupal/mcp_server:2.0.0-beta6@beta' \
'drupal/mcp_server_oauth:1.0.0-alpha1@alpha'
ddev drush site:install standard --yes
ddev drush en midgard -y
Roadmap:
composer create-project drupal/midgard-project <dir>will provide a pre-wired project scaffold oncedrupal/midgard-projectships (per ADR-023). That package does not exist yet.
Set your API key
Put the key in .ddev/config.local.yaml (create the file if it does not
exist), then run ddev restart:
web_environment:
- MIDGARD_ANTHROPIC_API_KEY=sk-ant-...
Do not use ddev config --web-environment-add for the key: it writes into
.ddev/config.yaml, which DDEV projects normally commit. The
.ddev/.gitignore that DDEV generates ignores config.local.yaml, but DDEV
only writes that file on the project's first ddev start, so start the
project once before adding the key (or check git status afterwards).
Scope the key to a single workspace when you create it (the default workspace
is fine). A key that is not tied to one workspace needs an
anthropic-workspace-id header on every request, and the Anthropic provider
for drupal/ai does not send one.
The key never enters Drupal config or git. drush midgard:install reads
this env var at runtime via MidgardApiKeyResolver. There is no
--api-key= flag; presence of the env var does not imply validity —
the doctor's secret.key_resolves check verifies that.
Configure
ddev drush midgard:install --provider=anthropic --no-interaction
This runs a sequence of steps: pre-flight checks, applies the
midgard_quickstart recipe, provisions the Key entity, populates AI routing
for three tiers (flagship / workhorse / economy), assigns Heimdall permissions
to the administrator role, and runs the doctor. Expect ~30 seconds on a
fresh site.
Verify
ddev drush midgard:doctor
# Expected on a clean install: zero blockers and two by-design warnings
# (bifrost.client_count — no demo client; secret.heimdall_hmac — lazy on first approval).
The two expected warnings are by design:
bifrost.client_count— no implicit Bifrost OAuth client; opt in with--create-demo-clientat install time.secret.heimdall_hmac— the HMAC approval secret is lazy-initialized on first approval token issuance.
Try it
ddev launch /admin/midgard
The Midgard Hall renders. The setup wizard at /admin/midgard/setup is
the GUI alternative to drush midgard:install and reaches the same state.
Gate config imports (Eir)
Heimdall guards configuration changes made at runtime. A drush config:import
from config/sync is a different path: the importer applies the whole
changelist and Heimdall can only record those saves after the fact. The
optional midgard_eir submodule puts a gate in front of that path — the
drush command and the config sync UI alike.
ddev drush en midgard_eir -y
Before an import applies anything, Eir hashes the full staged content, gives
the changelist a risk score and takes a snapshot. Below the write threshold
(threshold_write in midgard.heimdall.settings, 45 by default) the import
goes through and gets a chained audit row. At or above it the whole import is
refused, nothing is applied, and a pending entry waits for a decision:
ddev drush midgard:imports:list # pending and approved imports
ddev drush midgard:imports:approve <hash> # single-use approval of that changelist
ddev drush config:import -y # the approved changelist now goes through
ddev drush midgard:imports:deny <hash> # or refuse it
ddev drush midgard:imports:rollback <hash> --dry-run # what a rollback would restore
ddev drush midgard:imports:rollback <hash> # restore the pre-import values
An approval covers the exact changelist that was scored: if any staged value
changes, it is a different changelist and it is scored again. It is also
single-use and short-lived: approval_ttl_seconds in midgard.heimdall.settings
(30 minutes by default). An approval that expires unused is recorded as
expired, and the next import is scored and paused again. Two limits in this
release: the gate is all-or-nothing per import (not per item), and a rollback
restores configuration the import changed but does not delete configuration it
created. With the module disabled, config import behaves exactly as in core.
Next steps
- Design rationale: the architecture and ADRs (ADR-020 through ADR-024) live
in
specs/in the development repository — see the issue queue for questions. - Selective installs: the 5 recipes in
recipes/let you apply only the layers you need (e.g.midgard_corewithout Bifrost). - Teardown:
drush midgard:uninstall --help— preserves secrets by default; 4 opt-in purge flags (--purge-secrets,--purge-keys,--purge-clients,--purge-audit-entries) for nuclear cleanup. - GUI configuration changes: the wizard at
/admin/midgard/setup.
Similar projects and how they differ
Midgard occupies a specific niche: governance of config-AI — making the configuration that AI agents write to Drupal safe, auditable, and reversible. It doesn't compete with the projects below; it relates to them with three verbs. Midgard builds on the AI framework, governs the agents that mutate config, composes with the automation layer, and complements the official governance work. The thesis is that the output worth governing is config, not text.
drupal/ai — built on. drupal/ai is
the provider abstraction and AI framework. Midgard is a hard dependency on it,
not an alternative: the AI Provider Layer, MCP Bridge, and Audit Ledger all sit
on top of drupal/ai.
AI Agents (and its
agent family, e.g. the Views agent) — we govern what they generate. AI Agents
turns natural-language instructions into Drupal config and content changes via
tool-calling. Midgard doesn't generate those changes — it governs them: each
write passes through the guard, lands a tamper-evident audit row, and stays
reversible via config_guardian snapshot/rollback. AI Agents is the producer;
Midgard is the safety net.
AI Generation — we govern what it writes to disk. AI Generation creates real Drupal config, modules, and recipes from a prompt. It's the most literal expression of "the output is config": where it writes configuration, Midgard makes that write guarded, audited, and reversible. Generation and governance are complementary halves.
Apex AI — a different axis. Apex AI is a multi-provider orchestration engine: it governs the model calls (routing, cost, middleware). Midgard governs the config mutations an AI makes. Both use the word "governance"; they govern different things, at different layers.
ECA — we compose with it. ECA is the no-code automation substrate (events-conditions-actions). Midgard composes with it rather than replacing it — the "output is config" path can emit ECA models, and Midgard governs the resulting changes.
MCP Server — Bifrost rides on
it. MCP Server exposes Drupal over the Model Context Protocol with OAuth 2.1.
Midgard's midgard_bifrost submodule builds on MCP Server behind a small
wrapper firewall and adds allowlisted, audited, reversible tool execution. Midgard's
contribution is the governance layer over MCP — not the transport itself.
Context Control Center — Muninn will bridge it. CCC governs the context an agent sees (a managed, moderated context library). Midgard governs the changes an agent makes. They're adjacent layers of the same safety story; a future Midgard submodule (Muninn) will bridge CCC.
Workspaces (the official governance work) — we complement, not compete. Tag1 is extending Workspaces — Drupal's staging/review/rollback layer — so AI agents inherit governed staging through interface contracts: the official, in-core trajectory. Midgard is the lightweight, install-now, config-AI-specific option that fills the gap today and is designed to interoperate with those contracts as they land. It is explicitly not a competitor to core.
Midgard is pre-1.0 and not yet covered by a Drupal security advisory — see
SECURITY.md and CHANGELOG.md for maturity.
The comparison above is about approach, not maturity.
AI disclosure
Midgard was built with AI-assisted tooling — used for code generation, test scaffolding, and documentation drafting. Every change has been reviewed, tested, and validated by the maintainer before it landed: the suite ships a comprehensive automated test set across unit, kernel, and functional layers, and the full DrupalCI pipeline (phpcs, PHPStan level 6, PHPUnit, plus spell/style/lint checks) must pass before any release. AI accelerated the work; responsibility for the code's correctness, security model, and design rests entirely with the maintainer.