Skip to content

Commit a28261d

Browse files
fix(plugin-mcp): create and update resource tools now support point fields (#15381)
Fixes: #15287 The Create and Update Resource tools were expecting a tuple for point fields, however they needed to also accept nest lat/lng objects. Tools affected: - createResource - updateResource
1 parent 3d357b3 commit a28261d

8 files changed

Lines changed: 399 additions & 176 deletions

File tree

‎packages/plugin-mcp/src/mcp/tools/resource/create.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { PluginMCPServerConfig } from '../../../types.js'
88

99
import{toCamelCase}from'../../../utils/camelCase.js'
1010
import{convertCollectionSchemaToZod}from'../../../utils/convertCollectionSchemaToZod.js'
11+
import{transformPointDataToPayload}from'../../../utils/transformPointDataToPayload.js'
1112
import{toolSchemas}from'../schemas.js'
1213
exportconstcreateResourceTool=(
1314
server: McpServer,
@@ -44,6 +45,10 @@ export const createResourceTool = (
4445
letparsedData: Record<string,unknown>
4546
try{
4647
parsedData=JSON.parse(data)
48+
49+
// Transform point fields from object format to tuple array
50+
parsedData=transformPointDataToPayload(parsedData)
51+
4752
if(verboseLogs){
4853
payload.logger.info(
4954
`[payload-mcp] Parsed data for ${collectionSlug}: ${JSON.stringify(parsedData)}`,

‎packages/plugin-mcp/src/mcp/tools/resource/update.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { PluginMCPServerConfig } from '../../../types.js'
88

99
import{toCamelCase}from'../../../utils/camelCase.js'
1010
import{convertCollectionSchemaToZod}from'../../../utils/convertCollectionSchemaToZod.js'
11+
import{transformPointDataToPayload}from'../../../utils/transformPointDataToPayload.js'
1112
import{toolSchemas}from'../schemas.js'
1213
exportconstupdateResourceTool=(
1314
server: McpServer,
@@ -49,6 +50,10 @@ export const updateResourceTool = (
4950
letparsedData: Record<string,unknown>
5051
try{
5152
parsedData=JSON.parse(data)
53+
54+
// Transform point fields from object format to tuple array
55+
parsedData=transformPointDataToPayload(parsedData)
56+
5257
if(verboseLogs){
5358
payload.logger.info(
5459
`[payload-mcp] Parsed data for ${collectionSlug}: ${JSON.stringify(parsedData)}`,

‎packages/plugin-mcp/src/utils/convertCollectionSchemaToZod.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { jsonSchemaToZod } from 'json-schema-to-zod'
44
import*astsfrom'typescript'
55
import{z}from'zod'
66

7+
import{transformPointFieldsForMCP}from'./transformPointFields.js'
8+
79
/**
810
* Recursively processes JSON schema properties to simplify relationship fields.
911
* For create/update validation we only need to accept IDs (string/number),
@@ -65,7 +67,8 @@ export const convertCollectionSchemaToZod = (schema: JSONSchema4) => {
6567
}
6668
}
6769

68-
constsimplifiedSchema=simplifyRelationshipFields(schemaClone)
70+
constpointTransformed=transformPointFieldsForMCP(schemaClone)
71+
constsimplifiedSchema=simplifyRelationshipFields(pointTransformed)
6972

7073
constzodSchemaAsString=jsonSchemaToZod(simplifiedSchema)
7174

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Transforms incoming MCP tool data from object format to tuple array format.
3+
* Converts { longitude: number, latitude: number } back to [longitude, latitude]
4+
* for Payload's internal point field representation.
5+
*/
6+
exportfunctiontransformPointDataToPayload(
7+
data: Record<string,unknown>,
8+
): Record<string,unknown>{
9+
if(!data||typeofdata!=='object'){
10+
returndata
11+
}
12+
13+
consttransformed: Record<string,unknown>={}
14+
15+
for(const[key,value]ofObject.entries(data)){
16+
if(
17+
value&&
18+
typeofvalue==='object'&&
19+
'longitude'invalue&&
20+
'latitude'invalue&&
21+
typeofvalue.longitude==='number'&&
22+
typeofvalue.latitude==='number'
23+
){
24+
// Transform to tuple array [longitude, latitude]
25+
transformed[key]=[value.longitude,value.latitude]
26+
}elseif(Array.isArray(value)){
27+
transformed[key]=value.map((item)=>
28+
typeofitem==='object'
29+
? transformPointDataToPayload(itemasRecord<string,unknown>)
30+
: item,
31+
)
32+
}elseif(value&&typeofvalue==='object'){
33+
transformed[key]=transformPointDataToPayload(valueasRecord<string,unknown>)
34+
}else{
35+
transformed[key]=value
36+
}
37+
}
38+
39+
returntransformed
40+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
importtype{JSONSchema4}from'json-schema'
2+
3+
exportfunctiontransformPointFieldsForMCP(schema: JSONSchema4): JSONSchema4{
4+
if(!schema||typeofschema!=='object'){
5+
returnschema
6+
}
7+
8+
consttransformed={ ...schema}
9+
10+
if(transformed.properties&&typeoftransformed.properties==='object'){
11+
transformed.properties=Object.fromEntries(
12+
Object.entries(transformed.properties).map(([key,value])=>{
13+
constisArrayType=
14+
value.type==='array'||(Array.isArray(value.type)&&value.type.includes('array'))
15+
16+
if(
17+
value&&
18+
typeofvalue==='object'&&
19+
isArrayType&&
20+
Array.isArray(value.items)&&
21+
value.items.length===2&&
22+
value.items.every((item: JSONSchema4)=>item?.type==='number')
23+
){
24+
// Transform to object format
25+
constisNullable=Array.isArray(value.type)&&value.type.includes('null')
26+
27+
return[
28+
key,
29+
{
30+
type: isNullable ? ['object','null'] : 'object',
31+
description: value.description||'Geographic coordinates (longitude, latitude)',
32+
properties: {
33+
latitude: {type: 'number',description: 'Latitude coordinate'},
34+
longitude: {type: 'number',description: 'Longitude coordinate'},
35+
},
36+
required: ['longitude','latitude'],
37+
},
38+
]
39+
}
40+
41+
return[key,transformPointFieldsForMCP(value)]
42+
}),
43+
)
44+
}
45+
46+
if(
47+
transformed.items&&
48+
typeoftransformed.items==='object'&&
49+
!Array.isArray(transformed.items)
50+
){
51+
transformed.items=transformPointFieldsForMCP(transformed.items)
52+
}
53+
54+
returntransformed
55+
}

‎test/plugin-mcp/collections/Posts.ts‎

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

33
exportconstPosts: CollectionConfig={
44
slug: 'posts',
5-
versions: {
6-
drafts: true,
7-
},
85
fields: [
96
{
107
name: 'title',
118
type: 'text',
12-
localized: true,
139
admin: {
1410
description: 'The title of the post',
1511
},
12+
localized: true,
1613
required: true,
1714
},
1815
{
1916
name: 'content',
2017
type: 'text',
21-
localized: true,
2218
admin: {
2319
description: 'The content of the post',
2420
},
2521
defaultValue: 'Hello World.',
22+
localized: true,
2623
},
2724
{
2825
name: 'author',
2926
type: 'relationship',
30-
relationTo: 'users',
3127
admin: {
3228
description: 'The author of the post',
3329
},
30+
relationTo: 'users',
31+
},
32+
{
33+
name: 'location',
34+
type: 'point',
35+
admin: {
36+
description: 'Geographic location coordinates',
37+
},
3438
},
3539
],
3640
hooks: {
@@ -52,4 +56,7 @@ export const Posts: CollectionConfig = {
5256
},
5357
],
5458
},
59+
versions: {
60+
drafts: true,
61+
},
5562
}

0 commit comments

Comments
 (0)