@payloadcms/plugin-mcp 4.0.0-internal.a0ef1b8 → 4.0.0-internal.a2d053c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/defaultAccess.d.ts +3 -0
  3. package/dist/defaultAccess.d.ts.map +1 -0
  4. package/dist/defaultAccess.js +3 -0
  5. package/dist/defaultAccess.js.map +1 -0
  6. package/dist/defineTool.d.ts +8 -8
  7. package/dist/defineTool.d.ts.map +1 -1
  8. package/dist/endpoint/access.d.ts +12 -5
  9. package/dist/endpoint/access.d.ts.map +1 -1
  10. package/dist/endpoint/access.js +95 -88
  11. package/dist/endpoint/access.js.map +1 -1
  12. package/dist/endpoint/index.d.ts +1 -1
  13. package/dist/endpoint/index.d.ts.map +1 -1
  14. package/dist/endpoint/index.js +109 -16
  15. package/dist/endpoint/index.js.map +1 -1
  16. package/dist/endpoint/oauth.d.ts +39 -0
  17. package/dist/endpoint/oauth.d.ts.map +1 -0
  18. package/dist/endpoint/oauth.js +94 -0
  19. package/dist/endpoint/oauth.js.map +1 -0
  20. package/dist/endpoint/oauthLoginToken.d.ts +21 -0
  21. package/dist/endpoint/oauthLoginToken.d.ts.map +1 -0
  22. package/dist/endpoint/oauthLoginToken.js +79 -0
  23. package/dist/endpoint/oauthLoginToken.js.map +1 -0
  24. package/dist/endpoint/oauthServer.d.ts +12 -0
  25. package/dist/endpoint/oauthServer.d.ts.map +1 -0
  26. package/dist/endpoint/oauthServer.js +362 -0
  27. package/dist/endpoint/oauthServer.js.map +1 -0
  28. package/dist/exports/client.d.ts +1 -1
  29. package/dist/exports/client.d.ts.map +1 -1
  30. package/dist/exports/client.js +1 -1
  31. package/dist/exports/client.js.map +1 -1
  32. package/dist/exports/internal.d.ts +2 -0
  33. package/dist/exports/internal.d.ts.map +1 -0
  34. package/dist/exports/internal.js +3 -0
  35. package/dist/exports/internal.js.map +1 -0
  36. package/dist/index.d.ts +12 -4
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +30 -22
  39. package/dist/index.js.map +1 -1
  40. package/dist/mcp/buildMcpServer.d.ts +6 -6
  41. package/dist/mcp/buildMcpServer.d.ts.map +1 -1
  42. package/dist/mcp/buildMcpServer.js +146 -81
  43. package/dist/mcp/buildMcpServer.js.map +1 -1
  44. package/dist/mcp/builtin/collections/authTools.d.ts.map +1 -1
  45. package/dist/mcp/builtin/collections/authTools.js +123 -61
  46. package/dist/mcp/builtin/collections/authTools.js.map +1 -1
  47. package/dist/mcp/builtin/collections/countTool.d.ts +2 -0
  48. package/dist/mcp/builtin/collections/countTool.d.ts.map +1 -0
  49. package/dist/mcp/builtin/collections/countTool.js +63 -0
  50. package/dist/mcp/builtin/collections/countTool.js.map +1 -0
  51. package/dist/mcp/builtin/collections/countVersionsTool.d.ts +2 -0
  52. package/dist/mcp/builtin/collections/countVersionsTool.d.ts.map +1 -0
  53. package/dist/mcp/builtin/collections/countVersionsTool.js +60 -0
  54. package/dist/mcp/builtin/collections/countVersionsTool.js.map +1 -0
  55. package/dist/mcp/builtin/collections/createTool.d.ts +1 -1
  56. package/dist/mcp/builtin/collections/createTool.d.ts.map +1 -1
  57. package/dist/mcp/builtin/collections/createTool.js +114 -68
  58. package/dist/mcp/builtin/collections/createTool.js.map +1 -1
  59. package/dist/mcp/builtin/collections/deleteTool.d.ts +1 -1
  60. package/dist/mcp/builtin/collections/deleteTool.d.ts.map +1 -1
  61. package/dist/mcp/builtin/collections/deleteTool.js +55 -61
  62. package/dist/mcp/builtin/collections/deleteTool.js.map +1 -1
  63. package/dist/mcp/builtin/collections/duplicateTool.d.ts +2 -0
  64. package/dist/mcp/builtin/collections/duplicateTool.d.ts.map +1 -0
  65. package/dist/mcp/builtin/collections/duplicateTool.js +90 -0
  66. package/dist/mcp/builtin/collections/duplicateTool.js.map +1 -0
  67. package/dist/mcp/builtin/collections/fileInput.d.ts +28 -0
  68. package/dist/mcp/builtin/collections/fileInput.d.ts.map +1 -0
  69. package/dist/mcp/builtin/collections/fileInput.js +88 -0
  70. package/dist/mcp/builtin/collections/fileInput.js.map +1 -0
  71. package/dist/mcp/builtin/collections/findDistinctTool.d.ts +2 -0
  72. package/dist/mcp/builtin/collections/findDistinctTool.d.ts.map +1 -0
  73. package/dist/mcp/builtin/collections/findDistinctTool.js +77 -0
  74. package/dist/mcp/builtin/collections/findDistinctTool.js.map +1 -0
  75. package/dist/mcp/builtin/collections/findTool.d.ts +1 -1
  76. package/dist/mcp/builtin/collections/findTool.d.ts.map +1 -1
  77. package/dist/mcp/builtin/collections/findTool.js +62 -75
  78. package/dist/mcp/builtin/collections/findTool.js.map +1 -1
  79. package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts +2 -0
  80. package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts.map +1 -0
  81. package/dist/mcp/builtin/collections/findVersionByIDTool.js +74 -0
  82. package/dist/mcp/builtin/collections/findVersionByIDTool.js.map +1 -0
  83. package/dist/mcp/builtin/collections/findVersionsTool.d.ts +2 -0
  84. package/dist/mcp/builtin/collections/findVersionsTool.d.ts.map +1 -0
  85. package/dist/mcp/builtin/collections/findVersionsTool.js +84 -0
  86. package/dist/mcp/builtin/collections/findVersionsTool.js.map +1 -0
  87. package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts +2 -0
  88. package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts.map +1 -0
  89. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js +103 -0
  90. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js.map +1 -0
  91. package/dist/mcp/builtin/collections/restoreVersionTool.d.ts +2 -0
  92. package/dist/mcp/builtin/collections/restoreVersionTool.d.ts.map +1 -0
  93. package/dist/mcp/builtin/collections/restoreVersionTool.js +69 -0
  94. package/dist/mcp/builtin/collections/restoreVersionTool.js.map +1 -0
  95. package/dist/mcp/builtin/collections/updateTool.d.ts +1 -1
  96. package/dist/mcp/builtin/collections/updateTool.d.ts.map +1 -1
  97. package/dist/mcp/builtin/collections/updateTool.js +118 -137
  98. package/dist/mcp/builtin/collections/updateTool.js.map +1 -1
  99. package/dist/mcp/builtin/collections/uploadInstructionsTool.d.ts +2 -0
  100. package/dist/mcp/builtin/collections/uploadInstructionsTool.d.ts.map +1 -0
  101. package/dist/mcp/builtin/collections/uploadInstructionsTool.js +55 -0
  102. package/dist/mcp/builtin/collections/uploadInstructionsTool.js.map +1 -0
  103. package/dist/mcp/builtin/formatEntityError.d.ts +16 -0
  104. package/dist/mcp/builtin/formatEntityError.d.ts.map +1 -0
  105. package/dist/mcp/builtin/formatEntityError.js +55 -0
  106. package/dist/mcp/builtin/formatEntityError.js.map +1 -0
  107. package/dist/mcp/builtin/getConfigInfoTool.d.ts +2 -0
  108. package/dist/mcp/builtin/getConfigInfoTool.d.ts.map +1 -0
  109. package/dist/mcp/builtin/getConfigInfoTool.js +69 -0
  110. package/dist/mcp/builtin/getConfigInfoTool.js.map +1 -0
  111. package/dist/mcp/builtin/globals/countVersionsTool.d.ts +2 -0
  112. package/dist/mcp/builtin/globals/countVersionsTool.d.ts.map +1 -0
  113. package/dist/mcp/builtin/globals/countVersionsTool.js +60 -0
  114. package/dist/mcp/builtin/globals/countVersionsTool.js.map +1 -0
  115. package/dist/mcp/builtin/globals/findTool.d.ts.map +1 -1
  116. package/dist/mcp/builtin/globals/findTool.js +27 -36
  117. package/dist/mcp/builtin/globals/findTool.js.map +1 -1
  118. package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts +2 -0
  119. package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts.map +1 -0
  120. package/dist/mcp/builtin/globals/findVersionByIDTool.js +68 -0
  121. package/dist/mcp/builtin/globals/findVersionByIDTool.js.map +1 -0
  122. package/dist/mcp/builtin/globals/findVersionsTool.d.ts +2 -0
  123. package/dist/mcp/builtin/globals/findVersionsTool.d.ts.map +1 -0
  124. package/dist/mcp/builtin/globals/findVersionsTool.js +78 -0
  125. package/dist/mcp/builtin/globals/findVersionsTool.js.map +1 -0
  126. package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts +2 -0
  127. package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts.map +1 -0
  128. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js +81 -0
  129. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js.map +1 -0
  130. package/dist/mcp/builtin/globals/restoreVersionTool.d.ts +2 -0
  131. package/dist/mcp/builtin/globals/restoreVersionTool.d.ts.map +1 -0
  132. package/dist/mcp/builtin/globals/restoreVersionTool.js +70 -0
  133. package/dist/mcp/builtin/globals/restoreVersionTool.js.map +1 -0
  134. package/dist/mcp/builtin/globals/updateTool.d.ts.map +1 -1
  135. package/dist/mcp/builtin/globals/updateTool.js +44 -62
  136. package/dist/mcp/builtin/globals/updateTool.js.map +1 -1
  137. package/dist/mcp/builtinTools.d.ts +150 -19
  138. package/dist/mcp/builtinTools.d.ts.map +1 -1
  139. package/dist/mcp/builtinTools.js +125 -20
  140. package/dist/mcp/builtinTools.js.map +1 -1
  141. package/dist/mcp/sanitizeMCPConfig.d.ts +0 -1
  142. package/dist/mcp/sanitizeMCPConfig.d.ts.map +1 -1
  143. package/dist/mcp/sanitizeMCPConfig.js +127 -58
  144. package/dist/mcp/sanitizeMCPConfig.js.map +1 -1
  145. package/dist/stdio.d.ts +3 -3
  146. package/dist/stdio.d.ts.map +1 -1
  147. package/dist/stdio.js +86 -47
  148. package/dist/stdio.js.map +1 -1
  149. package/dist/types.d.ts +205 -114
  150. package/dist/types.d.ts.map +1 -1
  151. package/dist/types.js +2 -4
  152. package/dist/types.js.map +1 -1
  153. package/dist/utils/camelCase.d.ts.map +1 -1
  154. package/dist/utils/getPluginConfig.d.ts +1 -1
  155. package/dist/utils/getPluginConfig.js +1 -1
  156. package/dist/utils/getPluginConfig.js.map +1 -1
  157. package/dist/utils/resolveProjectRoot.d.ts.map +1 -1
  158. package/dist/utils/seal.d.ts +18 -0
  159. package/dist/utils/seal.d.ts.map +1 -0
  160. package/dist/utils/seal.js +27 -0
  161. package/dist/utils/seal.js.map +1 -0
  162. package/dist/utils/toStandardSchema.d.ts.map +1 -1
  163. package/dist/utils/toStandardSchema.js.map +1 -1
  164. package/package.json +27 -37
  165. package/src/defaultAccess.ts +3 -0
  166. package/src/endpoint/access.ts +99 -101
  167. package/src/endpoint/index.ts +95 -15
  168. package/src/endpoint/oauth.ts +92 -0
  169. package/src/endpoint/oauthLoginToken.ts +91 -0
  170. package/src/endpoint/oauthServer.ts +396 -0
  171. package/src/exports/client.ts +2 -1
  172. package/src/exports/internal.ts +1 -0
  173. package/src/index.ts +52 -25
  174. package/src/mcp/buildMcpServer.ts +181 -116
  175. package/src/mcp/builtin/collections/authTools.ts +124 -77
  176. package/src/mcp/builtin/collections/countTool.ts +61 -0
  177. package/src/mcp/builtin/collections/countVersionsTool.ts +60 -0
  178. package/src/mcp/builtin/collections/createTool.ts +115 -88
  179. package/src/mcp/builtin/collections/deleteTool.ts +49 -75
  180. package/src/mcp/builtin/collections/duplicateTool.ts +84 -0
  181. package/src/mcp/builtin/collections/fileInput.spec.ts +93 -0
  182. package/src/mcp/builtin/collections/fileInput.ts +127 -0
  183. package/src/mcp/builtin/collections/findDistinctTool.ts +67 -0
  184. package/src/mcp/builtin/collections/findTool.ts +58 -111
  185. package/src/mcp/builtin/collections/findVersionByIDTool.ts +66 -0
  186. package/src/mcp/builtin/collections/findVersionsTool.ts +83 -0
  187. package/src/mcp/builtin/collections/getCollectionSchemaTool.ts +91 -0
  188. package/src/mcp/builtin/collections/restoreVersionTool.ts +65 -0
  189. package/src/mcp/builtin/collections/updateTool.ts +106 -143
  190. package/src/mcp/builtin/collections/uploadInstructionsTool.ts +64 -0
  191. package/src/mcp/builtin/formatEntityError.ts +77 -0
  192. package/src/mcp/builtin/getConfigInfoTool.ts +68 -0
  193. package/src/mcp/builtin/globals/countVersionsTool.ts +60 -0
  194. package/src/mcp/builtin/globals/findTool.ts +28 -58
  195. package/src/mcp/builtin/globals/findVersionByIDTool.ts +64 -0
  196. package/src/mcp/builtin/globals/findVersionsTool.ts +69 -0
  197. package/src/mcp/builtin/globals/getGlobalSchemaTool.ts +73 -0
  198. package/src/mcp/builtin/globals/restoreVersionTool.ts +68 -0
  199. package/src/mcp/builtin/globals/updateTool.ts +52 -80
  200. package/src/mcp/builtinTools.ts +118 -41
  201. package/src/mcp/sanitizeMCPConfig.ts +121 -78
  202. package/src/stdio.ts +80 -47
  203. package/src/types.ts +223 -119
  204. package/src/utils/getPluginConfig.ts +1 -1
  205. package/src/utils/seal.ts +46 -0
  206. package/src/utils/toStandardSchema.ts +1 -1
  207. package/dist/@types/assets.d.js +0 -2
  208. package/dist/@types/assets.d.js.map +0 -1
  209. package/dist/collection/getAccessField.d.ts +0 -12
  210. package/dist/collection/getAccessField.d.ts.map +0 -1
  211. package/dist/collection/getAccessField.js +0 -57
  212. package/dist/collection/getAccessField.js.map +0 -1
  213. package/dist/collection/index.d.ts +0 -6
  214. package/dist/collection/index.d.ts.map +0 -1
  215. package/dist/collection/index.js +0 -60
  216. package/dist/collection/index.js.map +0 -1
  217. package/dist/components/AccessField/index.client.d.ts +0 -10
  218. package/dist/components/AccessField/index.client.d.ts.map +0 -1
  219. package/dist/components/AccessField/index.client.js +0 -305
  220. package/dist/components/AccessField/index.client.js.map +0 -1
  221. package/dist/components/AccessField/index.css +0 -93
  222. package/dist/utils/getVirtualFieldNames.d.ts +0 -14
  223. package/dist/utils/getVirtualFieldNames.d.ts.map +0 -1
  224. package/dist/utils/getVirtualFieldNames.js +0 -35
  225. package/dist/utils/getVirtualFieldNames.js.map +0 -1
  226. package/dist/utils/localAPIDefaults.d.ts +0 -20
  227. package/dist/utils/localAPIDefaults.d.ts.map +0 -1
  228. package/dist/utils/localAPIDefaults.js +0 -19
  229. package/dist/utils/localAPIDefaults.js.map +0 -1
  230. package/dist/utils/schemaConversion/buildToolInput.d.ts +0 -29
  231. package/dist/utils/schemaConversion/buildToolInput.d.ts.map +0 -1
  232. package/dist/utils/schemaConversion/buildToolInput.js +0 -51
  233. package/dist/utils/schemaConversion/buildToolInput.js.map +0 -1
  234. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts +0 -7
  235. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts.map +0 -1
  236. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js +0 -20
  237. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js.map +0 -1
  238. package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts +0 -15
  239. package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts.map +0 -1
  240. package/dist/utils/schemaConversion/sanitizeEntitySchema.js +0 -464
  241. package/dist/utils/schemaConversion/sanitizeEntitySchema.js.map +0 -1
  242. package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js +0 -158
  243. package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js.map +0 -1
  244. package/dist/utils/transformPointDataToPayload.d.ts +0 -7
  245. package/dist/utils/transformPointDataToPayload.d.ts.map +0 -1
  246. package/dist/utils/transformPointDataToPayload.js +0 -28
  247. package/dist/utils/transformPointDataToPayload.js.map +0 -1
  248. package/src/@types/assets.d.ts +0 -3
  249. package/src/collection/getAccessField.ts +0 -64
  250. package/src/collection/index.ts +0 -64
  251. package/src/components/AccessField/index.client.tsx +0 -344
  252. package/src/components/AccessField/index.css +0 -93
  253. package/src/utils/getVirtualFieldNames.ts +0 -53
  254. package/src/utils/localAPIDefaults.ts +0 -22
  255. package/src/utils/schemaConversion/buildToolInput.ts +0 -68
  256. package/src/utils/schemaConversion/removeVirtualFieldsFromSchema.ts +0 -27
  257. package/src/utils/schemaConversion/sanitizeEntitySchema.spec.ts +0 -103
  258. package/src/utils/schemaConversion/sanitizeEntitySchema.ts +0 -529
  259. package/src/utils/transformPointDataToPayload.ts +0 -40
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal.d.ts","sourceRoot":"","sources":["../../src/utils/seal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C;;;GAGG;AACH,eAAO,MAAM,IAAI,oCAKd;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,cAAc;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,cAAc,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb,KAAG,MAGD,CAAA;AAEH,eAAO,MAAM,MAAM,GAAI,CAAC,yBAIrB;IACD,GAAG,EAAE,cAAc,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,IAAI,GAAG,MAAM,CAAA;CACrB,KAAG,CAAC,GAAG,SAeP,CAAA"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Encrypts data for the client to hold, so it doesn't need to be stored. `type` keeps one kind
3
+ * from being used as another, e.g. a client ID as an authorization code.
4
+ */ export const seal = ({ type, data, expiresIn, req })=>req.payload.encrypt(JSON.stringify({
5
+ ...data,
6
+ type,
7
+ exp: expiresIn && Date.now() + expiresIn * 1000
8
+ }));
9
+ export const unseal = ({ type, req, value })=>{
10
+ // Only the v1 format is authenticated. Older formats decrypt anything without an error.
11
+ if (!value?.startsWith('v1:')) {
12
+ return undefined;
13
+ }
14
+ try {
15
+ // Previous secrets can be public, like a default secret from source code, so only client IDs
16
+ // accept them. Client IDs grant nothing, and this keeps clients registered when the secret changes.
17
+ const secret = type === 'client' ? undefined : req.payload.config.secret;
18
+ const data = JSON.parse(req.payload.decrypt(value, {
19
+ secret
20
+ }));
21
+ return data.type === type && (!data.exp || data.exp > Date.now()) ? data : undefined;
22
+ } catch {
23
+ return undefined;
24
+ }
25
+ };
26
+
27
+ //# sourceMappingURL=seal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/seal.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\n/**\n * Encrypts data for the client to hold, so it doesn't need to be stored. `type` keeps one kind\n * from being used as another, e.g. a client ID as an authorization code.\n */\nexport const seal = ({\n type,\n data,\n expiresIn,\n req,\n}: {\n data: object\n /** Seconds */\n expiresIn?: number\n req: PayloadRequest\n type: string\n}): string =>\n req.payload.encrypt(\n JSON.stringify({ ...data, type, exp: expiresIn && Date.now() + expiresIn * 1000 }),\n )\n\nexport const unseal = <T>({\n type,\n req,\n value,\n}: {\n req: PayloadRequest\n type: string\n value: null | string\n}): T | undefined => {\n // Only the v1 format is authenticated. Older formats decrypt anything without an error.\n if (!value?.startsWith('v1:')) {\n return undefined\n }\n\n try {\n // Previous secrets can be public, like a default secret from source code, so only client IDs\n // accept them. Client IDs grant nothing, and this keeps clients registered when the secret changes.\n const secret = type === 'client' ? undefined : req.payload.config.secret\n const data = JSON.parse(req.payload.decrypt(value, { secret }))\n return data.type === type && (!data.exp || data.exp > Date.now()) ? data : undefined\n } catch {\n return undefined\n }\n}\n"],"names":["seal","type","data","expiresIn","req","payload","encrypt","JSON","stringify","exp","Date","now","unseal","value","startsWith","undefined","secret","config","parse","decrypt"],"mappings":"AAEA;;;CAGC,GACD,OAAO,MAAMA,OAAO,CAAC,EACnBC,IAAI,EACJC,IAAI,EACJC,SAAS,EACTC,GAAG,EAOJ,GACCA,IAAIC,OAAO,CAACC,OAAO,CACjBC,KAAKC,SAAS,CAAC;QAAE,GAAGN,IAAI;QAAED;QAAMQ,KAAKN,aAAaO,KAAKC,GAAG,KAAKR,YAAY;IAAK,IACjF;AAEH,OAAO,MAAMS,SAAS,CAAI,EACxBX,IAAI,EACJG,GAAG,EACHS,KAAK,EAKN;IACC,wFAAwF;IACxF,IAAI,CAACA,OAAOC,WAAW,QAAQ;QAC7B,OAAOC;IACT;IAEA,IAAI;QACF,6FAA6F;QAC7F,oGAAoG;QACpG,MAAMC,SAASf,SAAS,WAAWc,YAAYX,IAAIC,OAAO,CAACY,MAAM,CAACD,MAAM;QACxE,MAAMd,OAAOK,KAAKW,KAAK,CAACd,IAAIC,OAAO,CAACc,OAAO,CAACN,OAAO;YAAEG;QAAO;QAC5D,OAAOd,KAAKD,IAAI,KAAKA,QAAS,CAAA,CAACC,KAAKO,GAAG,IAAIP,KAAKO,GAAG,GAAGC,KAAKC,GAAG,EAAC,IAAKT,OAAOa;IAC7E,EAAE,OAAM;QACN,OAAOA;IACT;AACF,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"toStandardSchema.d.ts","sourceRoot":"","sources":["../../src/utils/toStandardSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AAE1F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,gIAAgI;AAChI,eAAO,MAAM,gBAAgB,GAAI,QAAQ,eAAe,KAAG,sBAG/B,CAAA"}
1
+ {"version":3,"file":"toStandardSchema.d.ts","sourceRoot":"","sources":["../../src/utils/toStandardSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AAE1F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,gIAAgI;AAChI,eAAO,MAAM,gBAAgB,WAAY,eAAe,KAAG,sBAG/B,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/toStandardSchema.ts"],"sourcesContent":["import { fromJsonSchema, type StandardSchemaWithJSON } from '@modelcontextprotocol/server'\n\nimport type { ToolInputSchema } from '../types.js'\n\n/** Normalize a tool/prompt input to a Standard Schema. Raw JSON Schema gets wrapped; Standard Schema instances pass through. */\nexport const toStandardSchema = (schema: ToolInputSchema): StandardSchemaWithJSON =>\n typeof schema === 'object' && schema !== null && '~standard' in schema\n ? (schema as StandardSchemaWithJSON)\n : fromJsonSchema(schema)\n"],"names":["fromJsonSchema","toStandardSchema","schema"],"mappings":"AAAA,SAASA,cAAc,QAAqC,+BAA8B;AAI1F,8HAA8H,GAC9H,OAAO,MAAMC,mBAAmB,CAACC,SAC/B,OAAOA,WAAW,YAAYA,WAAW,QAAQ,eAAeA,SAC3DA,SACDF,eAAeE,QAAO"}
1
+ {"version":3,"sources":["../../src/utils/toStandardSchema.ts"],"sourcesContent":["import { fromJsonSchema, type StandardSchemaWithJSON } from '@modelcontextprotocol/server'\n\nimport type { ToolInputSchema } from '../types.js'\n\n/** Normalize a tool/prompt input to a Standard Schema. Raw JSON Schema gets wrapped; Standard Schema instances pass through. */\nexport const toStandardSchema = (schema: ToolInputSchema): StandardSchemaWithJSON =>\n typeof schema === 'object' && schema !== null && '~standard' in schema\n ? schema\n : fromJsonSchema(schema)\n"],"names":["fromJsonSchema","toStandardSchema","schema"],"mappings":"AAAA,SAASA,cAAc,QAAqC,+BAA8B;AAI1F,8HAA8H,GAC9H,OAAO,MAAMC,mBAAmB,CAACC,SAC/B,OAAOA,WAAW,YAAYA,WAAW,QAAQ,eAAeA,SAC5DA,SACAF,eAAeE,QAAO"}
package/package.json CHANGED
@@ -1,17 +1,12 @@
1
1
  {
2
2
  "name": "@payloadcms/plugin-mcp",
3
- "version": "4.0.0-internal.a0ef1b8",
3
+ "version": "4.0.0-internal.a2d053c",
4
4
  "description": "MCP (Model Context Protocol) capabilities with Payload",
5
5
  "keywords": [
6
- "plugin",
7
6
  "mcp",
8
- "model context protocol"
7
+ "model context protocol",
8
+ "plugin"
9
9
  ],
10
- "repository": {
11
- "type": "git",
12
- "url": "https://github.com/payloadcms/payload.git",
13
- "directory": "packages/plugin-mcp"
14
- },
15
10
  "license": "MIT",
16
11
  "author": "Payload <dev@payloadcms.com> (https://payloadcms.com)",
17
12
  "maintainers": [
@@ -21,10 +16,22 @@
21
16
  "url": "https://payloadcms.com"
22
17
  }
23
18
  ],
