---
title: MCP Plugin
label: MCP
order: 49
desc: MCP (Model Context Protocol) capabilities with Payload
keywords: plugins, mcp, ai, model context protocol, plugin, payload, cms
source: https://payloadcms.com/docs/plugins/mcp
---

![[410ba741e3f5fcfea1aa0e579172b5b1_MD5.svg]]

This plugin adds [Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro) capabilities.

<Banner type="info">
  This plugin is completely open-source and the [source code can be found
  here](https://github.com/payloadcms/payload/tree/main/packages/plugin-mcp). If
  you need help, check out our [Community
  Help](https://payloadcms.com/community-help). If you think you've found a bug,
  please [open a new
  issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%mcp&template=bug_report.md&title=plugin-mcp%3A)
  with as much detail as possible.
</Banner>

<YouTube
  id="UBbElyhoszQ"
  title="Supercharge Your CMS: Payload MCP Server Overview & Demo"
/>

## Core features

- Adds a collection to your config where:
  - You can allow / disallow `find`, `create`, `update`, and `delete` operations for each collection
  - You can allow / disallow `find` and `update` operations for each global
  - You can allow / disallow capabilities in real time
  - You can define your own Prompts, Tools and Resources available over MCP

## Installation

Install the plugin using any JavaScript package manager like [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):

```bash
  pnpm add @payloadcms/plugin-mcp
```

## Basic Usage

In the `plugins` array of your [Payload Config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):

```ts
import { buildConfig } from 'payload'
import { mcpPlugin } from '@payloadcms/plugin-mcp'

const config = buildConfig({
  collections: [
    {
      slug: 'posts',
      fields: [],
    },
  ],
  plugins: [
    mcpPlugin({
      collections: {
        posts: {
          enabled: true,
        },
      },
    }),
  ],
})

export default config
```

### Options

| Option                                 | Type                | Description                                                                                    |
| -------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------- |
| `collections`                          | `object`            | An object of collection slugs to use for MCP capabilities.                                     |
| `collections[slug]`                    | `object`            | An object of collection slugs to use for MCP capabilities.                                     |
| `collections[slug].description`        | `string`            | A description for the collection.                                                              |
| `collections[slug].overrideResponse`   | `function`          | A function that allows you to override the response from the operation tool call.              |
| `collections[slug].enabled`            | `object or boolean` | Determines whether the model can find, create, update, and delete documents in the collection. |
| `collections[slug].enabled.find`       | `boolean`           | Whether to allow the model to find documents in the collection.                                |
| `collections[slug].enabled.create`     | `boolean`           | Whether to allow the model to create documents in the collection.                              |
| `collections[slug].enabled.update`     | `boolean`           | Whether to allow the model to update documents in the collection.                              |
| `collections[slug].enabled.delete`     | `boolean`           | Whether to allow the model to delete documents in the collection.                              |
| `globals`                              | `object`            | An object of global slugs to expose via MCP. Globals only support find and update.             |
| `globals[slug].description`            | `string`            | A description for the global shown to models.                                                  |
| `globals[slug].overrideResponse`       | `function`          | A function that allows you to override the response from the operation tool call.              |
| `globals[slug].enabled`                | `object or boolean` | Determines whether the model can find or update the global.                                    |
| `globals[slug].enabled.find`           | `boolean`           | Whether to allow the model to read the global.                                                 |
| `globals[slug].enabled.update`         | `boolean`           | Whether to allow the model to update the global.                                               |
| `disabled`                             | `boolean`           | Disable the MCP plugin while keeping database schema consistent.                               |
| `userCollection`                       | `CollectionSlug`    | The users collection that API keys are associated with. Defaults to `config.admin.user`.       |
| `overrideApiKeyCollection`             | `function`          | A function that allows you to override the automatically generated API Keys collection.        |
| `overrideAuth`                         | `function`          | Replace the default Bearer-token / API-key auth with a completely custom access strategy.      |
| `mcp`                                  | `object`            | MCP options that allow you to customize the MCP server.                                        |
| `mcp.tools`                            | `array`             | An array of tools to add to the MCP server.                                                    |
| `mcp.tools.name`                       | `string`            | The name of the tool.                                                                          |
| `mcp.tools.description`                | `string`            | The description of the tool.                                                                   |
| `mcp.tools.handler`                    | `function`          | The handler function for the tool.                                                             |
| `mcp.tools.parameters`                 | `object`            | The parameters for the tool (Zod schema).                                                      |
| `mcp.prompts`                          | `array`             | An array of prompts to add to the MCP server.                                                  |
| `mcp.prompts.name`                     | `string`            | The name of the prompt.                                                                        |
| `mcp.prompts.title`                    | `string`            | The title of the prompt (used by models to determine when to use it).                          |
| `mcp.prompts.description`              | `string`            | The description of the prompt.                                                                 |
| `mcp.prompts.handler`                  | `function`          | The handler function for the prompt.                                                           |
| `mcp.prompts.argsSchema`               | `object`            | The arguments schema for the prompt (Zod schema).                                              |
| `mcp.resources`                        | `array`             | An array of resources to add to the MCP server.                                                |
| `mcp.resources.name`                   | `string`            | The name of the resource.                                                                      |
| `mcp.resources.title`                  | `string`            | The title of the resource (used by models to determine when to use it).                        |
| `mcp.resources.description`            | `string`            | The description of the resource.                                                               |
| `mcp.resources.handler`                | `function`          | The handler function for the resource.                                                         |
| `mcp.resources.uri`                    | `string or object`  | The URI of the resource (can be a string or ResourceTemplate for dynamic URIs).                |
| `mcp.resources.mimeType`               | `string`            | The MIME type of the resource.                                                                 |
| `mcp.handlerOptions`                   | `object`            | The handler options for the MCP server.                                                        |
| `mcp.handlerOptions.verboseLogs`       | `boolean`           | Whether to log verbose logs to the console (default: false).                                   |
| `mcp.handlerOptions.maxDuration`       | `number`            | The maximum duration for the MCP server requests in seconds (default: 60).                     |
| `mcp.handlerOptions.onEvent`           | `function`          | Callback invoked for every MCP event. Useful for analytics and audit logging.                  |
| `mcp.serverOptions`                    | `object`            | The server options for the MCP server.                                                         |
| `mcp.serverOptions.serverInfo`         | `object`            | The server info for the MCP server.                                                            |
| `mcp.serverOptions.serverInfo.name`    | `string`            | The name of the MCP server (default: 'Payload MCP Server').                                    |
| `mcp.serverOptions.serverInfo.version` | `string`            | The version of the MCP server (default: '1.0.0').                                              |

## How Access Control Works with MCP

<Banner type="warning">
  Enabling a collection or global in the plugin config does **not**
  automatically make it accessible to MCP clients. There are two separate steps.
</Banner>

**Step 1 — Enable in your config**

Add the collection or global to the plugin with `enabled: true` (or a capabilities object):

```ts
mcpPlugin({
  collections: {
    posts: { enabled: true },
  },
  globals: {
    'site-settings': { enabled: { find: true, update: true } },
  },
})
```

**Step 2 — Allow in the API Key**

In your Payload admin panel, navigate to **MCP → API Keys**, create a new key, and
toggle the individual capabilities on for each collection, global, tool, prompt, and
resource you want that key to be able to use.

All MCP requests must include a valid API key as a Bearer token — requests without
one are rejected immediately. Here is a complete example of an MCP `tools/list` request
showing the correct headers:

```bash
curl -i 'http://localhost:3000/api/mcp' \
  -X POST \
  -H 'Authorization: Bearer MCP-USER-API-KEY' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{}}'
```

Access controls are also enforced at the Payload level using the user associated with
the API key, so existing collection access rules, hooks, and multi-tenant restrictions
all continue to apply.

## Connecting to MCP Clients

After installing and configuring the plugin, you can connect apps with MCP client capabilities to Payload.

### Step 1: Create an API Key

1. Start your Payload server
2. Navigate to your admin panel at `http://localhost:3000/admin`
3. Go to the **MCP → API Keys** collection
4. Click **Create New**
5. Allow or Disallow MCP traffic permissions for each collection (enable find, create, update, delete as needed)
6. Click **Create** and copy the uniquely generated API key

### Step 2: Configure Your MCP Client

MCP Clients can be configured to interact with your MCP server.
These clients require some JSON configuration, or platform configuration in order to know how to reach your MCP server.

<Banner type="warning">
  Caution: the format of these JSON files may change over time. Please check the
  client website for updates.
</Banner>

Our recommended approach to make your server available for most MCP clients is to use the [mcp-remote](https://www.npmjs.com/package/mcp-remote) package via `npx`.

Below are configuration examples for popular MCP clients.

#### [VSCode](https://code.visualstudio.com/docs/copilot/customization/mcp-servers)

```json
{
  "mcp.servers": {
    "Payload": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "http://127.0.0.1:3000/api/mcp",
        "--header",
        "Authorization: Bearer MCP-USER-API-KEY"
      ]
    }
  }
}
```

#### [Cursor](https://cursor.com/docs/context/mcp)

```json
{
  "mcpServers": {
    "Payload": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "http://localhost:3000/api/mcp",
        "--header",
        "Authorization: Bearer MCP-USER-API-KEY"
      ]
    }
  }
}
```

#### [Claude Code](https://docs.claude.ai/en/docs/claude-code/mcp)

```bash
claude mcp add --transport http Payload http://127.0.0.1:3000/api/mcp \
  --header "Authorization: Bearer MCP-USER-API-KEY"
```

#### Other MCP Clients

For connections without using `mcp-remote` you can use this configuration format:

```json
{
  "mcpServers": {
    "Payload": {
      "type": "http",
      "url": "http://localhost:3000/api/mcp",
      "headers": {
        "Authorization": "Bearer MCP-USER-API-KEY"
      }
    }
  }
}
```

## Testing Your MCP Endpoint

The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the recommended
way to explore and test your MCP server interactively:

```bash
npx @modelcontextprotocol/inspector
```

Open the inspector, set the URL to `http://127.0.0.1:3000/api/mcp`, and add an
`Authorization: Bearer MCP-USER-API-KEY` header.

You can also test directly with `curl`:

```bash
curl -i 'http://localhost:3000/api/mcp' \
  -X POST \
  -H 'Authorization: Bearer MCP-USER-API-KEY' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{}}'
```

## Customizations

The plugin supports fully custom `prompts`, `tools` and `resources` that can be called or retrieved by MCP clients.
After defining a custom method you can allow / disallow the feature from the admin panel by adjusting the `API Key` MCP Options checklist.

## Globals

Globals are singleton configuration objects (e.g. site settings, navigation). The plugin
exposes them as `find` and `update` tools — one tool per global per operation.

```ts
mcpPlugin({
  globals: {
    'site-settings': {
      enabled: {
        find: true,
        update: true,
      },
      description:
        'Site-wide configuration settings including name, description, and maintenance mode.',
    },
  },
})
```

This produces two MCP tools: `findSiteSettings` and `updateSiteSettings`. Globals do not
support `create` or `delete` because they are singletons managed by Payload.

## Prompts

Prompts allow models to generate structured messages for specific tasks. Each prompt defines a schema for arguments and returns formatted messages:

```ts
prompts: [
  {
    name: 'reviewContent',
    title: 'Content Review Prompt',
    description: 'Creates a prompt for reviewing content quality',
    argsSchema: {
      content: z.string().describe('The content to review'),
      criteria: z.array(z.string()).describe('Review criteria'),
    },
    handler: ({ content, criteria }, req) => ({
      messages: [
        {
          content: {
            type: 'text',
            text: `Please review this content based on the following criteria: ${criteria.join(', ')}\n\nContent: ${content}`,
          },
          role: 'user',
        },
      ],
    }),
  },
]
```

## Resources

Resources provide access to data or content that models can read. They can be static or dynamic with parameterized URIs:

```ts
resources: [
  // Static resource
  {
    name: 'guidelines',
    title: 'Content Guidelines',
    description: 'Company content creation guidelines',
    uri: 'guidelines://company',
    mimeType: 'text/markdown',
    handler: (uri, req) => ({
      contents: [
        {
          uri: uri.href,
          text: '# Content Guidelines\n\n1. Keep it concise\n2. Use clear language',
        },
      ],
    }),
  },

  // Dynamic resource with template
  {
    name: 'userProfile',
    title: 'User Profile',
    description: 'Access user profile information',
    uri: new ResourceTemplate('users://profile/{userId}', { list: undefined }),
    mimeType: 'application/json',
    handler: async (uri, { userId }, req) => {
      // Fetch user data from your system
      const userData = await getUserById(userId)
      return {
        contents: [
          {
            uri: uri.href,
            text: JSON.stringify(userData, null, 2),
          },
        ],
      }
    },
  },
]
```

## Tools

Tools allow you to extend MCP capabilities beyond basic CRUD operations. Use them when you need to perform complex queries, aggregations, or business logic that isn't covered by the standard collection operations.

Every tool `handler` receives `(args, req)` where `req` is the full Payload
[`PayloadRequest`](https://payloadcms.com/docs/rest-api/overview#custom-routes) object. From it you have access to:

| Property      | Description                                                                                                |
| ------------- | ---------------------------------------------------------------------------------------------------------- |
| `req.payload` | The initialised Payload instance — use it to call `payload.find`, `payload.create`, `payload.update`, etc. |
| `req.user`    | The authenticated user making the request (the MCP API key owner).                                         |
| `req.locale`  | The active locale, if localisation is enabled.                                                             |
| `req.headers` | Incoming request headers.                                                                                  |

Always pass `req` through to Payload operations and set `overrideAccess: false` so the
request inherits the API key owner's access control rules.

```ts
tools: [
  {
    name: 'getPostScores',
    description: 'Get useful scores about content in posts',
    handler: async (args, req) => {
      const { payload } = req
      const stats = await payload.find({
        collection: 'posts',
        where: {
          createdAt: {
            greater_than: args.since,
          },
        },
        req,
        overrideAccess: false,
        user: req.user,
      })

      return {
        content: [
          {
            type: 'text',
            text: `Found ${stats.totalDocs} posts created since ${args.since}`,
          },
        ],
      }
    },
    parameters: z.object({
      since: z.string().describe('ISO date string for filtering posts'),
    }).shape,
  },
]
```

## Reducing Token Usage with `select`

All collection and global tools accept an optional `select` parameter. It limits which
fields are returned in the response, which can significantly reduce token consumption
when working with large documents.

`select` is a JSON string that follows [Payload's Select API](https://payloadcms.com/docs/queries/select)
syntax — set a field to `true` to include it:

```ts
// In a tool call, pass select as a JSON string:
// arguments: { select: '{"title": true, "slug": true}' }
```

For example, to list only post titles:

```bash
curl -i 'http://localhost:3000/api/mcp' \
  -X POST \
  -H 'Authorization: Bearer MCP-USER-API-KEY' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "tools/call",
    "params": {
      "name": "findPosts",
      "arguments": {
        "select": "{\"title\": true, \"slug\": true}"
      }
    }
  }'
```

Without `select`, the full document is returned for every result. On collections with
many fields or rich text, this can exhaust a model's context budget quickly.

## Modifying Responses

Use `overrideResponse` on a collection or global to intercept what is sent back to the
model after any operation. This is the primary place to sanitize sensitive data.

```ts
mcpPlugin({
  collections: {
    posts: {
      enabled: true,
      overrideResponse: (response, doc, req) => {
        req.payload.logger.info('[MCP] Post response intercepted')

        // Append additional context
        response.content.push({
          type: 'text',
          text: `Document last modified by: ${doc.updatedBy ?? 'unknown'}`,
        })

        return response
      },
    },
    users: {
      enabled: { find: true },
      overrideResponse: (response, doc, req) => {
        // Redact sensitive fields before the model sees the document
        response.content = response.content.map((item) => ({
          ...item,
          text: item.text
            .replace(/"hash":\s*"[^"]*"/g, '"hash": "[redacted]"')
            .replace(/"salt":\s*"[^"]*"/g, '"salt": "[redacted]"'),
        }))
        return response
      },
    },
  },
})
```

<Banner type="warning">
  Models receive the full document by default. Use `overrideResponse` or
  `select` to remove sensitive fields before they reach the model.
</Banner>

## API Key Access to MCP

Payload adds an API key collection that allows admins to manage MCP capabilities. Admins can:

- Create user associated API keys for MCP clients
- `Allow` or `disallow` endpoint traffic in real-time
- `Allow` or `disallow` tools, resources, and prompts

You can customize the API Key collection using the `overrideApiKeyCollection` option:

```ts
mcpPlugin({
  overrideApiKeyCollection: (collection) => {
    // Add fields to the API Keys collection
    collection.fields.push({
      name: 'department',
      type: 'select',
      options: [
        { label: 'Development', value: 'dev' },
        { label: 'Marketing', value: 'marketing' },
      ],
    })

    // You can also add hooks
    collection.hooks?.beforeRead?.push(({ doc, req }) => {
      req.payload.logger.info('Before Read MCP hook!')
      return doc
    })
    return collection
  },
  // ... other options
})
```

You can create an MCP access strategy using the `overrideAuth` option:

```ts
import { type MCPAccessSettings, mcpPlugin } from '@payloadcms/plugin-mcp'

// ... other config

mcpPlugin({
  overrideAuth: (req, getDefaultMcpAccessSettings) => {
    const { payload } = req

    // This will return the default MCPAccessSettings
    // getDefaultMcpAccessSettings()

    payload.logger.info('Custom access Settings for all MCP traffic')
    return {
      posts: {
        find: true,
      },
      products: {
        find: true,
      },
    } as MCPAccessSettings
  },
  // ... other options
})
```

If you want the default `MCPAccessSettings`, you can use the additional argument `getDefaultMcpAccessSettings`.
This will use the Bearer token found in the headers on the req to return the `MCPAccessSettings` related to the user assigned to the API key.

## Hooks

To understand or modify data returned by models at runtime use a collection [Hook](https://payloadcms.com/docs/hooks/collections). Within a hook you can look up the API context. If the context is `MCP` that collection was triggered by the MCP Plugin. This does not apply to custom tools or resources that have their own context, and can make unrelated database calls.

In this example, Post titles are modified to include '(MCP Hook Override)' when they are read using MCP.

```ts
import type { CollectionConfig } from 'payload'

export const Posts: CollectionConfig = {
  slug: 'posts',
  fields: [
    {
      name: 'title',
      type: 'text',
      admin: {
        description: 'The title of the post',
      },
      required: true,
    },

    // ... other fields
  ],
  hooks: {
    beforeRead: [
      ({ doc, req }) => {
        if (req.payloadAPI === 'MCP') {
          doc.title = `${doc.title} (MCP Hook Override)`
        }
        return doc
      },
    ],
  },
}
```

## Localization

When your Payload config has localization enabled, all collection and global tools
automatically include `locale` and `fallbackLocale` parameters — no extra plugin
configuration is required.

| Parameter        | Description                                                                                                    |
| ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `locale`         | Retrieve or write data in a specific locale (e.g. `"en"`, `"es"`). Pass `"all"` to return all locales at once. |
| `fallbackLocale` | The locale to use when the requested locale has no translation for a field.                                    |

Example — find posts in Spanish with English as fallback:

```bash
curl -i 'http://localhost:3000/api/mcp' \
  -X POST \
  -H 'Authorization: Bearer MCP-USER-API-KEY' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "tools/call",
    "params": {
      "name": "findPosts",
      "arguments": {
        "locale": "es",
        "fallbackLocale": "en"
      }
    }
  }'
```

## Tracking MCP Events

Use the `onEvent` callback to receive a notification for every MCP request processed
by your server. This is useful for analytics, audit logging, or debugging.

```ts
mcpPlugin({
  mcp: {
    handlerOptions: {
      onEvent: (event) => {
        // Forward to your analytics pipeline, audit log, etc.
        console.log('[MCP event]', event)
      },
    },
  },
})
```

## Virtual Fields

Virtual fields (computed, read-only fields) are automatically excluded from the `create`
and `update` tool schemas. They cannot be set by a model, so including them in the schema
would be misleading and add noise. They are still present in `find` responses.

## Performance

There are several levers for reducing the number of tokens consumed per MCP request.
Token efficiency matters because large responses can exhaust a model's context budget,
slow down responses, and increase cost.

### Write strong descriptions

The description you provide for a collection or global is the primary signal a model
uses to decide which tool to call. A vague description leads to missed or incorrect
tool calls; a precise description gets the right tool called on the first try.

```ts
// Weak — a model has no idea what kind of posts these are or when to use this collection
mcpPlugin({
  collections: {
    posts: {
      enabled: true,
      description: 'My posts',
    },
  },
})

// Strong — the model understands the content and its purpose
mcpPlugin({
  collections: {
    posts: {
      enabled: true,
      description:
        'Published articles covering science and nature topics, with title, body, tags, and publication date.',
    },
  },
})
```

### Use `select` to return only the fields you need

By default, the full document is returned for every operation. On collections with
many fields, rich text, or deeply nested relationships this can be very large. Pass
a `select` JSON string to limit what comes back:

```bash
# Return only title and slug instead of the full document
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"findPosts","arguments":{"select":"{\"title\": true, \"slug\": true}"}}}'
```

See [Reducing Token Usage with `select`](#reducing-token-usage-with-select) for more detail.

### Sanitize responses with `overrideResponse`

If a collection has fields that are irrelevant or sensitive for the model's task,
strip them before the response leaves the server. Fewer fields returned means fewer
tokens consumed on every call.

See [Modifying Responses](#modifying-responses) for examples.

### Only enable the operations you need

If a model only needs to read data, enable only `find`. Every additional operation
(`create`, `update`, `delete`) adds more tools to the model's context, which costs
tokens and increases the chance of an unintended action.

```ts
mcpPlugin({
  collections: {
    posts: {
      enabled: { find: true }, // create / update / delete are off
    },
  },
})
```
