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

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 (278) 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 +29 -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 +46 -3
  32. package/dist/index.js.map +1 -1
  33. package/dist/translations/index.d.ts +11 -0
  34. package/dist/translations/index.d.ts.map +1 -0
  35. package/dist/translations/index.js +92 -0
  36. package/dist/translations/index.js.map +1 -0
  37. package/dist/translations/languages/ar.d.ts +31 -0
  38. package/dist/translations/languages/ar.d.ts.map +1 -0
  39. package/dist/translations/languages/ar.js +34 -0
  40. package/dist/translations/languages/ar.js.map +1 -0
  41. package/dist/translations/languages/az.d.ts +31 -0
  42. package/dist/translations/languages/az.d.ts.map +1 -0
  43. package/dist/translations/languages/az.js +34 -0
  44. package/dist/translations/languages/az.js.map +1 -0
  45. package/dist/translations/languages/bg.d.ts +31 -0
  46. package/dist/translations/languages/bg.d.ts.map +1 -0
  47. package/dist/translations/languages/bg.js +34 -0
  48. package/dist/translations/languages/bg.js.map +1 -0
  49. package/dist/translations/languages/bnBd.d.ts +31 -0
  50. package/dist/translations/languages/bnBd.d.ts.map +1 -0
  51. package/dist/translations/languages/bnBd.js +34 -0
  52. package/dist/translations/languages/bnBd.js.map +1 -0
  53. package/dist/translations/languages/bnIn.d.ts +31 -0
  54. package/dist/translations/languages/bnIn.d.ts.map +1 -0
  55. package/dist/translations/languages/bnIn.js +34 -0
  56. package/dist/translations/languages/bnIn.js.map +1 -0
  57. package/dist/translations/languages/ca.d.ts +31 -0
  58. package/dist/translations/languages/ca.d.ts.map +1 -0
  59. package/dist/translations/languages/ca.js +34 -0
  60. package/dist/translations/languages/ca.js.map +1 -0
  61. package/dist/translations/languages/cs.d.ts +31 -0
  62. package/dist/translations/languages/cs.d.ts.map +1 -0
  63. package/dist/translations/languages/cs.js +34 -0
  64. package/dist/translations/languages/cs.js.map +1 -0
  65. package/dist/translations/languages/da.d.ts +31 -0
  66. package/dist/translations/languages/da.d.ts.map +1 -0
  67. package/dist/translations/languages/da.js +34 -0
  68. package/dist/translations/languages/da.js.map +1 -0
  69. package/dist/translations/languages/de.d.ts +31 -0
  70. package/dist/translations/languages/de.d.ts.map +1 -0
  71. package/dist/translations/languages/de.js +34 -0
  72. package/dist/translations/languages/de.js.map +1 -0
  73. package/dist/translations/languages/en.d.ts +31 -0
  74. package/dist/translations/languages/en.d.ts.map +1 -0
  75. package/dist/translations/languages/en.js +34 -0
  76. package/dist/translations/languages/en.js.map +1 -0
  77. package/dist/translations/languages/es.d.ts +31 -0
  78. package/dist/translations/languages/es.d.ts.map +1 -0
  79. package/dist/translations/languages/es.js +34 -0
  80. package/dist/translations/languages/es.js.map +1 -0
  81. package/dist/translations/languages/et.d.ts +31 -0
  82. package/dist/translations/languages/et.d.ts.map +1 -0
  83. package/dist/translations/languages/et.js +34 -0
  84. package/dist/translations/languages/et.js.map +1 -0
  85. package/dist/translations/languages/fa.d.ts +31 -0
  86. package/dist/translations/languages/fa.d.ts.map +1 -0
  87. package/dist/translations/languages/fa.js +34 -0
  88. package/dist/translations/languages/fa.js.map +1 -0
  89. package/dist/translations/languages/fr.d.ts +31 -0
  90. package/dist/translations/languages/fr.d.ts.map +1 -0
  91. package/dist/translations/languages/fr.js +34 -0
  92. package/dist/translations/languages/fr.js.map +1 -0
  93. package/dist/translations/languages/he.d.ts +31 -0
  94. package/dist/translations/languages/he.d.ts.map +1 -0
  95. package/dist/translations/languages/he.js +34 -0
  96. package/dist/translations/languages/he.js.map +1 -0
  97. package/dist/translations/languages/hr.d.ts +31 -0
  98. package/dist/translations/languages/hr.d.ts.map +1 -0
  99. package/dist/translations/languages/hr.js +34 -0
  100. package/dist/translations/languages/hr.js.map +1 -0
  101. package/dist/translations/languages/hu.d.ts +31 -0
  102. package/dist/translations/languages/hu.d.ts.map +1 -0
  103. package/dist/translations/languages/hu.js +34 -0
  104. package/dist/translations/languages/hu.js.map +1 -0
  105. package/dist/translations/languages/hy.d.ts +31 -0
  106. package/dist/translations/languages/hy.d.ts.map +1 -0
  107. package/dist/translations/languages/hy.js +34 -0
  108. package/dist/translations/languages/hy.js.map +1 -0
  109. package/dist/translations/languages/id.d.ts +31 -0
  110. package/dist/translations/languages/id.d.ts.map +1 -0
  111. package/dist/translations/languages/id.js +34 -0
  112. package/dist/translations/languages/id.js.map +1 -0
  113. package/dist/translations/languages/is.d.ts +31 -0
  114. package/dist/translations/languages/is.d.ts.map +1 -0
  115. package/dist/translations/languages/is.js +34 -0
  116. package/dist/translations/languages/is.js.map +1 -0
  117. package/dist/translations/languages/it.d.ts +31 -0
  118. package/dist/translations/languages/it.d.ts.map +1 -0
  119. package/dist/translations/languages/it.js +34 -0
  120. package/dist/translations/languages/it.js.map +1 -0
  121. package/dist/translations/languages/ja.d.ts +31 -0
  122. package/dist/translations/languages/ja.d.ts.map +1 -0
  123. package/dist/translations/languages/ja.js +34 -0
  124. package/dist/translations/languages/ja.js.map +1 -0
  125. package/dist/translations/languages/ko.d.ts +31 -0
  126. package/dist/translations/languages/ko.d.ts.map +1 -0
  127. package/dist/translations/languages/ko.js +34 -0
  128. package/dist/translations/languages/ko.js.map +1 -0
  129. package/dist/translations/languages/lt.d.ts +31 -0
  130. package/dist/translations/languages/lt.d.ts.map +1 -0
  131. package/dist/translations/languages/lt.js +34 -0
  132. package/dist/translations/languages/lt.js.map +1 -0
  133. package/dist/translations/languages/lv.d.ts +31 -0
  134. package/dist/translations/languages/lv.d.ts.map +1 -0
  135. package/dist/translations/languages/lv.js +34 -0
  136. package/dist/translations/languages/lv.js.map +1 -0
  137. package/dist/translations/languages/my.d.ts +31 -0
  138. package/dist/translations/languages/my.d.ts.map +1 -0
  139. package/dist/translations/languages/my.js +34 -0
  140. package/dist/translations/languages/my.js.map +1 -0
  141. package/dist/translations/languages/nb.d.ts +31 -0
  142. package/dist/translations/languages/nb.d.ts.map +1 -0
  143. package/dist/translations/languages/nb.js +34 -0
  144. package/dist/translations/languages/nb.js.map +1 -0
  145. package/dist/translations/languages/nl.d.ts +31 -0
  146. package/dist/translations/languages/nl.d.ts.map +1 -0
  147. package/dist/translations/languages/nl.js +34 -0
  148. package/dist/translations/languages/nl.js.map +1 -0
  149. package/dist/translations/languages/pl.d.ts +31 -0
  150. package/dist/translations/languages/pl.d.ts.map +1 -0
  151. package/dist/translations/languages/pl.js +34 -0
  152. package/dist/translations/languages/pl.js.map +1 -0
  153. package/dist/translations/languages/pt.d.ts +31 -0
  154. package/dist/translations/languages/pt.d.ts.map +1 -0
  155. package/dist/translations/languages/pt.js +34 -0
  156. package/dist/translations/languages/pt.js.map +1 -0
  157. package/dist/translations/languages/ro.d.ts +31 -0
  158. package/dist/translations/languages/ro.d.ts.map +1 -0
  159. package/dist/translations/languages/ro.js +34 -0
  160. package/dist/translations/languages/ro.js.map +1 -0
  161. package/dist/translations/languages/rs.d.ts +31 -0
  162. package/dist/translations/languages/rs.d.ts.map +1 -0
  163. package/dist/translations/languages/rs.js +34 -0
  164. package/dist/translations/languages/rs.js.map +1 -0
  165. package/dist/translations/languages/rsLatin.d.ts +31 -0
  166. package/dist/translations/languages/rsLatin.d.ts.map +1 -0
  167. package/dist/translations/languages/rsLatin.js +34 -0
  168. package/dist/translations/languages/rsLatin.js.map +1 -0
  169. package/dist/translations/languages/ru.d.ts +31 -0
  170. package/dist/translations/languages/ru.d.ts.map +1 -0
  171. package/dist/translations/languages/ru.js +34 -0
  172. package/dist/translations/languages/ru.js.map +1 -0
  173. package/dist/translations/languages/sk.d.ts +31 -0
  174. package/dist/translations/languages/sk.d.ts.map +1 -0
  175. package/dist/translations/languages/sk.js +34 -0
  176. package/dist/translations/languages/sk.js.map +1 -0
  177. package/dist/translations/languages/sl.d.ts +31 -0
  178. package/dist/translations/languages/sl.d.ts.map +1 -0
  179. package/dist/translations/languages/sl.js +34 -0
  180. package/dist/translations/languages/sl.js.map +1 -0
  181. package/dist/translations/languages/sv.d.ts +31 -0
  182. package/dist/translations/languages/sv.d.ts.map +1 -0
  183. package/dist/translations/languages/sv.js +34 -0
  184. package/dist/translations/languages/sv.js.map +1 -0
  185. package/dist/translations/languages/ta.d.ts +31 -0
  186. package/dist/translations/languages/ta.d.ts.map +1 -0
  187. package/dist/translations/languages/ta.js +34 -0
  188. package/dist/translations/languages/ta.js.map +1 -0
  189. package/dist/translations/languages/th.d.ts +31 -0
  190. package/dist/translations/languages/th.d.ts.map +1 -0
  191. package/dist/translations/languages/th.js +34 -0
  192. package/dist/translations/languages/th.js.map +1 -0
  193. package/dist/translations/languages/tr.d.ts +31 -0
  194. package/dist/translations/languages/tr.d.ts.map +1 -0
  195. package/dist/translations/languages/tr.js +34 -0
  196. package/dist/translations/languages/tr.js.map +1 -0
  197. package/dist/translations/languages/uk.d.ts +31 -0
  198. package/dist/translations/languages/uk.d.ts.map +1 -0
  199. package/dist/translations/languages/uk.js +34 -0
  200. package/dist/translations/languages/uk.js.map +1 -0
  201. package/dist/translations/languages/vi.d.ts +31 -0
  202. package/dist/translations/languages/vi.d.ts.map +1 -0
  203. package/dist/translations/languages/vi.js +34 -0
  204. package/dist/translations/languages/vi.js.map +1 -0
  205. package/dist/translations/languages/zh.d.ts +31 -0
  206. package/dist/translations/languages/zh.d.ts.map +1 -0
  207. package/dist/translations/languages/zh.js +34 -0
  208. package/dist/translations/languages/zh.js.map +1 -0
  209. package/dist/translations/languages/zhTw.d.ts +31 -0
  210. package/dist/translations/languages/zhTw.d.ts.map +1 -0
  211. package/dist/translations/languages/zhTw.js +34 -0
  212. package/dist/translations/languages/zhTw.js.map +1 -0
  213. package/dist/translations/types.d.ts +32 -0
  214. package/dist/translations/types.d.ts.map +1 -0
  215. package/dist/translations/types.js +3 -0
  216. package/dist/translations/types.js.map +1 -0
  217. package/dist/types.d.ts +6 -1
  218. package/dist/types.d.ts.map +1 -1
  219. package/dist/types.js.map +1 -1
  220. package/package.json +7 -6
  221. package/src/collection/getAccessField.ts +33 -13
  222. package/src/collection/index.ts +63 -22
  223. package/src/components/APIKeyField/index.client.tsx +73 -0
  224. package/src/components/APIKeyField/index.css +105 -0
  225. package/src/components/APIKeysEmptyState/index.client.tsx +36 -0
  226. package/src/components/AccessField/index.client.tsx +163 -211
  227. package/src/components/AccessField/index.css +50 -44
  228. package/src/components/SettingsMenu/index.client.tsx +34 -0
  229. package/src/endpoint/access.ts +12 -0
  230. package/src/exports/client.ts +3 -0
  231. package/src/index.ts +49 -3
  232. package/src/translations/index.ts +108 -0
  233. package/src/translations/languages/ar.ts +35 -0
  234. package/src/translations/languages/az.ts +35 -0
  235. package/src/translations/languages/bg.ts +35 -0
  236. package/src/translations/languages/bnBd.ts +35 -0
  237. package/src/translations/languages/bnIn.ts +35 -0
  238. package/src/translations/languages/ca.ts +35 -0
  239. package/src/translations/languages/cs.ts +35 -0
  240. package/src/translations/languages/da.ts +35 -0
  241. package/src/translations/languages/de.ts +35 -0
  242. package/src/translations/languages/en.ts +35 -0
  243. package/src/translations/languages/es.ts +35 -0
  244. package/src/translations/languages/et.ts +35 -0
  245. package/src/translations/languages/fa.ts +35 -0
  246. package/src/translations/languages/fr.ts +35 -0
  247. package/src/translations/languages/he.ts +35 -0
  248. package/src/translations/languages/hr.ts +35 -0
  249. package/src/translations/languages/hu.ts +35 -0
  250. package/src/translations/languages/hy.ts +35 -0
  251. package/src/translations/languages/id.ts +35 -0
  252. package/src/translations/languages/is.ts +35 -0
  253. package/src/translations/languages/it.ts +35 -0
  254. package/src/translations/languages/ja.ts +35 -0
  255. package/src/translations/languages/ko.ts +35 -0
  256. package/src/translations/languages/lt.ts +35 -0
  257. package/src/translations/languages/lv.ts +35 -0
  258. package/src/translations/languages/my.ts +35 -0
  259. package/src/translations/languages/nb.ts +35 -0
  260. package/src/translations/languages/nl.ts +35 -0
  261. package/src/translations/languages/pl.ts +35 -0
  262. package/src/translations/languages/pt.ts +35 -0
  263. package/src/translations/languages/ro.ts +35 -0
  264. package/src/translations/languages/rs.ts +35 -0
  265. package/src/translations/languages/rsLatin.ts +35 -0
  266. package/src/translations/languages/ru.ts +35 -0
  267. package/src/translations/languages/sk.ts +35 -0
  268. package/src/translations/languages/sl.ts +35 -0
  269. package/src/translations/languages/sv.ts +35 -0
  270. package/src/translations/languages/ta.ts +35 -0
  271. package/src/translations/languages/th.ts +35 -0
  272. package/src/translations/languages/tr.ts +35 -0
  273. package/src/translations/languages/uk.ts +35 -0
  274. package/src/translations/languages/vi.ts +35 -0
  275. package/src/translations/languages/zh.ts +35 -0
  276. package/src/translations/languages/zhTw.ts +35 -0
  277. package/src/translations/types.ts +34 -0
  278. package/src/types.ts +6 -1
