Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 96 additions & 42 deletions docs/plugins/mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,94 @@ export default config
| `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'). |

## 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 API-KEY-HERE"
]
}
}
}
```

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

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

#### 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 API-KEY-HERE"
}
}
}
}
```

## 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.

## Prompts

Prompts allow models to generate structured messages for specific tasks. Each prompt defines a schema for arguments and returns formatted messages:
Expand All @@ -126,7 +214,7 @@ prompts: [
content: z.string().describe('The content to review'),
criteria: z.array(z.string()).describe('Review criteria'),
},
handler: ({ content, criteria }) => ({
handler: ({ content, criteria }, req) => ({
messages: [
{
content: {
Expand Down Expand Up @@ -154,6 +242,7 @@ resources: [
description: 'Company content creation guidelines',
uri: 'guidelines://company',
mimeType: 'text/markdown',
handler: (uri, req) => ({
handler: (uri, req) => ({
contents: [
{
Expand Down Expand Up @@ -198,7 +287,6 @@ tools: [
description: 'Get useful scores about content in posts',
handler: async (args, req) => {
const { payload } = req

const stats = await payload.find({
collection: 'posts',
where: {
Expand Down Expand Up @@ -249,6 +337,12 @@ mcpPlugin({
{ 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
Expand Down Expand Up @@ -363,43 +457,3 @@ const config = buildConfig({
],
})
```

## MCP Clients

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.

> Caution: the format of these JSON files may change over time. Please check the client website for updates.

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

```json
{
"mcp.servers": {
"Payload": {
"url": "http://localhost:3000/api/mcp",
"headers": {
"Authorization": "Bearer API-KEY-HERE"
}
}
}
}
```

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

```json
{
"mcpServers": {
"Payload": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://localhost:3000/api/mcp",
"--header",
"Authorization: Bearer API-KEY-HERE"
]
}
}
}
```
44 changes: 40 additions & 4 deletions packages/plugin-mcp/src/mcp/tools/resource/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import type { JSONSchema4 } from 'json-schema'
import type { PayloadRequest, TypedUser } from 'payload'

import { z } from 'zod'

import type { PluginMCPServerConfig } from '../../../types.js'

