@payloadcms/plugin-mcp 4.0.0-canary.1 → 4.0.0-canary.3

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 (330) hide show
  1. package/dist/collection/getAccessField.d.ts.map +1 -1
  2. package/dist/collection/getAccessField.js +24 -9
  3. package/dist/collection/getAccessField.js.map +1 -1
  4. package/dist/collection/index.d.ts.map +1 -1
  5. package/dist/collection/index.js +71 -26
  6. package/dist/collection/index.js.map +1 -1
  7. package/dist/components/APIKeyField/index.client.d.ts +9 -0
  8. package/dist/components/APIKeyField/index.client.d.ts.map +1 -0
  9. package/dist/components/APIKeyField/index.client.js +85 -0
  10. package/dist/components/APIKeyField/index.client.js.map +1 -0
  11. package/dist/components/APIKeyField/index.css +105 -0
  12. package/dist/components/APIKeysEmptyState/index.client.d.ts +4 -0
  13. package/dist/components/APIKeysEmptyState/index.client.d.ts.map +1 -0
  14. package/dist/components/APIKeysEmptyState/index.client.js +21 -0
  15. package/dist/components/APIKeysEmptyState/index.client.js.map +1 -0
  16. package/dist/components/AccessField/index.client.d.ts.map +1 -1
  17. package/dist/components/AccessField/index.client.js +139 -197
  18. package/dist/components/AccessField/index.client.js.map +1 -1
  19. package/dist/components/AccessField/index.css +50 -44
  20. package/dist/components/SettingsMenu/index.client.d.ts +8 -0
  21. package/dist/components/SettingsMenu/index.client.d.ts.map +1 -0
  22. package/dist/components/SettingsMenu/index.client.js +24 -0
  23. package/dist/components/SettingsMenu/index.client.js.map +1 -0
  24. package/dist/endpoint/access.js +16 -0
  25. package/dist/endpoint/access.js.map +1 -1
  26. package/dist/exports/client.d.ts +3 -0
  27. package/dist/exports/client.d.ts.map +1 -1
  28. package/dist/exports/client.js +3 -0
  29. package/dist/exports/client.js.map +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +51 -3
  32. package/dist/index.js.map +1 -1
  33. package/dist/mcp/buildMcpServer.d.ts.map +1 -1
  34. package/dist/mcp/buildMcpServer.js +2 -0
  35. package/dist/mcp/buildMcpServer.js.map +1 -1
  36. package/dist/mcp/builtin/collections/authTools.d.ts.map +1 -1
  37. package/dist/mcp/builtin/collections/authTools.js +44 -6
  38. package/dist/mcp/builtin/collections/authTools.js.map +1 -1
  39. package/dist/mcp/builtin/collections/createTool.d.ts.map +1 -1
  40. package/dist/mcp/builtin/collections/createTool.js +10 -19
  41. package/dist/mcp/builtin/collections/createTool.js.map +1 -1
  42. package/dist/mcp/builtin/collections/deleteTool.d.ts.map +1 -1
  43. package/dist/mcp/builtin/collections/deleteTool.js +7 -0
  44. package/dist/mcp/builtin/collections/deleteTool.js.map +1 -1
  45. package/dist/mcp/builtin/collections/findTool.d.ts.map +1 -1
  46. package/dist/mcp/builtin/collections/findTool.js +41 -22
  47. package/dist/mcp/builtin/collections/findTool.js.map +1 -1
  48. package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts.map +1 -1
  49. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js +7 -0
  50. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js.map +1 -1
  51. package/dist/mcp/builtin/collections/updateTool.d.ts.map +1 -1
  52. package/dist/mcp/builtin/collections/updateTool.js +12 -21
  53. package/dist/mcp/builtin/collections/updateTool.js.map +1 -1
  54. package/dist/mcp/builtin/getConfigInfoTool.d.ts.map +1 -1
  55. package/dist/mcp/builtin/getConfigInfoTool.js +7 -0
  56. package/dist/mcp/builtin/getConfigInfoTool.js.map +1 -1
  57. package/dist/mcp/builtin/globals/findTool.d.ts.map +1 -1
  58. package/dist/mcp/builtin/globals/findTool.js +15 -20
  59. package/dist/mcp/builtin/globals/findTool.js.map +1 -1
  60. package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts.map +1 -1
  61. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js +7 -0
  62. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js.map +1 -1
  63. package/dist/mcp/builtin/globals/updateTool.d.ts.map +1 -1
  64. package/dist/mcp/builtin/globals/updateTool.js +10 -19
  65. package/dist/mcp/builtin/globals/updateTool.js.map +1 -1
  66. package/dist/mcp/builtinTools.d.ts +2 -10
  67. package/dist/mcp/builtinTools.d.ts.map +1 -1
  68. package/dist/mcp/builtinTools.js +2 -10
  69. package/dist/mcp/builtinTools.js.map +1 -1
  70. package/dist/mcp/sanitizeMCPConfig.js +24 -10
  71. package/dist/mcp/sanitizeMCPConfig.js.map +1 -1
  72. package/dist/translations/index.d.ts +11 -0
  73. package/dist/translations/index.d.ts.map +1 -0
  74. package/dist/translations/index.js +92 -0
  75. package/dist/translations/index.js.map +1 -0
  76. package/dist/translations/languages/ar.d.ts +31 -0
  77. package/dist/translations/languages/ar.d.ts.map +1 -0
  78. package/dist/translations/languages/ar.js +34 -0
  79. package/dist/translations/languages/ar.js.map +1 -0
  80. package/dist/translations/languages/az.d.ts +31 -0
  81. package/dist/translations/languages/az.d.ts.map +1 -0
  82. package/dist/translations/languages/az.js +34 -0
  83. package/dist/translations/languages/az.js.map +1 -0
  84. package/dist/translations/languages/bg.d.ts +31 -0
  85. package/dist/translations/languages/bg.d.ts.map +1 -0
  86. package/dist/translations/languages/bg.js +34 -0
  87. package/dist/translations/languages/bg.js.map +1 -0
  88. package/dist/translations/languages/bnBd.d.ts +31 -0
  89. package/dist/translations/languages/bnBd.d.ts.map +1 -0
  90. package/dist/translations/languages/bnBd.js +34 -0
  91. package/dist/translations/languages/bnBd.js.map +1 -0
  92. package/dist/translations/languages/bnIn.d.ts +31 -0
  93. package/dist/translations/languages/bnIn.d.ts.map +1 -0
  94. package/dist/translations/languages/bnIn.js +34 -0
  95. package/dist/translations/languages/bnIn.js.map +1 -0
  96. package/dist/translations/languages/ca.d.ts +31 -0
  97. package/dist/translations/languages/ca.d.ts.map +1 -0
  98. package/dist/translations/languages/ca.js +34 -0
  99. package/dist/translations/languages/ca.js.map +1 -0
  100. package/dist/translations/languages/cs.d.ts +31 -0
  101. package/dist/translations/languages/cs.d.ts.map +1 -0
  102. package/dist/translations/languages/cs.js +34 -0
  103. package/dist/translations/languages/cs.js.map +1 -0
  104. package/dist/translations/languages/da.d.ts +31 -0
  105. package/dist/translations/languages/da.d.ts.map +1 -0
  106. package/dist/translations/languages/da.js +34 -0
  107. package/dist/translations/languages/da.js.map +1 -0
  108. package/dist/translations/languages/de.d.ts +31 -0
  109. package/dist/translations/languages/de.d.ts.map +1 -0
  110. package/dist/translations/languages/de.js +34 -0
  111. package/dist/translations/languages/de.js.map +1 -0
  112. package/dist/translations/languages/en.d.ts +31 -0
  113. package/dist/translations/languages/en.d.ts.map +1 -0
  114. package/dist/translations/languages/en.js +34 -0
  115. package/dist/translations/languages/en.js.map +1 -0
  116. package/dist/translations/languages/es.d.ts +31 -0
  117. package/dist/translations/languages/es.d.ts.map +1 -0
  118. package/dist/translations/languages/es.js +34 -0
  119. package/dist/translations/languages/es.js.map +1 -0
  120. package/dist/translations/languages/et.d.ts +31 -0
  121. package/dist/translations/languages/et.d.ts.map +1 -0
  122. package/dist/translations/languages/et.js +34 -0
  123. package/dist/translations/languages/et.js.map +1 -0
  124. package/dist/translations/languages/fa.d.ts +31 -0
  125. package/dist/translations/languages/fa.d.ts.map +1 -0
  126. package/dist/translations/languages/fa.js +34 -0
  127. package/dist/translations/languages/fa.js.map +1 -0
  128. package/dist/translations/languages/fr.d.ts +31 -0
  129. package/dist/translations/languages/fr.d.ts.map +1 -0
  130. package/dist/translations/languages/fr.js +34 -0
  131. package/dist/translations/languages/fr.js.map +1 -0
  132. package/dist/translations/languages/he.d.ts +31 -0
  133. package/dist/translations/languages/he.d.ts.map +1 -0
  134. package/dist/translations/languages/he.js +34 -0
  135. package/dist/translations/languages/he.js.map +1 -0
  136. package/dist/translations/languages/hr.d.ts +31 -0
  137. package/dist/translations/languages/hr.d.ts.map +1 -0
  138. package/dist/translations/languages/hr.js +34 -0
  139. package/dist/translations/languages/hr.js.map +1 -0
  140. package/dist/translations/languages/hu.d.ts +31 -0
  141. package/dist/translations/languages/hu.d.ts.map +1 -0
  142. package/dist/translations/languages/hu.js +34 -0
  143. package/dist/translations/languages/hu.js.map +1 -0
  144. package/dist/translations/languages/hy.d.ts +31 -0
  145. package/dist/translations/languages/hy.d.ts.map +1 -0
  146. package/dist/translations/languages/hy.js +34 -0
  147. package/dist/translations/languages/hy.js.map +1 -0
  148. package/dist/translations/languages/id.d.ts +31 -0
  149. package/dist/translations/languages/id.d.ts.map +1 -0
  150. package/dist/translations/languages/id.js +34 -0
  151. package/dist/translations/languages/id.js.map +1 -0
  152. package/dist/translations/languages/is.d.ts +31 -0
  153. package/dist/translations/languages/is.d.ts.map +1 -0
  154. package/dist/translations/languages/is.js +34 -0
  155. package/dist/translations/languages/is.js.map +1 -0
  156. package/dist/translations/languages/it.d.ts +31 -0
  157. package/dist/translations/languages/it.d.ts.map +1 -0
  158. package/dist/translations/languages/it.js +34 -0
  159. package/dist/translations/languages/it.js.map +1 -0
  160. package/dist/translations/languages/ja.d.ts +31 -0
  161. package/dist/translations/languages/ja.d.ts.map +1 -0
  162. package/dist/translations/languages/ja.js +34 -0
  163. package/dist/translations/languages/ja.js.map +1 -0
  164. package/dist/translations/languages/ko.d.ts +31 -0
  165. package/dist/translations/languages/ko.d.ts.map +1 -0
  166. package/dist/translations/languages/ko.js +34 -0
  167. package/dist/translations/languages/ko.js.map +1 -0
  168. package/dist/translations/languages/lt.d.ts +31 -0
  169. package/dist/translations/languages/lt.d.ts.map +1 -0
  170. package/dist/translations/languages/lt.js +34 -0
  171. package/dist/translations/languages/lt.js.map +1 -0
  172. package/dist/translations/languages/lv.d.ts +31 -0
  173. package/dist/translations/languages/lv.d.ts.map +1 -0
  174. package/dist/translations/languages/lv.js +34 -0
  175. package/dist/translations/languages/lv.js.map +1 -0
  176. package/dist/translations/languages/my.d.ts +31 -0
  177. package/dist/translations/languages/my.d.ts.map +1 -0
  178. package/dist/translations/languages/my.js +34 -0
  179. package/dist/translations/languages/my.js.map +1 -0
  180. package/dist/translations/languages/nb.d.ts +31 -0
  181. package/dist/translations/languages/nb.d.ts.map +1 -0
  182. package/dist/translations/languages/nb.js +34 -0
  183. package/dist/translations/languages/nb.js.map +1 -0
  184. package/dist/translations/languages/nl.d.ts +31 -0
  185. package/dist/translations/languages/nl.d.ts.map +1 -0
  186. package/dist/translations/languages/nl.js +34 -0
  187. package/dist/translations/languages/nl.js.map +1 -0
  188. package/dist/translations/languages/pl.d.ts +31 -0
  189. package/dist/translations/languages/pl.d.ts.map +1 -0
  190. package/dist/translations/languages/pl.js +34 -0
  191. package/dist/translations/languages/pl.js.map +1 -0
  192. package/dist/translations/languages/pt.d.ts +31 -0
  193. package/dist/translations/languages/pt.d.ts.map +1 -0
  194. package/dist/translations/languages/pt.js +34 -0
  195. package/dist/translations/languages/pt.js.map +1 -0
  196. package/dist/translations/languages/ro.d.ts +31 -0
  197. package/dist/translations/languages/ro.d.ts.map +1 -0
  198. package/dist/translations/languages/ro.js +34 -0
  199. package/dist/translations/languages/ro.js.map +1 -0
  200. package/dist/translations/languages/rs.d.ts +31 -0
  201. package/dist/translations/languages/rs.d.ts.map +1 -0
  202. package/dist/translations/languages/rs.js +34 -0
  203. package/dist/translations/languages/rs.js.map +1 -0
  204. package/dist/translations/languages/rsLatin.d.ts +31 -0
  205. package/dist/translations/languages/rsLatin.d.ts.map +1 -0
  206. package/dist/translations/languages/rsLatin.js +34 -0
  207. package/dist/translations/languages/rsLatin.js.map +1 -0
  208. package/dist/translations/languages/ru.d.ts +31 -0
  209. package/dist/translations/languages/ru.d.ts.map +1 -0
  210. package/dist/translations/languages/ru.js +34 -0
  211. package/dist/translations/languages/ru.js.map +1 -0
  212. package/dist/translations/languages/sk.d.ts +31 -0
  213. package/dist/translations/languages/sk.d.ts.map +1 -0
  214. package/dist/translations/languages/sk.js +34 -0
  215. package/dist/translations/languages/sk.js.map +1 -0
  216. package/dist/translations/languages/sl.d.ts +31 -0
  217. package/dist/translations/languages/sl.d.ts.map +1 -0
  218. package/dist/translations/languages/sl.js +34 -0
  219. package/dist/translations/languages/sl.js.map +1 -0
  220. package/dist/translations/languages/sv.d.ts +31 -0
  221. package/dist/translations/languages/sv.d.ts.map +1 -0
  222. package/dist/translations/languages/sv.js +34 -0
  223. package/dist/translations/languages/sv.js.map +1 -0
  224. package/dist/translations/languages/ta.d.ts +31 -0
  225. package/dist/translations/languages/ta.d.ts.map +1 -0
  226. package/dist/translations/languages/ta.js +34 -0
  227. package/dist/translations/languages/ta.js.map +1 -0
  228. package/dist/translations/languages/th.d.ts +31 -0
  229. package/dist/translations/languages/th.d.ts.map +1 -0
  230. package/dist/translations/languages/th.js +34 -0
  231. package/dist/translations/languages/th.js.map +1 -0
  232. package/dist/translations/languages/tr.d.ts +31 -0
  233. package/dist/translations/languages/tr.d.ts.map +1 -0
  234. package/dist/translations/languages/tr.js +34 -0
  235. package/dist/translations/languages/tr.js.map +1 -0
  236. package/dist/translations/languages/uk.d.ts +31 -0
  237. package/dist/translations/languages/uk.d.ts.map +1 -0
  238. package/dist/translations/languages/uk.js +34 -0
  239. package/dist/translations/languages/uk.js.map +1 -0
  240. package/dist/translations/languages/vi.d.ts +31 -0
  241. package/dist/translations/languages/vi.d.ts.map +1 -0
  242. package/dist/translations/languages/vi.js +34 -0
  243. package/dist/translations/languages/vi.js.map +1 -0
  244. package/dist/translations/languages/zh.d.ts +31 -0
  245. package/dist/translations/languages/zh.d.ts.map +1 -0
  246. package/dist/translations/languages/zh.js +34 -0
  247. package/dist/translations/languages/zh.js.map +1 -0
  248. package/dist/translations/languages/zhTw.d.ts +31 -0
  249. package/dist/translations/languages/zhTw.d.ts.map +1 -0
  250. package/dist/translations/languages/zhTw.js +34 -0
  251. package/dist/translations/languages/zhTw.js.map +1 -0
  252. package/dist/translations/types.d.ts +32 -0
  253. package/dist/translations/types.d.ts.map +1 -0
  254. package/dist/translations/types.js +3 -0
  255. package/dist/translations/types.js.map +1 -0
  256. package/dist/types.d.ts +13 -6
  257. package/dist/types.d.ts.map +1 -1
  258. package/dist/types.js.map +1 -1
  259. package/package.json +7 -6
  260. package/src/collection/getAccessField.ts +33 -13
  261. package/src/collection/index.ts +63 -22
  262. package/src/components/APIKeyField/index.client.tsx +68 -0
  263. package/src/components/APIKeyField/index.css +105 -0
  264. package/src/components/APIKeysEmptyState/index.client.tsx +33 -0
  265. package/src/components/AccessField/index.client.tsx +160 -211
  266. package/src/components/AccessField/index.css +50 -44
  267. package/src/components/SettingsMenu/index.client.tsx +30 -0
  268. package/src/endpoint/access.ts +12 -0
  269. package/src/exports/client.ts +3 -0
  270. package/src/index.ts +52 -3
  271. package/src/mcp/buildMcpServer.ts +2 -0
  272. package/src/mcp/builtin/collections/authTools.ts +45 -9
  273. package/src/mcp/builtin/collections/createTool.ts +10 -13
  274. package/src/mcp/builtin/collections/deleteTool.ts +7 -0
  275. package/src/mcp/builtin/collections/findTool.ts +53 -16
  276. package/src/mcp/builtin/collections/getCollectionSchemaTool.ts +7 -0
  277. package/src/mcp/builtin/collections/updateTool.ts +11 -14
  278. package/src/mcp/builtin/getConfigInfoTool.ts +7 -0
  279. package/src/mcp/builtin/globals/findTool.ts +22 -16
  280. package/src/mcp/builtin/globals/getGlobalSchemaTool.ts +7 -0
  281. package/src/mcp/builtin/globals/updateTool.ts +11 -14
  282. package/src/mcp/builtinTools.ts +9 -12
  283. package/src/mcp/sanitizeMCPConfig.ts +15 -11
  284. package/src/translations/index.ts +105 -0
  285. package/src/translations/languages/ar.ts +38 -0
  286. package/src/translations/languages/az.ts +38 -0
  287. package/src/translations/languages/bg.ts +39 -0
  288. package/src/translations/languages/bnBd.ts +39 -0
  289. package/src/translations/languages/bnIn.ts +39 -0
  290. package/src/translations/languages/ca.ts +39 -0
  291. package/src/translations/languages/cs.ts +39 -0
  292. package/src/translations/languages/da.ts +38 -0
  293. package/src/translations/languages/de.ts +39 -0
  294. package/src/translations/languages/en.ts +39 -0
  295. package/src/translations/languages/es.ts +39 -0
  296. package/src/translations/languages/et.ts +38 -0
  297. package/src/translations/languages/fa.ts +39 -0
  298. package/src/translations/languages/fr.ts +39 -0
  299. package/src/translations/languages/he.ts +37 -0
  300. package/src/translations/languages/hr.ts +39 -0
  301. package/src/translations/languages/hu.ts +39 -0
  302. package/src/translations/languages/hy.ts +39 -0
  303. package/src/translations/languages/id.ts +39 -0
  304. package/src/translations/languages/is.ts +39 -0
  305. package/src/translations/languages/it.ts +39 -0
  306. package/src/translations/languages/ja.ts +39 -0
  307. package/src/translations/languages/ko.ts +39 -0
  308. package/src/translations/languages/lt.ts +39 -0
  309. package/src/translations/languages/lv.ts +38 -0
  310. package/src/translations/languages/my.ts +39 -0
  311. package/src/translations/languages/nb.ts +39 -0
  312. package/src/translations/languages/nl.ts +39 -0
  313. package/src/translations/languages/pl.ts +38 -0
  314. package/src/translations/languages/pt.ts +39 -0
  315. package/src/translations/languages/ro.ts +39 -0
  316. package/src/translations/languages/rs.ts +38 -0
  317. package/src/translations/languages/rsLatin.ts +38 -0
  318. package/src/translations/languages/ru.ts +39 -0
  319. package/src/translations/languages/sk.ts +39 -0
  320. package/src/translations/languages/sl.ts +38 -0
  321. package/src/translations/languages/sv.ts +39 -0
  322. package/src/translations/languages/ta.ts +39 -0
  323. package/src/translations/languages/th.ts +38 -0
  324. package/src/translations/languages/tr.ts +38 -0
  325. package/src/translations/languages/uk.ts +39 -0
  326. package/src/translations/languages/vi.ts +39 -0
  327. package/src/translations/languages/zh.ts +36 -0
  328. package/src/translations/languages/zhTw.ts +36 -0
  329. package/src/translations/types.ts +34 -0
  330. package/src/types.ts +13 -5