24
- "sideEffects": [
25
- "*.css"
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/payloadcms/payload.git",
22
+ "directory": "packages/plugin-mcp"
23
+ },
24
+ "bin": {
25
+ "payload-mcp": "./bin.js"
26
+ },
27
+ "files": [
28
+ "src",
29
+ "dist",
30
+ "bin.js"
26
31
  ],
27
32
  "type": "module",
33
+ "main": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
28
35
  "exports": {
29
36
  ".": {
30
37
  "import": "./dist/index.js",
@@ -40,39 +47,24 @@
40
47
  "import": "./dist/stdio.js",
41
48
  "types": "./dist/stdio.d.ts",
42
49
  "default": "./dist/stdio.js"
50
+ },
51
+ "./internal": {
52
+ "import": "./dist/exports/internal.js",
53
+ "types": "./dist/exports/internal.d.ts",
54
+ "default": "./dist/exports/internal.js"
43
55
  }
44
56
  },
45
- "main": "./dist/index.js",
46
- "types": "./dist/index.d.ts",
47
- "bin": {
48
- "payload-mcp": "./bin.js"
49
- },
50
- "files": [
51
- "src",
52
- "dist",
53
- "bin.js"
54
- ],
55
57
  "dependencies": {
56
- "@cfworker/json-schema": "^4.1.1",
57
- "@modelcontextprotocol/server": "2.0.0-alpha.2",
58
- "tsx": "4.22.4",
59
- "zod": "^4.0.0"
58
+ "@modelcontextprotocol/server": "2.4.0",
59
+ "tsx": "4.23.15"
60
60
  },
