Meridian handbook
Integrating

Content MCP

How to connect an AI agent (Claude or any Model Context Protocol client) to a Meridian site: turning the endpoint on, OAuth sign-in and the consent choices, the mcp configuration block, registering clients, personal access tokens and engine-dmo mcp call for scripts, engine-dmo mcp over stdio for local development, the tools, resources and prompts, limits, and what to tell a data protection officer.

The content MCP lets a member of staff connect their own AI agent to a site, through the Model Context Protocol, so the agent can read content and write drafts as that person and never as more: it sees what they may see, writes where they may write, and publishes only where they could publish in the admin, through the same review workflow and checks. Everything it writes is recorded as written through an agent, for whom and by which client.

Meridian does not choose the model, send content to one through this channel, or see the agent’s reasoning: the person chooses the client, and the client its model. Meridian’s own AI assistance features are separate (see AI assistance).

There are two ways in:

Remote endpointengine-dmo mcp over stdio
ForStaff and their agents, on a running installA coding agent working on a project on your machine
Addresshttps://<admin host><admin_path>/mcpA child process the agent starts
Sign-inOAuth 2.1 with PKCE through the admin’s sign-in, or a personal access tokenNone: every call acts as the staff user --as names
Allowed inEvery environment, when mcp.enabledDevelopment; in staging only with dev.authoring: true; never in production

engine-cms serves the same MCP, with the toolsets of its own modules.

Turning it on

# config/engine.yaml
mcp:
  enabled: true
  personal_data: refuse     # the default

Then decide who may connect. The verb mcp.connect, judged per site, is held by the built-in Administrator and Editor roles and not by Translator or Contributor; move it in your roles like any permission. A person without it on a site cannot connect an agent for that site, and losing it takes the site out of their connections on the next call.

Before deciding, list what an agent can do on your bundle:

engine-dmo mcp tools --project .          # tools, resources and prompts, with their toolsets
engine-dmo mcp tools --json --project .   # with every tool's input and output schema

Connecting a client

The endpoint is a standard remote MCP server with OAuth discovery, so a client finds the sign-in by itself. Give it the endpoint’s address, such as https://studio.example.org/studio/mcp.

  • Claude Code: claude mcp add --transport http meridian https://studio.example.org/studio/mcp, then /mcp in a session to sign in. The browser opens the admin’s sign-in, then the consent screen.
  • Claude on the web or the desktop app: add a custom connector with the endpoint’s address; sign-in and consent open in the browser.
  • Any other client supporting the MCP authorisation flow (protocol revision 2025-06-18 or later; the endpoint answers 2026-07-28, 2025-11-25 and 2025-06-18).

The endpoint publishes its OAuth metadata at /.well-known/oauth-protected-resource<admin_path>/mcp and /.well-known/oauth-authorization-server<admin_path>/oauth.

The client does the whole sign-in itself, from the first refused call to the first tool call; the person sees only the admin’s sign-in and the consent screen. The diagram uses the default admin path, /studio:

sequenceDiagram
  participant C as MCP client
  participant B as Browser
  participant E as Engine
  C->>E: POST /studio/mcp (no token)
  E-->>C: 401, WWW-Authenticate: resource_metadata
  C->>E: GET the protected resource and server metadata
  E-->>C: authorize and token endpoints, S256 only
  C->>B: open /studio/oauth/authorize<br/>with code_challenge
  B->>E: authorize request
  E->>E: identify the client (metadata document,<br/>configured, or registered)
  E-->>B: admin sign-in and second factor, if needed
  E-->>B: consent screen
  B->>E: approve choices
  E-->>B: redirect to redirect_uri with a code
  B-->>C: code
  C->>E: POST /studio/oauth/token<br/>code and code_verifier
  E-->>C: access token (1 hour) and refresh token
  C->>E: POST /studio/mcp, Bearer token
  E-->>C: tool results, as the person

When the access token lapses the client exchanges its refresh token at the same token endpoint; refresh tokens rotate, and one used twice ends the connection.

How clients register

WayConfigurationConsent screen shows
Client metadata document (the client’s client_id is a URL of a JSON document describing it)mcp.registration.metadata_documents: true, the defaultThe host of the metadata document
A client an administrator configuredmcp.clientsIts configured name
Dynamic registration (RFC 7591)mcp.registration.dynamic: true; off by defaultThe name it registered

Configure a client when your organisation’s agent platform cannot register itself:

# config/engine.yaml
mcp:
  enabled: true
  clients:
    - id: acme-agents                 # the client_id the client sends
      name: Acme agent platform       # shown on the consent screen
      redirect_uris:
        - https://agents.acme.example/oauth/callback
        - http://127.0.0.1:33418/callback

A client’s id must not be a URL (a URL client id is a metadata document, which needs no configuration) and must be unique; name and at least one redirect URI are required; each redirect URI is https, or http on a loopback address, with no fragment.

Every choice only takes away from what the person may already do; none grants anything.

