# MCP server integration

<ExperimentalFeatureAside feature="MCP Server" />

**Goal:** connect the MCP client of your choice to your [site](/start-here/glossary/#site)'s [MCP server](/start-here/glossary/#mcp-server), and know what the server exposes before you point an agent at it.

## What you'll have when you're done

- Your MCP client connected and authorized: Claude Code, Claude Desktop, Cursor, VS Code, GitHub Copilot, OpenCode, Codex, or MCP Inspector.
- A working map of the server: `drupal://` and `canvas://` resource URIs, the tool catalog by domain, and the limits to design around.

## Prerequisites

- The `MCP Server` experimental feature enabled on your site; the [MCP quickstart](/source-cms/ai-agents/quickstart/) covers enabling it and the fastest path to a first tool call with Claude Code.
- An MCP-capable client installed; the [MCP quickstart](/source-cms/ai-agents/quickstart/) sets one up (Claude Code) if you don't have one yet.

## Steps

<Steps>

1. ### Copy the connection details from your site

   In the site admin UI: `Configurations > Experimental features > MCP Server > Configure`. The panel gives you everything a client needs:

   - `MCP client configuration`: the server URL and parameters to paste into `claude_desktop_config.json` or Cursor.
   - `MCP Inspector`: a ready-made command to launch the debug environment.
   - `OAuth configuration`: the scopes and authentication parameters the server accepts.

   You typically don't change anything here; copy the connection URL and move on.

2. ### Understand how credentials reach the server

   The server speaks MCP over **streamable HTTP** at `{DRUPAL_SITE_URL}/mcp`, and every request is authorized by **OAuth 2.0**: a bearer token on each HTTP request. There is no API key to paste. In practice: the client registers itself with the site and opens your browser; you sign in (if you aren't already) and click `Allow`; from then on the client attaches and refreshes its token without your involvement.

   <details>
   <summary>How the handshake works, step by step</summary>

   An unauthenticated request gets a `401` whose `WWW-Authenticate` header carries only `Bearer realm="mcp_server"` (captured 2026-07-03): no [scope](/start-here/glossary/#scope) list, no metadata URL. The client therefore derives the discovery locations from the server URL itself: the resource metadata at `{DRUPAL_SITE_URL}/.well-known/oauth-protected-resource/mcp` lists the scopes the server accepts, and the site's `/.well-known/oauth-authorization-server` document names the endpoints. From there the client **registers itself** at the `registration_endpoint` (`/oauth/register`, dynamic client registration; this is how Claude Code and other MCP clients self-register), then opens the browser window where you approve the connection. The resulting access token rides on every subsequent request.

   </details>

   The whole exchange, from the challenge to authorized tool calls: an unauthenticated request is refused, the client discovers the OAuth metadata and self-registers, and you approve in the browser. The token then rides every request until it expires and the client silently re-authorizes.

   <ConceptDiagram name="mcp-oauth-handshake" height={560} />

   Three consequences worth knowing:

   - The agent acts **as you**. Tool calls carry your authorization, so what the agent can read and write is bounded by what you approved.
   - Tokens expire after **300 seconds**. Clients re-authorize silently, but a long-idle session may drop you back into the browser flow.
   - Headless setups don't need the browser: tokens minted by the site's existing API clients also work, a `client_credentials` grant against `{DRUPAL_SITE_URL}/oauth/token` (no scope parameter needed). Pass the token as an `Authorization` header in the client's server configuration, e.g. `claude mcp add acquia-source-mcp {DRUPAL_SITE_URL}/mcp --transport http --header "Authorization: Bearer <token>"`; remember it expires after 300 seconds, so CI mints one per run.

   The OAuth machinery (grants, scopes, token lifetimes) is the same one behind the Content API; the [authentication guide](/source-cms/authenticate/guide/) covers it in depth.

3. ### Configure your client

   <Tabs syncKey="agent">
     <TabItem label="Claude Code">
       The canonical client, documented end to end in the [quickstart](/source-cms/ai-agents/quickstart/):

       ```bash
       claude mcp add acquia-source-mcp <URL> \
         --transport http --scope project
       ```

       Then inside Claude Code: `/mcp` → select `acquia-source-mcp` → `Authenticate` → complete the browser flow. `--scope project` writes the server to `.mcp.json` in the project root so the whole team shares it; use `--scope user` instead if the server should follow you across projects rather than live in one repo.
     </TabItem>
     <TabItem label="Claude Desktop">
       For a site reachable over the public internet, add a custom connector: `Customize > Connectors`, `+`, `Add custom connector`, paste the MCP URL, click `Add`, then `Connect` to authorize. Custom connectors reach the server from Anthropic's cloud, not from your machine; on Team and Enterprise plans an Owner adds the connector under `Organization settings > Connectors` first.

       Alternatively, paste the parameters from the `MCP client configuration` panel into `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`; Windows: `%APPDATA%\Claude\claude_desktop_config.json`), then restart Claude Desktop and complete the authorization prompt. The panel's snippet is authoritative for the exact parameter names.
     </TabItem>
     <TabItem label="Cursor">
       Add the server to `.cursor/mcp.json` in your project (or the global `~/.cursor/mcp.json`), pasting the URL from the `MCP client configuration` panel:

       ```json
       {
         "mcpServers": {
           "acquia-source-mcp": {
             "url": "PASTE_MCP_URL_FROM_CONFIGURE_PANEL"
           }
         }
       }
       ```

       Cursor picks the server up on reload and walks you through the same browser OAuth flow.
     </TabItem>
     <TabItem label="VS Code">
       Add the server to `.vscode/mcp.json` in your project (or run `MCP: Add Server` from the Command Palette; `MCP: Open User Configuration` targets your user profile instead):

       ```json
       {
         "servers": {
           "acquia-source-mcp": {
             "type": "http",
             "url": "PASTE_MCP_URL_FROM_CONFIGURE_PANEL"
           }
         }
       }
       ```

       VS Code asks you to confirm that you trust the server when it first starts, then opens a browser window to complete the authorization.
     </TabItem>
     <TabItem label="GitHub Copilot">
       Copilot Chat in VS Code reads MCP servers from the repository's `.vscode/mcp.json` (the configuration under VS Code) or from your personal `settings.json`; `MCP: List Servers` confirms the server registered. On Copilot Business and Enterprise plans, the `MCP servers in Copilot` policy must be enabled for the organization. JetBrains, Visual Studio, Eclipse, and Xcode carry their own MCP configuration files: see [Extending Copilot Chat with MCP](https://docs.github.com/en/copilot/how-tos/provide-context/use-mcp/extend-copilot-chat-with-mcp).
     </TabItem>
     <TabItem label="OpenCode">
       Add the server to `opencode.json` at the project root:

       ```json
       {
         "$schema": "https://opencode.ai/config.json",
         "mcp": {
           "acquia-source-mcp": {
             "type": "remote",
             "url": "PASTE_MCP_URL_FROM_CONFIGURE_PANEL"
           }
         }
       }
       ```

       OpenCode prompts for the OAuth flow on the server's first use; `opencode mcp auth acquia-source-mcp` triggers it manually.
     </TabItem>
     <TabItem label="Codex">
       Register the server and log in:

       ```bash
       codex mcp add acquia-source-mcp --url PASTE_MCP_URL_FROM_CONFIGURE_PANEL
       codex mcp login acquia-source-mcp
       ```

       Codex stores the server in `~/.codex/config.toml` (user-wide, not per project); `codex mcp list` shows its auth status.
     </TabItem>
   </Tabs>

   **MCP Inspector** (for debugging, not day-to-day use):

   ```bash
   npx @modelcontextprotocol/inspector --transport http
   ```

   The command starts a local web UI and prints its address (`http://localhost:6274/…`); open it in a browser. In the Inspector UI: paste the site's MCP server URL, set connection type to `Direct`, click `Connect`, complete the OAuth flow for dynamic client registration, and click `Allow`. You can then browse `List Resources`, `List Templates`, and `Tools`: the fastest way to see exactly what your server exposes, live.

4. ### Browse what the server exposes as resources

   Resources are read-only data, addressed by URI. The server uses two schemes: `drupal://` for the content model and content entities, `canvas://` for [Drupal Canvas](/start-here/glossary/#drupal-canvas) pages and [components](/start-here/glossary/#component). Reading one takes no special syntax: in Claude Code, just ask (`Read drupal://content-types and summarize the fields`); in MCP Inspector, use `List Resources` and click through.

   Fixed resources list things:

   | URI | Returns |
   | --- | --- |
   | `drupal://content-types` | The site's [content types](/start-here/glossary/#content-type-bundle), each with its [workflow](/start-here/glossary/#workflow) and schema URI |
   | `drupal://media-types` | [Media types](/start-here/glossary/#media-type) |
   | `drupal://vocabularies` | Taxonomy [vocabularies](/start-here/glossary/#vocabulary) |
   | `drupal://filter-formats` | Text formats |
   | `drupal://content-types/fields` | Content-type [field](/start-here/glossary/#field) storages |
   | `drupal://field-types` | Available field types, by category |
   | `drupal://menus` | The site's menus, each with an `items_uri` for its item tree |
   | `drupal://site-info` | Site name and configured home page |
   | `canvas://components` | Drupal Canvas components |
   | `canvas://pages` | Drupal Canvas pages, with live and draft metadata |
   | `canvas://page-regions` | Global page regions (header, footer, …) for the active theme |
   | `canvas://auto-saves` | Pending auto-saved drafts, with the keys the publish/discard tools need |

   Resource *templates* take parameters for point lookups:

   | URI template | Returns |
   | --- | --- |
   | `drupal://entity/{entity_type_id}/{id}` | One entity (e.g. `drupal://entity/node/17`) |
   | `drupal://content-types/{bundle}` | JSON Schema for a content type (e.g. `drupal://content-types/article`) |
   | `drupal://content-types/{bundle}/fields/{field_name}` | One field's instance and storage configuration |
   | `drupal://media-types/{bundle}` | JSON Schema for a media type |
   | `drupal://vocabularies/{vid}` | JSON Schema for a vocabulary |
   | `drupal://menus/{menu_name}` | One menu's item tree, with the item IDs the menu tools take |
   | `canvas://components/{id}` | One Canvas component's detail |

   So "what fields does an article have?" is one read of `drupal://content-types/article`, and "what components can I place on a page?" is one read of `canvas://components`. Response shapes for each are specified in the [MCP server reference](/source-cms/reference/mcp-server/#resources).

5. ### Call tools from the catalog

   Tools are the operations that change state (plus a few parameterized reads), 38 in all. Each name links to its entry in the [reference](/source-cms/reference/mcp-server/), which specifies inputs, responses, and errors:

   **Content management**, read and write content entities:
   [`create_node`](/source-cms/reference/mcp-server/#create_node) · [`update_node`](/source-cms/reference/mcp-server/#update_node) · [`batch_create_nodes`](/source-cms/reference/mcp-server/#batch_create_nodes) · [`list_entities`](/source-cms/reference/mcp-server/#list_entities)

   **Content modeling**, define and evolve content structure:
   [`create_content_type`](/source-cms/reference/mcp-server/#create_content_type) · [`add_field_to_content_type`](/source-cms/reference/mcp-server/#add_field_to_content_type) · [`batch_add_fields_to_content_type`](/source-cms/reference/mcp-server/#batch_add_fields_to_content_type) · [`update_field_config`](/source-cms/reference/mcp-server/#update_field_config)

   **Taxonomy and menus**, vocabularies, terms, menus, and menu items:
   [`create_vocabulary`](/source-cms/reference/mcp-server/#create_vocabulary) · [`get_or_create_term`](/source-cms/reference/mcp-server/#get_or_create_term) · [`create_menu`](/source-cms/reference/mcp-server/#create_menu) · [`update_menu`](/source-cms/reference/mcp-server/#update_menu) · [`create_menu_item`](/source-cms/reference/mcp-server/#create_menu_item) · [`update_menu_item`](/source-cms/reference/mcp-server/#update_menu_item) · [`delete_menu_item`](/source-cms/reference/mcp-server/#delete_menu_item)

   **Media**, file uploads, remote video, and DAM references:
   [`create_media`](/source-cms/reference/mcp-server/#create_media) · [`create_remote_video`](/source-cms/reference/mcp-server/#create_remote_video) · [`create_dam_media`](/source-cms/reference/mcp-server/#create_dam_media)

   **Site settings**, site name, logo, and homepage:
   [`update_site_settings`](/source-cms/reference/mcp-server/#update_site_settings) · [`update_site_logo`](/source-cms/reference/mcp-server/#update_site_logo) · [`set_homepage`](/source-cms/reference/mcp-server/#set_homepage)

   **Drupal Canvas pages and components**, pages, component-based layouts, and the global page regions:
   [`create_canvas_page`](/source-cms/reference/mcp-server/#create_canvas_page) · [`update_canvas_page`](/source-cms/reference/mcp-server/#update_canvas_page) · [`delete_canvas_page`](/source-cms/reference/mcp-server/#delete_canvas_page) · [`get_page_layout`](/source-cms/reference/mcp-server/#get_page_layout) · [`set_page_layout`](/source-cms/reference/mcp-server/#set_page_layout) · [`add_component_to_page`](/source-cms/reference/mcp-server/#add_component_to_page) · [`batch_add_components_to_page`](/source-cms/reference/mcp-server/#batch_add_components_to_page) · [`move_component`](/source-cms/reference/mcp-server/#move_component) · [`remove_component`](/source-cms/reference/mcp-server/#remove_component) · [`update_component_props`](/source-cms/reference/mcp-server/#update_component_props) · [`get_page_region_layout`](/source-cms/reference/mcp-server/#get_page_region_layout) · [`set_page_region_layout`](/source-cms/reference/mcp-server/#set_page_region_layout) · [`add_component_to_page_region`](/source-cms/reference/mcp-server/#add_component_to_page_region)

   **Drafts and publishing**, make drafts live, or throw them away:
   [`publish_canvas_page`](/source-cms/reference/mcp-server/#publish_canvas_page) · [`publish_auto_saves`](/source-cms/reference/mcp-server/#publish_auto_saves) · [`discard_auto_saves`](/source-cms/reference/mcp-server/#discard_auto_saves) · [`get_canvas_preview_signed_url`](/source-cms/reference/mcp-server/#get_canvas_preview_signed_url)

   The combination is where the value is: an agent reads `drupal://content-types/article` to learn the shape, then calls `batch_create_nodes` to generate structured content in bulk; or reads `canvas://components` and composes a landing page with `create_canvas_page`, `batch_add_components_to_page`, and `publish_canvas_page`.

6. ### Work within the known limits

   - **Experimental.** The server may change or be removed without notice, and it is off by default, per-site opt-in under `Configurations > Experimental features`.
   - **Your permissions are the ceiling.** Every tool call runs with the authorization you granted in the OAuth flow; the server grants agents no access you don't have.
   - **Content types need a workflow that already exists, and no tool creates one.** `create_content_type` requires a `workflow` parameter naming an existing workflow (empty or null is rejected), the catalog has no workflow-creation tool, and a fresh Source CMS site ships with none. On a fresh site the call fails with `Workflow "…" does not exist.` until an administrator creates a workflow in the admin UI ([content workflows, on docs.acquia.com](https://docs.acquia.com/acquia-source/content-workflows)). Check what exists first: `drupal://content-types` shows each content type's workflow.
   - **No content deletion.** The catalog has create and update tools for content entries, media, and terms, but no delete: the only delete tools are `delete_menu_item` and `delete_canvas_page`. Deleting content stays a human job in the admin UI or [JSON:API](/start-here/glossary/#jsonapi).
   - **Batch creation covers nodes, fields, and components** (`batch_create_nodes`, `batch_add_fields_to_content_type`, `batch_add_components_to_page`); media and terms are created one call at a time.
   - **Many writes land as drafts, not live changes.** Canvas layout edits, menu items, and staged site settings go to the auto-save channel; nothing is visible until you publish via `publish_auto_saves` (or `publish_canvas_page` for a whole page). Pending drafts are listed at `canvas://auto-saves`.
   - **No fixed numeric limits; the timeout is the bound.** Verification surfaced no request rate limit, no maximum `batch_create_nodes` batch size, and no request payload cap. The only ceiling is the per-request timeout, so very large batches or queries fail by timing out (`-32001`) rather than by hitting a documented cap. Split or narrow them: see the failure entries below.

7. ### Govern the agent's access

   Least privilege lives on the credential, not the connection. Give each agent its own [API client](/start-here/glossary/#api-client) with the fewest `Allowed Scopes` its job needs ([the scope catalog](/source-cms/authenticate/guide/#select-the-minimum-scopes)); a browser-authorized agent's ceiling is the person who clicked `Allow`. One verified caution: scope gating was not observable in MCP server verification ([Connection](/source-cms/reference/mcp-server/#connection)). Treat scopes as the control you set deliberately, and lean on the permission layer, which still refuses and names the missing permission ([scopes are not team roles](/source-cms/authenticate/guide/#scopes-are-not-team-roles)).

   Revocation has two levers and one clock:

   - Toggle `Enable MCP Server` off. This invalidates the endpoint for every agent on that site, reversibly.
   - Delete the agent's client at `API > API clients`. The next token request fails with `invalid_client`; for zero-downtime swaps, use the [rotation order](/source-cms/authenticate/guide/#rotate-credentials-with-zero-downtime).

   Either way, no already-issued token outlives its 300 seconds.

   Attribution follows the credential: a browser-authorized agent acts as its approver, a `client_credentials` token acts as the client's own identity, so per-agent clients are per-agent accountability. And most agent writes land as drafts first: inspect pending work at `canvas://auto-saves`, then [`publish_auto_saves`](/source-cms/reference/mcp-server/#publish_auto_saves) or [`discard_auto_saves`](/source-cms/reference/mcp-server/#discard_auto_saves) before anything reaches readers.

</Steps>

## When something goes wrong

**`401 Unauthorized` on every request, or the client reports the server needs authentication**: the OAuth handshake never completed, or the token expired (tokens last 300 seconds) and silent refresh failed. The `401` itself says little: its `WWW-Authenticate` header carries only `Bearer realm="mcp_server"`, so when diagnosing by hand read the resource metadata at `{DRUPAL_SITE_URL}/.well-known/oauth-protected-resource/mcp` directly for the scopes and endpoints the server accepts. Re-run your client's authenticate action (in Claude Code: `/mcp` → select the server → `Authenticate`) and finish the browser flow through `Allow`. If the browser step itself fails, confirm you are signed in to the site in that browser, and that the `MCP Server` feature is still enabled; disabling it invalidates the endpoint. Deeper OAuth diagnostics: [the auth guide's troubleshooting](/source-cms/authenticate/guide/#when-something-goes-wrong).

**`MCP error -32601: Method not found` on a tool the reference says exists**: your client cached the server's capabilities from an earlier session, or client and server negotiated mismatched protocol versions. Restart the client; if the tool list is still stale, remove and re-add the server, and update the client; MCP clients pin the protocol versions they support.

**Tools are missing from the client's list even though MCP Inspector shows them**: client-side caching. The Inspector talks to the server directly, so if it lists the tool and your client doesn't, the client's cached capability list is stale: restart the client or re-add the server. If the Inspector doesn't show the tool either, the server genuinely doesn't expose it: check the [reference](/source-cms/reference/mcp-server/) for what's actually in the catalog.

**`MCP error -32001: Request timed out`**, the call was too big: a `list_entities` sweep over a large site, or a `batch_create_nodes` payload with too many nodes. Narrow the query (filter by [content type](/start-here/glossary/#content-type-bundle), request fewer entities) or split the batch into smaller calls and let the agent iterate.

## Next steps

- [MCP server reference](/source-cms/reference/mcp-server/): the connection spec and every tool's inputs, responses, and errors.
- [Authentication guide](/source-cms/authenticate/guide/): the OAuth machinery this server authorizes with.