@@ -0,0 +1,73 @@
1
+ 'use client'
2
+
3
+ import { APIKeyInput, Button, useField, useTranslation, WarningTriangleIcon } from '@payloadcms/ui'
4
+ import React, { useState } from 'react'
5
+
6
+ import type {
7
+ PluginMCPTranslationKeys,
8
+ PluginMCPTranslations,
9
+ } from '../../translations/index.js'
10
+
11
+ import './index.css'
12
+
13
+ const baseClass = 'mcp-api-key-field'
14
+
15
+ /**
16
+ * Custom component for the MCP API-keys collection's `apiKey` field:
17
+ * - no key yet: a "Generate new key" button
18
+ * - key set: a dismissible privacy warning + the shared masked-key input
19
+ */
20
+ export const APIKeyField: React.FC = () => {
21
+ const { setValue: setApiKey, value: apiKey } = useField<string>({ path: 'apiKey' })
22
+ const { t } = useTranslation<PluginMCPTranslations, PluginMCPTranslationKeys>()
23
+ const [isWarningDismissed, setIsWarningDismissed] = useState(false)
24
+
25
+ const generateKey = () => {
26
+ setApiKey(crypto.randomUUID())
27
+ setIsWarningDismissed(false)
28
+ }
29
+
30
+ return (
31
+ <div className={baseClass}>
32
+ <p className={`${baseClass}__description`}>
33
+ {t('plugin-mcp:apiKeyDescription')}
34
+ </p>
35
+ <div className={`${baseClass}__panel`}>
36
+ <div className={`${baseClass}__header`}>
37
+ <span className={`${baseClass}__title`}>{t('authentication:apiKey')}</span>
38
+ {!apiKey && (
39
+ <Button
40
+ buttonStyle="primary"
41
+ className={`${baseClass}__generate`}
42
+ onClick={generateKey}
43
+ >
44
+ {t('authentication:generateNewAPIKey')}
45
+ </Button>
46
+ )}
47
+ </div>
48
+ {Boolean(apiKey) && (
49
+ <div className={`${baseClass}__body`}>
50
+ {!isWarningDismissed && (
51
+ <div className={`${baseClass}__warning`}>
52
+ <WarningTriangleIcon className={`${baseClass}__warning-icon`} />
53
+ <p className={`${baseClass}__warning-text`}>
54
+ <strong>{t('plugin-mcp:keepKeyPrivate')}</strong>
55
+ {` ${t('plugin-mcp:keyPrivateDescription')}`}
56
+ </p>
57
+ <button
58
+ aria-label={t('plugin-mcp:dismiss')}
59
+ className={`${baseClass}__warning-dismiss`}
60
+ onClick={() => setIsWarningDismissed(true)}
61
+ type="button"
62
+ >
63
+ &times;
64
+ </button>
65
+ </div>
66
+ )}
67
+ <APIKeyInput aria-label={t('authentication:apiKey')} value={apiKey} />
68
+ </div>
69
+ )}
70
+ </div>
71
+ </div>
72
+ )
73
+ }
@@ -0,0 +1,105 @@
1
+ @layer payload-default {
2
+ /* Section wrapper: description + panel, closed off by a full-bleed divider
3
+ that spans the fields column past its horizontal gutter. */
4
+ .mcp-api-key-field {
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: var(--base);
8
+ margin-inline: calc(-1 * var(--gutter-h));
9
+ padding-inline: var(--gutter-h);
10
+ padding-block-end: var(--spacer-4);
11
+ border-block-end: var(--stroke-width-small) solid var(--color-border);
12
+ }
13
+
14
+ .mcp-api-key-field__description {
15
+ margin: 0;
16
+ color: var(--color-text);
17
+ font-family: var(--text-body-medium-font-family);
18
+ font-size: var(--text-body-medium-font-size);
19
+ font-weight: var(--text-body-medium-font-weight);
20
+ line-height: var(--text-body-medium-line-height);
21
+ letter-spacing: var(--text-body-medium-letter-spacing);
22
+ }
23
+
24
+ .mcp-api-key-field__panel {
25
+ overflow: hidden;
26
+ background: var(--color-bg);
27
+ border: var(--stroke-width-small) solid var(--color-border);
28
+ border-radius: var(--button-radius);
29
+ }
30
+
31
+ .mcp-api-key-field__header {
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: space-between;
35
+ gap: var(--spacer-2);
36
+ min-height: var(--spacer-5);
37
+ padding: var(--spacer-2) var(--spacer-3);
38
+ background: var(--color-bg-secondary);
39
+ }
40
+
41
+ /* Holds the warning + key row; its top border is the full-width divider
42
+ under the header. Only rendered once a key exists. */
43
+ .mcp-api-key-field__body {
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: var(--spacer-2);
47
+ padding: var(--spacer-3);
48
+ border-block-start: var(--stroke-width-small) solid var(--color-border);
49
+ }
50
+
51
+ .mcp-api-key-field__title {
52
+ color: var(--color-text);
53
+ font-family: var(--text-body-medium-strong-font-family);
54
+ font-size: var(--text-body-medium-strong-font-size);
55
+ font-weight: var(--text-body-medium-strong-font-weight);
56
+ line-height: var(--text-body-medium-strong-line-height);
57
+ }
58
+
59
+ .mcp-api-key-field__generate {
60
+ margin: 0;
61
+ }
62
+
63
+ /* Matches the core Banner's warning treatment: theme-adaptive warning-tertiary
64
+ bg with regular --color-text (NOT --color-text-onwarning, which is only
65
+ defined for light mode and reads as muddy dark-on-dark in dark mode). */
66
+ .mcp-api-key-field__warning {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: calc(var(--base) / 2);
70
+ padding: calc(var(--spacer-2) + (var(--spacer-1) / 2)) calc(var(--base) / 2);
71
+ background: var(--color-bg-warning-tertiary);
72
+ border: none;
73
+ border-radius: var(--radius-medium);
74
+ color: var(--color-text);
75
+ }
76
+
77
+ .mcp-api-key-field__warning-icon {
78
+ flex-shrink: 0;
79
+ width: 15px;
80
+ height: 13px;
81
+ }
82
+
83
+ .mcp-api-key-field__warning-text {
84
+ flex-grow: 1;
85
+ margin: 0;
86
+ font-family: var(--text-body-medium-font-family);
87
+ font-size: var(--text-body-medium-font-size);
88
+ line-height: var(--text-body-medium-line-height);
89
+ }
90
+
91
+ .mcp-api-key-field__warning-text strong {
92
+ font-weight: var(--text-body-medium-strong-font-weight);
93
+ }
94
+
95
+ .mcp-api-key-field__warning-dismiss {
96
+ appearance: none;
97
+ background: none;
98
+ border: none;
99
+ cursor: pointer;
100
+ color: inherit;
101
+ font-size: 1rem;
102
+ line-height: 1;
103
+ padding: 0;
104
+ }
105
+ }
@@ -0,0 +1,36 @@
1
+ 'use client'
2
+
3
+ import type { NoResultsClientProps } from 'payload'
4
+
5
+ import { Button, NoListResults, useTranslation } from '@payloadcms/ui'
6
+ import React from 'react'
7
+
8
+ import type {
9
+ PluginMCPTranslationKeys,
10
+ PluginMCPTranslations,
11
+ } from '../../translations/index.js'
12
+
13
+ export const APIKeysEmptyState: React.FC<NoResultsClientProps> = ({
14
+ hasCreatePermission,
15
+ newDocumentURL,
16
+ viewType,
17
+ }) => {
18
+ const { t } = useTranslation<PluginMCPTranslations, PluginMCPTranslationKeys>()
19
+
20
+ return (
21
+ <NoListResults
22
+ Actions={
23
+ hasCreatePermission && newDocumentURL && viewType !== 'trash'
24
+ ? [
25
+ <Button el="link" key="create" to={newDocumentURL}>
26
+ {t('authentication:generateNewAPIKey')}
27
+ </Button>,
28
+ ]
29
+ : []
30
+ }
31
+ description={t('plugin-mcp:apiKeyDescription')}
32
+ title={t('plugin-mcp:noAPIKeys')}
33
+ withMargin
34
+ />
35
+ )
36
+ }
@@ -2,9 +2,13 @@
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 {
9
+ PluginMCPTranslationKeys,
10
+ PluginMCPTranslations,
11
+ } from '../../translations/index.js'
8
12
  import type { ClientMCPPluginConfig, MCPAPIKeysDocAccessTree } from '../../types.js'