ChoiceWhat it meansDefault
SitesWhich sites the agent may work on, among those where the person holds mcp.connectThe site they were on
LocalesWhich languages it may editAll the person may edit
What it may doToolsets. Read is always on. Create and edit drafts (content), Media, Notes, Tags, smart lists and navigation (structure), Bulk changes, and one for each module the bundle hasRead, content, media and notes
Typed toolsUp to eight content types given their own create_<type> and update_<type> tools, which some models use more reliablyNone
PublishingAs my permissions allow: when asked, it publishes where the person could and sends to review where their changes go to review. Drafts only: it never publishesAs my permissions allow; every write is still a draft unless a call asks to publish
Expires1, 7, 30 or 90 days, at most mcp.tokens.connection_max30 days

The screen also says, in plain sentences, what the agent will be able to do, that it acts as the person and is recorded under their name, and that what it reads leaves the install for the client and its model. A connection that is Drafts only and asked to publish is refused in a way the client can show (“connect again with publishing allowed”).

The People toolset (CRM personal data, engine-dmo only) is offered only when mcp.personal_data: allow.

Seeing and ending connections

  • You, Agents and tokens (<admin_path>/you/agents): your own connections and tokens, with the client, the sites and toolsets chosen, when each was last used and how many writes it made this week. End one, or all.
  • Settings, Agent connections (<admin_path>/settings/agents, for people with users.manage): every connection on the install, filterable by person, client and site. End any, or block a client.
  • The editor and the revision history name the agent on every revision it wrote, the approvals queue marks agent-written changes, and content lists can be filtered to “written by an agent”.

Suspending a person ends their connections at once.

Personal access tokens for scripts

A script that moves content (a nightly sync, a one-off bulk tag) uses a personal access token, made on You, Agents and tokens with the same choices as consent plus a kind: agent (a model drives it) or script (deterministic code, whose writes are not counted as AI assisted). It is sent as Authorization: Bearer mrd_pat_... to the same endpoint. A token belongs to a person and ends with them: there are no service accounts.

From a shell:

export MERIDIAN_TOKEN=mrd_pat_...
engine-dmo mcp call documents_search \
  --url https://studio.example.org/studio/mcp \
  --args '{"site": "main", "query": "summer fair"}'
FlagMeaning
--urlThe endpoint’s address (required).
--token-envThe environment variable holding the token, MERIDIAN_TOKEN by default. The token is never a command line argument.
--argsThe tool’s arguments as a JSON object.

It prints the tool’s structured result as JSON; a tool error exits non-zero with its JSON on standard output. Read a tool’s arguments with engine-dmo mcp tools --json.

engine-dmo mcp over stdio

For a coding agent working on a project locally, the engine serves the same tools over standard input and output (newline-delimited JSON-RPC), every call acting as one staff user with exactly their grants:

engine-dmo mcp --as editor@example.org --project /path/to/project
claude mcp add meridian -- engine-dmo mcp --as editor@example.org --project /path/to/project

The process needs the project’s environment (DATABASE_URL and the rest) and a migrated database.

FlagMeaning
--asThe staff user every call acts as, by email (required).
--siteThe site a call that names none is for; the project’s default site when absent, the first of --sites when that is set.
--sitesNarrow to these sites, comma separated.
--toolsetsNarrow to these toolsets, comma separated; read is always included.
--typesGive these types typed tools, comma separated, at most eight.
--drafts-onlyNever publish, as a Drafts only connection.

It is refused when engine.environment is production, and in staging unless dev.authoring: true. The narrowing flags only remove, as consent does.

What an agent can do

engine-dmo mcp tools lists exactly what your bundle offers. On engine-dmo:

ToolsetTools
read (always)sites_list, types_list, type_describe, documents_search, document_get, document_history, singleton_get, translation_status, notes_list, picker_search, media_search, media_get, tags_list, smart_lists_list, smart_list_get, content_check, preview_link, bulk_status
contentdocument_create, document_update, document_clone, document_trash, document_restore, document_sites_set, document_override_set, document_variant_set, publish, unpublish, review_request, schedule_cancel, landing_content_get, landing_content_set
mediamedia_upload, media_upload_url, media_upload_page, media_upload_page_get, media_text_set, media_rights_set
notesnote_add
structuretag_create, smart_list_save, navigation_get, navigation_edit
bulkbulk_plan, bulk_apply
crmorganisations_search, organisation_get, organisation_update
peoplepeople_search, person_get (only with mcp.personal_data: allow)
newsletternewsletter_campaigns_list, newsletter_campaign_get, newsletter_campaign_draft
affiliatesaffiliate_programmes_list, affiliate_links_list, affiliate_link_create
claimsclaims_list, claim_get (read only)
promotionspromotions_list, promotion_request_get, promotion_slots_list (read only)
paidlistingspaid_orders_list, paid_order_get (read only)

Resources: meridian://rules (rules for agents), meridian://sites, meridian://sites/{site}/schema, meridian://sites/{site}/guidance and meridian://sites/{site}/guidance/{type} (the site’s editorial guidance), meridian://types/{type} and meridian://documents/{id}. Prompts: draft_article (site, type, topic, sources), fix_content_health (site, type) and translate_document (document, target locale, source locale), each in the content toolset.