@@ -2,9 +2,10 @@
2
2
 
3
3
  import type { JSONFieldClientProps } from 'payload'
4
4
 
5
- import { CheckboxInput, Collapsible, useField } from '@payloadcms/ui'
6
- import React from 'react'
5
+ import { CheckboxInput, Collapsible, Tabs, useField, useTranslation } from '@payloadcms/ui'
6
+ import React, { useState } from 'react'
7
7
 
8
+ import type { PluginMCPTranslationKeys, PluginMCPTranslations } from '../../translations/index.js'
8
9
  import type { ClientMCPPluginConfig, MCPAPIKeysDocAccessTree } from '../../types.js'
9
10
 
10
11
  import './index.css'
@@ -14,6 +15,11 @@ const baseClass = 'mcp-access-field'
14
15
  type ClientItem = ClientMCPPluginConfig['items'][number]
15
16
  type ScopeKey = 'collections' | 'globals'
16
17
  type FlatKey = 'prompts' | 'resources' | 'tools'
18
+ type TabKey = 'collections' | 'globals' | 'server'
19
+
20
+ const LEAF_GROUP_KEYS = ['operations', 'auth', 'custom'] as const
21
+
22
+ type LeafGroupKey = (typeof LEAF_GROUP_KEYS)[number]
17
23
 
