@payloadcms/plugin-mcp 4.0.0-internal.167c7cd → 4.0.0-internal.16f218d

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 (196) hide show
  1. package/bin.js +0 -7
  2. package/dist/endpoint/access.d.ts.map +1 -1
  3. package/dist/endpoint/access.js +25 -5
  4. package/dist/endpoint/access.js.map +1 -1
  5. package/dist/endpoint/index.d.ts.map +1 -1
  6. package/dist/endpoint/index.js +45 -4
  7. package/dist/endpoint/index.js.map +1 -1
  8. package/dist/endpoint/oauth.d.ts +35 -0
  9. package/dist/endpoint/oauth.d.ts.map +1 -0
  10. package/dist/endpoint/oauth.js +96 -0
  11. package/dist/endpoint/oauth.js.map +1 -0
  12. package/dist/endpoint/oauthLoginToken.d.ts +21 -0
  13. package/dist/endpoint/oauthLoginToken.d.ts.map +1 -0
  14. package/dist/endpoint/oauthLoginToken.js +79 -0
  15. package/dist/endpoint/oauthLoginToken.js.map +1 -0
  16. package/dist/endpoint/oauthServer.d.ts +12 -0
  17. package/dist/endpoint/oauthServer.d.ts.map +1 -0
  18. package/dist/endpoint/oauthServer.js +361 -0
  19. package/dist/endpoint/oauthServer.js.map +1 -0
  20. package/dist/index.d.ts +10 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +15 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/mcp/buildMcpServer.d.ts +2 -2
  25. package/dist/mcp/buildMcpServer.d.ts.map +1 -1
  26. package/dist/mcp/buildMcpServer.js +36 -24
  27. package/dist/mcp/buildMcpServer.js.map +1 -1
  28. package/dist/mcp/builtin/collections/authTools.d.ts.map +1 -1
  29. package/dist/mcp/builtin/collections/authTools.js +80 -56
  30. package/dist/mcp/builtin/collections/authTools.js.map +1 -1
  31. package/dist/mcp/builtin/collections/countTool.d.ts.map +1 -1
  32. package/dist/mcp/builtin/collections/countTool.js +9 -14
  33. package/dist/mcp/builtin/collections/countTool.js.map +1 -1
  34. package/dist/mcp/builtin/collections/countVersionsTool.d.ts.map +1 -1
  35. package/dist/mcp/builtin/collections/countVersionsTool.js +9 -13
  36. package/dist/mcp/builtin/collections/countVersionsTool.js.map +1 -1
  37. package/dist/mcp/builtin/collections/createTool.d.ts.map +1 -1
  38. package/dist/mcp/builtin/collections/createTool.js +48 -49
  39. package/dist/mcp/builtin/collections/createTool.js.map +1 -1
  40. package/dist/mcp/builtin/collections/deleteTool.d.ts.map +1 -1
  41. package/dist/mcp/builtin/collections/deleteTool.js +44 -43
  42. package/dist/mcp/builtin/collections/deleteTool.js.map +1 -1
  43. package/dist/mcp/builtin/collections/duplicateTool.d.ts.map +1 -1
  44. package/dist/mcp/builtin/collections/duplicateTool.js +31 -37
  45. package/dist/mcp/builtin/collections/duplicateTool.js.map +1 -1
  46. package/dist/mcp/builtin/collections/fileInput.d.ts +21 -21
  47. package/dist/mcp/builtin/collections/fileInput.d.ts.map +1 -1
  48. package/dist/mcp/builtin/collections/fileInput.js +34 -70
  49. package/dist/mcp/builtin/collections/fileInput.js.map +1 -1
  50. package/dist/mcp/builtin/collections/findDistinctTool.d.ts.map +1 -1
  51. package/dist/mcp/builtin/collections/findDistinctTool.js +11 -26
  52. package/dist/mcp/builtin/collections/findDistinctTool.js.map +1 -1
  53. package/dist/mcp/builtin/collections/findTool.d.ts.map +1 -1
  54. package/dist/mcp/builtin/collections/findTool.js +28 -46
  55. package/dist/mcp/builtin/collections/findTool.js.map +1 -1
  56. package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts.map +1 -1
  57. package/dist/mcp/builtin/collections/findVersionByIDTool.js +17 -26
  58. package/dist/mcp/builtin/collections/findVersionByIDTool.js.map +1 -1
  59. package/dist/mcp/builtin/collections/findVersionsTool.d.ts.map +1 -1
  60. package/dist/mcp/builtin/collections/findVersionsTool.js +16 -30
  61. package/dist/mcp/builtin/collections/findVersionsTool.js.map +1 -1
  62. package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts.map +1 -1
  63. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js +30 -14
  64. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js.map +1 -1
  65. package/dist/mcp/builtin/collections/restoreVersionTool.d.ts.map +1 -1
  66. package/dist/mcp/builtin/collections/restoreVersionTool.js +14 -26
  67. package/dist/mcp/builtin/collections/restoreVersionTool.js.map +1 -1
  68. package/dist/mcp/builtin/collections/updateTool.d.ts.map +1 -1
  69. package/dist/mcp/builtin/collections/updateTool.js +55 -87
  70. package/dist/mcp/builtin/collections/updateTool.js.map +1 -1
  71. package/dist/mcp/builtin/collections/uploadInstructionsTool.js +11 -11
  72. package/dist/mcp/builtin/collections/uploadInstructionsTool.js.map +1 -1
  73. package/dist/mcp/builtin/formatEntityError.d.ts +16 -0
  74. package/dist/mcp/builtin/formatEntityError.d.ts.map +1 -0
  75. package/dist/mcp/builtin/formatEntityError.js +55 -0
  76. package/dist/mcp/builtin/formatEntityError.js.map +1 -0
  77. package/dist/mcp/builtin/getConfigInfoTool.d.ts.map +1 -1
  78. package/dist/mcp/builtin/getConfigInfoTool.js +3 -2
  79. package/dist/mcp/builtin/getConfigInfoTool.js.map +1 -1
  80. package/dist/mcp/builtin/globals/countVersionsTool.d.ts.map +1 -1
  81. package/dist/mcp/builtin/globals/countVersionsTool.js +9 -13
  82. package/dist/mcp/builtin/globals/countVersionsTool.js.map +1 -1
  83. package/dist/mcp/builtin/globals/findTool.d.ts.map +1 -1
  84. package/dist/mcp/builtin/globals/findTool.js +10 -16
  85. package/dist/mcp/builtin/globals/findTool.js.map +1 -1
  86. package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts.map +1 -1
  87. package/dist/mcp/builtin/globals/findVersionByIDTool.js +13 -24
  88. package/dist/mcp/builtin/globals/findVersionByIDTool.js.map +1 -1
  89. package/dist/mcp/builtin/globals/findVersionsTool.d.ts.map +1 -1
  90. package/dist/mcp/builtin/globals/findVersionsTool.js +13 -29
  91. package/dist/mcp/builtin/globals/findVersionsTool.js.map +1 -1
  92. package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts.map +1 -1
  93. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js +29 -13
  94. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js.map +1 -1
  95. package/dist/mcp/builtin/globals/restoreVersionTool.d.ts.map +1 -1
  96. package/dist/mcp/builtin/globals/restoreVersionTool.js +15 -22
  97. package/dist/mcp/builtin/globals/restoreVersionTool.js.map +1 -1
  98. package/dist/mcp/builtin/globals/updateTool.d.ts.map +1 -1
  99. package/dist/mcp/builtin/globals/updateTool.js +27 -36
  100. package/dist/mcp/builtin/globals/updateTool.js.map +1 -1
  101. package/dist/mcp/sanitizeMCPConfig.d.ts.map +1 -1
  102. package/dist/mcp/sanitizeMCPConfig.js +3 -0
  103. package/dist/mcp/sanitizeMCPConfig.js.map +1 -1
  104. package/dist/stdio.js +4 -3
  105. package/dist/stdio.js.map +1 -1
  106. package/dist/types.d.ts +99 -7
  107. package/dist/types.d.ts.map +1 -1
  108. package/dist/types.js.map +1 -1
  109. package/dist/utils/seal.d.ts +18 -0
  110. package/dist/utils/seal.d.ts.map +1 -0
  111. package/dist/utils/seal.js +27 -0
  112. package/dist/utils/seal.js.map +1 -0
  113. package/dist/utils/toStandardSchema.js.map +1 -1
  114. package/package.json +23 -28
  115. package/src/endpoint/access.ts +24 -6
  116. package/src/endpoint/index.ts +41 -3
  117. package/src/endpoint/oauth.ts +100 -0
  118. package/src/endpoint/oauthLoginToken.ts +91 -0
  119. package/src/endpoint/oauthServer.ts +396 -0
  120. package/src/index.ts +40 -5
  121. package/src/mcp/buildMcpServer.ts +40 -30
  122. package/src/mcp/builtin/collections/authTools.ts +82 -71
  123. package/src/mcp/builtin/collections/countTool.ts +9 -21
  124. package/src/mcp/builtin/collections/countVersionsTool.ts +9 -18
  125. package/src/mcp/builtin/collections/createTool.ts +71 -96
  126. package/src/mcp/builtin/collections/deleteTool.ts +37 -65
  127. package/src/mcp/builtin/collections/duplicateTool.ts +36 -86
  128. package/src/mcp/builtin/collections/fileInput.spec.ts +2 -2
  129. package/src/mcp/builtin/collections/fileInput.ts +44 -81
  130. package/src/mcp/builtin/collections/findDistinctTool.ts +11 -63
  131. package/src/mcp/builtin/collections/findTool.ts +24 -106
  132. package/src/mcp/builtin/collections/findVersionByIDTool.ts +15 -58
  133. package/src/mcp/builtin/collections/findVersionsTool.ts +14 -85
  134. package/src/mcp/builtin/collections/getCollectionSchemaTool.ts +23 -12
  135. package/src/mcp/builtin/collections/restoreVersionTool.ts +15 -57
  136. package/src/mcp/builtin/collections/updateTool.ts +55 -109
  137. package/src/mcp/builtin/collections/uploadInstructionsTool.ts +12 -12
  138. package/src/mcp/builtin/formatEntityError.ts +77 -0
  139. package/src/mcp/builtin/getConfigInfoTool.ts +2 -1
  140. package/src/mcp/builtin/globals/countVersionsTool.ts +9 -17
  141. package/src/mcp/builtin/globals/findTool.ts +13 -52
  142. package/src/mcp/builtin/globals/findVersionByIDTool.ts +13 -52
  143. package/src/mcp/builtin/globals/findVersionsTool.ts +14 -92
  144. package/src/mcp/builtin/globals/getGlobalSchemaTool.ts +21 -11
  145. package/src/mcp/builtin/globals/restoreVersionTool.ts +14 -45
  146. package/src/mcp/builtin/globals/updateTool.ts +38 -61
  147. package/src/mcp/sanitizeMCPConfig.ts +1 -0
  148. package/src/stdio.ts +2 -2
  149. package/src/types.ts +99 -7
  150. package/src/utils/seal.ts +46 -0
  151. package/src/utils/toStandardSchema.ts +1 -1
  152. package/dist/mcp/builtin/collections/fileInput.spec.js +0 -86
  153. package/dist/mcp/builtin/collections/fileInput.spec.js.map +0 -1
  154. package/dist/mcp/builtin/collections/formatCollectionError.d.ts +0 -9
  155. package/dist/mcp/builtin/collections/formatCollectionError.d.ts.map +0 -1
  156. package/dist/mcp/builtin/collections/formatCollectionError.js +0 -60
  157. package/dist/mcp/builtin/collections/formatCollectionError.js.map +0 -1
  158. package/dist/mcp/builtin/validateEntityData.d.ts +0 -14
  159. package/dist/mcp/builtin/validateEntityData.d.ts.map +0 -1
  160. package/dist/mcp/builtin/validateEntityData.js +0 -82
  161. package/dist/mcp/builtin/validateEntityData.js.map +0 -1
  162. package/dist/utils/getVirtualFieldNames.d.ts +0 -14
  163. package/dist/utils/getVirtualFieldNames.d.ts.map +0 -1
  164. package/dist/utils/getVirtualFieldNames.js +0 -35
  165. package/dist/utils/getVirtualFieldNames.js.map +0 -1
  166. package/dist/utils/schemaConversion/filterFieldsByAccess.d.ts +0 -28
  167. package/dist/utils/schemaConversion/filterFieldsByAccess.d.ts.map +0 -1
  168. package/dist/utils/schemaConversion/filterFieldsByAccess.js +0 -71
  169. package/dist/utils/schemaConversion/filterFieldsByAccess.js.map +0 -1
  170. package/dist/utils/schemaConversion/getEntityInputSchema.d.ts +0 -13
  171. package/dist/utils/schemaConversion/getEntityInputSchema.d.ts.map +0 -1
  172. package/dist/utils/schemaConversion/getEntityInputSchema.js +0 -66
  173. package/dist/utils/schemaConversion/getEntityInputSchema.js.map +0 -1
  174. package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts +0 -10
  175. package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts.map +0 -1
  176. package/dist/utils/schemaConversion/sanitizeEntitySchema.js +0 -384
  177. package/dist/utils/schemaConversion/sanitizeEntitySchema.js.map +0 -1
  178. package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js +0 -133
  179. package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js.map +0 -1
  180. package/dist/utils/transformPointDataToPayload.d.ts +0 -7
  181. package/dist/utils/transformPointDataToPayload.d.ts.map +0 -1
  182. package/dist/utils/transformPointDataToPayload.js +0 -28
  183. package/dist/utils/transformPointDataToPayload.js.map +0 -1
  184. package/dist/utils/whereSchema.d.ts +0 -9
  185. package/dist/utils/whereSchema.d.ts.map +0 -1
  186. package/dist/utils/whereSchema.js +0 -13
  187. package/dist/utils/whereSchema.js.map +0 -1
  188. package/src/mcp/builtin/collections/formatCollectionError.ts +0 -84
  189. package/src/mcp/builtin/validateEntityData.ts +0 -132
  190. package/src/utils/getVirtualFieldNames.ts +0 -53
  191. package/src/utils/schemaConversion/filterFieldsByAccess.ts +0 -123
  192. package/src/utils/schemaConversion/getEntityInputSchema.ts +0 -96
  193. package/src/utils/schemaConversion/sanitizeEntitySchema.spec.ts +0 -96
  194. package/src/utils/schemaConversion/sanitizeEntitySchema.ts +0 -432
  195. package/src/utils/transformPointDataToPayload.ts +0 -40
  196. package/src/utils/whereSchema.ts +0 -24
