@payloadcms/plugin-mcp 4.0.0-internal.cea8867 → 4.0.0-internal.d927017

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 (58) hide show
  1. package/bin.js +8 -6
  2. package/dist/bin/index.d.ts +2 -0
  3. package/dist/bin/index.d.ts.map +1 -0
  4. package/dist/bin/index.js +12 -0
  5. package/dist/bin/index.js.map +1 -0
  6. package/dist/bin/initializeViteHMR.d.ts +8 -0
  7. package/dist/bin/initializeViteHMR.d.ts.map +1 -0
  8. package/dist/bin/initializeViteHMR.js +63 -0
  9. package/dist/bin/initializeViteHMR.js.map +1 -0
  10. package/dist/bin/viteConfigBoundary.d.ts +6 -0
  11. package/dist/bin/viteConfigBoundary.d.ts.map +1 -0
  12. package/dist/bin/viteConfigBoundary.js +25 -0
  13. package/dist/bin/viteConfigBoundary.js.map +1 -0
  14. package/dist/defaultAccess.d.ts.map +1 -1
  15. package/dist/defineTool.d.ts +8 -8
  16. package/dist/defineTool.d.ts.map +1 -1
  17. package/dist/endpoint/access.d.ts.map +1 -1
  18. package/dist/mcp/buildMcpServer.d.ts.map +1 -1
  19. package/dist/mcp/builtin/collections/createTool.d.ts.map +1 -1
  20. package/dist/mcp/builtin/collections/createTool.js +1 -11
  21. package/dist/mcp/builtin/collections/createTool.js.map +1 -1
  22. package/dist/mcp/builtin/collections/formatCollectionError.d.ts +1 -1
  23. package/dist/mcp/builtin/collections/formatCollectionError.d.ts.map +1 -1
  24. package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts.map +1 -1
  25. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js +2 -23
  26. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js.map +1 -1
  27. package/dist/mcp/builtin/collections/updateTool.d.ts.map +1 -1
  28. package/dist/mcp/builtin/collections/updateTool.js +6 -12
  29. package/dist/mcp/builtin/collections/updateTool.js.map +1 -1
  30. package/dist/mcp/builtin/validateEntityData.d.ts.map +1 -1
  31. package/dist/mcp/builtinTools.d.ts.map +1 -1
  32. package/dist/mcp/sanitizeMCPConfig.d.ts.map +1 -1
  33. package/dist/stdio.d.ts +2 -1
  34. package/dist/stdio.d.ts.map +1 -1
  35. package/dist/stdio.js +4 -3
  36. package/dist/stdio.js.map +1 -1
  37. package/dist/utils/camelCase.d.ts.map +1 -1
  38. package/dist/utils/resolveProjectRoot.d.ts.map +1 -1
  39. package/dist/utils/schemaConversion/filterFieldsByAccess.d.ts.map +1 -1
  40. package/dist/utils/schemaConversion/getEntityInputSchema.d.ts.map +1 -1
  41. package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts.map +1 -1
  42. package/dist/utils/toStandardSchema.d.ts.map +1 -1
  43. package/package.json +17 -4
  44. package/src/bin/index.ts +16 -0
  45. package/src/bin/initializeViteHMR.ts +60 -0
  46. package/src/bin/viteConfigBoundary.ts +39 -0
  47. package/src/mcp/builtin/collections/createTool.ts +1 -5
  48. package/src/mcp/builtin/collections/getCollectionSchemaTool.ts +1 -14
  49. package/src/mcp/builtin/collections/updateTool.ts +4 -6
  50. package/src/stdio.ts +4 -3
  51. package/dist/mcp/builtin/collections/fileInput.d.ts +0 -20
  52. package/dist/mcp/builtin/collections/fileInput.d.ts.map +0 -1
  53. package/dist/mcp/builtin/collections/fileInput.js +0 -100
  54. package/dist/mcp/builtin/collections/fileInput.js.map +0 -1
  55. package/dist/mcp/builtin/collections/fileInput.spec.js +0 -72
  56. package/dist/mcp/builtin/collections/fileInput.spec.js.map +0 -1
  57. package/src/mcp/builtin/collections/fileInput.spec.ts +0 -79
  58. package/src/mcp/builtin/collections/fileInput.ts +0 -139
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":"AAgBA;;;;;GAKG;AACH,eAAO,MAAM,WAAW,QAAa,OAAO,CAAC,IAAI,CA2GhD,CAAA"}
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAA;AAgB9D;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAU,iBAAiB,eAAe,KAAG,OAAO,CAAC,IAAI,CA4GhF,CAAA"}
package/dist/stdio.js CHANGED
@@ -12,7 +12,7 @@ import { resolveProjectRoot } from './utils/resolveProjectRoot.js';
12
12
  *
13
13
  * Set `PAYLOAD_MCP_AUTHORIZATION` to authenticate. In development,
14
14
  * `PAYLOAD_MCP_OVERRIDE_ACCESS=true` skips access checks.