61
61
  "devDependencies": {
62
- "@types/react": "19.2.14",
63
- "react": "^19.0.1 || ^19.1.2 || ^19.2.1",
64
- "@payloadcms/eslint-config": "3.28.0",
65
- "payload": "4.0.0-internal.a0ef1b8",
66
- "@payloadcms/ui": "4.0.0-internal.a0ef1b8"
62
+ "payload": "4.0.0-internal.a2d053c"
67
63
  },
68
64
  "peerDependencies": {
69
- "react": "^19.0.1 || ^19.1.2 || ^19.2.1",
70
- "@payloadcms/ui": "4.0.0-internal.a0ef1b8",
71
- "payload": "4.0.0-internal.a0ef1b8"
65
+ "payload": "4.0.0-internal.a2d053c"
72
66
  },
73
67
  "//deps_notes": {
74
- "zod": "zod is a hard dependency of @modelcontextprotocol/server, thus we can safely use it without it impacting bundle size. Make extra sure the zod version here matches exactly what's defined in the dependencies of @modelcontextprotocol/server to avoid duplicate versions being installed.",
75
- "@cfworker/json-schema": "Should be able to remove in next @modelcontextprotocol/server alpha release. Marked as an optional peer of @modelcontextprotocol/server, but its bundle does an eager top-level `import { Validator } from '@cfworker/json-schema'`, so it must be installed even when only zod-registered tools are used. Drop once upstream lazy-loads the validator. See: https://github.com/modelcontextprotocol/typescript-sdk",
76
68
  "tsx": "Required by the `bin.js` stdio mcp. Keep the version pinned to whatever `payload` ships to avoid duplicate installs."
77
69
  },