package/bin.js CHANGED
@@ -27,13 +27,6 @@ const start = async () => {
27
27
  return
28
28
  }
29
29
 
30
- // tsx 4.22.4's synchronous loader leaks its namespace query onto Node built-ins on Node 23.5+.
31
- // Force the stable asynchronous loader path, matching payload/bin.js.
32
- const nodeModule = await import('node:module')
33
- if (typeof nodeModule.default.registerHooks === 'function') {
34
- nodeModule.default.registerHooks = undefined
35
- }
36
-
37
30
  const { tsImport } = await import('tsx/esm/api')
38
31
  const { runMcpStdio } = await tsImport('./dist/stdio.js', baseURL)
39
32
  await runMcpStdio()
@@ -1 +1 @@
1
- {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/endpoint/access.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAwB,MAAM,SAAS,CAAA;AAInE,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAIzD,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,EAAE,OAAO,CAAA;IACvB,GAAG,EAAE,cAAc,CAAA;CACpB,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,OAAO,CAAC,aAAa,CAkCnF,CAAA;AAED,eAAO,MAAM,cAAc,oCAIxB;IACD,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,cAAc,EAAE,OAAO,CAAA;IACvB,GAAG,EAAE,cAAc,CAAA;CACpB,KAAG,OAAO,CAAC,OAAO,EAAE,CAkBpB,CAAA"}
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/endpoint/access.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAwB,MAAM,SAAS,CAAA;AAInE,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAKzD,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,EAAE,OAAO,CAAA;IACvB,GAAG,EAAE,cAAc,CAAA;CACpB,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,OAAO,CAAC,aAAa,CAoDnF,CAAA;AAED,eAAO,MAAM,cAAc,oCAIxB;IACD,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,cAAc,EAAE,OAAO,CAAA;IACvB,GAAG,EAAE,cAAc,CAAA;CACpB,KAAG,OAAO,CAAC,OAAO,EAAE,CAkBpB,CAAA"}
@@ -1,5 +1,6 @@
1
- import { getAccessResults, UnauthorizedError } from 'payload';
1
+ import { executeAuthStrategies, getAccessResults, UnauthorizedError } from 'payload';
2
2
  import { getPluginConfig } from '../utils/getPluginConfig.js';
3
+ import { authenticateLoginToken } from './oauthLoginToken.js';
3
4
  /**
4
5
  * Authenticates an MCP request and removes items the user cannot access.
5
6
  * `overrideAccess` skips Payload and MCP item access checks.
@@ -18,11 +19,30 @@ import { getPluginConfig } from '../utils/getPluginConfig.js';
18
19
  const headers = new Headers(req.headers);
19
20
  const hasAuthorization = headers.has('Authorization');
20
21
  headers.set('DisableAutologin', 'true');
21
- req.user = (await req.payload.auth({
22
+ // Not `payload.auth()`, which also computes permissions that filterMCPItems computes anyway
23
+ req.user = (await executeAuthStrategies({
22
24
  headers,
25
+ payload: req.payload,
23
26
  req
24
27
  })).user;
25
- if (hasAuthorization && !req.user) {
28
+ // Only bearer tokens that no auth strategy accepted get here, e.g. an OAuth access token.
29
+ // API keys use the `<collection> API-Key <key>` scheme instead of `Bearer`, so they never do.
30
+ const bearerToken = headers.get('Authorization')?.match(/^Bearer (.+)$/)?.[1];
31
+ // Sealed Payload login tokens from the built-in OAuth server
32
+ if (!req.user && bearerToken && pluginConfig.oauth?.type === 'builtin' && !pluginConfig.oauth.issueToken) {
33
+ req.user = await authenticateLoginToken({
34
+ req,
35
+ token: bearerToken
36
+ });
37
+ }
38
+ if (!req.user && bearerToken && pluginConfig.oauth?.authenticate) {
39
+ req.user = await pluginConfig.oauth.authenticate({
40
+ req,
41
+ token: bearerToken
42
+ });
43
+ }
44
+ // With OAuth, the endpoint answers with a challenge that starts the client's sign-in
45
+ if (hasAuthorization && !req.user && !pluginConfig.oauth) {
26
46
  throw new UnauthorizedError(req.t);
27
47
  }
28
48
  }
@@ -62,13 +82,13 @@ export const filterMCPItems = async ({ items, overrideAccess, req })=>{
62
82
  switch(item.type){
63
83
  case 'collectionTool':
64
84
  return !item.tool.access || await item.tool.access({
65
- collectionSlug: item.collectionSlug,
85
+ slug: item.collectionSlug,
66
86
  permissions,
67
87
  req
68
88
  });
69
89
  case 'globalTool':
70
90
  return !item.tool.access || await item.tool.access({
71
- globalSlug: item.globalSlug,
91
+ slug: item.globalSlug,
72
92
  permissions,
73
93
  req
74
94
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/endpoint/access.ts"],"sourcesContent":["import type { PayloadRequest, SanitizedPermissions } from 'payload'\n\nimport { getAccessResults, UnauthorizedError } from 'payload'\n\nimport type { AuthorizedMCP, MCPItem } from '../types.js'\n\nimport { getPluginConfig } from '../utils/getPluginConfig.js'\n\nexport type GetAuthorizedMCPArgs = {\n overrideAccess: boolean\n req: PayloadRequest\n}\n\n/**\n * Authenticates an MCP request and removes items the user cannot access.\n * `overrideAccess` skips Payload and MCP item access checks.\n */\nexport const getAuthorizedMCP: (args: GetAuthorizedMCPArgs) => Promise<AuthorizedMCP> = async ({\n overrideAccess,\n req,\n}) => {\n const pluginConfig = getPluginConfig({ config: req.payload.config })\n\n if (pluginConfig.overrideGetAuthorizedMCP) {\n return await pluginConfig.overrideGetAuthorizedMCP({\n overrideAccess,\n pluginConfig,\n req,\n })\n }\n\n if (req.headers) {\n const headers = new Headers(req.headers)\n const hasAuthorization = headers.has('Authorization')\n\n headers.set('DisableAutologin', 'true')\n req.user = (await req.payload.auth({ headers, req })).user\n\n if (hasAuthorization && !req.user) {\n throw new UnauthorizedError(req.t)\n }\n }\n\n return {\n items: await filterMCPItems({\n items: pluginConfig.items,\n overrideAccess,\n req,\n }),\n overrideAccess,\n }\n}\n\nexport const filterMCPItems = async ({\n items,\n overrideAccess,\n req,\n}: {\n items: MCPItem[]\n overrideAccess: boolean\n req: PayloadRequest\n}): Promise<MCPItem[]> => {\n // Match Payload core: overrideAccess bypasses access evaluation\n if (overrideAccess) {\n return items\n }\n\n const authorizedItems: MCPItem[] = []\n\n const permissions = await getAccessResults({ req })\n\n for (const item of items) {\n if (!(await checkItemAccess({ item, permissions, req }))) {\n continue\n }\n authorizedItems.push(item)\n }\n\n return authorizedItems\n}\n\n/**\n * Runs MCP item access callbacks\n */\nconst checkItemAccess = async ({\n item,\n permissions,\n req,\n}: {\n item: MCPItem\n permissions?: SanitizedPermissions\n req: PayloadRequest\n}): Promise<boolean> => {\n switch (item.type) {\n case 'collectionTool':\n return (\n !item.tool.access ||\n (await item.tool.access({ collectionSlug: item.collectionSlug, permissions, req }))\n )\n case 'globalTool':\n return (\n !item.tool.access ||\n (await item.tool.access({ globalSlug: item.globalSlug, permissions, req }))\n )\n case 'prompt':\n return !item.prompt.access || (await item.prompt.access({ permissions, req }))\n case 'resource':\n return !item.resource.access || (await item.resource.access({ permissions, req }))\n case 'tool':\n return !item.tool.access || (await item.tool.access({ permissions, req }))\n }\n}\n"],"names":["getAccessResults","UnauthorizedError","getPluginConfig","getAuthorizedMCP","overrideAccess","req","pluginConfig","config","payload","overrideGetAuthorizedMCP","headers","Headers","hasAuthorization","has","set","user","auth","t","items","filterMCPItems","authorizedItems","permissions","item","checkItemAccess","push","type","tool","access","collectionSlug","globalSlug","prompt","resource"],"mappings":"AAEA,SAASA,gBAAgB,EAAEC,iBAAiB,QAAQ,UAAS;AAI7D,SAASC,eAAe,QAAQ,8BAA6B;AAO7D;;;CAGC,GACD,OAAO,MAAMC,mBAA2E,OAAO,EAC7FC,cAAc,EACdC,GAAG,EACJ;IACC,MAAMC,eAAeJ,gBAAgB;QAAEK,QAAQF,IAAIG,OAAO,CAACD,MAAM;IAAC;IAElE,IAAID,aAAaG,wBAAwB,EAAE;QACzC,OAAO,MAAMH,aAAaG,wBAAwB,CAAC;YACjDL;YACAE;YACAD;QACF;IACF;IAEA,IAAIA,IAAIK,OAAO,EAAE;QACf,MAAMA,UAAU,IAAIC,QAAQN,IAAIK,OAAO;QACvC,MAAME,mBAAmBF,QAAQG,GAAG,CAAC;QAErCH,QAAQI,GAAG,CAAC,oBAAoB;QAChCT,IAAIU,IAAI,GAAG,AAAC,CAAA,MAAMV,IAAIG,OAAO,CAACQ,IAAI,CAAC;YAAEN;YAASL;QAAI,EAAC,EAAGU,IAAI;QAE1D,IAAIH,oBAAoB,CAACP,IAAIU,IAAI,EAAE;YACjC,MAAM,IAAId,kBAAkBI,IAAIY,CAAC;QACnC;IACF;IAEA,OAAO;QACLC,OAAO,MAAMC,eAAe;YAC1BD,OAAOZ,aAAaY,KAAK;YACzBd;YACAC;QACF;QACAD;IACF;AACF,EAAC;AAED,OAAO,MAAMe,iBAAiB,OAAO,EACnCD,KAAK,EACLd,cAAc,EACdC,GAAG,EAKJ;IACC,gEAAgE;IAChE,IAAID,gBAAgB;QAClB,OAAOc;IACT;IAEA,MAAME,kBAA6B,EAAE;IAErC,MAAMC,cAAc,MAAMrB,iBAAiB;QAAEK;IAAI;IAEjD,KAAK,MAAMiB,QAAQJ,MAAO;QACxB,IAAI,CAAE,MAAMK,gBAAgB;YAAED;YAAMD;YAAahB;QAAI,IAAK;YACxD;QACF;QACAe,gBAAgBI,IAAI,CAACF;IACvB;IAEA,OAAOF;AACT,EAAC;AAED;;CAEC,GACD,MAAMG,kBAAkB,OAAO,EAC7BD,IAAI,EACJD,WAAW,EACXhB,GAAG,EAKJ;IACC,OAAQiB,KAAKG,IAAI;QACf,KAAK;YACH,OACE,CAACH,KAAKI,IAAI,CAACC,MAAM,IAChB,MAAML,KAAKI,IAAI,CAACC,MAAM,CAAC;gBAAEC,gBAAgBN,KAAKM,cAAc;gBAAEP;gBAAahB;YAAI;QAEpF,KAAK;YACH,OACE,CAACiB,KAAKI,IAAI,CAACC,MAAM,IAChB,MAAML,KAAKI,IAAI,CAACC,MAAM,CAAC;gBAAEE,YAAYP,KAAKO,UAAU;gBAAER;gBAAahB;YAAI;QAE5E,KAAK;YACH,OAAO,CAACiB,KAAKQ,MAAM,CAACH,MAAM,IAAK,MAAML,KAAKQ,MAAM,CAACH,MAAM,CAAC;gBAAEN;gBAAahB;YAAI;QAC7E,KAAK;YACH,OAAO,CAACiB,KAAKS,QAAQ,CAACJ,MAAM,IAAK,MAAML,KAAKS,QAAQ,CAACJ,MAAM,CAAC;gBAAEN;gBAAahB;YAAI;QACjF,KAAK;YACH,OAAO,CAACiB,KAAKI,IAAI,CAACC,MAAM,IAAK,MAAML,KAAKI,IAAI,CAACC,MAAM,CAAC;gBAAEN;gBAAahB;YAAI;IAC3E;AACF"}
1
+ {"version":3,"sources":["../../src/endpoint/access.ts"],"sourcesContent":["import type { PayloadRequest, SanitizedPermissions } from 'payload'\n\nimport { executeAuthStrategies, getAccessResults, UnauthorizedError } from 'payload'\n\nimport type { AuthorizedMCP, MCPItem } from '../types.js'\n\nimport { getPluginConfig } from '../utils/getPluginConfig.js'\nimport { authenticateLoginToken } from './oauthLoginToken.js'\n\nexport type GetAuthorizedMCPArgs = {\n overrideAccess: boolean\n req: PayloadRequest\n}\n\n/**\n * Authenticates an MCP request and removes items the user cannot access.\n * `overrideAccess` skips Payload and MCP item access checks.\n */\nexport const getAuthorizedMCP: (args: GetAuthorizedMCPArgs) => Promise<AuthorizedMCP> = async ({\n overrideAccess,\n req,\n}) => {\n const pluginConfig = getPluginConfig({ config: req.payload.config })\n\n if (pluginConfig.overrideGetAuthorizedMCP) {\n return await pluginConfig.overrideGetAuthorizedMCP({\n overrideAccess,\n pluginConfig,\n req,\n })\n }\n\n if (req.headers) {\n const headers = new Headers(req.headers)\n const hasAuthorization = headers.has('Authorization')\n\n headers.set('DisableAutologin', 'true')\n // Not `payload.auth()`, which also computes permissions that filterMCPItems computes anyway\n req.user = (await executeAuthStrategies({ headers, payload: req.payload, req })).user\n\n // Only bearer tokens that no auth strategy accepted get here, e.g. an OAuth access token.\n // API keys use the `<collection> API-Key <key>` scheme instead of `Bearer`, so they never do.\n const bearerToken = headers.get('Authorization')?.match(/^Bearer (.+)$/)?.[1]\n // Sealed Payload login tokens from the built-in OAuth server\n if (\n !req.user &&\n bearerToken &&\n pluginConfig.oauth?.type === 'builtin' &&\n !pluginConfig.oauth.issueToken\n ) {\n req.user = await authenticateLoginToken({ req, token: bearerToken })\n }\n if (!req.user && bearerToken && pluginConfig.oauth?.authenticate) {\n req.user = await pluginConfig.oauth.authenticate({ req, token: bearerToken })\n }\n\n // With OAuth, the endpoint answers with a challenge that starts the client's sign-in\n if (hasAuthorization && !req.user && !pluginConfig.oauth) {\n throw new UnauthorizedError(req.t)\n }\n }\n\n return {\n items: await filterMCPItems({\n items: pluginConfig.items,\n overrideAccess,\n req,\n }),\n overrideAccess,\n }\n}\n\nexport const filterMCPItems = async ({\n items,\n overrideAccess,\n req,\n}: {\n items: MCPItem[]\n overrideAccess: boolean\n req: PayloadRequest\n}): Promise<MCPItem[]> => {\n // Match Payload core: overrideAccess bypasses access evaluation\n if (overrideAccess) {\n return items\n }\n\n const authorizedItems: MCPItem[] = []\n\n const permissions = await getAccessResults({ req })\n\n for (const item of items) {\n if (!(await checkItemAccess({ item, permissions, req }))) {\n continue\n }\n authorizedItems.push(item)\n }\n\n return authorizedItems\n}\n\n/**\n * Runs MCP item access callbacks\n */\nconst checkItemAccess = async ({\n item,\n permissions,\n req,\n}: {\n item: MCPItem\n permissions?: SanitizedPermissions\n req: PayloadRequest\n}): Promise<boolean> => {\n switch (item.type) {\n case 'collectionTool':\n return (\n !item.tool.access ||\n (await item.tool.access({ slug: item.collectionSlug, permissions, req }))\n )\n case 'globalTool':\n return (\n !item.tool.access || (await item.tool.access({ slug: item.globalSlug, permissions, req }))\n )\n case 'prompt':\n return !item.prompt.access || (await item.prompt.access({ permissions, req }))\n case 'resource':\n return !item.resource.access || (await item.resource.access({ permissions, req }))\n case 'tool':\n return !item.tool.access || (await item.tool.access({ permissions, req }))\n }\n}\n"],"names":["executeAuthStrategies","getAccessResults","UnauthorizedError","getPluginConfig","authenticateLoginToken","getAuthorizedMCP","overrideAccess","req","pluginConfig","config","payload","overrideGetAuthorizedMCP","headers","Headers","hasAuthorization","has","set","user","bearerToken","get","match","oauth","type","issueToken","token","authenticate","t","items","filterMCPItems","authorizedItems","permissions","item","checkItemAccess","push","tool","access","slug","collectionSlug","globalSlug","prompt","resource"],"mappings":"AAEA,SAASA,qBAAqB,EAAEC,gBAAgB,EAAEC,iBAAiB,QAAQ,UAAS;AAIpF,SAASC,eAAe,QAAQ,8BAA6B;AAC7D,SAASC,sBAAsB,QAAQ,uBAAsB;AAO7D;;;CAGC,GACD,OAAO,MAAMC,mBAA2E,OAAO,EAC7FC,cAAc,EACdC,GAAG,EACJ;IACC,MAAMC,eAAeL,gBAAgB;QAAEM,QAAQF,IAAIG,OAAO,CAACD,MAAM;IAAC;IAElE,IAAID,aAAaG,wBAAwB,EAAE;QACzC,OAAO,MAAMH,aAAaG,wBAAwB,CAAC;YACjDL;YACAE;YACAD;QACF;IACF;IAEA,IAAIA,IAAIK,OAAO,EAAE;QACf,MAAMA,UAAU,IAAIC,QAAQN,IAAIK,OAAO;QACvC,MAAME,mBAAmBF,QAAQG,GAAG,CAAC;QAErCH,QAAQI,GAAG,CAAC,oBAAoB;QAChC,4FAA4F;QAC5FT,IAAIU,IAAI,GAAG,AAAC,CAAA,MAAMjB,sBAAsB;YAAEY;YAASF,SAASH,IAAIG,OAAO;YAAEH;QAAI,EAAC,EAAGU,IAAI;QAErF,0FAA0F;QAC1F,8FAA8F;QAC9F,MAAMC,cAAcN,QAAQO,GAAG,CAAC,kBAAkBC,MAAM,kBAAkB,CAAC,EAAE;QAC7E,6DAA6D;QAC7D,IACE,CAACb,IAAIU,IAAI,IACTC,eACAV,aAAaa,KAAK,EAAEC,SAAS,aAC7B,CAACd,aAAaa,KAAK,CAACE,UAAU,EAC9B;YACAhB,IAAIU,IAAI,GAAG,MAAMb,uBAAuB;gBAAEG;gBAAKiB,OAAON;YAAY;QACpE;QACA,IAAI,CAACX,IAAIU,IAAI,IAAIC,eAAeV,aAAaa,KAAK,EAAEI,cAAc;YAChElB,IAAIU,IAAI,GAAG,MAAMT,aAAaa,KAAK,CAACI,YAAY,CAAC;gBAAElB;gBAAKiB,OAAON;YAAY;QAC7E;QAEA,qFAAqF;QACrF,IAAIJ,oBAAoB,CAACP,IAAIU,IAAI,IAAI,CAACT,aAAaa,KAAK,EAAE;YACxD,MAAM,IAAInB,kBAAkBK,IAAImB,CAAC;QACnC;IACF;IAEA,OAAO;QACLC,OAAO,MAAMC,eAAe;YAC1BD,OAAOnB,aAAamB,KAAK;YACzBrB;YACAC;QACF;QACAD;IACF;AACF,EAAC;AAED,OAAO,MAAMsB,iBAAiB,OAAO,EACnCD,KAAK,EACLrB,cAAc,EACdC,GAAG,EAKJ;IACC,gEAAgE;IAChE,IAAID,gBAAgB;QAClB,OAAOqB;IACT;IAEA,MAAME,kBAA6B,EAAE;IAErC,MAAMC,cAAc,MAAM7B,iBAAiB;QAAEM;IAAI;IAEjD,KAAK,MAAMwB,QAAQJ,MAAO;QACxB,IAAI,CAAE,MAAMK,gBAAgB;YAAED;YAAMD;YAAavB;QAAI,IAAK;YACxD;QACF;QACAsB,gBAAgBI,IAAI,CAACF;IACvB;IAEA,OAAOF;AACT,EAAC;AAED;;CAEC,GACD,MAAMG,kBAAkB,OAAO,EAC7BD,IAAI,EACJD,WAAW,EACXvB,GAAG,EAKJ;IACC,OAAQwB,KAAKT,IAAI;QACf,KAAK;YACH,OACE,CAACS,KAAKG,IAAI,CAACC,MAAM,IAChB,MAAMJ,KAAKG,IAAI,CAACC,MAAM,CAAC;gBAAEC,MAAML,KAAKM,cAAc;gBAAEP;gBAAavB;YAAI;QAE1E,KAAK;YACH,OACE,CAACwB,KAAKG,IAAI,CAACC,MAAM,IAAK,MAAMJ,KAAKG,IAAI,CAACC,MAAM,CAAC;gBAAEC,MAAML,KAAKO,UAAU;gBAAER;gBAAavB;YAAI;QAE3F,KAAK;YACH,OAAO,CAACwB,KAAKQ,MAAM,CAACJ,MAAM,IAAK,MAAMJ,KAAKQ,MAAM,CAACJ,MAAM,CAAC;gBAAEL;gBAAavB;YAAI;QAC7E,KAAK;YACH,OAAO,CAACwB,KAAKS,QAAQ,CAACL,MAAM,IAAK,MAAMJ,KAAKS,QAAQ,CAACL,MAAM,CAAC;gBAAEL;gBAAavB;YAAI;QACjF,KAAK;YACH,OAAO,CAACwB,KAAKG,IAAI,CAACC,MAAM,IAAK,MAAMJ,KAAKG,IAAI,CAACC,MAAM,CAAC;gBAAEL;gBAAavB;YAAI;IAC3E;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/endpoint/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,SAAS,CAAA;AAa7D,eAAO,MAAM,WAAW,EAAE,cA+DzB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/endpoint/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,SAAS,CAAA;AAe7D,eAAO,MAAM,WAAW,EAAE,cA0EzB,CAAA"}
@@ -1,13 +1,20 @@
1
- import { createMcpHandler, isLegacyRequest, WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server';
1
+ import { createMcpHandler, isLegacyRequest, readRequestBody, WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server';
2
2
  import { APIError } from 'payload';
3
3
  import { buildMcpServer } from '../mcp/buildMcpServer.js';
4
4
  import { getPluginConfig } from '../utils/getPluginConfig.js';
5
5
  import { getAuthorizedMCP } from './access.js';
6
+ import { getMCPResource, oauthChallenge } from './oauth.js';
6
7
  export const mcpEndpoint = async (req)=>{
7
8
  if (!req.url) {
8
9
  throw new APIError('Missing request URL', 400);
9
10
  }
10
11
  req.payloadAPI = 'MCP';
12
+ // Lets auth strategies check that an OAuth access token was issued for this MCP server
13
+ req.context.mcp = {
14
+ resource: getMCPResource({
15
+ req
16
+ })
17
+ };
11
18
  const pluginConfig = getPluginConfig({
12
19
  config: req.payload.config
13
20
  });
@@ -25,10 +32,22 @@ export const mcpEndpoint = async (req)=>{
25
32
  overrideAccess,
26
33
  req
27
34
  });
35
+ if (pluginConfig.oauth && !req.user) {
36
+ return oauthChallenge({
37
+ req
38
+ });
39
+ }
40
+ const maxRequestBodySize = pluginConfig.mcp?.maxRequestBodySize;
28
41
  // Payload augments the original web-standard Request in place.
29
42
  const mcpRequest = req;
43
+ const parsedBody = await parseRequestBody({
44
+ maxRequestBodySize,
45
+ request: mcpRequest
46
+ });
30
47
  // Keep the old JSON-only, stateless behavior because the SDK's 2025 fallback uses SSE.
31
- if (await isLegacyRequest(mcpRequest)) {
48
+ if (await isLegacyRequest(mcpRequest, parsedBody, {
49
+ maxRequestBodySize
50
+ })) {
32
51
  const server = buildMcpServer({
33
52
  authorizedMCP,
34
53
  pluginConfig,
@@ -36,6 +55,7 @@ export const mcpEndpoint = async (req)=>{
36
55
  });
37
56
  const transport = new WebStandardStreamableHTTPServerTransport({
38
57
  enableJsonResponse: true,
58
+ maxRequestBodySize,
39
59
  sessionIdGenerator: undefined
40
60
  });
41
61
  transport.onerror = (err)=>{
@@ -46,7 +66,9 @@ export const mcpEndpoint = async (req)=>{
46
66
  };
47
67
  try {
48
68
  await server.connect(transport);
49
- return await transport.handleRequest(mcpRequest);
69
+ return await transport.handleRequest(mcpRequest, {
70
+ parsedBody
71
+ });
50
72
  } finally{
51
73
  await server.close().catch((err)=>{
52
74
  req.payload.logger.error({
@@ -62,6 +84,7 @@ export const mcpEndpoint = async (req)=>{
62
84
  req
63
85
  }), {
64
86
  legacy: 'reject',
87
+ maxRequestBodySize,
65
88
  // SDK subscriptions always use SSE, so disable them to keep every response JSON-only.
66
89
  maxSubscriptions: 0,
67
90
  onerror: (err)=>{
@@ -73,7 +96,9 @@ export const mcpEndpoint = async (req)=>{
73
96
  responseMode: 'json'
74
97
  });
75
98
  try {
76
- return await handler.fetch(mcpRequest);
99
+ return await handler.fetch(mcpRequest, {
100
+ parsedBody
101
+ });
77
102
  } finally{
78
103
  await handler.close().catch((err)=>{
79
104
  req.payload.logger.error({
@@ -83,5 +108,21 @@ export const mcpEndpoint = async (req)=>{
83
108
  });
84
109
  }
85
110
  };
111
+ /**
112
+ * Parses a POST body once so the era check and the SDK handler can share it. The SDK
113
+ * skips its size limit for a pre-parsed body, so this reads with the same limit. It reads
114
+ * a clone and returns `undefined` for any body the SDK should reject (too large, unreadable,
115
+ * or not JSON), leaving the original request for the SDK to read and answer.
116
+ */ const parseRequestBody = async ({ maxRequestBodySize, request })=>{
117
+ if (request.method.toUpperCase() !== 'POST') {
118
+ return undefined;
119
+ }
120
+ try {
121
+ const body = await readRequestBody(request.clone(), maxRequestBodySize);
122
+ return body.tooLarge ? undefined : JSON.parse(body.text);
123
+ } catch {
124
+ return undefined;
125
+ }
126
+ };
86
127
 
87
128
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/endpoint/index.ts"],"sourcesContent":["import type { PayloadHandler, PayloadRequest } from 'payload'\n\nimport {\n createMcpHandler,\n isLegacyRequest,\n WebStandardStreamableHTTPServerTransport,\n} from '@modelcontextprotocol/server'\nimport { APIError } from 'payload'\n\nimport { buildMcpServer } from '../mcp/buildMcpServer.js'\nimport { getPluginConfig } from '../utils/getPluginConfig.js'\nimport { getAuthorizedMCP } from './access.js'\n\nexport const mcpEndpoint: PayloadHandler = async (req) => {\n if (!req.url) {\n throw new APIError('Missing request URL', 400)\n }\n\n req.payloadAPI = 'MCP' as const\n\n const pluginConfig = getPluginConfig({ config: req.payload.config })\n const overrideAccessParam = new URL(req.url).searchParams.get('overrideAccess')\n\n if (overrideAccessParam !== null && process.env.NODE_ENV !== 'development') {\n throw new APIError('MCP overrideAccess is only available in development.', 400)\n }\n\n let overrideAccess = false\n if (overrideAccessParam === 'true') {\n overrideAccess = true\n } else if (overrideAccessParam !== null && overrideAccessParam !== 'false') {\n throw new APIError('MCP overrideAccess must be \"true\" or \"false\".', 400)\n }\n\n const authorizedMCP = await getAuthorizedMCP({ overrideAccess, req })\n // Payload augments the original web-standard Request in place.\n const mcpRequest = req as PayloadRequest & Request\n\n // Keep the old JSON-only, stateless behavior because the SDK's 2025 fallback uses SSE.\n if (await isLegacyRequest(mcpRequest)) {\n const server = buildMcpServer({ authorizedMCP, pluginConfig, req })\n const transport = new WebStandardStreamableHTTPServerTransport({\n enableJsonResponse: true,\n sessionIdGenerator: undefined, // stateless mode\n })\n transport.onerror = (err) => {\n req.payload.logger.error({ err, msg: 'Error serving legacy MCP request' })\n }\n\n try {\n await server.connect(transport)\n return await transport.handleRequest(mcpRequest)\n } finally {\n await server.close().catch((err) => {\n req.payload.logger.error({ err, msg: 'Error closing MCP server' })\n })\n }\n }\n\n const handler = createMcpHandler(() => buildMcpServer({ authorizedMCP, pluginConfig, req }), {\n legacy: 'reject',\n // SDK subscriptions always use SSE, so disable them to keep every response JSON-only.\n maxSubscriptions: 0,\n onerror: (err) => {\n req.payload.logger.error({ err, msg: 'Error serving modern MCP request' })\n },\n responseMode: 'json',\n })\n\n try {\n return await handler.fetch(mcpRequest)\n } finally {\n await handler.close().catch((err) => {\n req.payload.logger.error({ err, msg: 'Error closing modern MCP handler' })\n })\n }\n}\n"],"names":["createMcpHandler","isLegacyRequest","WebStandardStreamableHTTPServerTransport","APIError","buildMcpServer","getPluginConfig","getAuthorizedMCP","mcpEndpoint","req","url","payloadAPI","pluginConfig","config","payload","overrideAccessParam","URL","searchParams","get","process","env","NODE_ENV","overrideAccess","authorizedMCP","mcpRequest","server","transport","enableJsonResponse","sessionIdGenerator","undefined","onerror","err","logger","error","msg","connect","handleRequest","close","catch","handler","legacy","maxSubscriptions","responseMode","fetch"],"mappings":"AAEA,SACEA,gBAAgB,EAChBC,eAAe,EACfC,wCAAwC,QACnC,+BAA8B;AACrC,SAASC,QAAQ,QAAQ,UAAS;AAElC,SAASC,cAAc,QAAQ,2BAA0B;AACzD,SAASC,eAAe,QAAQ,8BAA6B;AAC7D,SAASC,gBAAgB,QAAQ,cAAa;AAE9C,OAAO,MAAMC,cAA8B,OAAOC;IAChD,IAAI,CAACA,IAAIC,GAAG,EAAE;QACZ,MAAM,IAAIN,SAAS,uBAAuB;IAC5C;IAEAK,IAAIE,UAAU,GAAG;IAEjB,MAAMC,eAAeN,gBAAgB;QAAEO,QAAQJ,IAAIK,OAAO,CAACD,MAAM;IAAC;IAClE,MAAME,sBAAsB,IAAIC,IAAIP,IAAIC,GAAG,EAAEO,YAAY,CAACC,GAAG,CAAC;IAE9D,IAAIH,wBAAwB,QAAQI,QAAQC,GAAG,CAACC,QAAQ,KAAK,eAAe;QAC1E,MAAM,IAAIjB,SAAS,wDAAwD;IAC7E;IAEA,IAAIkB,iBAAiB;IACrB,IAAIP,wBAAwB,QAAQ;QAClCO,iBAAiB;IACnB,OAAO,IAAIP,wBAAwB,QAAQA,wBAAwB,SAAS;QAC1E,MAAM,IAAIX,SAAS,iDAAiD;IACtE;IAEA,MAAMmB,gBAAgB,MAAMhB,iBAAiB;QAAEe;QAAgBb;IAAI;IACnE,+DAA+D;IAC/D,MAAMe,aAAaf;IAEnB,uFAAuF;IACvF,IAAI,MAAMP,gBAAgBsB,aAAa;QACrC,MAAMC,SAASpB,eAAe;YAAEkB;YAAeX;YAAcH;QAAI;QACjE,MAAMiB,YAAY,IAAIvB,yCAAyC;YAC7DwB,oBAAoB;YACpBC,oBAAoBC;QACtB;QACAH,UAAUI,OAAO,GAAG,CAACC;YACnBtB,IAAIK,OAAO,CAACkB,MAAM,CAACC,KAAK,CAAC;gBAAEF;gBAAKG,KAAK;YAAmC;QAC1E;QAEA,IAAI;YACF,MAAMT,OAAOU,OAAO,CAACT;YACrB,OAAO,MAAMA,UAAUU,aAAa,CAACZ;QACvC,SAAU;YACR,MAAMC,OAAOY,KAAK,GAAGC,KAAK,CAAC,CAACP;gBAC1BtB,IAAIK,OAAO,CAACkB,MAAM,CAACC,KAAK,CAAC;oBAAEF;oBAAKG,KAAK;gBAA2B;YAClE;QACF;IACF;IAEA,MAAMK,UAAUtC,iBAAiB,IAAMI,eAAe;YAAEkB;YAAeX;YAAcH;QAAI,IAAI;QAC3F+B,QAAQ;QACR,sFAAsF;QACtFC,kBAAkB;QAClBX,SAAS,CAACC;YACRtB,IAAIK,OAAO,CAACkB,MAAM,CAACC,KAAK,CAAC;gBAAEF;gBAAKG,KAAK;YAAmC;QAC1E;QACAQ,cAAc;IAChB;IAEA,IAAI;QACF,OAAO,MAAMH,QAAQI,KAAK,CAACnB;IAC7B,SAAU;QACR,MAAMe,QAAQF,KAAK,GAAGC,KAAK,CAAC,CAACP;YAC3BtB,IAAIK,OAAO,CAACkB,MAAM,CAACC,KAAK,CAAC;gBAAEF;gBAAKG,KAAK;YAAmC;QAC1E;IACF;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/endpoint/index.ts"],"sourcesContent":["import type { PayloadHandler, PayloadRequest } from 'payload'\n\nimport {\n createMcpHandler,\n isLegacyRequest,\n readRequestBody,\n WebStandardStreamableHTTPServerTransport,\n} from '@modelcontextprotocol/server'\nimport { APIError } from 'payload'\n\nimport { buildMcpServer } from '../mcp/buildMcpServer.js'\nimport { getPluginConfig } from '../utils/getPluginConfig.js'\nimport { getAuthorizedMCP } from './access.js'\nimport { getMCPResource, oauthChallenge } from './oauth.js'\n\nexport const mcpEndpoint: PayloadHandler = async (req) => {\n if (!req.url) {\n throw new APIError('Missing request URL', 400)\n }\n\n req.payloadAPI = 'MCP' as const\n // Lets auth strategies check that an OAuth access token was issued for this MCP server\n req.context.mcp = { resource: getMCPResource({ req }) }\n\n const pluginConfig = getPluginConfig({ config: req.payload.config })\n const overrideAccessParam = new URL(req.url).searchParams.get('overrideAccess')\n\n if (overrideAccessParam !== null && process.env.NODE_ENV !== 'development') {\n throw new APIError('MCP overrideAccess is only available in development.', 400)\n }\n\n let overrideAccess = false\n if (overrideAccessParam === 'true') {\n overrideAccess = true\n } else if (overrideAccessParam !== null && overrideAccessParam !== 'false') {\n throw new APIError('MCP overrideAccess must be \"true\" or \"false\".', 400)\n }\n\n const authorizedMCP = await getAuthorizedMCP({ overrideAccess, req })\n\n if (pluginConfig.oauth && !req.user) {\n return oauthChallenge({ req })\n }\n\n const maxRequestBodySize = pluginConfig.mcp?.maxRequestBodySize\n // Payload augments the original web-standard Request in place.\n const mcpRequest = req as PayloadRequest & Request\n const parsedBody = await parseRequestBody({ maxRequestBodySize, request: mcpRequest })\n\n // Keep the old JSON-only, stateless behavior because the SDK's 2025 fallback uses SSE.\n if (await isLegacyRequest(mcpRequest, parsedBody, { maxRequestBodySize })) {\n const server = buildMcpServer({ authorizedMCP, pluginConfig, req })\n const transport = new WebStandardStreamableHTTPServerTransport({\n enableJsonResponse: true,\n maxRequestBodySize,\n sessionIdGenerator: undefined, // stateless mode\n })\n transport.onerror = (err) => {\n req.payload.logger.error({ err, msg: 'Error serving legacy MCP request' })\n }\n\n try {\n await server.connect(transport)\n return await transport.handleRequest(mcpRequest, { parsedBody })\n } finally {\n await server.close().catch((err) => {\n req.payload.logger.error({ err, msg: 'Error closing MCP server' })\n })\n }\n }\n\n const handler = createMcpHandler(() => buildMcpServer({ authorizedMCP, pluginConfig, req }), {\n legacy: 'reject',\n maxRequestBodySize,\n // SDK subscriptions always use SSE, so disable them to keep every response JSON-only.\n maxSubscriptions: 0,\n onerror: (err) => {\n req.payload.logger.error({ err, msg: 'Error serving modern MCP request' })\n },\n responseMode: 'json',\n })\n\n try {\n return await handler.fetch(mcpRequest, { parsedBody })\n } finally {\n await handler.close().catch((err) => {\n req.payload.logger.error({ err, msg: 'Error closing modern MCP handler' })\n })\n }\n}\n\n/**\n * Parses a POST body once so the era check and the SDK handler can share it. The SDK\n * skips its size limit for a pre-parsed body, so this reads with the same limit. It reads\n * a clone and returns `undefined` for any body the SDK should reject (too large, unreadable,\n * or not JSON), leaving the original request for the SDK to read and answer.\n */\nconst parseRequestBody = async ({\n maxRequestBodySize,\n request,\n}: {\n maxRequestBodySize?: number\n request: Request\n}): Promise<unknown> => {\n if (request.method.toUpperCase() !== 'POST') {\n return undefined\n }\n\n try {\n const body = await readRequestBody(request.clone(), maxRequestBodySize)\n return body.tooLarge ? undefined : JSON.parse(body.text)\n } catch {\n return undefined\n }\n}\n"],"names":["createMcpHandler","isLegacyRequest","readRequestBody","WebStandardStreamableHTTPServerTransport","APIError","buildMcpServer","getPluginConfig","getAuthorizedMCP","getMCPResource","oauthChallenge","mcpEndpoint","req","url","payloadAPI","context","mcp","resource","pluginConfig","config","payload","overrideAccessParam","URL","searchParams","get","process","env","NODE_ENV","overrideAccess","authorizedMCP","oauth","user","maxRequestBodySize","mcpRequest","parsedBody","parseRequestBody","request","server","transport","enableJsonResponse","sessionIdGenerator","undefined","onerror","err","logger","error","msg","connect","handleRequest","close","catch","handler","legacy","maxSubscriptions","responseMode","fetch","method","toUpperCase","body","clone","tooLarge","JSON","parse","text"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { PayloadHandler, PayloadRequest, SanitizedConfig } from 'payload';
2
+ /**
3
+ * Serves the OAuth protected resource metadata (RFC 9728) at `/api/mcp/oauth-protected-resource`.
4
+ * The OAuth challenge links here, so MCP clients find the authorization server without setup.
5
+ */
6
+ export declare const oauthProtectedResourceEndpoint: PayloadHandler;
7
+ /**
8
+ * Serves the same metadata for clients that ignore the challenge's link and only look at
9
+ * `/.well-known/oauth-protected-resource`. Mount it at the site root, e.g. in Next.js at
10
+ * `app/.well-known/oauth-protected-resource/[[...path]]/route.ts`:
11
+ *
12
+ * ```ts
13
+ * import config from '@payload-config'
14
+ * import { oauthProtectedResourceHandler } from '@payloadcms/plugin-mcp'
15
+ *
16
+ * export const GET = oauthProtectedResourceHandler(config)
17
+ * ```
18
+ */
19
+ export declare const oauthProtectedResourceHandler: (config: Promise<SanitizedConfig> | SanitizedConfig) => (request: Request) => Promise<Response>;
20
+ /**
21
+ * Answers an unauthenticated MCP request with a challenge linking to the protected resource
22
+ * metadata, which starts the client's OAuth flow.
23
+ */
24
+ export declare const oauthChallenge: ({ req }: {
25
+ req: PayloadRequest;
26
+ }) => Response;
27
+ /**
28
+ * The MCP endpoint's address, as advertised in the metadata's `resource`. The metadata link and
29
+ * the built-in authorization server's issuer are based on it. Clients send it when they sign in,
30
+ * and authorization servers that support it put it in the access token's audience.
31
+ */
32
+ export declare const getMCPResource: ({ req }: {
33
+ req: PayloadRequest;
34
+ }) => string;
35
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/endpoint/oauth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAW9E;;;GAGG;AACH,eAAO,MAAM,8BAA8B,EAAE,cAAsD,CAAA;AAEnG;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,6BAA6B,WAC/B,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe,eACnC,OAAO,KAAG,OAAO,CAAC,QAAQ,CAGtC,CAAA;AAEN;;;GAGG;AACH,eAAO,MAAM,cAAc,YAAa;IAAE,GAAG,EAAE,cAAc,CAAA;CAAE,KAAG,QAcjE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,YAAa;IAAE,GAAG,EAAE,cAAc,CAAA;CAAE,KAAG,MASjE,CAAA"}
@@ -0,0 +1,96 @@
1
+ import { bearerAuthChallengeResponse, OAuthError, OAuthErrorCode } from '@modelcontextprotocol/server';
2
+ import { createPayloadRequest, getPayload } from 'payload';
3
+ import { getPluginConfig } from '../utils/getPluginConfig.js';
4
+ /**
5
+ * Serves the OAuth protected resource metadata (RFC 9728) at `/api/mcp/oauth-protected-resource`.
6
+ * The OAuth challenge links here, so MCP clients find the authorization server without setup.
7
+ */ export const oauthProtectedResourceEndpoint = (req)=>getMetadataResponse({
8
+ req
9
+ });
10
+ /**
11
+ * Serves the same metadata for clients that ignore the challenge's link and only look at
12
+ * `/.well-known/oauth-protected-resource`. Mount it at the site root, e.g. in Next.js at
13
+ * `app/.well-known/oauth-protected-resource/[[...path]]/route.ts`:
14
+ *
15
+ * ```ts
16
+ * import config from '@payload-config'
17
+ * import { oauthProtectedResourceHandler } from '@payloadcms/plugin-mcp'
18
+ *
19
+ * export const GET = oauthProtectedResourceHandler(config)
20
+ * ```
21
+ */ export const oauthProtectedResourceHandler = (config)=>async (request)=>getMetadataResponse({
22
+ req: await createPayloadRequest({
23
+ payload: await getPayload({
24
+ config
25
+ }),
26
+ req: request
27
+ })
28
+ });
29
+ /**
30
+ * Answers an unauthenticated MCP request with a challenge linking to the protected resource
31
+ * metadata, which starts the client's OAuth flow.
32
+ */ export const oauthChallenge = ({ req })=>{
33
+ const { oauth } = getPluginConfig({
34
+ config: req.payload.config
35
+ });
36
+ const response = bearerAuthChallengeResponse(new OAuthError(OAuthErrorCode.InvalidToken, req.t('error:unauthorized')), {
37
+ requiredScopes: oauth?.type === 'external' ? oauth.scopes : undefined,
38
+ resourceMetadataUrl: `${getMCPResource({
39
+ req
40
+ })}/oauth-protected-resource`
41
+ });
42
+ // Lets browser-based MCP clients read the challenge, when Payload's `cors` setting allows them
43
+ response.headers.set('Access-Control-Expose-Headers', 'WWW-Authenticate');
44
+ return response;
45
+ };
46
+ /**
47
+ * The MCP endpoint's address, as advertised in the metadata's `resource`. The metadata link and
48
+ * the built-in authorization server's issuer are based on it. Clients send it when they sign in,
49
+ * and authorization servers that support it put it in the access token's audience.
50
+ */ export const getMCPResource = ({ req })=>{
51
+ const { config } = req.payload;
52
+ const resource = getPluginConfig({
53
+ config
54
+ }).oauth?.resource;
55
+ if (typeof resource === 'function') {
56
+ return resource({
57
+ req
58
+ });
59
+ }
60
+ return resource ?? new URL(`${config.routes.api}/mcp`, config.serverURL || req.url).href;
61
+ };
62
+ const getMetadataResponse = async ({ req })=>{
63
+ const { config } = req.payload;
64
+ const { oauth } = getPluginConfig({
65
+ config
66
+ });
67
+ if (!oauth) {
68
+ return new Response(null, {
69
+ status: 404
70
+ });
71
+ }
72
+ const resource = getMCPResource({
73
+ req
74
+ });
75
+ return Response.json({
76
+ authorization_servers: oauth.type === 'builtin' ? [
77
+ `${resource}/oauth`
78
+ ] : typeof oauth.authorizationServers === 'function' ? await oauth.authorizationServers({
79
+ req
80
+ }) : oauth.authorizationServers,
81
+ bearer_methods_supported: [
82
+ 'header'
83
+ ],
84
+ resource,
85
+ scopes_supported: oauth.type === 'external' ? oauth.scopes : undefined
86
+ }, // Public document, readable by browser-based MCP clients on any origin. Not cached, since its
87
+ // URLs can depend on the request.
88
+ {
89
+ headers: {
90
+ 'Access-Control-Allow-Origin': '*',
91
+ 'Cache-Control': 'no-store'
92
+ }
93
+ });
94
+ };
95
+
96
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoint/oauth.ts"],"sourcesContent":["import type { OAuthProtectedResourceMetadata } from '@modelcontextprotocol/server'\nimport type { PayloadHandler, PayloadRequest, SanitizedConfig } from 'payload'\n\nimport {\n bearerAuthChallengeResponse,\n OAuthError,\n OAuthErrorCode,\n} from '@modelcontextprotocol/server'\nimport { createPayloadRequest, getPayload } from 'payload'\n\nimport { getPluginConfig } from '../utils/getPluginConfig.js'\n\n/**\n * Serves the OAuth protected resource metadata (RFC 9728) at `/api/mcp/oauth-protected-resource`.\n * The OAuth challenge links here, so MCP clients find the authorization server without setup.\n */\nexport const oauthProtectedResourceEndpoint: PayloadHandler = (req) => getMetadataResponse({ req })\n\n/**\n * Serves the same metadata for clients that ignore the challenge's link and only look at\n * `/.well-known/oauth-protected-resource`. Mount it at the site root, e.g. in Next.js at\n * `app/.well-known/oauth-protected-resource/[[...path]]/route.ts`:\n *\n * ```ts\n * import config from '@payload-config'\n * import { oauthProtectedResourceHandler } from '@payloadcms/plugin-mcp'\n *\n * export const GET = oauthProtectedResourceHandler(config)\n * ```\n */\nexport const oauthProtectedResourceHandler =\n (config: Promise<SanitizedConfig> | SanitizedConfig) =>\n async (request: Request): Promise<Response> =>\n getMetadataResponse({\n req: await createPayloadRequest({ payload: await getPayload({ config }), req: request }),\n })\n\n/**\n * Answers an unauthenticated MCP request with a challenge linking to the protected resource\n * metadata, which starts the client's OAuth flow.\n */\nexport const oauthChallenge = ({ req }: { req: PayloadRequest }): Response => {\n const { oauth } = getPluginConfig({ config: req.payload.config })\n const response = bearerAuthChallengeResponse(\n new OAuthError(OAuthErrorCode.InvalidToken, req.t('error:unauthorized')),\n {\n requiredScopes: oauth?.type === 'external' ? oauth.scopes : undefined,\n resourceMetadataUrl: `${getMCPResource({ req })}/oauth-protected-resource`,\n },\n )\n\n // Lets browser-based MCP clients read the challenge, when Payload's `cors` setting allows them\n response.headers.set('Access-Control-Expose-Headers', 'WWW-Authenticate')\n\n return response\n}\n\n/**\n * The MCP endpoint's address, as advertised in the metadata's `resource`. The metadata link and\n * the built-in authorization server's issuer are based on it. Clients send it when they sign in,\n * and authorization servers that support it put it in the access token's audience.\n */\nexport const getMCPResource = ({ req }: { req: PayloadRequest }): string => {\n const { config } = req.payload\n const resource = getPluginConfig({ config }).oauth?.resource\n\n if (typeof resource === 'function') {\n return resource({ req })\n }\n\n return resource ?? new URL(`${config.routes.api}/mcp`, config.serverURL || req.url).href\n}\n\nconst getMetadataResponse = async ({ req }: { req: PayloadRequest }): Promise<Response> => {\n const { config } = req.payload\n const { oauth } = getPluginConfig({ config })\n\n if (!oauth) {\n return new Response(null, { status: 404 })\n }\n\n const resource = getMCPResource({ req })\n\n return Response.json(\n {\n authorization_servers:\n oauth.type === 'builtin'\n ? [`${resource}/oauth`]\n : typeof oauth.authorizationServers === 'function'\n ? await oauth.authorizationServers({ req })\n : oauth.authorizationServers,\n bearer_methods_supported: ['header'],\n resource,\n scopes_supported: oauth.type === 'external' ? oauth.scopes : undefined,\n } satisfies OAuthProtectedResourceMetadata,\n // Public document, readable by browser-based MCP clients on any origin. Not cached, since its\n // URLs can depend on the request.\n { headers: { 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-store' } },\n )\n}\n"],"names":["bearerAuthChallengeResponse","OAuthError","OAuthErrorCode","createPayloadRequest","getPayload","getPluginConfig","oauthProtectedResourceEndpoint","req","getMetadataResponse","oauthProtectedResourceHandler","config","request","payload","oauthChallenge","oauth","response","InvalidToken","t","requiredScopes","type","scopes","undefined","resourceMetadataUrl","getMCPResource","headers","set","resource","URL","routes","api","serverURL","url","href","Response","status","json","authorization_servers","authorizationServers","bearer_methods_supported","scopes_supported"],"mappings":"AAGA,SACEA,2BAA2B,EAC3BC,UAAU,EACVC,cAAc,QACT,+BAA8B;AACrC,SAASC,oBAAoB,EAAEC,UAAU,QAAQ,UAAS;AAE1D,SAASC,eAAe,QAAQ,8BAA6B;AAE7D;;;CAGC,GACD,OAAO,MAAMC,iCAAiD,CAACC,MAAQC,oBAAoB;QAAED;IAAI,GAAE;AAEnG;;;;;;;;;;;CAWC,GACD,OAAO,MAAME,gCACX,CAACC,SACD,OAAOC,UACLH,oBAAoB;YAClBD,KAAK,MAAMJ,qBAAqB;gBAAES,SAAS,MAAMR,WAAW;oBAAEM;gBAAO;gBAAIH,KAAKI;YAAQ;QACxF,GAAE;AAEN;;;CAGC,GACD,OAAO,MAAME,iBAAiB,CAAC,EAAEN,GAAG,EAA2B;IAC7D,MAAM,EAAEO,KAAK,EAAE,GAAGT,gBAAgB;QAAEK,QAAQH,IAAIK,OAAO,CAACF,MAAM;IAAC;IAC/D,MAAMK,WAAWf,4BACf,IAAIC,WAAWC,eAAec,YAAY,EAAET,IAAIU,CAAC,CAAC,wBAClD;QACEC,gBAAgBJ,OAAOK,SAAS,aAAaL,MAAMM,MAAM,GAAGC;QAC5DC,qBAAqB,GAAGC,eAAe;YAAEhB;QAAI,GAAG,yBAAyB,CAAC;IAC5E;IAGF,+FAA+F;IAC/FQ,SAASS,OAAO,CAACC,GAAG,CAAC,iCAAiC;IAEtD,OAAOV;AACT,EAAC;AAED;;;;CAIC,GACD,OAAO,MAAMQ,iBAAiB,CAAC,EAAEhB,GAAG,EAA2B;IAC7D,MAAM,EAAEG,MAAM,EAAE,GAAGH,IAAIK,OAAO;IAC9B,MAAMc,WAAWrB,gBAAgB;QAAEK;IAAO,GAAGI,KAAK,EAAEY;IAEpD,IAAI,OAAOA,aAAa,YAAY;QAClC,OAAOA,SAAS;YAAEnB;QAAI;IACxB;IAEA,OAAOmB,YAAY,IAAIC,IAAI,GAAGjB,OAAOkB,MAAM,CAACC,GAAG,CAAC,IAAI,CAAC,EAAEnB,OAAOoB,SAAS,IAAIvB,IAAIwB,GAAG,EAAEC,IAAI;AAC1F,EAAC;AAED,MAAMxB,sBAAsB,OAAO,EAAED,GAAG,EAA2B;IACjE,MAAM,EAAEG,MAAM,EAAE,GAAGH,IAAIK,OAAO;IAC9B,MAAM,EAAEE,KAAK,EAAE,GAAGT,gBAAgB;QAAEK;IAAO;IAE3C,IAAI,CAACI,OAAO;QACV,OAAO,IAAImB,SAAS,MAAM;YAAEC,QAAQ;QAAI;IAC1C;IAEA,MAAMR,WAAWH,eAAe;QAAEhB;IAAI;IAEtC,OAAO0B,SAASE,IAAI,CAClB;QACEC,uBACEtB,MAAMK,IAAI,KAAK,YACX;YAAC,GAAGO,SAAS,MAAM,CAAC;SAAC,GACrB,OAAOZ,MAAMuB,oBAAoB,KAAK,aACpC,MAAMvB,MAAMuB,oBAAoB,CAAC;YAAE9B;QAAI,KACvCO,MAAMuB,oBAAoB;QAClCC,0BAA0B;YAAC;SAAS;QACpCZ;QACAa,kBAAkBzB,MAAMK,IAAI,KAAK,aAAaL,MAAMM,MAAM,GAAGC;IAC/D,GACA,8FAA8F;IAC9F,kCAAkC;IAClC;QAAEG,SAAS;YAAE,+BAA+B;YAAK,iBAAiB;QAAW;IAAE;AAEnF"}
@@ -0,0 +1,21 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { MCPOAuthTokens } from '../types.js';
3
+ /**
4
+ * The default `issueToken`: a Payload login token, sealed so it only works for MCP, where
5
+ * `authenticateLoginToken` unseals it. Otherwise an MCP client could use it as the user's admin
6
+ * panel login. When the collection uses sessions, the client also gets a refresh token.
7
+ */
8
+ export declare const issueLoginToken: ({ req, }: {
9
+ req: PayloadRequest;
10
+ }) => Promise<MCPOAuthTokens>;
11
+ /** The default `refreshToken`. Payload logs out the session when a refresh token is reused. */
12
+ export declare const refreshLoginToken: ({ refreshToken, req, }: {
13
+ refreshToken: string;
14
+ req: PayloadRequest;
15
+ }) => Promise<MCPOAuthTokens | null>;
16
+ /** Finds the user of an MCP request's access token from `issueLoginToken`. */
17
+ export declare const authenticateLoginToken: ({ req, token, }: {
18
+ req: PayloadRequest;
19
+ token: string;
20
+ }) => Promise<PayloadRequest['user']>;
21
+ //# sourceMappingURL=oauthLoginToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthLoginToken.d.ts","sourceRoot":"","sources":["../../src/endpoint/oauthLoginToken.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAI7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIjD;;;;GAIG;AACH,eAAO,MAAM,eAAe,aAEzB;IACD,GAAG,EAAE,cAAc,CAAA;CACpB,KAAG,OAAO,CAAC,cAAc,CA6BzB,CAAA;AAED,+FAA+F;AAC/F,eAAO,MAAM,iBAAiB,2BAG3B;IACD,YAAY,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,cAAc,CAAA;CACpB,KAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAUhC,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,oBAGhC;IACD,GAAG,EAAE,cAAc,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;CACd,KAAG,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAajC,CAAA"}
@@ -0,0 +1,79 @@
1
+ import { APIError, createAuthToken, executeAuthStrategies, redeemRefreshToken } from 'payload';
2
+ import { seal, unseal } from '../utils/seal.js';
3
+ /**
4
+ * The default `issueToken`: a Payload login token, sealed so it only works for MCP, where
5
+ * `authenticateLoginToken` unseals it. Otherwise an MCP client could use it as the user's admin
6
+ * panel login. When the collection uses sessions, the client also gets a refresh token.
7
+ */ export const issueLoginToken = async ({ req })=>{
8
+ const collectionConfig = req.payload.collections[req.user.collection].config;
9
+ if (collectionConfig.auth.disableLocalStrategy) {
10
+ throw new APIError(`MCP clients can't get Payload login tokens for the "${collectionConfig.slug}" collection because its local strategy is disabled. Pass oauth.issueToken to the MCP plugin.`);
11
+ }
12
+ // The full document, like the one a password login starts its session on
13
+ const user = await req.payload.db.findOne({
14
+ collection: collectionConfig.slug,
15
+ req,
16
+ where: {
17
+ id: {
18
+ equals: req.user.id
19
+ }
20
+ }
21
+ });
22
+ const { refreshToken, token } = await createAuthToken({
23
+ collectionConfig,
24
+ email: user.email,
25
+ req,
26
+ shouldCreateRefreshToken: true,
27
+ user
28
+ });
29
+ return {
30
+ accessToken: sealLoginToken({
31
+ req,
32
+ token
33
+ }),
34
+ expiresIn: collectionConfig.auth.tokenExpiration,
35
+ refreshToken
36
+ };
37
+ };
38
+ /** The default `refreshToken`. Payload logs out the session when a refresh token is reused. */ export const refreshLoginToken = async ({ refreshToken, req })=>{
39
+ const refreshed = await redeemRefreshToken({
40
+ refreshToken,
41
+ req
42
+ });
43
+ return refreshed && {
44
+ accessToken: sealLoginToken({
45
+ req,
46
+ token: refreshed.token
47
+ }),
48
+ expiresIn: refreshed.collectionConfig.auth.tokenExpiration,
49
+ refreshToken: refreshed.refreshToken
50
+ };
51
+ };
52
+ /** Finds the user of an MCP request's access token from `issueLoginToken`. */ export const authenticateLoginToken = async ({ req, token })=>{
53
+ const loginToken = unseal({
54
+ type: 'access',
55
+ req,
56
+ value: token
57
+ });
58
+ if (!loginToken) {
59
+ return null;
60
+ }
61
+ const headers = new Headers({
62
+ Authorization: `JWT ${loginToken.token}`,
63
+ DisableAutologin: 'true'
64
+ });
65
+ return (await executeAuthStrategies({
66
+ headers,
67
+ payload: req.payload,
68
+ req
69
+ })).user;
70
+ };
71
+ const sealLoginToken = ({ req, token })=>seal({
72
+ type: 'access',
73
+ data: {
74
+ token
75
+ },
76
+ req
77
+ });
78
+
79
+ //# sourceMappingURL=oauthLoginToken.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoint/oauthLoginToken.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { APIError, createAuthToken, executeAuthStrategies, redeemRefreshToken } from 'payload'\n\nimport type { MCPOAuthTokens } from '../types.js'\n\nimport { seal, unseal } from '../utils/seal.js'\n\n/**\n * The default `issueToken`: a Payload login token, sealed so it only works for MCP, where\n * `authenticateLoginToken` unseals it. Otherwise an MCP client could use it as the user's admin\n * panel login. When the collection uses sessions, the client also gets a refresh token.\n */\nexport const issueLoginToken = async ({\n req,\n}: {\n req: PayloadRequest\n}): Promise<MCPOAuthTokens> => {\n const collectionConfig = req.payload.collections[req.user!.collection]!.config\n\n if (collectionConfig.auth.disableLocalStrategy) {\n throw new APIError(\n `MCP clients can't get Payload login tokens for the \"${collectionConfig.slug}\" collection because its local strategy is disabled. Pass oauth.issueToken to the MCP plugin.`,\n )\n }\n\n // The full document, like the one a password login starts its session on\n const user = (await req.payload.db.findOne<NonNullable<PayloadRequest['user']>>({\n collection: collectionConfig.slug,\n req,\n where: { id: { equals: req.user!.id } },\n }))!\n\n const { refreshToken, token } = await createAuthToken({\n collectionConfig,\n email: user.email!,\n req,\n shouldCreateRefreshToken: true,\n user,\n })\n\n return {\n accessToken: sealLoginToken({ req, token }),\n expiresIn: collectionConfig.auth.tokenExpiration,\n refreshToken,\n }\n}\n\n/** The default `refreshToken`. Payload logs out the session when a refresh token is reused. */\nexport const refreshLoginToken = async ({\n refreshToken,\n req,\n}: {\n refreshToken: string\n req: PayloadRequest\n}): Promise<MCPOAuthTokens | null> => {\n const refreshed = await redeemRefreshToken({ refreshToken, req })\n\n return (\n refreshed && {\n accessToken: sealLoginToken({ req, token: refreshed.token }),\n expiresIn: refreshed.collectionConfig.auth.tokenExpiration,\n refreshToken: refreshed.refreshToken,\n }\n )\n}\n\n/** Finds the user of an MCP request's access token from `issueLoginToken`. */\nexport const authenticateLoginToken = async ({\n req,\n token,\n}: {\n req: PayloadRequest\n token: string\n}): Promise<PayloadRequest['user']> => {\n const loginToken = unseal<{ token: string }>({ type: 'access', req, value: token })\n\n if (!loginToken) {\n return null\n }\n\n const headers = new Headers({\n Authorization: `JWT ${loginToken.token}`,\n DisableAutologin: 'true',\n })\n\n return (await executeAuthStrategies({ headers, payload: req.payload, req })).user\n}\n\nconst sealLoginToken = ({ req, token }: { req: PayloadRequest; token: string }): string =>\n seal({ type: 'access', data: { token }, req })\n"],"names":["APIError","createAuthToken","executeAuthStrategies","redeemRefreshToken","seal","unseal","issueLoginToken","req","collectionConfig","payload","collections","user","collection","config","auth","disableLocalStrategy","slug","db","findOne","where","id","equals","refreshToken","token","email","shouldCreateRefreshToken","accessToken","sealLoginToken","expiresIn","tokenExpiration","refreshLoginToken","refreshed","authenticateLoginToken","loginToken","type","value","headers","Headers","Authorization","DisableAutologin","data"],"mappings":"AAEA,SAASA,QAAQ,EAAEC,eAAe,EAAEC,qBAAqB,EAAEC,kBAAkB,QAAQ,UAAS;AAI9F,SAASC,IAAI,EAAEC,MAAM,QAAQ,mBAAkB;AAE/C;;;;CAIC,GACD,OAAO,MAAMC,kBAAkB,OAAO,EACpCC,GAAG,EAGJ;IACC,MAAMC,mBAAmBD,IAAIE,OAAO,CAACC,WAAW,CAACH,IAAII,IAAI,CAAEC,UAAU,CAAC,CAAEC,MAAM;IAE9E,IAAIL,iBAAiBM,IAAI,CAACC,oBAAoB,EAAE;QAC9C,MAAM,IAAIf,SACR,CAAC,oDAAoD,EAAEQ,iBAAiBQ,IAAI,CAAC,6FAA6F,CAAC;IAE/K;IAEA,yEAAyE;IACzE,MAAML,OAAQ,MAAMJ,IAAIE,OAAO,CAACQ,EAAE,CAACC,OAAO,CAAsC;QAC9EN,YAAYJ,iBAAiBQ,IAAI;QACjCT;QACAY,OAAO;YAAEC,IAAI;gBAAEC,QAAQd,IAAII,IAAI,CAAES,EAAE;YAAC;QAAE;IACxC;IAEA,MAAM,EAAEE,YAAY,EAAEC,KAAK,EAAE,GAAG,MAAMtB,gBAAgB;QACpDO;QACAgB,OAAOb,KAAKa,KAAK;QACjBjB;QACAkB,0BAA0B;QAC1Bd;IACF;IAEA,OAAO;QACLe,aAAaC,eAAe;YAAEpB;YAAKgB;QAAM;QACzCK,WAAWpB,iBAAiBM,IAAI,CAACe,eAAe;QAChDP;IACF;AACF,EAAC;AAED,6FAA6F,GAC7F,OAAO,MAAMQ,oBAAoB,OAAO,EACtCR,YAAY,EACZf,GAAG,EAIJ;IACC,MAAMwB,YAAY,MAAM5B,mBAAmB;QAAEmB;QAAcf;IAAI;IAE/D,OACEwB,aAAa;QACXL,aAAaC,eAAe;YAAEpB;YAAKgB,OAAOQ,UAAUR,KAAK;QAAC;QAC1DK,WAAWG,UAAUvB,gBAAgB,CAACM,IAAI,CAACe,eAAe;QAC1DP,cAAcS,UAAUT,YAAY;IACtC;AAEJ,EAAC;AAED,4EAA4E,GAC5E,OAAO,MAAMU,yBAAyB,OAAO,EAC3CzB,GAAG,EACHgB,KAAK,EAIN;IACC,MAAMU,aAAa5B,OAA0B;QAAE6B,MAAM;QAAU3B;QAAK4B,OAAOZ;IAAM;IAEjF,IAAI,CAACU,YAAY;QACf,OAAO;IACT;IAEA,MAAMG,UAAU,IAAIC,QAAQ;QAC1BC,eAAe,CAAC,IAAI,EAAEL,WAAWV,KAAK,EAAE;QACxCgB,kBAAkB;IACpB;IAEA,OAAO,AAAC,CAAA,MAAMrC,sBAAsB;QAAEkC;QAAS3B,SAASF,IAAIE,OAAO;QAAEF;IAAI,EAAC,EAAGI,IAAI;AACnF,EAAC;AAED,MAAMgB,iBAAiB,CAAC,EAAEpB,GAAG,EAAEgB,KAAK,EAA0C,GAC5EnB,KAAK;QAAE8B,MAAM;QAAUM,MAAM;YAAEjB;QAAM;QAAGhB;IAAI"}
@@ -0,0 +1,12 @@
1
+ import type { Endpoint } from 'payload';
2
+ /**
3
+ * Turns Payload into the OAuth authorization server for MCP clients when `oauth.type` is
4
+ * `'builtin'`. Clients register, the user allows them after logging in to the admin panel, and
5
+ * the client receives the tokens `issueToken` creates.
6
+ *
7
+ * Clients, consent forms, authorization codes, and refresh tokens are encrypted and handed to the
8
+ * client instead of being stored. Only the default tokens write to the database, to the user's
9
+ * login sessions.
10
+ */
11
+ export declare const oauthServerEndpoints: Endpoint[];
12
+ //# sourceMappingURL=oauthServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthServer.d.ts","sourceRoot":"","sources":["../../src/endpoint/oauthServer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAkC,MAAM,SAAS,CAAA;AAoCvE;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,EAAE,QAAQ,EAqC1C,CAAA"}