15
- */ export const runMcpStdio = async ()=>{
15
+ */ export const runMcpStdio = async (configOverride)=>{
16
16
  // MCP clients may start this command from another folder. Move to the Payload
17
17
  // project so findConfig() can find the config.
18
18
  const projectRoot = resolveProjectRoot(fileURLToPath(import.meta.url));
@@ -20,8 +20,9 @@ import { resolveProjectRoot } from './utils/resolveProjectRoot.js';
20
20
  process.chdir(projectRoot);
21
21
  }
22
22
  const configPath = findConfig();
23
- const configModule = await import(pathToFileURL(configPath).toString());
24
- const config = await (configModule.default ?? configModule);
23
+ const configURL = pathToFileURL(configPath).href;
24
+ const configModule = configOverride ? undefined : await import(configURL);
25
+ const config = configOverride ?? await (configModule?.default ?? configModule);
25
26
  /**
26
27
  * stdout is only for MCP messages. The spec says the server must not write
27
28
  * logs there and may write them to stderr instead.
package/dist/stdio.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stdio.ts"],"sourcesContent":["import type { Config, Plugin, SanitizedConfig } from 'payload'\n\n/* eslint-disable no-console */\nimport { serveStdio } from '@modelcontextprotocol/server/stdio'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\nimport { createLocalReq, getPayload } from 'payload'\nimport { findConfig } from 'payload/node'\n\nimport type { SanitizedMCPPluginConfig } from './types.js'\n\nimport { getAuthorizedMCP } from './endpoint/access.js'\nimport { buildMcpServer } from './mcp/buildMcpServer.js'\nimport { sanitizeMCPConfig } from './mcp/sanitizeMCPConfig.js'\nimport { getPluginConfig } from './utils/getPluginConfig.js'\nimport { resolveProjectRoot } from './utils/resolveProjectRoot.js'\n\n/**\n * Starts Payload's MCP server over stdin and stdout.\n *\n * Set `PAYLOAD_MCP_AUTHORIZATION` to authenticate. In development,\n * `PAYLOAD_MCP_OVERRIDE_ACCESS=true` skips access checks.\n */\nexport const runMcpStdio = async (): Promise<void> => {\n // MCP clients may start this command from another folder. Move to the Payload\n // project so findConfig() can find the config.\n const projectRoot = resolveProjectRoot(fileURLToPath(import.meta.url))\n if (projectRoot) {\n process.chdir(projectRoot)\n }\n\n const configPath = findConfig()\n const configModule = await import(pathToFileURL(configPath).toString())\n const config = (await (configModule.default ?? configModule)) as SanitizedConfig\n\n /**\n * stdout is only for MCP messages. The spec says the server must not write\n * logs there and may write them to stderr instead.\n *\n * The 2.0 client library recovers from stray stdout logs, but that behavior is not\n * guaranteed. Move Payload logs to stderr so Payload does not add invalid data\n * to stdout. Keep options from configurable loggers. Replace pre-built loggers\n * because their output cannot be redirected.\n *\n * @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#stdio\n */\n const loggerConfig: { logger?: Config['logger'] } = config\n if (\n loggerConfig.logger &&\n typeof loggerConfig.logger === 'object' &&\n 'options' in loggerConfig.logger\n ) {\n loggerConfig.logger.destination = process.stderr\n } else {\n loggerConfig.logger = { destination: process.stderr, options: {} }\n }\n\n const payload = await getPayload({ config, cron: false, disableOnInit: true })\n\n let pluginConfig: SanitizedMCPPluginConfig\n try {\n pluginConfig = getPluginConfig({ config: payload.config })\n } catch {\n // The command also works when mcpPlugin() is not in the Payload config.\n pluginConfig = sanitizeMCPConfig({ config: payload.config, pluginConfig: {} })\n\n const fallbackPlugin: Plugin = (config) => config\n Object.assign(fallbackPlugin, {\n slug: '@payloadcms/plugin-mcp',\n sanitizedOptions: pluginConfig,\n })\n payload.config.plugins ??= []\n payload.config.plugins.push(fallbackPlugin)\n }\n\n const overrideAccessEnv = process.env.PAYLOAD_MCP_OVERRIDE_ACCESS\n let overrideAccess = false\n\n if (overrideAccessEnv === 'true') {\n overrideAccess = true\n } else if (overrideAccessEnv && overrideAccessEnv !== 'false') {\n throw new Error('PAYLOAD_MCP_OVERRIDE_ACCESS must be \"true\" or \"false\".')\n }\n\n if (overrideAccess && process.env.NODE_ENV !== 'development') {\n throw new Error('PAYLOAD_MCP_OVERRIDE_ACCESS is only available in development.')\n }\n\n const headers = new Headers()\n if (process.env.PAYLOAD_MCP_AUTHORIZATION) {\n headers.set('Authorization', process.env.PAYLOAD_MCP_AUTHORIZATION)\n }\n\n const req = await createLocalReq({ req: { headers } }, payload)\n req.payloadAPI = 'MCP' as const\n const authorizedMCP = await getAuthorizedMCP({ overrideAccess, req })\n\n const stdioServer = serveStdio(() => buildMcpServer({ authorizedMCP, pluginConfig, req }), {\n onerror: (err) => {\n // MCP messages use stdout, so write SDK errors to stderr.\n console.error('[payload-mcp] error serving MCP over stdio:', err)\n },\n })\n\n // Close the MCP server and Payload when the client disconnects or the process stops.\n let isShuttingDown = false\n const shutdown = async () => {\n if (isShuttingDown) {\n return\n }\n isShuttingDown = true\n\n try {\n await stdioServer.close()\n } catch (err) {\n console.error('[payload-mcp] error closing server:', err)\n }\n\n try {\n await payload.destroy()\n } catch (err) {\n console.error('[payload-mcp] error destroying payload:', err)\n }\n\n process.exit(0)\n }\n\n process.once('SIGINT', () => void shutdown())\n process.once('SIGTERM', () => void shutdown())\n process.stdin.once('close', () => void shutdown())\n}\n"],"names":["serveStdio","fileURLToPath","pathToFileURL","createLocalReq","getPayload","findConfig","getAuthorizedMCP","buildMcpServer","sanitizeMCPConfig","getPluginConfig","resolveProjectRoot","runMcpStdio","projectRoot","url","process","chdir","configPath","configModule","toString","config","default","loggerConfig","logger","destination","stderr","options","payload","cron","disableOnInit","pluginConfig","fallbackPlugin","Object","assign","slug","sanitizedOptions","plugins","push","overrideAccessEnv","env","PAYLOAD_MCP_OVERRIDE_ACCESS","overrideAccess","Error","NODE_ENV","headers","Headers","PAYLOAD_MCP_AUTHORIZATION","set","req","payloadAPI","authorizedMCP","stdioServer","onerror","err","console","error","isShuttingDown","shutdown","close","destroy","exit","once","stdin"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/stdio.ts"],"sourcesContent":["import type { Config, Plugin, SanitizedConfig } from 'payload'\n\n/* eslint-disable no-console */\nimport { serveStdio } from '@modelcontextprotocol/server/stdio'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\nimport { createLocalReq, getPayload } from 'payload'\nimport { findConfig } from 'payload/node'\n\nimport type { SanitizedMCPPluginConfig } from './types.js'\n\nimport { getAuthorizedMCP } from './endpoint/access.js'\nimport { buildMcpServer } from './mcp/buildMcpServer.js'\nimport { sanitizeMCPConfig } from './mcp/sanitizeMCPConfig.js'\nimport { getPluginConfig } from './utils/getPluginConfig.js'\nimport { resolveProjectRoot } from './utils/resolveProjectRoot.js'\n\n/**\n * Starts Payload's MCP server over stdin and stdout.\n *\n * Set `PAYLOAD_MCP_AUTHORIZATION` to authenticate. In development,\n * `PAYLOAD_MCP_OVERRIDE_ACCESS=true` skips access checks.\n */\nexport const runMcpStdio = async (configOverride?: SanitizedConfig): Promise<void> => {\n // MCP clients may start this command from another folder. Move to the Payload\n // project so findConfig() can find the config.\n const projectRoot = resolveProjectRoot(fileURLToPath(import.meta.url))\n if (projectRoot) {\n process.chdir(projectRoot)\n }\n\n const configPath = findConfig()\n const configURL = pathToFileURL(configPath).href\n const configModule = configOverride ? undefined : await import(configURL)\n const config: SanitizedConfig = configOverride ?? (await (configModule?.default ?? configModule))\n\n /**\n * stdout is only for MCP messages. The spec says the server must not write\n * logs there and may write them to stderr instead.\n *\n * The 2.0 client library recovers from stray stdout logs, but that behavior is not\n * guaranteed. Move Payload logs to stderr so Payload does not add invalid data\n * to stdout. Keep options from configurable loggers. Replace pre-built loggers\n * because their output cannot be redirected.\n *\n * @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#stdio\n */\n const loggerConfig: { logger?: Config['logger'] } = config\n if (\n loggerConfig.logger &&\n typeof loggerConfig.logger === 'object' &&\n 'options' in loggerConfig.logger\n ) {\n loggerConfig.logger.destination = process.stderr\n } else {\n loggerConfig.logger = { destination: process.stderr, options: {} }\n }\n\n const payload = await getPayload({ config, cron: false, disableOnInit: true })\n\n let pluginConfig: SanitizedMCPPluginConfig\n try {\n pluginConfig = getPluginConfig({ config: payload.config })\n } catch {\n // The command also works when mcpPlugin() is not in the Payload config.\n pluginConfig = sanitizeMCPConfig({ config: payload.config, pluginConfig: {} })\n\n const fallbackPlugin: Plugin = (config) => config\n Object.assign(fallbackPlugin, {\n slug: '@payloadcms/plugin-mcp',\n sanitizedOptions: pluginConfig,\n })\n payload.config.plugins ??= []\n payload.config.plugins.push(fallbackPlugin)\n }\n\n const overrideAccessEnv = process.env.PAYLOAD_MCP_OVERRIDE_ACCESS\n let overrideAccess = false\n\n if (overrideAccessEnv === 'true') {\n overrideAccess = true\n } else if (overrideAccessEnv && overrideAccessEnv !== 'false') {\n throw new Error('PAYLOAD_MCP_OVERRIDE_ACCESS must be \"true\" or \"false\".')\n }\n\n if (overrideAccess && process.env.NODE_ENV !== 'development') {\n throw new Error('PAYLOAD_MCP_OVERRIDE_ACCESS is only available in development.')\n }\n\n const headers = new Headers()\n if (process.env.PAYLOAD_MCP_AUTHORIZATION) {\n headers.set('Authorization', process.env.PAYLOAD_MCP_AUTHORIZATION)\n }\n\n const req = await createLocalReq({ req: { headers } }, payload)\n req.payloadAPI = 'MCP' as const\n const authorizedMCP = await getAuthorizedMCP({ overrideAccess, req })\n\n const stdioServer = serveStdio(() => buildMcpServer({ authorizedMCP, pluginConfig, req }), {\n onerror: (err) => {\n // MCP messages use stdout, so write SDK errors to stderr.\n console.error('[payload-mcp] error serving MCP over stdio:', err)\n },\n })\n\n // Close the MCP server and Payload when the client disconnects or the process stops.\n let isShuttingDown = false\n const shutdown = async () => {\n if (isShuttingDown) {\n return\n }\n isShuttingDown = true\n\n try {\n await stdioServer.close()\n } catch (err) {\n console.error('[payload-mcp] error closing server:', err)\n }\n\n try {\n await payload.destroy()\n } catch (err) {\n console.error('[payload-mcp] error destroying payload:', err)\n }\n\n process.exit(0)\n }\n\n process.once('SIGINT', () => void shutdown())\n process.once('SIGTERM', () => void shutdown())\n process.stdin.once('close', () => void shutdown())\n}\n"],"names":["serveStdio","fileURLToPath","pathToFileURL","createLocalReq","getPayload","findConfig","getAuthorizedMCP","buildMcpServer","sanitizeMCPConfig","getPluginConfig","resolveProjectRoot","runMcpStdio","configOverride","projectRoot","url","process","chdir","configPath","configURL","href","configModule","undefined","config","default","loggerConfig","logger","destination","stderr","options","payload","cron","disableOnInit","pluginConfig","fallbackPlugin","Object","assign","slug","sanitizedOptions","plugins","push","overrideAccessEnv","env","PAYLOAD_MCP_OVERRIDE_ACCESS","overrideAccess","Error","NODE_ENV","headers","Headers","PAYLOAD_MCP_AUTHORIZATION","set","req","payloadAPI","authorizedMCP","stdioServer","onerror","err","console","error","isShuttingDown","shutdown","close","destroy","exit","once","stdin"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"camelCase.d.ts","sourceRoot":"","sources":["../../src/utils/camelCase.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,QAAS,MAAM,KAAG,MAIzC,CAAA"}
1
+ {"version":3,"file":"camelCase.d.ts","sourceRoot":"","sources":["../../src/utils/camelCase.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,MAIzC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"resolveProjectRoot.d.ts","sourceRoot":"","sources":["../../src/utils/resolveProjectRoot.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,aAAc,MAAM,KAAG,IAAI,GAAG,MAS5D,CAAA"}
1
+ {"version":3,"file":"resolveProjectRoot.d.ts","sourceRoot":"","sources":["../../src/utils/resolveProjectRoot.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,IAAI,GAAG,MAS5D,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"filterFieldsByAccess.d.ts","sourceRoot":"","sources":["../../../src/utils/schemaConversion/filterFieldsByAccess.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,SAAS,EACT,4BAA4B,EAE7B,MAAM,SAAS,CAAA;AAEhB,KAAK,cAAc,GAAG,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;AAClD,KAAK,sBAAsB,GAAG,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,CAAA;AAE7D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,oBAAoB,yDAK9B;IACD,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;IACzB,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,WAAW,EAAE,4BAA4B,CAAA;IACzC,kBAAkB,EAAE,CAAC,iBAAiB,EAAE,sBAAsB,KAAK,OAAO,CAAA;CAC3E,KAAG,cAAc,EAoFjB,CAAA"}
1
+ {"version":3,"file":"filterFieldsByAccess.d.ts","sourceRoot":"","sources":["../../../src/utils/schemaConversion/filterFieldsByAccess.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,SAAS,EACT,4BAA4B,EAE7B,MAAM,SAAS,CAAA;AAEhB,KAAK,cAAc,GAAG,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;AAClD,KAAK,sBAAsB,GAAG,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,CAAA;AAE7D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,oBAAoB,GAAI,sDAKlC;IACD,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;IACzB,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,WAAW,EAAE,4BAA4B,CAAA;IACzC,kBAAkB,EAAE,CAAC,iBAAiB,EAAE,sBAAsB,KAAK,OAAO,CAAA;CAC3E,KAAG,cAAc,EAoFjB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"getEntityInputSchema.d.ts","sourceRoot":"","sources":["../../../src/utils/schemaConversion/getEntityInputSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EAGnB,KAAK,UAAU,EACf,KAAK,cAAc,EAEnB,KAAK,6BAA6B,EAElC,KAAK,yBAAyB,EAC/B,MAAM,SAAS,CAAA;AAEhB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAKpD,eAAO,MAAM,wBAAwB,0CAIlC;IACD,cAAc,EAAE,cAAc,CAAA;IAC9B,WAAW,CAAC,EAAE,6BAA6B,CAAA;IAC3C,GAAG,EAAE,cAAc,CAAA;CACpB,KAAG,cAAc,GAAG,IAmBpB,CAAA;AAED,eAAO,MAAM,oBAAoB,sCAI9B;IACD,UAAU,EAAE,UAAU,CAAA;IACtB,WAAW,CAAC,EAAE,yBAAyB,CAAA;IACvC,GAAG,EAAE,cAAc,CAAA;CACpB,KAAG,cAAc,GAAG,IAmBpB,CAAA"}
1
+ {"version":3,"file":"getEntityInputSchema.d.ts","sourceRoot":"","sources":["../../../src/utils/schemaConversion/getEntityInputSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EAGnB,KAAK,UAAU,EACf,KAAK,cAAc,EAEnB,KAAK,6BAA6B,EAElC,KAAK,yBAAyB,EAC/B,MAAM,SAAS,CAAA;AAEhB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAKpD,eAAO,MAAM,wBAAwB,GAAI,uCAItC;IACD,cAAc,EAAE,cAAc,CAAA;IAC9B,WAAW,CAAC,EAAE,6BAA6B,CAAA;IAC3C,GAAG,EAAE,cAAc,CAAA;CACpB,KAAG,cAAc,GAAG,IAmBpB,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,mCAIlC;IACD,UAAU,EAAE,UAAU,CAAA;IACtB,WAAW,CAAC,EAAE,yBAAyB,CAAA;IACvC,GAAG,EAAE,cAAc,CAAA;CACpB,KAAG,cAAc,GAAG,IAmBpB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"sanitizeEntitySchema.d.ts","sourceRoot":"","sources":["../../../src/utils/schemaConversion/sanitizeEntitySchema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,WAAY,cAAc,KAAG,cA0B7D,CAAA"}
1
+ {"version":3,"file":"sanitizeEntitySchema.d.ts","sourceRoot":"","sources":["../../../src/utils/schemaConversion/sanitizeEntitySchema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,cAAc,KAAG,cA0B7D,CAAA"}
@@ -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,WAAY,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,GAAI,QAAQ,eAAe,KAAG,sBAG/B,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payloadcms/plugin-mcp",
3
- "version": "4.0.0-internal.cea8867",
3
+ "version": "4.0.0-internal.d927017",
4
4
  "description": "MCP (Model Context Protocol) capabilities with Payload",
5
5
  "keywords": [
6
6
  "plugin",
@@ -28,6 +28,11 @@
28
28
  "types": "./dist/index.d.ts",
29
29
  "default": "./dist/index.js"
30
30
  },
31
+ "./bin": {
32
+ "import": "./dist/bin/index.js",
33
+ "types": "./dist/bin/index.d.ts",
34
+ "default": "./dist/bin/index.js"
35
+ },
31
36
  "./client": {
32
37
  "import": "./dist/exports/client.js",
33
38
  "types": "./dist/exports/client.d.ts",
@@ -60,15 +65,23 @@
60
65
  "zod": "^4.2.0"
61
66
  },
62
67
  "devDependencies": {
68
+ "vite": "~8.1.4",
63
69
  "@payloadcms/eslint-config": "3.28.0",
64
- "payload": "4.0.0-internal.cea8867"
70
+ "payload": "4.0.0-internal.d927017"
65
71
  },
66
72
  "peerDependencies": {
67
- "payload": "4.0.0-internal.cea8867"
73
+ "vite": "~8.1.4",
74
+ "payload": "4.0.0-internal.d927017"
75
+ },
76
+ "peerDependenciesMeta": {
77
+ "vite": {
78
+ "optional": true
79
+ }
68
80
  },
69
81
  "//deps_notes": {
70
82
  "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.",
71
- "tsx": "Required by the `bin.js` stdio mcp. Keep the version pinned to whatever `payload` ships to avoid duplicate installs."
83
+ "tsx": "Required by the `bin.js` stdio mcp. Keep the version pinned to whatever `payload` ships to avoid duplicate installs.",
84
+ "vite": "Vite is an optional peer used by stdio development HMR and duplicated in devDependencies for this package's build and tests. Keep the Vite minor pinned while its server module runner API is experimental."
72
85
  },
73
86
  "homepage:": "https://payloadcms.com",
74
87
  "scripts": {
@@ -0,0 +1,16 @@
1
+ import { runMcpStdio } from '../stdio.js'
2
+
3
+ export const bin = async (): Promise<void> => {
4
+ let viteHMR
5
+ if (
6
+ process.env.NODE_ENV !== 'production' &&
7
+ process.env.NODE_ENV !== 'test' &&
8
+ process.env.DISABLE_PAYLOAD_HMR !== 'true'
9
+ ) {
10
+ const { initializeViteHMR } = await import('./initializeViteHMR.js')
11
+ viteHMR = await initializeViteHMR()
12
+ }
13
+
14
+ await runMcpStdio(viteHMR?.config)
15
+ await viteHMR?.connect()
16
+ }
@@ -0,0 +1,60 @@
1
+ import type { SanitizedConfig } from 'payload'
2
+
3
+ /* eslint-disable no-console */
4
+ import { fileURLToPath } from 'node:url'
5
+ import { getPayload, reload } from 'payload'
6
+ import { findConfig } from 'payload/node'
7
+ import { createServer, createServerModuleRunner, normalizePath } from 'vite'
8
+
9
+ import type * as ConfigBoundary from './viteConfigBoundary.js'
10
+
11
+ import { resolveProjectRoot } from '../utils/resolveProjectRoot.js'
12
+
13
+ type ViteHMR = {
14
+ config: SanitizedConfig
15
+ connect: () => Promise<void>
16
+ }
17
+
18
+ export const initializeViteHMR = async (): Promise<ViteHMR> => {
19
+ const projectRoot = resolveProjectRoot(fileURLToPath(import.meta.url))
20
+ if (projectRoot) {
21
+ process.chdir(projectRoot)
22
+ }
23
+
24
+ const vite = await createServer({
25
+ clearScreen: false,
26
+ configFile: false,
27
+ envDir: false,
28
+ logLevel: 'error',
29
+ optimizeDeps: { noDiscovery: true },
30
+ publicDir: false,
31
+ resolve: { alias: { 'payload-mcp:config': normalizePath(findConfig()) }, tsconfigPaths: true },
32
+ server: { middlewareMode: true, ws: false },
33
+ ssr: { noExternal: [/^@payloadcms\//, /^payload$/] },
34
+ })
35
+ const runner = createServerModuleRunner(vite.environments.ssr)
36
+ const boundaryPath = fileURLToPath(new URL('./viteConfigBoundary.js', import.meta.url))
37
+ const boundary = await runner.import<typeof ConfigBoundary>(boundaryPath)
38
+ const config = boundary.getConfig()
39
+
40
+ return {
41
+ config,
42
+ connect: async () => {
43
+ const payload = await getPayload({ config, cron: false, disableOnInit: true })
44
+ let payloadReloadQueue = Promise.resolve()
45
+ const reloadPayload = (nextConfig: SanitizedConfig): Promise<void> => {
46
+ payloadReloadQueue = payloadReloadQueue
47
+ .then(() => reload(nextConfig, payload, false))
48
+ .catch((error) => console.error('[payload-mcp] config reload failed:', error))
49
+ return payloadReloadQueue
50
+ }
51
+
52
+ boundary.subscribe(reloadPayload)
53
+
54
+ const latestConfig = boundary.getConfig()
55
+ if (config !== latestConfig) {
56
+ await reloadPayload(latestConfig)
57
+ }
58
+ },
59
+ }
60
+ }
@@ -0,0 +1,39 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ import type { SanitizedConfig } from 'payload'
4
+
5
+ /* eslint-disable no-console */
6
+ // @ts-expect-error - The Vite initializer resolves this ID to the Payload config.
7
+ import * as configModule from 'payload-mcp:config'
8
+
9
+ type ConfigListener = (config: SanitizedConfig) => Promise<void> | void
10
+
11
+ let listener: ConfigListener | undefined
12
+ let configQueue = Promise.resolve()
13
+
14
+ const resolveConfig = async (module: unknown): Promise<SanitizedConfig> => {
15
+ const config = module as { default?: unknown }
16
+
17
+ return (await (config.default ?? module)) as SanitizedConfig
18
+ }
19
+
20
+ let config = await resolveConfig(configModule)
21
+
22
+ export const getConfig = (): SanitizedConfig => config
23
+
24
+ export const subscribe = (nextListener: ConfigListener): void => {
25
+ listener = nextListener
26
+ }
27
+
28
+ import.meta.hot?.accept('payload-mcp:config', (nextModule) => {
29
+ if (!nextModule) {
30
+ return
31
+ }
32
+
33
+ configQueue = configQueue
34
+ .then(async () => {
35
+ config = await resolveConfig(nextModule)
36
+ await listener?.(config)
37
+ })
38
+ .catch((error) => console.error('[payload-mcp] config reload failed:', error))
39
+ })
@@ -11,7 +11,6 @@ import {
11
11
  } from '../../../utils/getVirtualFieldNames.js'
12
12
  import { transformPointDataToPayload } from '../../../utils/transformPointDataToPayload.js'
13
13
  import { validateCollectionData } from '../validateEntityData.js'
14
- import { fileInputSchema, resolveFileInput } from './fileInput.js'
15
14
  import { formatCollectionError } from './formatCollectionError.js'
16
15
 
17
16
  const DEFAULT_DESCRIPTION =
@@ -53,7 +52,6 @@ export const createDocumentTool = defineCollectionTool({
53
52
  .string()
54
53
  .describe('Optional: fallback locale code to use when requested locale is not available')
55
54
  .optional(),
56
- file: fileInputSchema.optional(),
57
55
  locale: z
58
56
  .string()
59
57
  .describe(
@@ -71,7 +69,7 @@ export const createDocumentTool = defineCollectionTool({
71
69
  const payload = req.payload
72
70
  const logger = getLogger({ payload })
73
71
 
74
- const { data, depth, draft, fallbackLocale, file: fileInput, locale, select } = input
72
+ const { data, depth, draft, fallbackLocale, locale, select } = input
75
73
 
76
74
  logger.info(
77
75
  `Creating document in collection: ${collectionSlug}${locale ? ` with locale: ${locale}` : ''}`,
@@ -87,7 +85,6 @@ export const createDocumentTool = defineCollectionTool({
87
85
  }
88
86
 
89
87
  const parsedData = transformPointDataToPayload(inputData)
90
- const file = await resolveFileInput({ collectionSlug, input: fileInput, req })
91
88
 
92
89
  const result = await payload.create({
93
90
  collection: collectionSlug,
@@ -96,7 +93,6 @@ export const createDocumentTool = defineCollectionTool({
96
93
  draft,
97
94
  overrideAccess: authorizedMCP.overrideAccess,
98
95
  req,
99
- ...(file ? { file } : {}),
100
96
  ...(locale ? { locale } : {}),
101
97
  ...(fallbackLocale ? { fallbackLocale } : {}),
102
98
  ...(select ? { select: select as SelectType } : {}),
@@ -48,29 +48,16 @@ export const getCollectionSchemaTool = defineCollectionTool({
48
48
  }
49
49
  }
50
50
 
51
- const uploadConfig = req.payload.collections[collectionSlug]?.config.upload
52
- const maxFileSize = req.payload.config.upload.limits?.fileSize
53
- const upload = uploadConfig
54
- ? {
55
- enabled: true,
56
- filesRequiredOnCreate: uploadConfig.filesRequiredOnCreate !== false,
57
- mimeTypes: uploadConfig.mimeTypes ?? ['*/*'],
58
- sources: [...(uploadConfig.pasteURL !== false ? ['url'] : []), 'base64'],
59
- ...(typeof maxFileSize === 'number' && Number.isFinite(maxFileSize) ? { maxFileSize } : {}),
60
- }
61
- : { enabled: false }
62
-
63
51
  return {
64
52
  content: [
65
53
  {
66
54
  type: 'text',
67
- text: `Schema for collection "${collectionSlug}":\n\`\`\`json\n${JSON.stringify(inputSchema)}\n\`\`\`\nUpload configuration:\n\`\`\`json\n${JSON.stringify(upload)}\n\`\`\``,
55
+ text: `Schema for collection "${collectionSlug}":\n\`\`\`json\n${JSON.stringify(inputSchema)}\n\`\`\``,
68
56
  },
69
57
  ],
70
58
  structuredContent: {
71
59
  collectionSlug,
72
60
  schema: inputSchema,
73
- upload,
74
61
  },
75
62
  }
76
63
  })
@@ -13,7 +13,6 @@ import { getCollectionInputSchema } from '../../../utils/schemaConversion/getEnt
13
13
  import { transformPointDataToPayload } from '../../../utils/transformPointDataToPayload.js'
14
14
  import { whereSchema } from '../../../utils/whereSchema.js'
15
15
  import { validateCollectionData } from '../validateEntityData.js'
16
- import { fileInputSchema, resolveFileInput } from './fileInput.js'
17
16
  import { formatCollectionError } from './formatCollectionError.js'
18
17
 
19
18
  const DEFAULT_DESCRIPTION =
@@ -53,7 +52,7 @@ export const updateDocumentTool = defineCollectionTool({
53
52
  .string()
54
53
  .describe('Optional: fallback locale code to use when requested locale is not available')
55
54
  .optional(),
56
- file: fileInputSchema.optional(),
55
+ filePath: z.string().describe('File path for file uploads').optional(),
57
56
  locale: z
58
57
  .string()
59
58
  .describe(
@@ -98,7 +97,7 @@ export const updateDocumentTool = defineCollectionTool({
98
97
  depth,
99
98
  draft,
100
99
  fallbackLocale,
101
- file: fileInput,
100
+ filePath,
102
101
  locale,
103
102
  overrideLock,
104
103
  overwriteExistingFiles,
@@ -132,7 +131,6 @@ export const updateDocumentTool = defineCollectionTool({
132
131
  }
133
132
 
134
133
  const parsedData = transformPointDataToPayload(inputData)
135
- const file = await resolveFileInput({ collectionSlug, input: fileInput, req })
136
134
 
137
135
  const whereClause: Where = where ?? {}
138
136
 
@@ -146,7 +144,7 @@ export const updateDocumentTool = defineCollectionTool({
146
144
  overrideAccess: authorizedMCP.overrideAccess,
147
145
  overrideLock,
148
146
  req,
149
- ...(file ? { file } : {}),
147
+ ...(filePath ? { filePath } : {}),
150
148
  ...(overwriteExistingFiles ? { overwriteExistingFiles } : {}),
151
149
  ...(publishAllLocales !== undefined ? { publishAllLocales } : {}),
152
150
  ...(locale ? { locale } : {}),
@@ -174,7 +172,7 @@ export const updateDocumentTool = defineCollectionTool({
174
172
  overrideLock,
175
173
  req,
176
174
  where: whereClause,
177
- ...(file ? { file } : {}),
175
+ ...(filePath ? { filePath } : {}),
178
176
  ...(overwriteExistingFiles ? { overwriteExistingFiles } : {}),
179
177
  ...(publishAllLocales !== undefined ? { publishAllLocales } : {}),
180
178
  ...(locale ? { locale } : {}),
package/src/stdio.ts CHANGED
@@ -20,7 +20,7 @@ import { resolveProjectRoot } from './utils/resolveProjectRoot.js'
20
20
  * Set `PAYLOAD_MCP_AUTHORIZATION` to authenticate. In development,
21
21
  * `PAYLOAD_MCP_OVERRIDE_ACCESS=true` skips access checks.
22
22
  */
23
- export const runMcpStdio = async (): Promise<void> => {
23
+ export const runMcpStdio = async (configOverride?: SanitizedConfig): Promise<void> => {
24
24
  // MCP clients may start this command from another folder. Move to the Payload
25
25
  // project so findConfig() can find the config.
26
26
  const projectRoot = resolveProjectRoot(fileURLToPath(import.meta.url))
@@ -29,8 +29,9 @@ export const runMcpStdio = async (): Promise<void> => {
29
29
  }
30
30
 
31
31
  const configPath = findConfig()
32
- const configModule = await import(pathToFileURL(configPath).toString())
33
- const config = (await (configModule.default ?? configModule)) as SanitizedConfig
32
+ const configURL = pathToFileURL(configPath).href
33
+ const configModule = configOverride ? undefined : await import(configURL)
34
+ const config: SanitizedConfig = configOverride ?? (await (configModule?.default ?? configModule))
34
35
 
35
36
  /**
36
37
  * stdout is only for MCP messages. The spec says the server must not write
@@ -1,20 +0,0 @@
1
- import type { CollectionSlug, File, PayloadRequest } from 'payload';
2
- import { z } from 'zod';
3
- export declare const fileInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4
- name: z.ZodString;
5
- data: z.ZodString;
6
- mimeType: z.ZodString;
7
- source: z.ZodLiteral<"base64">;
8
- }, z.core.$strip>, z.ZodObject<{
9
- name: z.ZodOptional<z.ZodString>;
10
- source: z.ZodLiteral<"url">;
11
- url: z.ZodURL;
12
- }, z.core.$strip>], "source">;
13
- type FileInput = z.infer<typeof fileInputSchema>;
14
- export declare function resolveFileInput({ collectionSlug, input, req, }: {
15
- collectionSlug: CollectionSlug;
16
- input?: FileInput;
17
- req: PayloadRequest;
18
- }): Promise<File | undefined>;
19
- export {};
20
- //# sourceMappingURL=fileInput.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fileInput.d.ts","sourceRoot":"","sources":["../../../../src/mcp/builtin/collections/fileInput.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAY,cAAc,EAAE,MAAM,SAAS,CAAA;AAK7E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAMvB,eAAO,MAAM,eAAe;;;;;;;;;6BAgBzB,CAAA;AAEH,KAAK,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAEhD,wBAAsB,gBAAgB,CAAC,EACrC,cAAc,EACd,KAAK,EACL,GAAG,GACJ,EAAE;IACD,cAAc,EAAE,cAAc,CAAA;IAC9B,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,GAAG,EAAE,cAAc,CAAA;CACpB,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CAgE5B"}
@@ -1,100 +0,0 @@
1
- import { APIError } from 'payload';
2
- import { getExternalFile, isURLAllowed } from 'payload/internal';
3
- import { sanitizeFilename } from 'payload/shared';
4
- import { z } from 'zod';
5
- const mimeTypeSchema = z.string().regex(/^[!#$%&'*+.^`|~\w-]+\/[!#$%&'*+.^`|~\w-]+$/, 'MIME type must use the type/subtype format');
6
- export const fileInputSchema = z.discriminatedUnion('source', [
7
- z.object({
8
- name: z.string().min(1).describe('The file name, including its extension'),
9
- data: z.string().describe('The base64-encoded file bytes, without a data URL prefix'),
10
- mimeType: mimeTypeSchema.describe('The file MIME type, for example image/png'),
11
- source: z.literal('base64')
12
- }),
13
- z.object({
14
- name: z.string().min(1).describe('Optional file name override').optional(),
15
- source: z.literal('url'),
16
- url: z.url().describe('The http or https URL to download')
17
- })
18
- ]).describe('A file for an upload collection. Use only a source listed by getCollectionSchema: url for an online file or base64 for a local file.');
19
- export async function resolveFileInput({ collectionSlug, input, req }) {
20
- if (!input) {
21
- return undefined;
22
- }
23
- const uploadConfig = req.payload.collections[collectionSlug]?.config.upload;
24
- if (!uploadConfig) {
25
- throw new APIError(`Collection "${collectionSlug}" does not support file uploads.`, 400);
26
- }
27
- const maxFileSize = req.payload.config.upload.limits?.fileSize;
28
- let file;
29
- if (input.source === 'base64') {
30
- const data = decodeBase64({
31
- maxFileSize,
32
- value: input.data
33
- });
34
- file = {
35
- name: sanitizeFilename(input.name),
36
- data,
37
- mimetype: input.mimeType,
38
- size: data.length
39
- };
40
- } else {
41
- if (uploadConfig.pasteURL === false) {
42
- throw new APIError(`Uploading files from URLs is disabled for collection "${collectionSlug}".`, 400);
43
- }
44
- const url = new URL(input.url);
45
- if (![
46
- 'http:',
47
- 'https:'
48
- ].includes(url.protocol)) {
49
- throw new APIError('File URLs must use http or https.', 400);
50
- }
51
- if (typeof uploadConfig.pasteURL === 'object' && !isURLAllowed(input.url, uploadConfig.pasteURL.allowList)) {
52
- throw new APIError('The provided file URL is not allowed.', 400);
53
- }
54
- file = await getExternalFile({
55
- data: {
56
- filename: sanitizeFilename(input.name || getURLFilename(url)),
57
- url: input.url
58
- },
59
- req,
60
- uploadConfig: {
61
- ...uploadConfig,
62
- externalFileHeaderFilter: uploadConfig.externalFileHeaderFilter ?? (()=>({}))
63
- }
64
- });
65
- file.mimetype = file.mimetype?.split(';')[0] || 'application/octet-stream';
66
- file.size = file.data.length;
67
- }
68
- if (maxFileSize !== undefined && Number.isFinite(maxFileSize) && file.size > maxFileSize) {
69
- throw new APIError(`File exceeds the ${maxFileSize} byte upload limit.`, 400);
70
- }
71
- return file;
72
- }
73
- function decodeBase64({ maxFileSize, value }) {
74
- const normalized = value.replace(/\s/g, '');
75
- if (!/^[a-z0-9+/]*={0,2}$/i.test(normalized) || normalized.length % 4 === 1) {
76
- throw new APIError('File data must be valid base64.', 400);
77
- }
78
- if (maxFileSize !== undefined && Number.isFinite(maxFileSize)) {
79
- const paddingLength = normalized.endsWith('==') ? 2 : normalized.endsWith('=') ? 1 : 0;
80
- const decodedSize = Math.floor(normalized.length * 3 / 4) - paddingLength;
81
- if (decodedSize > maxFileSize) {
82
- throw new APIError(`File exceeds the ${maxFileSize} byte upload limit.`, 400);
83
- }
84
- }
85
- const data = Buffer.from(normalized, 'base64');
86
- if (data.toString('base64').replace(/=+$/, '') !== normalized.replace(/=+$/, '')) {
87
- throw new APIError('File data must be valid base64.', 400);
88
- }
89
- return data;
90
- }
91
- function getURLFilename(url) {
92
- const pathSegment = url.pathname.split('/').pop() || 'upload';
93
- try {
94
- return decodeURIComponent(pathSegment);
95
- } catch {
96
- return pathSegment;
97
- }
98
- }
99
-
100
- //# sourceMappingURL=fileInput.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/mcp/builtin/collections/fileInput.ts"],"sourcesContent":["import type { CollectionSlug, File, FileData, PayloadRequest } from 'payload'\n\nimport { APIError } from 'payload'\nimport { getExternalFile, isURLAllowed } from 'payload/internal'\nimport { sanitizeFilename } from 'payload/shared'\nimport { z } from 'zod'\n\nconst mimeTypeSchema = z\n .string()\n .regex(/^[!#$%&'*+.^`|~\\w-]+\\/[!#$%&'*+.^`|~\\w-]+$/, 'MIME type must use the type/subtype format')\n\nexport const fileInputSchema = z\n .discriminatedUnion('source', [\n z.object({\n name: z.string().min(1).describe('The file name, including its extension'),\n data: z.string().describe('The base64-encoded file bytes, without a data URL prefix'),\n mimeType: mimeTypeSchema.describe('The file MIME type, for example image/png'),\n source: z.literal('base64'),\n }),\n z.object({\n name: z.string().min(1).describe('Optional file name override').optional(),\n source: z.literal('url'),\n url: z.url().describe('The http or https URL to download'),\n }),\n ])\n .describe(\n 'A file for an upload collection. Use only a source listed by getCollectionSchema: url for an online file or base64 for a local file.',\n )\n\ntype FileInput = z.infer<typeof fileInputSchema>\n\nexport async function resolveFileInput({\n collectionSlug,\n input,\n req,\n}: {\n collectionSlug: CollectionSlug\n input?: FileInput\n req: PayloadRequest\n}): Promise<File | undefined> {\n if (!input) {\n return undefined\n }\n\n const uploadConfig = req.payload.collections[collectionSlug]?.config.upload\n\n if (!uploadConfig) {\n throw new APIError(`Collection \"${collectionSlug}\" does not support file uploads.`, 400)\n }\n\n const maxFileSize = req.payload.config.upload.limits?.fileSize\n let file: File\n\n if (input.source === 'base64') {\n const data = decodeBase64({ maxFileSize, value: input.data })\n\n file = {\n name: sanitizeFilename(input.name),\n data,\n mimetype: input.mimeType,\n size: data.length,\n }\n } else {\n if (uploadConfig.pasteURL === false) {\n throw new APIError(\n `Uploading files from URLs is disabled for collection \"${collectionSlug}\".`,\n 400,\n )\n }\n\n const url = new URL(input.url)\n\n if (!['http:', 'https:'].includes(url.protocol)) {\n throw new APIError('File URLs must use http or https.', 400)\n }\n\n if (\n typeof uploadConfig.pasteURL === 'object' &&\n !isURLAllowed(input.url, uploadConfig.pasteURL.allowList)\n ) {\n throw new APIError('The provided file URL is not allowed.', 400)\n }\n\n file = await getExternalFile({\n data: {\n filename: sanitizeFilename(input.name || getURLFilename(url)),\n url: input.url,\n } as FileData,\n req,\n uploadConfig: {\n ...uploadConfig,\n externalFileHeaderFilter: uploadConfig.externalFileHeaderFilter ?? (() => ({})),\n },\n })\n file.mimetype = file.mimetype?.split(';')[0] || 'application/octet-stream'\n file.size = file.data.length\n }\n\n if (maxFileSize !== undefined && Number.isFinite(maxFileSize) && file.size > maxFileSize) {\n throw new APIError(`File exceeds the ${maxFileSize} byte upload limit.`, 400)\n }\n\n return file\n}\n\nfunction decodeBase64({ maxFileSize, value }: { maxFileSize?: number; value: string }): Buffer {\n const normalized = value.replace(/\\s/g, '')\n\n if (!/^[a-z0-9+/]*={0,2}$/i.test(normalized) || normalized.length % 4 === 1) {\n throw new APIError('File data must be valid base64.', 400)\n }\n\n if (maxFileSize !== undefined && Number.isFinite(maxFileSize)) {\n const paddingLength = normalized.endsWith('==') ? 2 : normalized.endsWith('=') ? 1 : 0\n const decodedSize = Math.floor((normalized.length * 3) / 4) - paddingLength\n\n if (decodedSize > maxFileSize) {\n throw new APIError(`File exceeds the ${maxFileSize} byte upload limit.`, 400)\n }\n }\n\n const data = Buffer.from(normalized, 'base64')\n\n if (data.toString('base64').replace(/=+$/, '') !== normalized.replace(/=+$/, '')) {\n throw new APIError('File data must be valid base64.', 400)\n }\n\n return data\n}\n\nfunction getURLFilename(url: URL): string {\n const pathSegment = url.pathname.split('/').pop() || 'upload'\n\n try {\n return decodeURIComponent(pathSegment)\n } catch {\n return pathSegment\n }\n}\n"],"names":["APIError","getExternalFile","isURLAllowed","sanitizeFilename","z","mimeTypeSchema","string","regex","fileInputSchema","discriminatedUnion","object","name","min","describe","data","mimeType","source","literal","optional","url","resolveFileInput","collectionSlug","input","req","undefined","uploadConfig","payload","collections","config","upload","maxFileSize","limits","fileSize","file","decodeBase64","value","mimetype","size","length","pasteURL","URL","includes","protocol","allowList","filename","getURLFilename","externalFileHeaderFilter","split","Number","isFinite","normalized","replace","test","paddingLength","endsWith","decodedSize","Math","floor","Buffer","from","toString","pathSegment","pathname","pop","decodeURIComponent"],"mappings":"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"}
@@ -1,72 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { fileInputSchema, resolveFileInput } from './fileInput.js';
3
- describe('MCP file input', ()=>{
4
- it('should reject malformed MIME types', ()=>{
5
- const result = fileInputSchema.safeParse({
6
- data: 'aGVsbG8=',
7
- mimeType: 'text/plain\r\nx-test: value',
8
- name: 'hello.txt',
9
- source: 'base64'
10
- });
11
- expect(result.success).toBe(false);
12
- });
13
- it('should reject oversized base64 files', async ()=>{
14
- const req = createRequest({
15
- maxFileSize: 4
16
- });
17
- await expect(resolveFileInput({
18
- collectionSlug: 'media',
19
- input: {
20
- data: Buffer.from('hello').toString('base64'),
21
- mimeType: 'text/plain',
22
- name: 'hello.txt',
23
- source: 'base64'
24
- },
25
- req
26
- })).rejects.toThrow('File exceeds the 4 byte upload limit.');
27
- });
28
- it('should reject URLs outside the collection allowlist', async ()=>{
29
- const req = createRequest({
30
- allowList: [
31
- {
32
- hostname: 'assets.example.com',
33
- protocol: 'https'
34
- }
35
- ]
36
- });
37
- await expect(resolveFileInput({
38
- collectionSlug: 'media',
39
- input: {
40
- source: 'url',
41
- url: 'https://example.com/image.png'
42
- },
43
- req
44
- })).rejects.toThrow('The provided file URL is not allowed.');
45
- });
46
- });
47
- function createRequest({ allowList, maxFileSize }) {
48
- return {
49
- payload: {
50
- collections: {
51
- media: {
52
- config: {
53
- upload: allowList ? {
54
- pasteURL: {
55
- allowList
56
- }
57
- } : {}
58
- }
59
- }
60
- },
61
- config: {
62
- upload: {
63
- limits: {
64
- fileSize: maxFileSize
65
- }
66
- }
67
- }
68
- }
69
- };
70
- }
71
-
72
- //# sourceMappingURL=fileInput.spec.js.map