78
70
  "homepage:": "https://payloadcms.com",
@@ -82,9 +74,7 @@
82
74
  "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
83
75
  "build:types": "tsc --emitDeclarationOnly --outDir dist",
84
76
  "clean": "rimraf {dist,*.tsbuildinfo}",
85
- "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
86
- "lint": "eslint .",
87
- "lint:fix": "eslint . --fix",
77
+ "copyfiles": "copyfiles -u 1 -e \"**/*.spec.*\" \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
88
78
  "pack:plugin": "pnpm build && pnpm pack"
89
79
  }
90
80
  }
@@ -0,0 +1,3 @@
1
+ import type { MCPAccessArgs } from './types.js'
2
+
3
+ export const defaultAccess = ({ req }: MCPAccessArgs): boolean => Boolean(req.user)
@@ -1,132 +1,130 @@
1
- import type { DefaultDocumentIDType, PayloadRequest, TypedUser } from 'payload'
1
+ import type { PayloadRequest, SanitizedPermissions } from 'payload'
2
2
 
3
- import crypto from 'crypto'
4
- import { UnauthorizedError } from 'payload'
3
+ import { executeAuthStrategies, getAccessResults, UnauthorizedError } from 'payload'
5
4
 
6
- import type { AuthorizedMCP, MCPAPIKeysDoc } from '../types.js'
5
+ import type { AuthorizedMCP, MCPItem } from '../types.js'
7
6
 