18
24
  type Props = {
19
25
  pluginConfig: ClientMCPPluginConfig
@@ -39,15 +45,21 @@ const setKey = <T extends Record<string, unknown>>(
39
45
 
40
46
  export const AccessField: React.FC<Props> = ({ path, pluginConfig }) => {
41
47
  const { setValue, value } = useField<MCPAPIKeysDocAccessTree>({ path })
48
+ const { t } = useTranslation<PluginMCPTranslations, PluginMCPTranslationKeys>()
49
+ const [activeTab, setActiveTab] = useState<null | TabKey>(null)
42
50
  const access = value ?? {}
51
+ const leafGroupLabels: Record<LeafGroupKey, string> = {
52
+ auth: t('plugin-mcp:authentication'),
53
+ custom: t('general:custom'),
54
+ operations: t('plugin-mcp:operations'),
55
+ }
43
56
 
44
- // Bucket items for rendering. (Bucketing is cheap and runs once per render;
45
- // memoizing would mean managing inputs/refs for marginal benefit.)
46
57
  const collectionsBySlug: Record<string, ClientItem[]> = {}
47
58
  const globalsBySlug: Record<string, ClientItem[]> = {}
48
- const tools: ClientItem[] = []
49
59
  const prompts: ClientItem[] = []
50
60
  const resources: ClientItem[] = []
61
+ const tools: ClientItem[] = []
62
+
51
63
  for (const item of pluginConfig.items) {
52
64
  switch (item.type) {
53
65
  case 'collectionTool':
@@ -126,221 +138,158 @@ export const AccessField: React.FC<Props> = ({ path, pluginConfig }) => {
126
138
  }
127
139
  }
128
140
 
129
- const collectionSlugs = Object.keys(collectionsBySlug)
130
- const globalSlugs = Object.keys(globalsBySlug)
131
-
132
- return (
133
- <div className={baseClass}>
134
- {collectionSlugs.length > 0 && (
135
- <section className={`${baseClass}__section`}>
136
- <header className={`${baseClass}__section-header`}>
137
- {/* TODO: needs i18n once design is finalized */}
138
- <h4>Collection-level permissions</h4>
139
- {/* TODO: needs i18n once design is finalized */}
140
- <p>Allow MCP clients to perform the following actions within these collections:</p>
141
- </header>
142
- {collectionSlugs.map((slug) => {
143
- const leaves = collectionsBySlug[slug]!
144
- return (
145
- <Collapsible
146
- actions={
147
- <GroupActions
148
- onSetAll={(allow) => setAllScoped('collections', slug, leaves, allow)}
149
- />
150
- }
151
- className={`${baseClass}__group`}
152
- header={<span className={`${baseClass}__group-label`}>{titleCase(slug)}</span>}
153
- initCollapsed
154
- key={`collection-${slug}`}
155
- >
156
- <ul className={`${baseClass}__list`}>
157
- {leaves.map((leaf) => (
158
- <li key={leaf.configKey}>
159
- <CheckboxInput
160
- checked={isScopedAllowed('collections', slug, leaf.configKey)}
161
- id={`${path}.collections.${slug}.${leaf.configKey}`}
162
- label={leaf.label}
163
- onToggle={(e) =>
164
- toggleScoped('collections', slug, leaf.configKey, e.target.checked)
165
- }
166
- tooltip={leaf.description}
167
- />
168
- </li>
169
- ))}
170
- </ul>
171
- </Collapsible>
172
- )
173
- })}
174
- </section>
175
- )}
141
+ const renderLeaf = (
142
+ leaf: ClientItem,
143
+ id: string,
144
+ checked: boolean,
145
+ onToggle: (allow: boolean) => void,
146
+ ) => (
147
+ <li className={`${baseClass}__leaf`} key={leaf.configKey}>
148
+ <CheckboxInput
149
+ checked={checked}
150
+ id={id}
151
+ label={leaf.label}
152
+ onToggle={(e) => onToggle(e.target.checked)}
153
+ />
154
+ {leaf.description && <p className={`${baseClass}__leaf-description`}>{leaf.description}</p>}
155
+ </li>
156
+ )
176
157
 
177
- {globalSlugs.length > 0 && (
178
- <section className={`${baseClass}__section`}>
179
- <header className={`${baseClass}__section-header`}>
180
- {/* TODO: needs i18n once design is finalized */}
181
- <h4>Global-level permissions</h4>
182
- {/* TODO: needs i18n once design is finalized */}
183
- <p>Allow MCP clients to perform the following actions on these globals:</p>
184
- </header>
185
- {globalSlugs.map((slug) => {
186
- const leaves = globalsBySlug[slug]!
187
- return (
188
- <Collapsible
189
- actions={
190
- <GroupActions
191
- onSetAll={(allow) => setAllScoped('globals', slug, leaves, allow)}
192
- />
193
- }
194
- className={`${baseClass}__group`}
195
- header={<span className={`${baseClass}__group-label`}>{titleCase(slug)}</span>}
196
- initCollapsed
197
- key={`global-${slug}`}
198
- >
199
- <ul className={`${baseClass}__list`}>
200
- {leaves.map((leaf) => (
201
- <li key={leaf.configKey}>
202
- <CheckboxInput
203
- checked={isScopedAllowed('globals', slug, leaf.configKey)}
204
- id={`${path}.globals.${slug}.${leaf.configKey}`}
205
- label={leaf.label}
206
- onToggle={(e) =>
207
- toggleScoped('globals', slug, leaf.configKey, e.target.checked)
208
- }
209
- tooltip={leaf.description}
210
- />
211
- </li>
212
- ))}
213
- </ul>
214
- </Collapsible>
215
- )
216
- })}
217
- </section>
218
- )}
158
+ const renderCard = ({
159
+ id,
160
+ isLeafAllowed,
161
+ label,
162
+ leaves,
163
+ onSetAll,
164
+ onToggleLeaf,
165
+ }: {
166
+ id: string
167
+ isLeafAllowed: (leaf: ClientItem) => boolean
168
+ label: string
169
+ leaves: ClientItem[]
170
+ onSetAll: (allow: boolean) => void
171
+ onToggleLeaf: (leaf: ClientItem, allow: boolean) => void
172
+ }) => {
173
+ const allowedCount = leaves.filter(isLeafAllowed).length
174
+ const groups = LEAF_GROUP_KEYS.map((key) => ({
175
+ key,
176
+ label: leafGroupLabels[key],
177
+ leaves: leaves.filter((leaf) => (leaf.group ?? 'custom') === key),
178
+ })).filter((group) => group.leaves.length > 0)
179
+ const hasGroupLabels = groups.length > 1
219
180
 
220
- {(tools.length > 0 || prompts.length > 0 || resources.length > 0) && (
221
- <section className={`${baseClass}__section`}>
222
- <header className={`${baseClass}__section-header`}>
223
- {/* TODO: needs i18n once design is finalized */}
224
- <h4>Project-level permissions</h4>
225
- {/* TODO: needs i18n once design is finalized */}
226
- <p>Cross-cutting tools, prompts, and resources not scoped to a single collection.</p>
227
- </header>
228
- {tools.length > 0 && (
229
- <Collapsible
230
- actions={<GroupActions onSetAll={(allow) => setAllFlat('tools', tools, allow)} />}
231
- className={`${baseClass}__group`}
232
- header={
233
- /* TODO: needs i18n once design is finalized */
234
- <span className={`${baseClass}__group-label`}>Tools</span>
235
- }
236
- initCollapsed
237
- >
238
- <ul className={`${baseClass}__list`}>
239
- {tools.map((leaf) => (
240
- <li key={leaf.configKey}>
241
- <CheckboxInput
242
- checked={isFlatAllowed('tools', leaf.configKey)}
243
- id={`${path}.tools.${leaf.configKey}`}
244
- label={leaf.label}
245
- onToggle={(e) => toggleFlat('tools', leaf.configKey, e.target.checked)}
246
- tooltip={leaf.description}
247
- />
248
- </li>
249
- ))}
250
- </ul>
251
- </Collapsible>
252
- )}
253
- {prompts.length > 0 && (
254
- <Collapsible
255
- actions={<GroupActions onSetAll={(allow) => setAllFlat('prompts', prompts, allow)} />}
256
- className={`${baseClass}__group`}
257
- header={
258
- /* TODO: needs i18n once design is finalized */
259
- <span className={`${baseClass}__group-label`}>Prompts</span>
260
- }
261
- initCollapsed
262
- >
181
+ return (
182
+ <Collapsible
183
+ className={`${baseClass}__card`}
184
+ header={
185
+ // Keep header clicks on the checkbox from also toggling the collapsible.
186
+ <span
187
+ className={`${baseClass}__card-checkbox`}
188
+ onClick={(e) => e.stopPropagation()}
189
+ role="presentation"
190
+ >
191
+ <CheckboxInput
192
+ checked={allowedCount === leaves.length}
193
+ id={`${id}._all`}
194
+ label={label}
195
+ onToggle={() => onSetAll(allowedCount < leaves.length)}
196
+ partialChecked={allowedCount > 0 && allowedCount < leaves.length}
197
+ />
198
+ </span>
199
+ }
200
+ initCollapsed
201
+ key={id}
202
+ >
203
+ <div className={`${baseClass}__card-groups`}>
204
+ {groups.map((group) => (
205
+ <div className={`${baseClass}__leaf-group`} key={group.key}>
206
+ {hasGroupLabels && <p className={`${baseClass}__leaf-group-label`}>{group.label}</p>}
263
207
  <ul className={`${baseClass}__list`}>
264
- {prompts.map((leaf) => (
265
- <li key={leaf.configKey}>
266
- <CheckboxInput
267
- checked={isFlatAllowed('prompts', leaf.configKey)}
268
- id={`${path}.prompts.${leaf.configKey}`}
269
- label={leaf.label}
270
- onToggle={(e) => toggleFlat('prompts', leaf.configKey, e.target.checked)}
271
- tooltip={leaf.description}
272
- />
273
- </li>
274
- ))}
208
+ {group.leaves.map((leaf) =>
209
+ renderLeaf(leaf, `${id}.${leaf.configKey}`, isLeafAllowed(leaf), (allow) =>
210
+ onToggleLeaf(leaf, allow),
211
+ ),
212
+ )}
275
213
  </ul>
276
- </Collapsible>
277
- )}
278
- {resources.length > 0 && (
279
- <Collapsible
280
- actions={
281
- <GroupActions onSetAll={(allow) => setAllFlat('resources', resources, allow)} />
282
- }
283
- className={`${baseClass}__group`}
284
- header={
285
- /* TODO: needs i18n once design is finalized */
286
- <span className={`${baseClass}__group-label`}>Resources</span>
287
- }
288
- initCollapsed
289
- >
290
- <ul className={`${baseClass}__list`}>
291
- {resources.map((leaf) => (
292
- <li key={leaf.configKey}>
293
- <CheckboxInput
294
- checked={isFlatAllowed('resources', leaf.configKey)}
295
- id={`${path}.resources.${leaf.configKey}`}
296
- label={leaf.label}
297
- onToggle={(e) => toggleFlat('resources', leaf.configKey, e.target.checked)}
298
- tooltip={leaf.description}
299
- />
300
- </li>
301
- ))}
302
- </ul>
303
- </Collapsible>
214
+ </div>
215
+ ))}
216
+ </div>
217
+ </Collapsible>
218
+ )
219
+ }
220
+
221
+ const renderScope = (scope: ScopeKey, bySlug: Record<string, ClientItem[]>) =>
222
+ Object.entries(bySlug).map(([slug, leaves]) =>
223
+ renderCard({
224
+ id: `${path}.${scope}.${slug}`,
225
+ isLeafAllowed: (leaf) => isScopedAllowed(scope, slug, leaf.configKey),
226
+ label: titleCase(slug),
227
+ leaves,
228
+ onSetAll: (allow) => setAllScoped(scope, slug, leaves, allow),
229
+ onToggleLeaf: (leaf, allow) => toggleScoped(scope, slug, leaf.configKey, allow),
230
+ }),
231
+ )
232
+
233
+ const renderFlat = (scope: FlatKey, label: string, leaves: ClientItem[]) =>
234
+ leaves.length > 0 &&
235
+ renderCard({
236
+ id: `${path}.${scope}`,
237
+ isLeafAllowed: (leaf) => isFlatAllowed(scope, leaf.configKey),
238
+ label,
239
+ leaves,
240
+ onSetAll: (allow) => setAllFlat(scope, leaves, allow),
241
+ onToggleLeaf: (leaf, allow) => toggleFlat(scope, leaf.configKey, allow),
242
+ })
243
+
244
+ const tabs: Array<{ key: TabKey; label: string }> = [
245
+ ...(Object.keys(collectionsBySlug).length > 0
246
+ ? [{ key: 'collections' as const, label: t('general:collections') }]
247
+ : []),
248
+ ...(Object.keys(globalsBySlug).length > 0
249
+ ? [{ key: 'globals' as const, label: t('general:globals') }]
250
+ : []),
251
+ ...(prompts.length > 0 || resources.length > 0 || tools.length > 0
252
+ ? [{ key: 'server' as const, label: t('plugin-mcp:server') }]
253
+ : []),
254
+ ]
255
+ if (tabs.length === 0) {
256
+ return null
257
+ }
258
+
259
+ const currentTab = activeTab ?? tabs[0]!.key
260
+
261
+ return (
262
+ <div className={baseClass}>
263
+ <header className={`${baseClass}__header`}>
264
+ <h4>{t('plugin-mcp:permissions')}</h4>
265
+ <p>{t('plugin-mcp:permissionsDescription')}</p>
266
+ </header>
267
+ <div className={`${baseClass}__tabbed-content`}>
268
+ <Tabs
269
+ className={`${baseClass}__tabs`}
270
+ onChange={setActiveTab}
271
+ tabs={tabs.map((tab) => ({
272
+ label: tab.label,
273
+ value: tab.key,
274
+ }))}
275
+ value={currentTab}
276
+ />
277
+ <div className={`${baseClass}__cards`}>
278
+ {currentTab === 'collections' && renderScope('collections', collectionsBySlug)}
279
+ {currentTab === 'globals' && renderScope('globals', globalsBySlug)}
280
+ {currentTab === 'server' && (
281
+ <>
282
+ {renderFlat('prompts', t('plugin-mcp:prompts'), prompts)}
283
+ {renderFlat('resources', t('plugin-mcp:resources'), resources)}
284
+ {renderFlat('tools', t('plugin-mcp:tools'), tools)}
285
+ </>
304
286
  )}
305
- </section>
306
- )}
287
+ </div>
288
+ </div>
307
289
  </div>
308
290
  )
309
291
  }
310
292
 
311
- const GroupActions: React.FC<{ onSetAll: (allow: boolean) => void }> = ({ onSetAll }) => (
312
- // TODO: button labels + aria-labels need i18n once design is finalized
313
- <div className={`${baseClass}__group-actions`}>
314
- <button
315
- aria-label="Select all"
316
- className={`${baseClass}__action`}
317
- onClick={(e) => {
318
- e.stopPropagation()
319
- onSetAll(true)
320
- }}
321
- title="Select all"
322
- type="button"
323
- >
324
- all
325
- </button>
326
- <span aria-hidden className={`${baseClass}__action-sep`}>
327
- /
328
- </span>
329
- <button
330
- aria-label="Clear all"
331
- className={`${baseClass}__action`}
332
- onClick={(e) => {
333
- e.stopPropagation()
334
- onSetAll(false)
335
- }}
336
- title="Clear all"
337
- type="button"
338
- >
339
- none
340
- </button>
341
- </div>
342
- )
343
-
344
293
  const titleCase = (slug: string): string =>
345
294
  slug.replace(/(^|[-_])(.)/g, (_, sep: string, ch: string) =>
346
295
  sep ? ` ${ch.toUpperCase()}` : ch.toUpperCase(),
@@ -3,32 +3,22 @@
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  gap: var(--base);
6
+ /* Breathing room below the API-key section's divider - the fields
7
+ container itself adds no gap between these two fields. */
8
+ padding-block-start: var(--spacer-4);
9
+ /* This custom field renders straight into `render-fields` (no `.field-type`
10
+ wrapper), so it must carry the standard inter-field margin itself -
11
+ otherwise the next field (e.g. an overrideApiKeyCollection field) butts
12
+ against the last card. */
13
+ margin-block-end: var(--spacer-3);
6
14
  }
7
15
 
8
- .mcp-access-field__section {
9
- display: flex;
10
- flex-direction: column;
11
- gap: calc(var(--base) / 2);
12
- padding-bottom: var(--base);
13
- /* Full-bleed separator: extend past the sidebar's horizontal gutter so the
14
- border-block-end spans the entire sidebar width. */
15
- margin-inline-start: calc(-1 * var(--sidebar-gutter-h-left, var(--gutter-h)));
16
- margin-inline-end: calc(-1 * var(--sidebar-gutter-h-right, var(--gutter-h)));
17
- padding-inline-start: var(--sidebar-gutter-h-left, var(--gutter-h));
18
- padding-inline-end: var(--sidebar-gutter-h-right, var(--gutter-h));
19
- border-block-end: 1px solid var(--theme-elevation-100);
20
- }
21
-
22
- .mcp-access-field__section:last-child {
23
- border-block-end: none;
24
- }
25
-
26
- .mcp-access-field__section-header {
16
+ .mcp-access-field__header {
27
17
  display: flex;
28
18
  flex-direction: column;
29
19
  }
30
20
 
31
- .mcp-access-field__section-header h4 {
21
+ .mcp-access-field__header h4 {
32
22
  margin: 0;
33
23
  color: var(--color-text);
34
24
  font-family: var(--text-heading-small-font-family);
@@ -38,7 +28,7 @@
38
28
  letter-spacing: var(--text-heading-small-letter-spacing);
39
29
  }
40
30
 
41
- .mcp-access-field__section-header p {
31
+ .mcp-access-field__header p {
42
32
  margin: 0;
43
33
  color: var(--color-text-secondary);
44
34
  font-family: var(--text-body-medium-font-family);
@@ -48,38 +38,46 @@
48
38
  letter-spacing: var(--text-body-medium-letter-spacing);
49
39
  }
50
40
 
51
- .mcp-access-field__group {
52
- margin-block-end: calc(var(--base) / 2);
41
+ .mcp-access-field__tabbed-content {
42
+ display: flex;
43
+ flex-direction: column;
53
44
  }
54
45
 
55
- .mcp-access-field__group-label {
56
- font-weight: 600;
46
+ .mcp-access-field__cards {
47
+ display: flex;
48
+ flex-direction: column;
49
+ gap: calc(var(--base) / 2);
57
50
  }
58
51
 
59
- .mcp-access-field__group-actions {
60
- display: flex;
61
- align-items: center;
62
- gap: calc(var(--base) / 6);
52
+ /* The collapsible header is pointer-events: none so clicks reach the toggle
53
+ overlay; re-enable events on the checkbox and lift it above the toggle. */
54
+ .mcp-access-field__card-checkbox {
55
+ position: relative;
56
+ z-index: 1;
57
+ display: inline-flex;
58
+ pointer-events: auto;
63
59
  }
64
60
 
65
- .mcp-access-field__action {
66
- appearance: none;
67
- background: none;
68
- border: none;
69
- color: var(--theme-elevation-500);
70
- cursor: pointer;
71
- font-size: 0.75rem;
72
- padding: 0;
61
+ .mcp-access-field__leaf-group {
62
+ padding-block: calc(var(--base) / 2);
63
+ border-block-end: 1px solid var(--theme-elevation-100);
64
+ }
65
+
66
+ .mcp-access-field__leaf-group:first-child {
67
+ padding-block-start: 0;
73
68
  }
74
69
 
75
- .mcp-access-field__action:hover {
76
- color: var(--theme-text);
77
- text-decoration: underline;
70
+ .mcp-access-field__leaf-group:last-child {
71
+ border-block-end: none;
72
+ padding-block-end: 0;
78
73
  }
79
74
 
80
- .mcp-access-field__action-sep {
81
- color: var(--theme-elevation-300);
82
- font-size: 0.75rem;
75
+ .mcp-access-field__leaf-group-label {
76
+ margin: 0 0 calc(var(--base) / 2);
77
+ color: var(--color-text-secondary);
78
+ font-family: var(--text-body-medium-font-family);
79
+ font-size: var(--text-body-medium-font-size);
80
+ line-height: var(--text-body-medium-line-height);
83
81
  }
84
82
 
85
83
  .mcp-access-field__list {
@@ -88,6 +86,14 @@
88
86
  padding: 0;
89
87
  display: flex;
90
88
  flex-direction: column;
91
- gap: calc(var(--base) / 3);
89
+ gap: calc(var(--base) / 2);
90
+ }
91
+
92
+ .mcp-access-field__leaf-description {
93
+ margin: calc(var(--base) / 6) 0 0;
94
+ color: var(--color-text-secondary);
95
+ font-family: var(--text-body-medium-font-family);
96
+ font-size: var(--text-body-medium-font-size);
97
+ line-height: var(--text-body-medium-line-height);
92
98
  }
93
99
  }
@@ -0,0 +1,30 @@
1
+ 'use client'
2
+
3
+ import { PopupList, useConfig, useTranslation } from '@payloadcms/ui'
4
+ import { formatAdminURL } from 'payload/shared'
5
+ import React from 'react'
6
+
7
+ import type { PluginMCPTranslationKeys, PluginMCPTranslations } from '../../translations/index.js'
8
+
9
+ /**
10
+ * Entry in the user menu's Settings sub-popup linking to the API keys
11
+ * collection. The collection is excluded from the main nav
12
+ * (`admin.group: false`) and managed from here instead.
13
+ */
14
+ export const MCPSettingsMenu: React.FC = () => {
15
+ const { config } = useConfig()
16
+ const { t } = useTranslation<PluginMCPTranslations, PluginMCPTranslationKeys>()
17
+
18
+ return (
19
+ <PopupList.MenuItem>
20
+ <PopupList.Button
21
+ href={formatAdminURL({
22
+ adminRoute: config.routes.admin,
23
+ path: '/collections/payload-mcp-api-keys',
24
+ })}
25
+ >
26
+ {t('plugin-mcp:manageAPIKeys')}
27
+ </PopupList.Button>
28
+ </PopupList.MenuItem>
29
+ )
30
+ }
@@ -121,6 +121,18 @@ const getAPIKeyDoc = async ({
121
121
  throw new UnauthorizedError()
122
122
  }
123
123
 
124
+ try {
125
+ await req.payload.db.updateOne({
126
+ id: doc.id,
127
+ collection: 'payload-mcp-api-keys',
128
+ data: { lastUsed: new Date().toISOString() },
129
+ req,
130
+ returning: false,
131
+ })
132
+ } catch (err) {
133
+ logger.warn({ err, msg: 'Failed to update MCP API key last-used timestamp' })
134
+ }
135
+
124
136
  return {
125
137
  ...doc,
126
138
  user: {
@@ -1,2 +1,5 @@
1
1
  'use client'
2
2
  export { AccessField } from '../components/AccessField/index.client.js'
3
+ export { APIKeyField } from '../components/APIKeyField/index.client.js'
4
+ export { APIKeysEmptyState } from '../components/APIKeysEmptyState/index.client.js'
5
+ export { MCPSettingsMenu } from '../components/SettingsMenu/index.client.js'