An agent cannot purge or export content, change users, settings or permissions, read the audit log, act as a member, call Meridian’s own AI features, send a newsletter or broadcast, change a price, create an order, or decide a claim, a promotion or a membership. Those stay with a person in the admin.

Photos from a phone

An agent in a chat app cannot pass a photo’s file to the install, so media_upload_page gives the person a one-time link instead. They open it on their phone, pick photos (20 unless the agent asked for another number, never more than 30), and go back to the conversation; the agent then sees the photos with media_upload_page_get, writes their alt text and puts them where asked. The link needs no sign-in (the link is the key), works for an hour, and ends when the connection ends. The photos are stripped of location and camera metadata like any upload, and are not published until a published page uses them; a page with a photo still missing alt text cannot be published. The agent needs the Media toolset and the person must be allowed to upload.

Configuration

Key in engine.yamlDefaultLimitsMeaning
mcp.enabledfalseServes <admin_path>/mcp and its OAuth endpoints.
mcp.personal_datarefuserefuse or allowWhether a toolset that returns CRM personal data may be chosen and called.
mcp.registration.metadata_documentstrueAccept clients identified by a metadata document URL.
mcp.registration.dynamicfalseAccept dynamic client registration.
mcp.clientsnoneid, name, redirect_uris each requiredConfigured clients.
mcp.tokens.access_ttl1h5m to 24hAn access token’s life.
mcp.tokens.refresh_idle30d1d to 180dHow long a refresh token may go unused.
mcp.tokens.connection_max90d1d to 180dThe longest a connection or personal token may live.
mcp.media.fetchtrueWhether media_upload_url may fetch a file from an address.
mcp.media.fetch_hostsanyHost namesWhen set, only these hosts and their subdomains may be fetched from.
mcp.limits.*See belowSee belowThe operational limits.
# config/engine.yaml
mcp:
  enabled: true
  personal_data: refuse
  registration: { metadata_documents: true, dynamic: false }
  tokens: { access_ttl: 1h, refresh_idle: 30d, connection_max: 90d }
  media: { fetch: true, fetch_hosts: [images.example.org] }
  limits: { calls_per_minute: 600, writes_per_minute: 60, writes_per_day: 2000, bulk_items: 200, media_by_url_per_hour: 20 }

Limits

LimitDefaultCeilingKey
Weighted calls per connection a minute (a read 1, a search 2, content_check 3, a write 5, bulk_plan 1 an item)6001,200calls_per_minute
Writes per person a minute, across every connection60120writes_per_minute
Writes per person a day, across every connection2,0005,000writes_per_day
Operations in one bulk plan200500bulk_items
Uploads by address per connection an hour20100media_by_url_per_hour

A value above its ceiling, or zero or below, is a check error; leave a key out for its default. The weighted call limit is counted in each engine process’s memory, so with several replicas behind a load balancer a connection can make up to calls_per_minute on each replica: with three, up to three times the figure. The write limits are counted in the database and hold across replicas. A call past a limit is refused rate_limited with the seconds to wait. The arguments of one write are at most 1 MB. Sign-in, token and registration attempts have fixed limits of their own. The operational limits fail open if they cannot be counted; the security limits fail closed.

What to tell your data protection officer

This section describes the install as shipped; your configuration and choice of clients decide the rest.

  • Who decides where content goes. The person who connects an agent chooses the client, and the client chooses its model provider. Meridian sends nothing to any model through this channel and proxies nothing: content an agent reads goes from your install to that person’s client, under their own account with it. Treat each client your organisation approves as a processor you have assessed, and tell staff which clients are approved.
  • What can leave. By default only what the person can already see in the admin: the site’s content, drafts included, its media and the notes on its documents. CRM personal data about people is refused by default (mcp.personal_data: refuse) and no tool returns it. Organisations’ public profile fields are reachable through the CRM toolset. A claim’s claimant contact details are left out while personal data is refused. Fields marked sensitive in the CRM are never returned.
  • If you allow personal data (mcp.personal_data: allow), people with the CRM permissions may choose the People toolset, and every read through it that returns records is written to the audit log. Add “CRM personal data to the person’s MCP client” to your record of processing and your DPIA, naming the approved clients and their providers.
  • What is recorded. Every write is in the audit log as the person via the client, with the connection, the tool and the call’s key; every revision it made says it came through an agent and for whom; consent, token issue, token reuse and every ending of a connection are audited. Reads are counted per connection and day rather than logged one by one, except personal data reads. The audit log’s retention is retention.audit_log.
  • Tokens and sign-in. Connections use OAuth 2.1 with PKCE through the admin’s own sign-in and second factor. Tokens are random, held only as hashes, and access tokens live an hour by default; refresh tokens rotate, and one used twice ends the whole connection. Tokens are never put in a URL or a log.
  • Prompt injection. Content written by members or imported from feeds may contain text addressed to a model. The engine never treats content as an instruction, and marks untrusted content it hands a prompt. An agent misled by such text can still do only what its connection allows, publishing only as its person’s workflow allows, and anything it trashes stays in the trash for content.trash_retention (30 days by default).