9
13
 
10
14
  import './index.css'
@@ -14,6 +18,11 @@ const baseClass = 'mcp-access-field'
14
18
  type ClientItem = ClientMCPPluginConfig['items'][number]
15
19
  type ScopeKey = 'collections' | 'globals'
16
20
  type FlatKey = 'prompts' | 'resources' | 'tools'
21
+ type TabKey = 'collections' | 'globals' | 'server'
22
+
23
+ const LEAF_GROUP_KEYS = ['operations', 'auth', 'custom'] as const
24
+
25
+ type LeafGroupKey = (typeof LEAF_GROUP_KEYS)[number]
17
26
 
18
27
  type Props = {
19
28
  pluginConfig: ClientMCPPluginConfig
@@ -39,15 +48,21 @@ const setKey = <T extends Record<string, unknown>>(
39
48
 
40
49
  export const AccessField: React.FC<Props> = ({ path, pluginConfig }) => {
41
50
  const { setValue, value } = useField<MCPAPIKeysDocAccessTree>({ path })
51
+ const { t } = useTranslation<PluginMCPTranslations, PluginMCPTranslationKeys>()
52
+ const [activeTab, setActiveTab] = useState<null | TabKey>(null)
42
53
  const access = value ?? {}
54
+ const leafGroupLabels: Record<LeafGroupKey, string> = {
55
+ auth: t('plugin-mcp:authentication'),
56
+ custom: t('general:custom'),
57
+ operations: t('plugin-mcp:operations'),
58
+ }
43
59
 
44
- // Bucket items for rendering. (Bucketing is cheap and runs once per render;
45
- // memoizing would mean managing inputs/refs for marginal benefit.)
46
60
  const collectionsBySlug: Record<string, ClientItem[]> = {}
47
61
  const globalsBySlug: Record<string, ClientItem[]> = {}
48
- const tools: ClientItem[] = []
49
62
  const prompts: ClientItem[] = []
50
63
  const resources: ClientItem[] = []
64
+ const tools: ClientItem[] = []
65
+
51
66
  for (const item of pluginConfig.items) {
52
67
  switch (item.type) {
53
68
  case 'collectionTool':
@@ -126,221 +141,158 @@ export const AccessField: React.FC<Props> = ({ path, pluginConfig }) => {
126
141
  }
127
142
  }
128
143
 
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
- )}
144
+ const renderLeaf = (
145
+ leaf: ClientItem,
146
+ id: string,
147
+ checked: boolean,
148
+ onToggle: (allow: boolean) => void,
149
+ ) => (
150
+ <li className={`${baseClass}__leaf`} key={leaf.configKey}>
151
+ <CheckboxInput
152
+ checked={checked}
153
+ id={id}
154
+ label={leaf.label}
155
+ onToggle={(e) => onToggle(e.target.checked)}
156
+ />
157
+ {leaf.description && <p className={`${baseClass}__leaf-description`}>{leaf.description}</p>}
158
+ </li>
159
+ )
176
160
 
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
- )}
161
+ const renderCard = ({
162
+ id,
163
+ isLeafAllowed,
164
+ label,
165
+ leaves,
166
+ onSetAll,
167
+ onToggleLeaf,
168
+ }: {
169
+ id: string
170
+ isLeafAllowed: (leaf: ClientItem) => boolean
171
+ label: string
172
+ leaves: ClientItem[]
173
+ onSetAll: (allow: boolean) => void
174
+ onToggleLeaf: (leaf: ClientItem, allow: boolean) => void
175
+ }) => {
176
+ const allowedCount = leaves.filter(isLeafAllowed).length
177
+ const groups = LEAF_GROUP_KEYS.map((key) => ({
178
+ key,
179
+ label: leafGroupLabels[key],
180
+ leaves: leaves.filter((leaf) => (leaf.group ?? 'custom') === key),
181
+ })).filter((group) => group.leaves.length > 0)
182
+ const hasGroupLabels = groups.length > 1
219
183
 
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
- >
184
+ return (
185
+ <Collapsible
186
+ className={`${baseClass}__card`}
187
+ header={
188
+ // Keep header clicks on the checkbox from also toggling the collapsible.
189
+ <span
190
+ className={`${baseClass}__card-checkbox`}
191
+ onClick={(e) => e.stopPropagation()}
192
+ role="presentation"
193
+ >
194
+ <CheckboxInput
195
+ checked={allowedCount === leaves.length}
196
+ id={`${id}._all`}
197
+ label={label}
198
+ onToggle={() => onSetAll(allowedCount < leaves.length)}
199
+ partialChecked={allowedCount > 0 && allowedCount < leaves.length}
200
+ />
201
+ </span>
202
+ }
203
+ initCollapsed
204
+ key={id}
205
+ >
206
+ <div className={`${baseClass}__card-groups`}>
207
+ {groups.map((group) => (
208
+ <div className={`${baseClass}__leaf-group`} key={group.key}>
209
+ {hasGroupLabels && <p className={`${baseClass}__leaf-group-label`}>{group.label}</p>}
263
210
  <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
- ))}
211
+ {group.leaves.map((leaf) =>
212
+ renderLeaf(leaf, `${id}.${leaf.configKey}`, isLeafAllowed(leaf), (allow) =>
213
+ onToggleLeaf(leaf, allow),
214
+ ),
215
+ )}
275
216
  </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>
217
+ </div>
218
+ ))}
219
+ </div>
220
+ </Collapsible>
221
+ )
222
+ }
223
+
224
+ const renderScope = (scope: ScopeKey, bySlug: Record<string, ClientItem[]>) =>
225
+ Object.entries(bySlug).map(([slug, leaves]) =>
226
+ renderCard({
227
+ id: `${path}.${scope}.${slug}`,
228
+ isLeafAllowed: (leaf) => isScopedAllowed(scope, slug, leaf.configKey),
229
+ label: titleCase(slug),
230
+ leaves,
231
+ onSetAll: (allow) => setAllScoped(scope, slug, leaves, allow),
232
+ onToggleLeaf: (leaf, allow) => toggleScoped(scope, slug, leaf.configKey, allow),
233
+ }),
234
+ )
235
+
236
+ const renderFlat = (scope: FlatKey, label: string, leaves: ClientItem[]) =>
237
+ leaves.length > 0 &&
238
+ renderCard({
239
+ id: `${path}.${scope}`,
240
+ isLeafAllowed: (leaf) => isFlatAllowed(scope, leaf.configKey),
241
+ label,
242
+ leaves,
243
+ onSetAll: (allow) => setAllFlat(scope, leaves, allow),
244
+ onToggleLeaf: (leaf, allow) => toggleFlat(scope, leaf.configKey, allow),
245
+ })
246
+
247
+ const tabs: Array<{ key: TabKey; label: string }> = [
248
+ ...(Object.keys(collectionsBySlug).length > 0
249
+ ? [{ key: 'collections' as const, label: t('general:collections') }]
250
+ : []),
251
+ ...(Object.keys(globalsBySlug).length > 0
252
+ ? [{ key: 'globals' as const, label: t('general:globals') }]
253
+ : []),
254
+ ...(prompts.length > 0 || resources.length > 0 || tools.length > 0
255
+ ? [{ key: 'server' as const, label: t('plugin-mcp:server') }]
256
+ : []),
257
+ ]
258
+ if (tabs.length === 0) {
259
+ return null
260
+ }
261
+
262
+ const currentTab = activeTab ?? tabs[0]!.key
263
+
264
+ return (
265
+ <div className={baseClass}>
266
+ <header className={`${baseClass}__header`}>
267
+ <h4>{t('plugin-mcp:permissions')}</h4>
268
+ <p>{t('plugin-mcp:permissionsDescription')}</p>
269
+ </header>
270
+ <div className={`${baseClass}__tabbed-content`}>
271
+ <Tabs
272
+ className={`${baseClass}__tabs`}
273
+ onChange={setActiveTab}
274
+ tabs={tabs.map((tab) => ({
275
+ label: tab.label,
276
+ value: tab.key,
277
+ }))}
278
+ value={currentTab}
279
+ />
280
+ <div className={`${baseClass}__cards`}>
281
+ {currentTab === 'collections' && renderScope('collections', collectionsBySlug)}
282
+ {currentTab === 'globals' && renderScope('globals', globalsBySlug)}
283
+ {currentTab === 'server' && (
284
+ <>
285
+ {renderFlat('prompts', t('plugin-mcp:prompts'), prompts)}
286
+ {renderFlat('resources', t('plugin-mcp:resources'), resources)}
287
+ {renderFlat('tools', t('plugin-mcp:tools'), tools)}
288
+ </>
304
289
  )}
305
- </section>
306
- )}
290
+ </div>
291
+ </div>
307
292
  </div>
308
293
  )
309
294
  }
310
295
 
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
296
  const titleCase = (slug: string): string =>
345
297
  slug.replace(/(^|[-_])(.)/g, (_, sep: string, ch: string) =>
346
298
  sep ? ` ${ch.toUpperCase()}` : ch.toUpperCase(),