import { toCamelCase } from '../../../utils/camelCase.js'
Expand All @@ -18,6 +20,9 @@ export const createResourceTool = (
) => {
const tool = async (
data: string,
draft: boolean,
locale?: string,
fallbackLocale?: string,
): Promise<{
content: Array<{
text: string
Expand All @@ -27,7 +32,9 @@ export const createResourceTool = (
const payload = req.payload

if (verboseLogs) {
payload.logger.info(`[payload-mcp] Creating resource in collection: ${collectionSlug}`)
payload.logger.info(
`[payload-mcp] Creating resource in collection: ${collectionSlug}${locale ? ` with locale: ${locale}` : ''}`,
)
}

try {
Expand All @@ -51,9 +58,12 @@ export const createResourceTool = (
const result = await payload.create({
collection: collectionSlug,
data: parsedData,
draft,
overrideAccess: false,
req,
user,
...(locale && { locale }),
...(fallbackLocale && { fallbackLocale }),
})

if (verboseLogs) {
Expand Down Expand Up @@ -109,13 +119,39 @@ ${JSON.stringify(result, null, 2)}
if (collections?.[collectionSlug]?.enabled) {
const convertedFields = convertCollectionSchemaToZod(schema)

// Create a new schema that combines the converted fields with create-specific parameters
const createResourceSchema = z.object({
...convertedFields.shape,
draft: z
.boolean()
.optional()
.default(false)
.describe('Whether to create the document as a draft'),
fallbackLocale: z
.string()
.optional()
.describe('Optional: fallback locale code to use when requested locale is not available'),
locale: z
.string()
.optional()
.describe(
'Optional: locale code to create the document in (e.g., "en", "es"). Defaults to the default locale',
),
})

server.tool(
`create${collectionSlug.charAt(0).toUpperCase() + toCamelCase(collectionSlug).slice(1)}`,
`${collections?.[collectionSlug]?.description || toolSchemas.createResource.description.trim()}`,
convertedFields.shape,
createResourceSchema.shape,
async (params: Record<string, unknown>) => {
const data = JSON.stringify(params)
return await tool(data)
const { draft, fallbackLocale, locale, ...fieldData } = params
const data = JSON.stringify(fieldData)
return await tool(
data,
draft as boolean,
locale as string | undefined,
fallbackLocale as string | undefined,
)
},
)
}
Expand Down
12 changes: 8 additions & 4 deletions packages/plugin-mcp/src/mcp/tools/resource/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ export const deleteResourceTool = (
collections: PluginMCPServerConfig['collections'],
) => {
const tool = async (
id?: string,
id?: number | string,
where?: string,
depth: number = 0,
locale?: string,
fallbackLocale?: string,
): Promise<{
content: Array<{
text: string
Expand All @@ -28,7 +30,7 @@ export const deleteResourceTool = (

if (verboseLogs) {
payload.logger.info(
`[payload-mcp] Deleting resource from collection: ${collectionSlug}${id ? ` with ID: ${id}` : ' with where clause'}`,
`[payload-mcp] Deleting resource from collection: ${collectionSlug}${id ? ` with ID: ${id}` : ' with where clause'}${locale ? `, locale: ${locale}` : ''}`,
)
}

Expand Down Expand Up @@ -80,6 +82,8 @@ export const deleteResourceTool = (
overrideAccess: false,
req,
user,
...(locale && { locale }),
...(fallbackLocale && { fallbackLocale }),
}

// Delete by ID or where clause
Expand Down Expand Up @@ -204,8 +208,8 @@ ${JSON.stringify(errors, null, 2)}
`delete${collectionSlug.charAt(0).toUpperCase() + toCamelCase(collectionSlug).slice(1)}`,
`${collections?.[collectionSlug]?.description || toolSchemas.deleteResource.description.trim()}`,
toolSchemas.deleteResource.parameters.shape,
async ({ id, depth, where }) => {
return await tool(id, where, depth)
async ({ id, depth, fallbackLocale, locale, where }) => {
return await tool(id, where, depth, locale, fallbackLocale)
},
)
}
Expand Down
14 changes: 10 additions & 4 deletions packages/plugin-mcp/src/mcp/tools/resource/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ export const findResourceTool = (
collections: PluginMCPServerConfig['collections'],
) => {
const tool = async (
id?: string,
id?: number | string,
limit: number = 10,
page: number = 1,
sort?: string,
where?: string,
locale?: string,
fallbackLocale?: string,
): Promise<{
content: Array<{
text: string
Expand All @@ -30,7 +32,7 @@ export const findResourceTool = (

if (verboseLogs) {
payload.logger.info(
`[payload-mcp] Reading resource from collection: ${collectionSlug}${id ? ` with ID: ${id}` : ''}, limit: ${limit}, page: ${page}`,
`[payload-mcp] Reading resource from collection: ${collectionSlug}${id ? ` with ID: ${id}` : ''}, limit: ${limit}, page: ${page}${locale ? `, locale: ${locale}` : ''}`,
)
}

Expand Down Expand Up @@ -67,6 +69,8 @@ export const findResourceTool = (
overrideAccess: false,
req,
user,
...(locale && { locale }),
...(fallbackLocale && { fallbackLocale }),
})

if (verboseLogs) {
Expand Down Expand Up @@ -120,6 +124,8 @@ ${JSON.stringify(doc, null, 2)}`,
page,
req,
user,
...(locale && { locale }),
...(fallbackLocale && { fallbackLocale }),
}

if (sort) {
Expand Down Expand Up @@ -190,8 +196,8 @@ Page: ${result.page} of ${result.totalPages}
`find${collectionSlug.charAt(0).toUpperCase() + toCamelCase(collectionSlug).slice(1)}`,
`${collections?.[collectionSlug]?.description || toolSchemas.findResources.description.trim()}`,
toolSchemas.findResources.parameters.shape,
async ({ id, limit, page, sort, where }) => {
return await tool(id, limit, page, sort, where)
async ({ id, fallbackLocale, limit, locale, page, sort, where }) => {
return await tool(id, limit, page, sort, where, locale, fallbackLocale)
},
)
}
Expand Down
Loading