8
- import { getLogger } from '../utils/getLogger.js'
9
7
  import { getPluginConfig } from '../utils/getPluginConfig.js'
8
+ import { authenticateLoginToken } from './oauthLoginToken.js'
9
+
10
+ export type GetAuthorizedMCPArgs = {
11
+ overrideAccess: boolean
12
+ req: PayloadRequest
13
+ }
10
14
 
11
15
  /**
12
- * Resolves the API key (or dev-mode session) and returns the items the caller
13
- * may use. Denied items are dropped from the array.
16
+ * Authenticates an MCP request and removes items the user cannot access.
17
+ * `overrideAccess` skips Payload and MCP item access checks.
14
18
  */
15
- export const getAuthorizedMCP: (args: { req: PayloadRequest }) => Promise<AuthorizedMCP> = async ({
19
+ export const getAuthorizedMCP: (args: GetAuthorizedMCPArgs) => Promise<AuthorizedMCP> = async ({
20
+ overrideAccess,
16
21
  req,
17
22
  }) => {
18
- const logger = getLogger({ payload: req.payload })
19
23
  const pluginConfig = getPluginConfig({ config: req.payload.config })
20
24
 
21
- const authHeader = req.headers.get('Authorization')
22
- const hasBearerToken = authHeader?.startsWith('Bearer ')
23
-
24
- const buildAuthorized = (apiKeyDoc: MCPAPIKeysDoc): AuthorizedMCP => ({
25
- items: pluginConfig.items.filter((item) => {
26
- switch (item.type) {
27
- case 'collectionTool':
28
- return apiKeyDoc.access.collections?.[item.collectionSlug]?.[item.key] !== false
29
- case 'globalTool':
30
- return apiKeyDoc.access.globals?.[item.globalSlug]?.[item.key] !== false
31
- case 'prompt':
32
- return apiKeyDoc.access.prompts?.[item.key] !== false
33
- case 'resource':
34
- return apiKeyDoc.access.resources?.[item.key] !== false
35
- case 'tool':
36
- return apiKeyDoc.access.tools?.[item.key] !== false
37
- }
38
- }),
39
- overrideAccess:
40
- typeof apiKeyDoc.overrideAccess === 'boolean' ? apiKeyDoc.overrideAccess : false,
41
- user: apiKeyDoc.user,
42
- })
43
-
44
- if (pluginConfig.overrideAuth) {
45
- return await pluginConfig.overrideAuth({
46
- getAPIKeyDoc: (overrideApiKey) => getAPIKeyDoc({ logger, overrideApiKey, pluginConfig, req }),
47
- getAuthorizedMCP: ({ apiKeyDoc }) => buildAuthorized(apiKeyDoc),
25
+ if (pluginConfig.overrideGetAuthorizedMCP) {
26
+ return await pluginConfig.overrideGetAuthorizedMCP({
27
+ overrideAccess,
48
28
  pluginConfig,
49
29
  req,
50
30
  })
51
31
  }
52
32
 
53
- if (process.env.NODE_ENV === 'development' && !hasBearerToken) {
54
- logger.info('Dev mode: skipping API key check, using session user')
55
- return buildAuthorized({
56
- id: -1,
57
- access: {},
58
- overrideAccess: true,
59
- user: req.user ?? null,
60
- })
33
+ if (req.headers) {
34
+ const headers = new Headers(req.headers)
35
+ const hasAuthorization = headers.has('Authorization')
36
+
37
+ headers.set('DisableAutologin', 'true')
38
+ // Not `payload.auth()`, which also computes permissions that filterMCPItems computes anyway
39
+ req.user = (await executeAuthStrategies({ headers, payload: req.payload, req })).user
40
+
41
+ // Only bearer tokens that no auth strategy accepted get here, e.g. an OAuth access token.
42
+ // API keys use the `<collection> API-Key <key>` scheme instead of `Bearer`, so they never do.
43
+ const bearerToken = headers.get('Authorization')?.match(/^Bearer (.+)$/)?.[1]
44
+ // Sealed Payload login tokens from the built-in OAuth server
45
+ if (
46
+ !req.user &&
47
+ bearerToken &&
48
+ pluginConfig.oauth?.type === 'builtin' &&
49
+ !pluginConfig.oauth.issueToken
50
+ ) {
51
+ req.user = await authenticateLoginToken({ req, token: bearerToken })
52
+ }
53
+ if (!req.user && bearerToken && pluginConfig.oauth?.authenticate) {
54
+ req.user = await pluginConfig.oauth.authenticate({ req, token: bearerToken })
55
+ }
56
+
57
+ // With OAuth, the endpoint answers with a challenge that starts the client's sign-in
58
+ if (hasAuthorization && !req.user && !pluginConfig.oauth) {
59
+ throw new UnauthorizedError(req.t)
60
+ }
61
61
  }
62
62
 
63
- return buildAuthorized(await getAPIKeyDoc({ logger, pluginConfig, req }))
63
+ return {
64
+ items: await filterMCPItems({
65
+ items: pluginConfig.items,
66
+ overrideAccess,
67
+ req,
68
+ }),
69
+ overrideAccess,
70
+ }
64
71
  }
65
72
 
66
- const getAPIKeyDoc = async ({
67
- logger,
68
- overrideApiKey,
69
- pluginConfig,
73
+ export const filterMCPItems = async ({
74
+ items,
75
+ overrideAccess,
70
76
  req,
71
77
  }: {
72
- logger: ReturnType<typeof getLogger>
73
- overrideApiKey?: string
74
- pluginConfig: ReturnType<typeof getPluginConfig>
78
+ items: MCPItem[]
79
+ overrideAccess: boolean
75
80
  req: PayloadRequest
76
- }): Promise<MCPAPIKeysDoc> => {
77
- const authHeader = req.headers.get('Authorization')
78
- const hasBearerToken = authHeader?.startsWith('Bearer ')
79
-
80
- const apiKey =
81
- overrideApiKey ?? (hasBearerToken ? authHeader?.replace('Bearer ', '').trim() || null : null)
82
-
83
- if (!apiKey) {
84
- throw new UnauthorizedError()
81
+ }): Promise<MCPItem[]> => {
82
+ // Match Payload core: overrideAccess bypasses access evaluation
83
+ if (overrideAccess) {
84
+ return items
85
85
  }
86
86
 
87
- const sha256APIKeyIndex = crypto
88
- .createHmac('sha256', req.payload.secret)
89
- .update(apiKey)
90
- .digest('hex')
91
-
92
- const doc = await req.payload.db.findOne<MCPAPIKeysDoc>({
93
- collection: 'payload-mcp-api-keys',
94
- req,
95
- where: {
96
- apiKeyIndex: { equals: sha256APIKeyIndex },
97
- },
98
- })
99
-
100
- if (!doc || !doc.user) {
101
- throw new UnauthorizedError()
102
- }
103
-
104
- logger.info('API Key is valid')
105
-
106
- const userRef = doc.user
107
- const userID =
108
- typeof userRef === 'object' && userRef !== null && 'id' in userRef
109
- ? userRef.id
110
- : (userRef as unknown as DefaultDocumentIDType)
87
+ const authorizedItems: MCPItem[] = []
111
88
 
112
- const user = (await req.payload.findByID({
113
- id: userID,
114
- collection: pluginConfig.userCollection,
115
- depth: 0,
116
- disableErrors: true,
117
- req,
118
- })) as null | TypedUser
89
+ const permissions = await getAccessResults({ req })
119
90
 
120
- if (!user) {
121
- throw new UnauthorizedError()
91
+ for (const item of items) {
92
+ if (!(await checkItemAccess({ item, permissions, req }))) {
93
+ continue
94
+ }
95
+ authorizedItems.push(item)
122
96
  }
123
97
 
124
- return {
125
- ...doc,
126
- user: {
127
- ...user,
128
- _strategy: 'mcp-api-key' as const,
129
- collection: pluginConfig.userCollection,
130
- },
98
+ return authorizedItems
99
+ }
100
+
101
+ /**
102
+ * Runs MCP item access callbacks
103
+ */
104
+ const checkItemAccess = async ({
105
+ item,
106
+ permissions,
107
+ req,
108
+ }: {
109
+ item: MCPItem
110
+ permissions?: SanitizedPermissions
111
+ req: PayloadRequest
112
+ }): Promise<boolean> => {
113
+ switch (item.type) {
114
+ case 'collectionTool':
115
+ return (
116
+ !item.tool.access ||
117
+ (await item.tool.access({ slug: item.collectionSlug, permissions, req }))
118
+ )
119
+ case 'globalTool':
120
+ return (
121
+ !item.tool.access || (await item.tool.access({ slug: item.globalSlug, permissions, req }))
122
+ )
123
+ case 'prompt':
124
+ return !item.prompt.access || (await item.prompt.access({ permissions, req }))
125
+ case 'resource':
126
+ return !item.resource.access || (await item.resource.access({ permissions, req }))
127
+ case 'tool':
128
+ return !item.tool.access || (await item.tool.access({ permissions, req }))
131
129
  }
132
130
  }
@@ -1,9 +1,17 @@
1
- import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server'
2
- import { APIError, type PayloadHandler } from 'payload'
1
+ import type { PayloadHandler, PayloadRequest } from 'payload'
2
+
3
+ import {
4
+ createMcpHandler,
5
+ isLegacyRequest,
6
+ readRequestBody,
7
+ WebStandardStreamableHTTPServerTransport,
8
+ } from '@modelcontextprotocol/server'
9
+ import { APIError } from 'payload'
3
10
 
4
11
  import { buildMcpServer } from '../mcp/buildMcpServer.js'
5
12
  import { getPluginConfig } from '../utils/getPluginConfig.js'
6
13
  import { getAuthorizedMCP } from './access.js'
14
+ import { getMCPResource, oauthChallenge } from './oauth.js'
7
15
 
8
16
  export const mcpEndpoint: PayloadHandler = async (req) => {
9
17
  if (!req.url) {
@@ -11,25 +19,97 @@ export const mcpEndpoint: PayloadHandler = async (req) => {
11
19
  }
12
20
 
13
21
  req.payloadAPI = 'MCP' as const
22
+ // Lets auth strategies check that an OAuth access token was issued for this MCP server
23
+ req.context.mcp = { resource: getMCPResource({ config: req.payload.config, url: req.url }) }
14
24
 
15
25
  const pluginConfig = getPluginConfig({ config: req.payload.config })
16
- const authorizedMCP = await getAuthorizedMCP({ req })
26
+ const overrideAccessParam = new URL(req.url).searchParams.get('overrideAccess')
27
+
28
+ if (overrideAccessParam !== null && process.env.NODE_ENV !== 'development') {
29
+ throw new APIError('MCP overrideAccess is only available in development.', 400)
30
+ }
31
+
32
+ let overrideAccess = false
33
+ if (overrideAccessParam === 'true') {
34
+ overrideAccess = true
35
+ } else if (overrideAccessParam !== null && overrideAccessParam !== 'false') {
36
+ throw new APIError('MCP overrideAccess must be "true" or "false".', 400)
37
+ }
38
+
39
+ const authorizedMCP = await getAuthorizedMCP({ overrideAccess, req })
40
+
41
+ if (pluginConfig.oauth && !req.user) {
42
+ return oauthChallenge({ req })
43
+ }
17
44
 
18
- const server = buildMcpServer({ authorizedMCP, pluginConfig, req })
45
+ const maxRequestBodySize = pluginConfig.mcp?.maxRequestBodySize
46
+ // Payload augments the original web-standard Request in place.
47
+ const mcpRequest = req as PayloadRequest & Request
48
+ const parsedBody = await parseRequestBody({ maxRequestBodySize, request: mcpRequest })
19
49
 
20
- const transport = new WebStandardStreamableHTTPServerTransport({
21
- enableJsonResponse: true,
22
- sessionIdGenerator: undefined, // stateless mode
50
+ // Keep the old JSON-only, stateless behavior because the SDK's 2025 fallback uses SSE.
51
+ if (await isLegacyRequest(mcpRequest, parsedBody, { maxRequestBodySize })) {
52
+ const server = buildMcpServer({ authorizedMCP, pluginConfig, req })
53
+ const transport = new WebStandardStreamableHTTPServerTransport({
54
+ enableJsonResponse: true,
55
+ maxRequestBodySize,
56
+ sessionIdGenerator: undefined, // stateless mode
57
+ })
58
+ transport.onerror = (err) => {
59
+ req.payload.logger.error({ err, msg: 'Error serving legacy MCP request' })
60
+ }
61
+
62
+ try {
63
+ await server.connect(transport)
64
+ return await transport.handleRequest(mcpRequest, { parsedBody })
65
+ } finally {
66
+ await server.close().catch((err) => {
67
+ req.payload.logger.error({ err, msg: 'Error closing MCP server' })
68
+ })
69
+ }
70
+ }
71
+
72
+ const handler = createMcpHandler(() => buildMcpServer({ authorizedMCP, pluginConfig, req }), {
73
+ legacy: 'reject',
74
+ maxRequestBodySize,
75
+ // SDK subscriptions always use SSE, so disable them to keep every response JSON-only.
76
+ maxSubscriptions: 0,
77
+ onerror: (err) => {
78
+ req.payload.logger.error({ err, msg: 'Error serving modern MCP request' })
79
+ },
80
+ responseMode: 'json',
23
81
  })
24
82
 
25
- await server.connect(transport)
83
+ try {
84
+ return await handler.fetch(mcpRequest, { parsedBody })
85
+ } finally {
86
+ await handler.close().catch((err) => {
87
+ req.payload.logger.error({ err, msg: 'Error closing modern MCP handler' })
88
+ })
89
+ }
90
+ }
26
91
 
27
- const mcpRequest = new Request(req.url, {
28
- body: req.body,
29
- duplex: 'half',
30
- headers: req.headers,
31
- method: req.method,
32
- } as { duplex: 'half' } & RequestInit)
92
+ /**
93
+ * Parses a POST body once so the era check and the SDK handler can share it. The SDK
94
+ * skips its size limit for a pre-parsed body, so this reads with the same limit. It reads
95
+ * a clone and returns `undefined` for any body the SDK should reject (too large, unreadable,
96
+ * or not JSON), leaving the original request for the SDK to read and answer.
97
+ */
98
+ const parseRequestBody = async ({
99
+ maxRequestBodySize,
100
+ request,
101
+ }: {
102
+ maxRequestBodySize?: number
103
+ request: Request
104
+ }): Promise<unknown> => {
105
+ if (request.method.toUpperCase() !== 'POST') {
106
+ return undefined
107
+ }
33
108
 
34
- return await transport.handleRequest(mcpRequest)
109
+ try {
110
+ const body = await readRequestBody(request.clone(), maxRequestBodySize)
111
+ return body.tooLarge ? undefined : JSON.parse(body.text)
112
+ } catch {
113
+ return undefined
114
+ }
35
115
  }
@@ -0,0 +1,92 @@
1
+ import type { OAuthProtectedResourceMetadata } from '@modelcontextprotocol/server'
2
+ import type { PayloadHandler, PayloadRequest, SanitizedConfig } from 'payload'
3
+
4
+ import {
5
+ bearerAuthChallengeResponse,
6
+ OAuthError,
7
+ OAuthErrorCode,
8
+ } from '@modelcontextprotocol/server'
9
+ import { createPayloadRequest, getPayload } from 'payload'
10
+
11
+ import { getPluginConfig } from '../utils/getPluginConfig.js'
12
+
13
+ /**
14
+ * Serves the OAuth protected resource metadata (RFC 9728) at `/api/mcp/oauth-protected-resource`.
15
+ * The OAuth challenge links here, so MCP clients find the authorization server without setup.
16
+ */
17
+ export const oauthProtectedResourceEndpoint: PayloadHandler = (req) => getMetadataResponse({ req })
18
+
19
+ /**
20
+ * Serves the same metadata for clients that ignore the challenge's link and only look at
21
+ * `/.well-known/oauth-protected-resource`. Mount it at the site root, e.g. in Next.js at
22
+ * `app/.well-known/oauth-protected-resource/[[...path]]/route.ts`:
23
+ *
24
+ * ```ts
25
+ * import config from '@payload-config'
26
+ * import { oauthProtectedResourceHandler } from '@payloadcms/plugin-mcp'
27
+ *
28
+ * export const GET = oauthProtectedResourceHandler(config)
29
+ * ```
30
+ */
31
+ export const oauthProtectedResourceHandler =
32
+ (config: Promise<SanitizedConfig> | SanitizedConfig) =>
33
+ async (request: Request): Promise<Response> =>
34
+ getMetadataResponse({
35
+ req: await createPayloadRequest({ payload: await getPayload({ config }), req: request }),
36
+ })
37
+
38
+ /**
39
+ * Answers an unauthenticated MCP request with a challenge linking to the protected resource
40
+ * metadata, which starts the client's OAuth flow.
41
+ */
42
+ export const oauthChallenge = ({ req }: { req: PayloadRequest }): Response => {
43
+ const { oauth } = getPluginConfig({ config: req.payload.config })
44
+ const response = bearerAuthChallengeResponse(
45
+ new OAuthError(OAuthErrorCode.InvalidToken, req.t('error:unauthorized')),
46
+ {
47
+ requiredScopes: oauth?.type === 'external' ? oauth.scopes : undefined,
48
+ resourceMetadataUrl: `${getMCPURL({ config: req.payload.config, url: req.url! })}/oauth-protected-resource`,
49
+ },
50
+ )
51
+
52
+ // Lets browser-based MCP clients read the challenge, when Payload's `cors` setting allows them
53
+ response.headers.set('Access-Control-Expose-Headers', 'WWW-Authenticate')
54
+
55
+ return response
56
+ }
57
+
58
+ /**
59
+ * The MCP endpoint's address, as advertised in the metadata's `resource`. Clients send it when
60
+ * they sign in, and authorization servers that support it put it in the access token's audience.
61
+ */
62
+ export const getMCPResource = ({ config, url }: { config: SanitizedConfig; url: string }): string =>
63
+ getPluginConfig({ config }).oauth?.resource ?? getMCPURL({ config, url })
64
+
65
+ export const getMCPURL = ({ config, url }: { config: SanitizedConfig; url: string }): string =>
66
+ new URL(`${config.routes.api}/mcp`, config.serverURL || url).href
67
+
68
+ const getMetadataResponse = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
69
+ const { config } = req.payload
70
+ const { oauth } = getPluginConfig({ config })
71
+
72
+ if (!oauth) {
73
+ return new Response(null, { status: 404 })
74
+ }
75
+
76
+ return Response.json(
77
+ {
78
+ authorization_servers:
79
+ oauth.type === 'builtin'
80
+ ? [`${getMCPURL({ config, url: req.url! })}/oauth`]
81
+ : typeof oauth.authorizationServers === 'function'
82
+ ? await oauth.authorizationServers({ req })
83
+ : oauth.authorizationServers,
84
+ bearer_methods_supported: ['header'],
85
+ resource: getMCPResource({ config, url: req.url! }),
86
+ scopes_supported: oauth.type === 'external' ? oauth.scopes : undefined,
87
+ } satisfies OAuthProtectedResourceMetadata,
88
+ // Public document, readable by browser-based MCP clients on any origin. Not cached, since
89
+ // without `serverURL` its URLs come from the request.
90
+ { headers: { 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-store' } },
91
+ )
92
+ }