Skip to content
Open
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
71 changes: 69 additions & 2 deletions packages/plugin-mcp/src/mcp/tools/resource/find.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import type { PayloadRequest, SelectType, TypedUser } from 'payload'
import type { PayloadRequest, PopulateType, SelectType, TypedUser } from 'payload'

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

import { toCamelCase } from '../../../utils/camelCase.js'
import { toolSchemas } from '../schemas.js'

function parseJoins(
joins: boolean | string | undefined,
): false | Record<string, unknown> | undefined {
if (joins === undefined || joins === true) {
return undefined
}
if (joins === false) {
return false
}
try {
return JSON.parse(joins) as false | Record<string, unknown>
} catch {
return undefined
}
}

export const findResourceTool = (
server: McpServer,
req: PayloadRequest,
Expand All @@ -25,6 +41,10 @@ export const findResourceTool = (
locale?: string,
fallbackLocale?: string,
draft?: boolean,
populate?: string,
joins?: boolean | string,
trash?: boolean,
pagination?: boolean,
): Promise<{
content: Array<{
text: string
Expand Down Expand Up @@ -83,6 +103,27 @@ export const findResourceTool = (
}
}

let populateClause: PopulateType | undefined
if (populate) {
try {
populateClause = JSON.parse(populate) as PopulateType
} catch (_parseError) {
payload.logger.warn(`[payload-mcp] Invalid populate clause JSON: ${populate}`)
const response = {
content: [{ type: 'text' as const, text: 'Error: Invalid JSON in populate clause' }],
}
return (collections?.[collectionSlug]?.overrideResponse?.(response, {}, req) ||
response) as {
content: Array<{
text: string
type: 'text'
}>
}
}
}

const joinsClause = parseJoins(joins)

// If ID is provided, use findByID
if (id) {
try {
Expand All @@ -91,6 +132,9 @@ export const findResourceTool = (
collection: collectionSlug,
depth,
...(selectClause && { select: selectClause }),
...(populateClause && { populate: populateClause }),
...(joinsClause !== undefined && { joins: joinsClause }),
...(trash !== undefined && { trash }),
overrideAccess: false,
req,
user,
Expand Down Expand Up @@ -152,6 +196,10 @@ ${JSON.stringify(doc, null, 2)}`,
req,
user,
...(selectClause && { select: selectClause }),
...(populateClause && { populate: populateClause }),
...(joinsClause !== undefined && { joins: joinsClause }),
...(trash !== undefined && { trash }),
...(pagination !== undefined && { pagination }),
...(locale && { locale }),
...(fallbackLocale && { fallbackLocale }),
...(draft !== undefined && { draft }),
Expand Down Expand Up @@ -227,7 +275,22 @@ Page: ${result.page} of ${result.totalPages}
description: `${collections?.[collectionSlug]?.description || toolSchemas.findResources.description.trim()}`,
inputSchema: toolSchemas.findResources.parameters.shape,
},
async ({ id, depth, draft, fallbackLocale, limit, locale, page, select, sort, where }) => {
async ({
id,
depth,
draft,
fallbackLocale,
joins,
limit,
locale,
page,
pagination,
populate,
select,
sort,
trash,
where,
}) => {
return await tool(
id,
limit,
Expand All @@ -239,6 +302,10 @@ Page: ${result.page} of ${result.totalPages}
locale,
fallbackLocale,
draft,
populate,
joins,
trash,
pagination,
)
},
)
Expand Down
24 changes: 24 additions & 0 deletions packages/plugin-mcp/src/mcp/tools/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export const toolSchemas = {
.string()
.optional()
.describe('Optional: fallback locale code to use when requested locale is not available'),
joins: z
.union([z.boolean(), z.string()])
.optional()
.describe(
'Set to false to disable all join fields. Pass JSON string for specific join field queries.',
),
limit: z
.number()
.int()
Expand All @@ -79,6 +85,18 @@ export const toolSchemas = {
.optional()
.default(1)
.describe('Page number for pagination (default: 1)'),
pagination: z
.boolean()
.optional()
.describe(
'Set to false to skip the count query and avoid overhead. Use with limit to get documents without total count.',
),
populate: z
.string()
.optional()
.describe(
'Optional JSON to control which fields to include from populated documents. Reduces response size.',
),
select: z
.string()
.optional()
Expand All @@ -89,6 +107,12 @@ export const toolSchemas = {
.string()
.optional()
.describe('Field to sort by (e.g., "createdAt", "-updatedAt" for descending)'),
trash: z
.boolean()
.optional()
.describe(
'When true, includes soft-deleted documents. Only applies when collection has trash enabled.',
),
where: z
.string()
.optional()
Expand Down
Loading