Skip to content

Commit 744a593

Browse files
feat(plugin-mcp): add draft parameter support to MCP find resource tool (#14924)
### What? Add `draft` parameter support to the MCP plugin's find tool ### Why? MCP clients currently have no way to query draft/unpublished documents. When a collection has drafts enabled, agents need the ability to retrieve draft versions of documents for preview, editing workflows, or content management tasks. ### How? - Added `draft` boolean parameter to the `findResources` schema in `schemas.ts` - Updated `find.ts` to accept and pass the `draft` parameter to both `payload.find()` and `payload.findByID()` calls - Enabled drafts on the test Posts collection to support testing - Added integration tests Co-authored-by: Kendell Joseph <kjoseph@figma.com>
1 parent cc33129 commit 744a593

4 files changed

Lines changed: 116 additions & 2 deletions

File tree

packages/plugin-mcp/src/mcp/tools/resource/find.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const findResourceTool = (
2222
where?: string,
2323
locale?: string,
2424
fallbackLocale?: string,
25+
draft?: boolean,
2526
): Promise<{
2627
content: Array<{
2728
text: string
@@ -71,6 +72,7 @@ export const findResourceTool = (
7172
user,
7273
...(locale && { locale }),
7374
...(fallbackLocale && { fallbackLocale }),
75+
...(draft !== undefined && { draft }),
7476
})
7577

7678
if (verboseLogs) {
@@ -126,6 +128,7 @@ ${JSON.stringify(doc, null, 2)}`,
126128
user,
127129
...(locale && { locale }),
128130
...(fallbackLocale && { fallbackLocale }),
131+
...(draft !== undefined && { draft }),
129132
}
130133

131134
if (sort) {
@@ -196,8 +199,8 @@ Page: ${result.page} of ${result.totalPages}
196199
`find${collectionSlug.charAt(0).toUpperCase() + toCamelCase(collectionSlug).slice(1)}`,
197200
`${collections?.[collectionSlug]?.description || toolSchemas.findResources.description.trim()}`,
198201
toolSchemas.findResources.parameters.shape,
199-
async ({ id, fallbackLocale, limit, locale, page, sort, where }) => {
200-
return await tool(id, limit, page, sort, where, locale, fallbackLocale)
202+
async ({ id, draft, fallbackLocale, limit, locale, page, sort, where }) => {
203+
return await tool(id, limit, page, sort, where, locale, fallbackLocale, draft)
201204
},
202205
)
203206
}

packages/plugin-mcp/src/mcp/tools/schemas.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ export const toolSchemas = {
3434
.describe(
3535
'Optional: specific document ID to retrieve. If not provided, returns all documents',
3636
),
37+
draft: z
38+
.boolean()
39+
.optional()
40+
.describe(
41+
'Optional: Whether the document should be queried from the versions table/collection or not.',
42+
),
3743
fallbackLocale: z
3844
.string()
3945
.optional()

test/plugin-mcp/collections/Posts.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import type { CollectionConfig } from 'payload'
22

33
export const Posts: CollectionConfig = {
44
slug: 'posts',
5+
versions: {
6+
drafts: true,
7+
},
58
fields: [
69
{
710
name: 'title',

test/plugin-mcp/int.spec.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,108 @@ describe('@payloadcms/plugin-mcp', () => {
769769
)
770770
})
771771

772+
describe('Drafts', () => {
773+
it('should include draft parameter in find tool schema', async () => {
774+
const apiKey = await getApiKey()
775+
const response = await restClient.POST('/mcp', {
776+
headers: {
777+
Authorization: `Bearer ${apiKey}`,
778+
Accept: 'application/json, text/event-stream',
779+
'Content-Type': 'application/json',
780+
},
781+
body: JSON.stringify({
782+
id: 1,
783+
jsonrpc: '2.0',
784+
method: 'tools/list',
785+
params: {},
786+
}),
787+
})
788+
789+
const json = await parseStreamResponse(response)
790+
const findTool = json.result.tools.find((t: any) => t.name === 'findPosts')
791+
792+
expect(findTool).toBeDefined()
793+
expect(findTool.inputSchema.properties.draft).toBeDefined()
794+
expect(findTool.inputSchema.properties.draft.type).toBe('boolean')
795+
expect(findTool.inputSchema.properties.draft.description).toContain('versions')
796+
})
797+
798+
it('should find draft documents when draft is true', async () => {
799+
// Create a published post
800+
const post = await payload.create({
801+
collection: 'posts',
802+
data: {
803+
title: 'Published Title',
804+
content: 'Published Content',
805+
},
806+
})
807+
808+
// Update with draft content (not published)
809+
await payload.update({
810+
id: post.id,
811+
collection: 'posts',
812+
data: {
813+
title: 'Draft Title',
814+
content: 'Draft Content',
815+
},
816+
draft: true,
817+
})
818+
819+
const apiKey = await getApiKey()
820+
821+
// Find without draft flag - should get published version
822+
const publishedResponse = await restClient.POST('/mcp', {
823+
headers: {
824+
Authorization: `Bearer ${apiKey}`,
825+
Accept: 'application/json, text/event-stream',
826+
'Content-Type': 'application/json',
827+
},
828+
body: JSON.stringify({
829+
id: 1,
830+
jsonrpc: '2.0',
831+
method: 'tools/call',
832+
params: {
833+
name: 'findPosts',
834+
arguments: {
835+
id: post.id,
836+
},
837+
},
838+
}),
839+
})
840+
841+
const publishedJson = await parseStreamResponse(publishedResponse)
842+
expect(publishedJson.result.content[0].text).toContain(
843+
'"title": "Published Title (MCP Hook Override)"',
844+
)
845+
846+
// Find with draft: true - should get draft version
847+
const draftResponse = await restClient.POST('/mcp', {
848+
headers: {
849+
Authorization: `Bearer ${apiKey}`,
850+
Accept: 'application/json, text/event-stream',
851+
'Content-Type': 'application/json',
852+
},
853+
body: JSON.stringify({
854+
id: 1,
855+
jsonrpc: '2.0',
856+
method: 'tools/call',
857+
params: {
858+
name: 'findPosts',
859+
arguments: {
860+
id: post.id,
861+
draft: true,
862+
},
863+
},
864+
}),
865+
})
866+
867+
const draftJson = await parseStreamResponse(draftResponse)
868+
expect(draftJson.result.content[0].text).toContain(
869+
'"title": "Draft Title (MCP Hook Override)"',
870+
)
871+
})
872+
})
873+
772874
it('should find site-settings global', async () => {
773875
const apiKey = await getApiKey(false, false, true)
774876
const response = await restClient.POST('/mcp', {

0 commit comments

Comments
 (0)